deepseek-harness-background 0.5.5 → 0.5.6

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.
package/README.md CHANGED
@@ -58,7 +58,8 @@ The look (fixed wallpaper layer + theme-aware scrim + translucent glass panels d
58
58
 
59
59
  | Plugin release | Supported dsh versions |
60
60
  | --- | --- |
61
- | **0.5.5** | **dsh 0.1.2-rc.1 and 0.1.3-alpha.x (verified on alpha.2), plus 0.1.5-alpha.1/alpha.2/rc.1/rc.2 (verified on rc.2), 0.1.6-alpha.1/alpha.2 and 0.1.7-alpha.1** |
61
+ | **0.5.6** | **dsh 0.1.2-rc.1 and 0.1.3-alpha.x (verified on alpha.2), plus 0.1.5-alpha.1/alpha.2/rc.1/rc.2 (verified on rc.2), 0.1.6-alpha.1/alpha.2 and 0.1.7-alpha.1/alpha.2/rc.1/rc.2 (verified on rc.2)** |
62
+ | 0.5.5 | dsh 0.1.2-rc.1 and 0.1.3-alpha.x, plus 0.1.5-alpha.1/alpha.2/rc.1/rc.2 and 0.1.6-alpha.1/alpha.2, 0.1.7-alpha.1 |
62
63
  | 0.5.3 – 0.5.4 | dsh 0.1.2-rc.1 and 0.1.3-alpha.x, plus 0.1.5-alpha.1/alpha.2/rc.1/rc.2 and 0.1.6-alpha.1/alpha.2 |
63
64
  | 0.5.2 | dsh 0.1.1-rc.2 and 0.1.2-alpha.1 ~ alpha.5 |
64
65
 
package/README.zh.md CHANGED
@@ -57,7 +57,8 @@
57
57
 
58
58
  | 本插件版本 | 支持的 dsh 版本 |
59
59
  | --- | --- |
60
- | **0.5.5** | **dsh 0.1.2-rc.1 与 0.1.3-alpha.x(已在 alpha.2 验证),另支持 0.1.5-alpha.1 / alpha.2 / rc.1 / rc.2(已在 rc.2 验证)、0.1.6-alpha.1 / alpha.2 与 0.1.7-alpha.1** |
60
+ | **0.5.6** | **dsh 0.1.2-rc.1 与 0.1.3-alpha.x(已在 alpha.2 验证),另支持 0.1.5-alpha.1 / alpha.2 / rc.1 / rc.2(已在 rc.2 验证)、0.1.6-alpha.1 / alpha.2 与 0.1.7-alpha.1 / alpha.2 / rc.1 / rc.2(已在 rc.2 验证)** |
61
+ | 0.5.5 | dsh 0.1.2-rc.1 与 0.1.3-alpha.x,另支持 0.1.5-alpha.1 / alpha.2 / rc.1 / rc.2 与 0.1.6-alpha.1 / alpha.2、0.1.7-alpha.1 |
61
62
  | 0.5.3 – 0.5.4 | dsh 0.1.2-rc.1 与 0.1.3-alpha.x,另支持 0.1.5-alpha.1 / alpha.2 / rc.1 / rc.2 与 0.1.6-alpha.1 / alpha.2 |
62
63
  | 0.5.2 | dsh 0.1.1-rc.2 与 0.1.2-alpha.1 ~ alpha.5 |
63
64
 
package/lib/client.js CHANGED
@@ -62,8 +62,9 @@ window.__ModuleLoader__.load({
62
62
  * button, scroll-to-bottom), the load-earlier history button, the composer
63
63
  * dock family (agent task strips: TodoPanel / GoalBar / QueueDock) and its
64
64
  * takeover panels (approval, question, plan review), the subagent lineage
65
- * popover, the home hero "preview" badge, the sidebar build badge, and the
66
- * timeline rail (the official-rail rules below).
65
+ * popover, the home hero "preview" badge, the sidebar build badge, the turn
66
+ * deliverables (the changed-files card and the diff hover preview it opens),
67
+ * and the timeline rail (the official-rail rules below).
67
68
  * Every glassed surface shares ONE recipe:
68
69
  * fill from the painter's --dsw-specific-input-major token (same alpha
69
70
  * curve + theme dimming), the full shared blur/saturate/brightness chain
@@ -71,10 +72,14 @@ window.__ModuleLoader__.load({
71
72
  * surface reads heavier than the composer card. Dialogs, the settings UI,
72
73
  * menus, tooltips, toasts and every hover/accent fill keep the OFFICIAL
73
74
  * opaque paints — they are reading surfaces and must stay legible.
75
+ * The same rule excludes the HoverCard primitive's compact pod (a fixed
76
+ * #2C2C2E chip): it is hover FEEDBACK, not a floating card, and the official
77
+ * opaque paint is what keeps its label legible over any wallpaper.
74
78
  *
75
79
  * Selectors target authored attributes (`data-dsh-bg`, `data-composer-card`,
76
- * `data-terminal`…), the `:global` `.md-code-block` anchor, and CSS-module
77
- * SUFFIX conventions (`_bubble`, `_newSession`…) audited for collisions.
80
+ * `data-terminal`, `data-changes-hover-preview`…), the `:global`
81
+ * `.md-code-block` anchor, and CSS-module SUFFIX conventions (`_bubble`,
82
+ * `_newSession`…) audited for collisions.
78
83
  *
79
84
  * MAINTENANCE NOTE: the suffix anchors are the host's only stable-enough
80
85
  * integration surface today, but a host restyle that renames them silently
@@ -83,11 +88,14 @@ window.__ModuleLoader__.load({
83
88
  * suffix entries.
84
89
  * `_bubble` excludes `role="tooltip"` (Tooltip.module.css shares the suffix),
85
90
  * `_add` is scoped under `[data-composer-card]` (DiffBlock line markers share
86
- * it), `_newSession` excludes `Label` (the button's inner label span) and
87
- * `_toBottom` excludes `Slot` (the zero-height sticky wrapper) — both share
88
- * the substring with the chrome control but must keep their own paint, and
89
- * the subagent popover is `role="tree"` + `_menu` so the generic menu
90
- * surface stays untouched.
91
+ * it), `_newSession` is scoped to the `button` element — an element test, not
92
+ * another suffix exclusion, because 0.1.7-rc.2 added `newSessionLabelMask` /
93
+ * `newSessionContent` / `newSessionShortcut` INSIDE the button and each of
94
+ * those spans would otherwise take the chrome paint (the composer-sized
95
+ * second box the user sees inside the control) — and `_toBottom` excludes
96
+ * `Slot` (the zero-height sticky wrapper) — both share the substring with the
97
+ * chrome control but must keep their own paint, and the subagent popover is
98
+ * `role="tree"` + `_menu` so the generic menu surface stays untouched.
91
99
  */
92
100
  const BACKGROUND_CSS = `
93
101
  .dsh-bg-layer {
@@ -141,7 +149,7 @@ window.__ModuleLoader__.load({
141
149
  body[data-dsh-bg-glass] [data-web],
142
150
  body[data-dsh-bg-glass] [class*="_ioCard"],
143
151
  body[data-dsh-bg-glass] [class*="_instructionsCard"],
144
- body[data-dsh-bg-glass] [class*="_markdown"] :not(pre) > code {
152
+ body[data-dsh-bg-glass] [class*="_markdown"]:not(pre) > code {
145
153
  background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
146
154
  -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
147
155
  backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
@@ -178,7 +186,7 @@ window.__ModuleLoader__.load({
178
186
  caps), sheen = the shared sheen vars. Hover = the same white glass at the
179
187
  boosted alpha var. Gated on data-dsh-bg-glass (off when the panel is
180
188
  fully opaque). */
181
- body[data-dsh-bg-glass] [class*="_newSession"]:not([class*="Label"]),
189
+ body[data-dsh-bg-glass] button[class*="_newSession"],
182
190
  body[data-dsh-bg-glass] [data-composer-card] [class*="_add"],
183
191
  body[data-dsh-bg-glass] [class*="_toBottom"]:not([class*="Slot"]) {
184
192
  background-color: var(--dsw-specific-input-major);
@@ -190,15 +198,15 @@ window.__ModuleLoader__.load({
190
198
  inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),
191
199
  0 4px 14px rgba(0, 0, 0, 0.08);
192
200
  }
193
- body[data-dsh-bg-glass] [class*="_newSession"]:not([class*="Label"]):hover,
201
+ body[data-dsh-bg-glass] button[class*="_newSession"]:hover,
194
202
  body[data-dsh-bg-glass] [data-composer-card] [class*="_add"]:hover:not(:disabled),
195
203
  body[data-dsh-bg-glass] [class*="_toBottom"]:not([class*="Slot"]):hover {
196
204
  background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);
197
205
  }
198
206
  /* Collapsed rail renders the new-session control as a bare icon — keep the
199
207
  official transparent paint (and its official translucent hover). */
200
- body[data-dsh-bg-glass] [class*="_collapsed"] [class*="_newSession"],
201
- body[data-dsh-bg-glass] [class*="_collapsed"] [class*="_newSession"]:hover {
208
+ body[data-dsh-bg-glass] [data-sidebar-collapsed="true"] [class*="_newSession"],
209
+ body[data-dsh-bg-glass] [data-sidebar-collapsed="true"] [class*="_newSession"]:hover {
202
210
  background-color: transparent;
203
211
  background-image: none;
204
212
  box-shadow: none;
@@ -278,19 +286,45 @@ window.__ModuleLoader__.load({
278
286
  backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
279
287
  }
280
288
 
289
+ /* ---- Turn deliverables: the changed-files card + its hover preview -----
290
+ Both paint with the official opaque --dsw-alias-bg-layer-1 (the token the
291
+ painter deliberately keeps opaque, because it backs the settings UI,
292
+ dialogs and menus), so each needs an EXPLICIT fill on top of the shared
293
+ sheen/blur chain — the token-mediated rule above cannot reach them.
294
+ [data-changed-files] is the card a turn tail renders; its header and
295
+ rows carry their own fills, so the card surface alone is glassed and the
296
+ contents keep their official paints. [data-changes-hover-preview] is
297
+ the fixed HoverCard the changed-files rows open on hover and the shared
298
+ component behind every diff preview; its width/height are set by the
299
+ hover-card primitive, so this only supplies the material. The preview's
300
+ official elevation shadow rides its own token untouched — only the fill
301
+ and the filter chain are taken over here. */
302
+ body[data-dsh-bg-glass] [data-changed-files],
303
+ body[data-dsh-bg-glass] [data-changes-hover-preview] {
304
+ background-color: var(--dsw-specific-input-major);
305
+ background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
306
+ -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
307
+ backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
308
+ box-shadow: var(--dsw-elevation-panel, 0 4px 14px rgba(0, 0, 0, 0.08));
309
+ }
310
+
281
311
  /* ---- Official turn rail (dsh >= 0.1.2, TurnNavigator) -----------------
282
312
  The enhance mode borrows the kernel's rail untouched, so its hover
283
313
  preview card keeps the OFFICIAL opaque fill (bg-layer-1, not mediated
284
314
  by any overridden token) — the "悬浮反馈形态不透明" complaint. Glass it
285
315
  explicitly with the same explicit-fill recipe as the chrome buttons
286
- (fill = the painter's composer token + shared blur/sheen). Anchors are
287
- structural, never class names: the kernel's module-CSS class hashes are
288
- not a stable contract, but nav[style*="--turn-natural-height"] (its
289
- inline rail metrics) and the preview's role="tooltip" are. No edge
316
+ (fill = the painter's composer token + shared blur/sheen). The anchor is
317
+ the rail's SEAT, the same one timeline/official-enhance.tsx identifies it
318
+ by: the only nav inside the conversation scrollport. 0.1.7 virtualized
319
+ the rail (commit f89cd786aa), which deleted the turn-natural-height
320
+ inline metric that used to sit on that nav — the nav carries no style
321
+ attribute at all now — so the selector that keyed on it matched nothing
322
+ and the preview went bare. The scrollport attribute and the preview's
323
+ role="tooltip" are the structural hooks; class hashes never are. No edge
290
324
  dissolve on the marks column: the first and last tick dashes straddle
291
325
  the marks box edges by 1px, so a fade zone sized off the box height
292
326
  swallows them instead of the band. */
293
- body[data-dsh-bg-glass] nav[style*="--turn-natural-height"] [role="tooltip"] {
327
+ body[data-dsh-bg-glass] [data-conversation-scroll] nav [role="tooltip"] {
294
328
  background-color: var(--dsw-specific-input-major);
295
329
  background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
296
330
  -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
@@ -1125,44 +1159,44 @@ window.__ModuleLoader__.load({
1125
1159
  document.head.appendChild(tag);
1126
1160
  }
1127
1161
  var SettingsRow_module_css_default = {
1128
- "previewGlassText": "-ozqdq_previewGlassText",
1129
- "title": "-ozqdq_title",
1162
+ "sectionLabel": "-ozqdq_sectionLabel",
1163
+ "row": "-ozqdq_row",
1164
+ "previewGlass": "-ozqdq_previewGlass",
1130
1165
  "switchKnob": "-ozqdq_switchKnob",
1131
- "sliderValue": "-ozqdq_sliderValue",
1132
- "error": "-ozqdq_error",
1133
- "urlRow": "-ozqdq_urlRow",
1134
- "btn": "-ozqdq_btn",
1135
- "sliderLabel": "-ozqdq_sliderLabel",
1136
- "slider": "-ozqdq_slider",
1137
1166
  "clearBtn": "-ozqdq_clearBtn",
1138
- "preview": "-ozqdq_preview",
1139
- "urlInput": "-ozqdq_urlInput",
1140
1167
  "segRow": "-ozqdq_segRow",
1141
- "enabledBadge": "-ozqdq_enabledBadge",
1142
- "titleRow": "-ozqdq_titleRow",
1143
- "sectionLabel": "-ozqdq_sectionLabel",
1168
+ "desc": "-ozqdq_desc",
1144
1169
  "sourceRow": "-ozqdq_sourceRow",
1145
- "segBtn": "-ozqdq_segBtn",
1146
- "row": "-ozqdq_row",
1170
+ "btnPrimary": "-ozqdq_btnPrimary",
1171
+ "titleRow": "-ozqdq_titleRow",
1147
1172
  "noticeBtn": "-ozqdq_noticeBtn",
1148
- "previewImg": "-ozqdq_previewImg",
1149
- "switch": "-ozqdq_switch",
1150
- "btnDanger": "-ozqdq_btnDanger",
1151
- "segLabel": "-ozqdq_segLabel",
1152
- "previewGlass": "-ozqdq_previewGlass",
1153
- "previewEmpty": "-ozqdq_previewEmpty",
1154
- "noticeText": "-ozqdq_noticeText",
1155
- "header": "-ozqdq_header",
1156
1173
  "section": "-ozqdq_section",
1157
- "previewScrim": "-ozqdq_previewScrim",
1158
- "desc": "-ozqdq_desc",
1159
- "notice": "-ozqdq_notice",
1160
1174
  "sliderRow": "-ozqdq_sliderRow",
1161
- "segGroup": "-ozqdq_segGroup",
1175
+ "enabledBadge": "-ozqdq_enabledBadge",
1176
+ "noticeText": "-ozqdq_noticeText",
1177
+ "urlRow": "-ozqdq_urlRow",
1178
+ "previewScrim": "-ozqdq_previewScrim",
1179
+ "sliderLabel": "-ozqdq_sliderLabel",
1180
+ "sliderValue": "-ozqdq_sliderValue",
1181
+ "header": "-ozqdq_header",
1182
+ "error": "-ozqdq_error",
1183
+ "urlInput": "-ozqdq_urlInput",
1162
1184
  "hint": "-ozqdq_hint",
1163
- "btnPrimary": "-ozqdq_btnPrimary",
1185
+ "btnDanger": "-ozqdq_btnDanger",
1186
+ "title": "-ozqdq_title",
1187
+ "previewImg": "-ozqdq_previewImg",
1188
+ "segLabel": "-ozqdq_segLabel",
1189
+ "switch": "-ozqdq_switch",
1190
+ "preview": "-ozqdq_preview",
1191
+ "segGroup": "-ozqdq_segGroup",
1192
+ "previewGlassText": "-ozqdq_previewGlassText",
1164
1193
  "segActive": "-ozqdq_segActive",
1165
- "clearRow": "-ozqdq_clearRow"
1194
+ "btn": "-ozqdq_btn",
1195
+ "clearRow": "-ozqdq_clearRow",
1196
+ "notice": "-ozqdq_notice",
1197
+ "segBtn": "-ozqdq_segBtn",
1198
+ "previewEmpty": "-ozqdq_previewEmpty",
1199
+ "slider": "-ozqdq_slider"
1166
1200
  };
1167
1201
  //#endregion
1168
1202
  //#region src/client/SettingsRow.tsx
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useState","useRef","css","useSyncExternalStore","useCallback"],"sources":["../src/client/background-css.ts","../src/settings.ts","../src/client/backdrop.ts","../src/client/glass-registry.ts","../src/client/locales.ts","../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","../src/client/settings-client.ts","../src/client/SettingsRow.tsx","../src/client/timeline/rail-pointer.ts","../src/client/timeline/official-enhance.tsx","../src/client/timeline/jump.ts","../src/client/timeline/index.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Background CSS — the stylesheet injected once into the document when the\n * plugin first runs. It defines the behind-body wallpaper layer, the scrim,\n * and the frosted-glass surfaces, all driven by CSS variables the painter\n * writes onto `document.body`. Kept in a single idempotent\n * `<style data-plugin-css>` tag (mirrors the reference `dsh-wallpaper-engine`\n * bundle).\n */\n\n/** Unique id stamped on the injected style element (dedup key). */\nexport const BACKGROUND_CSS_TAG = 'deepseek-harness-background/styles'\n\n/** Body attribute the painter sets while a wallpaper is active. */\nexport const ACTIVE_ATTR = 'data-dsh-bg'\n\n/**\n * Body attribute the painter sets only while the glass is actually on (a\n * wallpaper is active AND panelOpacity < 1). The explicit-fill rules below —\n * whitelisted buttons, popovers and badges whose official fills are not\n * token-mediated — key on this gate so they return to the exact official\n * paints when the user maxes the panel-opacity slider.\n */\nexport const GLASS_ATTR = 'data-dsh-bg-glass'\n\n/**\n * The backdrop CSS. The wallpaper is a fixed `z-index:-2` element (below the\n * scrim at `-1`, below the UI). While active, the app frame + sidebar fills\n * turn transparent so all columns share one backdrop.\n *\n * Frosted glass is a WHITELIST, not a blanket: only the surfaces that float\n * as small cards/buttons over the wallpaper are glassed — composer card,\n * message bubbles, code surfaces (blocks, inline code, tool IO cards,\n * skill/MCP call cards), the three chrome buttons (new session, composer +\n * button, scroll-to-bottom), the load-earlier history button, the composer\n * dock family (agent task strips: TodoPanel / GoalBar / QueueDock) and its\n * takeover panels (approval, question, plan review), the subagent lineage\n * popover, the home hero \"preview\" badge, the sidebar build badge, and the\n * timeline rail (the official-rail rules below).\n * Every glassed surface shares ONE recipe:\n * fill from the painter's --dsw-specific-input-major token (same alpha\n * curve + theme dimming), the full shared blur/saturate/brightness chain\n * driven by the glass-blur slider, and the shared sheen vars — so no\n * surface reads heavier than the composer card. Dialogs, the settings UI,\n * menus, tooltips, toasts and every hover/accent fill keep the OFFICIAL\n * opaque paints — they are reading surfaces and must stay legible.\n *\n * Selectors target authored attributes (`data-dsh-bg`, `data-composer-card`,\n * `data-terminal`…), the `:global` `.md-code-block` anchor, and CSS-module\n * SUFFIX conventions (`_bubble`, `_newSession`…) audited for collisions.\n *\n * MAINTENANCE NOTE: the suffix anchors are the host's only stable-enough\n * integration surface today, but a host restyle that renames them silently\n * drops the glass. Upgrade path: once the host ships stable data-* anchors\n * for these surfaces, swap the selector lists below to them and delete the\n * suffix entries.\n * `_bubble` excludes `role=\"tooltip\"` (Tooltip.module.css shares the suffix),\n * `_add` is scoped under `[data-composer-card]` (DiffBlock line markers share\n * it), `_newSession` excludes `Label` (the button's inner label span) and\n * `_toBottom` excludes `Slot` (the zero-height sticky wrapper) — both share\n * the substring with the chrome control but must keep their own paint, and\n * the subagent popover is `role=\"tree\"` + `_menu` so the generic menu\n * surface stays untouched.\n */\nexport const BACKGROUND_CSS = `\n .dsh-bg-layer {\n position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none;\n }\n .dsh-bg-layer .dsh-bg-image {\n width: 100%; height: 100%; display: block; border: 0; background: transparent;\n object-fit: var(--bg-object-fit, cover);\n opacity: var(--bg-opacity, 1);\n filter: blur(var(--bg-wallpaper-blur, 0px));\n /* Blur thins the edges; scale the image up to hide the fringe. */\n transform: scale(var(--bg-wallpaper-scale, 1));\n transform-origin: center;\n }\n /* Scrim is theme-aware: a WHITE veil in the light scheme (it lifts the art\n toward the near-white surface so dark text keeps contrast) and a BLACK veil\n in the dark scheme (it dims the art so light text keeps contrast). */\n .dsh-bg-scrim {\n position: fixed; inset: 0; z-index: -1; pointer-events: none;\n background: rgba(255, 255, 255, var(--bg-scrim, 0.25));\n }\n body[data-ds-dark-theme] .dsh-bg-scrim {\n background: rgba(0, 0, 0, var(--bg-scrim, 0.25));\n }\n\n /* Active: make the app frame + sidebar background transparent so all columns\n share the same wallpaper + scrim backdrop. */\n body[data-dsh-bg] {\n --dsw-alias-bg-base: transparent;\n --dsw-specific-sidebar-fill: transparent;\n }\n\n /* ---- Whitelisted frosted-glass sheet ---------------------------------\n Fills come from the --dsw-* tokens the painter overrides on body ONLY for\n this list's own surfaces (input/bubble/code/tip); this block adds the\n shared wet-glass sheen + blur from --bg-glass-blur. Exposure is calibrated\n per scheme (the painter writes --bg-glass-brightness / --bg-glass-sheen):\n light glass slightly DIMS, dark glass keeps a slight lift. Gated on the\n GLASS ATTR (wallpaper active AND panelOpacity < 1): maxing the panel\n slider returns these surfaces to the exact official paints. */\n body[data-dsh-bg-glass] [data-composer-card],\n body[data-dsh-bg-glass] [class*=\"_bubble\"]:not([role=\"tooltip\"]),\n /* The read_image toolview (dsh >= 0.1.2-rc.1 ToolRow) collapses its result\n into an image body card; glass it like the other content blocks. */\n body[data-dsh-bg-glass] [class*=\"_imageBody\"],\n body[data-dsh-bg-glass] .md-code-block,\n body[data-dsh-bg-glass] [data-terminal],\n body[data-dsh-bg-glass] [data-diff],\n body[data-dsh-bg-glass] [data-read],\n body[data-dsh-bg-glass] [data-search],\n body[data-dsh-bg-glass] [data-web],\n body[data-dsh-bg-glass] [class*=\"_ioCard\"],\n body[data-dsh-bg-glass] [class*=\"_instructionsCard\"],\n body[data-dsh-bg-glass] [class*=\"_markdown\"] :not(pre) > code {\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.32),\n inset 0 -1px 0 rgba(255, 255, 255, 0.08),\n inset 0 0 0 0.5px rgba(255, 255, 255, 0.08),\n 0 12px 40px rgba(0, 0, 0, 0.12);\n }\n\n /* The sticky code-block header officially occludes scrolled code with\n bg-base (transparent under a wallpaper): refill it with the banner token\n (itself translucent glass) so code stays hidden beneath the sticky bar. */\n body[data-dsh-bg] .md-code-block [class*=\"_bannerWrap\"] {\n background-color: var(--dsw-alias-markdown-code-block-banner);\n }\n\n /* The sidebar build badge paints with the label-ink token — a TEXT token we\n must not override globally — so it gets a theme-aware translucent ink. */\n body[data-dsh-bg] [class*=\"_buildVersion\"] {\n background-color: rgba(15, 17, 21, 0.62);\n }\n body[data-ds-dark-theme][data-dsh-bg] [class*=\"_buildVersion\"] {\n background-color: rgba(249, 250, 251, 0.62);\n }\n\n /* ---- Whitelisted chrome buttons --------------------------------------\n These three buttons' official fills ride button-* tokens shared with\n unrelated surfaces, so instead of overriding the tokens they get explicit\n paints — but the SAME glass recipe as the composer card and bubbles:\n fill = the painter's --dsw-specific-input-major token (the identical\n translucent white + theme curve), filter = the sheet's exact\n blur/saturate/brightness chain (blur follows the glass-blur slider, no\n caps), sheen = the shared sheen vars. Hover = the same white glass at the\n boosted alpha var. Gated on data-dsh-bg-glass (off when the panel is\n fully opaque). */\n body[data-dsh-bg-glass] [class*=\"_newSession\"]:not([class*=\"Label\"]),\n body[data-dsh-bg-glass] [data-composer-card] [class*=\"_add\"],\n body[data-dsh-bg-glass] [class*=\"_toBottom\"]:not([class*=\"Slot\"]) {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.32),\n inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),\n 0 4px 14px rgba(0, 0, 0, 0.08);\n }\n body[data-dsh-bg-glass] [class*=\"_newSession\"]:not([class*=\"Label\"]):hover,\n body[data-dsh-bg-glass] [data-composer-card] [class*=\"_add\"]:hover:not(:disabled),\n body[data-dsh-bg-glass] [class*=\"_toBottom\"]:not([class*=\"Slot\"]):hover {\n background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);\n }\n /* Collapsed rail renders the new-session control as a bare icon — keep the\n official transparent paint (and its official translucent hover). */\n body[data-dsh-bg-glass] [class*=\"_collapsed\"] [class*=\"_newSession\"],\n body[data-dsh-bg-glass] [class*=\"_collapsed\"] [class*=\"_newSession\"]:hover {\n background-color: transparent;\n background-image: none;\n box-shadow: none;\n }\n\n /* ---- Subagent lineage popover (title-bar expanded list) --------------\n Its official fill rides the menu token shared with every dropdown; glass\n THIS popover explicitly via its role=\"tree\" + _menu combination so real\n menus stay officially opaque and legible. Same unified recipe: the\n composer fill token + the full shared filter chain (the official\n elevation shadow stays). */\n body[data-dsh-bg-glass] [role=\"tree\"][class*=\"_menu\"] {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n\n /* ---- Home hero \"preview\" badge (top-right superscript pill) ----------\n Officially the state-business-tertiary pastel; re-emit the same hue as\n glass but with the UNIFIED alpha curve (color-mix against the painter's\n --bg-glass-alpha) and the shared blur chain, so it sits on the wallpaper\n with exactly the glass strength of the composer card. */\n body[data-dsh-bg-glass] [class*=\"_previewBadge\"] {\n background-color: color-mix(in srgb, rgb(228 237 253) calc(var(--bg-glass-alpha, 0.72) * 100%), transparent);\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n body[data-ds-dark-theme][data-dsh-bg-glass] [class*=\"_previewBadge\"] {\n background-color: color-mix(in srgb, rgb(52 65 91) calc(var(--bg-glass-alpha, 0.72) * 100%), transparent);\n }\n\n /* ---- Composer dock family: task strips + takeover panels --------------\n The agent task strips (TodoPanel, GoalBar, QueueDock — one dock column\n above the composer) and the composer takeovers (approval, question,\n plan review) paint with tokens the painter turns translucent\n (--dsw-specific-tip / --dsw-specific-input-major) but their own styles\n never blur anything: translucent-without-blur reads as bare\n transparency, not frosted glass. Give each the shared sheen + filter\n chain; their fills stay token-driven so the panel-opacity slider keeps\n driving them. Anchors are the components' own stable data attributes\n plus a structural child, so no generic class suffix is swept in. */\n body[data-dsh-bg-glass] [data-testid=\"todo-panel\"],\n body[data-dsh-bg-glass] [data-goal-bar] > div,\n body[data-dsh-bg-glass] [data-queue-dock] > div,\n body[data-dsh-bg-glass] [data-approval-key] > div,\n body[data-dsh-bg-glass] [data-question-key] > section,\n body[data-dsh-bg-glass] [data-plan-review-key] > section {\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n\n /* ---- \"Load earlier\" history button (top of long conversations) --------\n Its official fill is an opaque solid button token that is deliberately\n NOT overridden, so unlike the tip family it needs an explicit glass\n fill too — same recipe as the three chrome buttons above. */\n body[data-dsh-bg-glass] [class*=\"_older\"] button {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.32),\n inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),\n 0 4px 14px rgba(0, 0, 0, 0.08);\n }\n body[data-dsh-bg-glass] [class*=\"_older\"] button:hover:not(:disabled) {\n background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);\n }\n\n /* Sidebar build badge: translucent ink already, but it sat directly on\n the wallpaper without frost. Join the shared blur chain under the same\n gate (its theme-aware ink fills from the active block above). */\n body[data-dsh-bg-glass] [class*=\"_buildVersion\"] {\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n\n /* ---- Official turn rail (dsh >= 0.1.2, TurnNavigator) -----------------\n The enhance mode borrows the kernel's rail untouched, so its hover\n preview card keeps the OFFICIAL opaque fill (bg-layer-1, not mediated\n by any overridden token) — the \"悬浮反馈形态不透明\" complaint. Glass it\n explicitly with the same explicit-fill recipe as the chrome buttons\n (fill = the painter's composer token + shared blur/sheen). Anchors are\n structural, never class names: the kernel's module-CSS class hashes are\n not a stable contract, but nav[style*=\"--turn-natural-height\"] (its\n inline rail metrics) and the preview's role=\"tooltip\" are. No edge\n dissolve on the marks column: the first and last tick dashes straddle\n the marks box edges by 1px, so a fade zone sized off the box height\n swallows them instead of the band. */\n body[data-dsh-bg-glass] nav[style*=\"--turn-natural-height\"] [role=\"tooltip\"] {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n`\n\n/**\n * Inject the backdrop stylesheet once. Safe to call repeatedly; a second call\n * is a no-op (dedup key on the style tag).\n */\nexport function injectBackgroundCss(): void {\n if (document.querySelector(`style[data-plugin-css=\"${BACKGROUND_CSS_TAG}\"]`)) return\n const tag = document.createElement('style')\n tag.dataset.plugin = 'deepseek-harness-background'\n tag.dataset.pluginCss = BACKGROUND_CSS_TAG\n tag.textContent = BACKGROUND_CSS\n document.head.appendChild(tag)\n}\n","/** Durable background settings — pure constants/types shared by both halves. */\r\n\r\n/** Settings namespace owned by the background plugin. */\r\nexport const BACKGROUND_SETTINGS_NAMESPACE = 'ui-background'\r\n\r\n/**\r\n * The schema's field names, in schema order. Used as a whitelist when a\r\n * section write commits: the settings layer keeps unknown keys in the user\r\n * document (a legacy schema field would linger forever under a merge-only\r\n * write), so the write path strips everything outside this list.\r\n */\r\nexport const BACKGROUND_SETTINGS_FIELDS = [\r\n 'enabled', 'uploadId', 'url', 'opacity', 'scrim',\r\n 'panelOpacity', 'blur', 'wallpaperBlur', 'fit', 'timeline',\r\n] as const\r\n\r\n/** Browser-facing base path of the background API (shared with the host routes). */\r\nexport const BACKGROUND_API_PREFIX = '/api/bg-wallpaper'\r\n\r\n/** Minimum scrim opacity (0 = no veil at all). */\r\nexport const SCRIM_MIN = 0\r\n\r\n/** Maximum scrim opacity (1 = fully opaque veil — hides the wallpaper entirely).\r\n * Kept at 1 so every percent-unit slider shares one 0..1 domain: the same\r\n * displayed percentage puts every thumb at the same fraction of its track. */\r\nexport const SCRIM_MAX = 1\r\n\r\n/** Default scrim strength applied over user art (a light readability veil). */\r\nexport const DEFAULT_SCRIM = 0.25\r\n\r\n/** Default background image opacity (1 = fully opaque). */\r\nexport const DEFAULT_OPACITY = 1\r\n\r\n/** Minimum background image opacity (0 = fully invisible). */\r\nexport const OPACITY_MIN = 0\r\n\r\n/** Maximum background image opacity (1 = fully opaque). */\r\nexport const OPACITY_MAX = 1\r\n\r\n/** Default panel opacity (0..1); 1 = fully opaque official surfaces, 0 = fully transparent glass. */\r\nexport const DEFAULT_PANEL_OPACITY = 0.15\r\n\r\n/** Minimum panel opacity. */\r\nexport const PANEL_OPACITY_MIN = 0\r\n\r\n/** Maximum panel opacity (opaque — effectively disables the glass). */\r\nexport const PANEL_OPACITY_MAX = 1\r\n\r\n/** Default frosted-glass blur radius in px (0 disables backdrop-filter). */\r\nexport const DEFAULT_BLUR = 16\r\n\r\n/** Minimum glass blur radius. */\r\nexport const BLUR_MIN = 0\r\n\r\n/** Maximum glass blur radius. */\r\nexport const BLUR_MAX = 40\r\n\r\n/** Default wallpaper blur radius in px (blurs the image itself). */\r\nexport const DEFAULT_WALLPAPER_BLUR = 0\r\n\r\n/** Maximum wallpaper blur radius. */\r\nexport const WALLPAPER_BLUR_MAX = 60\r\n\r\n/** Whether the DeepSeek-style conversation timeline rail renders (default on). */\r\nexport const DEFAULT_TIMELINE = true\r\n\r\n/** Default background rendering mode. */\r\nexport const DEFAULT_FIT = 'cover'\r\n\r\n/** Image positioning modes. `cover` fills the frame (cropping as needed); `contain` fits the whole image inside the frame (letterboxing). */\r\nexport const FIT_MODES = ['cover', 'contain'] as const\r\n\r\n/** Background rendering mode. */\r\nexport type BackgroundFit = typeof FIT_MODES[number]\r\n\r\n/**\r\n * User-owned background section, resolved from schema defaults + user layer.\r\n * The active source is either a local upload id (host serves it via\r\n * `/api/bg-wallpaper/image/<id>`) or a raw HTTP(S) image URL the browser\r\n * loads; exactly one of `uploadId` / `url` is set — when both are empty no\r\n * background renders.\r\n */\r\nexport interface BackgroundSettings {\r\n /** Whether the custom background renders at all. */\r\n enabled: boolean\r\n /** Local upload id when the active source is an uploaded file. */\r\n uploadId: string\r\n /** Image URL when the active source is a remote URL. */\r\n url: string\r\n /** Background image opacity (0..1); the image fades toward the surface color. */\r\n opacity: number\r\n /** Readability scrim strength (0..1) layered between the art and the UI. */\r\n scrim: number\r\n /** Panel transparency (0..1): 0 = fully transparent surfaces, 1 = unaffected. */\r\n panelOpacity: number\r\n /** Frosted-glass blur radius on opaque surfaces (px; 0 disables). */\r\n blur: number\r\n /** Blur of the wallpaper image itself (px; 0 disables). */\r\n wallpaperBlur: number\r\n /** How the image fits the frame. */\r\n fit: BackgroundFit\r\n /** Whether the conversation timeline rail renders at the right edge. */\r\n timeline: boolean\r\n}\r\n","/**\n * Background painter — renders the user background behind the whole app frame:\n * a fixed `z-index:-2` wallpaper element plus a `z-index:-1` scrim on\n * `document.body`, driven by the `data-dsh-bg` body attribute and a set of\n * CSS variables. It also applies the frosted-glass surface tokens so the\n * wallpaper shows through translucent input / bubble panels.\n *\n * Retraction discipline mirrors the reference skin plugins: every property /\n * DOM node this painter owns is restored to its prior state on dispose, and\n * the `data-ds-dark-theme` observer is disconnected.\n */\n\nimport { ACTIVE_ATTR, GLASS_ATTR, injectBackgroundCss } from './background-css.ts'\nimport { BACKGROUND_API_PREFIX, type BackgroundSettings } from '../settings.ts'\n\n/** Class names the layer and scrim elements carry. */\nconst LAYER_CLASS = 'dsh-bg-layer'\nconst IMAGE_CLASS = 'dsh-bg-image'\nconst SCRIM_CLASS = 'dsh-bg-scrim'\n\n/** Body style properties the painter owns while a wallpaper paints: the app\n * frame and sidebar fills go transparent so every column shares one art.\n * Inline values outrank any stylesheet rule (the body[data-dsh-bg] gate only\n * covers the CSS copy), so a sourceless section must actively restore them. */\nconst FRAME_TOKENS = ['--dsw-alias-bg-base', '--dsw-specific-sidebar-fill'] as const\n\n/**\n * White-glass tokens — the WHITELISTED surfaces only. Everything that floats\n * as a small card over the wallpaper: the composer input stack, message\n * bubbles, the markdown code surfaces (blocks + banner + inline code), and\n * the agent task strip family (`--dsw-specific-tip`: TodoPanel + GoalBar +\n * QueueDock share one dock column and one fill token). Menus, dialogs, the\n * settings UI, tooltips, toasts, hover fills and every accent keep the\n * OFFICIAL token values — they are reading surfaces and must stay legible.\n * `factor` scales the alpha relative to the input surface.\n */\nconst GLASS_SURFACE_TOKENS: readonly { token: string; factor: number }[] = [\n { token: '--dsw-specific-input-major', factor: 1 },\n { token: '--dsw-specific-bubble', factor: 0.8 },\n { token: '--dsw-alias-markdown-code-block', factor: 1 },\n { token: '--dsw-alias-markdown-code-block-banner', factor: 1 },\n { token: '--dsw-alias-markdown-inline-code', factor: 0.9 },\n { token: '--dsw-specific-tip', factor: 1 },\n]\n\n/** Every token the glass owns (save/restore key set). */\nconst GLASS_TOKENS: readonly string[] = GLASS_SURFACE_TOKENS.map(({ token }) => token)\n\n/** Fraction of the official white surface alpha kept at the most transparent panel setting. */\nconst GLASS_MIN_ALPHA = 0.05\n\n/** Base white-glass alpha for one panelOpacity (the curve every surface shares). */\nfunction glassAlpha(panelOpacity: number): number {\n const alpha = GLASS_MIN_ALPHA + panelOpacity * (0.9 - GLASS_MIN_ALPHA)\n return Math.max(0, Math.min(0.9, alpha))\n}\n\n/**\n * Saturation boost that rides the blur radius. Gentler than the reference\n * engine (1.1 + blur * 0.02, capped at 1.6): stacking a hot saturate on top of\n * the brightness/sheen layers pushed bright wallpapers into clipping.\n */\nfunction glassSaturate(blur: number): number {\n if (blur <= 0) return 1\n // Round off float dust (1.1 + 16 * 0.02 is 1.4200000000000002 raw).\n return Math.round(Math.min(1.6, 1.1 + blur * 0.02) * 1000) / 1000\n}\n\n/**\n * Exposure calibration per scheme. Light glass neither boosts brightness nor\n * stacks a heavy sheen (mainstream frosted recipes — macOS vibrancy, Windows\n * acrylic, common web glass — blur + saturate + tint without a positive\n * brightness gain); dark glass keeps the reference engine's slight lift, which\n * stops the white-glass fills from turning muddy.\n */\nfunction glassExposure(inDark: boolean): { brightness: string; sheen: string; sheenMid: string } {\n return inDark\n ? { brightness: '1.04', sheen: '0.16', sheenMid: '0.05' }\n : { brightness: '0.98', sheen: '0.07', sheenMid: '0.02' }\n}\n\n/**\n * The theme-aware base alpha for one panelOpacity — THE curve every glass\n * surface shares (surface tokens, bare alpha knobs and the settings preview\n * included). Single-sourced here so the live painter and the preview card\n * cannot drift.\n */\nfunction glassBaseAlpha(panelOpacity: number, inDark: boolean): number {\n return glassAlpha(panelOpacity) * (inDark ? 0.4 : 0.8)\n}\n\n/**\n * The theme-aware glass surface alphas for one panelOpacity: the input-surface\n * token and the bubble token share one curve, dimmed per scheme. Shared by the\n * live painter and the settings-row preview card so the two cannot drift.\n */\nfunction glassSurfaceAlphas(panelOpacity: number, inDark: boolean): { major: string; bubble: string } {\n const alpha = glassBaseAlpha(panelOpacity, inDark)\n return {\n major: `rgba(255, 255, 255, ${alpha.toFixed(3)})`,\n bubble: `rgba(255, 255, 255, ${(alpha * 0.8).toFixed(3)})`,\n }\n}\n\n/**\n * Resolve the effective background image url for a section.\n * @param settings - resolved background section.\n * @returns the image url, or '' when no source is set.\n */\nexport function backgroundImageUrl(settings: BackgroundSettings, uploadBase: string): string {\n if (settings.uploadId) return `${uploadBase}/image/${settings.uploadId}`\n return settings.url\n}\n\n/**\n * Manage the behind-body wallpaper + scrim layers and the glass surface.\n */\nexport class BackgroundPainter {\n private layer: HTMLDivElement | null = null\n private img: HTMLImageElement | null = null\n private scrim: HTMLDivElement | null = null\n private observer: MutationObserver | undefined\n private active = false\n private settings: BackgroundSettings | undefined\n /** Whether the current section resolves to an actual image source. */\n private hasSource = false\n private readonly savedTokens = new Map<string, string>()\n private readonly savedVars = new Map<string, string>()\n\n /** Remember + hijack a property only once so dispose restores the original. */\n private rememberOnce(prop: string): void {\n if (this.savedTokens.has(prop) || this.savedVars.has(prop)) return\n const value = document.body.style.getPropertyValue(prop)\n if (GLASS_TOKENS.includes(prop)) {\n this.savedTokens.set(prop, value)\n } else {\n this.savedVars.set(prop, value)\n }\n }\n\n /**\n * Apply the latest settings. Idempotent.\n * @param settings - resolved background section.\n * @param uploadBase - the API base for resolving uploads.\n */\n apply(settings: BackgroundSettings, uploadBase: string): void {\n this.settings = settings\n if (!settings.enabled) {\n this.dispose()\n return\n }\n injectBackgroundCss()\n\n const url = backgroundImageUrl(settings, uploadBase)\n if (url === '') {\n // No source: the frame transparency is owned only while a wallpaper\n // paints — hand the base/sidebar fills back (a sourceless \"enabled\"\n // section must never leave transparent chrome over the bare page), and\n // drop the theme observer + glass tokens whose flip handler would\n // otherwise re-write translucent panels over that bare page too.\n this.observer?.disconnect()\n this.observer = undefined\n this.restoreFrameTokens()\n this.applySurfaceGlass(settings, true)\n this.active = false\n this.hasSource = false\n this.removeLayers()\n document.body.removeAttribute(ACTIVE_ATTR)\n return\n }\n\n // Transparent frame: all columns share the wallpaper behind the UI.\n for (const prop of FRAME_TOKENS) {\n this.rememberOnce(prop)\n document.body.style.setProperty(prop, 'transparent')\n }\n\n // Wallpaper layer + scrim.\n if (!this.layer) {\n this.layer = document.createElement('div')\n this.layer.className = LAYER_CLASS\n this.img = document.createElement('img')\n this.img.className = IMAGE_CLASS\n this.img.alt = ''\n // Remote-url sources must not leak the local origin via Referer.\n this.img.referrerPolicy = 'no-referrer'\n // A dead source (404 / network error / revoked upload) must not leave a\n // blank frozen backdrop: hide the broken image once (the scrim and\n // glass still paint) and say so once on the console. Switching to a\n // different URL below clears the failure state and retries.\n this.img.onerror = () => {\n if (this.img === null || this.img.dataset.failed === '1') return\n this.img.dataset.failed = '1'\n this.img.style.visibility = 'hidden'\n console.warn('[deepseek-harness-background] wallpaper image failed to load:', this.img.getAttribute('src'))\n }\n this.layer.appendChild(this.img)\n document.body.appendChild(this.layer)\n }\n // Compare the *attribute* (the raw value we set) rather than the .src\n // property, which resolves to an absolute URL and would make this check\n // always-true for relative urls. Same-value writes are no-ops in the DOM,\n // so this is a clarity fix, not a behavior fix.\n if (this.img && this.img.getAttribute('src') !== url) {\n delete this.img.dataset.failed\n this.img.style.visibility = ''\n this.img.src = url\n }\n\n if (!this.scrim) {\n this.scrim = document.createElement('div')\n this.scrim.className = SCRIM_CLASS\n document.body.appendChild(this.scrim)\n }\n document.body.setAttribute(ACTIVE_ATTR, 'on')\n this.active = true\n this.hasSource = true\n\n // Push the knobs into CSS variables.\n const s = document.body.style\n this.setVar('--bg-object-fit', settings.fit)\n this.setVar('--bg-opacity', String(settings.opacity))\n this.setVar('--bg-wallpaper-blur', `${settings.wallpaperBlur}px`)\n this.setVar('--bg-wallpaper-scale', (1 + settings.wallpaperBlur * 0.006).toFixed(4))\n this.setVar('--bg-scrim', String(settings.scrim))\n this.setVar('--bg-glass-blur', `${settings.blur}px`)\n this.setVar('--bg-glass-saturate', String(glassSaturate(settings.blur)))\n\n // Glass surface: theme-aware translucent token (panelOpacity === 1 keeps\n // the official opaque surfaces and disables the blur).\n this.applySurfaceGlass(settings, false)\n\n // Theme observer: repaint the glass tokens when the light/dark scheme\n // flips (the surface translucency differs between schemes). Disconnects\n // on dispose.\n if (!this.observer) {\n this.observer = new MutationObserver(() => this.applySurfaceGlass(this.settings, false))\n this.observer.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })\n }\n }\n\n /** Write one owned CSS variable onto body. */\n private setVar(name: string, value: string): void {\n this.rememberOnce(name)\n document.body.style.setProperty(name, value)\n }\n\n /**\n * Return the owned frame-transparency properties to their saved state (or\n * remove them when this session never overrode them). dispose() restores\n * everything wholesale; this targeted variant serves the sourceless path,\n * where the painter keeps running for a later source.\n */\n private restoreFrameTokens(): void {\n for (const prop of FRAME_TOKENS) {\n this.rememberOnce(prop)\n const original = this.savedVars.get(prop)\n if (original !== undefined && original !== '') document.body.style.setProperty(prop, original)\n else document.body.style.removeProperty(prop)\n }\n }\n\n /**\n * Update one effect knob without re-running the full apply. This is the hot\n * path for slider drags (many events per second): only the single CSS\n * variable changes, the DOM layers are untouched.\n * @param key - the knob to update.\n * @param value - its new value in its canonical unit.\n */\n setKnob(key: 'opacity' | 'scrim' | 'panelOpacity' | 'blur' | 'wallpaperBlur' | 'fit', value: number | BackgroundSettings['fit']): void {\n if (this.settings === undefined || !this.settings.enabled) return\n switch (key) {\n case 'opacity':\n this.setVar('--bg-opacity', String(value))\n break\n case 'scrim':\n this.setVar('--bg-scrim', String(value))\n break\n case 'panelOpacity': {\n // The glass surface tokens depend on the panel opacity; repaint ONLY\n // them (the slider hot path — no layer churn, no full apply). The\n // stored section adopts the value so a later theme flip repaints with\n // the fresh opacity, and a sourceless section keeps the official\n // opaque surfaces (forceRestore) exactly like the full apply does.\n const next = { ...this.settings, panelOpacity: Number(value) }\n this.settings = next\n this.applySurfaceGlass(next, !this.hasSource)\n break\n }\n case 'blur': {\n this.setVar('--bg-glass-blur', `${value}px`)\n this.setVar('--bg-glass-saturate', String(glassSaturate(Number(value))))\n break\n }\n case 'wallpaperBlur': {\n this.setVar('--bg-wallpaper-blur', `${value}px`)\n this.setVar('--bg-wallpaper-scale', (1 + Number(value) * 0.006).toFixed(4))\n break\n }\n case 'fit':\n this.setVar('--bg-object-fit', String(value))\n break\n }\n }\n\n /**\n * Apply the translucent-glass surface tokens (or restore the official ones\n * when the panel is fully opaque / there is no background).\n * @param settings - resolved section (for panelOpacity).\n * @param forceRestore - true to restore official surfaces regardless (used\n * when no source is set, so leftover glass tokens never linger).\n */\n private applySurfaceGlass(settings: BackgroundSettings | undefined, forceRestore: boolean): void {\n const s = document.body.style\n const inDark = document.body.dataset.dsDarkTheme !== undefined\n // Always remember the original surface values so a restore / dispose\n // returns them cleanly.\n for (const token of GLASS_TOKENS) this.rememberOnce(token)\n\n const restore = () => {\n for (const token of GLASS_TOKENS) {\n const original = this.savedTokens.get(token)\n if (original !== undefined && original !== '') s.setProperty(token, original)\n else s.removeProperty(token)\n }\n }\n\n if (forceRestore || settings === undefined || settings.panelOpacity >= 1) {\n restore()\n document.body.removeAttribute(GLASS_ATTR)\n // No blur and a neutral exposure when glass is off; the bare alpha\n // knobs only matter under the glass gate.\n this.setVar('--bg-glass-blur', '0px')\n this.setVar('--bg-glass-brightness', '1')\n s.removeProperty('--bg-glass-alpha')\n s.removeProperty('--bg-glass-alpha-strong')\n return\n }\n\n document.body.setAttribute(GLASS_ATTR, 'on')\n\n // Exposure calibration (theme-aware, mirrored by the preview surface).\n const exposure = glassExposure(inDark)\n this.setVar('--bg-glass-brightness', exposure.brightness)\n this.setVar('--bg-glass-sheen', exposure.sheen)\n this.setVar('--bg-glass-sheen-mid', exposure.sheenMid)\n\n // Dark scheme uses a lower white alpha (the wallpaper stays visible\n // without washing the surface), mirroring the reference engine.\n const base = glassBaseAlpha(settings.panelOpacity, inDark)\n for (const { token, factor } of GLASS_SURFACE_TOKENS) {\n s.setProperty(token, `rgba(255, 255, 255, ${(base * factor).toFixed(3)})`)\n }\n // Bare alpha knobs for the surfaces that keep their own hue (preview\n // badge, via color-mix) or need a hover boost (chrome buttons): the same\n // curve every glass surface shares.\n this.setVar('--bg-glass-alpha', base.toFixed(3))\n this.setVar('--bg-glass-alpha-strong', Math.min(0.9, base * 1.5).toFixed(3))\n }\n\n /** Remove the wallpaper + scrim layers and clear the active attribute. */\n private removeLayers(): void {\n if (this.layer) { this.layer.remove(); this.layer = null; this.img = null }\n if (this.scrim) { this.scrim.remove(); this.scrim = null }\n }\n\n /** Restore every owned property and remove the layers. */\n dispose(): void {\n this.observer?.disconnect()\n this.observer = undefined\n this.removeLayers()\n const s = document.body.style\n document.body.removeAttribute(ACTIVE_ATTR)\n document.body.removeAttribute(GLASS_ATTR)\n for (const [prop, value] of this.savedTokens) s.setProperty(prop, value)\n for (const [prop, value] of this.savedVars) s.setProperty(prop, value)\n this.savedTokens.clear()\n this.savedVars.clear()\n this.active = false\n this.settings = undefined\n }\n\n /** @returns whether a wallpaper is currently rendered. */\n isActive(): boolean {\n return this.active\n }\n}\n\n/** Module-level painter singleton shared by the settings row preview and the live apply. */\nexport const backgroundPainter = new BackgroundPainter()\n\n/**\n * Apply the given settings through the shared painter (idempotent). Convenience\n * used by the settings row for live previews and by the plugin apply to render\n * the persisted section.\n * @param settings - settings to apply (may be an unsaved draft for preview).\n */\nexport function paintBackground(settings: BackgroundSettings): void {\n backgroundPainter.apply(settings, BACKGROUND_API_PREFIX)\n}\n\n/** Numeric effect knobs the slider drag path repaints. */\nexport type BackgroundKnob = 'opacity' | 'scrim' | 'panelOpacity' | 'blur' | 'wallpaperBlur' | 'fit'\n\n/**\n * Update one effect knob on the live backdrop without re-running the full\n * apply — the hot path for slider drags (see BackgroundPainter.setKnob).\n * @param key - the knob to update.\n * @param value - its new value.\n */\nexport function paintBackgroundKnob(key: BackgroundKnob, value: number | BackgroundSettings['fit']): void {\n backgroundPainter.setKnob(key, value)\n}\n\n/** Preview variables the settings-surface preview card consumes. */\nconst PREVIEW_VARS = [\n '--bg-opacity', '--bg-scrim', '--bg-object-fit',\n '--bg-wallpaper-blur', '--bg-wallpaper-scale',\n '--bg-glass-blur', '--bg-glass-saturate', '--bg-glass-brightness',\n '--bg-preview-glass',\n] as const\n\n/**\n * Push the effect knobs onto one element (the settings-surface preview card)\n * so dragging a slider updates the card without touching the live backdrop.\n * The card's CSS reads the same variable names as the painter writes on body.\n * @param el - the preview element.\n * @param settings - the draft settings to render (may be unsaved).\n */\nexport function paintPreviewSurface(el: HTMLElement, settings: BackgroundSettings): void {\n const s = el.style\n s.setProperty('--bg-object-fit', settings.fit)\n s.setProperty('--bg-opacity', String(settings.opacity))\n s.setProperty('--bg-scrim', String(settings.scrim))\n s.setProperty('--bg-wallpaper-blur', `${settings.wallpaperBlur}px`)\n s.setProperty('--bg-wallpaper-scale', (1 + settings.wallpaperBlur * 0.006).toFixed(4))\n // The glass bubble mirrors the live panel surface: below panelOpacity 1 its\n // alpha follows the same theme-aware curve as --dsw-specific-bubble (so the\n // panel-opacity slider is visible in the preview); at 1 the official opaque\n // surface returns — the bubble turns opaque and the blur switches off, just\n // like applySurfaceGlass does on the real backdrop.\n const inDark = document.body.dataset.dsDarkTheme !== undefined\n if (settings.panelOpacity >= 1) {\n s.setProperty('--bg-glass-blur', '0px')\n s.setProperty('--bg-glass-brightness', '1')\n s.setProperty('--bg-preview-glass', 'var(--dsw-alias-bg-layer-1)')\n return\n }\n s.setProperty('--bg-glass-blur', `${settings.blur}px`)\n s.setProperty('--bg-glass-saturate', String(glassSaturate(settings.blur)))\n // The preview filter mirrors the live exposure calibration (slightly dimmed\n // in light, lifted in dark) so the card never reads brighter than the app.\n s.setProperty('--bg-glass-brightness', glassExposure(inDark).brightness)\n // The preview bubble mirrors the live --dsw-specific-bubble token.\n s.setProperty('--bg-preview-glass', glassSurfaceAlphas(settings.panelOpacity, inDark).bubble)\n}\n\n/** Remove every preview variable from an element (restores CSS defaults). */\nexport function clearPreviewSurface(el: HTMLElement): void {\n for (const name of PREVIEW_VARS) el.style.removeProperty(name)\n}\n","\n/**\n * Frosted-glass registry — the public integration surface other plugins use\n * to join this plugin's frosted-glass system.\n *\n * Third-party panels (diff viewers, file peeks, tool cards...) often fill\n * with an official --dsw-* token that the painter already turns translucent\n * under a wallpaper, but nothing adds the shared blur/sheen chain to THEM,\n * so they read as bare transparency instead of frosted glass (or stay fully\n * opaque when they carry their own literal fills). This registry lets their\n * authors register CSS selectors; every registered surface receives the\n * exact unified recipe the whitelisted official surfaces use:\n *\n * - mode 'token' (default) — adds ONLY the wet-glass sheen gradient and the\n * shared backdrop-filter chain. Pick it when the panel already paints with\n * a token the painter overrides (--dsw-specific-input-major,\n * --dsw-specific-bubble, --dsw-alias-markdown-code-block,\n * --dsw-alias-markdown-code-block-banner, --dsw-alias-markdown-inline-code,\n * --dsw-specific-tip): its fill then follows the panel-opacity slider\n * automatically. Same treatment as the composer dock family.\n * - mode 'fill' — also takes over the fill itself\n * (background-color: var(--dsh-specific-input-major)). Pick it when the\n * panel carries its own opaque/literal background today. Same explicit\n * recipe as the chrome buttons and the lineage popover.\n *\n * Rules are generated under the body[data-dsh-bg-glass] gate, so glass\n * toggling (wallpaper disabled, source cleared, or the user maxing the\n * panel-opacity slider) restores the registered surfaces' own paints with\n * ZERO extra coordination — the gate is the same one the built-in whitelist\n * keys on.\n *\n * Transport: because plugin client bundles load in no guaranteed order, the\n * api is published on window.__DSH_BACKGROUND_GLASS__ and announced with the\n * 'dsh-background-glass:ready' CustomEvent (detail = the api). Consumers\n * either poll the global or await the event; both are covered by the\n * copy-paste helper in docs/GLASS_API.md. The whole bridge is torn down when\n * this plugin's client fiber disposes.\n */\n\nimport { GLASS_ATTR } from './background-css.ts'\n\n/** Semantic version of this bridge contract (bumped on breaking changes). */\nexport const GLASS_BRIDGE_VERSION = 1 as const\n\n/** Property name on window where the api is published. */\nexport const GLASS_BRIDGE_GLOBAL = '__DSH_BACKGROUND_GLASS__'\n\n/** DOM event announced once the api becomes available (detail = the api). */\nexport const GLASS_READY_EVENT = 'dsh-background-glass:ready'\n\n/** Style-tag dedup key of the registry stylesheet (data-plugin-css). */\nconst REGISTRY_CSS_TAG = 'deepseek-harness-background/glass-registry'\n\n/** Plugin identity stamped onto the registry <style> element. */\nconst PLUGIN_ID = 'deepseek-harness-background'\n\n/**\n * Fill strategy for a registered surface.\n * - 'token': the surface paints with an overridden --dsw-* token; only the\n * sheen + blur chain is added (its fill follows the sliders).\n * - 'fill': the surface keeps its own opaque paint today; the registry takes\n * the fill over too (--dsw-specific-input-major, the composer's white).\n */\nexport type GlassSurfaceMode = 'token' | 'fill'\n\n/** Registration payload passed to BackgroundGlassApi.register. */\nexport interface GlassSurfaceSpec {\n /**\n * Caller identity, conventionally the consumer package name (e.g.\n * 'dsh-diff-stat'). Used for diagnostics only — registrations from\n * different plugins never conflict.\n */\n plugin: string\n /** One selector or a list. Each must match a stable anchor you render. */\n selectors: string | readonly string[]\n /** Fill strategy; defaults to 'token'. */\n mode?: GlassSurfaceMode\n}\n\n/** The object published at window.__DSH_BACKGROUND_GLASS__. */\nexport interface BackgroundGlassApi {\n /** Contract version (currently GLASS_BRIDGE_VERSION). */\n readonly version: typeof GLASS_BRIDGE_VERSION\n /** Publisher identity — lets consumers assert WHO owns the global. */\n readonly bridgeId: 'deepseek-harness-background'\n /**\n * Whether the frosted glass is ON right now (wallpaper active AND\n * panelOpacity < 1). Registrations made while this is false simply wait\n * under the gate until the glass turns on.\n */\n isActive(): boolean\n /**\n * Register surfaces to receive the unified frosted-glass recipe while the\n * glass is on. Idempotent per (plugin, mode, selector): re-registering the\n * same triple replaces the earlier entry. Returns an unregister function —\n * idempotent, safe to call twice, and the right cleanup inside a cordis\n * effect/fiber dispose.\n */\n register(spec: GlassSurfaceSpec): () => void\n}\n\n/**\n * Characters whose presence would let a selector escape its rule context.\n * ',' is refused too: inside the generated rule body\n * (`body[gate] <selector> { … }`) a comma lets one registration broaden the\n * rule to ARBITRARY extra subjects (`.x, html body`) — an unintended\n * widening of the glass sheet beyond the registered surface. Multiple\n * surfaces stay supported through the selectors ARRAY.\n */\nconst FORBIDDEN_SELECTOR_CHARS = ['{', '}', ';', '@', '<', '>', ','] as const\n\n/** Hard cap on one selector's length (diagnostics-friendly, abuse-proof). */\nconst MAX_SELECTOR_LENGTH = 500\n\n/** Hard cap on selectors per registration call. */\nconst MAX_SELECTORS_PER_SPEC = 64\n\n/**\n * Validate one raw selector string. Structural checks only: anything that\n * could escape the generated rule body ({}, ;, @, <, >) is rejected, and a\n * backslash is refused too (escape sequences have no business in an anchor\n * selector); ordinary CSS mistakes (a typo'd pseudo-class) are left to the\n * browser, which drops just that one rule — a safe failure mode.\n * @returns an error message, or null when the selector is acceptable.\n */\nfunction selectorProblem(selector: unknown): string | null {\n if (typeof selector !== 'string') return 'must be a string'\n const trimmed = selector.trim()\n if (trimmed === '') return 'must not be empty'\n if (trimmed.length > MAX_SELECTOR_LENGTH) {\n return 'exceeds the ' + MAX_SELECTOR_LENGTH + '-character limit (' + trimmed.length + ')'\n }\n for (const ch of FORBIDDEN_SELECTOR_CHARS) {\n if (trimmed.includes(ch)) {\n return 'contains a forbidden character (' + JSON.stringify(ch) + ')'\n }\n }\n if (trimmed.includes(String.fromCharCode(92))) {\n return 'contains a backslash (escape sequences are not accepted)'\n }\n return null\n}\n\n/** Normalize a spec into validated (plugin, mode, selector) triples.\n * @returns the accepted triples plus one warning line per rejected selector. */\nfunction normalizeSpec(spec: GlassSurfaceSpec): {\n rows: { plugin: string; mode: GlassSurfaceMode; selector: string }[]\n warnings: string[]\n} {\n const plugin = typeof spec?.plugin === 'string' && spec.plugin.trim() !== '' ? spec.plugin.trim() : '(anonymous)'\n const mode: GlassSurfaceMode = spec?.mode === 'fill' ? 'fill' : 'token'\n const raw: unknown[] = Array.isArray(spec?.selectors) ? [...spec.selectors] : [spec?.selectors]\n const rows: { plugin: string; mode: GlassSurfaceMode; selector: string }[] = []\n const warnings: string[] = []\n if (raw.length > MAX_SELECTORS_PER_SPEC) {\n warnings.push('[glass-registry] ' + plugin + ': ' + raw.length + ' selectors exceeds the '\n + MAX_SELECTORS_PER_SPEC + '-selector cap; extra entries dropped.')\n }\n for (const item of raw.slice(0, MAX_SELECTORS_PER_SPEC)) {\n // selectorProblem already covers non-strings; the typeof check below is\n // what narrows `item` to string on the accepted path.\n const problem = selectorProblem(item)\n if (typeof item !== 'string' || problem !== null) {\n warnings.push('[glass-registry] ' + plugin + ': rejected selector '\n + JSON.stringify(item) + ' — ' + (problem ?? 'must be a string') + '.')\n continue\n }\n rows.push({ plugin, mode, selector: item.trim() })\n }\n return { rows, warnings }\n}\n\n/** The shared sheen gradient declaration (identical to the built-in sheet). */\nconst SHEEN_DECL = 'background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));'\n\n/** The shared exposure chain (blur follows the glass-blur slider). */\nconst FILTER_DECL = '-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01); backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);'\n\n/**\n * Render one registered surface as a gated CSS rule — the same recipes the\n * built-in sheets emit for their explicit-fill families.\n */\nfunction ruleFor(row: { mode: GlassSurfaceMode; selector: string }): string {\n const gate = 'body[' + GLASS_ATTR + ']'\n const fill = row.mode === 'fill'\n ? 'background-color: var(--dsw-specific-input-major); ' + SHEEN_DECL + ' ' + FILTER_DECL\n : SHEEN_DECL + ' ' + FILTER_DECL\n return gate + ' ' + row.selector + ' { ' + fill + ' }'\n}\n\n/**\n * Owns the registered surfaces and the registry stylesheet.\n */\nexport class GlassRegistry {\n private readonly entries = new Map<string, { row: { plugin: string; mode: GlassSurfaceMode; selector: string }; owner: object }>()\n private styleEl: HTMLStyleElement | null = null\n private disposed = false\n\n /**\n * Register one spec. See BackgroundGlassApi.register.\n * @param spec - the surfaces to glass.\n * @returns the unregister function (idempotent).\n */\n register(spec: GlassSurfaceSpec): () => void {\n // A disposed registry — or one superseded by a reinstall — must stay\n // INERT: accepting entries would orphan them outside the live fiber's\n // teardown (the stale api closure can outlive this plugin's disposal).\n if (this.disposed || (publishedRegistry !== undefined && publishedRegistry !== this)) {\n console.warn('[glass-registry] registration ignored: this bridge is no longer the live publication')\n return () => {}\n }\n let normalized: { rows: { plugin: string; mode: GlassSurfaceMode; selector: string }[]; warnings: string[] }\n try {\n normalized = normalizeSpec(spec)\n } catch (error) {\n // Hostile input (e.g. an array with a throwing getter) degrades to\n // warn-and-skip exactly like any other malformed spec — never throws.\n console.warn('[glass-registry] spec rejected: ' + (error instanceof Error ? error.message : String(error)))\n normalized = { rows: [], warnings: [] }\n }\n const { rows, warnings } = normalized\n for (const message of warnings) console.warn(message)\n const owner = {}\n const keys: string[] = []\n for (const row of rows) {\n // JSON encoding makes the three-part key unambiguous BY CONSTRUCTION:\n // control characters smuggled into any part cannot forge another triple.\n const key = JSON.stringify([row.plugin, row.mode, row.selector])\n this.entries.set(key, { row, owner })\n keys.push(key)\n }\n this.sync()\n let unregistered = false\n return () => {\n if (unregistered) return\n unregistered = true\n // The ownership check keeps a STALE handle from unregistering a newer\n // registration that replaced the same key (idempotent re-register).\n for (const key of keys) {\n const entry = this.entries.get(key)\n if (entry !== undefined && entry.owner === owner) this.entries.delete(key)\n }\n this.sync()\n }\n }\n\n /** Rebuild the registry stylesheet from the live entries. */\n private sync(): void {\n if (this.entries.size === 0) {\n this.styleEl?.remove()\n this.styleEl = null\n return\n }\n // Distinct rule texts only: two plugins may legitimately register the\n // same selector; cascade resolves duplicates anyway, but emitting one\n // identical rule keeps the sheet honest.\n const seen = new Set<string>()\n const rules: string[] = []\n for (const { row } of this.entries.values()) {\n const rule = ruleFor(row)\n if (!seen.has(rule)) {\n seen.add(rule)\n rules.push(rule)\n }\n }\n if (this.styleEl === null) {\n this.styleEl = document.createElement('style')\n this.styleEl.dataset.plugin = PLUGIN_ID\n this.styleEl.dataset.pluginCss = REGISTRY_CSS_TAG\n document.head.appendChild(this.styleEl)\n }\n this.styleEl.textContent = rules.join(String.fromCharCode(10))\n }\n\n /** Tear everything down: entries, stylesheet, and the window publication. */\n dispose(): void {\n this.disposed = true\n this.entries.clear()\n this.sync()\n const w = window as unknown as Record<string, unknown>\n if (w[GLASS_BRIDGE_GLOBAL] === this.api) delete w[GLASS_BRIDGE_GLOBAL]\n if (publishedRegistry === this) publishedRegistry = undefined\n }\n\n /** The published api face over this registry. */\n readonly api: BackgroundGlassApi = {\n version: GLASS_BRIDGE_VERSION,\n bridgeId: 'deepseek-harness-background',\n isActive: (): boolean =>\n typeof document !== 'undefined' && document.body !== null && document.body.hasAttribute(GLASS_ATTR),\n register: (spec: GlassSurfaceSpec): (() => void) => this.register(spec),\n }\n}\n\n/** The registry whose api is CURRENTLY published on window. Module-level on\n * purpose: the published api face deliberately carries no dispose member\n * (internal teardown stays unreachable for consumers), so reinstall-time\n * cleanup needs a handle the window key cannot provide. */\nlet publishedRegistry: GlassRegistry | undefined\n\n/**\n * Publish the glass registry bridge onto window and announce it. Safe to\n * call repeatedly: a previous publication's REGISTRY is disposed first, so\n * exactly one bridge — and one stylesheet — is ever live.\n * @returns the bridge handle — call .dispose() when this plugin's client\n * fiber tears down.\n */\nexport function installGlassBridge(): { api: BackgroundGlassApi; dispose(): void } {\n // A stale publication (hot reload of this very plugin) must not linger:\n // disposing its REGISTRY removes its stylesheet and clears its window\n // key. Dispose is idempotent, so a stale reference left behind by an\n // earlier fiber dispose is harmless to dispose again here.\n publishedRegistry?.dispose()\n // Belt-and-suspenders for the path where the module factory itself was\n // re-evaluated without a prior fiber dispose (closure state reset, so\n // publishedRegistry above is already undefined): any stylesheet carrying\n // our dedup key can only belong to a stale bridge — drop it.\n document.querySelector('style[data-plugin-css=\"' + REGISTRY_CSS_TAG + '\"]')?.remove()\n const registry = new GlassRegistry()\n publishedRegistry = registry\n ;(window as unknown as Record<string, unknown>)[GLASS_BRIDGE_GLOBAL] = registry.api\n // Announce right after publishing: consumers that already polled the\n // global see it immediately; listeners get the event synchronously now.\n window.dispatchEvent(new CustomEvent(GLASS_READY_EVENT, { detail: registry.api }))\n return { api: registry.api, dispose: () => registry.dispose() }\n}\n","/** Locale dictionaries for the background plugin. */\n\nimport type { BackgroundCardLocaleKey } from './types.ts'\n\n/** Dictionary of the namespace this plugin owns. */\nexport type BackgroundCardDict = Record<BackgroundCardLocaleKey, string>\n\n/** Chinese copy. */\nexport const zh: BackgroundCardDict = {\n 'background.title': '自定义背景',\n 'background.description': '为整个应用界面设置背景图片,可调不透明度、遮罩、面板透明与毛玻璃模糊',\n 'background.upload': '上传图片',\n 'background.uploading': '上传中…',\n 'background.uploadMediaTypes': '支持 JPG / PNG / WebP / GIF',\n 'background.urlPlaceholder': '或粘贴图片 http(s) 链接后回车',\n 'background.invalidUrl': '链接无效,请粘贴 http(s) 开头的图片链接',\n 'background.applyUrl': '应用链接',\n 'background.enabled': '背景已启用',\n 'background.source': '图片',\n 'background.effects': '效果',\n 'background.noPreview': '上传图片或粘贴链接后,此处实时预览效果',\n 'background.previewGlass': '毛玻璃',\n 'background.opacity': '不透明度',\n 'background.scrim': '遮罩',\n 'background.panelOpacity': '面板不透明度',\n 'background.blur': '毛玻璃模糊',\n 'background.wallpaperBlur': '壁纸模糊',\n 'background.fit': '填充方式',\n 'background.cover': '铺满',\n 'background.contain': '完整',\n 'background.clear': '清除背景',\n 'background.uploadFailed': '上传失败,请重试',\n 'background.saveFailed': '保存失败,请重试',\n 'background.timelineEnhance': '会话时间线增强',\n 'background.timelineEnhanceNarrowHint': '为官方会话时间线的已加载轮次补上平滑跳转',\n 'background.migratedNotice': '已从旧版 dsh 的设置中恢复你的背景设置',\n 'background.migratedDismiss': '知道了',\n}\n\n/** English copy. */\nexport const en: BackgroundCardDict = {\n 'background.title': 'Custom Background',\n 'background.description': 'Set a background image behind the whole app surface with adjustable opacity, scrim, panel transparency and glass blur',\n 'background.upload': 'Upload image',\n 'background.uploading': 'Uploading…',\n 'background.uploadMediaTypes': 'JPG / PNG / WebP / GIF',\n 'background.urlPlaceholder': 'Or paste an image http(s) link and press Enter',\n 'background.invalidUrl': 'Invalid link — paste an image link starting with http(s)',\n 'background.applyUrl': 'Apply URL',\n 'background.enabled': 'Background on',\n 'background.source': 'Image',\n 'background.effects': 'Effects',\n 'background.noPreview': 'Upload an image or paste a link to preview live here',\n 'background.previewGlass': 'Glass',\n 'background.opacity': 'Opacity',\n 'background.scrim': 'Scrim',\n 'background.panelOpacity': 'Panel opacity',\n 'background.blur': 'Glass blur',\n 'background.wallpaperBlur': 'Wallpaper blur',\n 'background.fit': 'Fit',\n 'background.cover': 'Cover',\n 'background.contain': 'Contain',\n 'background.clear': 'Clear background',\n 'background.uploadFailed': 'Upload failed, try again',\n 'background.saveFailed': 'Save failed, try again',\n 'background.timelineEnhance': 'Conversation timeline enhancement',\n 'background.timelineEnhanceNarrowHint': 'Adds smooth jumps for the rail\\'s loaded turns',\n 'background.migratedNotice': 'Restored your background settings from the previous dsh version',\n 'background.migratedDismiss': 'Got it',\n}\n","function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","/**\n * Background settings transport — reads and writes the `ui-background`\n * section, and uploads local images, through the plugin's own same-origin\n * HTTP routes (see src/routes.ts). A custom route family keeps the section\n * usable even though the api-proxy settings allowlist does not expose\n * third-party namespaces over the settings RPC. One module-level client\n * serves the painter and the settings row.\n */\n\nimport type { BackgroundSettings } from '../settings.ts'\nimport { BACKGROUND_API_PREFIX } from '../settings.ts'\n\n/** One snapshot of the section as last seen by the transport. */\nexport interface SettingsSnapshot {\n status: 'loading' | 'ready' | 'error'\n value?: BackgroundSettings\n /**\n * Set by the host on the one read that adopted a pre-0.1.7 section (dsh\n * 0.1.7 renamed `settings.yaml` away and skipped the plugin's namespace, so\n * the host pulls it back into the entry-keyed document on first read). The\n * row turns it into a one-time notice; a later boot no longer reports it.\n */\n migrated?: { from: string }\n}\n\ntype Listener = () => void\n\n/** Outcome of one save attempt: committed, displaced by a newer save (the\n * caller must not adopt its own section back into the draft), or rejected. */\nexport type SaveResult = 'ok' | 'superseded' | 'failed'\n\n/**\n * Observable settings client (getSnapshot/subscribe — the uSES currency).\n * Methods are arrow properties so React's useSyncExternalStore can call them\n * without an owning `this`.\n */\nexport class SettingsClient {\n private snapshot: SettingsSnapshot = { status: 'loading' }\n private readonly listeners = new Set<Listener>()\n /** Monotonic request id; the latest save wins even when POSTs interleave. */\n private saveSeq = 0\n /** Pending save promise (used by the row's flush-on-unmount). */\n private pending: Promise<SaveResult> | undefined\n\n /** @returns the current sync snapshot (stable reference until the next change). */\n getSnapshot = (): SettingsSnapshot => this.snapshot\n\n /** Observe snapshot replacements. @returns the disposer removing this listener. */\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener()\n }\n\n /** Await any in-flight save (used before teardown so edits are not dropped). */\n async flush(): Promise<void> {\n await this.pending\n }\n\n /** Fetch the section from the host. */\n async load(): Promise<void> {\n try {\n const response = await fetch(`${BACKGROUND_API_PREFIX}/settings`)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const body = await response.json() as {\n ok: boolean\n value?: BackgroundSettings\n migrated?: { from: string }\n }\n if (!body.ok || body.value === undefined) throw new Error('unexpected payload')\n this.snapshot = {\n status: 'ready',\n value: body.value,\n ...(body.migrated === undefined ? {} : { migrated: body.migrated }),\n }\n } catch {\n this.snapshot = { status: 'error' }\n }\n this.notify()\n }\n\n /**\n * Persist the section through the host route. Concurrent calls are\n * serialized by a request sequence: an older save that resolves after a\n * newer one answers 'superseded' instead of 'ok', so the caller can never\n * mistake a stale write for success and adopt its outdated section.\n * @param section - the complete next section.\n * @returns whether the write committed, was displaced by a newer save, or failed.\n */\n async save(section: BackgroundSettings): Promise<SaveResult> {\n const seq = ++this.saveSeq\n const run = async (): Promise<SaveResult> => {\n try {\n const response = await fetch(`${BACKGROUND_API_PREFIX}/settings`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(section),\n })\n const body = await response.json() as { ok: boolean; value?: BackgroundSettings }\n if (!response.ok || !body.ok || body.value === undefined) return 'failed'\n // Discard the response of a superseded save — it reflects an older\n // document that must not overwrite the latest one.\n if (seq !== this.saveSeq) return 'superseded'\n // A save is not a reload: carry the migration notice the load adopted\n // so it survives the user's first interaction instead of blinking out.\n this.snapshot = {\n status: 'ready',\n value: body.value,\n ...(this.snapshot.migrated === undefined ? {} : { migrated: this.snapshot.migrated }),\n }\n this.notify()\n return 'ok'\n } catch {\n return 'failed'\n }\n }\n const promise = run()\n this.pending = promise\n // Release the handle once settled so flush() never awaits a stale save\n // from a long-finished gesture (a later flush then resolves instantly).\n void promise.then(() => {\n if (this.pending === promise) this.pending = undefined\n })\n return promise\n }\n\n /**\n * Upload a local image file; on success stores its id into the snapshot and\n * returns the resolved background url (or null on failure).\n * @param file - the chosen image file.\n * @returns the resolved image url, or null.\n */\n async upload(file: File): Promise<string | null> {\n try {\n const response = await fetch(`${BACKGROUND_API_PREFIX}/upload`, {\n method: 'POST',\n headers: { 'content-type': file.type },\n body: file,\n })\n const body = await response.json() as { ok: boolean; id?: string; url?: string }\n if (!response.ok || !body.ok || body.id === undefined || body.url === undefined) return null\n return body.url ?? null\n } catch {\n return null\n }\n }\n}\n\n/** Shared client for the painter and the settings row. */\nexport const settingsClient = new SettingsClient()\n","/**\n * Background settings row — the editable form rendered inside the General\n * settings section (`settings.general.item` slot), in the same visual area as\n * the official Appearance row. Layout mirrors the reference wallpaper engine\n * picker (preview surface + grouped sections), styled with the harness alias\n * tokens so it reads as part of the settings chrome.\n *\n * Interaction model (fixes the previous jank):\n * - Sliders STEP in 5% (or 2px/5px) increments, so dragging snaps between\n * discrete values instead of firing dozens of sub-percent updates.\n * - While dragging, only the CSS variables change: the slider holds its own\n * state (no parent re-render) and the preview card repaints in isolation.\n * React's onChange maps to the native `input` event and would fire on every\n * step, so a commit is deferred to release (pointerup / keyup / blur) and\n * debounced — one POST per gesture, never per tick.\n * - A `lastSavedRef` distinguishes our own saves from external changes, so\n * the snapshot sync can never clobber a draft the user is still editing.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ChangeEvent } from 'react'\nimport { useSyncExternalStore } from 'react'\nimport type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport clsx from 'clsx'\nimport {\n BACKGROUND_API_PREFIX, BLUR_MAX, BLUR_MIN, DEFAULT_BLUR, DEFAULT_FIT,\n DEFAULT_OPACITY, DEFAULT_PANEL_OPACITY, DEFAULT_SCRIM, DEFAULT_TIMELINE,\n DEFAULT_WALLPAPER_BLUR, FIT_MODES, OPACITY_MAX, OPACITY_MIN, PANEL_OPACITY_MAX,\n PANEL_OPACITY_MIN, SCRIM_MAX, SCRIM_MIN, WALLPAPER_BLUR_MAX, type BackgroundFit,\n type BackgroundSettings,\n} from '../settings.ts'\nimport { clearPreviewSurface, paintBackground, paintBackgroundKnob, paintPreviewSurface } from './backdrop.ts'\nimport { settingsClient, type SettingsSnapshot } from './settings-client.ts'\nimport css from './SettingsRow.module.css'\n\n/** Full component props: runtime share + locale seat. */\nexport type BackgroundRowProps =\n PropsRuntime<'settings.general.item'> & PropsLocale<'ui-background'>\n\n/** Defaults mirror the host schema (also the pre-load draft fallback). */\nconst DEFAULTS: BackgroundSettings = {\n enabled: false,\n uploadId: '',\n url: '',\n opacity: DEFAULT_OPACITY,\n scrim: DEFAULT_SCRIM,\n panelOpacity: DEFAULT_PANEL_OPACITY,\n blur: DEFAULT_BLUR,\n wallpaperBlur: DEFAULT_WALLPAPER_BLUR,\n fit: DEFAULT_FIT,\n timeline: DEFAULT_TIMELINE,\n}\n\n/** The numeric field keys a slider edits. */\ntype NumField = 'opacity' | 'scrim' | 'panelOpacity' | 'blur' | 'wallpaperBlur'\n\n/** Per-field slider geometry: 5% steps for ratios, fine px steps for radii so\n * the two blur sliders feel smooth near small values. Bounds come from the\n * shared settings constants so the schema boundary and the UI cannot drift. */\nconst SLIDER_SPEC: Record<NumField, { min: number; max: number; step: number; unit: 'percent' | 'px' }> = {\n opacity: { min: OPACITY_MIN, max: OPACITY_MAX, step: 0.05, unit: 'percent' },\n scrim: { min: SCRIM_MIN, max: SCRIM_MAX, step: 0.05, unit: 'percent' },\n panelOpacity: { min: PANEL_OPACITY_MIN, max: PANEL_OPACITY_MAX, step: 0.05, unit: 'percent' },\n blur: { min: BLUR_MIN, max: BLUR_MAX, step: 1, unit: 'px' },\n wallpaperBlur: { min: BLUR_MIN, max: WALLPAPER_BLUR_MAX, step: 2, unit: 'px' },\n}\n\n/** Derive the bare upload id from a resolve url like /api/bg-wallpaper/image/<id>. */\nfunction stripId(url: string): string {\n const prefix = `${BACKGROUND_API_PREFIX}/image/`\n return url.startsWith(prefix) ? url.slice(prefix.length) : ''\n}\n\n/** Read the settings store (uSES-friendly). */\nfunction readSnapshot(): SettingsSnapshot {\n return settingsClient.getSnapshot()\n}\n\n/** Field-wise equality for distinguishing our own saves from external changes. */\nfunction sameSettings(a: BackgroundSettings, b: BackgroundSettings): boolean {\n return a.enabled === b.enabled && a.uploadId === b.uploadId && a.url === b.url\n && a.opacity === b.opacity && a.scrim === b.scrim && a.panelOpacity === b.panelOpacity\n && a.blur === b.blur && a.wallpaperBlur === b.wallpaperBlur && a.fit === b.fit\n && a.timeline === b.timeline\n}\n\n/**\n * A stepped range control. While dragging it re-renders only itself: the value\n * is local state, `onLive(name, value)` repaints (CSS variables, no React\n * state), and `onCommit()` fires once on release through a debounced save.\n */\nfunction ControlSlider(props: {\n name: NumField\n label: string\n value: number\n onLive: (name: NumField, value: number) => void\n onCommit: () => void\n}) {\n const { name, label, value, onLive, onCommit } = props\n const spec = SLIDER_SPEC[name]\n const [local, setLocal] = useState(value)\n const localRef = useRef(value)\n const dragging = useRef(false)\n const onLiveRef = useRef(onLive)\n const onCommitRef = useRef(onCommit)\n onLiveRef.current = onLive\n onCommitRef.current = onCommit\n\n // Adopt an external value (load / save) when not mid-drag.\n useEffect(() => {\n if (!dragging.current && localRef.current !== value) {\n localRef.current = value\n setLocal(value)\n }\n }, [value])\n\n const handleLive = (e: ChangeEvent<HTMLInputElement>): void => {\n dragging.current = true\n const v = Number(e.currentTarget.value)\n localRef.current = v\n setLocal(v)\n syncTrackFill(e.currentTarget)\n onLiveRef.current(name, v)\n }\n\n const handleCommit = (): void => {\n if (!dragging.current) return\n dragging.current = false\n onCommitRef.current()\n }\n\n const text = spec.unit === 'percent'\n ? `${Math.round(local * 100)}%`\n : `${local}px`\n\n return (\n <div className={css.sliderRow}>\n <span className={css.sliderLabel}>{label}</span>\n <input\n type=\"range\"\n className={css.slider}\n ref={(el) => {\n // Sync the \"filled\" track fraction on mount and on every external\n // value adoption (the webkit track reads --bg-fill; moz draws its\n // own ::-moz-range-progress).\n if (el) syncTrackFill(el)\n }}\n min={spec.min} max={spec.max} step={spec.step}\n value={String(local)}\n aria-label={label}\n onInput={handleLive}\n onPointerUp={handleCommit}\n onKeyUp={(e) => {\n if (e.key.startsWith('Arrow') || e.key === 'Home' || e.key === 'End'\n || e.key === 'PageUp' || e.key === 'PageDown') handleCommit()\n }}\n onBlur={handleCommit}\n />\n <span className={css.sliderValue}>{text}</span>\n </div>\n )\n}\n\n/** Write the filled-track fraction (%) of a range input into its --bg-fill\n * variable (webkit has no range-progress pseudo-element; moz draws its own). */\nfunction syncTrackFill(el: HTMLInputElement): void {\n const min = Number(el.min)\n const max = Number(el.max)\n const value = Number(el.value)\n const pct = max === min ? 0 : ((value - min) / (max - min)) * 100\n el.style.setProperty('--bg-fill', `${pct}%`)\n}\n\n/**\n * The background settings row. A draft mirrors the persisted section; dragging\n * repaints the preview card and the live backdrop through CSS variables, and a\n * release commits once through the plugin's own host route.\n */\nexport function BackgroundSettingsRow({ t }: BackgroundRowProps) {\n const snapshot = useSyncExternalStore(settingsClient.subscribe, readSnapshot)\n // Supported dsh baseline >= 0.1.2-rc.1: the official frame-style rail is\n // always present, so the row copy is the fixed \"enhancement\" form — no\n // session scope needed here to detect anything.\n const timelineLabelKey = 'background.timelineEnhance'\n const timelineHintKey = 'background.timelineEnhanceNarrowHint'\n const [draft, setDraft] = useState<BackgroundSettings>(DEFAULTS)\n const [uploading, setUploading] = useState(false)\n const [urlText, setUrlText] = useState('')\n const [error, setError] = useState('')\n // The host reports the adopted pre-0.1.7 section on the read that migrated\n // it; the notice is one-shot and the user closes it.\n const [noticeDismissed, setNoticeDismissed] = useState(false)\n const fileInput = useRef<HTMLInputElement>(null)\n const previewRef = useRef<HTMLDivElement>(null)\n // The latest draft, read by every handler (never a stale closure).\n const draftRef = useRef<BackgroundSettings>(DEFAULTS)\n // True while a drag/gesture is in flight: hold off snapshot sync.\n const editingRef = useRef(false)\n // The last section this row saved; external changes are snapshots that differ.\n const lastSavedRef = useRef<BackgroundSettings | null>(null)\n // True while an edit has not yet reached the host (drives flush-on-unmount).\n const dirtyRef = useRef(false)\n const commitTimer = useRef<number | undefined>(undefined)\n const editingTimer = useRef<number | undefined>(undefined)\n\n /** Repaint the live backdrop + preview card from one draft (no React state). */\n const paintLive = useCallback((next: BackgroundSettings): void => {\n paintBackground(next)\n if (previewRef.current) paintPreviewSurface(previewRef.current, next)\n }, [])\n\n /** Mark the row as being edited; clears after a quiet 600ms window. */\n const markEditing = useCallback((): void => {\n editingRef.current = true\n clearTimeout(editingTimer.current)\n editingTimer.current = window.setTimeout(() => { editingRef.current = false }, 600)\n }, [])\n\n /** Slider live handler (stable identity for the memoized sliders). */\n const onLive = useCallback((key: NumField, value: number): void => {\n const next = { ...draftRef.current, [key]: value }\n draftRef.current = next\n markEditing()\n // Hot path: every knob repaints through the painter's single-variable\n // writer (no DOM layer churn); panelOpacity goes through the knob path\n // too — it repaints only the glass surface tokens instead of re-running\n // the full apply.\n paintBackgroundKnob(key, value)\n if (previewRef.current) paintPreviewSurface(previewRef.current, next)\n }, [markEditing])\n\n /** Persist one section through the host route and adopt it on success. */\n const saveNow = useCallback(async (next: BackgroundSettings): Promise<void> => {\n dirtyRef.current = true\n const result = await settingsClient.save(next)\n if (result === 'failed') {\n // A failed save has settled (no in-flight write left to flush), so the\n // dirty flag must drop with it — keeping it would make the unmount\n // flush wait for a long-settled promise and re-run saveNow on a draft\n // the host already rejected.\n dirtyRef.current = false\n setError(t('background.saveFailed'))\n return\n }\n // A superseded save lost to a newer in-flight one, which now owns BOTH\n // the snapshot adoption and the dirty flag (cleared when IT commits).\n // Clearing the flag here would strand that write, and adopting this\n // response would resurrect an outdated section — so keep it set.\n if (result === 'superseded') return\n dirtyRef.current = false\n setError('')\n lastSavedRef.current = next\n draftRef.current = next\n setDraft(next)\n if (!next.uploadId) setUrlText(next.url)\n }, [t])\n\n // The latest saveNow, readable from stable callbacks and the unmount\n // cleanup (never a stale closure).\n const saveNowRef = useRef(saveNow)\n saveNowRef.current = saveNow\n\n /** Debounced commit: one POST per drag gesture, from the latest draft. */\n const onCommit = useCallback((): void => {\n clearTimeout(commitTimer.current)\n commitTimer.current = window.setTimeout(() => {\n // Reset before saving: a fired timer must never look like an armed one,\n // or the unmount flush would re-save an already-committed gesture.\n commitTimer.current = undefined\n void saveNowRef.current(draftRef.current)\n }, 120)\n }, [])\n\n // Sync the draft from the persisted value when it changes externally, but\n // never overwrite a draft the user is mid-editing.\n useEffect(() => {\n if (snapshot.status !== 'ready' || snapshot.value === undefined) return\n const saved = lastSavedRef.current\n const external = saved === null || !sameSettings(saved, snapshot.value)\n if (external && !editingRef.current) {\n lastSavedRef.current = snapshot.value\n draftRef.current = snapshot.value\n setDraft(snapshot.value)\n setUrlText(snapshot.value.uploadId ? '' : snapshot.value.url)\n if (previewRef.current) paintPreviewSurface(previewRef.current, snapshot.value)\n }\n }, [snapshot])\n\n // Teardown: a pending debounced commit must not die with the row. If the\n // timer is still armed, fire the save NOW (the timer callback would never\n // run after unmount, and dirtyRef is only set inside saveNow — clearing\n // the timer alone would silently drop the last slider adjustment). Then\n // wait out any save already in flight. The effect runs once per mount:\n // saveNow is reached through its ref, so an identity change mid-session\n // cannot re-trigger this teardown, and editingRef is reset so a cleared\n // editing window can never block snapshot sync forever.\n useEffect(() => () => {\n clearTimeout(editingTimer.current)\n editingRef.current = false\n if (commitTimer.current !== undefined) {\n clearTimeout(commitTimer.current)\n commitTimer.current = undefined\n void saveNowRef.current(draftRef.current)\n }\n if (dirtyRef.current) void settingsClient.flush()\n }, [])\n\n const handleFile = async (file?: File): Promise<void> => {\n if (!file) return\n markEditing()\n setUploading(true)\n setError('')\n try {\n const url = await settingsClient.upload(file)\n if (url === null) {\n setError(t('background.uploadFailed'))\n return\n }\n const next: BackgroundSettings = { ...draftRef.current, uploadId: stripId(url), url: '', enabled: true }\n draftRef.current = next\n setDraft(next)\n setUrlText('')\n paintLive(next)\n await saveNow(next)\n } finally {\n setUploading(false)\n }\n }\n\n const handleUrlSubmit = (): void => {\n const url = urlText.trim()\n if (!/^https?:\\/\\//i.test(url)) {\n setError(t('background.invalidUrl'))\n return\n }\n markEditing()\n const next: BackgroundSettings = { ...draftRef.current, uploadId: '', url, enabled: true }\n draftRef.current = next\n setDraft(next)\n paintLive(next)\n void saveNow(next)\n }\n\n const handleClear = (): void => {\n const next: BackgroundSettings = { ...draftRef.current, uploadId: '', url: '', enabled: false }\n draftRef.current = next\n setDraft(next)\n setUrlText('')\n if (previewRef.current) clearPreviewSurface(previewRef.current)\n paintBackground(next)\n void saveNow(next)\n }\n\n const handleFit = (mode: BackgroundFit): void => {\n markEditing()\n const next: BackgroundSettings = { ...draftRef.current, fit: mode }\n draftRef.current = next\n setDraft(next)\n paintLive(next)\n void saveNow(next)\n }\n\n const handleTimelineToggle = (): void => {\n markEditing()\n const next: BackgroundSettings = { ...draftRef.current, timeline: !draftRef.current.timeline }\n draftRef.current = next\n setDraft(next)\n // The rail reads the shared snapshot itself; no painter repaint needed.\n void saveNow(next)\n }\n\n const sourceUrl = draft.uploadId ? `${BACKGROUND_API_PREFIX}/image/${draft.uploadId}` : draft.url\n const hasImage = sourceUrl !== ''\n\n return (\n <div className={css.row}>\n <div className={css.header}>\n <div className={css.titleRow}>\n <div className={css.title}>{t('background.title')}</div>\n {hasImage && (\n <button type=\"button\" className={clsx(css.btn, css.btnDanger, css.clearBtn)} onClick={handleClear}>\n {t('background.clear')}\n </button>\n )}\n </div>\n <div className={css.desc}>{t('background.description')}</div>\n </div>\n\n {/* One-shot notice: the host adopted a section written by dsh <= 0.1.6. */}\n {snapshot.migrated !== undefined && !noticeDismissed && (\n <div className={css.notice} role=\"status\">\n <span className={css.noticeText}>{t('background.migratedNotice')}</span>\n <button type=\"button\" className={css.noticeBtn} onClick={() => setNoticeDismissed(true)}>\n {t('background.migratedDismiss')}\n </button>\n </div>\n )}\n\n {/* Live preview surface: image + theme-aware scrim + frosted glass bubble. */}\n <div className={css.preview} ref={previewRef}>\n {hasImage\n ? (\n <>\n <img className={css.previewImg} src={sourceUrl} alt=\"\" draggable={false} referrerPolicy=\"no-referrer\" />\n <div className={css.previewScrim} />\n <div className={css.previewGlass}>\n <span className={css.previewGlassText}>{t('background.previewGlass')}</span>\n </div>\n </>\n )\n : (\n <div className={css.previewEmpty}>{t('background.noPreview')}</div>\n )}\n </div>\n\n {/* Image source: upload or paste an http(s) link. */}\n <div className={css.section}>\n <div className={css.sectionLabel}>{t('background.source')}</div>\n <div className={css.sourceRow}>\n <input\n type=\"file\"\n ref={fileInput}\n hidden\n accept=\"image/jpeg,image/png,image/webp,image/gif\"\n onChange={(e) => {\n void handleFile(e.currentTarget.files?.[0])\n e.currentTarget.value = ''\n }}\n />\n <button\n type=\"button\"\n className={clsx(css.btn, css.btnPrimary)}\n disabled={uploading}\n onClick={() => fileInput.current?.click()}\n >\n {uploading ? t('background.uploading') : t('background.upload')}\n </button>\n <span className={css.hint}>{t('background.uploadMediaTypes')}</span>\n {draft.uploadId && <span className={css.enabledBadge}>✓ {t('background.enabled')}</span>}\n </div>\n <div className={css.urlRow}>\n <input\n className={css.urlInput}\n value={urlText}\n placeholder={t('background.urlPlaceholder')}\n onKeyDown={(e) => { if (e.key === 'Enter') handleUrlSubmit() }}\n onChange={(e) => setUrlText(e.currentTarget.value)}\n />\n <button\n type=\"button\"\n className={css.btn}\n disabled={!urlText.trim()}\n onClick={handleUrlSubmit}\n >\n {t('background.applyUrl')}\n </button>\n </div>\n </div>\n\n {/* Effects: stepped sliders + fit segmented control. */}\n <div className={css.section}>\n <div className={css.sectionLabel}>{t('background.effects')}</div>\n <ControlSlider name=\"opacity\" label={t('background.opacity')} value={draft.opacity} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"scrim\" label={t('background.scrim')} value={draft.scrim} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"panelOpacity\" label={t('background.panelOpacity')} value={draft.panelOpacity} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"blur\" label={t('background.blur')} value={draft.blur} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"wallpaperBlur\" label={t('background.wallpaperBlur')} value={draft.wallpaperBlur} onLive={onLive} onCommit={onCommit} />\n <div className={css.segRow}>\n <span className={css.segLabel}>{t('background.fit')}</span>\n <div className={css.segGroup} role=\"group\" aria-label={t('background.fit')}>\n {FIT_MODES.map((mode) => (\n <button\n key={mode}\n type=\"button\"\n className={clsx(css.segBtn, draft.fit === mode && css.segActive)}\n onClick={() => handleFit(mode)}\n >\n {t(mode === 'cover' ? 'background.cover' : 'background.contain')}\n </button>\n ))}\n </div>\n </div>\n <div className={css.segRow}>\n <span className={css.segLabel}>{t(timelineLabelKey)}</span>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={draft.timeline}\n aria-label={t(timelineLabelKey)}\n title={t(timelineHintKey)}\n className={css.switch}\n onClick={handleTimelineToggle}\n >\n <span className={css.switchKnob} />\n </button>\n <span className={css.hint}>{t(timelineHintKey)}</span>\n </div>\n </div>\n\n {error && <div className={css.error} role=\"alert\">{error}</div>}\n\n {hasImage && (\n <div className={css.clearRow}>\n <button type=\"button\" className={clsx(css.btn, css.btnDanger)} onClick={handleClear}>\n {t('background.clear')}\n </button>\n </div>\n )}\n </div>\n )\n}\n","/**\r\n * Pointer/keyboard resolution for the official turn rail — the enhancer\r\n * borrows the official rail and resolves a gesture to the same entry the\r\n * kernel would have picked.\r\n *\r\n * The official rail owns pointer input for its whole column: the marks carry\r\n * `pointer-events: none` and only paint, so a MOUSE gesture arrives at the\r\n * `<nav>` with coordinates and must be mapped back to an index by geometry.\r\n * A KEYBOARD gesture instead activates the focused mark button, which carries\r\n * no index — it is recovered from the button's position among its siblings.\r\n *\r\n * Geometry contract (dsh >= 0.1.2-alpha.3, unchanged on 0.1.2-rc.1): the rail\r\n * is the frame-style generation — fixed 10px pitch, marks scroll INSIDE the\r\n * frame, and the scroller offset is published inline as `--turn-scroll-top`.\r\n * The frame property is checked before mapping (never a version compare): a\r\n * rail that does not publish it is not the supported generation, and the\r\n * enhancer stands down and lets the kernel own the gesture.\r\n */\r\n\r\nimport type { OfficialNavigationItem, TurnRailLadderItem } from './types.ts'\r\n\r\n/** Resting gap between neighbouring marks. */\r\nexport const TICK_SPACING_PX = 10\r\n/** Rail padding above the first mark and below the last one, per end. */\r\nexport const RAIL_INSET_PX = 6\r\n\r\n/**\r\n * The rail's own inset, read off the custom property the official component\r\n * publishes (`--turn-rail-inset`). Falls back to the official constant.\r\n * @param rail - the rail element (or null).\r\n */\r\nexport function railInsetOf(rail: HTMLElement | null): number {\r\n if (rail === null) return RAIL_INSET_PX\r\n const px = Number.parseFloat(rail.style.getPropertyValue('--turn-rail-inset'))\r\n return Number.isFinite(px) && px >= 0 ? px : RAIL_INSET_PX\r\n}\r\n\r\n/**\r\n * Whether `rail` is the frame-style rail (dsh >= 0.1.2-alpha.3): marks sit\r\n * at a fixed pitch and scroll INSIDE the frame, whose scroller offset the\r\n * component publishes inline as `--turn-scroll-top` — every render, resting\r\n * at `0px` included. The compressing alpha.1/2 rail never publishes that\r\n * property, so its presence is the generation test. The legacy port paints\r\n * its own `--dsbt-*` properties and can never trip this.\r\n * @param rail - the rail element (or null).\r\n */\r\nexport function isFrameRail(rail: HTMLElement | null): boolean {\r\n return rail !== null && rail.style.getPropertyValue('--turn-scroll-top') !== ''\r\n}\r\n\r\n/**\r\n * Resolve the entry under a pointer Y on the frame-style rail — the official\r\n * alpha.3 geometry verbatim (`itemAtPointer` in that generation's\r\n * TurnNavigator.tsx): marks live in the scrolled ladder at a fixed 10px\r\n * pitch, so the click offset adds the scroller offset. Read off the published\r\n * `--turn-scroll-top` var rather than the scroller element itself — the same\r\n * metric the official preview positioning uses, so any one-frame render lag\r\n * affects both identically.\r\n * @param count - number of entries the rail shows.\r\n * @param rail - the rail element.\r\n * @param clientY - pointer viewport Y.\r\n */\r\nexport function frameIndexAtPointer(count: number, rail: HTMLElement, clientY: number): number {\r\n const rect = rail.getBoundingClientRect()\r\n const inset = railInsetOf(rail)\r\n const raw = Number.parseFloat(rail.style.getPropertyValue('--turn-scroll-top'))\r\n const scrollTop = Number.isFinite(raw) ? raw : 0\r\n const offset = clientY - rect.top + scrollTop - inset\r\n return Math.max(0, Math.min(count - 1, Math.round(offset / TICK_SPACING_PX)))\r\n}\r\n\r\n/**\r\n * Resolve the gesture that produced a rail click to an entry index.\r\n *\r\n * Two rail generations answer here, told apart by capability:\r\n *\r\n * - The virtualized rail (dsh 0.1.7+) tags every RENDERED mark with its own\r\n * ladder index (`data-index`), so the gesture resolves by data. This is the\r\n * robust path: the virtualizer mounts only a window of marks, and their DOM\r\n * order is not the ladder order.\r\n * - The frame-style rail tags nothing and paints marks with\r\n * `pointer-events: none`, so a MOUSE gesture arrives at the `<nav>` with\r\n * coordinates and resolves through the rail geometry over the published\r\n * `--turn-scroll-top`; a KEYBOARD gesture activates the focused mark button,\r\n * recovered by its position among its siblings.\r\n *\r\n * Returns -1 when neither applies (a keyboard target outside the rail, or a\r\n * rail of an unsupported generation, which the kernel's own handler then owns).\r\n *\r\n * Assumption: the mark ticks are the ONLY buttons under the rail — the\r\n * official rail renders marks exclusively and its preview tooltip carries\r\n * none. A future non-mark control added inside the rail would shift the\r\n * button index and must revisit this.\r\n *\r\n * @param rail - the rail element.\r\n * @param event - the click event.\r\n */\r\nexport function indexForEvent(rail: HTMLElement, event: MouseEvent): number {\r\n const target = event.target\r\n if (target instanceof Element) {\r\n const mark = target.closest('[data-index]')\r\n if (mark !== null && rail.contains(mark)) {\r\n const tagged = Number(mark.getAttribute('data-index'))\r\n if (Number.isSafeInteger(tagged) && tagged >= 0) return tagged\r\n }\r\n const button = target.closest('button')\r\n if (button !== null && rail.contains(button)) {\r\n const index = Array.from(rail.querySelectorAll('button')).indexOf(button)\r\n if (index >= 0) return index\r\n }\r\n }\r\n const count = rail.querySelectorAll('button').length\r\n if (count === 0) return -1\r\n // The frame-style geometry is the only supported legacy generation (dsh >=\r\n // 0.1.2-alpha.3, unchanged through 0.1.6). A rail that does not publish\r\n // `--turn-scroll-top` is not that rail: stand down (-1) so the kernel's own\r\n // handler owns the gesture untouched.\r\n return isFrameRail(rail) ? frameIndexAtPointer(count, rail, event.clientY) : -1\r\n}\r\n\r\n/**\r\n * Validate the official navigation array crossing the slot boundary. The\r\n * value is produced by the kernel and may be absent, partial, or from an\r\n * older build, so every field is checked before use.\r\n * @param value - the raw `useChat(s => s.navigation.items())` result.\r\n */\r\nexport function normalizeNavigationItems(value: unknown): OfficialNavigationItem[] {\r\n if (!Array.isArray(value)) return []\r\n const out: OfficialNavigationItem[] = []\r\n const seen = new Set<number>()\r\n for (const item of value) {\r\n if (item === null || typeof item !== 'object') continue\r\n const rec = item as { turn?: unknown; anchorKey?: unknown; prompt?: unknown; response?: unknown }\r\n if (typeof rec.turn !== 'number' || !Number.isFinite(rec.turn)) continue\r\n if (typeof rec.anchorKey !== 'string' || rec.anchorKey === '') continue\r\n if (seen.has(rec.turn)) continue\r\n seen.add(rec.turn)\r\n out.push({\r\n turn: rec.turn,\r\n anchorKey: rec.anchorKey,\r\n prompt: typeof rec.prompt === 'string' ? rec.prompt : '',\r\n response: typeof rec.response === 'string' ? rec.response : '',\r\n })\r\n }\r\n return out\r\n}\r\n\r\n/* ---- Full-ladder merge (dsh >= 0.1.2-alpha.3) ---------------------------- */\r\n\r\n/**\r\n * Validate one `turnOutline` wire value into its entries. The value is the\r\n * host projection's wire view (an array of `{ turn, seq, prompt, response }`\r\n * — see `dsh-session-turn-outline`) read through `useProjection`, so it may\r\n * be absent (pre-baseline, or an older kernel that never registers the key —\r\n * which reads `undefined`), partial, or damaged. `turn` and `seq` are the\r\n * load-bearing fields — a mark cannot exist or page without them — and a\r\n * damaged one drops the entry; the previews are decorative and degrade to ''.\r\n * @param value - the raw `useProjection('turnOutline')` value.\r\n */\r\nexport function normalizeOutlineItems(value: unknown): { turn: number; seq: number; prompt: string; response: string }[] {\r\n if (!Array.isArray(value)) return []\r\n const out: { turn: number; seq: number; prompt: string; response: string }[] = []\r\n for (const item of value) {\r\n if (item === null || typeof item !== 'object') continue\r\n const rec = item as { turn?: unknown; seq?: unknown; prompt?: unknown; response?: unknown }\r\n if (typeof rec.turn !== 'number' || !Number.isSafeInteger(rec.turn) || rec.turn < 0) continue\r\n if (typeof rec.seq !== 'number' || !Number.isSafeInteger(rec.seq) || rec.seq < 0) continue\r\n out.push({\r\n turn: rec.turn,\r\n seq: rec.seq,\r\n prompt: typeof rec.prompt === 'string' ? rec.prompt : '',\r\n response: typeof rec.response === 'string' ? rec.response : '',\r\n })\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Rebuild the rail's full mark ladder exactly as the official ChatView does\r\n * (a mirror of `mergeTurnRailItems` in dsh-client-ui-chat): outline entries\r\n * seed every known turn as unloaded, loaded items then override with their\r\n * anchor key and richer previews, ascending by turn. The enhancer maps a\r\n * click to a mark by INDEX, so its ladder must match the RENDERED marks —\r\n * which are exactly this merge on an alpha.3 kernel; without the outline\r\n * value the merge degrades to the loaded items (the alpha.1/2 ladder).\r\n * @param loaded - the validated loaded-window items.\r\n * @param outline - the raw turnOutline wire value.\r\n */\r\nexport function mergeRailItems(loaded: OfficialNavigationItem[], outline: unknown): TurnRailLadderItem[] {\r\n const byTurn = new Map<number, TurnRailLadderItem>()\r\n for (const entry of normalizeOutlineItems(outline)) {\r\n byTurn.set(entry.turn, { turn: entry.turn, prompt: entry.prompt, response: entry.response })\r\n }\r\n for (const item of loaded) {\r\n const preview = byTurn.get(item.turn)\r\n byTurn.set(item.turn, {\r\n turn: item.turn,\r\n prompt: item.prompt !== '' ? item.prompt : preview?.prompt ?? '',\r\n response: item.response !== '' ? item.response : preview?.response ?? '',\r\n anchorKey: item.anchorKey,\r\n })\r\n }\r\n if (byTurn.size === 0) return []\r\n return [...byTurn.values()].sort((left, right) => left.turn - right.turn)\r\n}\r\n\r\n","/**\r\n * Official-rail enhancer — behaviour-only improvements to the kernel's own\r\n * turn navigator (dsh >= 0.1.2-rc.1, `TurnNavigator` in dsh-client-ui-chat).\r\n *\r\n * This component renders NOTHING. It borrows the official rail exactly as the\r\n * kernel painted it and fixes its behaviour, scoped to what the running rail\r\n * generation still lacks — told apart by capability, never by version compare:\r\n *\r\n * Smooth jump. The official `navigateToTurn` is one assignment\r\n * (`el.scrollTop += flowTop(row, el) - 24`); a click across a long transcript\r\n * teleports. Clicks on LOADED marks are intercepted in the capture phase and\r\n * re-run through the shared glide instead. Both generations also render marks\r\n * for turns OUTSIDE the loaded window (fed by the whole-log `turnOutline`\r\n * projection); those jumps page history through the kernel's own loadThrough\r\n * machinery — marks without an anchor key are left to it, and the click index\r\n * is resolved against the same merged ladder the kernel renders. While one of\r\n * those kernel jumps is still paging (a mark pulses `aria-busy`), the plugin\r\n * stands down entirely: the kernel's loaded branch cancels its own pending\r\n * jump before landing, and an interception would bypass that cancellation.\r\n *\r\n * Two rail generations are supported, discovered by {@link findOfficialRail}:\r\n * the frame-style rail of dsh 0.1.2-rc.1 … 0.1.6 (inline frame metrics, marks\r\n * mapped by geometry) and the virtualized rail of dsh 0.1.7+ (no inline\r\n * metrics, marks tagged `data-index`, only a window of them mounted at once).\r\n *\r\n * State-ownership note (why intercepting the official click loses nothing):\r\n * the kernel's scrollport (`data-conversation-scroll`, ChatView's\r\n * `onScrollRef` handler) treats every unaccounted scrollTop assignment as a\r\n * reader move and on each frame (a) flips its own at-bottom machinery,\r\n * (b) `chatScroll.save(position)` for view-tab restore, and (c) re-derives\r\n * the rail's active mark. The glide animates the real scrollport, so all three\r\n * keep converging to the target row while it runs — the only thing the\r\n * interception skips is the official handler's synchronous active-mark\r\n * assignment, which its own scroll-driven derivation re-settles by the time\r\n * the glide lands.\r\n *\r\n * The jump goes through the shared backend (jump.ts).\r\n *\r\n * Interception notes: React 18 dispatches `onClick` from a listener on the\r\n * root container during the BUBBLE phase. A capture listener on the rail\r\n * itself therefore runs first, and `stopImmediatePropagation()` there prevents\r\n * the event from ever reaching React's root — the official handler never\r\n * fires. The frame-style rail owns pointer input for its whole column (marks\r\n * carry `pointer-events: none`); the virtualized rail's marks are real\r\n * buttons. Both arrive at the same capture listener on the rail.\r\n *\r\n * Nothing here touches the official DOM: no nodes are injected into a\r\n * React-owned subtree and no official class or paint is overridden.\r\n */\r\n\r\nimport react from 'react'\r\nimport { indexForEvent, mergeRailItems, normalizeNavigationItems } from './rail-pointer.ts'\r\nimport type { JumpToAnchor, TurnRailLadderItem } from './types.ts'\r\n\r\n/**\r\n * Structural anchor of the LEGACY (frame-style) official rail — its inline\r\n * style carries the frame's own metric (`--turn-natural-height`), published on\r\n * every kernel from dsh 0.1.2-rc.1 through 0.1.6. dsh 0.1.7's virtualized rail\r\n * publishes no inline metrics, so this selector only names the older\r\n * generation; discovery is {@link findOfficialRail}.\r\n */\r\nexport const OFFICIAL_RAIL_SELECTOR = '[data-conversation-scroll] nav[style*=\"--turn-natural-height\"]'\r\n\r\n/**\r\n * Find the official turn rail, in either supported generation.\r\n *\r\n * Identity comes from the SEAT, not from a rendered mark: the rail is the only\r\n * `<nav>` inside the conversation scrollport, and dsh 0.1.7's virtualizer\r\n * mounts its marks lazily — measured on the real rail, the `<nav>` exists with\r\n * an EMPTY marks container until the virtualizer has layout. Requiring a\r\n * rendered `button[data-index]` would therefore miss the rail exactly when it\r\n * first appears.\r\n *\r\n * The generation is then told apart by capability where it matters: the click\r\n * resolver reads a mark's own `data-index`, and only falls back to the legacy\r\n * frame geometry for a rail that publishes `--turn-scroll-top`.\r\n * @param root - the tree to search (defaults to the document).\r\n * @returns the rail element, or null when the conversation shows none.\r\n */\r\nexport function findOfficialRail(root: ParentNode = document): HTMLElement | null {\r\n return root.querySelector<HTMLElement>('[data-conversation-scroll] nav')\r\n}\r\n\r\n/**\r\n * Rails claimed by an enhancer instance, keyed by the owning instance's\r\n * claim token. One session-scoped enhancer mounts per session; without a\r\n * claim two instances would both attach capture listeners to the FIRST rail\r\n * in the document (multi-column layouts) and one of them — not necessarily\r\n * the one owning the reader's column — would win the click. The token\r\n * distinguishes \"already mine\" (the poll keeps it) from \"someone else's\"\r\n * (this instance must stand down). Weak: a replaced rail element disappears\r\n * as soon as the browser collects it, so remounts need no bookkeeping.\r\n */\r\nconst claimedRails = new WeakMap<HTMLElement, symbol>()\r\n\r\n/** How often the official rail is looked for (it mounts with the chat view). */\r\nconst RAIL_POLL_MS = 400\r\n\r\n/** Component props delivered by the dock slot registration. */\r\nexport interface OfficialTimelineEnhancerProps {\r\n /** Session-bound jump verb from the registration factory (apply closure). */\r\n jumpToAnchor?: JumpToAnchor\r\n /** Kernel selector hook over the Chat snapshot (dsh >= 0.1.2-rc.1). */\r\n useChat?: (selector: (snapshot: unknown) => unknown) => unknown\r\n /** Framework projection reader; the whole-log turn outline\r\n * (`turnOutline`) arrives through it. */\r\n useProjection?: (key: string) => unknown\r\n /** Whether the persisted timeline toggle is on. */\r\n enabled: boolean\r\n t: (key: string, params?: Record<string, string | number>) => string\r\n}\r\n\r\n/**\r\n * The enhancer: no visual output, only the behaviour fixes the running rail\r\n * generation lacks.\r\n * @param props - dock slot props plus the enabled gate.\r\n * @returns null (always).\r\n */\r\nexport function OfficialTimelineEnhancer(props: OfficialTimelineEnhancerProps): null {\r\n const { jumpToAnchor, useChat, useProjection, enabled } = props\r\n\r\n // The official navigation index — the loaded-window half of what the rail\r\n // renders, so this plugin always targets a mark that visually exists.\r\n const raw = useChat === undefined ? undefined : useChat((snapshot: unknown) => {\r\n const nav = (snapshot as { navigation?: { items?: () => unknown } } | undefined)?.navigation\r\n return typeof nav?.items === 'function' ? nav.items() : undefined\r\n })\r\n const items = normalizeNavigationItems(raw)\r\n // The whole-log outline (dsh >= 0.1.2-alpha.3) — the other half of the\r\n // rendered ladder. Reading an unregistered key is `undefined` on older\r\n // kernels, and the merge then degrades to the loaded items, which IS the\r\n // alpha.1/2 ladder. Called unconditionally per render: useProjection's\r\n // presence never flips within a kernel, so the hook order stays stable.\r\n const rawOutline = useProjection === undefined ? undefined : useProjection('turnOutline')\r\n const ladder = mergeRailItems(items, rawOutline)\r\n\r\n const [rail, setRail] = react.useState<HTMLElement | null>(null)\r\n\r\n // Mirrors so the listeners below stay identity-stable: `ladder` changes\r\n // identity on every turn change, and re-attaching a listener each time\r\n // would be pure churn. `railRef` mirrors the claimed rail so the poll\r\n // effect can release its claim on unmount without re-subscribing.\r\n const ladderRef = react.useRef(ladder)\r\n ladderRef.current = ladder\r\n const railRef = react.useRef<HTMLElement | null>(rail)\r\n railRef.current = rail\r\n // The bound verb is a fresh closure per inject-factory run; mirror it so\r\n // `navigate` (and the click listener below) stays identity-stable while\r\n // always calling the latest verb — same rationale as `ladderRef` above.\r\n const jumpToAnchorRef = react.useRef(jumpToAnchor)\r\n jumpToAnchorRef.current = jumpToAnchor\r\n\r\n // Locate the official rail (either generation). It mounts with the chat\r\n // view and is replaced whenever the view remounts, so it is polled rather\r\n // than observed once.\r\n //\r\n // Ownership guard: one enhancer instance mounts per SESSION (the dock slot\r\n // is session-scoped), so a multi-column layout can run several instances\r\n // against the same selector. Without a claim, the first instance's capture\r\n // listener — the first registered on the shared rail — would swallow the\r\n // clicks of every other instance, which then silently jump the WRONG\r\n // session. The first instance to find the rail claims it for its\r\n // registration lifetime; later instances stand down and the stock\r\n // behaviour stays (no duplicate handlers, no mis-directed jumps). The\r\n // poll re-runs on the SAME instance, so the claim check must recognise\r\n // \"already mine\" instead of standing down against itself.\r\n const claimToken = react.useMemo<symbol>(() => Symbol('dsh-bg-timeline-claim'), [])\r\n react.useEffect(() => {\r\n const check = (): void => {\r\n const found = findOfficialRail()\r\n if (found === null) {\r\n setRail(null)\r\n return\r\n }\r\n const owner = claimedRails.get(found)\r\n if (owner !== undefined && owner !== claimToken) {\r\n setRail(null)\r\n return\r\n }\r\n claimedRails.set(found, claimToken)\r\n setRail(found)\r\n }\r\n check()\r\n const timer = window.setInterval(check, RAIL_POLL_MS)\r\n return () => {\r\n window.clearInterval(timer)\r\n // Release the claim on unmount so a later instance (or a same-layout\r\n // remount that reuses the element) can take over.\r\n if (railRef.current !== null) claimedRails.delete(railRef.current)\r\n }\r\n }, [claimToken])\r\n\r\n const navigate = react.useCallback((item: TurnRailLadderItem): void => {\r\n // An outline-only mark (an unloaded turn) has no addressable row here —\r\n // the kernel's own loadThrough jump owns it (the click handler never\r\n // routes one this way; the guard is for direct callers).\r\n if (item.anchorKey === undefined) return\r\n const jump = jumpToAnchorRef.current\r\n if (jump === undefined) return\r\n void jump(item.anchorKey).catch(() => {})\r\n }, [])\r\n\r\n // Click interception. Capture phase on the rail itself, so this runs before\r\n // React's root bubble listener and the official handler never fires — for a\r\n // LOADED mark. An outline-only mark (an unloaded turn, alpha.3) carries no\r\n // anchor key: standing down lets the event reach the kernel's own handler,\r\n // whose loadThrough machinery pages the history in and lands the row. While\r\n // one of those kernel jumps is still paging (its mark pulses aria-busy) the\r\n // plugin stands down ENTIRELY: the kernel's loaded branch cancels its own\r\n // pending jump before landing, which an interception would bypass — the\r\n // orphaned jump would teleport the scrollport away mid-glide at settle.\r\n react.useEffect(() => {\r\n if (!enabled || rail === null) return\r\n const onClick = (event: MouseEvent): void => {\r\n if (rail.querySelector('[aria-busy=\"true\"]') !== null) return\r\n const index = indexForEvent(rail, event)\r\n const item = ladderRef.current[index]\r\n if (item === undefined || item.anchorKey === undefined) return\r\n event.preventDefault()\r\n event.stopImmediatePropagation()\r\n navigate(item)\r\n }\r\n rail.addEventListener('click', onClick, true)\r\n return () => { rail.removeEventListener('click', onClick, true) }\r\n }, [enabled, rail, navigate])\r\n\r\n return null\r\n}\r\n","/**\r\n * Conversation jump engine — the shared behaviour the official-rail enhancer\r\n * runs.\r\n *\r\n * Everything below the \"which row\" question lives here: paging older history\r\n * until the target node is loaded, waiting for React to commit the row,\r\n * detaching the host conversation's bottom-follow state so a re-render cannot\r\n * yank the glide back to the floor, and the glide itself.\r\n *\r\n * The official ChatView jump is one synchronous assignment\r\n * (`el.scrollTop += flowTop(row, el) - 24`) with no paging at all, which is\r\n * the pair of shortcomings this engine exists to fix. The enhancer is its one\r\n * caller (dsh >= 0.1.2-rc.1), so the engine is fitted to the current ChatView:\r\n * its 24px bottom-follow threshold and its 24px landing inset.\r\n */\r\n\r\nimport type { TimelineSessionsService } from './types.ts'\r\n\r\nconst delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\r\n\r\n/** How long one jump may keep paging older history before giving up. */\r\nconst JUMP_PAGE_DEADLINE_MS = 30000\r\n/** How long to wait for the chat view to commit the target row. */\r\nconst JUMP_ROW_WAIT_MS = 3000\r\n/** Settle between consecutive pages so a resolver without loadingOlder cannot hot-spin the loop. */\r\nconst PAGE_COOLDOWN_MS = 50\r\n\r\n/**\r\n * Official conversation follow threshold of the current ChatView. The reader\r\n * stays \"at bottom\" while within this distance of the floor, and the view\r\n * keeps following new content.\r\n */\r\nexport const CHATVIEW_FOLLOW_ZONE_PX = 24\r\n\r\n/** Distance from the scrollport top the official ChatView lands a jumped row at. */\r\nconst OFFICIAL_LANDING_INSET_PX = 24\r\n\r\n/** Distances under this snap instead of animating. */\r\nconst MIN_GLIDE_DISTANCE_PX = 8\r\n\r\n/** Glide duration floor / cap — a far-boundary jump stays a tangible slide. */\r\nconst MIN_GLIDE_MS = 280\r\nconst MAX_GLIDE_MS = 900\r\n\r\n/**\r\n * Where a jumped row should land.\r\n * @param el - the conversation scrollport.\r\n * @param row - the target chat row.\r\n * @returns the destination scrollTop.\r\n */\r\nexport type TargetTopResolver = (el: HTMLElement, row: HTMLElement) => number\r\n\r\n/** Knobs one jump call may override. */\r\nexport interface JumpOptions {\r\n /** The host conversation's bottom-follow threshold in px. */\r\n followZonePx?: number\r\n /** Destination for the target row; defaults to centering it vertically. */\r\n targetTop?: TargetTopResolver\r\n /**\r\n * Restrict the pre-page bottom-follow detach to this scrollport. Omit to\r\n * detach every mounted scrollport (the safe default when the jump's target\r\n * column is not known yet).\r\n */\r\n scrollport?: HTMLElement | null\r\n /** Budget for paging older history. */\r\n pageDeadlineMs?: number\r\n /** Budget for waiting on the row to be committed. */\r\n rowWaitMs?: number\r\n}\r\n\r\n/** Every mounted conversation scrollport (multi-column layouts have several). */\r\nexport function conversationScrollports(): HTMLElement[] {\r\n return Array.from(document.querySelectorAll<HTMLElement>('[data-conversation-scroll]'))\r\n}\r\n\r\n/**\r\n * Whether the target chat row is committed in ANY mounted conversation\r\n * scrollport. Kernel generations disagree on where the node table lives\r\n * (0.1.1: `SessionSnapshot.chat.nodes`; 0.1.2+: the `useChat` ChatSnapshot\r\n * node store, unreachable from `session.getSnapshot()` — its SessionSnapshot\r\n * faces no node table at all), but the rendered DOM is the ground truth both\r\n * share, so a jump probes it FIRST before deciding whether paging is needed.\r\n * @param anchorKey - chat anchor key of the target row.\r\n */\r\nexport function rowRenderedInDom(anchorKey: string): boolean {\r\n const selector = `[data-chat-anchor-key=\"${CSS.escape(anchorKey)}\"]`\r\n for (const scrollport of conversationScrollports()) {\r\n if (scrollport.querySelector(selector) !== null) return true\r\n }\r\n return false\r\n}\r\n\r\n/**\r\n * Poll the conversation DOM until the target row is committed. The session\r\n * store updates BEFORE React renders the prepended page, so the old one-shot\r\n * query right after loadOlder() resolved could miss the row and silently\r\n * give up — the reported \"click does nothing until the conversation is\r\n * nudged (scroll/click)\" behavior.\r\n * @param key - chat anchor key to look for.\r\n * @param timeoutMs - polling budget.\r\n */\r\nexport async function waitForChatRow(key: string, timeoutMs: number): Promise<HTMLElement | null> {\r\n const selector = `[data-chat-anchor-key=\"${CSS.escape(key)}\"]`\r\n const deadline = Date.now() + timeoutMs\r\n for (;;) {\r\n // Re-scan on every round: a ChatView remount (or a second column coming\r\n // up) repoints the row target, and the one-shot snapshot at entry would\r\n // miss a row committed into the PORT that appeared mid-wait.\r\n for (const scrollport of conversationScrollports()) {\r\n const row = scrollport.querySelector<HTMLElement>(selector)\r\n if (row !== null) return row\r\n }\r\n if (Date.now() >= deadline) return null\r\n await delay(40)\r\n }\r\n}\r\n\r\n/** Maximum scrollable offset of a conversation scrollport. */\r\nexport function scrollFloorOf(el: HTMLElement): number {\r\n return Math.max(0, el.scrollHeight - el.clientHeight)\r\n}\r\n\r\n/**\r\n * Destination scrollTop that centers `row` inside `el` — the same geometry\r\n * as scrollIntoView({ block: 'center' }), computed directly so the glide owns\r\n * the scroll position instead of the browser.\r\n * @param el - the conversation scrollport.\r\n * @param row - the target chat row.\r\n */\r\nexport function centeredScrollTopFor(el: HTMLElement, row: HTMLElement): number {\r\n const floor = scrollFloorOf(el)\r\n const target = el.scrollTop\r\n + (row.getBoundingClientRect().top - el.getBoundingClientRect().top)\r\n - (el.clientHeight - row.offsetHeight) / 2\r\n if (!Number.isFinite(target)) return el.scrollTop\r\n return Math.max(0, Math.min(floor, target))\r\n}\r\n\r\n/**\r\n * Destination scrollTop the official ChatView uses for a navigation jump:\r\n * the row's top minus a 24px inset. Kept as a named resolver so the enhancer\r\n * lands exactly where the official would — only animated instead of assigned.\r\n * @param el - the conversation scrollport.\r\n * @param row - the target chat row.\r\n */\r\nexport function officialTargetTopFor(el: HTMLElement, row: HTMLElement): number {\r\n const floor = scrollFloorOf(el)\r\n const target = el.scrollTop\r\n + (row.getBoundingClientRect().top - el.getBoundingClientRect().top)\r\n - OFFICIAL_LANDING_INSET_PX\r\n if (!Number.isFinite(target)) return el.scrollTop\r\n return Math.max(0, Math.min(floor, target))\r\n}\r\n\r\n/**\r\n * Glide duration for a distance: longer slides take longer, bounded to\r\n * [MIN_GLIDE_MS, MAX_GLIDE_MS] (a deep-history jump reads as a deliberate\r\n * glide, not a sluggish race).\r\n * @param distance - px to travel.\r\n */\r\nexport function glideDurationFor(distance: number): number {\r\n if (!Number.isFinite(distance)) return MIN_GLIDE_MS\r\n return Math.round(Math.max(MIN_GLIDE_MS, Math.min(MAX_GLIDE_MS, 240 + Math.abs(distance) * 0.14)))\r\n}\r\n\r\n/**\r\n * Detach the host's bottom-follow state when the reader currently sits inside\r\n * its follow zone: assign the scroll position just outside the boundary. That\r\n * one synchronous scroll event settles at-bottom to false (moved-by-reader +\r\n * outside the zone) so a tip-moved re-render cannot yank the glide back to\r\n * the floor. No-op when already above the zone or when the assignment cannot\r\n * move (content shorter than the viewport).\r\n * @param el - the conversation scrollport.\r\n * @param followZonePx - the host's own follow threshold.\r\n */\r\nexport function detachBottomFollow(el: HTMLElement, followZonePx: number = CHATVIEW_FOLLOW_ZONE_PX): boolean {\r\n const floor = scrollFloorOf(el)\r\n // Only the bottom zone can still hold the follow flag: anywhere above it\r\n // the official scroll handler already settled at-bottom to false, and\r\n // dragging the viewport down to the zone would be a visible jump.\r\n if (el.scrollTop < floor - followZonePx) return false\r\n const detached = Math.max(0, floor - followZonePx - 1)\r\n if (Math.abs(el.scrollTop - detached) < 1) return false\r\n el.scrollTop = detached\r\n return true\r\n}\r\n\r\n/**\r\n * Wait until a detach assignment's scroll event has been dispatched AND\r\n * consumed by the host's scroll handler: scroll events fire on a later frame\r\n * than the assignment, and a paging commit's layout effect runs before that\r\n * dispatch — it would re-run the follow logic while the at-bottom flag is\r\n * still true and drag the viewport to the growing floor, undoing the detach\r\n * (two rAFs + a macrotask bracket the dispatch window).\r\n */\r\nexport function settleScrollEvents(): Promise<void> {\r\n if (typeof requestAnimationFrame !== 'function') return Promise.resolve()\r\n return new Promise((resolve) => {\r\n requestAnimationFrame(() => requestAnimationFrame(() => window.setTimeout(resolve, 0)))\r\n })\r\n}\r\n\r\n/**\r\n * Animate el.scrollTop toward target with an ease-in-out cubic profile.\r\n * Wheel / touch / keyboard-scroll input cancels the glide so the reader\r\n * takes over immediately. Resolves true when the glide completed, false\r\n * when the reader cancelled it.\r\n * @param el - the conversation scrollport.\r\n * @param target - destination scrollTop.\r\n * @param durationMs - animation budget.\r\n */\r\nexport function animateScrollTop(el: HTMLElement, target: number, durationMs: number): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n const start = el.scrollTop\r\n const delta = target - start\r\n let done = false\r\n let frame = 0\r\n const finish = (completed: boolean): void => {\r\n if (done) return\r\n done = true\r\n el.removeEventListener('wheel', onWheel)\r\n el.removeEventListener('touchstart', onTouch)\r\n window.removeEventListener('keydown', onKey)\r\n if (frame !== 0) cancelAnimationFrame(frame)\r\n resolve(completed)\r\n }\r\n const onWheel = (): void => finish(false)\r\n const onTouch = (): void => finish(false)\r\n const onKey = (e: KeyboardEvent): void => {\r\n if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(e.key)) finish(false)\r\n }\r\n el.addEventListener('wheel', onWheel, { passive: true })\r\n el.addEventListener('touchstart', onTouch, { passive: true })\r\n window.addEventListener('keydown', onKey)\r\n if (Math.abs(delta) < 0.5 || durationMs <= 0) {\r\n el.scrollTop = target\r\n finish(true)\r\n return\r\n }\r\n const t0 = performance.now()\r\n const ease = (x: number): number => x < 0.5 ? 4 * x * x * x : 1 - (-2 * x + 2) ** 3 / 2\r\n const step = (now: number): void => {\r\n if (done) return\r\n const t = Math.min(1, (now - t0) / durationMs)\r\n el.scrollTop = start + delta * ease(t)\r\n if (t < 1) frame = requestAnimationFrame(step)\r\n else finish(true)\r\n }\r\n frame = requestAnimationFrame(step)\r\n })\r\n}\r\n\r\n/**\r\n * Ensure the target node is loaded, wait for the chat view to commit its row\r\n * into the DOM, then glide it into view. Paging uses a time budget instead\r\n * of a fixed iteration guard (each in-flight page burned part of that guard,\r\n * so slow history could exhaust it and silently fail).\r\n *\r\n * @param sessionsService - the runtime sessions service.\r\n * @param sessionId - the session owning the target row.\r\n * @param anchorKey - chat anchor key of the target row.\r\n * @param options - follow zone, landing resolver, paging scopes and budgets.\r\n * @returns whether the row was reached (false when it never rendered or the\r\n * reader took the scrollport back mid-glide).\r\n */\r\nexport async function jumpToMessage(\r\n sessionsService: TimelineSessionsService,\r\n sessionId: string,\r\n anchorKey: string,\r\n options: JumpOptions = {},\r\n): Promise<boolean> {\r\n const session = sessionsService.binding(sessionId)?.session\r\n if (session === undefined) return false\r\n const followZonePx = options.followZonePx ?? CHATVIEW_FOLLOW_ZONE_PX\r\n const resolveTarget = options.targetTop ?? centeredScrollTopFor\r\n const pageDeadlineMs = options.pageDeadlineMs ?? JUMP_PAGE_DEADLINE_MS\r\n const rowWaitMs = options.rowWaitMs ?? JUMP_ROW_WAIT_MS\r\n\r\n // Detach the host's bottom-follow flag BEFORE paging: while a multi-page\r\n // loadOlder is in flight, every prepend commit re-runs the follow layout\r\n // effect (tip-moved + at-bottom) and toBottom() drags the viewport to the\r\n // (growing) floor — the jump would read as \"surf to the newest floor, then\r\n // slide back to the target\". One synchronous assignment just outside the\r\n // zone settles the flag for the whole paging phase, but the flag only flips\r\n // when the assignment's scroll event reaches the host handler, which\r\n // happens a frame later — so the paging phase waits for it first. The\r\n // post-wait detach below stays idempotent for the already-loaded case.\r\n // Multi-column layouts cannot pin the target session's scrollport before\r\n // the row exists, so every mounted scrollport is detached unless the caller\r\n // already knows the column (a ~zone-sized nudge elsewhere is the whole\r\n // cost; the reader's next scroll re-engages follow there).\r\n const ports = options.scrollport != null ? [options.scrollport] : conversationScrollports()\r\n let detachedAny = false\r\n for (const el of ports) {\r\n if (detachBottomFollow(el, followZonePx)) {\r\n // The host scroll handler flips its at-bottom flag on a scroll event,\r\n // and the real one only dispatches on a later frame — a paging\r\n // commit's layout effect runs before it and re-runs the follow logic.\r\n // A synthetic event notifies the handler synchronously, so the flag\r\n // settles before any prepend commit can observe it.\r\n el.dispatchEvent(new Event('scroll'))\r\n detachedAny = true\r\n }\r\n }\r\n if (detachedAny) await settleScrollEvents()\r\n const deadline = Date.now() + pageDeadlineMs\r\n for (;;) {\r\n // DOM ground truth FIRST: the row may already be committed in some\r\n // scrollport even though `session.getSnapshot()` exposes no node table\r\n // (the `useChat` ChatSnapshot store is unreachable from here), so\r\n // without this probe every jump would page the WHOLE history to the end\r\n // (or burn the 30s deadline) even when the target row is on screen\r\n // behind the \"load earlier\" button.\r\n if (rowRenderedInDom(anchorKey)) break\r\n const snapshot = session.getSnapshot() as {\r\n hasMore?: boolean\r\n loadingOlder?: boolean\r\n openState?: unknown\r\n }\r\n if (snapshot?.hasMore !== true) break\r\n if (Date.now() >= deadline) break\r\n if (snapshot.loadingOlder === true) {\r\n await delay(50)\r\n continue\r\n }\r\n // A session that is not open yet cannot page; give it a moment instead\r\n // of spinning the full budget on no-op calls.\r\n if (snapshot.openState !== undefined && snapshot.openState !== 'open') {\r\n await delay(100)\r\n continue\r\n }\r\n // A failing page must not reject the whole jump (or leave a lock to a\r\n // fallback timer): log and stop paging — the row may still render from an\r\n // earlier page, so waitForChatRow below gets its chance anyway.\r\n try {\r\n await session.loadOlder()\r\n } catch (error) {\r\n console.warn('[deepseek-harness-background] timeline jump paging failed:', error)\r\n break\r\n }\r\n // Throttle consecutive pages: a resolver that comes back without ever\r\n // flipping `loadingOlder` must not turn this loop into a hot spin (the\r\n // real kernel raises the flag; the cooldown also gives each page a\r\n // moment to commit before the next DOM probe).\r\n await delay(PAGE_COOLDOWN_MS)\r\n }\r\n const row = await waitForChatRow(anchorKey, rowWaitMs)\r\n if (row === null) {\r\n console.warn(`[deepseek-harness-background] timeline jump target never rendered: ${anchorKey}`)\r\n return false\r\n }\r\n const reducedMotion = typeof window.matchMedia === 'function'\r\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches\r\n const scroller = row.closest<HTMLElement>('[data-conversation-scroll]')\r\n // No addressable scrollport (defensive): keep the platform path — the\r\n // instant placement flips the host's bottom-follow state through its scroll\r\n // event, the smooth pass afterwards is a no-op when already centered (and a\r\n // graceful glide when the row moved since).\r\n if (scroller === null) {\r\n row.scrollIntoView({ behavior: 'auto', block: 'center' })\r\n if (!reducedMotion) row.scrollIntoView({ behavior: 'smooth', block: 'center' })\r\n return true\r\n }\r\n const target = resolveTarget(scroller, row)\r\n // Reduced motion or a hairline distance: one placement (its scroll event\r\n // still flips the bottom-follow state when it lands outside the zone).\r\n if (reducedMotion || Math.abs(target - scroller.scrollTop) < MIN_GLIDE_DISTANCE_PX) {\r\n scroller.scrollTop = target\r\n return true\r\n }\r\n // Bottom-follow detach BEFORE the glide: a pure slide starting at the floor\r\n // leaves the at-bottom flag true for its first frames, so a tip-moved\r\n // re-render (turn end / commit) yanks it back to the floor. The detach moves\r\n // at most ~zone px to just outside the follow zone (reads as a nudge, not a\r\n // jump); the flag settles once the dispatch frame passes, so the glide\r\n // waits for it first.\r\n if (detachBottomFollow(scroller, followZonePx)) {\r\n scroller.dispatchEvent(new Event('scroll'))\r\n await settleScrollEvents()\r\n }\r\n const completed = await animateScrollTop(scroller, target, glideDurationFor(Math.abs(target - scroller.scrollTop)))\r\n return completed\r\n}\r\n","/**\r\n * Dock-slot entry for the conversation timeline: mounts the official-rail\r\n * enhancer on kernels that publish the turn-navigation index.\r\n *\r\n * Supported dsh baseline: >= 0.1.2-rc.1. Every such kernel ships the official\r\n * turn rail (`TurnNavigator` in dsh-client-ui-chat), rendered from the very\r\n * index `ui-chat` publishes as a session hook (`useChat(s => s.navigation.items())`),\r\n * so the presence of that hook IS the presence of the official rail. This\r\n * plugin therefore never renders a rail of its own — it only improves the\r\n * official one's behaviour (smooth glide for loaded marks, standing down\r\n * while the kernel's own loadThrough jump is still paging).\r\n *\r\n * The hook-missing branch is a defensive no-op, not a second frontend: on\r\n * kernels without the official rail (everything before 0.1.2) this plugin\r\n * renders nothing, exactly as the supported-version contract requires\r\n * (README directs those users to an older plugin release).\r\n */\r\n\r\nimport react from 'react'\r\nimport { settingsClient } from '../settings-client.ts'\r\nimport { findOfficialRail, OfficialTimelineEnhancer } from './official-enhance.tsx'\r\nimport type { JumpToAnchor } from './types.ts'\r\n\r\n/** Props the dock slot delivers. */\r\nexport interface TimelineBridgeProps {\r\n /** Session-bound jump verb from the registration factory (apply closure). */\r\n jumpToAnchor?: JumpToAnchor\r\n /** Kernel selector hook over the Chat snapshot; present on dsh >= 0.1.2. */\r\n useChat?: (selector: (snapshot: unknown) => unknown) => unknown\r\n /** Framework projection reader (SessionStandardProps seat). */\r\n useProjection?: (key: string) => unknown\r\n /** Locale lookup. Dictionaries interpolate `{turn}` placeholders, like the\r\n * official chat namespace's own turn-navigation copy does. */\r\n t: (key: string, params?: Record<string, string | number>) => string\r\n}\r\n\r\n/**\r\n * Mount the timeline frontend the running kernel supports.\r\n * @param props - dock slot props.\r\n * @returns the enhancer, or nothing on kernels without the official rail.\r\n */\r\nexport function TimelineBridge(props: TimelineBridgeProps): react.ReactElement | null {\r\n const { useChat, t, ...rest } = props\r\n\r\n const settings = react.useSyncExternalStore(settingsClient.subscribe, settingsClient.getSnapshot)\r\n // Hide only while the persisted toggle is UNKNOWN (no wrong-state flash for\r\n // timeline:false users); once loading settles the default-on enhancement\r\n // runs even if the section errored.\r\n const enabled = settings.value?.timeline !== false && settings.status !== 'loading'\r\n\r\n if (useChat === undefined) return null\r\n return react.createElement(OfficialTimelineEnhancer, { ...rest, useChat, enabled, t })\r\n}\r\n\r\nexport {\r\n CHATVIEW_FOLLOW_ZONE_PX,\r\n centeredScrollTopFor,\r\n conversationScrollports,\r\n glideDurationFor,\r\n jumpToMessage,\r\n officialTargetTopFor,\r\n scrollFloorOf,\r\n settleScrollEvents,\r\n animateScrollTop,\r\n detachBottomFollow,\r\n waitForChatRow,\r\n type JumpOptions,\r\n type TargetTopResolver,\r\n} from './jump.ts'\r\nexport {\r\n RAIL_INSET_PX,\r\n TICK_SPACING_PX,\r\n frameIndexAtPointer,\r\n indexForEvent,\r\n isFrameRail,\r\n mergeRailItems,\r\n normalizeNavigationItems,\r\n normalizeOutlineItems,\r\n railInsetOf,\r\n} from './rail-pointer.ts'\r\nexport {\r\n findOfficialRail,\r\n OFFICIAL_RAIL_SELECTOR,\r\n OfficialTimelineEnhancer,\r\n type OfficialTimelineEnhancerProps,\r\n} from './official-enhance.tsx'\r\n\r\n/** localStorage prefix the removed key-point bookmark feature used (< 0.5). */\r\nconst MARKS_STORAGE_PREFIX = 'dsbt_marks_'\r\n\r\n/**\r\n * Drop the bookmark rows pre-0.5 versions left in localStorage.\r\n *\r\n * The bookmarks are gone — the rail now reuses the official UI, which has no\r\n * affordance for them — so the rows are dead weight every session carried\r\n * forever. One-way and irreversible by design; the key prefix is owned by\r\n * this plugin, so nothing else can be caught in the sweep.\r\n */\r\nexport function clearLegacyMarks(): void {\r\n if (typeof window === 'undefined') return\r\n try {\r\n const stale: string[] = []\r\n for (let i = 0; i < window.localStorage.length; i += 1) {\r\n const key = window.localStorage.key(i)\r\n if (key !== null && key.startsWith(MARKS_STORAGE_PREFIX)) stale.push(key)\r\n }\r\n for (const key of stale) window.localStorage.removeItem(key)\r\n } catch {\r\n /* storage unavailable (private mode, blocked origin) — nothing to clean */\r\n }\r\n}\r\nexport type {\r\n JumpToAnchor,\r\n OfficialNavigationItem,\r\n TimelineSessionHandle,\r\n TimelineSessionsService,\r\n TurnRailLadderItem,\r\n} from './types.ts'\r\n","/**\r\n * Browser half of the background plugin: paints the background behind the\r\n * whole app frame, provides the settings surface (a live row inside the\r\n * official General settings section), and renders the conversation timeline\r\n * rail (DeepSeek ScrollNav style) on the same frosted-glass system. Data\r\n * flows through the plugin's own host route (`/api/bg-wallpaper/*`), not the\r\n * settings RPC. Retraction discipline mirrors the skin plugins: every\r\n * property and DOM node the painter owns is restored on dispose.\r\n */\r\n\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport { backgroundPainter, paintBackground } from './backdrop.ts'\r\nimport { installGlassBridge } from './glass-registry.ts'\r\nimport { en, zh } from './locales.ts'\r\nimport { BackgroundSettingsRow } from './SettingsRow.tsx'\r\nimport { settingsClient } from './settings-client.ts'\r\nimport {\r\n CHATVIEW_FOLLOW_ZONE_PX, findOfficialRail, TimelineBridge, clearLegacyMarks,\r\n jumpToMessage, officialTargetTopFor,\r\n} from './timeline/index.tsx'\r\nimport type {} from './types.ts'\r\n\r\n/**\r\n * Service injections this entry declares: `slots` + `locale` for the settings\r\n * surface and the timeline slot seat, and `sessions` so the timeline rail can\r\n * enumerate user messages and jump between them. The settings surface itself\r\n * talks to the plugin's own host route over plain same-origin fetch.\r\n */\r\nexport const inject = ['slots', 'locale', 'sessions']\r\n\r\n/**\r\n * Client plugin body: paint the persisted background live, register the\r\n * General-settings row, and mount the conversation timeline rail.\r\n * @param ctx - client cordis context (slots/locale/sessions injected).\r\n */\r\nexport function apply(ctx: Context): void {\r\n // Publish the third-party frosted-glass registry bridge FIRST so an\r\n // eager consumer can register before the first snapshot repaints; the\r\n // bridge lives for the whole fiber and dies with it (see the dispose below).\r\n const glassBridge = installGlassBridge()\r\n // One-way sweep of the bookmark rows pre-0.5 versions left in localStorage\r\n // (the rail reuses the official UI now, which has no bookmarks).\r\n clearLegacyMarks()\r\n // Paint from the plugin's own transport; repaint on every snapshot change.\r\n const paint = (): void => {\r\n const snapshot = settingsClient.getSnapshot()\r\n const value = snapshot.status === 'ready' ? snapshot.value : undefined\r\n if (value === undefined || !value.enabled) {\r\n backgroundPainter.dispose()\r\n return\r\n }\r\n paintBackground(value)\r\n }\r\n const unsubscribe = settingsClient.subscribe(paint)\r\n void settingsClient.load()\r\n\r\n ctx.effect(() => ctx.locale.register('ui-background', { zh, en }), 'deepseek-harness-background: dictionaries')\r\n\r\n // Live settings row in the General section (same slot as the Appearance row).\r\n ctx.slots.inject('settings.general.item', () => ctx.slots.register({\r\n name: 'settings.general.item',\r\n id: 'deepseek-harness-background',\r\n order: 600,\r\n locale: 'ui-background',\r\n }, BackgroundSettingsRow))\r\n\r\n // Conversation timeline: takes a per-session dock seat only to bind its\r\n // lifecycle — the official rail is enhanced in place, never re-rendered.\r\n // A bound jumpToAnchor verb is handed to the component through the\r\n // registration's inject face (the ui-goal verbs+hooks shape): the service\r\n // never crosses the slot boundary, and the session is bound lazily inside\r\n // the factory so a runtime-side service rebuild can never leave the rail\r\n // holding a stale reference.\r\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\r\n name: 'conversation.input.dock',\r\n id: 'deepseek-harness-background.timeline',\r\n order: 45,\r\n locale: 'ui-background',\r\n inject: (sessionId: string) => ({\r\n jumpToAnchor: (anchorKey: string) => {\r\n // Scope the bottom-follow detach to this rail's own column: the\r\n // official rail is mounted, so its scrollport is known and the other\r\n // columns need no nudge. Resolved at call time, not at registration.\r\n const scrollport = findOfficialRail()\r\n ?.closest<HTMLElement>('[data-conversation-scroll]') ?? null\r\n return jumpToMessage(ctx.sessions, sessionId, anchorKey, {\r\n followZonePx: CHATVIEW_FOLLOW_ZONE_PX,\r\n targetTop: officialTargetTopFor,\r\n scrollport,\r\n })\r\n },\r\n }),\r\n }, TimelineBridge))\r\n\r\n ctx.effect(() => () => {\r\n unsubscribe()\r\n glassBridge.dispose()\r\n backgroundPainter.dispose()\r\n }, 'deepseek-harness-background: background surface')\r\n}\r\n"],"x_google_ignoreList":[5],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useState","useRef","css","useSyncExternalStore","useCallback"],"sources":["../src/client/background-css.ts","../src/settings.ts","../src/client/backdrop.ts","../src/client/glass-registry.ts","../src/client/locales.ts","../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","../src/client/settings-client.ts","../src/client/SettingsRow.tsx","../src/client/timeline/rail-pointer.ts","../src/client/timeline/official-enhance.tsx","../src/client/timeline/jump.ts","../src/client/timeline/index.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Background CSS — the stylesheet injected once into the document when the\n * plugin first runs. It defines the behind-body wallpaper layer, the scrim,\n * and the frosted-glass surfaces, all driven by CSS variables the painter\n * writes onto `document.body`. Kept in a single idempotent\n * `<style data-plugin-css>` tag (mirrors the reference `dsh-wallpaper-engine`\n * bundle).\n */\n\n/** Unique id stamped on the injected style element (dedup key). */\nexport const BACKGROUND_CSS_TAG = 'deepseek-harness-background/styles'\n\n/** Body attribute the painter sets while a wallpaper is active. */\nexport const ACTIVE_ATTR = 'data-dsh-bg'\n\n/**\n * Body attribute the painter sets only while the glass is actually on (a\n * wallpaper is active AND panelOpacity < 1). The explicit-fill rules below —\n * whitelisted buttons, popovers and badges whose official fills are not\n * token-mediated — key on this gate so they return to the exact official\n * paints when the user maxes the panel-opacity slider.\n */\nexport const GLASS_ATTR = 'data-dsh-bg-glass'\n\n/**\n * The backdrop CSS. The wallpaper is a fixed `z-index:-2` element (below the\n * scrim at `-1`, below the UI). While active, the app frame + sidebar fills\n * turn transparent so all columns share one backdrop.\n *\n * Frosted glass is a WHITELIST, not a blanket: only the surfaces that float\n * as small cards/buttons over the wallpaper are glassed — composer card,\n * message bubbles, code surfaces (blocks, inline code, tool IO cards,\n * skill/MCP call cards), the three chrome buttons (new session, composer +\n * button, scroll-to-bottom), the load-earlier history button, the composer\n * dock family (agent task strips: TodoPanel / GoalBar / QueueDock) and its\n * takeover panels (approval, question, plan review), the subagent lineage\n * popover, the home hero \"preview\" badge, the sidebar build badge, the turn\n * deliverables (the changed-files card and the diff hover preview it opens),\n * and the timeline rail (the official-rail rules below).\n * Every glassed surface shares ONE recipe:\n * fill from the painter's --dsw-specific-input-major token (same alpha\n * curve + theme dimming), the full shared blur/saturate/brightness chain\n * driven by the glass-blur slider, and the shared sheen vars — so no\n * surface reads heavier than the composer card. Dialogs, the settings UI,\n * menus, tooltips, toasts and every hover/accent fill keep the OFFICIAL\n * opaque paints — they are reading surfaces and must stay legible.\n * The same rule excludes the HoverCard primitive's compact pod (a fixed\n * #2C2C2E chip): it is hover FEEDBACK, not a floating card, and the official\n * opaque paint is what keeps its label legible over any wallpaper.\n *\n * Selectors target authored attributes (`data-dsh-bg`, `data-composer-card`,\n * `data-terminal`, `data-changes-hover-preview`…), the `:global`\n * `.md-code-block` anchor, and CSS-module SUFFIX conventions (`_bubble`,\n * `_newSession`…) audited for collisions.\n *\n * MAINTENANCE NOTE: the suffix anchors are the host's only stable-enough\n * integration surface today, but a host restyle that renames them silently\n * drops the glass. Upgrade path: once the host ships stable data-* anchors\n * for these surfaces, swap the selector lists below to them and delete the\n * suffix entries.\n * `_bubble` excludes `role=\"tooltip\"` (Tooltip.module.css shares the suffix),\n * `_add` is scoped under `[data-composer-card]` (DiffBlock line markers share\n * it), `_newSession` is scoped to the `button` element — an element test, not\n * another suffix exclusion, because 0.1.7-rc.2 added `newSessionLabelMask` /\n * `newSessionContent` / `newSessionShortcut` INSIDE the button and each of\n * those spans would otherwise take the chrome paint (the composer-sized\n * second box the user sees inside the control) — and `_toBottom` excludes\n * `Slot` (the zero-height sticky wrapper) — both share the substring with the\n * chrome control but must keep their own paint, and the subagent popover is\n * `role=\"tree\"` + `_menu` so the generic menu surface stays untouched.\n */\nexport const BACKGROUND_CSS = `\n .dsh-bg-layer {\n position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none;\n }\n .dsh-bg-layer .dsh-bg-image {\n width: 100%; height: 100%; display: block; border: 0; background: transparent;\n object-fit: var(--bg-object-fit, cover);\n opacity: var(--bg-opacity, 1);\n filter: blur(var(--bg-wallpaper-blur, 0px));\n /* Blur thins the edges; scale the image up to hide the fringe. */\n transform: scale(var(--bg-wallpaper-scale, 1));\n transform-origin: center;\n }\n /* Scrim is theme-aware: a WHITE veil in the light scheme (it lifts the art\n toward the near-white surface so dark text keeps contrast) and a BLACK veil\n in the dark scheme (it dims the art so light text keeps contrast). */\n .dsh-bg-scrim {\n position: fixed; inset: 0; z-index: -1; pointer-events: none;\n background: rgba(255, 255, 255, var(--bg-scrim, 0.25));\n }\n body[data-ds-dark-theme] .dsh-bg-scrim {\n background: rgba(0, 0, 0, var(--bg-scrim, 0.25));\n }\n\n /* Active: make the app frame + sidebar background transparent so all columns\n share the same wallpaper + scrim backdrop. */\n body[data-dsh-bg] {\n --dsw-alias-bg-base: transparent;\n --dsw-specific-sidebar-fill: transparent;\n }\n\n /* ---- Whitelisted frosted-glass sheet ---------------------------------\n Fills come from the --dsw-* tokens the painter overrides on body ONLY for\n this list's own surfaces (input/bubble/code/tip); this block adds the\n shared wet-glass sheen + blur from --bg-glass-blur. Exposure is calibrated\n per scheme (the painter writes --bg-glass-brightness / --bg-glass-sheen):\n light glass slightly DIMS, dark glass keeps a slight lift. Gated on the\n GLASS ATTR (wallpaper active AND panelOpacity < 1): maxing the panel\n slider returns these surfaces to the exact official paints. */\n body[data-dsh-bg-glass] [data-composer-card],\n body[data-dsh-bg-glass] [class*=\"_bubble\"]:not([role=\"tooltip\"]),\n /* The read_image toolview (dsh >= 0.1.2-rc.1 ToolRow) collapses its result\n into an image body card; glass it like the other content blocks. */\n body[data-dsh-bg-glass] [class*=\"_imageBody\"],\n body[data-dsh-bg-glass] .md-code-block,\n body[data-dsh-bg-glass] [data-terminal],\n body[data-dsh-bg-glass] [data-diff],\n body[data-dsh-bg-glass] [data-read],\n body[data-dsh-bg-glass] [data-search],\n body[data-dsh-bg-glass] [data-web],\n body[data-dsh-bg-glass] [class*=\"_ioCard\"],\n body[data-dsh-bg-glass] [class*=\"_instructionsCard\"],\n body[data-dsh-bg-glass] [class*=\"_markdown\"]:not(pre) > code {\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.32),\n inset 0 -1px 0 rgba(255, 255, 255, 0.08),\n inset 0 0 0 0.5px rgba(255, 255, 255, 0.08),\n 0 12px 40px rgba(0, 0, 0, 0.12);\n }\n\n /* The sticky code-block header officially occludes scrolled code with\n bg-base (transparent under a wallpaper): refill it with the banner token\n (itself translucent glass) so code stays hidden beneath the sticky bar. */\n body[data-dsh-bg] .md-code-block [class*=\"_bannerWrap\"] {\n background-color: var(--dsw-alias-markdown-code-block-banner);\n }\n\n /* The sidebar build badge paints with the label-ink token — a TEXT token we\n must not override globally — so it gets a theme-aware translucent ink. */\n body[data-dsh-bg] [class*=\"_buildVersion\"] {\n background-color: rgba(15, 17, 21, 0.62);\n }\n body[data-ds-dark-theme][data-dsh-bg] [class*=\"_buildVersion\"] {\n background-color: rgba(249, 250, 251, 0.62);\n }\n\n /* ---- Whitelisted chrome buttons --------------------------------------\n These three buttons' official fills ride button-* tokens shared with\n unrelated surfaces, so instead of overriding the tokens they get explicit\n paints — but the SAME glass recipe as the composer card and bubbles:\n fill = the painter's --dsw-specific-input-major token (the identical\n translucent white + theme curve), filter = the sheet's exact\n blur/saturate/brightness chain (blur follows the glass-blur slider, no\n caps), sheen = the shared sheen vars. Hover = the same white glass at the\n boosted alpha var. Gated on data-dsh-bg-glass (off when the panel is\n fully opaque). */\n body[data-dsh-bg-glass] button[class*=\"_newSession\"],\n body[data-dsh-bg-glass] [data-composer-card] [class*=\"_add\"],\n body[data-dsh-bg-glass] [class*=\"_toBottom\"]:not([class*=\"Slot\"]) {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.32),\n inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),\n 0 4px 14px rgba(0, 0, 0, 0.08);\n }\n body[data-dsh-bg-glass] button[class*=\"_newSession\"]:hover,\n body[data-dsh-bg-glass] [data-composer-card] [class*=\"_add\"]:hover:not(:disabled),\n body[data-dsh-bg-glass] [class*=\"_toBottom\"]:not([class*=\"Slot\"]):hover {\n background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);\n }\n /* Collapsed rail renders the new-session control as a bare icon — keep the\n official transparent paint (and its official translucent hover). */\n body[data-dsh-bg-glass] [data-sidebar-collapsed=\"true\"] [class*=\"_newSession\"],\n body[data-dsh-bg-glass] [data-sidebar-collapsed=\"true\"] [class*=\"_newSession\"]:hover {\n background-color: transparent;\n background-image: none;\n box-shadow: none;\n }\n\n /* ---- Subagent lineage popover (title-bar expanded list) --------------\n Its official fill rides the menu token shared with every dropdown; glass\n THIS popover explicitly via its role=\"tree\" + _menu combination so real\n menus stay officially opaque and legible. Same unified recipe: the\n composer fill token + the full shared filter chain (the official\n elevation shadow stays). */\n body[data-dsh-bg-glass] [role=\"tree\"][class*=\"_menu\"] {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n\n /* ---- Home hero \"preview\" badge (top-right superscript pill) ----------\n Officially the state-business-tertiary pastel; re-emit the same hue as\n glass but with the UNIFIED alpha curve (color-mix against the painter's\n --bg-glass-alpha) and the shared blur chain, so it sits on the wallpaper\n with exactly the glass strength of the composer card. */\n body[data-dsh-bg-glass] [class*=\"_previewBadge\"] {\n background-color: color-mix(in srgb, rgb(228 237 253) calc(var(--bg-glass-alpha, 0.72) * 100%), transparent);\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n body[data-ds-dark-theme][data-dsh-bg-glass] [class*=\"_previewBadge\"] {\n background-color: color-mix(in srgb, rgb(52 65 91) calc(var(--bg-glass-alpha, 0.72) * 100%), transparent);\n }\n\n /* ---- Composer dock family: task strips + takeover panels --------------\n The agent task strips (TodoPanel, GoalBar, QueueDock — one dock column\n above the composer) and the composer takeovers (approval, question,\n plan review) paint with tokens the painter turns translucent\n (--dsw-specific-tip / --dsw-specific-input-major) but their own styles\n never blur anything: translucent-without-blur reads as bare\n transparency, not frosted glass. Give each the shared sheen + filter\n chain; their fills stay token-driven so the panel-opacity slider keeps\n driving them. Anchors are the components' own stable data attributes\n plus a structural child, so no generic class suffix is swept in. */\n body[data-dsh-bg-glass] [data-testid=\"todo-panel\"],\n body[data-dsh-bg-glass] [data-goal-bar] > div,\n body[data-dsh-bg-glass] [data-queue-dock] > div,\n body[data-dsh-bg-glass] [data-approval-key] > div,\n body[data-dsh-bg-glass] [data-question-key] > section,\n body[data-dsh-bg-glass] [data-plan-review-key] > section {\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n\n /* ---- \"Load earlier\" history button (top of long conversations) --------\n Its official fill is an opaque solid button token that is deliberately\n NOT overridden, so unlike the tip family it needs an explicit glass\n fill too — same recipe as the three chrome buttons above. */\n body[data-dsh-bg-glass] [class*=\"_older\"] button {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.32),\n inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),\n 0 4px 14px rgba(0, 0, 0, 0.08);\n }\n body[data-dsh-bg-glass] [class*=\"_older\"] button:hover:not(:disabled) {\n background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);\n }\n\n /* Sidebar build badge: translucent ink already, but it sat directly on\n the wallpaper without frost. Join the shared blur chain under the same\n gate (its theme-aware ink fills from the active block above). */\n body[data-dsh-bg-glass] [class*=\"_buildVersion\"] {\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n\n /* ---- Turn deliverables: the changed-files card + its hover preview -----\n Both paint with the official opaque --dsw-alias-bg-layer-1 (the token the\n painter deliberately keeps opaque, because it backs the settings UI,\n dialogs and menus), so each needs an EXPLICIT fill on top of the shared\n sheen/blur chain — the token-mediated rule above cannot reach them.\n [data-changed-files] is the card a turn tail renders; its header and\n rows carry their own fills, so the card surface alone is glassed and the\n contents keep their official paints. [data-changes-hover-preview] is\n the fixed HoverCard the changed-files rows open on hover and the shared\n component behind every diff preview; its width/height are set by the\n hover-card primitive, so this only supplies the material. The preview's\n official elevation shadow rides its own token untouched — only the fill\n and the filter chain are taken over here. */\n body[data-dsh-bg-glass] [data-changed-files],\n body[data-dsh-bg-glass] [data-changes-hover-preview] {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n box-shadow: var(--dsw-elevation-panel, 0 4px 14px rgba(0, 0, 0, 0.08));\n }\n\n /* ---- Official turn rail (dsh >= 0.1.2, TurnNavigator) -----------------\n The enhance mode borrows the kernel's rail untouched, so its hover\n preview card keeps the OFFICIAL opaque fill (bg-layer-1, not mediated\n by any overridden token) — the \"悬浮反馈形态不透明\" complaint. Glass it\n explicitly with the same explicit-fill recipe as the chrome buttons\n (fill = the painter's composer token + shared blur/sheen). The anchor is\n the rail's SEAT, the same one timeline/official-enhance.tsx identifies it\n by: the only nav inside the conversation scrollport. 0.1.7 virtualized\n the rail (commit f89cd786aa), which deleted the turn-natural-height\n inline metric that used to sit on that nav — the nav carries no style\n attribute at all now — so the selector that keyed on it matched nothing\n and the preview went bare. The scrollport attribute and the preview's\n role=\"tooltip\" are the structural hooks; class hashes never are. No edge\n dissolve on the marks column: the first and last tick dashes straddle\n the marks box edges by 1px, so a fade zone sized off the box height\n swallows them instead of the band. */\n body[data-dsh-bg-glass] [data-conversation-scroll] nav [role=\"tooltip\"] {\n background-color: var(--dsw-specific-input-major);\n background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));\n -webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);\n }\n`\n\n/**\n * Inject the backdrop stylesheet once. Safe to call repeatedly; a second call\n * is a no-op (dedup key on the style tag).\n */\nexport function injectBackgroundCss(): void {\n if (document.querySelector(`style[data-plugin-css=\"${BACKGROUND_CSS_TAG}\"]`)) return\n const tag = document.createElement('style')\n tag.dataset.plugin = 'deepseek-harness-background'\n tag.dataset.pluginCss = BACKGROUND_CSS_TAG\n tag.textContent = BACKGROUND_CSS\n document.head.appendChild(tag)\n}\n","/** Durable background settings — pure constants/types shared by both halves. */\r\n\r\n/** Settings namespace owned by the background plugin. */\r\nexport const BACKGROUND_SETTINGS_NAMESPACE = 'ui-background'\r\n\r\n/**\r\n * The schema's field names, in schema order. Used as a whitelist when a\r\n * section write commits: the settings layer keeps unknown keys in the user\r\n * document (a legacy schema field would linger forever under a merge-only\r\n * write), so the write path strips everything outside this list.\r\n */\r\nexport const BACKGROUND_SETTINGS_FIELDS = [\r\n 'enabled', 'uploadId', 'url', 'opacity', 'scrim',\r\n 'panelOpacity', 'blur', 'wallpaperBlur', 'fit', 'timeline',\r\n] as const\r\n\r\n/** Browser-facing base path of the background API (shared with the host routes). */\r\nexport const BACKGROUND_API_PREFIX = '/api/bg-wallpaper'\r\n\r\n/** Minimum scrim opacity (0 = no veil at all). */\r\nexport const SCRIM_MIN = 0\r\n\r\n/** Maximum scrim opacity (1 = fully opaque veil — hides the wallpaper entirely).\r\n * Kept at 1 so every percent-unit slider shares one 0..1 domain: the same\r\n * displayed percentage puts every thumb at the same fraction of its track. */\r\nexport const SCRIM_MAX = 1\r\n\r\n/** Default scrim strength applied over user art (a light readability veil). */\r\nexport const DEFAULT_SCRIM = 0.25\r\n\r\n/** Default background image opacity (1 = fully opaque). */\r\nexport const DEFAULT_OPACITY = 1\r\n\r\n/** Minimum background image opacity (0 = fully invisible). */\r\nexport const OPACITY_MIN = 0\r\n\r\n/** Maximum background image opacity (1 = fully opaque). */\r\nexport const OPACITY_MAX = 1\r\n\r\n/** Default panel opacity (0..1); 1 = fully opaque official surfaces, 0 = fully transparent glass. */\r\nexport const DEFAULT_PANEL_OPACITY = 0.15\r\n\r\n/** Minimum panel opacity. */\r\nexport const PANEL_OPACITY_MIN = 0\r\n\r\n/** Maximum panel opacity (opaque — effectively disables the glass). */\r\nexport const PANEL_OPACITY_MAX = 1\r\n\r\n/** Default frosted-glass blur radius in px (0 disables backdrop-filter). */\r\nexport const DEFAULT_BLUR = 16\r\n\r\n/** Minimum glass blur radius. */\r\nexport const BLUR_MIN = 0\r\n\r\n/** Maximum glass blur radius. */\r\nexport const BLUR_MAX = 40\r\n\r\n/** Default wallpaper blur radius in px (blurs the image itself). */\r\nexport const DEFAULT_WALLPAPER_BLUR = 0\r\n\r\n/** Maximum wallpaper blur radius. */\r\nexport const WALLPAPER_BLUR_MAX = 60\r\n\r\n/** Whether the DeepSeek-style conversation timeline rail renders (default on). */\r\nexport const DEFAULT_TIMELINE = true\r\n\r\n/** Default background rendering mode. */\r\nexport const DEFAULT_FIT = 'cover'\r\n\r\n/** Image positioning modes. `cover` fills the frame (cropping as needed); `contain` fits the whole image inside the frame (letterboxing). */\r\nexport const FIT_MODES = ['cover', 'contain'] as const\r\n\r\n/** Background rendering mode. */\r\nexport type BackgroundFit = typeof FIT_MODES[number]\r\n\r\n/**\r\n * User-owned background section, resolved from schema defaults + user layer.\r\n * The active source is either a local upload id (host serves it via\r\n * `/api/bg-wallpaper/image/<id>`) or a raw HTTP(S) image URL the browser\r\n * loads; exactly one of `uploadId` / `url` is set — when both are empty no\r\n * background renders.\r\n */\r\nexport interface BackgroundSettings {\r\n /** Whether the custom background renders at all. */\r\n enabled: boolean\r\n /** Local upload id when the active source is an uploaded file. */\r\n uploadId: string\r\n /** Image URL when the active source is a remote URL. */\r\n url: string\r\n /** Background image opacity (0..1); the image fades toward the surface color. */\r\n opacity: number\r\n /** Readability scrim strength (0..1) layered between the art and the UI. */\r\n scrim: number\r\n /** Panel transparency (0..1): 0 = fully transparent surfaces, 1 = unaffected. */\r\n panelOpacity: number\r\n /** Frosted-glass blur radius on opaque surfaces (px; 0 disables). */\r\n blur: number\r\n /** Blur of the wallpaper image itself (px; 0 disables). */\r\n wallpaperBlur: number\r\n /** How the image fits the frame. */\r\n fit: BackgroundFit\r\n /** Whether the conversation timeline rail renders at the right edge. */\r\n timeline: boolean\r\n}\r\n","/**\n * Background painter — renders the user background behind the whole app frame:\n * a fixed `z-index:-2` wallpaper element plus a `z-index:-1` scrim on\n * `document.body`, driven by the `data-dsh-bg` body attribute and a set of\n * CSS variables. It also applies the frosted-glass surface tokens so the\n * wallpaper shows through translucent input / bubble panels.\n *\n * Retraction discipline mirrors the reference skin plugins: every property /\n * DOM node this painter owns is restored to its prior state on dispose, and\n * the `data-ds-dark-theme` observer is disconnected.\n */\n\nimport { ACTIVE_ATTR, GLASS_ATTR, injectBackgroundCss } from './background-css.ts'\nimport { BACKGROUND_API_PREFIX, type BackgroundSettings } from '../settings.ts'\n\n/** Class names the layer and scrim elements carry. */\nconst LAYER_CLASS = 'dsh-bg-layer'\nconst IMAGE_CLASS = 'dsh-bg-image'\nconst SCRIM_CLASS = 'dsh-bg-scrim'\n\n/** Body style properties the painter owns while a wallpaper paints: the app\n * frame and sidebar fills go transparent so every column shares one art.\n * Inline values outrank any stylesheet rule (the body[data-dsh-bg] gate only\n * covers the CSS copy), so a sourceless section must actively restore them. */\nconst FRAME_TOKENS = ['--dsw-alias-bg-base', '--dsw-specific-sidebar-fill'] as const\n\n/**\n * White-glass tokens — the WHITELISTED surfaces only. Everything that floats\n * as a small card over the wallpaper: the composer input stack, message\n * bubbles, the markdown code surfaces (blocks + banner + inline code), and\n * the agent task strip family (`--dsw-specific-tip`: TodoPanel + GoalBar +\n * QueueDock share one dock column and one fill token). Menus, dialogs, the\n * settings UI, tooltips, toasts, hover fills and every accent keep the\n * OFFICIAL token values — they are reading surfaces and must stay legible.\n * `factor` scales the alpha relative to the input surface.\n */\nconst GLASS_SURFACE_TOKENS: readonly { token: string; factor: number }[] = [\n { token: '--dsw-specific-input-major', factor: 1 },\n { token: '--dsw-specific-bubble', factor: 0.8 },\n { token: '--dsw-alias-markdown-code-block', factor: 1 },\n { token: '--dsw-alias-markdown-code-block-banner', factor: 1 },\n { token: '--dsw-alias-markdown-inline-code', factor: 0.9 },\n { token: '--dsw-specific-tip', factor: 1 },\n]\n\n/** Every token the glass owns (save/restore key set). */\nconst GLASS_TOKENS: readonly string[] = GLASS_SURFACE_TOKENS.map(({ token }) => token)\n\n/** Fraction of the official white surface alpha kept at the most transparent panel setting. */\nconst GLASS_MIN_ALPHA = 0.05\n\n/** Base white-glass alpha for one panelOpacity (the curve every surface shares). */\nfunction glassAlpha(panelOpacity: number): number {\n const alpha = GLASS_MIN_ALPHA + panelOpacity * (0.9 - GLASS_MIN_ALPHA)\n return Math.max(0, Math.min(0.9, alpha))\n}\n\n/**\n * Saturation boost that rides the blur radius. Gentler than the reference\n * engine (1.1 + blur * 0.02, capped at 1.6): stacking a hot saturate on top of\n * the brightness/sheen layers pushed bright wallpapers into clipping.\n */\nfunction glassSaturate(blur: number): number {\n if (blur <= 0) return 1\n // Round off float dust (1.1 + 16 * 0.02 is 1.4200000000000002 raw).\n return Math.round(Math.min(1.6, 1.1 + blur * 0.02) * 1000) / 1000\n}\n\n/**\n * Exposure calibration per scheme. Light glass neither boosts brightness nor\n * stacks a heavy sheen (mainstream frosted recipes — macOS vibrancy, Windows\n * acrylic, common web glass — blur + saturate + tint without a positive\n * brightness gain); dark glass keeps the reference engine's slight lift, which\n * stops the white-glass fills from turning muddy.\n */\nfunction glassExposure(inDark: boolean): { brightness: string; sheen: string; sheenMid: string } {\n return inDark\n ? { brightness: '1.04', sheen: '0.16', sheenMid: '0.05' }\n : { brightness: '0.98', sheen: '0.07', sheenMid: '0.02' }\n}\n\n/**\n * The theme-aware base alpha for one panelOpacity — THE curve every glass\n * surface shares (surface tokens, bare alpha knobs and the settings preview\n * included). Single-sourced here so the live painter and the preview card\n * cannot drift.\n */\nfunction glassBaseAlpha(panelOpacity: number, inDark: boolean): number {\n return glassAlpha(panelOpacity) * (inDark ? 0.4 : 0.8)\n}\n\n/**\n * The theme-aware glass surface alphas for one panelOpacity: the input-surface\n * token and the bubble token share one curve, dimmed per scheme. Shared by the\n * live painter and the settings-row preview card so the two cannot drift.\n */\nfunction glassSurfaceAlphas(panelOpacity: number, inDark: boolean): { major: string; bubble: string } {\n const alpha = glassBaseAlpha(panelOpacity, inDark)\n return {\n major: `rgba(255, 255, 255, ${alpha.toFixed(3)})`,\n bubble: `rgba(255, 255, 255, ${(alpha * 0.8).toFixed(3)})`,\n }\n}\n\n/**\n * Resolve the effective background image url for a section.\n * @param settings - resolved background section.\n * @returns the image url, or '' when no source is set.\n */\nexport function backgroundImageUrl(settings: BackgroundSettings, uploadBase: string): string {\n if (settings.uploadId) return `${uploadBase}/image/${settings.uploadId}`\n return settings.url\n}\n\n/**\n * Manage the behind-body wallpaper + scrim layers and the glass surface.\n */\nexport class BackgroundPainter {\n private layer: HTMLDivElement | null = null\n private img: HTMLImageElement | null = null\n private scrim: HTMLDivElement | null = null\n private observer: MutationObserver | undefined\n private active = false\n private settings: BackgroundSettings | undefined\n /** Whether the current section resolves to an actual image source. */\n private hasSource = false\n private readonly savedTokens = new Map<string, string>()\n private readonly savedVars = new Map<string, string>()\n\n /** Remember + hijack a property only once so dispose restores the original. */\n private rememberOnce(prop: string): void {\n if (this.savedTokens.has(prop) || this.savedVars.has(prop)) return\n const value = document.body.style.getPropertyValue(prop)\n if (GLASS_TOKENS.includes(prop)) {\n this.savedTokens.set(prop, value)\n } else {\n this.savedVars.set(prop, value)\n }\n }\n\n /**\n * Apply the latest settings. Idempotent.\n * @param settings - resolved background section.\n * @param uploadBase - the API base for resolving uploads.\n */\n apply(settings: BackgroundSettings, uploadBase: string): void {\n this.settings = settings\n if (!settings.enabled) {\n this.dispose()\n return\n }\n injectBackgroundCss()\n\n const url = backgroundImageUrl(settings, uploadBase)\n if (url === '') {\n // No source: the frame transparency is owned only while a wallpaper\n // paints — hand the base/sidebar fills back (a sourceless \"enabled\"\n // section must never leave transparent chrome over the bare page), and\n // drop the theme observer + glass tokens whose flip handler would\n // otherwise re-write translucent panels over that bare page too.\n this.observer?.disconnect()\n this.observer = undefined\n this.restoreFrameTokens()\n this.applySurfaceGlass(settings, true)\n this.active = false\n this.hasSource = false\n this.removeLayers()\n document.body.removeAttribute(ACTIVE_ATTR)\n return\n }\n\n // Transparent frame: all columns share the wallpaper behind the UI.\n for (const prop of FRAME_TOKENS) {\n this.rememberOnce(prop)\n document.body.style.setProperty(prop, 'transparent')\n }\n\n // Wallpaper layer + scrim.\n if (!this.layer) {\n this.layer = document.createElement('div')\n this.layer.className = LAYER_CLASS\n this.img = document.createElement('img')\n this.img.className = IMAGE_CLASS\n this.img.alt = ''\n // Remote-url sources must not leak the local origin via Referer.\n this.img.referrerPolicy = 'no-referrer'\n // A dead source (404 / network error / revoked upload) must not leave a\n // blank frozen backdrop: hide the broken image once (the scrim and\n // glass still paint) and say so once on the console. Switching to a\n // different URL below clears the failure state and retries.\n this.img.onerror = () => {\n if (this.img === null || this.img.dataset.failed === '1') return\n this.img.dataset.failed = '1'\n this.img.style.visibility = 'hidden'\n console.warn('[deepseek-harness-background] wallpaper image failed to load:', this.img.getAttribute('src'))\n }\n this.layer.appendChild(this.img)\n document.body.appendChild(this.layer)\n }\n // Compare the *attribute* (the raw value we set) rather than the .src\n // property, which resolves to an absolute URL and would make this check\n // always-true for relative urls. Same-value writes are no-ops in the DOM,\n // so this is a clarity fix, not a behavior fix.\n if (this.img && this.img.getAttribute('src') !== url) {\n delete this.img.dataset.failed\n this.img.style.visibility = ''\n this.img.src = url\n }\n\n if (!this.scrim) {\n this.scrim = document.createElement('div')\n this.scrim.className = SCRIM_CLASS\n document.body.appendChild(this.scrim)\n }\n document.body.setAttribute(ACTIVE_ATTR, 'on')\n this.active = true\n this.hasSource = true\n\n // Push the knobs into CSS variables.\n const s = document.body.style\n this.setVar('--bg-object-fit', settings.fit)\n this.setVar('--bg-opacity', String(settings.opacity))\n this.setVar('--bg-wallpaper-blur', `${settings.wallpaperBlur}px`)\n this.setVar('--bg-wallpaper-scale', (1 + settings.wallpaperBlur * 0.006).toFixed(4))\n this.setVar('--bg-scrim', String(settings.scrim))\n this.setVar('--bg-glass-blur', `${settings.blur}px`)\n this.setVar('--bg-glass-saturate', String(glassSaturate(settings.blur)))\n\n // Glass surface: theme-aware translucent token (panelOpacity === 1 keeps\n // the official opaque surfaces and disables the blur).\n this.applySurfaceGlass(settings, false)\n\n // Theme observer: repaint the glass tokens when the light/dark scheme\n // flips (the surface translucency differs between schemes). Disconnects\n // on dispose.\n if (!this.observer) {\n this.observer = new MutationObserver(() => this.applySurfaceGlass(this.settings, false))\n this.observer.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })\n }\n }\n\n /** Write one owned CSS variable onto body. */\n private setVar(name: string, value: string): void {\n this.rememberOnce(name)\n document.body.style.setProperty(name, value)\n }\n\n /**\n * Return the owned frame-transparency properties to their saved state (or\n * remove them when this session never overrode them). dispose() restores\n * everything wholesale; this targeted variant serves the sourceless path,\n * where the painter keeps running for a later source.\n */\n private restoreFrameTokens(): void {\n for (const prop of FRAME_TOKENS) {\n this.rememberOnce(prop)\n const original = this.savedVars.get(prop)\n if (original !== undefined && original !== '') document.body.style.setProperty(prop, original)\n else document.body.style.removeProperty(prop)\n }\n }\n\n /**\n * Update one effect knob without re-running the full apply. This is the hot\n * path for slider drags (many events per second): only the single CSS\n * variable changes, the DOM layers are untouched.\n * @param key - the knob to update.\n * @param value - its new value in its canonical unit.\n */\n setKnob(key: 'opacity' | 'scrim' | 'panelOpacity' | 'blur' | 'wallpaperBlur' | 'fit', value: number | BackgroundSettings['fit']): void {\n if (this.settings === undefined || !this.settings.enabled) return\n switch (key) {\n case 'opacity':\n this.setVar('--bg-opacity', String(value))\n break\n case 'scrim':\n this.setVar('--bg-scrim', String(value))\n break\n case 'panelOpacity': {\n // The glass surface tokens depend on the panel opacity; repaint ONLY\n // them (the slider hot path — no layer churn, no full apply). The\n // stored section adopts the value so a later theme flip repaints with\n // the fresh opacity, and a sourceless section keeps the official\n // opaque surfaces (forceRestore) exactly like the full apply does.\n const next = { ...this.settings, panelOpacity: Number(value) }\n this.settings = next\n this.applySurfaceGlass(next, !this.hasSource)\n break\n }\n case 'blur': {\n this.setVar('--bg-glass-blur', `${value}px`)\n this.setVar('--bg-glass-saturate', String(glassSaturate(Number(value))))\n break\n }\n case 'wallpaperBlur': {\n this.setVar('--bg-wallpaper-blur', `${value}px`)\n this.setVar('--bg-wallpaper-scale', (1 + Number(value) * 0.006).toFixed(4))\n break\n }\n case 'fit':\n this.setVar('--bg-object-fit', String(value))\n break\n }\n }\n\n /**\n * Apply the translucent-glass surface tokens (or restore the official ones\n * when the panel is fully opaque / there is no background).\n * @param settings - resolved section (for panelOpacity).\n * @param forceRestore - true to restore official surfaces regardless (used\n * when no source is set, so leftover glass tokens never linger).\n */\n private applySurfaceGlass(settings: BackgroundSettings | undefined, forceRestore: boolean): void {\n const s = document.body.style\n const inDark = document.body.dataset.dsDarkTheme !== undefined\n // Always remember the original surface values so a restore / dispose\n // returns them cleanly.\n for (const token of GLASS_TOKENS) this.rememberOnce(token)\n\n const restore = () => {\n for (const token of GLASS_TOKENS) {\n const original = this.savedTokens.get(token)\n if (original !== undefined && original !== '') s.setProperty(token, original)\n else s.removeProperty(token)\n }\n }\n\n if (forceRestore || settings === undefined || settings.panelOpacity >= 1) {\n restore()\n document.body.removeAttribute(GLASS_ATTR)\n // No blur and a neutral exposure when glass is off; the bare alpha\n // knobs only matter under the glass gate.\n this.setVar('--bg-glass-blur', '0px')\n this.setVar('--bg-glass-brightness', '1')\n s.removeProperty('--bg-glass-alpha')\n s.removeProperty('--bg-glass-alpha-strong')\n return\n }\n\n document.body.setAttribute(GLASS_ATTR, 'on')\n\n // Exposure calibration (theme-aware, mirrored by the preview surface).\n const exposure = glassExposure(inDark)\n this.setVar('--bg-glass-brightness', exposure.brightness)\n this.setVar('--bg-glass-sheen', exposure.sheen)\n this.setVar('--bg-glass-sheen-mid', exposure.sheenMid)\n\n // Dark scheme uses a lower white alpha (the wallpaper stays visible\n // without washing the surface), mirroring the reference engine.\n const base = glassBaseAlpha(settings.panelOpacity, inDark)\n for (const { token, factor } of GLASS_SURFACE_TOKENS) {\n s.setProperty(token, `rgba(255, 255, 255, ${(base * factor).toFixed(3)})`)\n }\n // Bare alpha knobs for the surfaces that keep their own hue (preview\n // badge, via color-mix) or need a hover boost (chrome buttons): the same\n // curve every glass surface shares.\n this.setVar('--bg-glass-alpha', base.toFixed(3))\n this.setVar('--bg-glass-alpha-strong', Math.min(0.9, base * 1.5).toFixed(3))\n }\n\n /** Remove the wallpaper + scrim layers and clear the active attribute. */\n private removeLayers(): void {\n if (this.layer) { this.layer.remove(); this.layer = null; this.img = null }\n if (this.scrim) { this.scrim.remove(); this.scrim = null }\n }\n\n /** Restore every owned property and remove the layers. */\n dispose(): void {\n this.observer?.disconnect()\n this.observer = undefined\n this.removeLayers()\n const s = document.body.style\n document.body.removeAttribute(ACTIVE_ATTR)\n document.body.removeAttribute(GLASS_ATTR)\n for (const [prop, value] of this.savedTokens) s.setProperty(prop, value)\n for (const [prop, value] of this.savedVars) s.setProperty(prop, value)\n this.savedTokens.clear()\n this.savedVars.clear()\n this.active = false\n this.settings = undefined\n }\n\n /** @returns whether a wallpaper is currently rendered. */\n isActive(): boolean {\n return this.active\n }\n}\n\n/** Module-level painter singleton shared by the settings row preview and the live apply. */\nexport const backgroundPainter = new BackgroundPainter()\n\n/**\n * Apply the given settings through the shared painter (idempotent). Convenience\n * used by the settings row for live previews and by the plugin apply to render\n * the persisted section.\n * @param settings - settings to apply (may be an unsaved draft for preview).\n */\nexport function paintBackground(settings: BackgroundSettings): void {\n backgroundPainter.apply(settings, BACKGROUND_API_PREFIX)\n}\n\n/** Numeric effect knobs the slider drag path repaints. */\nexport type BackgroundKnob = 'opacity' | 'scrim' | 'panelOpacity' | 'blur' | 'wallpaperBlur' | 'fit'\n\n/**\n * Update one effect knob on the live backdrop without re-running the full\n * apply — the hot path for slider drags (see BackgroundPainter.setKnob).\n * @param key - the knob to update.\n * @param value - its new value.\n */\nexport function paintBackgroundKnob(key: BackgroundKnob, value: number | BackgroundSettings['fit']): void {\n backgroundPainter.setKnob(key, value)\n}\n\n/** Preview variables the settings-surface preview card consumes. */\nconst PREVIEW_VARS = [\n '--bg-opacity', '--bg-scrim', '--bg-object-fit',\n '--bg-wallpaper-blur', '--bg-wallpaper-scale',\n '--bg-glass-blur', '--bg-glass-saturate', '--bg-glass-brightness',\n '--bg-preview-glass',\n] as const\n\n/**\n * Push the effect knobs onto one element (the settings-surface preview card)\n * so dragging a slider updates the card without touching the live backdrop.\n * The card's CSS reads the same variable names as the painter writes on body.\n * @param el - the preview element.\n * @param settings - the draft settings to render (may be unsaved).\n */\nexport function paintPreviewSurface(el: HTMLElement, settings: BackgroundSettings): void {\n const s = el.style\n s.setProperty('--bg-object-fit', settings.fit)\n s.setProperty('--bg-opacity', String(settings.opacity))\n s.setProperty('--bg-scrim', String(settings.scrim))\n s.setProperty('--bg-wallpaper-blur', `${settings.wallpaperBlur}px`)\n s.setProperty('--bg-wallpaper-scale', (1 + settings.wallpaperBlur * 0.006).toFixed(4))\n // The glass bubble mirrors the live panel surface: below panelOpacity 1 its\n // alpha follows the same theme-aware curve as --dsw-specific-bubble (so the\n // panel-opacity slider is visible in the preview); at 1 the official opaque\n // surface returns — the bubble turns opaque and the blur switches off, just\n // like applySurfaceGlass does on the real backdrop.\n const inDark = document.body.dataset.dsDarkTheme !== undefined\n if (settings.panelOpacity >= 1) {\n s.setProperty('--bg-glass-blur', '0px')\n s.setProperty('--bg-glass-brightness', '1')\n s.setProperty('--bg-preview-glass', 'var(--dsw-alias-bg-layer-1)')\n return\n }\n s.setProperty('--bg-glass-blur', `${settings.blur}px`)\n s.setProperty('--bg-glass-saturate', String(glassSaturate(settings.blur)))\n // The preview filter mirrors the live exposure calibration (slightly dimmed\n // in light, lifted in dark) so the card never reads brighter than the app.\n s.setProperty('--bg-glass-brightness', glassExposure(inDark).brightness)\n // The preview bubble mirrors the live --dsw-specific-bubble token.\n s.setProperty('--bg-preview-glass', glassSurfaceAlphas(settings.panelOpacity, inDark).bubble)\n}\n\n/** Remove every preview variable from an element (restores CSS defaults). */\nexport function clearPreviewSurface(el: HTMLElement): void {\n for (const name of PREVIEW_VARS) el.style.removeProperty(name)\n}\n","\n/**\n * Frosted-glass registry — the public integration surface other plugins use\n * to join this plugin's frosted-glass system.\n *\n * Third-party panels (diff viewers, file peeks, tool cards...) often fill\n * with an official --dsw-* token that the painter already turns translucent\n * under a wallpaper, but nothing adds the shared blur/sheen chain to THEM,\n * so they read as bare transparency instead of frosted glass (or stay fully\n * opaque when they carry their own literal fills). This registry lets their\n * authors register CSS selectors; every registered surface receives the\n * exact unified recipe the whitelisted official surfaces use:\n *\n * - mode 'token' (default) — adds ONLY the wet-glass sheen gradient and the\n * shared backdrop-filter chain. Pick it when the panel already paints with\n * a token the painter overrides (--dsw-specific-input-major,\n * --dsw-specific-bubble, --dsw-alias-markdown-code-block,\n * --dsw-alias-markdown-code-block-banner, --dsw-alias-markdown-inline-code,\n * --dsw-specific-tip): its fill then follows the panel-opacity slider\n * automatically. Same treatment as the composer dock family.\n * - mode 'fill' — also takes over the fill itself\n * (background-color: var(--dsh-specific-input-major)). Pick it when the\n * panel carries its own opaque/literal background today. Same explicit\n * recipe as the chrome buttons and the lineage popover.\n *\n * Rules are generated under the body[data-dsh-bg-glass] gate, so glass\n * toggling (wallpaper disabled, source cleared, or the user maxing the\n * panel-opacity slider) restores the registered surfaces' own paints with\n * ZERO extra coordination — the gate is the same one the built-in whitelist\n * keys on.\n *\n * Transport: because plugin client bundles load in no guaranteed order, the\n * api is published on window.__DSH_BACKGROUND_GLASS__ and announced with the\n * 'dsh-background-glass:ready' CustomEvent (detail = the api). Consumers\n * either poll the global or await the event; both are covered by the\n * copy-paste helper in docs/GLASS_API.md. The whole bridge is torn down when\n * this plugin's client fiber disposes.\n */\n\nimport { GLASS_ATTR } from './background-css.ts'\n\n/** Semantic version of this bridge contract (bumped on breaking changes). */\nexport const GLASS_BRIDGE_VERSION = 1 as const\n\n/** Property name on window where the api is published. */\nexport const GLASS_BRIDGE_GLOBAL = '__DSH_BACKGROUND_GLASS__'\n\n/** DOM event announced once the api becomes available (detail = the api). */\nexport const GLASS_READY_EVENT = 'dsh-background-glass:ready'\n\n/** Style-tag dedup key of the registry stylesheet (data-plugin-css). */\nconst REGISTRY_CSS_TAG = 'deepseek-harness-background/glass-registry'\n\n/** Plugin identity stamped onto the registry <style> element. */\nconst PLUGIN_ID = 'deepseek-harness-background'\n\n/**\n * Fill strategy for a registered surface.\n * - 'token': the surface paints with an overridden --dsw-* token; only the\n * sheen + blur chain is added (its fill follows the sliders).\n * - 'fill': the surface keeps its own opaque paint today; the registry takes\n * the fill over too (--dsw-specific-input-major, the composer's white).\n */\nexport type GlassSurfaceMode = 'token' | 'fill'\n\n/** Registration payload passed to BackgroundGlassApi.register. */\nexport interface GlassSurfaceSpec {\n /**\n * Caller identity, conventionally the consumer package name (e.g.\n * 'dsh-diff-stat'). Used for diagnostics only — registrations from\n * different plugins never conflict.\n */\n plugin: string\n /** One selector or a list. Each must match a stable anchor you render. */\n selectors: string | readonly string[]\n /** Fill strategy; defaults to 'token'. */\n mode?: GlassSurfaceMode\n}\n\n/** The object published at window.__DSH_BACKGROUND_GLASS__. */\nexport interface BackgroundGlassApi {\n /** Contract version (currently GLASS_BRIDGE_VERSION). */\n readonly version: typeof GLASS_BRIDGE_VERSION\n /** Publisher identity — lets consumers assert WHO owns the global. */\n readonly bridgeId: 'deepseek-harness-background'\n /**\n * Whether the frosted glass is ON right now (wallpaper active AND\n * panelOpacity < 1). Registrations made while this is false simply wait\n * under the gate until the glass turns on.\n */\n isActive(): boolean\n /**\n * Register surfaces to receive the unified frosted-glass recipe while the\n * glass is on. Idempotent per (plugin, mode, selector): re-registering the\n * same triple replaces the earlier entry. Returns an unregister function —\n * idempotent, safe to call twice, and the right cleanup inside a cordis\n * effect/fiber dispose.\n */\n register(spec: GlassSurfaceSpec): () => void\n}\n\n/**\n * Characters whose presence would let a selector escape its rule context.\n * ',' is refused too: inside the generated rule body\n * (`body[gate] <selector> { … }`) a comma lets one registration broaden the\n * rule to ARBITRARY extra subjects (`.x, html body`) — an unintended\n * widening of the glass sheet beyond the registered surface. Multiple\n * surfaces stay supported through the selectors ARRAY.\n */\nconst FORBIDDEN_SELECTOR_CHARS = ['{', '}', ';', '@', '<', '>', ','] as const\n\n/** Hard cap on one selector's length (diagnostics-friendly, abuse-proof). */\nconst MAX_SELECTOR_LENGTH = 500\n\n/** Hard cap on selectors per registration call. */\nconst MAX_SELECTORS_PER_SPEC = 64\n\n/**\n * Validate one raw selector string. Structural checks only: anything that\n * could escape the generated rule body ({}, ;, @, <, >) is rejected, and a\n * backslash is refused too (escape sequences have no business in an anchor\n * selector); ordinary CSS mistakes (a typo'd pseudo-class) are left to the\n * browser, which drops just that one rule — a safe failure mode.\n * @returns an error message, or null when the selector is acceptable.\n */\nfunction selectorProblem(selector: unknown): string | null {\n if (typeof selector !== 'string') return 'must be a string'\n const trimmed = selector.trim()\n if (trimmed === '') return 'must not be empty'\n if (trimmed.length > MAX_SELECTOR_LENGTH) {\n return 'exceeds the ' + MAX_SELECTOR_LENGTH + '-character limit (' + trimmed.length + ')'\n }\n for (const ch of FORBIDDEN_SELECTOR_CHARS) {\n if (trimmed.includes(ch)) {\n return 'contains a forbidden character (' + JSON.stringify(ch) + ')'\n }\n }\n if (trimmed.includes(String.fromCharCode(92))) {\n return 'contains a backslash (escape sequences are not accepted)'\n }\n return null\n}\n\n/** Normalize a spec into validated (plugin, mode, selector) triples.\n * @returns the accepted triples plus one warning line per rejected selector. */\nfunction normalizeSpec(spec: GlassSurfaceSpec): {\n rows: { plugin: string; mode: GlassSurfaceMode; selector: string }[]\n warnings: string[]\n} {\n const plugin = typeof spec?.plugin === 'string' && spec.plugin.trim() !== '' ? spec.plugin.trim() : '(anonymous)'\n const mode: GlassSurfaceMode = spec?.mode === 'fill' ? 'fill' : 'token'\n const raw: unknown[] = Array.isArray(spec?.selectors) ? [...spec.selectors] : [spec?.selectors]\n const rows: { plugin: string; mode: GlassSurfaceMode; selector: string }[] = []\n const warnings: string[] = []\n if (raw.length > MAX_SELECTORS_PER_SPEC) {\n warnings.push('[glass-registry] ' + plugin + ': ' + raw.length + ' selectors exceeds the '\n + MAX_SELECTORS_PER_SPEC + '-selector cap; extra entries dropped.')\n }\n for (const item of raw.slice(0, MAX_SELECTORS_PER_SPEC)) {\n // selectorProblem already covers non-strings; the typeof check below is\n // what narrows `item` to string on the accepted path.\n const problem = selectorProblem(item)\n if (typeof item !== 'string' || problem !== null) {\n warnings.push('[glass-registry] ' + plugin + ': rejected selector '\n + JSON.stringify(item) + ' — ' + (problem ?? 'must be a string') + '.')\n continue\n }\n rows.push({ plugin, mode, selector: item.trim() })\n }\n return { rows, warnings }\n}\n\n/** The shared sheen gradient declaration (identical to the built-in sheet). */\nconst SHEEN_DECL = 'background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));'\n\n/** The shared exposure chain (blur follows the glass-blur slider). */\nconst FILTER_DECL = '-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01); backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);'\n\n/**\n * Render one registered surface as a gated CSS rule — the same recipes the\n * built-in sheets emit for their explicit-fill families.\n */\nfunction ruleFor(row: { mode: GlassSurfaceMode; selector: string }): string {\n const gate = 'body[' + GLASS_ATTR + ']'\n const fill = row.mode === 'fill'\n ? 'background-color: var(--dsw-specific-input-major); ' + SHEEN_DECL + ' ' + FILTER_DECL\n : SHEEN_DECL + ' ' + FILTER_DECL\n return gate + ' ' + row.selector + ' { ' + fill + ' }'\n}\n\n/**\n * Owns the registered surfaces and the registry stylesheet.\n */\nexport class GlassRegistry {\n private readonly entries = new Map<string, { row: { plugin: string; mode: GlassSurfaceMode; selector: string }; owner: object }>()\n private styleEl: HTMLStyleElement | null = null\n private disposed = false\n\n /**\n * Register one spec. See BackgroundGlassApi.register.\n * @param spec - the surfaces to glass.\n * @returns the unregister function (idempotent).\n */\n register(spec: GlassSurfaceSpec): () => void {\n // A disposed registry — or one superseded by a reinstall — must stay\n // INERT: accepting entries would orphan them outside the live fiber's\n // teardown (the stale api closure can outlive this plugin's disposal).\n if (this.disposed || (publishedRegistry !== undefined && publishedRegistry !== this)) {\n console.warn('[glass-registry] registration ignored: this bridge is no longer the live publication')\n return () => {}\n }\n let normalized: { rows: { plugin: string; mode: GlassSurfaceMode; selector: string }[]; warnings: string[] }\n try {\n normalized = normalizeSpec(spec)\n } catch (error) {\n // Hostile input (e.g. an array with a throwing getter) degrades to\n // warn-and-skip exactly like any other malformed spec — never throws.\n console.warn('[glass-registry] spec rejected: ' + (error instanceof Error ? error.message : String(error)))\n normalized = { rows: [], warnings: [] }\n }\n const { rows, warnings } = normalized\n for (const message of warnings) console.warn(message)\n const owner = {}\n const keys: string[] = []\n for (const row of rows) {\n // JSON encoding makes the three-part key unambiguous BY CONSTRUCTION:\n // control characters smuggled into any part cannot forge another triple.\n const key = JSON.stringify([row.plugin, row.mode, row.selector])\n this.entries.set(key, { row, owner })\n keys.push(key)\n }\n this.sync()\n let unregistered = false\n return () => {\n if (unregistered) return\n unregistered = true\n // The ownership check keeps a STALE handle from unregistering a newer\n // registration that replaced the same key (idempotent re-register).\n for (const key of keys) {\n const entry = this.entries.get(key)\n if (entry !== undefined && entry.owner === owner) this.entries.delete(key)\n }\n this.sync()\n }\n }\n\n /** Rebuild the registry stylesheet from the live entries. */\n private sync(): void {\n if (this.entries.size === 0) {\n this.styleEl?.remove()\n this.styleEl = null\n return\n }\n // Distinct rule texts only: two plugins may legitimately register the\n // same selector; cascade resolves duplicates anyway, but emitting one\n // identical rule keeps the sheet honest.\n const seen = new Set<string>()\n const rules: string[] = []\n for (const { row } of this.entries.values()) {\n const rule = ruleFor(row)\n if (!seen.has(rule)) {\n seen.add(rule)\n rules.push(rule)\n }\n }\n if (this.styleEl === null) {\n this.styleEl = document.createElement('style')\n this.styleEl.dataset.plugin = PLUGIN_ID\n this.styleEl.dataset.pluginCss = REGISTRY_CSS_TAG\n document.head.appendChild(this.styleEl)\n }\n this.styleEl.textContent = rules.join(String.fromCharCode(10))\n }\n\n /** Tear everything down: entries, stylesheet, and the window publication. */\n dispose(): void {\n this.disposed = true\n this.entries.clear()\n this.sync()\n const w = window as unknown as Record<string, unknown>\n if (w[GLASS_BRIDGE_GLOBAL] === this.api) delete w[GLASS_BRIDGE_GLOBAL]\n if (publishedRegistry === this) publishedRegistry = undefined\n }\n\n /** The published api face over this registry. */\n readonly api: BackgroundGlassApi = {\n version: GLASS_BRIDGE_VERSION,\n bridgeId: 'deepseek-harness-background',\n isActive: (): boolean =>\n typeof document !== 'undefined' && document.body !== null && document.body.hasAttribute(GLASS_ATTR),\n register: (spec: GlassSurfaceSpec): (() => void) => this.register(spec),\n }\n}\n\n/** The registry whose api is CURRENTLY published on window. Module-level on\n * purpose: the published api face deliberately carries no dispose member\n * (internal teardown stays unreachable for consumers), so reinstall-time\n * cleanup needs a handle the window key cannot provide. */\nlet publishedRegistry: GlassRegistry | undefined\n\n/**\n * Publish the glass registry bridge onto window and announce it. Safe to\n * call repeatedly: a previous publication's REGISTRY is disposed first, so\n * exactly one bridge — and one stylesheet — is ever live.\n * @returns the bridge handle — call .dispose() when this plugin's client\n * fiber tears down.\n */\nexport function installGlassBridge(): { api: BackgroundGlassApi; dispose(): void } {\n // A stale publication (hot reload of this very plugin) must not linger:\n // disposing its REGISTRY removes its stylesheet and clears its window\n // key. Dispose is idempotent, so a stale reference left behind by an\n // earlier fiber dispose is harmless to dispose again here.\n publishedRegistry?.dispose()\n // Belt-and-suspenders for the path where the module factory itself was\n // re-evaluated without a prior fiber dispose (closure state reset, so\n // publishedRegistry above is already undefined): any stylesheet carrying\n // our dedup key can only belong to a stale bridge — drop it.\n document.querySelector('style[data-plugin-css=\"' + REGISTRY_CSS_TAG + '\"]')?.remove()\n const registry = new GlassRegistry()\n publishedRegistry = registry\n ;(window as unknown as Record<string, unknown>)[GLASS_BRIDGE_GLOBAL] = registry.api\n // Announce right after publishing: consumers that already polled the\n // global see it immediately; listeners get the event synchronously now.\n window.dispatchEvent(new CustomEvent(GLASS_READY_EVENT, { detail: registry.api }))\n return { api: registry.api, dispose: () => registry.dispose() }\n}\n","/** Locale dictionaries for the background plugin. */\n\nimport type { BackgroundCardLocaleKey } from './types.ts'\n\n/** Dictionary of the namespace this plugin owns. */\nexport type BackgroundCardDict = Record<BackgroundCardLocaleKey, string>\n\n/** Chinese copy. */\nexport const zh: BackgroundCardDict = {\n 'background.title': '自定义背景',\n 'background.description': '为整个应用界面设置背景图片,可调不透明度、遮罩、面板透明与毛玻璃模糊',\n 'background.upload': '上传图片',\n 'background.uploading': '上传中…',\n 'background.uploadMediaTypes': '支持 JPG / PNG / WebP / GIF',\n 'background.urlPlaceholder': '或粘贴图片 http(s) 链接后回车',\n 'background.invalidUrl': '链接无效,请粘贴 http(s) 开头的图片链接',\n 'background.applyUrl': '应用链接',\n 'background.enabled': '背景已启用',\n 'background.source': '图片',\n 'background.effects': '效果',\n 'background.noPreview': '上传图片或粘贴链接后,此处实时预览效果',\n 'background.previewGlass': '毛玻璃',\n 'background.opacity': '不透明度',\n 'background.scrim': '遮罩',\n 'background.panelOpacity': '面板不透明度',\n 'background.blur': '毛玻璃模糊',\n 'background.wallpaperBlur': '壁纸模糊',\n 'background.fit': '填充方式',\n 'background.cover': '铺满',\n 'background.contain': '完整',\n 'background.clear': '清除背景',\n 'background.uploadFailed': '上传失败,请重试',\n 'background.saveFailed': '保存失败,请重试',\n 'background.timelineEnhance': '会话时间线增强',\n 'background.timelineEnhanceNarrowHint': '为官方会话时间线的已加载轮次补上平滑跳转',\n 'background.migratedNotice': '已从旧版 dsh 的设置中恢复你的背景设置',\n 'background.migratedDismiss': '知道了',\n}\n\n/** English copy. */\nexport const en: BackgroundCardDict = {\n 'background.title': 'Custom Background',\n 'background.description': 'Set a background image behind the whole app surface with adjustable opacity, scrim, panel transparency and glass blur',\n 'background.upload': 'Upload image',\n 'background.uploading': 'Uploading…',\n 'background.uploadMediaTypes': 'JPG / PNG / WebP / GIF',\n 'background.urlPlaceholder': 'Or paste an image http(s) link and press Enter',\n 'background.invalidUrl': 'Invalid link — paste an image link starting with http(s)',\n 'background.applyUrl': 'Apply URL',\n 'background.enabled': 'Background on',\n 'background.source': 'Image',\n 'background.effects': 'Effects',\n 'background.noPreview': 'Upload an image or paste a link to preview live here',\n 'background.previewGlass': 'Glass',\n 'background.opacity': 'Opacity',\n 'background.scrim': 'Scrim',\n 'background.panelOpacity': 'Panel opacity',\n 'background.blur': 'Glass blur',\n 'background.wallpaperBlur': 'Wallpaper blur',\n 'background.fit': 'Fit',\n 'background.cover': 'Cover',\n 'background.contain': 'Contain',\n 'background.clear': 'Clear background',\n 'background.uploadFailed': 'Upload failed, try again',\n 'background.saveFailed': 'Save failed, try again',\n 'background.timelineEnhance': 'Conversation timeline enhancement',\n 'background.timelineEnhanceNarrowHint': 'Adds smooth jumps for the rail\\'s loaded turns',\n 'background.migratedNotice': 'Restored your background settings from the previous dsh version',\n 'background.migratedDismiss': 'Got it',\n}\n","function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","/**\n * Background settings transport — reads and writes the `ui-background`\n * section, and uploads local images, through the plugin's own same-origin\n * HTTP routes (see src/routes.ts). A custom route family keeps the section\n * usable even though the api-proxy settings allowlist does not expose\n * third-party namespaces over the settings RPC. One module-level client\n * serves the painter and the settings row.\n */\n\nimport type { BackgroundSettings } from '../settings.ts'\nimport { BACKGROUND_API_PREFIX } from '../settings.ts'\n\n/** One snapshot of the section as last seen by the transport. */\nexport interface SettingsSnapshot {\n status: 'loading' | 'ready' | 'error'\n value?: BackgroundSettings\n /**\n * Set by the host on the one read that adopted a pre-0.1.7 section (dsh\n * 0.1.7 renamed `settings.yaml` away and skipped the plugin's namespace, so\n * the host pulls it back into the entry-keyed document on first read). The\n * row turns it into a one-time notice; a later boot no longer reports it.\n */\n migrated?: { from: string }\n}\n\ntype Listener = () => void\n\n/** Outcome of one save attempt: committed, displaced by a newer save (the\n * caller must not adopt its own section back into the draft), or rejected. */\nexport type SaveResult = 'ok' | 'superseded' | 'failed'\n\n/**\n * Observable settings client (getSnapshot/subscribe — the uSES currency).\n * Methods are arrow properties so React's useSyncExternalStore can call them\n * without an owning `this`.\n */\nexport class SettingsClient {\n private snapshot: SettingsSnapshot = { status: 'loading' }\n private readonly listeners = new Set<Listener>()\n /** Monotonic request id; the latest save wins even when POSTs interleave. */\n private saveSeq = 0\n /** Pending save promise (used by the row's flush-on-unmount). */\n private pending: Promise<SaveResult> | undefined\n\n /** @returns the current sync snapshot (stable reference until the next change). */\n getSnapshot = (): SettingsSnapshot => this.snapshot\n\n /** Observe snapshot replacements. @returns the disposer removing this listener. */\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener()\n }\n\n /** Await any in-flight save (used before teardown so edits are not dropped). */\n async flush(): Promise<void> {\n await this.pending\n }\n\n /** Fetch the section from the host. */\n async load(): Promise<void> {\n try {\n const response = await fetch(`${BACKGROUND_API_PREFIX}/settings`)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const body = await response.json() as {\n ok: boolean\n value?: BackgroundSettings\n migrated?: { from: string }\n }\n if (!body.ok || body.value === undefined) throw new Error('unexpected payload')\n this.snapshot = {\n status: 'ready',\n value: body.value,\n ...(body.migrated === undefined ? {} : { migrated: body.migrated }),\n }\n } catch {\n this.snapshot = { status: 'error' }\n }\n this.notify()\n }\n\n /**\n * Persist the section through the host route. Concurrent calls are\n * serialized by a request sequence: an older save that resolves after a\n * newer one answers 'superseded' instead of 'ok', so the caller can never\n * mistake a stale write for success and adopt its outdated section.\n * @param section - the complete next section.\n * @returns whether the write committed, was displaced by a newer save, or failed.\n */\n async save(section: BackgroundSettings): Promise<SaveResult> {\n const seq = ++this.saveSeq\n const run = async (): Promise<SaveResult> => {\n try {\n const response = await fetch(`${BACKGROUND_API_PREFIX}/settings`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(section),\n })\n const body = await response.json() as { ok: boolean; value?: BackgroundSettings }\n if (!response.ok || !body.ok || body.value === undefined) return 'failed'\n // Discard the response of a superseded save — it reflects an older\n // document that must not overwrite the latest one.\n if (seq !== this.saveSeq) return 'superseded'\n // A save is not a reload: carry the migration notice the load adopted\n // so it survives the user's first interaction instead of blinking out.\n this.snapshot = {\n status: 'ready',\n value: body.value,\n ...(this.snapshot.migrated === undefined ? {} : { migrated: this.snapshot.migrated }),\n }\n this.notify()\n return 'ok'\n } catch {\n return 'failed'\n }\n }\n const promise = run()\n this.pending = promise\n // Release the handle once settled so flush() never awaits a stale save\n // from a long-finished gesture (a later flush then resolves instantly).\n void promise.then(() => {\n if (this.pending === promise) this.pending = undefined\n })\n return promise\n }\n\n /**\n * Upload a local image file; on success stores its id into the snapshot and\n * returns the resolved background url (or null on failure).\n * @param file - the chosen image file.\n * @returns the resolved image url, or null.\n */\n async upload(file: File): Promise<string | null> {\n try {\n const response = await fetch(`${BACKGROUND_API_PREFIX}/upload`, {\n method: 'POST',\n headers: { 'content-type': file.type },\n body: file,\n })\n const body = await response.json() as { ok: boolean; id?: string; url?: string }\n if (!response.ok || !body.ok || body.id === undefined || body.url === undefined) return null\n return body.url ?? null\n } catch {\n return null\n }\n }\n}\n\n/** Shared client for the painter and the settings row. */\nexport const settingsClient = new SettingsClient()\n","/**\n * Background settings row — the editable form rendered inside the General\n * settings section (`settings.general.item` slot), in the same visual area as\n * the official Appearance row. Layout mirrors the reference wallpaper engine\n * picker (preview surface + grouped sections), styled with the harness alias\n * tokens so it reads as part of the settings chrome.\n *\n * Interaction model (fixes the previous jank):\n * - Sliders STEP in 5% (or 2px/5px) increments, so dragging snaps between\n * discrete values instead of firing dozens of sub-percent updates.\n * - While dragging, only the CSS variables change: the slider holds its own\n * state (no parent re-render) and the preview card repaints in isolation.\n * React's onChange maps to the native `input` event and would fire on every\n * step, so a commit is deferred to release (pointerup / keyup / blur) and\n * debounced — one POST per gesture, never per tick.\n * - A `lastSavedRef` distinguishes our own saves from external changes, so\n * the snapshot sync can never clobber a draft the user is still editing.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ChangeEvent } from 'react'\nimport { useSyncExternalStore } from 'react'\nimport type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport clsx from 'clsx'\nimport {\n BACKGROUND_API_PREFIX, BLUR_MAX, BLUR_MIN, DEFAULT_BLUR, DEFAULT_FIT,\n DEFAULT_OPACITY, DEFAULT_PANEL_OPACITY, DEFAULT_SCRIM, DEFAULT_TIMELINE,\n DEFAULT_WALLPAPER_BLUR, FIT_MODES, OPACITY_MAX, OPACITY_MIN, PANEL_OPACITY_MAX,\n PANEL_OPACITY_MIN, SCRIM_MAX, SCRIM_MIN, WALLPAPER_BLUR_MAX, type BackgroundFit,\n type BackgroundSettings,\n} from '../settings.ts'\nimport { clearPreviewSurface, paintBackground, paintBackgroundKnob, paintPreviewSurface } from './backdrop.ts'\nimport { settingsClient, type SettingsSnapshot } from './settings-client.ts'\nimport css from './SettingsRow.module.css'\n\n/** Full component props: runtime share + locale seat. */\nexport type BackgroundRowProps =\n PropsRuntime<'settings.general.item'> & PropsLocale<'ui-background'>\n\n/** Defaults mirror the host schema (also the pre-load draft fallback). */\nconst DEFAULTS: BackgroundSettings = {\n enabled: false,\n uploadId: '',\n url: '',\n opacity: DEFAULT_OPACITY,\n scrim: DEFAULT_SCRIM,\n panelOpacity: DEFAULT_PANEL_OPACITY,\n blur: DEFAULT_BLUR,\n wallpaperBlur: DEFAULT_WALLPAPER_BLUR,\n fit: DEFAULT_FIT,\n timeline: DEFAULT_TIMELINE,\n}\n\n/** The numeric field keys a slider edits. */\ntype NumField = 'opacity' | 'scrim' | 'panelOpacity' | 'blur' | 'wallpaperBlur'\n\n/** Per-field slider geometry: 5% steps for ratios, fine px steps for radii so\n * the two blur sliders feel smooth near small values. Bounds come from the\n * shared settings constants so the schema boundary and the UI cannot drift. */\nconst SLIDER_SPEC: Record<NumField, { min: number; max: number; step: number; unit: 'percent' | 'px' }> = {\n opacity: { min: OPACITY_MIN, max: OPACITY_MAX, step: 0.05, unit: 'percent' },\n scrim: { min: SCRIM_MIN, max: SCRIM_MAX, step: 0.05, unit: 'percent' },\n panelOpacity: { min: PANEL_OPACITY_MIN, max: PANEL_OPACITY_MAX, step: 0.05, unit: 'percent' },\n blur: { min: BLUR_MIN, max: BLUR_MAX, step: 1, unit: 'px' },\n wallpaperBlur: { min: BLUR_MIN, max: WALLPAPER_BLUR_MAX, step: 2, unit: 'px' },\n}\n\n/** Derive the bare upload id from a resolve url like /api/bg-wallpaper/image/<id>. */\nfunction stripId(url: string): string {\n const prefix = `${BACKGROUND_API_PREFIX}/image/`\n return url.startsWith(prefix) ? url.slice(prefix.length) : ''\n}\n\n/** Read the settings store (uSES-friendly). */\nfunction readSnapshot(): SettingsSnapshot {\n return settingsClient.getSnapshot()\n}\n\n/** Field-wise equality for distinguishing our own saves from external changes. */\nfunction sameSettings(a: BackgroundSettings, b: BackgroundSettings): boolean {\n return a.enabled === b.enabled && a.uploadId === b.uploadId && a.url === b.url\n && a.opacity === b.opacity && a.scrim === b.scrim && a.panelOpacity === b.panelOpacity\n && a.blur === b.blur && a.wallpaperBlur === b.wallpaperBlur && a.fit === b.fit\n && a.timeline === b.timeline\n}\n\n/**\n * A stepped range control. While dragging it re-renders only itself: the value\n * is local state, `onLive(name, value)` repaints (CSS variables, no React\n * state), and `onCommit()` fires once on release through a debounced save.\n */\nfunction ControlSlider(props: {\n name: NumField\n label: string\n value: number\n onLive: (name: NumField, value: number) => void\n onCommit: () => void\n}) {\n const { name, label, value, onLive, onCommit } = props\n const spec = SLIDER_SPEC[name]\n const [local, setLocal] = useState(value)\n const localRef = useRef(value)\n const dragging = useRef(false)\n const onLiveRef = useRef(onLive)\n const onCommitRef = useRef(onCommit)\n onLiveRef.current = onLive\n onCommitRef.current = onCommit\n\n // Adopt an external value (load / save) when not mid-drag.\n useEffect(() => {\n if (!dragging.current && localRef.current !== value) {\n localRef.current = value\n setLocal(value)\n }\n }, [value])\n\n const handleLive = (e: ChangeEvent<HTMLInputElement>): void => {\n dragging.current = true\n const v = Number(e.currentTarget.value)\n localRef.current = v\n setLocal(v)\n syncTrackFill(e.currentTarget)\n onLiveRef.current(name, v)\n }\n\n const handleCommit = (): void => {\n if (!dragging.current) return\n dragging.current = false\n onCommitRef.current()\n }\n\n const text = spec.unit === 'percent'\n ? `${Math.round(local * 100)}%`\n : `${local}px`\n\n return (\n <div className={css.sliderRow}>\n <span className={css.sliderLabel}>{label}</span>\n <input\n type=\"range\"\n className={css.slider}\n ref={(el) => {\n // Sync the \"filled\" track fraction on mount and on every external\n // value adoption (the webkit track reads --bg-fill; moz draws its\n // own ::-moz-range-progress).\n if (el) syncTrackFill(el)\n }}\n min={spec.min} max={spec.max} step={spec.step}\n value={String(local)}\n aria-label={label}\n onInput={handleLive}\n onPointerUp={handleCommit}\n onKeyUp={(e) => {\n if (e.key.startsWith('Arrow') || e.key === 'Home' || e.key === 'End'\n || e.key === 'PageUp' || e.key === 'PageDown') handleCommit()\n }}\n onBlur={handleCommit}\n />\n <span className={css.sliderValue}>{text}</span>\n </div>\n )\n}\n\n/** Write the filled-track fraction (%) of a range input into its --bg-fill\n * variable (webkit has no range-progress pseudo-element; moz draws its own). */\nfunction syncTrackFill(el: HTMLInputElement): void {\n const min = Number(el.min)\n const max = Number(el.max)\n const value = Number(el.value)\n const pct = max === min ? 0 : ((value - min) / (max - min)) * 100\n el.style.setProperty('--bg-fill', `${pct}%`)\n}\n\n/**\n * The background settings row. A draft mirrors the persisted section; dragging\n * repaints the preview card and the live backdrop through CSS variables, and a\n * release commits once through the plugin's own host route.\n */\nexport function BackgroundSettingsRow({ t }: BackgroundRowProps) {\n const snapshot = useSyncExternalStore(settingsClient.subscribe, readSnapshot)\n // Supported dsh baseline >= 0.1.2-rc.1: the official frame-style rail is\n // always present, so the row copy is the fixed \"enhancement\" form — no\n // session scope needed here to detect anything.\n const timelineLabelKey = 'background.timelineEnhance'\n const timelineHintKey = 'background.timelineEnhanceNarrowHint'\n const [draft, setDraft] = useState<BackgroundSettings>(DEFAULTS)\n const [uploading, setUploading] = useState(false)\n const [urlText, setUrlText] = useState('')\n const [error, setError] = useState('')\n // The host reports the adopted pre-0.1.7 section on the read that migrated\n // it; the notice is one-shot and the user closes it.\n const [noticeDismissed, setNoticeDismissed] = useState(false)\n const fileInput = useRef<HTMLInputElement>(null)\n const previewRef = useRef<HTMLDivElement>(null)\n // The latest draft, read by every handler (never a stale closure).\n const draftRef = useRef<BackgroundSettings>(DEFAULTS)\n // True while a drag/gesture is in flight: hold off snapshot sync.\n const editingRef = useRef(false)\n // The last section this row saved; external changes are snapshots that differ.\n const lastSavedRef = useRef<BackgroundSettings | null>(null)\n // True while an edit has not yet reached the host (drives flush-on-unmount).\n const dirtyRef = useRef(false)\n const commitTimer = useRef<number | undefined>(undefined)\n const editingTimer = useRef<number | undefined>(undefined)\n\n /** Repaint the live backdrop + preview card from one draft (no React state). */\n const paintLive = useCallback((next: BackgroundSettings): void => {\n paintBackground(next)\n if (previewRef.current) paintPreviewSurface(previewRef.current, next)\n }, [])\n\n /** Mark the row as being edited; clears after a quiet 600ms window. */\n const markEditing = useCallback((): void => {\n editingRef.current = true\n clearTimeout(editingTimer.current)\n editingTimer.current = window.setTimeout(() => { editingRef.current = false }, 600)\n }, [])\n\n /** Slider live handler (stable identity for the memoized sliders). */\n const onLive = useCallback((key: NumField, value: number): void => {\n const next = { ...draftRef.current, [key]: value }\n draftRef.current = next\n markEditing()\n // Hot path: every knob repaints through the painter's single-variable\n // writer (no DOM layer churn); panelOpacity goes through the knob path\n // too — it repaints only the glass surface tokens instead of re-running\n // the full apply.\n paintBackgroundKnob(key, value)\n if (previewRef.current) paintPreviewSurface(previewRef.current, next)\n }, [markEditing])\n\n /** Persist one section through the host route and adopt it on success. */\n const saveNow = useCallback(async (next: BackgroundSettings): Promise<void> => {\n dirtyRef.current = true\n const result = await settingsClient.save(next)\n if (result === 'failed') {\n // A failed save has settled (no in-flight write left to flush), so the\n // dirty flag must drop with it — keeping it would make the unmount\n // flush wait for a long-settled promise and re-run saveNow on a draft\n // the host already rejected.\n dirtyRef.current = false\n setError(t('background.saveFailed'))\n return\n }\n // A superseded save lost to a newer in-flight one, which now owns BOTH\n // the snapshot adoption and the dirty flag (cleared when IT commits).\n // Clearing the flag here would strand that write, and adopting this\n // response would resurrect an outdated section — so keep it set.\n if (result === 'superseded') return\n dirtyRef.current = false\n setError('')\n lastSavedRef.current = next\n draftRef.current = next\n setDraft(next)\n if (!next.uploadId) setUrlText(next.url)\n }, [t])\n\n // The latest saveNow, readable from stable callbacks and the unmount\n // cleanup (never a stale closure).\n const saveNowRef = useRef(saveNow)\n saveNowRef.current = saveNow\n\n /** Debounced commit: one POST per drag gesture, from the latest draft. */\n const onCommit = useCallback((): void => {\n clearTimeout(commitTimer.current)\n commitTimer.current = window.setTimeout(() => {\n // Reset before saving: a fired timer must never look like an armed one,\n // or the unmount flush would re-save an already-committed gesture.\n commitTimer.current = undefined\n void saveNowRef.current(draftRef.current)\n }, 120)\n }, [])\n\n // Sync the draft from the persisted value when it changes externally, but\n // never overwrite a draft the user is mid-editing.\n useEffect(() => {\n if (snapshot.status !== 'ready' || snapshot.value === undefined) return\n const saved = lastSavedRef.current\n const external = saved === null || !sameSettings(saved, snapshot.value)\n if (external && !editingRef.current) {\n lastSavedRef.current = snapshot.value\n draftRef.current = snapshot.value\n setDraft(snapshot.value)\n setUrlText(snapshot.value.uploadId ? '' : snapshot.value.url)\n if (previewRef.current) paintPreviewSurface(previewRef.current, snapshot.value)\n }\n }, [snapshot])\n\n // Teardown: a pending debounced commit must not die with the row. If the\n // timer is still armed, fire the save NOW (the timer callback would never\n // run after unmount, and dirtyRef is only set inside saveNow — clearing\n // the timer alone would silently drop the last slider adjustment). Then\n // wait out any save already in flight. The effect runs once per mount:\n // saveNow is reached through its ref, so an identity change mid-session\n // cannot re-trigger this teardown, and editingRef is reset so a cleared\n // editing window can never block snapshot sync forever.\n useEffect(() => () => {\n clearTimeout(editingTimer.current)\n editingRef.current = false\n if (commitTimer.current !== undefined) {\n clearTimeout(commitTimer.current)\n commitTimer.current = undefined\n void saveNowRef.current(draftRef.current)\n }\n if (dirtyRef.current) void settingsClient.flush()\n }, [])\n\n const handleFile = async (file?: File): Promise<void> => {\n if (!file) return\n markEditing()\n setUploading(true)\n setError('')\n try {\n const url = await settingsClient.upload(file)\n if (url === null) {\n setError(t('background.uploadFailed'))\n return\n }\n const next: BackgroundSettings = { ...draftRef.current, uploadId: stripId(url), url: '', enabled: true }\n draftRef.current = next\n setDraft(next)\n setUrlText('')\n paintLive(next)\n await saveNow(next)\n } finally {\n setUploading(false)\n }\n }\n\n const handleUrlSubmit = (): void => {\n const url = urlText.trim()\n if (!/^https?:\\/\\//i.test(url)) {\n setError(t('background.invalidUrl'))\n return\n }\n markEditing()\n const next: BackgroundSettings = { ...draftRef.current, uploadId: '', url, enabled: true }\n draftRef.current = next\n setDraft(next)\n paintLive(next)\n void saveNow(next)\n }\n\n const handleClear = (): void => {\n const next: BackgroundSettings = { ...draftRef.current, uploadId: '', url: '', enabled: false }\n draftRef.current = next\n setDraft(next)\n setUrlText('')\n if (previewRef.current) clearPreviewSurface(previewRef.current)\n paintBackground(next)\n void saveNow(next)\n }\n\n const handleFit = (mode: BackgroundFit): void => {\n markEditing()\n const next: BackgroundSettings = { ...draftRef.current, fit: mode }\n draftRef.current = next\n setDraft(next)\n paintLive(next)\n void saveNow(next)\n }\n\n const handleTimelineToggle = (): void => {\n markEditing()\n const next: BackgroundSettings = { ...draftRef.current, timeline: !draftRef.current.timeline }\n draftRef.current = next\n setDraft(next)\n // The rail reads the shared snapshot itself; no painter repaint needed.\n void saveNow(next)\n }\n\n const sourceUrl = draft.uploadId ? `${BACKGROUND_API_PREFIX}/image/${draft.uploadId}` : draft.url\n const hasImage = sourceUrl !== ''\n\n return (\n <div className={css.row}>\n <div className={css.header}>\n <div className={css.titleRow}>\n <div className={css.title}>{t('background.title')}</div>\n {hasImage && (\n <button type=\"button\" className={clsx(css.btn, css.btnDanger, css.clearBtn)} onClick={handleClear}>\n {t('background.clear')}\n </button>\n )}\n </div>\n <div className={css.desc}>{t('background.description')}</div>\n </div>\n\n {/* One-shot notice: the host adopted a section written by dsh <= 0.1.6. */}\n {snapshot.migrated !== undefined && !noticeDismissed && (\n <div className={css.notice} role=\"status\">\n <span className={css.noticeText}>{t('background.migratedNotice')}</span>\n <button type=\"button\" className={css.noticeBtn} onClick={() => setNoticeDismissed(true)}>\n {t('background.migratedDismiss')}\n </button>\n </div>\n )}\n\n {/* Live preview surface: image + theme-aware scrim + frosted glass bubble. */}\n <div className={css.preview} ref={previewRef}>\n {hasImage\n ? (\n <>\n <img className={css.previewImg} src={sourceUrl} alt=\"\" draggable={false} referrerPolicy=\"no-referrer\" />\n <div className={css.previewScrim} />\n <div className={css.previewGlass}>\n <span className={css.previewGlassText}>{t('background.previewGlass')}</span>\n </div>\n </>\n )\n : (\n <div className={css.previewEmpty}>{t('background.noPreview')}</div>\n )}\n </div>\n\n {/* Image source: upload or paste an http(s) link. */}\n <div className={css.section}>\n <div className={css.sectionLabel}>{t('background.source')}</div>\n <div className={css.sourceRow}>\n <input\n type=\"file\"\n ref={fileInput}\n hidden\n accept=\"image/jpeg,image/png,image/webp,image/gif\"\n onChange={(e) => {\n void handleFile(e.currentTarget.files?.[0])\n e.currentTarget.value = ''\n }}\n />\n <button\n type=\"button\"\n className={clsx(css.btn, css.btnPrimary)}\n disabled={uploading}\n onClick={() => fileInput.current?.click()}\n >\n {uploading ? t('background.uploading') : t('background.upload')}\n </button>\n <span className={css.hint}>{t('background.uploadMediaTypes')}</span>\n {draft.uploadId && <span className={css.enabledBadge}>✓ {t('background.enabled')}</span>}\n </div>\n <div className={css.urlRow}>\n <input\n className={css.urlInput}\n value={urlText}\n placeholder={t('background.urlPlaceholder')}\n onKeyDown={(e) => { if (e.key === 'Enter') handleUrlSubmit() }}\n onChange={(e) => setUrlText(e.currentTarget.value)}\n />\n <button\n type=\"button\"\n className={css.btn}\n disabled={!urlText.trim()}\n onClick={handleUrlSubmit}\n >\n {t('background.applyUrl')}\n </button>\n </div>\n </div>\n\n {/* Effects: stepped sliders + fit segmented control. */}\n <div className={css.section}>\n <div className={css.sectionLabel}>{t('background.effects')}</div>\n <ControlSlider name=\"opacity\" label={t('background.opacity')} value={draft.opacity} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"scrim\" label={t('background.scrim')} value={draft.scrim} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"panelOpacity\" label={t('background.panelOpacity')} value={draft.panelOpacity} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"blur\" label={t('background.blur')} value={draft.blur} onLive={onLive} onCommit={onCommit} />\n <ControlSlider name=\"wallpaperBlur\" label={t('background.wallpaperBlur')} value={draft.wallpaperBlur} onLive={onLive} onCommit={onCommit} />\n <div className={css.segRow}>\n <span className={css.segLabel}>{t('background.fit')}</span>\n <div className={css.segGroup} role=\"group\" aria-label={t('background.fit')}>\n {FIT_MODES.map((mode) => (\n <button\n key={mode}\n type=\"button\"\n className={clsx(css.segBtn, draft.fit === mode && css.segActive)}\n onClick={() => handleFit(mode)}\n >\n {t(mode === 'cover' ? 'background.cover' : 'background.contain')}\n </button>\n ))}\n </div>\n </div>\n <div className={css.segRow}>\n <span className={css.segLabel}>{t(timelineLabelKey)}</span>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={draft.timeline}\n aria-label={t(timelineLabelKey)}\n title={t(timelineHintKey)}\n className={css.switch}\n onClick={handleTimelineToggle}\n >\n <span className={css.switchKnob} />\n </button>\n <span className={css.hint}>{t(timelineHintKey)}</span>\n </div>\n </div>\n\n {error && <div className={css.error} role=\"alert\">{error}</div>}\n\n {hasImage && (\n <div className={css.clearRow}>\n <button type=\"button\" className={clsx(css.btn, css.btnDanger)} onClick={handleClear}>\n {t('background.clear')}\n </button>\n </div>\n )}\n </div>\n )\n}\n","/**\r\n * Pointer/keyboard resolution for the official turn rail — the enhancer\r\n * borrows the official rail and resolves a gesture to the same entry the\r\n * kernel would have picked.\r\n *\r\n * The official rail owns pointer input for its whole column: the marks carry\r\n * `pointer-events: none` and only paint, so a MOUSE gesture arrives at the\r\n * `<nav>` with coordinates and must be mapped back to an index by geometry.\r\n * A KEYBOARD gesture instead activates the focused mark button, which carries\r\n * no index — it is recovered from the button's position among its siblings.\r\n *\r\n * Geometry contract (dsh >= 0.1.2-alpha.3, unchanged on 0.1.2-rc.1): the rail\r\n * is the frame-style generation — fixed 10px pitch, marks scroll INSIDE the\r\n * frame, and the scroller offset is published inline as `--turn-scroll-top`.\r\n * The frame property is checked before mapping (never a version compare): a\r\n * rail that does not publish it is not the supported generation, and the\r\n * enhancer stands down and lets the kernel own the gesture.\r\n */\r\n\r\nimport type { OfficialNavigationItem, TurnRailLadderItem } from './types.ts'\r\n\r\n/** Resting gap between neighbouring marks. */\r\nexport const TICK_SPACING_PX = 10\r\n/** Rail padding above the first mark and below the last one, per end. */\r\nexport const RAIL_INSET_PX = 6\r\n\r\n/**\r\n * The rail's own inset, read off the custom property the official component\r\n * publishes (`--turn-rail-inset`). Falls back to the official constant.\r\n * @param rail - the rail element (or null).\r\n */\r\nexport function railInsetOf(rail: HTMLElement | null): number {\r\n if (rail === null) return RAIL_INSET_PX\r\n const px = Number.parseFloat(rail.style.getPropertyValue('--turn-rail-inset'))\r\n return Number.isFinite(px) && px >= 0 ? px : RAIL_INSET_PX\r\n}\r\n\r\n/**\r\n * Whether `rail` is the frame-style rail (dsh >= 0.1.2-alpha.3): marks sit\r\n * at a fixed pitch and scroll INSIDE the frame, whose scroller offset the\r\n * component publishes inline as `--turn-scroll-top` — every render, resting\r\n * at `0px` included. The compressing alpha.1/2 rail never publishes that\r\n * property, so its presence is the generation test. The legacy port paints\r\n * its own `--dsbt-*` properties and can never trip this.\r\n * @param rail - the rail element (or null).\r\n */\r\nexport function isFrameRail(rail: HTMLElement | null): boolean {\r\n return rail !== null && rail.style.getPropertyValue('--turn-scroll-top') !== ''\r\n}\r\n\r\n/**\r\n * Resolve the entry under a pointer Y on the frame-style rail — the official\r\n * alpha.3 geometry verbatim (`itemAtPointer` in that generation's\r\n * TurnNavigator.tsx): marks live in the scrolled ladder at a fixed 10px\r\n * pitch, so the click offset adds the scroller offset. Read off the published\r\n * `--turn-scroll-top` var rather than the scroller element itself — the same\r\n * metric the official preview positioning uses, so any one-frame render lag\r\n * affects both identically.\r\n * @param count - number of entries the rail shows.\r\n * @param rail - the rail element.\r\n * @param clientY - pointer viewport Y.\r\n */\r\nexport function frameIndexAtPointer(count: number, rail: HTMLElement, clientY: number): number {\r\n const rect = rail.getBoundingClientRect()\r\n const inset = railInsetOf(rail)\r\n const raw = Number.parseFloat(rail.style.getPropertyValue('--turn-scroll-top'))\r\n const scrollTop = Number.isFinite(raw) ? raw : 0\r\n const offset = clientY - rect.top + scrollTop - inset\r\n return Math.max(0, Math.min(count - 1, Math.round(offset / TICK_SPACING_PX)))\r\n}\r\n\r\n/**\r\n * Resolve the gesture that produced a rail click to an entry index.\r\n *\r\n * Two rail generations answer here, told apart by capability:\r\n *\r\n * - The virtualized rail (dsh 0.1.7+) tags every RENDERED mark with its own\r\n * ladder index (`data-index`), so the gesture resolves by data. This is the\r\n * robust path: the virtualizer mounts only a window of marks, and their DOM\r\n * order is not the ladder order.\r\n * - The frame-style rail tags nothing and paints marks with\r\n * `pointer-events: none`, so a MOUSE gesture arrives at the `<nav>` with\r\n * coordinates and resolves through the rail geometry over the published\r\n * `--turn-scroll-top`; a KEYBOARD gesture activates the focused mark button,\r\n * recovered by its position among its siblings.\r\n *\r\n * Returns -1 when neither applies (a keyboard target outside the rail, or a\r\n * rail of an unsupported generation, which the kernel's own handler then owns).\r\n *\r\n * Assumption: the mark ticks are the ONLY buttons under the rail — the\r\n * official rail renders marks exclusively and its preview tooltip carries\r\n * none. A future non-mark control added inside the rail would shift the\r\n * button index and must revisit this.\r\n *\r\n * @param rail - the rail element.\r\n * @param event - the click event.\r\n */\r\nexport function indexForEvent(rail: HTMLElement, event: MouseEvent): number {\r\n const target = event.target\r\n if (target instanceof Element) {\r\n const mark = target.closest('[data-index]')\r\n if (mark !== null && rail.contains(mark)) {\r\n const tagged = Number(mark.getAttribute('data-index'))\r\n if (Number.isSafeInteger(tagged) && tagged >= 0) return tagged\r\n }\r\n const button = target.closest('button')\r\n if (button !== null && rail.contains(button)) {\r\n const index = Array.from(rail.querySelectorAll('button')).indexOf(button)\r\n if (index >= 0) return index\r\n }\r\n }\r\n const count = rail.querySelectorAll('button').length\r\n if (count === 0) return -1\r\n // The frame-style geometry is the only supported legacy generation (dsh >=\r\n // 0.1.2-alpha.3, unchanged through 0.1.6). A rail that does not publish\r\n // `--turn-scroll-top` is not that rail: stand down (-1) so the kernel's own\r\n // handler owns the gesture untouched.\r\n return isFrameRail(rail) ? frameIndexAtPointer(count, rail, event.clientY) : -1\r\n}\r\n\r\n/**\r\n * Validate the official navigation array crossing the slot boundary. The\r\n * value is produced by the kernel and may be absent, partial, or from an\r\n * older build, so every field is checked before use.\r\n * @param value - the raw `useChat(s => s.navigation.items())` result.\r\n */\r\nexport function normalizeNavigationItems(value: unknown): OfficialNavigationItem[] {\r\n if (!Array.isArray(value)) return []\r\n const out: OfficialNavigationItem[] = []\r\n const seen = new Set<number>()\r\n for (const item of value) {\r\n if (item === null || typeof item !== 'object') continue\r\n const rec = item as { turn?: unknown; anchorKey?: unknown; prompt?: unknown; response?: unknown }\r\n if (typeof rec.turn !== 'number' || !Number.isFinite(rec.turn)) continue\r\n if (typeof rec.anchorKey !== 'string' || rec.anchorKey === '') continue\r\n if (seen.has(rec.turn)) continue\r\n seen.add(rec.turn)\r\n out.push({\r\n turn: rec.turn,\r\n anchorKey: rec.anchorKey,\r\n prompt: typeof rec.prompt === 'string' ? rec.prompt : '',\r\n response: typeof rec.response === 'string' ? rec.response : '',\r\n })\r\n }\r\n return out\r\n}\r\n\r\n/* ---- Full-ladder merge (dsh >= 0.1.2-alpha.3) ---------------------------- */\r\n\r\n/**\r\n * Validate one `turnOutline` wire value into its entries. The value is the\r\n * host projection's wire view (an array of `{ turn, seq, prompt, response }`\r\n * — see `dsh-session-turn-outline`) read through `useProjection`, so it may\r\n * be absent (pre-baseline, or an older kernel that never registers the key —\r\n * which reads `undefined`), partial, or damaged. `turn` and `seq` are the\r\n * load-bearing fields — a mark cannot exist or page without them — and a\r\n * damaged one drops the entry; the previews are decorative and degrade to ''.\r\n * @param value - the raw `useProjection('turnOutline')` value.\r\n */\r\nexport function normalizeOutlineItems(value: unknown): { turn: number; seq: number; prompt: string; response: string }[] {\r\n if (!Array.isArray(value)) return []\r\n const out: { turn: number; seq: number; prompt: string; response: string }[] = []\r\n for (const item of value) {\r\n if (item === null || typeof item !== 'object') continue\r\n const rec = item as { turn?: unknown; seq?: unknown; prompt?: unknown; response?: unknown }\r\n if (typeof rec.turn !== 'number' || !Number.isSafeInteger(rec.turn) || rec.turn < 0) continue\r\n if (typeof rec.seq !== 'number' || !Number.isSafeInteger(rec.seq) || rec.seq < 0) continue\r\n out.push({\r\n turn: rec.turn,\r\n seq: rec.seq,\r\n prompt: typeof rec.prompt === 'string' ? rec.prompt : '',\r\n response: typeof rec.response === 'string' ? rec.response : '',\r\n })\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Rebuild the rail's full mark ladder exactly as the official ChatView does\r\n * (a mirror of `mergeTurnRailItems` in dsh-client-ui-chat): outline entries\r\n * seed every known turn as unloaded, loaded items then override with their\r\n * anchor key and richer previews, ascending by turn. The enhancer maps a\r\n * click to a mark by INDEX, so its ladder must match the RENDERED marks —\r\n * which are exactly this merge on an alpha.3 kernel; without the outline\r\n * value the merge degrades to the loaded items (the alpha.1/2 ladder).\r\n * @param loaded - the validated loaded-window items.\r\n * @param outline - the raw turnOutline wire value.\r\n */\r\nexport function mergeRailItems(loaded: OfficialNavigationItem[], outline: unknown): TurnRailLadderItem[] {\r\n const byTurn = new Map<number, TurnRailLadderItem>()\r\n for (const entry of normalizeOutlineItems(outline)) {\r\n byTurn.set(entry.turn, { turn: entry.turn, prompt: entry.prompt, response: entry.response })\r\n }\r\n for (const item of loaded) {\r\n const preview = byTurn.get(item.turn)\r\n byTurn.set(item.turn, {\r\n turn: item.turn,\r\n prompt: item.prompt !== '' ? item.prompt : preview?.prompt ?? '',\r\n response: item.response !== '' ? item.response : preview?.response ?? '',\r\n anchorKey: item.anchorKey,\r\n })\r\n }\r\n if (byTurn.size === 0) return []\r\n return [...byTurn.values()].sort((left, right) => left.turn - right.turn)\r\n}\r\n\r\n","/**\r\n * Official-rail enhancer — behaviour-only improvements to the kernel's own\r\n * turn navigator (dsh >= 0.1.2-rc.1, `TurnNavigator` in dsh-client-ui-chat).\r\n *\r\n * This component renders NOTHING. It borrows the official rail exactly as the\r\n * kernel painted it and fixes its behaviour, scoped to what the running rail\r\n * generation still lacks — told apart by capability, never by version compare:\r\n *\r\n * Smooth jump. The official `navigateToTurn` is one assignment\r\n * (`el.scrollTop += flowTop(row, el) - 24`); a click across a long transcript\r\n * teleports. Clicks on LOADED marks are intercepted in the capture phase and\r\n * re-run through the shared glide instead. Both generations also render marks\r\n * for turns OUTSIDE the loaded window (fed by the whole-log `turnOutline`\r\n * projection); those jumps page history through the kernel's own loadThrough\r\n * machinery — marks without an anchor key are left to it, and the click index\r\n * is resolved against the same merged ladder the kernel renders. While one of\r\n * those kernel jumps is still paging (a mark pulses `aria-busy`), the plugin\r\n * stands down entirely: the kernel's loaded branch cancels its own pending\r\n * jump before landing, and an interception would bypass that cancellation.\r\n *\r\n * Two rail generations are supported, discovered by {@link findOfficialRail}:\r\n * the frame-style rail of dsh 0.1.2-rc.1 … 0.1.6 (inline frame metrics, marks\r\n * mapped by geometry) and the virtualized rail of dsh 0.1.7+ (no inline\r\n * metrics, marks tagged `data-index`, only a window of them mounted at once).\r\n *\r\n * State-ownership note (why intercepting the official click loses nothing):\r\n * the kernel's scrollport (`data-conversation-scroll`, ChatView's\r\n * `onScrollRef` handler) treats every unaccounted scrollTop assignment as a\r\n * reader move and on each frame (a) flips its own at-bottom machinery,\r\n * (b) `chatScroll.save(position)` for view-tab restore, and (c) re-derives\r\n * the rail's active mark. The glide animates the real scrollport, so all three\r\n * keep converging to the target row while it runs — the only thing the\r\n * interception skips is the official handler's synchronous active-mark\r\n * assignment, which its own scroll-driven derivation re-settles by the time\r\n * the glide lands.\r\n *\r\n * The jump goes through the shared backend (jump.ts).\r\n *\r\n * Interception notes: React 18 dispatches `onClick` from a listener on the\r\n * root container during the BUBBLE phase. A capture listener on the rail\r\n * itself therefore runs first, and `stopImmediatePropagation()` there prevents\r\n * the event from ever reaching React's root — the official handler never\r\n * fires. The frame-style rail owns pointer input for its whole column (marks\r\n * carry `pointer-events: none`); the virtualized rail's marks are real\r\n * buttons. Both arrive at the same capture listener on the rail.\r\n *\r\n * Nothing here touches the official DOM: no nodes are injected into a\r\n * React-owned subtree and no official class or paint is overridden.\r\n */\r\n\r\nimport react from 'react'\r\nimport { indexForEvent, mergeRailItems, normalizeNavigationItems } from './rail-pointer.ts'\r\nimport type { JumpToAnchor, TurnRailLadderItem } from './types.ts'\r\n\r\n/**\r\n * Structural anchor of the LEGACY (frame-style) official rail — its inline\r\n * style carries the frame's own metric (`--turn-natural-height`), published on\r\n * every kernel from dsh 0.1.2-rc.1 through 0.1.6. dsh 0.1.7's virtualized rail\r\n * publishes no inline metrics, so this selector only names the older\r\n * generation; discovery is {@link findOfficialRail}.\r\n */\r\nexport const OFFICIAL_RAIL_SELECTOR = '[data-conversation-scroll] nav[style*=\"--turn-natural-height\"]'\r\n\r\n/**\r\n * Find the official turn rail, in either supported generation.\r\n *\r\n * Identity comes from the SEAT, not from a rendered mark: the rail is the only\r\n * `<nav>` inside the conversation scrollport, and dsh 0.1.7's virtualizer\r\n * mounts its marks lazily — measured on the real rail, the `<nav>` exists with\r\n * an EMPTY marks container until the virtualizer has layout. Requiring a\r\n * rendered `button[data-index]` would therefore miss the rail exactly when it\r\n * first appears.\r\n *\r\n * The generation is then told apart by capability where it matters: the click\r\n * resolver reads a mark's own `data-index`, and only falls back to the legacy\r\n * frame geometry for a rail that publishes `--turn-scroll-top`.\r\n * @param root - the tree to search (defaults to the document).\r\n * @returns the rail element, or null when the conversation shows none.\r\n */\r\nexport function findOfficialRail(root: ParentNode = document): HTMLElement | null {\r\n return root.querySelector<HTMLElement>('[data-conversation-scroll] nav')\r\n}\r\n\r\n/**\r\n * Rails claimed by an enhancer instance, keyed by the owning instance's\r\n * claim token. One session-scoped enhancer mounts per session; without a\r\n * claim two instances would both attach capture listeners to the FIRST rail\r\n * in the document (multi-column layouts) and one of them — not necessarily\r\n * the one owning the reader's column — would win the click. The token\r\n * distinguishes \"already mine\" (the poll keeps it) from \"someone else's\"\r\n * (this instance must stand down). Weak: a replaced rail element disappears\r\n * as soon as the browser collects it, so remounts need no bookkeeping.\r\n */\r\nconst claimedRails = new WeakMap<HTMLElement, symbol>()\r\n\r\n/** How often the official rail is looked for (it mounts with the chat view). */\r\nconst RAIL_POLL_MS = 400\r\n\r\n/** Component props delivered by the dock slot registration. */\r\nexport interface OfficialTimelineEnhancerProps {\r\n /** Session-bound jump verb from the registration factory (apply closure). */\r\n jumpToAnchor?: JumpToAnchor\r\n /** Kernel selector hook over the Chat snapshot (dsh >= 0.1.2-rc.1). */\r\n useChat?: (selector: (snapshot: unknown) => unknown) => unknown\r\n /** Framework projection reader; the whole-log turn outline\r\n * (`turnOutline`) arrives through it. */\r\n useProjection?: (key: string) => unknown\r\n /** Whether the persisted timeline toggle is on. */\r\n enabled: boolean\r\n t: (key: string, params?: Record<string, string | number>) => string\r\n}\r\n\r\n/**\r\n * The enhancer: no visual output, only the behaviour fixes the running rail\r\n * generation lacks.\r\n * @param props - dock slot props plus the enabled gate.\r\n * @returns null (always).\r\n */\r\nexport function OfficialTimelineEnhancer(props: OfficialTimelineEnhancerProps): null {\r\n const { jumpToAnchor, useChat, useProjection, enabled } = props\r\n\r\n // The official navigation index — the loaded-window half of what the rail\r\n // renders, so this plugin always targets a mark that visually exists.\r\n const raw = useChat === undefined ? undefined : useChat((snapshot: unknown) => {\r\n const nav = (snapshot as { navigation?: { items?: () => unknown } } | undefined)?.navigation\r\n return typeof nav?.items === 'function' ? nav.items() : undefined\r\n })\r\n const items = normalizeNavigationItems(raw)\r\n // The whole-log outline (dsh >= 0.1.2-alpha.3) — the other half of the\r\n // rendered ladder. Reading an unregistered key is `undefined` on older\r\n // kernels, and the merge then degrades to the loaded items, which IS the\r\n // alpha.1/2 ladder. Called unconditionally per render: useProjection's\r\n // presence never flips within a kernel, so the hook order stays stable.\r\n const rawOutline = useProjection === undefined ? undefined : useProjection('turnOutline')\r\n const ladder = mergeRailItems(items, rawOutline)\r\n\r\n const [rail, setRail] = react.useState<HTMLElement | null>(null)\r\n\r\n // Mirrors so the listeners below stay identity-stable: `ladder` changes\r\n // identity on every turn change, and re-attaching a listener each time\r\n // would be pure churn. `railRef` mirrors the claimed rail so the poll\r\n // effect can release its claim on unmount without re-subscribing.\r\n const ladderRef = react.useRef(ladder)\r\n ladderRef.current = ladder\r\n const railRef = react.useRef<HTMLElement | null>(rail)\r\n railRef.current = rail\r\n // The bound verb is a fresh closure per inject-factory run; mirror it so\r\n // `navigate` (and the click listener below) stays identity-stable while\r\n // always calling the latest verb — same rationale as `ladderRef` above.\r\n const jumpToAnchorRef = react.useRef(jumpToAnchor)\r\n jumpToAnchorRef.current = jumpToAnchor\r\n\r\n // Locate the official rail (either generation). It mounts with the chat\r\n // view and is replaced whenever the view remounts, so it is polled rather\r\n // than observed once.\r\n //\r\n // Ownership guard: one enhancer instance mounts per SESSION (the dock slot\r\n // is session-scoped), so a multi-column layout can run several instances\r\n // against the same selector. Without a claim, the first instance's capture\r\n // listener — the first registered on the shared rail — would swallow the\r\n // clicks of every other instance, which then silently jump the WRONG\r\n // session. The first instance to find the rail claims it for its\r\n // registration lifetime; later instances stand down and the stock\r\n // behaviour stays (no duplicate handlers, no mis-directed jumps). The\r\n // poll re-runs on the SAME instance, so the claim check must recognise\r\n // \"already mine\" instead of standing down against itself.\r\n const claimToken = react.useMemo<symbol>(() => Symbol('dsh-bg-timeline-claim'), [])\r\n react.useEffect(() => {\r\n const check = (): void => {\r\n const found = findOfficialRail()\r\n if (found === null) {\r\n setRail(null)\r\n return\r\n }\r\n const owner = claimedRails.get(found)\r\n if (owner !== undefined && owner !== claimToken) {\r\n setRail(null)\r\n return\r\n }\r\n claimedRails.set(found, claimToken)\r\n setRail(found)\r\n }\r\n check()\r\n const timer = window.setInterval(check, RAIL_POLL_MS)\r\n return () => {\r\n window.clearInterval(timer)\r\n // Release the claim on unmount so a later instance (or a same-layout\r\n // remount that reuses the element) can take over.\r\n if (railRef.current !== null) claimedRails.delete(railRef.current)\r\n }\r\n }, [claimToken])\r\n\r\n const navigate = react.useCallback((item: TurnRailLadderItem): void => {\r\n // An outline-only mark (an unloaded turn) has no addressable row here —\r\n // the kernel's own loadThrough jump owns it (the click handler never\r\n // routes one this way; the guard is for direct callers).\r\n if (item.anchorKey === undefined) return\r\n const jump = jumpToAnchorRef.current\r\n if (jump === undefined) return\r\n void jump(item.anchorKey).catch(() => {})\r\n }, [])\r\n\r\n // Click interception. Capture phase on the rail itself, so this runs before\r\n // React's root bubble listener and the official handler never fires — for a\r\n // LOADED mark. An outline-only mark (an unloaded turn, alpha.3) carries no\r\n // anchor key: standing down lets the event reach the kernel's own handler,\r\n // whose loadThrough machinery pages the history in and lands the row. While\r\n // one of those kernel jumps is still paging (its mark pulses aria-busy) the\r\n // plugin stands down ENTIRELY: the kernel's loaded branch cancels its own\r\n // pending jump before landing, which an interception would bypass — the\r\n // orphaned jump would teleport the scrollport away mid-glide at settle.\r\n react.useEffect(() => {\r\n if (!enabled || rail === null) return\r\n const onClick = (event: MouseEvent): void => {\r\n if (rail.querySelector('[aria-busy=\"true\"]') !== null) return\r\n const index = indexForEvent(rail, event)\r\n const item = ladderRef.current[index]\r\n if (item === undefined || item.anchorKey === undefined) return\r\n event.preventDefault()\r\n event.stopImmediatePropagation()\r\n navigate(item)\r\n }\r\n rail.addEventListener('click', onClick, true)\r\n return () => { rail.removeEventListener('click', onClick, true) }\r\n }, [enabled, rail, navigate])\r\n\r\n return null\r\n}\r\n","/**\r\n * Conversation jump engine — the shared behaviour the official-rail enhancer\r\n * runs.\r\n *\r\n * Everything below the \"which row\" question lives here: paging older history\r\n * until the target node is loaded, waiting for React to commit the row,\r\n * detaching the host conversation's bottom-follow state so a re-render cannot\r\n * yank the glide back to the floor, and the glide itself.\r\n *\r\n * The official ChatView jump is one synchronous assignment\r\n * (`el.scrollTop += flowTop(row, el) - 24`) with no paging at all, which is\r\n * the pair of shortcomings this engine exists to fix. The enhancer is its one\r\n * caller (dsh >= 0.1.2-rc.1), so the engine is fitted to the current ChatView:\r\n * its 24px bottom-follow threshold and its 24px landing inset.\r\n */\r\n\r\nimport type { TimelineSessionsService } from './types.ts'\r\n\r\nconst delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\r\n\r\n/** How long one jump may keep paging older history before giving up. */\r\nconst JUMP_PAGE_DEADLINE_MS = 30000\r\n/** How long to wait for the chat view to commit the target row. */\r\nconst JUMP_ROW_WAIT_MS = 3000\r\n/** Settle between consecutive pages so a resolver without loadingOlder cannot hot-spin the loop. */\r\nconst PAGE_COOLDOWN_MS = 50\r\n\r\n/**\r\n * Official conversation follow threshold of the current ChatView. The reader\r\n * stays \"at bottom\" while within this distance of the floor, and the view\r\n * keeps following new content.\r\n */\r\nexport const CHATVIEW_FOLLOW_ZONE_PX = 24\r\n\r\n/** Distance from the scrollport top the official ChatView lands a jumped row at. */\r\nconst OFFICIAL_LANDING_INSET_PX = 24\r\n\r\n/** Distances under this snap instead of animating. */\r\nconst MIN_GLIDE_DISTANCE_PX = 8\r\n\r\n/** Glide duration floor / cap — a far-boundary jump stays a tangible slide. */\r\nconst MIN_GLIDE_MS = 280\r\nconst MAX_GLIDE_MS = 900\r\n\r\n/**\r\n * Where a jumped row should land.\r\n * @param el - the conversation scrollport.\r\n * @param row - the target chat row.\r\n * @returns the destination scrollTop.\r\n */\r\nexport type TargetTopResolver = (el: HTMLElement, row: HTMLElement) => number\r\n\r\n/** Knobs one jump call may override. */\r\nexport interface JumpOptions {\r\n /** The host conversation's bottom-follow threshold in px. */\r\n followZonePx?: number\r\n /** Destination for the target row; defaults to centering it vertically. */\r\n targetTop?: TargetTopResolver\r\n /**\r\n * Restrict the pre-page bottom-follow detach to this scrollport. Omit to\r\n * detach every mounted scrollport (the safe default when the jump's target\r\n * column is not known yet).\r\n */\r\n scrollport?: HTMLElement | null\r\n /** Budget for paging older history. */\r\n pageDeadlineMs?: number\r\n /** Budget for waiting on the row to be committed. */\r\n rowWaitMs?: number\r\n}\r\n\r\n/** Every mounted conversation scrollport (multi-column layouts have several). */\r\nexport function conversationScrollports(): HTMLElement[] {\r\n return Array.from(document.querySelectorAll<HTMLElement>('[data-conversation-scroll]'))\r\n}\r\n\r\n/**\r\n * Whether the target chat row is committed in ANY mounted conversation\r\n * scrollport. Kernel generations disagree on where the node table lives\r\n * (0.1.1: `SessionSnapshot.chat.nodes`; 0.1.2+: the `useChat` ChatSnapshot\r\n * node store, unreachable from `session.getSnapshot()` — its SessionSnapshot\r\n * faces no node table at all), but the rendered DOM is the ground truth both\r\n * share, so a jump probes it FIRST before deciding whether paging is needed.\r\n * @param anchorKey - chat anchor key of the target row.\r\n */\r\nexport function rowRenderedInDom(anchorKey: string): boolean {\r\n const selector = `[data-chat-anchor-key=\"${CSS.escape(anchorKey)}\"]`\r\n for (const scrollport of conversationScrollports()) {\r\n if (scrollport.querySelector(selector) !== null) return true\r\n }\r\n return false\r\n}\r\n\r\n/**\r\n * Poll the conversation DOM until the target row is committed. The session\r\n * store updates BEFORE React renders the prepended page, so the old one-shot\r\n * query right after loadOlder() resolved could miss the row and silently\r\n * give up — the reported \"click does nothing until the conversation is\r\n * nudged (scroll/click)\" behavior.\r\n * @param key - chat anchor key to look for.\r\n * @param timeoutMs - polling budget.\r\n */\r\nexport async function waitForChatRow(key: string, timeoutMs: number): Promise<HTMLElement | null> {\r\n const selector = `[data-chat-anchor-key=\"${CSS.escape(key)}\"]`\r\n const deadline = Date.now() + timeoutMs\r\n for (;;) {\r\n // Re-scan on every round: a ChatView remount (or a second column coming\r\n // up) repoints the row target, and the one-shot snapshot at entry would\r\n // miss a row committed into the PORT that appeared mid-wait.\r\n for (const scrollport of conversationScrollports()) {\r\n const row = scrollport.querySelector<HTMLElement>(selector)\r\n if (row !== null) return row\r\n }\r\n if (Date.now() >= deadline) return null\r\n await delay(40)\r\n }\r\n}\r\n\r\n/** Maximum scrollable offset of a conversation scrollport. */\r\nexport function scrollFloorOf(el: HTMLElement): number {\r\n return Math.max(0, el.scrollHeight - el.clientHeight)\r\n}\r\n\r\n/**\r\n * Destination scrollTop that centers `row` inside `el` — the same geometry\r\n * as scrollIntoView({ block: 'center' }), computed directly so the glide owns\r\n * the scroll position instead of the browser.\r\n * @param el - the conversation scrollport.\r\n * @param row - the target chat row.\r\n */\r\nexport function centeredScrollTopFor(el: HTMLElement, row: HTMLElement): number {\r\n const floor = scrollFloorOf(el)\r\n const target = el.scrollTop\r\n + (row.getBoundingClientRect().top - el.getBoundingClientRect().top)\r\n - (el.clientHeight - row.offsetHeight) / 2\r\n if (!Number.isFinite(target)) return el.scrollTop\r\n return Math.max(0, Math.min(floor, target))\r\n}\r\n\r\n/**\r\n * Destination scrollTop the official ChatView uses for a navigation jump:\r\n * the row's top minus a 24px inset. Kept as a named resolver so the enhancer\r\n * lands exactly where the official would — only animated instead of assigned.\r\n * @param el - the conversation scrollport.\r\n * @param row - the target chat row.\r\n */\r\nexport function officialTargetTopFor(el: HTMLElement, row: HTMLElement): number {\r\n const floor = scrollFloorOf(el)\r\n const target = el.scrollTop\r\n + (row.getBoundingClientRect().top - el.getBoundingClientRect().top)\r\n - OFFICIAL_LANDING_INSET_PX\r\n if (!Number.isFinite(target)) return el.scrollTop\r\n return Math.max(0, Math.min(floor, target))\r\n}\r\n\r\n/**\r\n * Glide duration for a distance: longer slides take longer, bounded to\r\n * [MIN_GLIDE_MS, MAX_GLIDE_MS] (a deep-history jump reads as a deliberate\r\n * glide, not a sluggish race).\r\n * @param distance - px to travel.\r\n */\r\nexport function glideDurationFor(distance: number): number {\r\n if (!Number.isFinite(distance)) return MIN_GLIDE_MS\r\n return Math.round(Math.max(MIN_GLIDE_MS, Math.min(MAX_GLIDE_MS, 240 + Math.abs(distance) * 0.14)))\r\n}\r\n\r\n/**\r\n * Detach the host's bottom-follow state when the reader currently sits inside\r\n * its follow zone: assign the scroll position just outside the boundary. That\r\n * one synchronous scroll event settles at-bottom to false (moved-by-reader +\r\n * outside the zone) so a tip-moved re-render cannot yank the glide back to\r\n * the floor. No-op when already above the zone or when the assignment cannot\r\n * move (content shorter than the viewport).\r\n * @param el - the conversation scrollport.\r\n * @param followZonePx - the host's own follow threshold.\r\n */\r\nexport function detachBottomFollow(el: HTMLElement, followZonePx: number = CHATVIEW_FOLLOW_ZONE_PX): boolean {\r\n const floor = scrollFloorOf(el)\r\n // Only the bottom zone can still hold the follow flag: anywhere above it\r\n // the official scroll handler already settled at-bottom to false, and\r\n // dragging the viewport down to the zone would be a visible jump.\r\n if (el.scrollTop < floor - followZonePx) return false\r\n const detached = Math.max(0, floor - followZonePx - 1)\r\n if (Math.abs(el.scrollTop - detached) < 1) return false\r\n el.scrollTop = detached\r\n return true\r\n}\r\n\r\n/**\r\n * Wait until a detach assignment's scroll event has been dispatched AND\r\n * consumed by the host's scroll handler: scroll events fire on a later frame\r\n * than the assignment, and a paging commit's layout effect runs before that\r\n * dispatch — it would re-run the follow logic while the at-bottom flag is\r\n * still true and drag the viewport to the growing floor, undoing the detach\r\n * (two rAFs + a macrotask bracket the dispatch window).\r\n */\r\nexport function settleScrollEvents(): Promise<void> {\r\n if (typeof requestAnimationFrame !== 'function') return Promise.resolve()\r\n return new Promise((resolve) => {\r\n requestAnimationFrame(() => requestAnimationFrame(() => window.setTimeout(resolve, 0)))\r\n })\r\n}\r\n\r\n/**\r\n * Animate el.scrollTop toward target with an ease-in-out cubic profile.\r\n * Wheel / touch / keyboard-scroll input cancels the glide so the reader\r\n * takes over immediately. Resolves true when the glide completed, false\r\n * when the reader cancelled it.\r\n * @param el - the conversation scrollport.\r\n * @param target - destination scrollTop.\r\n * @param durationMs - animation budget.\r\n */\r\nexport function animateScrollTop(el: HTMLElement, target: number, durationMs: number): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n const start = el.scrollTop\r\n const delta = target - start\r\n let done = false\r\n let frame = 0\r\n const finish = (completed: boolean): void => {\r\n if (done) return\r\n done = true\r\n el.removeEventListener('wheel', onWheel)\r\n el.removeEventListener('touchstart', onTouch)\r\n window.removeEventListener('keydown', onKey)\r\n if (frame !== 0) cancelAnimationFrame(frame)\r\n resolve(completed)\r\n }\r\n const onWheel = (): void => finish(false)\r\n const onTouch = (): void => finish(false)\r\n const onKey = (e: KeyboardEvent): void => {\r\n if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(e.key)) finish(false)\r\n }\r\n el.addEventListener('wheel', onWheel, { passive: true })\r\n el.addEventListener('touchstart', onTouch, { passive: true })\r\n window.addEventListener('keydown', onKey)\r\n if (Math.abs(delta) < 0.5 || durationMs <= 0) {\r\n el.scrollTop = target\r\n finish(true)\r\n return\r\n }\r\n const t0 = performance.now()\r\n const ease = (x: number): number => x < 0.5 ? 4 * x * x * x : 1 - (-2 * x + 2) ** 3 / 2\r\n const step = (now: number): void => {\r\n if (done) return\r\n const t = Math.min(1, (now - t0) / durationMs)\r\n el.scrollTop = start + delta * ease(t)\r\n if (t < 1) frame = requestAnimationFrame(step)\r\n else finish(true)\r\n }\r\n frame = requestAnimationFrame(step)\r\n })\r\n}\r\n\r\n/**\r\n * Ensure the target node is loaded, wait for the chat view to commit its row\r\n * into the DOM, then glide it into view. Paging uses a time budget instead\r\n * of a fixed iteration guard (each in-flight page burned part of that guard,\r\n * so slow history could exhaust it and silently fail).\r\n *\r\n * @param sessionsService - the runtime sessions service.\r\n * @param sessionId - the session owning the target row.\r\n * @param anchorKey - chat anchor key of the target row.\r\n * @param options - follow zone, landing resolver, paging scopes and budgets.\r\n * @returns whether the row was reached (false when it never rendered or the\r\n * reader took the scrollport back mid-glide).\r\n */\r\nexport async function jumpToMessage(\r\n sessionsService: TimelineSessionsService,\r\n sessionId: string,\r\n anchorKey: string,\r\n options: JumpOptions = {},\r\n): Promise<boolean> {\r\n const session = sessionsService.binding(sessionId)?.session\r\n if (session === undefined) return false\r\n const followZonePx = options.followZonePx ?? CHATVIEW_FOLLOW_ZONE_PX\r\n const resolveTarget = options.targetTop ?? centeredScrollTopFor\r\n const pageDeadlineMs = options.pageDeadlineMs ?? JUMP_PAGE_DEADLINE_MS\r\n const rowWaitMs = options.rowWaitMs ?? JUMP_ROW_WAIT_MS\r\n\r\n // Detach the host's bottom-follow flag BEFORE paging: while a multi-page\r\n // loadOlder is in flight, every prepend commit re-runs the follow layout\r\n // effect (tip-moved + at-bottom) and toBottom() drags the viewport to the\r\n // (growing) floor — the jump would read as \"surf to the newest floor, then\r\n // slide back to the target\". One synchronous assignment just outside the\r\n // zone settles the flag for the whole paging phase, but the flag only flips\r\n // when the assignment's scroll event reaches the host handler, which\r\n // happens a frame later — so the paging phase waits for it first. The\r\n // post-wait detach below stays idempotent for the already-loaded case.\r\n // Multi-column layouts cannot pin the target session's scrollport before\r\n // the row exists, so every mounted scrollport is detached unless the caller\r\n // already knows the column (a ~zone-sized nudge elsewhere is the whole\r\n // cost; the reader's next scroll re-engages follow there).\r\n const ports = options.scrollport != null ? [options.scrollport] : conversationScrollports()\r\n let detachedAny = false\r\n for (const el of ports) {\r\n if (detachBottomFollow(el, followZonePx)) {\r\n // The host scroll handler flips its at-bottom flag on a scroll event,\r\n // and the real one only dispatches on a later frame — a paging\r\n // commit's layout effect runs before it and re-runs the follow logic.\r\n // A synthetic event notifies the handler synchronously, so the flag\r\n // settles before any prepend commit can observe it.\r\n el.dispatchEvent(new Event('scroll'))\r\n detachedAny = true\r\n }\r\n }\r\n if (detachedAny) await settleScrollEvents()\r\n const deadline = Date.now() + pageDeadlineMs\r\n for (;;) {\r\n // DOM ground truth FIRST: the row may already be committed in some\r\n // scrollport even though `session.getSnapshot()` exposes no node table\r\n // (the `useChat` ChatSnapshot store is unreachable from here), so\r\n // without this probe every jump would page the WHOLE history to the end\r\n // (or burn the 30s deadline) even when the target row is on screen\r\n // behind the \"load earlier\" button.\r\n if (rowRenderedInDom(anchorKey)) break\r\n const snapshot = session.getSnapshot() as {\r\n hasMore?: boolean\r\n loadingOlder?: boolean\r\n openState?: unknown\r\n }\r\n if (snapshot?.hasMore !== true) break\r\n if (Date.now() >= deadline) break\r\n if (snapshot.loadingOlder === true) {\r\n await delay(50)\r\n continue\r\n }\r\n // A session that is not open yet cannot page; give it a moment instead\r\n // of spinning the full budget on no-op calls.\r\n if (snapshot.openState !== undefined && snapshot.openState !== 'open') {\r\n await delay(100)\r\n continue\r\n }\r\n // A failing page must not reject the whole jump (or leave a lock to a\r\n // fallback timer): log and stop paging — the row may still render from an\r\n // earlier page, so waitForChatRow below gets its chance anyway.\r\n try {\r\n await session.loadOlder()\r\n } catch (error) {\r\n console.warn('[deepseek-harness-background] timeline jump paging failed:', error)\r\n break\r\n }\r\n // Throttle consecutive pages: a resolver that comes back without ever\r\n // flipping `loadingOlder` must not turn this loop into a hot spin (the\r\n // real kernel raises the flag; the cooldown also gives each page a\r\n // moment to commit before the next DOM probe).\r\n await delay(PAGE_COOLDOWN_MS)\r\n }\r\n const row = await waitForChatRow(anchorKey, rowWaitMs)\r\n if (row === null) {\r\n console.warn(`[deepseek-harness-background] timeline jump target never rendered: ${anchorKey}`)\r\n return false\r\n }\r\n const reducedMotion = typeof window.matchMedia === 'function'\r\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches\r\n const scroller = row.closest<HTMLElement>('[data-conversation-scroll]')\r\n // No addressable scrollport (defensive): keep the platform path — the\r\n // instant placement flips the host's bottom-follow state through its scroll\r\n // event, the smooth pass afterwards is a no-op when already centered (and a\r\n // graceful glide when the row moved since).\r\n if (scroller === null) {\r\n row.scrollIntoView({ behavior: 'auto', block: 'center' })\r\n if (!reducedMotion) row.scrollIntoView({ behavior: 'smooth', block: 'center' })\r\n return true\r\n }\r\n const target = resolveTarget(scroller, row)\r\n // Reduced motion or a hairline distance: one placement (its scroll event\r\n // still flips the bottom-follow state when it lands outside the zone).\r\n if (reducedMotion || Math.abs(target - scroller.scrollTop) < MIN_GLIDE_DISTANCE_PX) {\r\n scroller.scrollTop = target\r\n return true\r\n }\r\n // Bottom-follow detach BEFORE the glide: a pure slide starting at the floor\r\n // leaves the at-bottom flag true for its first frames, so a tip-moved\r\n // re-render (turn end / commit) yanks it back to the floor. The detach moves\r\n // at most ~zone px to just outside the follow zone (reads as a nudge, not a\r\n // jump); the flag settles once the dispatch frame passes, so the glide\r\n // waits for it first.\r\n if (detachBottomFollow(scroller, followZonePx)) {\r\n scroller.dispatchEvent(new Event('scroll'))\r\n await settleScrollEvents()\r\n }\r\n const completed = await animateScrollTop(scroller, target, glideDurationFor(Math.abs(target - scroller.scrollTop)))\r\n return completed\r\n}\r\n","/**\r\n * Dock-slot entry for the conversation timeline: mounts the official-rail\r\n * enhancer on kernels that publish the turn-navigation index.\r\n *\r\n * Supported dsh baseline: >= 0.1.2-rc.1. Every such kernel ships the official\r\n * turn rail (`TurnNavigator` in dsh-client-ui-chat), rendered from the very\r\n * index `ui-chat` publishes as a session hook (`useChat(s => s.navigation.items())`),\r\n * so the presence of that hook IS the presence of the official rail. This\r\n * plugin therefore never renders a rail of its own — it only improves the\r\n * official one's behaviour (smooth glide for loaded marks, standing down\r\n * while the kernel's own loadThrough jump is still paging).\r\n *\r\n * The hook-missing branch is a defensive no-op, not a second frontend: on\r\n * kernels without the official rail (everything before 0.1.2) this plugin\r\n * renders nothing, exactly as the supported-version contract requires\r\n * (README directs those users to an older plugin release).\r\n */\r\n\r\nimport react from 'react'\r\nimport { settingsClient } from '../settings-client.ts'\r\nimport { findOfficialRail, OfficialTimelineEnhancer } from './official-enhance.tsx'\r\nimport type { JumpToAnchor } from './types.ts'\r\n\r\n/** Props the dock slot delivers. */\r\nexport interface TimelineBridgeProps {\r\n /** Session-bound jump verb from the registration factory (apply closure). */\r\n jumpToAnchor?: JumpToAnchor\r\n /** Kernel selector hook over the Chat snapshot; present on dsh >= 0.1.2. */\r\n useChat?: (selector: (snapshot: unknown) => unknown) => unknown\r\n /** Framework projection reader (SessionStandardProps seat). */\r\n useProjection?: (key: string) => unknown\r\n /** Locale lookup. Dictionaries interpolate `{turn}` placeholders, like the\r\n * official chat namespace's own turn-navigation copy does. */\r\n t: (key: string, params?: Record<string, string | number>) => string\r\n}\r\n\r\n/**\r\n * Mount the timeline frontend the running kernel supports.\r\n * @param props - dock slot props.\r\n * @returns the enhancer, or nothing on kernels without the official rail.\r\n */\r\nexport function TimelineBridge(props: TimelineBridgeProps): react.ReactElement | null {\r\n const { useChat, t, ...rest } = props\r\n\r\n const settings = react.useSyncExternalStore(settingsClient.subscribe, settingsClient.getSnapshot)\r\n // Hide only while the persisted toggle is UNKNOWN (no wrong-state flash for\r\n // timeline:false users); once loading settles the default-on enhancement\r\n // runs even if the section errored.\r\n const enabled = settings.value?.timeline !== false && settings.status !== 'loading'\r\n\r\n if (useChat === undefined) return null\r\n return react.createElement(OfficialTimelineEnhancer, { ...rest, useChat, enabled, t })\r\n}\r\n\r\nexport {\r\n CHATVIEW_FOLLOW_ZONE_PX,\r\n centeredScrollTopFor,\r\n conversationScrollports,\r\n glideDurationFor,\r\n jumpToMessage,\r\n officialTargetTopFor,\r\n scrollFloorOf,\r\n settleScrollEvents,\r\n animateScrollTop,\r\n detachBottomFollow,\r\n waitForChatRow,\r\n type JumpOptions,\r\n type TargetTopResolver,\r\n} from './jump.ts'\r\nexport {\r\n RAIL_INSET_PX,\r\n TICK_SPACING_PX,\r\n frameIndexAtPointer,\r\n indexForEvent,\r\n isFrameRail,\r\n mergeRailItems,\r\n normalizeNavigationItems,\r\n normalizeOutlineItems,\r\n railInsetOf,\r\n} from './rail-pointer.ts'\r\nexport {\r\n findOfficialRail,\r\n OFFICIAL_RAIL_SELECTOR,\r\n OfficialTimelineEnhancer,\r\n type OfficialTimelineEnhancerProps,\r\n} from './official-enhance.tsx'\r\n\r\n/** localStorage prefix the removed key-point bookmark feature used (< 0.5). */\r\nconst MARKS_STORAGE_PREFIX = 'dsbt_marks_'\r\n\r\n/**\r\n * Drop the bookmark rows pre-0.5 versions left in localStorage.\r\n *\r\n * The bookmarks are gone — the rail now reuses the official UI, which has no\r\n * affordance for them — so the rows are dead weight every session carried\r\n * forever. One-way and irreversible by design; the key prefix is owned by\r\n * this plugin, so nothing else can be caught in the sweep.\r\n */\r\nexport function clearLegacyMarks(): void {\r\n if (typeof window === 'undefined') return\r\n try {\r\n const stale: string[] = []\r\n for (let i = 0; i < window.localStorage.length; i += 1) {\r\n const key = window.localStorage.key(i)\r\n if (key !== null && key.startsWith(MARKS_STORAGE_PREFIX)) stale.push(key)\r\n }\r\n for (const key of stale) window.localStorage.removeItem(key)\r\n } catch {\r\n /* storage unavailable (private mode, blocked origin) — nothing to clean */\r\n }\r\n}\r\nexport type {\r\n JumpToAnchor,\r\n OfficialNavigationItem,\r\n TimelineSessionHandle,\r\n TimelineSessionsService,\r\n TurnRailLadderItem,\r\n} from './types.ts'\r\n","/**\r\n * Browser half of the background plugin: paints the background behind the\r\n * whole app frame, provides the settings surface (a live row inside the\r\n * official General settings section), and renders the conversation timeline\r\n * rail (DeepSeek ScrollNav style) on the same frosted-glass system. Data\r\n * flows through the plugin's own host route (`/api/bg-wallpaper/*`), not the\r\n * settings RPC. Retraction discipline mirrors the skin plugins: every\r\n * property and DOM node the painter owns is restored on dispose.\r\n */\r\n\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport { backgroundPainter, paintBackground } from './backdrop.ts'\r\nimport { installGlassBridge } from './glass-registry.ts'\r\nimport { en, zh } from './locales.ts'\r\nimport { BackgroundSettingsRow } from './SettingsRow.tsx'\r\nimport { settingsClient } from './settings-client.ts'\r\nimport {\r\n CHATVIEW_FOLLOW_ZONE_PX, findOfficialRail, TimelineBridge, clearLegacyMarks,\r\n jumpToMessage, officialTargetTopFor,\r\n} from './timeline/index.tsx'\r\nimport type {} from './types.ts'\r\n\r\n/**\r\n * Service injections this entry declares: `slots` + `locale` for the settings\r\n * surface and the timeline slot seat, and `sessions` so the timeline rail can\r\n * enumerate user messages and jump between them. The settings surface itself\r\n * talks to the plugin's own host route over plain same-origin fetch.\r\n */\r\nexport const inject = ['slots', 'locale', 'sessions']\r\n\r\n/**\r\n * Client plugin body: paint the persisted background live, register the\r\n * General-settings row, and mount the conversation timeline rail.\r\n * @param ctx - client cordis context (slots/locale/sessions injected).\r\n */\r\nexport function apply(ctx: Context): void {\r\n // Publish the third-party frosted-glass registry bridge FIRST so an\r\n // eager consumer can register before the first snapshot repaints; the\r\n // bridge lives for the whole fiber and dies with it (see the dispose below).\r\n const glassBridge = installGlassBridge()\r\n // One-way sweep of the bookmark rows pre-0.5 versions left in localStorage\r\n // (the rail reuses the official UI now, which has no bookmarks).\r\n clearLegacyMarks()\r\n // Paint from the plugin's own transport; repaint on every snapshot change.\r\n const paint = (): void => {\r\n const snapshot = settingsClient.getSnapshot()\r\n const value = snapshot.status === 'ready' ? snapshot.value : undefined\r\n if (value === undefined || !value.enabled) {\r\n backgroundPainter.dispose()\r\n return\r\n }\r\n paintBackground(value)\r\n }\r\n const unsubscribe = settingsClient.subscribe(paint)\r\n void settingsClient.load()\r\n\r\n ctx.effect(() => ctx.locale.register('ui-background', { zh, en }), 'deepseek-harness-background: dictionaries')\r\n\r\n // Live settings row in the General section (same slot as the Appearance row).\r\n ctx.slots.inject('settings.general.item', () => ctx.slots.register({\r\n name: 'settings.general.item',\r\n id: 'deepseek-harness-background',\r\n order: 600,\r\n locale: 'ui-background',\r\n }, BackgroundSettingsRow))\r\n\r\n // Conversation timeline: takes a per-session dock seat only to bind its\r\n // lifecycle — the official rail is enhanced in place, never re-rendered.\r\n // A bound jumpToAnchor verb is handed to the component through the\r\n // registration's inject face (the ui-goal verbs+hooks shape): the service\r\n // never crosses the slot boundary, and the session is bound lazily inside\r\n // the factory so a runtime-side service rebuild can never leave the rail\r\n // holding a stale reference.\r\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\r\n name: 'conversation.input.dock',\r\n id: 'deepseek-harness-background.timeline',\r\n order: 45,\r\n locale: 'ui-background',\r\n inject: (sessionId: string) => ({\r\n jumpToAnchor: (anchorKey: string) => {\r\n // Scope the bottom-follow detach to this rail's own column: the\r\n // official rail is mounted, so its scrollport is known and the other\r\n // columns need no nudge. Resolved at call time, not at registration.\r\n const scrollport = findOfficialRail()\r\n ?.closest<HTMLElement>('[data-conversation-scroll]') ?? null\r\n return jumpToMessage(ctx.sessions, sessionId, anchorKey, {\r\n followZonePx: CHATVIEW_FOLLOW_ZONE_PX,\r\n targetTop: officialTargetTopFor,\r\n scrollport,\r\n })\r\n },\r\n }),\r\n }, TimelineBridge))\r\n\r\n ctx.effect(() => () => {\r\n unsubscribe()\r\n glassBridge.dispose()\r\n backgroundPainter.dispose()\r\n }, 'deepseek-harness-background: background surface')\r\n}\r\n"],"x_google_ignoreList":[5],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "deepseek-harness-background",
3
3
  "description": "Custom background image plugin for the DeepSeek Harness web GUI: upload a local picture or paste an image URL, and render it behind the whole app surface with adjustable opacity, scrim, panel transparency and frosted-glass blur.",
4
- "version": "0.5.5",
4
+ "version": "0.5.6",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",