@harness-mix/cli 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/NOTICE +12 -0
  3. package/README.md +74 -71
  4. package/docs/harness-management.md +1 -1
  5. package/docs/native-acp.md +7 -2
  6. package/docs/task-handoff-design.md +65 -0
  7. package/licenses/codex-styler-Apache-2.0.txt +193 -0
  8. package/licenses/heige-codex-skin-studio-MIT.txt +22 -0
  9. package/output/native-build/desktop-controller.mjs +3 -2
  10. package/output/native-build/renderer-extension.js +1350 -46
  11. package/package.json +189 -188
  12. package/scripts/acp-capabilities-probe.cjs +1 -0
  13. package/scripts/build-native.cjs +1 -1
  14. package/scripts/codex-adapter-test.cjs +44 -0
  15. package/scripts/collaboration-recovery-test.cjs +3 -0
  16. package/scripts/e2e-codex.cjs +3 -1
  17. package/scripts/e2e-native-acp.cjs +1 -1
  18. package/scripts/error-kind-test.cjs +107 -0
  19. package/scripts/handoff-checkpoint-test.cjs +38 -0
  20. package/scripts/integrations-test.cjs +5 -2
  21. package/scripts/kiro-cursor-adapters-test.cjs +18 -0
  22. package/scripts/native-acp-depth-test.cjs +1 -1
  23. package/scripts/native-config-test.cjs +32 -2
  24. package/scripts/native-protocol-test.cjs +60 -4
  25. package/scripts/native-ui-smoke.cjs +127 -2
  26. package/scripts/pets-test.cjs +146 -0
  27. package/scripts/switch-harness-test.cjs +79 -12
  28. package/scripts/tool-concurrency-regression-test.cjs +8 -6
  29. package/src/assets/icons/cline-color.svg +1 -0
  30. package/src/assets/skins/codex-styler/ASSET_LICENSES.md +36 -0
  31. package/src/assets/skins/codex-styler/NOTICE +11 -0
  32. package/src/assets/skins/codex-styler/themes/gilded-grandeur/hero.webp +0 -0
  33. package/src/assets/skins/codex-styler/themes/merry-big-top/hero.webp +0 -0
  34. package/src/assets/skins/codex-styler/themes/nocturne-studio/hero.webp +0 -0
  35. package/src/assets/skins/codex-styler/themes/quiet-garden/hero.webp +0 -0
  36. package/src/assets/skins/heige/ASSET_PROVENANCE.md +56 -0
  37. package/src/assets/skins/heige/NOTICE.md +13 -0
  38. package/src/assets/skins/heige/themes/dalao-dianyan/hero.webp +0 -0
  39. package/src/assets/skins/heige/themes/dalao-dianyan/theme.json +14 -0
  40. package/src/assets/skins/heige/themes/deepspace-dawn/hero.webp +0 -0
  41. package/src/assets/skins/heige/themes/deepspace-dawn/theme.json +14 -0
  42. package/src/assets/skins/heige/themes/deepspace-star/hero.webp +0 -0
  43. package/src/assets/skins/heige/themes/deepspace-star/theme.json +14 -0
  44. package/src/assets/skins/heige/themes/dragonball-nimbus/hero.webp +0 -0
  45. package/src/assets/skins/heige/themes/dragonball-nimbus/theme.json +16 -0
  46. package/src/assets/skins/heige/themes/dragonball-super-saiyan/hero.webp +0 -0
  47. package/src/assets/skins/heige/themes/dragonball-super-saiyan/theme.json +16 -0
  48. package/src/assets/skins/heige/themes/genshin-dawn/hero.webp +0 -0
  49. package/src/assets/skins/heige/themes/genshin-dawn/theme.json +14 -0
  50. package/src/assets/skins/heige/themes/genshin-night/hero.webp +0 -0
  51. package/src/assets/skins/heige/themes/genshin-night/theme.json +14 -0
  52. package/src/assets/skins/heige/themes/miku-488137/hero.webp +0 -0
  53. package/src/assets/skins/heige/themes/miku-488137/logo.webp +0 -0
  54. package/src/assets/skins/heige/themes/miku-488137/polaroid.webp +0 -0
  55. package/src/assets/skins/heige/themes/miku-488137/theme.json +16 -0
  56. package/src/assets/skins/heige/themes/naruto-hokage/hero.webp +0 -0
  57. package/src/assets/skins/heige/themes/naruto-hokage/theme.json +14 -0
  58. package/src/assets/skins/heige/themes/naruto-sasuke/hero.webp +0 -0
  59. package/src/assets/skins/heige/themes/naruto-sasuke/theme.json +14 -0
  60. package/src/assets/skins/heige/themes/wuthering-echo/hero.webp +0 -0
  61. package/src/assets/skins/heige/themes/wuthering-echo/theme.json +14 -0
  62. package/src/assets/skins/heige/themes/wuthering-tide/hero.webp +0 -0
  63. package/src/assets/skins/heige/themes/wuthering-tide/theme.json +14 -0
  64. package/src/main/adapters/cline.js +22 -0
  65. package/src/main/adapters/codex.js +78 -8
  66. package/src/main/adapters/index.js +2 -1
  67. package/src/main/adapters/native-acp-command.js +1 -0
  68. package/src/main/harness-adapter/error-kind.js +82 -0
  69. package/src/main/harness-adapter/event-normalizer.js +7 -1
  70. package/src/main/host/core-session.js +1 -1
  71. package/src/main/host/handoff-access.js +23 -4
  72. package/src/main/host/handoff-checkpoints.js +9 -2
  73. package/src/main/host/handoff-tools.js +3 -0
  74. package/src/main/host/handoff.js +11 -2
  75. package/src/main/host/runtime.js +272 -163
  76. package/src/main/host/session-history.js +54 -15
  77. package/src/main/native/icons.js +3 -2
  78. package/src/main/native/launcher.js +49 -2
  79. package/src/main/native/pets.js +395 -0
  80. package/src/main/native/protocol.js +197 -55
  81. package/src/main/protocol-core/projector.js +44 -41
  82. package/src/native-ui/desktop-control/dist/production-controller.d.ts.map +1 -1
  83. package/src/native-ui/desktop-control/dist/production-controller.js +1 -0
  84. package/src/native-ui/desktop-control/dist/production-controller.js.map +1 -1
  85. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js +4 -4
  86. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js.map +1 -1
  87. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
  88. package/src/native-ui/desktop-control/src/production-controller.ts +1 -0
  89. package/src/native-ui/desktop-control/src/renderer-draft-prewarm-runtime.ts +4 -4
  90. package/src/native-ui/desktop-control/test/production-controller.test.ts +1 -0
  91. package/src/native-ui/desktop-control/test/renderer-draft-prewarm-policy.test.ts +7 -0
  92. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts +5 -2
  93. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts.map +1 -1
  94. package/src/native-ui/renderer-extension/dist/types/renderer-agent-icon.d.ts.map +1 -1
  95. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -1
  96. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts +1 -0
  97. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  98. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  99. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +2 -0
  100. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  101. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +2 -1
  102. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
  103. package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts.map +1 -1
  104. package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts.map +1 -1
  105. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts +1 -1
  106. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts.map +1 -1
  107. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  108. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +5 -3
  109. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  110. package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts +5 -0
  111. package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts.map +1 -0
  112. package/src/native-ui/renderer-extension/dist/types/settings/pets-client.d.ts +57 -0
  113. package/src/native-ui/renderer-extension/dist/types/settings/pets-client.d.ts.map +1 -0
  114. package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts.map +1 -1
  115. package/src/native-ui/renderer-extension/dist/types/settings/skin-market.d.ts +4 -0
  116. package/src/native-ui/renderer-extension/dist/types/settings/skin-market.d.ts.map +1 -0
  117. package/src/native-ui/renderer-extension/dist/types/settings/skin-runtime.d.ts +31 -0
  118. package/src/native-ui/renderer-extension/dist/types/settings/skin-runtime.d.ts.map +1 -0
  119. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  120. package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts.map +1 -1
  121. package/src/native-ui/renderer-extension/package.json +1 -1
  122. package/src/native-ui/renderer-extension/src/agent-selection-state.ts +61 -14
  123. package/src/native-ui/renderer-extension/src/assets.d.ts +5 -0
  124. package/src/native-ui/renderer-extension/src/renderer-agent-icon.ts +5 -4
  125. package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +1 -0
  126. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +78 -33
  127. package/src/native-ui/renderer-extension/src/renderer-harness-handoff.ts +2 -2
  128. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +2 -0
  129. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +6 -0
  130. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +5 -0
  131. package/src/native-ui/renderer-extension/src/renderer-sidebar-agent-icons.ts +1 -0
  132. package/src/native-ui/renderer-extension/src/settings/connections-page.ts +2 -0
  133. package/src/native-ui/renderer-extension/src/settings/icons.ts +15 -6
  134. package/src/native-ui/renderer-extension/src/settings/localization.ts +6 -2
  135. package/src/native-ui/renderer-extension/src/settings/pages.ts +72 -61
  136. package/src/native-ui/renderer-extension/src/settings/pet-market.css +310 -0
  137. package/src/native-ui/renderer-extension/src/settings/pet-market.ts +447 -0
  138. package/src/native-ui/renderer-extension/src/settings/pets-client.ts +74 -0
  139. package/src/native-ui/renderer-extension/src/settings/shell.css +27 -8
  140. package/src/native-ui/renderer-extension/src/settings/shell.ts +3 -1
  141. package/src/native-ui/renderer-extension/src/settings/skin-market.css +213 -0
  142. package/src/native-ui/renderer-extension/src/settings/skin-market.ts +174 -0
  143. package/src/native-ui/renderer-extension/src/settings/skin-runtime.ts +630 -0
  144. package/src/native-ui/renderer-extension/src/versioned-renderer-adapter.ts +2 -1
  145. package/src/native-ui/renderer-extension/test/agent-selection-state.test.ts +58 -0
  146. package/src/native-ui/renderer-extension/test/renderer-binding-probe.test.ts +12 -0
  147. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +1 -0
  148. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +12 -1
  149. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +6 -0
  150. package/src/native-ui/renderer-extension/test/settings/skin-runtime.test.ts +162 -0
  151. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts +7 -0
  152. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts.map +1 -1
  153. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js +3 -0
  154. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js.map +1 -1
  155. package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
  156. package/src/native-ui/shared-contracts/src/thread-harness-switch.ts +3 -0
@@ -0,0 +1,213 @@
1
+ .skin-market__intro {
2
+ max-width: 680px;
3
+ margin: -8px 0 18px;
4
+ color: var(--settings-muted);
5
+ font-size: 13px;
6
+ }
7
+
8
+ .skin-safety-note {
9
+ display: grid;
10
+ grid-template-columns: 18px minmax(0, 1fr);
11
+ align-items: start;
12
+ gap: 10px;
13
+ margin: 0 0 22px;
14
+ padding: 12px 14px;
15
+ color: var(--settings-muted);
16
+ background: var(--settings-inset);
17
+ border: 1px solid var(--settings-border);
18
+ border-radius: 10px;
19
+ }
20
+
21
+ .skin-safety-note > span,
22
+ .skin-safety-note strong {
23
+ display: block;
24
+ }
25
+
26
+ .skin-safety-note strong {
27
+ margin-bottom: 2px;
28
+ color: var(--settings-text);
29
+ font-weight: 600;
30
+ }
31
+
32
+ .skin-market-grid {
33
+ display: grid;
34
+ grid-template-columns: repeat(3, minmax(0, 1fr));
35
+ gap: 13px;
36
+ }
37
+
38
+ .skin-card {
39
+ overflow: hidden;
40
+ position: relative;
41
+ min-width: 0;
42
+ background: var(--settings-panel);
43
+ border: 1px solid var(--settings-border);
44
+ border-radius: 12px;
45
+ }
46
+
47
+ .skin-card[data-selected] {
48
+ border-color: color-mix(in srgb, var(--settings-focus) 62%, var(--settings-border));
49
+ box-shadow: inset 3px 0 0 var(--settings-focus);
50
+ }
51
+
52
+ .skin-preview {
53
+ position: relative;
54
+ height: 128px;
55
+ overflow: hidden;
56
+ border-bottom: 1px solid rgb(255 255 255 / 10%);
57
+ }
58
+
59
+ .skin-preview::after {
60
+ content: "";
61
+ position: absolute;
62
+ inset: 0;
63
+ background: linear-gradient(180deg, rgb(255 255 255 / 8%), transparent 36%);
64
+ pointer-events: none;
65
+ }
66
+
67
+ .skin-preview__dots {
68
+ position: absolute;
69
+ top: 11px;
70
+ left: 12px;
71
+ width: 5px;
72
+ height: 5px;
73
+ border-radius: 50%;
74
+ background: rgb(255 255 255 / 70%);
75
+ box-shadow: 9px 0 rgb(255 255 255 / 42%), 18px 0 rgb(255 255 255 / 24%);
76
+ }
77
+
78
+ .skin-preview__sidebar,
79
+ .skin-preview__conversation,
80
+ .skin-preview__composer {
81
+ position: absolute;
82
+ background: rgb(5 10 16 / 48%);
83
+ border: 1px solid rgb(255 255 255 / 14%);
84
+ backdrop-filter: blur(8px);
85
+ }
86
+
87
+ .skin-preview__sidebar {
88
+ inset: 27px auto 10px 10px;
89
+ width: 26%;
90
+ border-radius: 6px;
91
+ }
92
+
93
+ .skin-preview__conversation {
94
+ inset: 27px 10px 42px 31%;
95
+ border-radius: 7px;
96
+ }
97
+
98
+ .skin-preview__conversation::before,
99
+ .skin-preview__conversation::after {
100
+ content: "";
101
+ position: absolute;
102
+ left: 12px;
103
+ height: 5px;
104
+ border-radius: 3px;
105
+ background: rgb(255 255 255 / 48%);
106
+ }
107
+
108
+ .skin-preview__conversation::before {
109
+ top: 14px;
110
+ width: 56%;
111
+ }
112
+
113
+ .skin-preview__conversation::after {
114
+ top: 27px;
115
+ width: 38%;
116
+ opacity: 0.55;
117
+ }
118
+
119
+ .skin-preview__composer {
120
+ right: 10px;
121
+ bottom: 10px;
122
+ left: 31%;
123
+ height: 24px;
124
+ border-radius: 8px;
125
+ }
126
+
127
+ .skin-card__body {
128
+ padding: 14px;
129
+ }
130
+
131
+ .skin-card__title-row,
132
+ .skin-card__footer {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 10px;
136
+ }
137
+
138
+ .skin-card__title {
139
+ font-size: 14px;
140
+ font-weight: 600;
141
+ }
142
+
143
+ .skin-card__current {
144
+ margin-left: auto;
145
+ color: var(--settings-success);
146
+ font-size: 11px;
147
+ font-weight: 600;
148
+ }
149
+
150
+ .skin-card__description {
151
+ min-height: 38px;
152
+ margin: 7px 0 13px;
153
+ color: var(--settings-muted);
154
+ font-size: 12px;
155
+ line-height: 1.55;
156
+ }
157
+
158
+ .skin-card__description a {
159
+ color: inherit;
160
+ text-decoration: underline;
161
+ text-decoration-color: var(--settings-border);
162
+ text-underline-offset: 3px;
163
+ }
164
+
165
+ .skin-card__description a:hover {
166
+ color: var(--settings-text);
167
+ }
168
+
169
+ .skin-card__palette {
170
+ display: flex;
171
+ align-items: center;
172
+ }
173
+
174
+ .skin-card__palette i {
175
+ width: 18px;
176
+ height: 18px;
177
+ margin-left: -4px;
178
+ border: 2px solid var(--settings-panel);
179
+ border-radius: 50%;
180
+ }
181
+
182
+ .skin-card__palette i:first-child {
183
+ margin-left: 0;
184
+ }
185
+
186
+ .skin-card__apply {
187
+ margin-left: auto;
188
+ }
189
+
190
+ .skin-card__apply:disabled {
191
+ color: var(--settings-muted);
192
+ background: var(--settings-surface);
193
+ cursor: default;
194
+ }
195
+
196
+ @media (max-width: 900px) {
197
+ .skin-market-grid {
198
+ grid-template-columns: repeat(2, minmax(0, 1fr));
199
+ }
200
+ }
201
+
202
+ @media (max-width: 620px) {
203
+ .skin-market-grid {
204
+ grid-template-columns: 1fr;
205
+ }
206
+ }
207
+
208
+ @media (prefers-reduced-motion: reduce) {
209
+ .skin-card,
210
+ .skin-card * {
211
+ transition: none !important;
212
+ }
213
+ }
@@ -0,0 +1,174 @@
1
+ import type { RendererSettingsMessages } from "./localization.js";
2
+ import type {
3
+ RendererSettingsPageDefinition,
4
+ RendererSettingsPageMountContext,
5
+ } from "./core.js";
6
+ import { createRendererSettingsIcon } from "./icons.js";
7
+ import {
8
+ RENDERER_SKINS,
9
+ applyRendererSkin,
10
+ readActiveRendererSkin,
11
+ type RendererSkinDefinition,
12
+ type RendererSkinId,
13
+ } from "./skin-runtime.js";
14
+
15
+ const COPY = {
16
+ en: {
17
+ title: "Skins",
18
+ intro: "Change the atmosphere, not the app. Every skin keeps Codex controls and native Harness behavior intact.",
19
+ safe: "Visual layer only",
20
+ safeDetail: "No replacement controls, scripts, credentials, model routes, tools, or permission changes.",
21
+ selected: "Current skin",
22
+ apply: "Apply skin",
23
+ applied: "Applied",
24
+ native: "The untouched Codex appearance. Select it anytime to remove every Harness Mix skin override.",
25
+ included: "Bundled theme from",
26
+ light: "Light",
27
+ dark: "Dark",
28
+ attribution: "Open source reference",
29
+ },
30
+ "zh-CN": {
31
+ title: "皮肤",
32
+ intro: "只换氛围,不换内核。所有皮肤都保留 Codex 原生控件与各 Harness 的真实行为。",
33
+ safe: "仅视觉层",
34
+ safeDetail: "不替换控件,不运行主题脚本,不改变凭据、模型路由、工具或权限。",
35
+ selected: "当前皮肤",
36
+ apply: "应用皮肤",
37
+ applied: "已应用",
38
+ native: "未经修改的 Codex 外观。随时选择它,即可移除 Harness Mix 的全部皮肤覆盖。",
39
+ included: "内置主题来自",
40
+ light: "浅色",
41
+ dark: "深色",
42
+ attribution: "开源参考项目",
43
+ },
44
+ } as const;
45
+
46
+ function previewChrome(document: Document, skin: RendererSkinDefinition): HTMLElement {
47
+ const preview = document.createElement("div");
48
+ preview.className = "skin-preview";
49
+ preview.style.backgroundImage = skin.heroUrl
50
+ ? `${skin.preview}, url("${skin.heroUrl}")`
51
+ : skin.preview;
52
+ preview.style.backgroundPosition = `center, ${skin.focus}`;
53
+ preview.style.backgroundSize = "cover";
54
+ preview.setAttribute("aria-hidden", "true");
55
+ const dots = document.createElement("span");
56
+ dots.className = "skin-preview__dots";
57
+ const sidebar = document.createElement("span");
58
+ sidebar.className = "skin-preview__sidebar";
59
+ const conversation = document.createElement("span");
60
+ conversation.className = "skin-preview__conversation";
61
+ const composer = document.createElement("span");
62
+ composer.className = "skin-preview__composer";
63
+ preview.append(dots, sidebar, conversation, composer);
64
+ return preview;
65
+ }
66
+
67
+ export function createSkinSettingsPage(
68
+ messages: RendererSettingsMessages,
69
+ ): RendererSettingsPageDefinition {
70
+ const copy = COPY[messages.locale];
71
+ return Object.freeze({
72
+ id: "skins",
73
+ label: messages.pageLabels.skins,
74
+ icon: "palette",
75
+ mount(context: RendererSettingsPageMountContext) {
76
+ const document = context.content.ownerDocument;
77
+ const heading = document.createElement("div");
78
+ heading.className = "settings-section-label";
79
+ heading.textContent = copy.title;
80
+ const introduction = document.createElement("p");
81
+ introduction.className = "skin-market__intro";
82
+ introduction.textContent = copy.intro;
83
+ const safety = document.createElement("aside");
84
+ safety.className = "skin-safety-note";
85
+ safety.append(createRendererSettingsIcon("shield", 17));
86
+ const safetyCopy = document.createElement("span");
87
+ const safetyTitle = document.createElement("strong");
88
+ safetyTitle.textContent = copy.safe;
89
+ const safetyDetail = document.createElement("span");
90
+ safetyDetail.textContent = copy.safeDetail;
91
+ safetyCopy.append(safetyTitle, safetyDetail);
92
+ safety.append(safetyCopy);
93
+
94
+ const grid = document.createElement("div");
95
+ grid.className = "skin-market-grid";
96
+ let selected = readActiveRendererSkin(document);
97
+ const cards = new Map<RendererSkinId, HTMLElement>();
98
+
99
+ const renderSelection = (): void => {
100
+ for (const [id, card] of cards) {
101
+ const active = id === selected;
102
+ card.toggleAttribute("data-selected", active);
103
+ const button = card.querySelector<HTMLButtonElement>("button[data-skin-action]");
104
+ if (button) {
105
+ button.disabled = active;
106
+ button.textContent = active ? copy.applied : copy.apply;
107
+ }
108
+ const badge = card.querySelector<HTMLElement>("[data-skin-current]");
109
+ if (badge) badge.hidden = !active;
110
+ }
111
+ };
112
+
113
+ for (const skin of RENDERER_SKINS) {
114
+ const card = document.createElement("article");
115
+ card.className = "skin-card";
116
+ card.dataset.skinId = skin.id;
117
+ card.append(previewChrome(document, skin));
118
+ const body = document.createElement("div");
119
+ body.className = "skin-card__body";
120
+ const titleRow = document.createElement("div");
121
+ titleRow.className = "skin-card__title-row";
122
+ const title = document.createElement("strong");
123
+ title.className = "skin-card__title";
124
+ title.textContent = skin.name;
125
+ const current = document.createElement("span");
126
+ current.className = "skin-card__current";
127
+ current.dataset.skinCurrent = "true";
128
+ current.textContent = copy.selected;
129
+ titleRow.append(title, current);
130
+ const description = document.createElement("p");
131
+ description.className = "skin-card__description";
132
+ if (skin.sourceUrl) {
133
+ description.append(`${copy.included} `);
134
+ const source = document.createElement("a");
135
+ source.href = skin.sourceUrl;
136
+ source.target = "_blank";
137
+ source.rel = "noopener noreferrer";
138
+ source.textContent = skin.sourceName;
139
+ source.title = copy.attribution;
140
+ description.append(source);
141
+ description.append(` · ${skin.dark ? copy.dark : copy.light}`);
142
+ } else {
143
+ description.textContent = copy.native;
144
+ }
145
+ const footer = document.createElement("div");
146
+ footer.className = "skin-card__footer";
147
+ const palette = document.createElement("span");
148
+ palette.className = "skin-card__palette";
149
+ palette.setAttribute("aria-hidden", "true");
150
+ for (const color of skin.palette) {
151
+ const swatch = document.createElement("i");
152
+ swatch.style.backgroundColor = color;
153
+ palette.append(swatch);
154
+ }
155
+ const apply = document.createElement("button");
156
+ apply.type = "button";
157
+ apply.className = "settings-command-button skin-card__apply";
158
+ apply.dataset.skinAction = skin.id;
159
+ apply.addEventListener("click", () => {
160
+ selected = applyRendererSkin(skin.id, document);
161
+ renderSelection();
162
+ });
163
+ footer.append(palette, apply);
164
+ body.append(titleRow, description, footer);
165
+ card.append(body);
166
+ cards.set(skin.id, card);
167
+ grid.append(card);
168
+ }
169
+ renderSelection();
170
+ context.content.append(heading, introduction, safety, grid);
171
+ return undefined;
172
+ },
173
+ });
174
+ }