deepseek-harness-background 0.4.2 → 0.5.0
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 +17 -6
- package/README.zh.md +14 -6
- package/lib/client.js +1274 -1077
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -1
- package/package.json +8 -9
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useState","useRef","css","useSyncExternalStore","useCallback","reactDom"],"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-css.ts","../src/client/timeline.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 (timeline-css.ts). 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 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*=\"_buildRevision\"] {\n background-color: rgba(15, 17, 21, 0.62);\n }\n body[data-ds-dark-theme][data-dsh-bg] [class*=\"_buildRevision\"] {\n background-color: rgba(249, 250, 251, 0.62);\n }\n\n /* The empty-state hero glow asset would wash out the wallpaper behind the\n welcome message — dim it so the art stays visible. */\n body[data-dsh-bg] [class*=\"_heroGlow\"] {\n opacity: 0.2;\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*=\"_buildRevision\"] {\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/**\r\n * Session-projection key this plugin owns in the client-visible value map —\r\n * the host fold registers it (src/projection.ts) and the rail reads it\r\n * (src/client/timeline.tsx); one shared constant so the two can never drift.\r\n */\r\nexport const TIMELINE_PROJECTION_KEY = 'bgTimeline'\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.timeline': '会话时间线',\n 'background.timelineHint': '右侧消息导航轨,悬停展开问题列表(DeepSeek 官网风格)',\n 'timeline.railLabel': '对话时间线',\n 'timeline.roleUser': '用户',\n 'timeline.noText': '(无文本内容)',\n 'timeline.marksOnly': '★ 只看标记',\n 'timeline.mark': '标记为重点',\n 'timeline.unmark': '取消重点标记',\n 'timeline.noMarks': '还没有标记的重点',\n 'timeline.cannotJump': '无法跳转:这条历史消息缺少定位信息',\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.timeline': 'Conversation timeline',\n 'background.timelineHint': 'Message rail at the right edge; hover to expand the question list (DeepSeek web style)',\n 'timeline.railLabel': 'Chat timeline',\n 'timeline.roleUser': 'User',\n 'timeline.noText': '(no text)',\n 'timeline.marksOnly': '★ Marked only',\n 'timeline.mark': 'Mark as key point',\n 'timeline.unmark': 'Unmark key point',\n 'timeline.noMarks': 'No marked key points',\n 'timeline.cannotJump': 'Cannot jump: this history message lacks position info',\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\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 { ok: boolean; value?: BackgroundSettings }\n if (!body.ok || body.value === undefined) throw new Error('unexpected payload')\n this.snapshot = { status: 'ready', value: body.value }\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 this.snapshot = { status: 'ready', value: body.value }\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 const [draft, setDraft] = useState<BackgroundSettings>(DEFAULTS)\n const [uploading, setUploading] = useState(false)\n const [urlText, setUrlText] = useState('')\n const [error, setError] = useState('')\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 {/* 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('background.timeline')}</span>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={draft.timeline}\n aria-label={t('background.timeline')}\n title={t('background.timelineHint')}\n className={css.switch}\n onClick={handleTimelineToggle}\n >\n <span className={css.switchKnob} />\n </button>\n <span className={css.hint}>{t('background.timelineHint')}</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","/**\n * Timeline CSS — the conversation scroll-navigation rail (DeepSeek web\n * ScrollNav structure, rebuilt on this plugin's frosted-glass system).\n *\n * Structure and metrics mirror the official chat.deepseek.com stylesheet\n * (module map extracted from the shipped bundle; v0.1.4 of the reference port\n * dsh-chat-timeline widens the panel to 260px and adds bookmarks):\n * nav 34px-wide rail, fixed right edge, vertically centered; collapsed\n * blurred capsule (height − 8px); expanding panel (max 260px, radius 16)\n * whose scroll area holds 30px rows (tick indicator + fading-in title);\n * 32px gradient fade veils top/bottom, expanded-only, shown while that\n * side actually clips.\n *\n * Deviations from the reference port, all fidelity fixes:\n * - The filter bar collapses to zero height while idle instead of staying\n * in layout invisibly (it pushed every tick ~33px down).\n * - Short stacks center vertically; overflowing stacks are bottom-pinned\n * by the component so the newest tick hugs the capsule's bottom edge\n * (the official idle strategy — the reference port flowed ticks from\n * the top and clipped the newest away).\n *\n * Glass integration: the official chat.deepseek.com paints (light\n * rgba(255,255,255,.8)/.94, dark rgba(21,21,23,.6)/rgba(28,28,32,.95),\n * 5px collapsed / 16px expanded blur) remain as the NO-GLASS fallback.\n * While the plugin's glass system is on (`body[data-dsh-bg-glass]`), the\n * rail joins the SAME unified recipe as the composer card and bubbles:\n * fill = the painter's `--dsw-specific-input-major` token, blur/saturate/\n * brightness = the shared `--bg-glass-*` variables, i.e. the glass-blur\n * slider. Class prefix `dsbt-` deliberately avoids every\n * `[class*=\"_xxx\"]` anchor of the plugin's glass sheet (underscores only).\n */\n\n/** Unique id stamped on the injected style element (dedup key). */\nexport const TIMELINE_CSS_TAG = 'deepseek-harness-background/timeline'\n\n/**\n * Expanded-row title size in px. The canvas width measurement in timeline.tsx\n * reads this same constant — a fitted panel width must track the real text\n * metrics, so the two can never drift apart.\n */\nexport const TIMELINE_TITLE_FONT_PX = 13\n\n/** Inject the timeline stylesheet once. Idempotent (dedup key on the tag). */\nexport function injectTimelineCss(): void {\n if (typeof document === 'undefined') return\n if (document.querySelector(`style[data-plugin-css=\"${TIMELINE_CSS_TAG}\"]`)) return\n const tag = document.createElement('style')\n tag.dataset.plugin = 'deepseek-harness-background'\n tag.dataset.pluginCss = TIMELINE_CSS_TAG\n tag.textContent = TIMELINE_CSS\n document.head.appendChild(tag)\n}\n\nexport const TIMELINE_CSS = `\n .dsbt-nav {\n user-select: none; -webkit-user-select: none;\n /* Filter-bar chrome height shared by the bar itself and the top veil's\n expanded offset. */\n --dsbt-bar-h: 33px;\n z-index: 90; align-items: center; width: 34px;\n height: var(--dsbt-h, 300px);\n transition: right .2s ease, opacity .2s ease;\n display: flex; position: fixed; top: 50%; right: 16px;\n transform: translateY(-50%); pointer-events: auto;\n }\n @media (max-width: 767px) { .dsbt-nav { display: none; } }\n\n /* Collapsed capsule — frosted strip behind the tick marks. Official paint:\n light rgba(255,255,255,.8), dark rgba(21,21,23,.6), 5px blur. */\n .dsbt-bg {\n background-color: rgba(255, 255, 255, .8);\n -webkit-backdrop-filter: blur(5px);\n backdrop-filter: blur(5px);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, .28),\n inset 0 0 0 .5px rgba(255, 255, 255, .1),\n 0 6px 18px rgba(0, 0, 0, .07);\n border-radius: 16px; width: 34px;\n height: calc(var(--dsbt-h, 300px) - 8px); max-height: calc(var(--dsbt-h, 300px) - 8px);\n position: absolute; top: 50%; right: 0; transform: translateY(-50%);\n transition: opacity .2s ease; pointer-events: none;\n }\n body[data-ds-dark-theme] .dsbt-bg {\n background-color: rgba(21, 21, 23, .6);\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, .09),\n inset 0 0 0 .5px rgba(255, 255, 255, .05),\n 0 6px 18px rgba(0, 0, 0, .3);\n }\n .dsbt-bg.dsbt-bg-hide { opacity: 0; }\n\n /* Expanding panel — exactly the capsule's box (identical height in both\n states), width animates 34px -> measured fit (max 260px). Official\n hover-state paint: white/black frosted glass at ~94% alpha, 16px blur. */\n .dsbt-wrap {\n width: 34px;\n height: calc(var(--dsbt-h, 300px) - 8px); max-height: calc(var(--dsbt-h, 300px) - 8px);\n border: 1px solid transparent; border-radius: 16px;\n flex-direction: column; align-items: stretch;\n transition: width .22s cubic-bezier(.4, 0, .2, 1), background-color .2s ease,\n box-shadow .2s ease, border-color .2s ease;\n display: flex; position: absolute; top: 50%; right: 0;\n transform: translateY(-50%);\n overflow: hidden; box-sizing: border-box; background: transparent;\n pointer-events: none;\n }\n .dsbt-wrap.dsbt-show {\n pointer-events: auto;\n width: min(var(--dsbt-w, 260px), 260px);\n background-color: rgba(255, 255, 255, .94);\n -webkit-backdrop-filter: blur(16px);\n backdrop-filter: blur(16px);\n border-color: rgba(0, 0, 0, .06);\n box-shadow: 0 10px 30px rgba(0, 0, 0, .08), 0 2px 8px rgba(0, 0, 0, .04);\n }\n body[data-ds-dark-theme] .dsbt-wrap.dsbt-show {\n background-color: rgba(28, 28, 32, .95);\n border-color: rgba(255, 255, 255, .08);\n box-shadow: 0 10px 30px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .25);\n }\n\n /* Content fade veils — the official ::before/::after pair rebuilt as\n elements: a bar of the panel's OWN fill color masked to nothing over\n 32px (solid -> mask == gradient-to-transparent, but works with the\n translucent fill). They belong to the EXPANDED panel only — the idle\n capsule hard-clips its ticks at the rounded ends like the official\n strip — and while expanded the top veil starts BELOW the filter bar,\n not over it. Shown only while that side actually clips. */\n .dsbt-fade {\n z-index: 2; pointer-events: none; opacity: 0;\n background-color: rgba(255, 255, 255, .94);\n -webkit-mask-image: linear-gradient(#000 20.19%, transparent 100%);\n mask-image: linear-gradient(#000 20.19%, transparent 100%);\n width: 100%; height: 32px; transition: opacity .2s ease;\n position: absolute; left: 0; top: 0;\n }\n body[data-ds-dark-theme] .dsbt-fade { background-color: rgba(28, 28, 32, .95); }\n .dsbt-fade.dsbt-fade-bot { top: auto; bottom: 0; transform: rotate(180deg); }\n .dsbt-wrap.dsbt-show .dsbt-fade.dsbt-fade-on { opacity: 1; }\n .dsbt-wrap.dsbt-show .dsbt-fade:not(.dsbt-fade-bot) { top: var(--dsbt-bar-h, 33px); }\n\n /* Filter bar — \"marked only\" toggle pinned above the scroll area while\n expanded. Collapsed, it collapses to ZERO height (not just opacity): a\n transparent-but-in-layout bar pushed every tick ~33px down, one of the\n reasons the idle capsule read as off-alignment. */\n .dsbt-filterbar {\n height: var(--dsbt-bar-h, 33px);\n padding: 8px 12px 4px; display: flex; justify-content: flex-end;\n align-items: center; border-bottom: 1px solid rgba(0, 0, 0, .05);\n box-sizing: border-box; flex: none; overflow: hidden;\n transition: opacity .2s ease, height .2s ease, padding .2s ease,\n border-color .2s ease;\n }\n .dsbt-wrap:not(.dsbt-show) .dsbt-filterbar {\n height: 0; padding-top: 0; padding-bottom: 0; opacity: 0;\n border-bottom-color: transparent;\n }\n body[data-ds-dark-theme] .dsbt-filterbar { border-bottom-color: rgba(255, 255, 255, .06); }\n .dsbt-filterbtn {\n font-size: 11px; line-height: 16px; padding: 2px 8px; border-radius: 10px;\n border: 1px solid rgba(0, 0, 0, .1); background: rgba(0, 0, 0, .03);\n color: rgba(0, 0, 0, .65); cursor: pointer; transition: all .15s ease;\n display: inline-flex; align-items: center; gap: 3px; font-family: inherit;\n }\n .dsbt-filterbtn:hover { background: rgba(0, 0, 0, .07); color: rgba(0, 0, 0, .9); }\n .dsbt-filterbtn.dsbt-filteron {\n background: rgba(245, 158, 11, .12); border-color: #f59e0b;\n color: #b45309; font-weight: 600;\n }\n body[data-ds-dark-theme] .dsbt-filterbtn {\n border-color: rgba(255, 255, 255, .12); background: rgba(255, 255, 255, .05);\n color: rgba(255, 255, 255, .7);\n }\n body[data-ds-dark-theme] .dsbt-filterbtn:hover { background: rgba(255, 255, 255, .1); color: rgba(255, 255, 255, .95); }\n body[data-ds-dark-theme] .dsbt-filterbtn.dsbt-filteron {\n background: rgba(251, 191, 36, .18); border-color: #fbbf24; color: #fbbf24;\n }\n\n /* Scroll area — rows hug the right edge; scrolls only when overfull. The\n auto-margin pseudo pair vertically centers the stack when it fits the\n capsule (short conversations), and resolves to zero free space when it\n overflows, so bottom-pinned scrolling behaves like a plain column. */\n .dsbt-page {\n padding: 15px 0 15px 24px; box-sizing: border-box;\n overscroll-behavior: contain; flex: 1 1 auto; min-height: 0;\n flex-direction: column; align-items: flex-end; display: flex;\n position: relative; width: 100%; overflow: hidden;\n }\n .dsbt-page::before, .dsbt-page::after {\n content: ''; flex: none; margin-block: auto;\n }\n .dsbt-wrap.dsbt-show .dsbt-page {\n overflow-y: auto; overflow-x: hidden;\n scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, .15) transparent;\n }\n body[data-ds-dark-theme] .dsbt-wrap.dsbt-show .dsbt-page { scrollbar-color: rgba(255, 255, 255, .25) transparent; }\n .dsbt-page::-webkit-scrollbar { width: 4px; }\n .dsbt-page::-webkit-scrollbar-track { background: transparent; }\n .dsbt-page::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, .15); border-radius: 4px; }\n body[data-ds-dark-theme] .dsbt-page::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .25); }\n\n /* Empty state (filtered list with no marks). */\n .dsbt-empty {\n font-size: 12px; line-height: 18px; padding: 16px 12px;\n color: rgba(0, 0, 0, .45); text-align: center; width: 100%; box-sizing: border-box;\n }\n body[data-ds-dark-theme] .dsbt-empty { color: rgba(255, 255, 255, .45); }\n\n /* One message row: 30px, star bookmark + tick indicator at the right,\n title fading in. */\n /* Row shell: layout + shared ink only — the interactive hit areas are the\n sibling .dsbt-jump / .dsbt-star buttons below. */\n .dsbt-item {\n height: 30px; min-height: 30px; flex-shrink: 0;\n justify-content: flex-end; align-items: center;\n width: calc(100% - 4px); margin-right: 4px;\n display: flex; box-sizing: border-box;\n color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .65));\n transition: color .15s ease;\n }\n\n /* Jump target: a real button so Enter/Space work natively without nesting\n an interactive element inside another. */\n .dsbt-jump {\n cursor: pointer; background: none; border: none; margin: 0; padding: 0;\n font: inherit; line-height: 20px; text-align: right; color: inherit;\n flex: 1 1 auto; min-width: 0; height: 100%;\n display: flex; align-items: center; justify-content: flex-end;\n }\n .dsbt-jump:focus-visible { outline: 1px solid currentColor; outline-offset: -1px; border-radius: 6px; }\n .dsbt-jump:disabled { cursor: default; opacity: .55; }\n .dsbt-jump:disabled .dsbt-title { text-decoration: line-through; text-decoration-color: currentColor; }\n .dsbt-item:hover { color: var(--dsw-alias-label-primary, rgba(0, 0, 0, .95)); }\n body[data-ds-dark-theme] .dsbt-item { color: rgba(255, 255, 255, .65); }\n body[data-ds-dark-theme] .dsbt-item:hover { color: rgba(255, 255, 255, .95); }\n .dsbt-title {\n font-size: ${TIMELINE_TITLE_FONT_PX}px; line-height: 20px; flex: 1 1 auto; min-width: 0;\n text-overflow: ellipsis; white-space: nowrap; overflow: hidden;\n margin-right: 6px; text-align: right; color: inherit;\n opacity: 0; transition: opacity .12s ease, color .15s ease;\n }\n .dsbt-show .dsbt-title { opacity: 1; }\n\n /* Star bookmark — appears on row hover while the panel is expanded; gold\n when marked. A real sibling button next to the jump target, so marking\n stays keyboard-operable without nested interactive elements. */\n .dsbt-star {\n font-size: ${TIMELINE_TITLE_FONT_PX}px; line-height: 16px; width: 18px; height: 18px;\n display: inline-flex; align-items: center; justify-content: center;\n opacity: 0; cursor: pointer; transition: all .15s ease; margin-right: 6px;\n flex-shrink: 0; color: rgba(0, 0, 0, .25); border-radius: 4px; user-select: none;\n -webkit-user-select: none;\n background: none; border: none; padding: 0; font-family: inherit;\n }\n .dsbt-star:focus-visible { outline: 1px solid currentColor; outline-offset: 1px; opacity: .9; }\n .dsbt-wrap.dsbt-show .dsbt-item:hover .dsbt-star { opacity: .75; }\n .dsbt-wrap.dsbt-show .dsbt-star:hover { opacity: 1; transform: scale(1.2); color: #d97706; }\n .dsbt-wrap.dsbt-show .dsbt-star.dsbt-staron { opacity: 1; color: #f59e0b; }\n body[data-ds-dark-theme] .dsbt-star { color: rgba(255, 255, 255, .25); }\n body[data-ds-dark-theme] .dsbt-wrap.dsbt-show .dsbt-star:hover { color: #fde047; }\n body[data-ds-dark-theme] .dsbt-wrap.dsbt-show .dsbt-star.dsbt-staron { opacity: 1; color: #fbbf24; }\n\n .dsbt-ind { flex-shrink: 0; justify-content: center; align-items: center; width: 16px; height: 20px; display: flex; }\n .dsbt-line {\n background-color: var(--dsw-alias-border-l4, rgba(0, 0, 0, .16));\n border-radius: 4px; flex-shrink: 0; width: 8px; height: 2px;\n transition: background-color .2s ease, transform .2s ease, width .2s ease, height .2s ease;\n }\n .dsbt-item:hover .dsbt-line { background-color: var(--dsw-alias-label-primary, rgba(0, 0, 0, .9)); }\n .dsbt-item.dsbt-active { color: var(--dsw-alias-state-business-primary, #4d6bfe); }\n .dsbt-item.dsbt-active .dsbt-title { color: var(--dsw-alias-state-business-primary, #4d6bfe); font-weight: 500; }\n .dsbt-item.dsbt-active .dsbt-line {\n background-color: var(--dsw-alias-state-business-primary, #4d6bfe);\n transform-origin: 50%; transform: scale(1.5);\n }\n body[data-ds-dark-theme] .dsbt-item.dsbt-active { color: var(--dsw-alias-state-business-primary, #4d6bfe); }\n body[data-ds-dark-theme] .dsbt-line { background-color: rgba(255, 255, 255, .2); }\n body[data-ds-dark-theme] .dsbt-item:hover .dsbt-line { background-color: rgba(255, 255, 255, .9); }\n\n /* Marked (key-point) rows: a wider golden tick in both themes; the active\n state keeps its brand-blue scale so reading position always wins. */\n .dsbt-item.dsbt-marked .dsbt-line { background-color: #d97706; width: 10px; height: 2.5px; }\n .dsbt-item.dsbt-marked:hover .dsbt-line { background-color: #b45309; }\n .dsbt-item.dsbt-marked.dsbt-active .dsbt-line {\n background-color: var(--dsw-alias-state-business-primary, #4d6bfe);\n transform-origin: 50%; transform: scale(1.5);\n }\n body[data-ds-dark-theme] .dsbt-item.dsbt-marked .dsbt-line { background-color: #fbbf24; width: 10px; height: 2.5px; }\n body[data-ds-dark-theme] .dsbt-item.dsbt-marked:hover .dsbt-line { background-color: #fde047; }\n\n /* ---- Unified glass integration -----------------------------------------\n Glass system on (wallpaper active + panelOpacity < 1): the rail joins\n the composer/bubble recipe — the same translucent fill token and the\n same blur/saturate/brightness chain (the glass-blur slider drives it,\n no fixed 5/16px). Placed after the dark-theme rules so equal-specificity\n overrides win in both schemes. */\n body[data-dsh-bg-glass] .dsbt-bg,\n body[data-dsh-bg-glass] .dsbt-wrap.dsbt-show {\n background-color: var(--dsw-specific-input-major);\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-dsh-bg-glass] .dsbt-fade {\n background-color: var(--dsw-specific-input-major);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .dsbt-nav, .dsbt-bg, .dsbt-wrap, .dsbt-fade, .dsbt-title, .dsbt-line, .dsbt-jump, .dsbt-star, .dsbt-filterbtn, .dsbt-filterbar { transition: none; }\n }\n`\n","/**\n * Conversation timeline rail — DeepSeek web ScrollNav rebuilt on this\n * plugin's frosted-glass system (styles: ./timeline-css.ts).\n *\n * Collapsed: a fixed 34px-wide frosted capsule at the right edge, vertically\n * centered, one tick per user message (active tick scaled + brand blue,\n * key-point bookmarks golden). Hovering expands the SAME box into a frosted\n * panel listing every user question (title fades in; >8 messages widen to\n * 260px, otherwise the width fits the longest title). Both states share one\n * computed outer height, so expanding never jumps, and 32px fade veils mask\n * the clipped edge on each side while that side actually scrolls.\n *\n * Enhancements ported from dsh-chat-timeline v0.1.4 (MIT):\n * - Key-point bookmarks: star a question in the expanded panel, persisted per\n * session in localStorage; marked rows show a golden tick in the capsule\n * and a \"marked only\" filter narrows the list.\n * - Jump stabilization: the reading-position tracker freezes while a jump\n * glide animates (cleared 150ms after the last scroll event, plus a 2s\n * fallback), so the panel cannot jitter mid-jump. The glide itself is a\n * rAF ease-in-out on the conversation scrollport: a ~26px bottom-follow\n * detach first settles the official at-bottom flag outside its 25px zone\n * (so a tip-moved re-render cannot yank the slide back to the floor — the\n * earlier instant+smooth double pass kept the jump working but made the\n * slide invisible), and wheel/touch/keyboard input cancels it so the\n * reader takes over instantly.\n * - Rewind integration: messages withdrawn by a rewind command node are\n * filtered out of the rail.\n *\n * Data source, fastest first: the host-side `bgTimeline` session projection\n * (src/projection.ts — a complete enumeration of the session's user messages\n * delivered through the projection baseline/push channel) -> the loaded chat\n * node window. The conversation's own lazy loading is never driven from here;\n * clicking a row pages older history on demand, scrolls the conversation to\n * the target row, and keeps the rail's own active row visible.\n *\n * Collapsed idle strategy (official ScrollNav behavior): the tick stack is\n * pinned to the BOTTOM inner edge — the newest question's tick always sits at\n * the bottom of the capsule, older ticks clip away at the top. Hovering\n * expands the SAME box and scrolls the active row into view instead.\n *\n * The rail reads the shared settings transport: it renders unless\n * `timeline: false`. Its glass paints are self-contained official values\n * (see timeline-css.ts), independent of the plugin's token overrides.\n */\n\nimport react from 'react'\nimport reactDom from 'react-dom'\nimport { settingsClient } from './settings-client.ts'\nimport { TIMELINE_PROJECTION_KEY } from '../settings.ts'\nimport { injectTimelineCss, TIMELINE_TITLE_FONT_PX } from './timeline-css.ts'\n\n/** Shared constant lives in settings.ts; re-exported for consumers of this module. */\nexport { TIMELINE_PROJECTION_KEY }\n\n// Inject the stylesheet once at module evaluation (the loader removes\n// plugin-owned tags on unload; re-evaluation dedupes on the tag id).\nif (typeof document !== 'undefined') injectTimelineCss()\n\n/** Structural view of one chat node this rail consumes. */\ninterface ChatNodeLike {\n kind?: string\n key?: string\n anchorSeq?: number\n data?: {\n time?: number\n content?: unknown\n name?: unknown\n seq?: unknown\n args?: unknown\n outcome?: { kind?: unknown; sourceEventSeq?: unknown; text?: unknown }\n }\n}\n\n/** One normalized user-message entry in the rail. */\nexport interface TimelineMessage {\n seq: number\n time: number\n text: string\n key?: string\n}\n\n/** Session handle face the rail needs off the runtime sessions service. */\nexport interface TimelineSessionHandle {\n subscribe(listener: () => void): () => void\n getSnapshot(): unknown\n loadOlder(): Promise<unknown>\n}\n\n/** Sessions service face (narrowed to what jump/data collection touches). */\nexport interface TimelineSessionsService {\n binding(sessionId: string): { session: TimelineSessionHandle } | undefined\n}\n\n/** Component props delivered by the dock slot registration. */\nexport interface TimelineRailProps {\n sessionId?: string\n sessionsService?: TimelineSessionsService\n /** Framework projection reader (SessionStandardProps seat; undefined-capable). */\n useProjection?: (key: string) => unknown\n t: (key: string) => string\n}\n\nconst delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\n/** Extract preview text from a user message's ContentBlock list. */\nfunction userTextOf(content: unknown): string {\n if (!Array.isArray(content)) return ''\n let out = ''\n for (const block of content) {\n if (block !== null && typeof block === 'object' && (block as { type?: unknown }).type === 'text') {\n const text = (block as { text?: unknown }).text\n if (typeof text === 'string') out += text\n }\n }\n return out.trim().slice(0, 80)\n}\n\n/* ---- Key-point bookmarks (ported from dsh-chat-timeline v0.1.4) --------- */\n\n/**\n * Stable bookmark key for one entry: the durable message key, falling back to\n * the anchor seq — so marks survive history reloads. Entries with neither are\n * not markable (empty key).\n * @param m - the entry (or any shaped object).\n */\nexport function markKeyOf(m: unknown): string {\n if (m === null || typeof m !== 'object') return ''\n const rec = m as { key?: unknown; seq?: unknown }\n if (typeof rec.key === 'string' && rec.key !== '') return `key:${rec.key}`\n return typeof rec.seq === 'number' ? `seq:${rec.seq}` : ''\n}\n\n/** localStorage key prefix for the per-session marked-key lists. */\nexport const MARKS_STORAGE_PREFIX = 'dsbt_marks_'\n\n/**\n * Read the marked keys for one session from localStorage.\n * @param sessionId - the session the marks belong to.\n */\nexport function readMarks(sessionId: string | undefined): string[] {\n if (typeof window === 'undefined' || !sessionId) return []\n try {\n const raw = window.localStorage.getItem(MARKS_STORAGE_PREFIX + sessionId)\n if (!raw) return []\n const parsed: unknown = JSON.parse(raw)\n return Array.isArray(parsed) ? parsed.filter((k): k is string => typeof k === 'string') : []\n } catch {\n return []\n }\n}\n\n/**\n * Persist the marked keys for one session (removes the key when empty).\n * @param sessionId - the session the marks belong to.\n * @param marks - the marked-key list.\n */\nexport function writeMarks(sessionId: string | undefined, marks: string[]): void {\n if (typeof window === 'undefined' || !sessionId) return\n try {\n if (!Array.isArray(marks) || marks.length === 0) window.localStorage.removeItem(MARKS_STORAGE_PREFIX + sessionId)\n else window.localStorage.setItem(MARKS_STORAGE_PREFIX + sessionId, JSON.stringify(marks))\n } catch { /* storage unavailable — marks stay in-memory */ }\n}\n\n/* ---- Rewind integration (ported from dsh-chat-timeline v0.1.4) ---------- */\n\n/** Parse digits into a finite number (undefined otherwise). */\nfunction parseSeqDigits(digits: string | undefined): number | undefined {\n if (digits === undefined) return undefined\n const n = Number.parseInt(digits, 10)\n return Number.isFinite(n) ? n : undefined\n}\n\n/** Extract a seq from a string-ish field ('@42', 'seq 42', '#42', bare 42). */\nfunction seqFromStringish(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value !== 'string') return undefined\n const m = /(?:@|seq\\s*|#)?(\\d+)/i.exec(value)\n return m === null ? undefined : parseSeqDigits(m[1])\n}\n\n/**\n * Extract the rewind target seq from a rewind outcome text. Multi-pattern\n * (ported from dsh-chat-timeline v0.1.5): an explicit `seq 42` declaration\n * wins, then `#42`, then the broad english/chinese verb forms — the old\n * single \"rewound\" regex silently missed real success texts like \"已撤回\n * seq 42\" / \"Withdrawn seq 42\" (upstream issue #6).\n * @param text - the command outcome text.\n */\nexport function rewindTargetOfOutcome(text: unknown): number | undefined {\n if (typeof text !== 'string') return undefined\n const seqMatch = /seq\\s*(\\d+)/i.exec(text)\n if (seqMatch !== null) return parseSeqDigits(seqMatch[1])\n const hashMatch = /#(\\d+)/.exec(text)\n if (hashMatch !== null) return parseSeqDigits(hashMatch[1])\n const broadMatch = /(?:rewound|withdrawn|已撤回).*?(?:target\\s+)?(\\d+)/i.exec(text)\n if (broadMatch !== null) return parseSeqDigits(broadMatch[1])\n return undefined\n}\n\n/**\n * Resolve the target of a rewind command: structured fields first\n * (outcome.targetSeq / outcome.target / args.targetSeq / args.target /\n * args.seq / args.raw / args[0]), then the outcome text — mirrors upstream\n * v0.1.5 so locale-independent producers keep parsing without text\n * heuristics (the args.seq guard skips the command's own seq).\n * @param command - the command payload.\n */\nexport function rewindTargetOfCommand(command: unknown): number | undefined {\n if (command === null || typeof command !== 'object') return undefined\n const rec = command as { outcome?: unknown; args?: unknown; seq?: unknown }\n const outcome = rec.outcome\n if (outcome !== null && typeof outcome === 'object') {\n const o = outcome as { targetSeq?: unknown; target?: unknown }\n if (typeof o.targetSeq === 'number' && Number.isFinite(o.targetSeq)) return o.targetSeq\n if (typeof o.target === 'number' && Number.isFinite(o.target)) return o.target\n }\n const args = rec.args\n if (args !== null && typeof args === 'object') {\n const record = args as { targetSeq?: unknown; target?: unknown; seq?: unknown; raw?: unknown; '0'?: unknown }\n if (typeof record.targetSeq === 'number' && Number.isFinite(record.targetSeq)) return record.targetSeq\n if (typeof record.target === 'number' && Number.isFinite(record.target)) return record.target\n if (typeof record.seq === 'number' && Number.isFinite(record.seq) && record.seq !== rec.seq) return record.seq\n const rawish = record.raw ?? record['0'] ?? (Array.isArray(args) ? args[0] : undefined)\n if (rawish !== undefined) {\n const n = seqFromStringish(rawish)\n if (n !== undefined) return n\n }\n if (typeof record.target === 'string') {\n const n = seqFromStringish(record.target)\n if (n !== undefined) return n\n }\n }\n if (outcome !== null && typeof outcome === 'object') {\n const text = (outcome as { text?: unknown }).text\n if (typeof text === 'string') return rewindTargetOfOutcome(text)\n }\n return undefined\n}\n\n/**\n * Is this rewind command a preview/dry-run (marks the target hidden without\n * an outcome)?\n * @param command - the command payload.\n */\nfunction isRewindPreviewCommand(command: { args?: unknown }): boolean {\n if (command.args === null || typeof command.args !== 'object') return false\n const args = command.args as { preview?: unknown; dryRun?: unknown }\n return args.preview === true || args.dryRun === true\n}\n\n/**\n * Collect the seq numbers a rewind hid: preview commands hide their target\n * directly; successful commands hide their own row plus every node anchored\n * inside the rewound span [target, sourceEventSeq].\n * @param chat - a chat snapshot (loosely typed on purpose).\n */\nexport function hiddenSeqsOfChat(chat: unknown): Set<number> {\n const hidden = rewindHiddenSeqsOfChat(chat)\n for (const seq of rewindMarkedSeqsOfChat(chat)) hidden.add(seq)\n const spans = rewindSpansOfChat(chat)\n if (spans.length === 0) return hidden\n const nodes = (chat as { nodes?: Map<string, ChatNodeLike> }).nodes\n if (nodes === undefined || typeof nodes.values !== 'function') return hidden\n for (const node of nodes.values()) {\n if (node === null || typeof node !== 'object') continue\n const anchor = typeof node.anchorSeq === 'number' ? node.anchorSeq : undefined\n if (anchor === undefined) continue\n if (spans.some((span) => anchor >= span.start && anchor <= span.end)) hidden.add(anchor)\n }\n return hidden\n}\n\n/**\n * Seq numbers of nodes carrying a rewind-hidden marker (upstream v0.1.5):\n * host/plugin producers can tag the node record itself (rewindHidden), its\n * data payload, or the payload's attributes map.\n * @param chat - a chat snapshot (loosely typed on purpose).\n */\nexport function rewindMarkedSeqsOfChat(chat: unknown): Set<number> {\n const marked = new Set<number>()\n const nodes = (chat as { nodes?: Map<string, ChatNodeLike> }).nodes\n if (nodes === undefined || typeof nodes.values !== 'function') return marked\n for (const node of nodes.values()) {\n if (node === null || typeof node !== 'object') continue\n const data = node.data\n if (data === null || typeof data !== 'object') continue\n const record = data as { attributes?: unknown; [k: string]: unknown }\n const attrs = record.attributes\n if (attrs !== null && typeof attrs === 'object'\n && (attrs as Record<string, unknown>)['data-dsh-rewind-hidden'] === true) {\n if (typeof node.anchorSeq === 'number') marked.add(node.anchorSeq)\n continue\n }\n if (record['data-dsh-rewind-hidden'] === true\n || (node as { rewindHidden?: unknown }).rewindHidden === true) {\n if (typeof node.anchorSeq === 'number') marked.add(node.anchorSeq)\n }\n }\n return marked\n}\n\n/**\n * Seq numbers a rewind hid directly: successful commands hide their own row;\n * preview commands hide their target.\n * @param chat - a chat snapshot (loosely typed on purpose).\n */\nexport function rewindHiddenSeqsOfChat(chat: unknown): Set<number> {\n const hidden = new Set<number>()\n if (chat === null || typeof chat !== 'object') return hidden\n const nodes = (chat as { nodes?: Map<string, ChatNodeLike> }).nodes\n if (nodes === undefined || typeof nodes.values !== 'function') return hidden\n for (const node of nodes.values()) {\n if (node === null || typeof node !== 'object') continue\n if (node.kind !== 'command') continue\n const command = node.data\n if (command === null || typeof command !== 'object') continue\n if (command.name !== 'rewind') continue\n if (isRewindPreviewCommand(command)) {\n if (typeof command.seq === 'number') hidden.add(command.seq)\n continue\n }\n const outcome = command.outcome\n if (outcome === null || typeof outcome !== 'object' || outcome.kind !== 'success') continue\n if (typeof command.seq === 'number') hidden.add(command.seq)\n }\n return hidden\n}\n\n/**\n * The [target, sourceEventSeq] spans successful rewind commands cut — the\n * range form of hiding, needed to filter sources that are NOT chat nodes\n * (the host projection) by plain seq membership.\n * @param chat - a chat snapshot (loosely typed on purpose).\n */\nexport function rewindSpansOfChat(chat: unknown): { start: number; end: number }[] {\n const spans: { start: number; end: number }[] = []\n if (chat === null || typeof chat !== 'object') return spans\n const nodes = (chat as { nodes?: Map<string, ChatNodeLike> }).nodes\n if (nodes === undefined || typeof nodes.values !== 'function') return spans\n for (const node of nodes.values()) {\n if (node === null || typeof node !== 'object') continue\n if (node.kind !== 'command') continue\n const command = node.data\n if (command === null || typeof command !== 'object') continue\n if (command.name !== 'rewind') continue\n if (isRewindPreviewCommand(command)) continue\n const outcome = command.outcome\n if (outcome === null || typeof outcome !== 'object' || outcome.kind !== 'success') continue\n const marker = outcome.sourceEventSeq\n if (typeof marker !== 'number') continue\n const target = rewindTargetOfCommand(command)\n if (target !== undefined) spans.push({ start: target, end: marker })\n }\n return spans\n}\n\n/**\n * Enumerate user messages from the loaded chat-node snapshot (the client-side\n * collector; sorted by anchor sequence, rewind-hidden entries dropped).\n * @param snapshot - a session snapshot (loosely typed on purpose).\n */\nexport function collectMessages(snapshot: unknown): TimelineMessage[] {\n const chat = (snapshot as { chat?: { nodes?: Map<string, ChatNodeLike> } } | undefined)?.chat\n const nodes = chat?.nodes\n if (nodes === undefined || typeof nodes.values !== 'function') return []\n const hidden = hiddenSeqsOfChat(chat)\n const out: TimelineMessage[] = []\n for (const node of nodes.values()) {\n if (node === null || typeof node !== 'object') continue\n if (node.kind !== 'user') continue\n const data = node.data\n if (data === null || typeof data !== 'object' || typeof data.time !== 'number' || !Array.isArray(data.content)) continue\n const key = typeof node.key === 'string' ? node.key : undefined\n if (key === undefined) continue\n const seq = typeof node.anchorSeq === 'number' ? node.anchorSeq : 0\n if (hidden.has(seq)) continue\n out.push({ seq, time: data.time, text: userTextOf(data.content), key })\n }\n out.sort((a, b) => a.seq - b.seq)\n return out\n}\n\n/**\n * Count the user messages the loaded window holds WITHOUT collecting or\n * sorting them — the fast gate railMessages uses to skip the merge path on\n * the streaming hot path (see there).\n * @param chat - the loaded chat snapshot.\n */\nfunction countWindowUserMessages(chat: unknown): number {\n const nodes = (chat as { nodes?: Map<string, ChatNodeLike> } | undefined)?.nodes\n if (nodes === undefined || typeof nodes.values !== 'function') return 0\n let count = 0\n for (const node of nodes.values()) {\n if (node !== null && typeof node === 'object' && node.kind === 'user') count += 1\n }\n return count\n}\n\n/** Resolve the DOM anchor key of one entry (direct key form only). */\nexport function resolveAnchorKey(m: TimelineMessage): string | undefined {\n return typeof m.key === 'string' && m.key !== '' ? m.key : undefined\n}\n\n/* ---- Host projection source --------------------------------------------- */\n\n/** The user-message node definition kind in ui-conversation. */\nconst INPUT_MESSAGE_KIND = 'input-message'\n\n/**\n * Rebuild a chat row's DOM anchor key from the durable message id — the same\n * engine formula as conversationContextKey(kind, id) for the input-message\n * definition (`13:input-message<id>`), so projected entries can jump even\n * when their chat node is not loaded yet.\n * @param id - the durable message id carried by the projection entry.\n */\nexport function inputAnchorKeyOf(id: string): string {\n return `${INPUT_MESSAGE_KIND.length}:${INPUT_MESSAGE_KIND}${id}`\n}\n\n/**\n * Validate one projected wire value into rail entries (defensive: the value\n * crosses the wire and may be absent, partial, or stale).\n * @param value - the raw useProjection(TIMELINE_PROJECTION_KEY) snapshot.\n */\nexport function normalizeProjectedTimeline(value: unknown): TimelineMessage[] {\n if (value === null || typeof value !== 'object') return []\n const raw = (value as { messages?: unknown }).messages\n if (!Array.isArray(raw)) return []\n const seen = new Set<number>()\n const out: TimelineMessage[] = []\n for (const item of raw) {\n if (item === null || typeof item !== 'object') continue\n const rec = item as { seq?: unknown; time?: unknown; text?: unknown; id?: unknown }\n if (typeof rec.seq !== 'number' || !Number.isFinite(rec.seq)) continue\n if (seen.has(rec.seq)) continue\n seen.add(rec.seq)\n out.push({\n seq: rec.seq,\n time: typeof rec.time === 'number' ? rec.time : 0,\n text: typeof rec.text === 'string' ? rec.text.slice(0, 80) : '',\n ...(typeof rec.id === 'string' && rec.id !== '' ? { key: inputAnchorKeyOf(rec.id) } : {}),\n })\n }\n out.sort((a, b) => a.seq - b.seq)\n return out\n}\n\n/**\n * Rail messages: the host projection (whole-session index) MERGED with the\n * loaded chat-node window. The projection normally wins outright, but a\n * projection that lost its baseline (e.g. the plugin was hot-reloaded after\n * the session's tail page seeded the client store) can miss early entries —\n * the window fills those gaps whenever it holds them, and window entries\n * lend their real anchor key to projected entries whose durable id was\n * absent (early events without one). Locally-known rewind hiding applies to\n * the merged list so a cut lands instantly, before the next projection push.\n * @param snapshot - the session conversation snapshot (node window).\n * @param projected - the raw bgTimeline projection value.\n */\nexport function railMessages(snapshot: unknown, projected: unknown): TimelineMessage[] {\n const projectedMessages = normalizeProjectedTimeline(projected)\n const chat = (snapshot as { chat?: { nodes?: Map<string, ChatNodeLike> } } | undefined)?.chat\n const hidden = rewindHiddenSeqsOfChat(chat)\n const marked = rewindMarkedSeqsOfChat(chat)\n const spans = rewindSpansOfChat(chat)\n const isHidden = (seq: number): boolean =>\n hidden.has(seq) || marked.has(seq) || spans.some((span) => seq >= span.start && seq <= span.end)\n\n if (projectedMessages.length === 0) return collectMessages(snapshot)\n // Fast gate: the loaded window is a subset of the whole log the projection\n // indexes, so when the window holds no MORE user messages than the\n // projection it cannot fill a gap. Skipping the collect+sort keeps the\n // streaming hot path (one render per token) allocation-free — the merged\n // path below runs only for a degraded projection (hot reload losing the\n // baseline) or rewinds cutting projection entries the window still holds.\n const windowUserCount = countWindowUserMessages(chat)\n // A keyless projected entry (early events without a durable id) can borrow\n // its anchor key from the window, so that case must reach the merge too.\n const projectionLacksKeys = projectedMessages.some((m) => m.key === undefined)\n if (windowUserCount <= projectedMessages.length && !projectionLacksKeys) {\n return marked.size === 0 && hidden.size === 0 && spans.length === 0\n ? projectedMessages\n : projectedMessages.filter((m) => !isHidden(m.seq))\n }\n const windowMessages = collectMessages(snapshot)\n\n const bySeq = new Map<number, TimelineMessage>()\n for (const m of projectedMessages) {\n if (!isHidden(m.seq)) bySeq.set(m.seq, m)\n }\n for (const m of windowMessages) {\n if (isHidden(m.seq)) continue\n const existing = bySeq.get(m.seq)\n if (existing === undefined) {\n bySeq.set(m.seq, m)\n continue\n }\n // A projected entry missing its anchor key (early events without a\n // durable id) borrows the window entry's real key so the row can jump.\n if (existing.key === undefined && m.key !== undefined) {\n bySeq.set(m.seq, {\n ...existing,\n key: m.key,\n ...(existing.text === '' && m.text !== '' ? { text: m.text } : {}),\n })\n }\n }\n return [...bySeq.values()].sort((a, b) => a.seq - b.seq)\n}\n\n/**\n * Outer rail height for a message count: rows + breathing room, clamped so\n * both states always share the exact same height (never jumps on expand).\n * @param count - number of user messages.\n */\nexport function railHeightFor(count: number): number {\n return Math.max(140, Math.min(300, count * 30 + 38))\n}\n\n/** Longest-title width estimate bounds. */\nconst RAIL_MIN_WIDTH = 96\nconst RAIL_MAX_WIDTH = 260\n\n/**\n * Measure the expanded-panel width for the given titles (canvas text metrics;\n * falls back to the max width when measurement is unavailable). Accounts for\n * the star column so a marked row never clips its tick.\n * @param texts - the message previews.\n */\nexport function railWidthFor(texts: string[]): number {\n if (texts.length > 8) return RAIL_MAX_WIDTH\n let widest = 0\n if (typeof document !== 'undefined') {\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n if (ctx) {\n ctx.font = `${TIMELINE_TITLE_FONT_PX}px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif`\n for (const t of texts) {\n const w = ctx.measureText(t === '' ? '…' : t).width\n if (w > widest) widest = w\n }\n }\n }\n // Title + title gap + star column + indicator + item gutters + border/padding.\n return Math.round(Math.max(RAIL_MIN_WIDTH, Math.min(RAIL_MAX_WIDTH, widest + 6 + 24 + 16 + 12)))\n}\n\n/** How long one jump may keep paging older history before giving up. */\nconst JUMP_PAGE_DEADLINE_MS = 30000\n/** How long to wait for the chat view to commit the target row. */\nconst JUMP_ROW_WAIT_MS = 3000\n/** How long the jump-stabilization lock may stay engaged without settling. */\nconst JUMP_LOCK_FALLBACK_MS = 2000\n\n/**\n * Poll the conversation DOM until the target row is committed. The session\n * store updates BEFORE React renders the prepended page, so the old one-shot\n * query right after loadOlder() resolved could miss the row and silently\n * give up — the reported \"click does nothing until the conversation is\n * nudged (scroll/click)\" behavior.\n * @param key - chat anchor key to look for.\n * @param timeoutMs - polling budget.\n */\nexport async function waitForChatRow(key: string, timeoutMs: number): Promise<HTMLElement | null> {\n // Multi-column layouts can mount more than one conversation scrollport;\n // the target row may live in ANY of them (the first match is no longer\n // assumed to own every session's rows).\n const scrollports = Array.from(document.querySelectorAll<HTMLElement>('[data-conversation-scroll]'))\n if (scrollports.length === 0) return null\n const selector = `[data-chat-anchor-key=\"${CSS.escape(key)}\"]`\n const deadline = Date.now() + timeoutMs\n for (;;) {\n for (const scrollport of scrollports) {\n const row = scrollport.querySelector<HTMLElement>(selector)\n if (row !== null) return row\n }\n if (Date.now() >= deadline) return null\n await delay(40)\n }\n}\n\n/** Official conversation follow threshold: the view keeps bottom-following while within this distance of the floor (dsh-client-ui-conversation, 25px). */\nconst CONVERSATION_FOLLOW_ZONE_PX = 25\n\n/** Distances under this snap instead of animating. */\nconst MIN_GLIDE_DISTANCE_PX = 8\n\n/** Glide duration floor / cap — a far-boundary rail click stays a tangible slide. */\nconst MIN_GLIDE_MS = 280\nconst MAX_GLIDE_MS = 900\n\n/** Maximum scrollable offset of a conversation scrollport. */\nexport function scrollFloorOf(el: HTMLElement): number {\n return Math.max(0, el.scrollHeight - el.clientHeight)\n}\n\n/**\n * Destination scrollTop that centers `row` inside `el` — the same geometry\n * as scrollIntoView({ block: 'center' }), computed directly so the glide owns\n * the scroll position instead of the browser.\n * @param el - the conversation scrollport.\n * @param row - the target chat row.\n */\nexport function centeredScrollTopFor(el: HTMLElement, row: HTMLElement): number {\n const floor = scrollFloorOf(el)\n const target = el.scrollTop\n + (row.getBoundingClientRect().top - el.getBoundingClientRect().top)\n - (el.clientHeight - row.offsetHeight) / 2\n if (!Number.isFinite(target)) return el.scrollTop\n return Math.max(0, Math.min(floor, target))\n}\n\n/**\n * Glide duration for a distance: longer slides take longer, bounded to\n * [MIN_GLIDE_MS, MAX_GLIDE_MS] (a deep-history rail click reads as a\n * deliberate glide, not a sluggish race).\n * @param distance - px to travel.\n */\nexport function glideDurationFor(distance: number): number {\n if (!Number.isFinite(distance)) return MIN_GLIDE_MS\n return Math.round(Math.max(MIN_GLIDE_MS, Math.min(MAX_GLIDE_MS, 240 + Math.abs(distance) * 0.14)))\n}\n\n/**\n * Detach the official bottom-follow state when the reader currently sits\n * inside its follow zone: assign the scroll position just outside the 25px\n * boundary. That one synchronous scroll event settles at-bottom to false\n * (moved-by-reader + outside the zone) so a tip-moved re-render cannot yank\n * the glide back to the floor. No-op when already above the zone or when\n * the assignment cannot move (content shorter than the viewport).\n * @param el - the conversation scrollport.\n */\nexport function detachBottomFollow(el: HTMLElement): boolean {\n const floor = scrollFloorOf(el)\n // Only the bottom zone can still hold the follow flag: anywhere above it\n // the official scroll handler already settled at-bottom to false, and\n // dragging the viewport down to the zone would be a visible jump.\n if (el.scrollTop < floor - CONVERSATION_FOLLOW_ZONE_PX) return false\n const detached = Math.max(0, floor - CONVERSATION_FOLLOW_ZONE_PX - 1)\n if (Math.abs(el.scrollTop - detached) < 1) return false\n el.scrollTop = detached\n return true\n}\n\n/**\n * Wait until a detach assignment's scroll event has been dispatched AND\n * consumed by the official scroll handler: scroll events fire on a later\n * frame than the assignment, and a paging commit's layout effect runs\n * before that dispatch — it would re-run the follow logic while the\n * at-bottom flag is still true and drag the viewport to the growing floor,\n * undoing the detach (two rAFs + a macrotask bracket the dispatch window).\n */\nexport function settleScrollEvents(): Promise<void> {\n if (typeof requestAnimationFrame !== 'function') return Promise.resolve()\n return new Promise((resolve) => {\n requestAnimationFrame(() => requestAnimationFrame(() => window.setTimeout(resolve, 0)))\n })\n}\n\n/**\n * Animate el.scrollTop toward target with an ease-in-out cubic profile.\n * Wheel / touch / keyboard-scroll input cancels the glide so the reader\n * takes over immediately. Resolves true when the glide completed, false\n * when the reader cancelled it.\n * @param el - the conversation scrollport.\n * @param target - destination scrollTop.\n * @param durationMs - animation budget.\n */\nexport function animateScrollTop(el: HTMLElement, target: number, durationMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n const start = el.scrollTop\n const delta = target - start\n let done = false\n let frame = 0\n const finish = (completed: boolean): void => {\n if (done) return\n done = true\n el.removeEventListener('wheel', onWheel)\n el.removeEventListener('touchstart', onTouch)\n window.removeEventListener('keydown', onKey)\n if (frame !== 0) cancelAnimationFrame(frame)\n resolve(completed)\n }\n const onWheel = (): void => finish(false)\n const onTouch = (): void => finish(false)\n const onKey = (e: KeyboardEvent): void => {\n if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(e.key)) finish(false)\n }\n el.addEventListener('wheel', onWheel, { passive: true })\n el.addEventListener('touchstart', onTouch, { passive: true })\n window.addEventListener('keydown', onKey)\n if (Math.abs(delta) < 0.5 || durationMs <= 0) {\n el.scrollTop = target\n finish(true)\n return\n }\n const t0 = performance.now()\n const ease = (x: number): number => x < 0.5 ? 4 * x * x * x : 1 - (-2 * x + 2) ** 3 / 2\n const step = (now: number): void => {\n if (done) return\n const t = Math.min(1, (now - t0) / durationMs)\n el.scrollTop = start + delta * ease(t)\n if (t < 1) frame = requestAnimationFrame(step)\n else finish(true)\n }\n frame = requestAnimationFrame(step)\n })\n}\n\n/**\n * Ensure the target node is loaded, wait for the chat view to commit its row\n * into the DOM, then glide it into view. Paging uses a time budget instead\n * of the old fixed 120-iteration guard (each in-flight page burned 50ms of\n * that guard, so slow history could exhaust it and silently fail).\n */\nexport async function jumpToMessage(sessionsService: TimelineSessionsService, sessionId: string, key: string, rowWaitMs: number = JUMP_ROW_WAIT_MS): Promise<boolean> {\n const session = sessionsService.binding(sessionId)?.session\n if (session === undefined) return false\n // Detach the official bottom-follow flag BEFORE paging: while a multi-page\n // loadOlder is in flight, every prepend commit re-runs the follow layout\n // effect (tip-moved + at-bottom) and toBottom() drags the viewport to the\n // (growing) floor — the rail click would read as \"surf to the newest floor,\n // then slide back to the target\". One synchronous assignment just outside\n // the 25px zone settles the flag for the whole paging phase, but the flag\n // only flips when the assignment's scroll event reaches the official\n // handler, which happens a frame later — so the paging phase waits for it\n // first. The post-wait detach below stays idempotent for the already-loaded\n // case. Multi-column layouts cannot pin the target session's scrollport\n // before the row exists, so every mounted scrollport is detached (a 26px\n // nudge elsewhere is the whole cost; the reader's next scroll re-engages\n // follow there).\n let detachedAny = false\n for (const el of Array.from(document.querySelectorAll<HTMLElement>('[data-conversation-scroll]'))) {\n if (detachBottomFollow(el)) {\n // The official scroll handler flips its at-bottom flag on a scroll\n // event, and the real one only dispatches on a later frame — a paging\n // commit's layout effect runs before it and re-runs the follow logic.\n // A synthetic event notifies the handler synchronously, so the flag\n // settles before any prepend commit can observe it.\n el.dispatchEvent(new Event('scroll'))\n detachedAny = true\n }\n }\n if (detachedAny) await settleScrollEvents()\n const deadline = Date.now() + JUMP_PAGE_DEADLINE_MS\n for (;;) {\n const snapshot = session.getSnapshot() as {\n chat?: { nodes?: Map<string, unknown> }\n hasMore?: boolean\n loadingOlder?: boolean\n openState?: unknown\n }\n if (snapshot?.chat?.nodes?.get(key) !== undefined) break\n if (snapshot?.hasMore !== true) break\n if (Date.now() >= deadline) break\n if (snapshot.loadingOlder === true) {\n await delay(50)\n continue\n }\n // A session that is not open yet cannot page; give it a moment instead\n // of spinning the full budget on no-op calls.\n if (snapshot.openState !== undefined && snapshot.openState !== 'open') {\n await delay(100)\n continue\n }\n // A failing page must not reject the whole jump (or leave the rail lock\n // to the fallback timer): log and stop paging — the row may still render\n // from an earlier page, so waitForChatRow below gets its chance anyway.\n try {\n await session.loadOlder()\n } catch (error) {\n console.warn('[deepseek-harness-background] timeline jump paging failed:', error)\n break\n }\n }\n const row = await waitForChatRow(key, rowWaitMs)\n if (row === null) {\n console.warn(`[deepseek-harness-background] timeline jump target never rendered: ${key}`)\n return false\n }\n const reducedMotion = typeof window.matchMedia === 'function'\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches\n const scroller = row.closest<HTMLElement>('[data-conversation-scroll]')\n // No addressable scrollport (defensive): keep the platform path — the\n // instant placement flips the official bottom-follow state through its\n // scroll event, the smooth pass afterwards is a no-op when already\n // centered (and a graceful glide when the row moved since).\n if (scroller === null) {\n row.scrollIntoView({ behavior: 'auto', block: 'center' })\n if (!reducedMotion) row.scrollIntoView({ behavior: 'smooth', block: 'center' })\n return true\n }\n const target = centeredScrollTopFor(scroller, row)\n // Reduced motion or a hairline distance: one placement (its scroll event\n // still flips the bottom-follow state when it lands outside the zone).\n if (reducedMotion || Math.abs(target - scroller.scrollTop) < MIN_GLIDE_DISTANCE_PX) {\n scroller.scrollTop = target\n return true\n }\n // Bottom-follow detach BEFORE the glide: a pure slide starting at the floor\n // leaves the official at-bottom flag true for its first frames, so a\n // tip-moved re-render (turn end / commit) yanks it back to the floor.\n // The previous fix scrolled instantly to the target and ran a zero-distance\n // smooth pass — functional, but the visible slide was gone. The detach moves\n // at most ~26px to just outside the follow zone (reads as a nudge, not a\n // jump); the flag settles once the dispatch frame passes, so the glide\n // waits for it first.\n if (detachBottomFollow(scroller)) {\n // Synchronous flag flip, then the glide (see the pre-paging detach above).\n scroller.dispatchEvent(new Event('scroll'))\n await settleScrollEvents()\n }\n const completed = await animateScrollTop(scroller, target, glideDurationFor(Math.abs(target - scroller.scrollTop)))\n return completed\n}\n\n/**\n * Scroll only the rail itself so the active row stays visible (min distance).\n * offsetTop math instead of getBoundingClientRect: stable while the panel's\n * own smooth scroll animates (no feedback loop with live rects).\n */\nfunction scrollTimelineItemIntoView(page: HTMLElement, item: HTMLElement): void {\n const pageTop = page.scrollTop\n const pageBottom = pageTop + page.clientHeight\n const itemTop = item.offsetTop\n const itemBottom = itemTop + item.offsetHeight\n if (itemTop < pageTop) page.scrollTop = Math.max(0, itemTop - 10)\n else if (itemBottom > pageBottom) page.scrollTop = itemBottom - page.clientHeight + 10\n}\n\n/** Is another timeline rail already mounted (e.g. dsh-chat-timeline)? */\nfunction otherRailPresent(): boolean {\n return document.querySelector('.dsct_nav') !== null\n}\n\n/**\n * The timeline rail component. Portaled to body; registered into the\n * conversation input dock purely to bind per-session lifecycle.\n */\nexport function TimelineRail(props: TimelineRailProps): react.ReactElement | null {\n const { sessionId, sessionsService, useProjection, t } = props\n\n // Shared settings: the rail hides while timeline === false. Read through a\n // hook like every other store here, but the gate itself must sit with the\n // FINAL early return below — an early return between hooks would crash\n // React (\"rendered fewer hooks\") the moment the toggle flips mid-session.\n const settings = react.useSyncExternalStore(settingsClient.subscribe, settingsClient.getSnapshot)\n // Hide only while the persisted toggle is UNKNOWN (no wrong-state flash for\n // timeline:false users); once loading settles the default-on rail renders\n // even if the section errored.\n const timelineUnknown = settings.status === 'loading'\n const timelineDisabled = settings.value?.timeline === false\n\n const session = sessionId !== undefined && sessionsService !== undefined ? sessionsService.binding(sessionId)?.session : undefined\n // SessionFace methods are prototype methods reading \"this\"; React invokes\n // store callbacks as bare functions, so keep the receiver via closures\n // (same shape as the host's own wiring, e.g. ModelSelect). Identity stays\n // stable per session so uSES does not resubscribe on every render.\n const subscribeSession = react.useCallback(\n (listener: () => void) => session === undefined ? () => {} : session.subscribe(listener),\n [session],\n )\n const snapshotSession = react.useCallback(\n () => session === undefined ? undefined : session.getSnapshot(),\n [session],\n )\n const nodeSnapshot = react.useSyncExternalStore(subscribeSession, snapshotSession)\n\n // Host projection first: the full-session user-message index (baseline on\n // session open, live push frames after). Undefined until the framework\n // seat exists or the deployment lacks the projection registry — then the\n // loaded chat-node window below is the fallback source.\n const projected = useProjection !== undefined ? useProjection(TIMELINE_PROJECTION_KEY) : undefined\n\n // Collected per snapshot/projection change (the collector allocates +\n // sorts); memoized so width measurement and tracking effects key on\n // content, not renders.\n const messages = react.useMemo(() => railMessages(nodeSnapshot, projected), [nodeSnapshot, projected])\n\n const [activeIndex, setActiveIndex] = react.useState(-1)\n const [show, setShow] = react.useState(false)\n const [rightOffset, setRightOffset] = react.useState(16)\n const [railWidth, setRailWidth] = react.useState(RAIL_MAX_WIDTH)\n const [fades, setFades] = react.useState({ top: false, bottom: false })\n const [hidden, setHidden] = react.useState(() => otherRailPresent())\n // Key-point bookmarks: per-session list + the \"marked only\" filter toggle.\n const [marks, setMarks] = react.useState<string[]>(() => readMarks(sessionId))\n const [marksOnly, setMarksOnly] = react.useState(false)\n const [isNarrow, setIsNarrow] = react.useState(\n () => typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n && window.matchMedia('(max-width: 767px)').matches,\n )\n const pageRef = react.useRef<HTMLDivElement | null>(null)\n const activeItemRef = react.useRef<HTMLButtonElement | null>(null)\n // Jump-stabilization lock: freezes the reading-position tracker while a\n // click-triggered jump glide animates (cleared by the settle timer below\n // or the JUMP_LOCK_FALLBACK_MS fallback in the click handler). The fallback\n // timeout is tracked so the settle path can cancel it and unmount cannot\n // leak it.\n const jumpPendingRef = react.useRef(false)\n const jumpFallbackRef = react.useRef<number | undefined>(undefined)\n\n // Marks follow the session switch; the filter resets with them.\n react.useEffect(() => {\n setMarks(readMarks(sessionId))\n setMarksOnly(false)\n }, [sessionId])\n\n const markedSet = react.useMemo(() => new Set(marks), [marks])\n // Mirror ref so rapid successive toggles always see the latest list (the\n // handler writes storage directly, outside the setState updater — updaters\n // must stay pure and StrictMode re-runs them).\n const marksRef = react.useRef(marks)\n marksRef.current = marks\n const toggleMark = react.useCallback((m: TimelineMessage) => {\n const k = markKeyOf(m)\n if (k === '') return\n const prev = marksRef.current\n const next = prev.includes(k) ? prev.filter((x) => x !== k) : [...prev, k]\n marksRef.current = next\n setMarks(next)\n writeMarks(sessionId, next)\n }, [sessionId])\n\n // Narrow-viewport guard (matches the official breakpoint).\n react.useEffect(() => {\n if (typeof window.matchMedia !== 'function') return\n const mql = window.matchMedia('(max-width: 767px)')\n const onChange = (e: MediaQueryListEvent): void => setIsNarrow(e.matches)\n mql.addEventListener('change', onChange)\n return () => mql.removeEventListener('change', onChange)\n }, [])\n\n // Width follows the longest title (remeasured when messages arrive/change).\n // No gating on a \"fully loaded\" flag: the projection source is complete by\n // construction, and the node fallback measures whatever it has.\n react.useEffect(() => {\n setRailWidth(railWidthFor(messages.map((m) => m.text)))\n }, [messages])\n\n // Reading position tracking: nearest user row to the 40% viewport line.\n // An IntersectionObserver (when available) keeps a live set of the rows\n // currently inside the scrollport — the best row is always one of them — so\n // each pass reads a few dozen rects instead of one per row of a possibly\n // thousand-row history; without IO measure() scans every row as before.\n react.useEffect(() => {\n if (messages.length === 0) return\n const indexByKey = new Map<string, number>()\n messages.forEach((m, i) => {\n const key = resolveAnchorKey(m)\n if (key !== undefined) indexByKey.set(key, i)\n })\n const sp = document.querySelector('[data-conversation-scroll]')\n if (sp === null) return\n const rows = sp.querySelectorAll('[data-chat-anchor-key]')\n\n let settleTimer: number | undefined\n let frame = 0\n const nearRows = new Set<Element>()\n\n const updateActive = (): void => {\n // A jump is animating: freeze the highlight so the panel cannot jitter.\n if (jumpPendingRef.current) return\n const rect = sp.getBoundingClientRect()\n if (rect.height === 0) return\n const candidates = nearRows.size > 0 ? nearRows : rows\n const line = rect.top + rect.height * 0.4\n let best = -1\n let bestDist = Infinity\n for (const row of candidates) {\n const key = row.getAttribute('data-chat-anchor-key')\n if (key === null) continue\n const idx = indexByKey.get(key) ?? -1\n if (idx === -1) continue\n const r = row.getBoundingClientRect()\n const dist = Math.abs(r.top + r.height / 2 - line)\n if (dist < bestDist) {\n bestDist = dist\n best = idx\n }\n }\n setActiveIndex(best)\n }\n const scheduleMeasure = (): void => {\n if (frame !== 0) return\n frame = window.requestAnimationFrame(() => {\n frame = 0\n updateActive()\n })\n }\n\n let io: IntersectionObserver | undefined\n if (typeof IntersectionObserver === 'function') {\n io = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) nearRows.add(entry.target)\n else nearRows.delete(entry.target)\n }\n scheduleMeasure()\n }, { root: sp })\n rows.forEach((row) => io?.observe(row))\n }\n\n const onScroll = (): void => {\n // Detect the true end of scrolling: 150ms after the LAST scroll event\n // release the jump lock and refresh the highlight exactly once.\n if (settleTimer !== undefined) clearTimeout(settleTimer)\n settleTimer = window.setTimeout(() => {\n settleTimer = undefined\n if (jumpPendingRef.current) {\n jumpPendingRef.current = false\n if (jumpFallbackRef.current !== undefined) {\n clearTimeout(jumpFallbackRef.current)\n jumpFallbackRef.current = undefined\n }\n updateActive()\n }\n }, 150)\n scheduleMeasure()\n }\n sp.addEventListener('scroll', onScroll, { passive: true })\n const interval = window.setInterval(scheduleMeasure, 2000)\n scheduleMeasure()\n return () => {\n io?.disconnect()\n if (frame !== 0) cancelAnimationFrame(frame)\n if (settleTimer !== undefined) clearTimeout(settleTimer)\n if (jumpFallbackRef.current !== undefined) {\n clearTimeout(jumpFallbackRef.current)\n jumpFallbackRef.current = undefined\n }\n sp.removeEventListener('scroll', onScroll)\n clearInterval(interval)\n }\n }, [messages])\n\n // Fade veils reflect the page's actual clip state (scroll position).\n const syncFades = react.useCallback((): void => {\n const page = pageRef.current\n if (page === null) {\n setFades({ top: false, bottom: false })\n return\n }\n const canTop = page.scrollTop > 2\n const canBot = page.scrollTop + page.clientHeight < page.scrollHeight - 2\n setFades((prev) => (prev.top === canTop && prev.bottom === canBot ? prev : { top: canTop, bottom: canBot }))\n }, [])\n react.useEffect(syncFades, [syncFades, show, messages.length, marksOnly])\n\n // Keep the active row visible inside the rail without moving the chat.\n react.useLayoutEffect(() => {\n if (!show) return\n const page = pageRef.current\n const item = activeItemRef.current\n if (page === null || item === null) return\n scrollTimelineItemIntoView(page, item)\n syncFades()\n }, [activeIndex, messages.length, marksOnly, show, syncFades])\n\n // Collapsed idle strategy (official ScrollNav behavior): pin the tick stack\n // to the BOTTOM inner edge so the newest question's tick always hugs the\n // bottom of the capsule and older ticks clip away at the top. overflow:hidden\n // boxes still accept programmatic scrollTop; scrollHeight clamps the value,\n // and short stacks that fit simply stay put (the page centers them). Runs on\n // every collapse and whenever the stack changes while idle, so a message\n // sent in another window keeps the newest tick pinned.\n const height = railHeightFor(messages.length)\n react.useLayoutEffect(() => {\n if (show) return\n const page = pageRef.current\n if (page === null) return\n page.scrollTop = page.scrollHeight\n }, [show, messages.length, marksOnly, height])\n\n // Avoid right-side workbenches: derive the offset from the chat scrollport.\n react.useEffect(() => {\n let raf = 0\n const measure = (): void => {\n raf = 0\n const sp = document.querySelector('[data-conversation-scroll]')\n if (sp === null) return\n const rect = sp.getBoundingClientRect()\n if (rect.width === 0 && rect.height === 0) return\n const next = Math.max(8, Math.round(window.innerWidth - rect.right + 12))\n setRightOffset((prev) => (Math.abs(prev - next) > 0.5 ? next : prev))\n }\n const schedule = (): void => {\n if (raf === 0) raf = window.requestAnimationFrame(measure)\n }\n const sp = document.querySelector('[data-conversation-scroll]')\n const ro = typeof ResizeObserver === 'function' && sp !== null ? new ResizeObserver(schedule) : null\n if (ro !== null && sp !== null) ro.observe(sp)\n window.addEventListener('resize', schedule)\n // Slow guard: pick up a competing rail (un)mounting mid-session.\n const guard = window.setInterval(() => setHidden(otherRailPresent()), 2000)\n measure()\n return () => {\n if (raf !== 0) cancelAnimationFrame(raf)\n ro?.disconnect()\n window.removeEventListener('resize', schedule)\n clearInterval(guard)\n }\n }, [])\n\n // Any non-empty conversation gets the rail — the official ScrollNav renders\n // for a lone question too, and the clamped shared height keeps a single\n // tick cleanly bottom-pinned instead of hiding the capsule.\n if (timelineUnknown || timelineDisabled\n || sessionId === undefined || sessionsService === undefined || isNarrow || hidden || messages.length === 0) {\n return null\n }\n\n // Compare actives by bookmark key, not index: the filtered display list\n // reindexes rows, and the active ref must track the SAME question.\n const activeKey = activeIndex >= 0 && activeIndex < messages.length ? markKeyOf(messages[activeIndex]) : ''\n const displayMessages = marksOnly ? messages.filter((m) => markedSet.has(markKeyOf(m))) : messages\n\n return reactDom.createPortal(\n <div\n className=\"dsbt-nav\"\n style={{ right: rightOffset, ['--dsbt-h' as string]: `${height}px`, ['--dsbt-w' as string]: `${railWidth}px` }}\n role=\"navigation\"\n aria-label={t('timeline.railLabel')}\n onMouseEnter={() => setShow(true)}\n onMouseLeave={() => setShow(false)}\n >\n <div className={show ? 'dsbt-bg dsbt-bg-hide' : 'dsbt-bg'} />\n <div className={show ? 'dsbt-wrap dsbt-show' : 'dsbt-wrap'}>\n <div className=\"dsbt-filterbar\">\n <button\n type=\"button\"\n className={marksOnly ? 'dsbt-filterbtn dsbt-filteron' : 'dsbt-filterbtn'}\n aria-pressed={marksOnly}\n onClick={() => setMarksOnly((v) => !v)}\n >\n {t('timeline.marksOnly')}{marks.length > 0 ? ` (${marks.length})` : ''}\n </button>\n </div>\n <div className={fades.top ? 'dsbt-fade dsbt-fade-on' : 'dsbt-fade'} aria-hidden />\n <div className={fades.bottom ? 'dsbt-fade dsbt-fade-bot dsbt-fade-on' : 'dsbt-fade dsbt-fade-bot'} aria-hidden />\n <div\n className=\"dsbt-page\"\n ref={pageRef}\n onScroll={syncFades}\n >\n {displayMessages.length === 0\n ? <div className=\"dsbt-empty\">{t('timeline.noMarks')}</div>\n : displayMessages.map((m) => {\n const key = resolveAnchorKey(m)\n const mk = markKeyOf(m)\n const marked = markedSet.has(mk)\n const isActive = activeKey !== '' && mk === activeKey\n const cls = `dsbt-item${isActive ? ' dsbt-active' : ''}${marked ? ' dsbt-marked' : ''}`\n return (\n <div key={m.seq} className={cls}>\n {/* Jump target and star bookmark are SIBLING real buttons —\n the previous span[role=button] nested inside the row\n button was invalid interactive-in-interactive markup,\n while native buttons keep Enter/Space for both roles.\n Entries without an anchor key (early events lacking a\n durable id) render disabled instead of silently eating\n the click. */}\n <button\n type=\"button\"\n className=\"dsbt-jump\"\n ref={isActive ? activeItemRef : undefined}\n disabled={key === undefined}\n title={key === undefined\n ? t('timeline.cannotJump')\n : `${marked ? '★ ' : ''}${m.text === '' ? t('timeline.noText') : m.text}`}\n aria-label={key === undefined\n ? `${marked ? '★ ' : ''}${t('timeline.roleUser')}: ${t('timeline.cannotJump')}`\n : `${marked ? '★ ' : ''}${t('timeline.roleUser')}: ${m.text.slice(0, 60) || t('timeline.noText')}`}\n aria-current={isActive ? 'location' : undefined}\n onClick={() => {\n if (key === undefined) return\n // Engage the stabilization lock with a fallback timeout:\n // the settle timer normally releases it after the jump.\n // A fresh click replaces the pending fallback so a rapid\n // second jump cannot be unlocked by the first one's timer.\n jumpPendingRef.current = true\n if (jumpFallbackRef.current !== undefined) clearTimeout(jumpFallbackRef.current)\n jumpFallbackRef.current = window.setTimeout(() => {\n jumpFallbackRef.current = undefined\n jumpPendingRef.current = false\n }, JUMP_LOCK_FALLBACK_MS)\n // The glide resolves on completion OR reader takeover —\n // release the lock right then; the settle timer path\n // stays idempotent either way.\n void jumpToMessage(sessionsService, sessionId, key)\n .catch(() => {})\n .finally(() => {\n if (jumpFallbackRef.current !== undefined) {\n clearTimeout(jumpFallbackRef.current)\n jumpFallbackRef.current = undefined\n }\n jumpPendingRef.current = false\n })\n }}\n >\n <span className=\"dsbt-title\">{m.text === '' ? t('timeline.noText') : m.text}</span>\n </button>\n <button\n type=\"button\"\n className={marked ? 'dsbt-star dsbt-staron' : 'dsbt-star'}\n aria-pressed={marked}\n aria-label={marked ? t('timeline.unmark') : t('timeline.mark')}\n onClick={() => toggleMark(m)}\n >\n ★\n </button>\n <span className=\"dsbt-ind\" aria-hidden>\n <span className=\"dsbt-line\" />\n </span>\n </div>\n )\n })}\n </div>\n </div>\n </div>,\n document.body,\n )\n}\n","/**\n * Browser half of the background plugin: paints the background behind the\n * whole app frame, provides the settings surface (a live row inside the\n * official General settings section), and renders the conversation timeline\n * rail (DeepSeek ScrollNav style) on the same frosted-glass system. Data\n * flows through the plugin's own host route (`/api/bg-wallpaper/*`), not the\n * settings RPC. Retraction discipline mirrors the skin plugins: every\n * property and DOM node the painter owns is restored on dispose.\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport { backgroundPainter, paintBackground } from './backdrop.ts'\nimport { installGlassBridge } from './glass-registry.ts'\nimport { en, zh } from './locales.ts'\nimport { BackgroundSettingsRow } from './SettingsRow.tsx'\nimport { settingsClient } from './settings-client.ts'\nimport { TimelineRail } from './timeline.tsx'\nimport type {} from './types.ts'\n\n/**\n * Service injections this entry declares: `slots` + `locale` for the settings\n * surface and the timeline slot seat, and `sessions` so the timeline rail can\n * enumerate user messages and jump between them. The settings surface itself\n * talks to the plugin's own host route over plain same-origin fetch.\n */\nexport const inject = ['slots', 'locale', 'sessions']\n\n/**\n * Client plugin body: paint the persisted background live, register the\n * General-settings row, and mount the conversation timeline rail.\n * @param ctx - client cordis context (slots/locale/sessions injected).\n */\nexport function apply(ctx: Context): void {\n // Publish the third-party frosted-glass registry bridge FIRST so an\n // eager consumer can register before the first snapshot repaints; the\n // bridge lives for the whole fiber and dies with it (see the dispose below).\n const glassBridge = installGlassBridge()\n // Paint from the plugin's own transport; repaint on every snapshot change.\n const paint = (): void => {\n const snapshot = settingsClient.getSnapshot()\n const value = snapshot.status === 'ready' ? snapshot.value : undefined\n if (value === undefined || !value.enabled) {\n backgroundPainter.dispose()\n return\n }\n paintBackground(value)\n }\n const unsubscribe = settingsClient.subscribe(paint)\n void settingsClient.load()\n\n ctx.effect(() => ctx.locale.register('ui-background', { zh, en }), 'deepseek-harness-background: dictionaries')\n\n // Live settings row in the General section (same slot as the Appearance row).\n ctx.slots.inject('settings.general.item', () => ctx.slots.register({\n name: 'settings.general.item',\n id: 'deepseek-harness-background',\n order: 600,\n locale: 'ui-background',\n }, BackgroundSettingsRow))\n\n // Conversation timeline rail: takes a per-session dock seat only to bind\n // its lifecycle; the rail portals to body. The sessions service is handed\n // to the component through the registration's inject face — resolved\n // lazily inside the factory so a runtime-side service rebuild can never\n // leave the rail holding a stale reference.\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'deepseek-harness-background.timeline',\n order: 45,\n locale: 'ui-background',\n inject: () => ({ sessionsService: ctx.sessions }),\n }, TimelineRail))\n\n ctx.effect(() => () => {\n unsubscribe()\n glassBridge.dispose()\n backgroundPainter.dispose()\n }, 'deepseek-harness-background: background surface')\n}\n"],"x_google_ignoreList":[5],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","useRef","css","useSyncExternalStore","useCallback","reactDom"],"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/timeline/mode-store.ts","../src/client/SettingsRow.tsx","../src/client/timeline/jump.ts","../src/client/timeline/rail-pointer.ts","../src/client/timeline/source.ts","../src/client/timeline/legacy-rail-css.ts","../src/client/timeline/legacy-rail.tsx","../src/client/timeline/official-enhance.tsx","../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 (timeline/legacy-rail-css.ts + 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 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*=\"_buildRevision\"] {\n background-color: rgba(15, 17, 21, 0.62);\n }\n body[data-ds-dark-theme][data-dsh-bg] [class*=\"_buildRevision\"] {\n background-color: rgba(249, 250, 251, 0.62);\n }\n\n /* The empty-state hero glow asset would wash out the wallpaper behind the\n welcome message — dim it so the art stays visible. */\n body[data-dsh-bg] [class*=\"_heroGlow\"] {\n opacity: 0.2;\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*=\"_buildRevision\"] {\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/**\r\n * Session-projection key this plugin owns in the client-visible value map —\r\n * the host fold registers it (src/projection.ts) and the rail reads it\r\n * (src/client/timeline/source.ts); one shared constant so the two can never drift.\r\n */\r\nexport const TIMELINE_PROJECTION_KEY = 'bgTimeline'\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.timeline': '会话时间线',\n 'background.timelineHint': '右侧轮次导航轨,悬停预览该轮问题与直达更早历史',\n 'background.timelineEnhance': '会话时间线增强',\n 'background.timelineEnhanceHint': '为官方会话时间线补上平滑跳转,并可直达需要“加载更多”的更早轮次',\n 'timeline.railLabel': '轮次导航',\n 'timeline.noText': '(无文本内容)',\n 'timeline.jump': '跳转到第 {turn} 轮',\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.timeline': 'Conversation timeline',\n 'background.timelineHint': 'Turn rail at the right edge; hover to preview a turn and reach earlier history',\n 'background.timelineEnhance': 'Conversation timeline enhancement',\n 'background.timelineEnhanceHint': 'Adds smooth jumps and direct reach into turns that still need “load earlier”',\n 'timeline.railLabel': 'Turn navigation',\n 'timeline.noText': '(no text)',\n 'timeline.jump': 'Jump to turn {turn}',\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\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 { ok: boolean; value?: BackgroundSettings }\n if (!body.ok || body.value === undefined) throw new Error('unexpected payload')\n this.snapshot = { status: 'ready', value: body.value }\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 this.snapshot = { status: 'ready', value: body.value }\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","/**\r\n * Detected-timeline-mode store — how the General-settings row learns which\r\n * frontend is live.\r\n *\r\n * The settings row sits in the root-scoped `settings.general.item` slot and\r\n * therefore has no session scope: it never receives `useChat` and cannot\r\n * probe the kernel itself. The session-scoped dock entry does the probing and\r\n * publishes the result here; the row subscribes and switches its copy between\r\n * \"conversation timeline\" (this plugin renders the rail) and \"conversation\r\n * timeline enhancement\" (the official rail exists and this plugin only\r\n * improves its behaviour).\r\n */\r\n\r\nimport type { TimelineMode } from './types.ts'\r\n\r\ntype Listener = () => void\r\n\r\n// The store cannot know the live mode before the first session-scoped dock\r\n// entry probes the kernel (the settings row is root-scoped and has no session\r\n// either). 'legacy' is the conservative pre-probe default: the settings row\r\n// briefly shows the plain \"conversation timeline\" label until the dock probes,\r\n// then switches to \"…enhancement\" when the kernel publishes useChat. The\r\n// flip is unavoidable — the information genuinely arrives later — and it\r\n// happens once, right after the first session mounts.\r\nlet mode: TimelineMode = 'legacy'\r\nconst listeners = new Set<Listener>()\r\n\r\n/** @returns the currently detected mode. */\r\nexport function timelineMode(): TimelineMode {\r\n return mode\r\n}\r\n\r\n/** Observe mode changes. @returns the disposer. */\r\nexport function subscribeTimelineMode(listener: Listener): () => void {\r\n listeners.add(listener)\r\n return () => { listeners.delete(listener) }\r\n}\r\n\r\n/**\r\n * Publish the detected mode (called by the session-scoped dock entry).\r\n * No-ops when the mode is unchanged, so a re-render cannot notify.\r\n * @param next - the mode to publish.\r\n */\r\nexport function reportTimelineMode(next: TimelineMode): void {\r\n if (mode === next) return\r\n mode = next\r\n for (const listener of listeners) listener()\r\n}\r\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 { subscribeTimelineMode, timelineMode } from './timeline/mode-store.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 // Which timeline frontend is live: this row has no session scope and cannot\n // probe the kernel itself, so it reads what the dock entry detected.\n const navMode = useSyncExternalStore(subscribeTimelineMode, timelineMode)\n const timelineLabelKey = navMode === 'enhance'\n ? 'background.timelineEnhance'\n : 'background.timeline'\n const timelineHintKey = navMode === 'enhance'\n ? 'background.timelineEnhanceHint'\n : 'background.timelineHint'\n const [draft, setDraft] = useState<BackgroundSettings>(DEFAULTS)\n const [uploading, setUploading] = useState(false)\n const [urlText, setUrlText] = useState('')\n const [error, setError] = useState('')\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 {/* 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 * Conversation jump engine — the shared behaviour both frontends run.\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. Both callers therefore\r\n * reuse the SAME code and differ only in two parameters: the host's\r\n * bottom-follow threshold (25px on the legacy conversation, 24px on the\r\n * current ChatView) and where the target row should land.\r\n */\r\n\r\nimport type { TimelineSessionHandle, 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\r\n/**\r\n * Official conversation follow threshold of the current ChatView (25px on the\r\n * legacy conversation view). The reader stays \"at bottom\" while within this\r\n * distance of the floor, and the view keeps following new content.\r\n */\r\nexport const CHATVIEW_FOLLOW_ZONE_PX = 24\r\nexport const LEGACY_FOLLOW_ZONE_PX = 25\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/** How many rounds one warm-up page waits for its prepend to commit. */\r\nconst WARM_PAGE_SETTLE_ROUNDS = 8\r\n/** Pause between those rounds. */\r\nconst WARM_PAGE_SETTLE_MS = 25\r\n\r\n/** Wall-clock budget for one warm-up run. */\r\nconst WARM_DEADLINE_MS = 15000\r\n/** Page budget for one warm-up run. */\r\nconst WARM_MAX_PAGES = 40\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 = LEGACY_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 * Page older history in WITHOUT moving what the reader is looking at.\r\n *\r\n * The host's ChatView compensates a prepend through an internal anchor ref\r\n * that only its own \"load earlier\" button and its own navigation arm. A\r\n * plugin calling `session.loadOlder()` from the outside gets no compensation,\r\n * so the prepended rows push the reader's content down — a visible jump.\r\n * This restores the visual position by the growth the prepend added.\r\n *\r\n * A reader pinned to the floor needs none of it: the host's follow logic\r\n * re-pins to the (growing) floor by itself, and compensating there would\r\n * fight it and overshoot.\r\n *\r\n * @param session - the session handle to page.\r\n * @param el - the conversation scrollport.\r\n * @param followZonePx - the host's bottom-follow threshold.\r\n * @returns whether the page actually grew the transcript (false = exhausted\r\n * or failed, so the caller should stop paging).\r\n */\r\nexport async function compensatedLoadOlder(\r\n session: { loadOlder(): Promise<unknown> },\r\n el: HTMLElement,\r\n followZonePx: number = LEGACY_FOLLOW_ZONE_PX,\r\n): Promise<boolean> {\r\n const pinned = scrollFloorOf(el) - el.scrollTop <= followZonePx\r\n const heightBefore = el.scrollHeight\r\n try {\r\n await session.loadOlder()\r\n } catch (error) {\r\n console.warn('[deepseek-harness-background] timeline history page failed:', error)\r\n return false\r\n }\r\n // The prepend commits on a later frame than the request's resolution\r\n // (store update -> React commit), so wait for the growth to land.\r\n for (let round = 0; round < WARM_PAGE_SETTLE_ROUNDS; round += 1) {\r\n await settleScrollEvents()\r\n const growth = el.scrollHeight - heightBefore\r\n if (growth > 0) {\r\n // Relative, not absolute: the browser keeps scrollTop constant while\r\n // content above it grows, so whatever the reader scrolled to during\r\n // the round-trip is preserved and only the prepend's height is undone.\r\n if (!pinned) el.scrollTop = el.scrollTop + growth\r\n return true\r\n }\r\n if (round < WARM_PAGE_SETTLE_ROUNDS - 1) await delay(WARM_PAGE_SETTLE_MS)\r\n }\r\n return false\r\n}\r\n\r\n/** Knobs one warm-up run may override. */\r\nexport interface WarmOptions {\r\n /** The host conversation's bottom-follow threshold in px. */\r\n followZonePx?: number\r\n /** Wall-clock budget for the whole run. */\r\n deadlineMs?: number\r\n /** Maximum number of pages one run may request. */\r\n maxPages?: number\r\n /** False once the caller unmounted — the run then stops at its next check. */\r\n alive?: () => boolean\r\n /** How many entries the rail currently shows. */\r\n countOf: () => number\r\n /** Marks the rail can show before it starts compressing them. */\r\n capacity: number\r\n}\r\n\r\n/**\r\n * Page older history in until the rail is full or the log is exhausted — the\r\n * \"turns behind the load-earlier button become reachable\" half of the\r\n * enhancement, shared by both frontends.\r\n *\r\n * Stops at `capacity` on purpose: past the rail's uncompressed tick count the\r\n * official stylesheet squeezes every mark into the same 420px column and the\r\n * rail degrades into a solid bar the reader cannot aim at. Filling it exactly\r\n * to capacity is free reach; going past it is not.\r\n *\r\n * @param session - the session handle to page.\r\n * @param scrollport - the conversation scrollport (compensation anchor).\r\n * @param options - budgets, the live entry count and the rail's capacity.\r\n */\r\nexport async function warmHistory(\r\n session: TimelineSessionHandle,\r\n scrollport: HTMLElement,\r\n options: WarmOptions,\r\n): Promise<void> {\r\n const followZonePx = options.followZonePx ?? LEGACY_FOLLOW_ZONE_PX\r\n const deadline = Date.now() + (options.deadlineMs ?? WARM_DEADLINE_MS)\r\n const maxPages = options.maxPages ?? WARM_MAX_PAGES\r\n const alive = options.alive ?? ((): boolean => true)\r\n for (let page = 0; page < maxPages; page += 1) {\r\n if (!alive()) return\r\n if (options.countOf() >= options.capacity) return\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) return\r\n if (Date.now() >= deadline) return\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; wait instead of burning the\r\n // budget on calls that cannot succeed.\r\n if (snapshot.openState !== undefined && snapshot.openState !== 'open') {\r\n await delay(100)\r\n continue\r\n }\r\n const grew = await compensatedLoadOlder(session, scrollport, followZonePx)\r\n if (!grew) return\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 ?? LEGACY_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 while the snapshot's node table — whatever generation it is\r\n // — does not host it. On 0.1.2+ `session.getSnapshot()` exposes no node\r\n // table at all (the `useChat` ChatSnapshot store is unreachable from\r\n // here), so without this probe every jump would page the WHOLE history\r\n // to the end (or burn the 30s deadline) even when the target row is on\r\n // screen behind the \"load earlier\" button.\r\n if (rowRenderedInDom(anchorKey)) break\r\n const snapshot = session.getSnapshot() as {\r\n chat?: { nodes?: { get(key: string): unknown } }\r\n nodes?: { get(key: string): unknown }\r\n hasMore?: boolean\r\n loadingOlder?: boolean\r\n openState?: unknown\r\n }\r\n // Legacy (<= 0.1.1) fast path: both kernel generations expose a keyed\r\n // node reader where one exists — the legacy session snapshot's\r\n // `chat.nodes` Map, and the current Chat snapshot's node store.\r\n const loaded = snapshot?.chat?.nodes?.get(anchorKey) ?? snapshot?.nodes?.get(anchorKey)\r\n if (loaded !== undefined) break\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 }\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 * Pointer/keyboard resolution for the official turn rail — shared by the\r\n * enhancer (which borrows the official rail) and the legacy port (which\r\n * renders an identical one), so both resolve a gesture to the same entry.\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\r\nimport type { OfficialNavigationItem } from './types.ts'\r\n\r\n/** Resting gap between neighbouring marks before the rail compresses to fit. */\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/** Fallback capacity when the rail has not been measured yet. */\r\nexport const DEFAULT_RAIL_CAPACITY = 41\r\n\r\n/** The official stylesheet's rail height cap (TurnNavigator.module.css). */\r\nconst RAIL_HEIGHT_CAP_PX = 420\r\n/** Band clearance the official stylesheet reserves around the rail. */\r\nconst RAIL_BAND_CLEARANCE_PX = 64\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 * The tallest the rail can render. The official stylesheet clamps the rail's\r\n * height with `min(natural, band - 64px, 420px)` and the band measurements\r\n * are published inline on the scroll host (ConversationRoot's\r\n * `--dsh-conversation-viewport-height` / `--dsh-composer-height`). Outside\r\n * the official layout — the ported rail portals itself to body, and its own\r\n * stylesheet carries the same 420px cap — only the cap applies.\r\n *\r\n * The rendered `clientHeight` must NOT be used here: with few loaded turns\r\n * the height IS the natural (count-driven) height, so a measured capacity\r\n * would equal the count and a \"rail full\" gate could never open — the\r\n * warm-up fixed point that left early turns unreachable.\r\n */\r\nfunction railMaxHeightOf(rail: HTMLElement): number {\r\n const scrollport = rail.closest<HTMLElement>('[data-conversation-scroll]')\r\n if (scrollport !== null) {\r\n const viewport = Number.parseFloat(scrollport.style.getPropertyValue('--dsh-conversation-viewport-height'))\r\n const composer = Number.parseFloat(scrollport.style.getPropertyValue('--dsh-composer-height'))\r\n if (Number.isFinite(viewport) && Number.isFinite(composer)) {\r\n return Math.min(RAIL_HEIGHT_CAP_PX, Math.max(0, viewport - composer - RAIL_BAND_CLEARANCE_PX))\r\n }\r\n }\r\n return RAIL_HEIGHT_CAP_PX\r\n}\r\n\r\n/**\r\n * How many marks the rail can show before it starts compressing them.\r\n *\r\n * Past this count the official stylesheet clamps the rail's height and every\r\n * mark switches from its fixed 10px spacing to a percentage position, so the\r\n * column degrades into a solid bar the reader cannot aim at. Filling the rail\r\n * exactly to this capacity is therefore free extra reach; going past it is\r\n * not.\r\n *\r\n * @param rail - the rail element (or null).\r\n */\r\nexport function railCapacityOf(rail: HTMLElement | null): number {\r\n if (rail === null) return DEFAULT_RAIL_CAPACITY\r\n const usable = railMaxHeightOf(rail) - 2 * railInsetOf(rail)\r\n if (!Number.isFinite(usable)) return DEFAULT_RAIL_CAPACITY\r\n return Math.max(2, Math.floor(usable / TICK_SPACING_PX) + 1)\r\n}\r\n\r\n/**\r\n * Resolve the entry under a pointer Y — the official geometry verbatim\r\n * (`itemAtPointer` in TurnNavigator.tsx), so this plugin lands on exactly the\r\n * mark the official rail would have picked.\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 indexAtPointer(count: number, rail: HTMLElement, clientY: number): number {\r\n const rect = rail.getBoundingClientRect()\r\n const inset = railInsetOf(rail)\r\n const usableHeight = Math.max(1, rect.height - 2 * inset)\r\n const ratio = Math.max(0, Math.min(1, (clientY - rect.top - inset) / usableHeight))\r\n return Math.round(ratio * (count - 1))\r\n}\r\n\r\n/**\r\n * Resolve the gesture that produced a rail click to an entry index.\r\n *\r\n * Keyboard activation targets the focused mark button (recovered by its\r\n * position among the rail's buttons); a mouse click carries coordinates and\r\n * resolves through the rail geometry. Returns -1 when neither applies.\r\n *\r\n * Assumption (both frontends): the mark ticks are the ONLY buttons under the\r\n * rail — the official rail renders marks exclusively and its preview tooltip\r\n * carries none, and the ported rail does the same. A future non-mark control\r\n * added inside the rail would shift the 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 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 return count === 0 ? -1 : indexAtPointer(count, rail, event.clientY)\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","/**\r\n * Conversation timeline data source — the shared backend both frontends read.\r\n *\r\n * Two producers feed one normalized entry list, fastest first:\r\n * - The host-side `bgTimeline` session projection (src/projection.ts): a\r\n * complete enumeration of the session's user messages, delivered through\r\n * the projection baseline/push channel. It names turns the chat view has\r\n * never loaded, which is what makes them jumpable at all.\r\n * - The loaded chat-node window: the fallback when the projection registry is\r\n * absent or lost its baseline. **This is the one surface the 0.1.2 kernel\r\n * broke** — it moved the Chat snapshot from `session.getSnapshot().chat`\r\n * (a Map) to `useChat()` (a `{ order, nodes: { values() } }` store), so the\r\n * reader below duck-types across both generations instead of assuming one.\r\n *\r\n * Enhancements carried over from the pre-0.5 rail:\r\n * - Rewind integration: messages withdrawn by a rewind command node are\r\n * filtered out, so a cut lands in the rail before the next projection push.\r\n * - Entries without an anchor key (early events lacking a durable id) borrow\r\n * the real key from the loaded window when it holds the same question.\r\n *\r\n * Enhancements REMOVED in 0.5 (the rail now reuses the official UI, which has\r\n * no affordance for them): key-point bookmarks, the \"marked only\" filter and\r\n * their localStorage persistence.\r\n */\r\n\r\nimport type { TimelineEntry } from './types.ts'\r\n\r\n/** Shared constant lives in settings.ts; re-exported for consumers of this module. */\r\nexport { TIMELINE_PROJECTION_KEY } from '../../settings.ts'\r\n\r\n/** Structural view of one chat node this rail consumes. */\r\ninterface ChatNodeLike {\r\n kind?: string\r\n key?: string\r\n anchorSeq?: number\r\n /** dsh >= 0.1.2 only: nodes removed from the visible surface stay hidden. */\r\n visibility?: string\r\n data?: {\r\n time?: number\r\n content?: unknown\r\n name?: unknown\r\n seq?: unknown\r\n args?: unknown\r\n outcome?: { kind?: unknown; sourceEventSeq?: unknown; text?: unknown }\r\n attributes?: unknown\r\n }\r\n rewindHidden?: unknown\r\n 'data-dsh-rewind-hidden'?: unknown\r\n}\r\n\r\n/* ---- Node-window enumeration (both kernel generations) ------------------ */\r\n\r\n/**\r\n * Enumerate the chat nodes of one conversation snapshot, duck-typed across\r\n * both kernel generations:\r\n * - dsh <= 0.1.1: `snapshot.chat.nodes` is a `Map` keyed by node key.\r\n * - dsh >= 0.1.2: the Chat snapshot is `{ order, nodes: { values() } }` and\r\n * the session snapshot has no `chat` field at all.\r\n *\r\n * Both stores expose `values()`, so one structural read covers both; the\r\n * receiver must survive the call because these are prototype methods (the\r\n * real Session's own methods read `this`, and so do Map's).\r\n *\r\n * @param snapshot - a session snapshot or a Chat snapshot (loosely typed).\r\n * @returns the nodes, in the store's own order; empty when neither shape fits.\r\n */\r\nexport function chatNodesOf(snapshot: unknown): ChatNodeLike[] {\r\n const valuesOf = (store: unknown): ChatNodeLike[] | undefined => {\r\n if (store === null || typeof store !== 'object') return undefined\r\n const values = (store as { values?: unknown }).values\r\n if (typeof values !== 'function') return undefined\r\n let iterable: Iterable<unknown>\r\n try {\r\n iterable = (values as () => Iterable<unknown>).call(store)\r\n } catch {\r\n return undefined\r\n }\r\n if (iterable === null || typeof iterable !== 'object') return undefined\r\n const out: ChatNodeLike[] = []\r\n for (const node of iterable) {\r\n if (node !== null && typeof node === 'object') out.push(node as ChatNodeLike)\r\n }\r\n return out\r\n }\r\n const rec = (snapshot ?? {}) as { chat?: { nodes?: unknown }; nodes?: unknown }\r\n return valuesOf(rec.chat?.nodes) ?? valuesOf(rec.nodes) ?? []\r\n}\r\n\r\n/** Is this node part of the visible surface? Nodes without the flag are legacy. */\r\nfunction isVisible(node: ChatNodeLike): boolean {\r\n return node.visibility === undefined || node.visibility === 'visible'\r\n}\r\n\r\n/* ---- Text extraction ----------------------------------------------------- */\r\n\r\n/** Preview cap so entries stay small (one short line per row). */\r\nconst MAX_TEXT_CHARS = 80\r\n\r\n/** Extract preview text from a user message's ContentBlock list. */\r\nfunction userTextOf(content: unknown): string {\r\n if (!Array.isArray(content)) return ''\r\n let out = ''\r\n for (const block of content) {\r\n if (block !== null && typeof block === 'object' && (block as { type?: unknown }).type === 'text') {\r\n const text = (block as { text?: unknown }).text\r\n if (typeof text === 'string') out += text\r\n }\r\n }\r\n return out.trim().slice(0, MAX_TEXT_CHARS)\r\n}\r\n\r\n/* ---- Rewind integration -------------------------------------------------- */\r\n\r\n/** Parse digits into a finite number (undefined otherwise). */\r\nfunction parseSeqDigits(digits: string | undefined): number | undefined {\r\n if (digits === undefined) return undefined\r\n const n = Number.parseInt(digits, 10)\r\n return Number.isFinite(n) ? n : undefined\r\n}\r\n\r\n/** Extract a seq from a string-ish field ('@42', 'seq 42', '#42', bare 42). */\r\nfunction seqFromStringish(value: unknown): number | undefined {\r\n if (typeof value === 'number' && Number.isFinite(value)) return value\r\n if (typeof value !== 'string') return undefined\r\n const m = /(?:@|seq\\s*|#)?(\\d+)/i.exec(value)\r\n return m === null ? undefined : parseSeqDigits(m[1])\r\n}\r\n\r\n/**\r\n * Extract the rewind target seq from a rewind outcome text. Multi-pattern:\r\n * an explicit `seq 42` declaration wins, then `#42`, then the broad\r\n * english/chinese verb forms — a single \"rewound\" regex silently missed real\r\n * success texts like \"已撤回 seq 42\" / \"Withdrawn seq 42\".\r\n * @param text - the command outcome text.\r\n */\r\nexport function rewindTargetOfOutcome(text: unknown): number | undefined {\r\n if (typeof text !== 'string') return undefined\r\n const seqMatch = /seq\\s*(\\d+)/i.exec(text)\r\n if (seqMatch !== null) return parseSeqDigits(seqMatch[1])\r\n const hashMatch = /#(\\d+)/.exec(text)\r\n if (hashMatch !== null) return parseSeqDigits(hashMatch[1])\r\n const broadMatch = /(?:rewound|withdrawn|已撤回).*?(?:target\\s+)?(\\d+)/i.exec(text)\r\n if (broadMatch !== null) return parseSeqDigits(broadMatch[1])\r\n return undefined\r\n}\r\n\r\n/**\r\n * Resolve the target of a rewind command: structured fields first\r\n * (outcome.targetSeq / outcome.target / args.targetSeq / args.target /\r\n * args.seq / args.raw / args[0]), then the outcome text — so\r\n * locale-independent producers keep parsing without text heuristics (the\r\n * args.seq guard skips the command's own seq).\r\n * @param command - the command payload.\r\n */\r\nexport function rewindTargetOfCommand(command: unknown): number | undefined {\r\n if (command === null || typeof command !== 'object') return undefined\r\n const rec = command as { outcome?: unknown; args?: unknown; seq?: unknown }\r\n const outcome = rec.outcome\r\n if (outcome !== null && typeof outcome === 'object') {\r\n const o = outcome as { targetSeq?: unknown; target?: unknown }\r\n if (typeof o.targetSeq === 'number' && Number.isFinite(o.targetSeq)) return o.targetSeq\r\n if (typeof o.target === 'number' && Number.isFinite(o.target)) return o.target\r\n }\r\n const args = rec.args\r\n if (args !== null && typeof args === 'object') {\r\n const record = args as { targetSeq?: unknown; target?: unknown; seq?: unknown; raw?: unknown; '0'?: unknown }\r\n if (typeof record.targetSeq === 'number' && Number.isFinite(record.targetSeq)) return record.targetSeq\r\n if (typeof record.target === 'number' && Number.isFinite(record.target)) return record.target\r\n if (typeof record.seq === 'number' && Number.isFinite(record.seq) && record.seq !== rec.seq) return record.seq\r\n const rawish = record.raw ?? record['0'] ?? (Array.isArray(args) ? args[0] : undefined)\r\n if (rawish !== undefined) {\r\n const n = seqFromStringish(rawish)\r\n if (n !== undefined) return n\r\n }\r\n if (typeof record.target === 'string') {\r\n const n = seqFromStringish(record.target)\r\n if (n !== undefined) return n\r\n }\r\n }\r\n if (outcome !== null && typeof outcome === 'object') {\r\n const text = (outcome as { text?: unknown }).text\r\n if (typeof text === 'string') return rewindTargetOfOutcome(text)\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * Is this rewind command a preview/dry-run (marks the target hidden without\r\n * an outcome)?\r\n * @param command - the command payload.\r\n */\r\nfunction isRewindPreviewCommand(command: { args?: unknown }): boolean {\r\n if (command.args === null || typeof command.args !== 'object') return false\r\n const args = command.args as { preview?: unknown; dryRun?: unknown }\r\n return args.preview === true || args.dryRun === true\r\n}\r\n\r\n/**\r\n * Seq numbers of nodes carrying a rewind-hidden marker: host/plugin producers\r\n * can tag the node record itself (rewindHidden), its data payload, or the\r\n * payload's attributes map.\r\n * @param chat - a chat snapshot (loosely typed on purpose).\r\n */\r\nexport function rewindMarkedSeqsOfChat(chat: unknown): Set<number> {\r\n const marked = new Set<number>()\r\n for (const node of chatNodesOf(chat)) {\r\n const data = node.data\r\n if (data === null || typeof data !== 'object') continue\r\n const record = data as { attributes?: unknown; [k: string]: unknown }\r\n const attrs = record.attributes\r\n if (attrs !== null && typeof attrs === 'object'\r\n && (attrs as Record<string, unknown>)['data-dsh-rewind-hidden'] === true) {\r\n if (typeof node.anchorSeq === 'number') marked.add(node.anchorSeq)\r\n continue\r\n }\r\n if (record['data-dsh-rewind-hidden'] === true || node.rewindHidden === true) {\r\n if (typeof node.anchorSeq === 'number') marked.add(node.anchorSeq)\r\n }\r\n }\r\n return marked\r\n}\r\n\r\n/**\r\n * Seq numbers a rewind hid directly: successful commands hide their own row;\r\n * preview commands hide their target.\r\n * @param chat - a chat snapshot (loosely typed on purpose).\r\n */\r\nexport function rewindHiddenSeqsOfChat(chat: unknown): Set<number> {\r\n const hidden = new Set<number>()\r\n if (chat === null || typeof chat !== 'object') return hidden\r\n for (const node of chatNodesOf(chat)) {\r\n if (node.kind !== 'command') continue\r\n const command = node.data\r\n if (command === null || typeof command !== 'object') continue\r\n if ((command as { name?: unknown }).name !== 'rewind') continue\r\n if (isRewindPreviewCommand(command as { args?: unknown })) {\r\n if (typeof (command as { seq?: unknown }).seq === 'number') hidden.add((command as { seq: number }).seq)\r\n continue\r\n }\r\n const outcome = (command as { outcome?: unknown }).outcome\r\n if (outcome === null || typeof outcome !== 'object' || (outcome as { kind?: unknown }).kind !== 'success') continue\r\n const own = (command as { seq?: unknown }).seq\r\n if (typeof own === 'number') hidden.add(own)\r\n }\r\n return hidden\r\n}\r\n\r\n/**\r\n * The [target, sourceEventSeq] spans successful rewind commands cut — the\r\n * range form of hiding, needed to filter sources that are NOT chat nodes\r\n * (the host projection) by plain seq membership.\r\n * @param chat - a chat snapshot (loosely typed on purpose).\r\n */\r\nexport function rewindSpansOfChat(chat: unknown): { start: number; end: number }[] {\r\n const spans: { start: number; end: number }[] = []\r\n if (chat === null || typeof chat !== 'object') return spans\r\n for (const node of chatNodesOf(chat)) {\r\n if (node.kind !== 'command') continue\r\n const command = node.data\r\n if (command === null || typeof command !== 'object') continue\r\n if ((command as { name?: unknown }).name !== 'rewind') continue\r\n if (isRewindPreviewCommand(command as { args?: unknown })) continue\r\n const outcome = (command as { outcome?: unknown }).outcome\r\n if (outcome === null || typeof outcome !== 'object') continue\r\n const record = outcome as { kind?: unknown; sourceEventSeq?: unknown }\r\n if (record.kind !== 'success') continue\r\n const marker = record.sourceEventSeq\r\n if (typeof marker !== 'number') continue\r\n const target = rewindTargetOfCommand(command)\r\n if (target !== undefined) spans.push({ start: target, end: marker })\r\n }\r\n return spans\r\n}\r\n\r\n/**\r\n * Collect the seq numbers a rewind hid: preview commands hide their target\r\n * directly; successful commands hide their own row plus every node anchored\r\n * inside the rewound span [target, sourceEventSeq].\r\n * @param chat - a chat snapshot (loosely typed on purpose).\r\n */\r\nexport function hiddenSeqsOfChat(chat: unknown): Set<number> {\r\n const hidden = rewindHiddenSeqsOfChat(chat)\r\n for (const seq of rewindMarkedSeqsOfChat(chat)) hidden.add(seq)\r\n const spans = rewindSpansOfChat(chat)\r\n if (spans.length === 0) return hidden\r\n for (const node of chatNodesOf(chat)) {\r\n const anchor = typeof node.anchorSeq === 'number' ? node.anchorSeq : undefined\r\n if (anchor === undefined) continue\r\n if (spans.some((span) => anchor >= span.start && anchor <= span.end)) hidden.add(anchor)\r\n }\r\n return hidden\r\n}\r\n\r\n/* ---- Projection source --------------------------------------------------- */\r\n\r\n/** The user-message node definition kind in ui-conversation. */\r\nconst INPUT_MESSAGE_KIND = 'input-message'\r\n\r\n/**\r\n * Rebuild a chat row's DOM anchor key from the durable message id — the same\r\n * engine formula as conversationContextKey(kind, id) for the input-message\r\n * definition (`13:input-message<id>`), so projected entries can jump even\r\n * when their chat node is not loaded yet.\r\n * @param id - the durable message id carried by the projection entry.\r\n */\r\nexport function inputAnchorKeyOf(id: string): string {\r\n return `${INPUT_MESSAGE_KIND.length}:${INPUT_MESSAGE_KIND}${id}`\r\n}\r\n\r\n/**\r\n * Validate one projected wire value into rail entries (defensive: the value\r\n * crosses the wire and may be absent, partial, or stale).\r\n * @param value - the raw useProjection(TIMELINE_PROJECTION_KEY) snapshot.\r\n */\r\nexport function normalizeProjectedTimeline(value: unknown): TimelineEntry[] {\r\n if (value === null || typeof value !== 'object') return []\r\n const raw = (value as { messages?: unknown }).messages\r\n if (!Array.isArray(raw)) return []\r\n const seen = new Set<number>()\r\n const out: TimelineEntry[] = []\r\n for (const item of raw) {\r\n if (item === null || typeof item !== 'object') continue\r\n const rec = item as { seq?: unknown; time?: unknown; text?: unknown; id?: unknown }\r\n if (typeof rec.seq !== 'number' || !Number.isFinite(rec.seq)) continue\r\n if (seen.has(rec.seq)) continue\r\n seen.add(rec.seq)\r\n out.push({\r\n seq: rec.seq,\r\n time: typeof rec.time === 'number' ? rec.time : 0,\r\n text: typeof rec.text === 'string' ? rec.text.slice(0, MAX_TEXT_CHARS) : '',\r\n ...(typeof rec.id === 'string' && rec.id !== '' ? { anchorKey: inputAnchorKeyOf(rec.id) } : {}),\r\n })\r\n }\r\n out.sort((a, b) => a.seq - b.seq)\r\n return out\r\n}\r\n\r\n/* ---- Node-window source -------------------------------------------------- */\r\n\r\n/**\r\n * Enumerate user messages from the loaded chat-node snapshot (the client-side\r\n * collector; sorted by anchor sequence, rewind-hidden entries dropped).\r\n * @param snapshot - a session snapshot or Chat snapshot (loosely typed).\r\n */\r\nexport function collectMessages(snapshot: unknown): TimelineEntry[] {\r\n const hidden = hiddenSeqsOfChat(snapshot)\r\n const out: TimelineEntry[] = []\r\n for (const node of chatNodesOf(snapshot)) {\r\n if (node.kind !== 'user') continue\r\n if (!isVisible(node)) continue\r\n const data = node.data\r\n if (data === null || typeof data !== 'object' || typeof data.time !== 'number' || !Array.isArray(data.content)) continue\r\n const anchorKey = typeof node.key === 'string' ? node.key : undefined\r\n if (anchorKey === undefined) continue\r\n const seq = typeof node.anchorSeq === 'number' ? node.anchorSeq : 0\r\n if (hidden.has(seq)) continue\r\n out.push({ seq, time: data.time, text: userTextOf(data.content), anchorKey })\r\n }\r\n out.sort((a, b) => a.seq - b.seq)\r\n return out\r\n}\r\n\r\n/**\r\n * Count the user messages the loaded window holds WITHOUT collecting or\r\n * sorting them — the fast gate railMessages uses to skip the merge path on\r\n * the streaming hot path (see there).\r\n * @param snapshot - the loaded chat snapshot.\r\n */\r\nfunction countWindowUserMessages(snapshot: unknown): number {\r\n let count = 0\r\n for (const node of chatNodesOf(snapshot)) {\r\n if (node.kind === 'user' && isVisible(node)) count += 1\r\n }\r\n return count\r\n}\r\n\r\n/**\r\n * Rail entries: the host projection (whole-session index) MERGED with the\r\n * loaded chat-node window. The projection normally wins outright, but a\r\n * projection that lost its baseline (e.g. the plugin was hot-reloaded after\r\n * the session's tail page seeded the client store) can miss early entries —\r\n * the window fills those gaps whenever it holds them, and window entries\r\n * lend their real anchor key to projected entries whose durable id was\r\n * absent (early events without one). Locally-known rewind hiding applies to\r\n * the merged list so a cut lands instantly, before the next projection push.\r\n * @param snapshot - the session conversation snapshot (node window).\r\n * @param projected - the raw bgTimeline projection value.\r\n */\r\nexport function railMessages(snapshot: unknown, projected: unknown): TimelineEntry[] {\r\n const projectedMessages = normalizeProjectedTimeline(projected)\r\n const hidden = rewindHiddenSeqsOfChat(snapshot)\r\n const marked = rewindMarkedSeqsOfChat(snapshot)\r\n const spans = rewindSpansOfChat(snapshot)\r\n const isHidden = (seq: number): boolean =>\r\n hidden.has(seq) || marked.has(seq) || spans.some((span) => seq >= span.start && seq <= span.end)\r\n\r\n if (projectedMessages.length === 0) return collectMessages(snapshot)\r\n // Fast gate: the loaded window is a subset of the whole log the projection\r\n // indexes, so when the window holds no MORE user messages than the\r\n // projection it cannot fill a gap. Skipping the collect+sort keeps the\r\n // streaming hot path (one render per token) allocation-free — the merged\r\n // path below runs only for a degraded projection (hot reload losing the\r\n // baseline) or rewinds cutting projection entries the window still holds.\r\n const windowUserCount = countWindowUserMessages(snapshot)\r\n // A keyless projected entry (early events without a durable id) can borrow\r\n // its anchor key from the window, so that case must reach the merge too.\r\n const projectionLacksKeys = projectedMessages.some((m) => m.anchorKey === undefined)\r\n if (windowUserCount <= projectedMessages.length && !projectionLacksKeys) {\r\n return marked.size === 0 && hidden.size === 0 && spans.length === 0\r\n ? projectedMessages\r\n : projectedMessages.filter((m) => !isHidden(m.seq))\r\n }\r\n const windowMessages = collectMessages(snapshot)\r\n\r\n const bySeq = new Map<number, TimelineEntry>()\r\n for (const m of projectedMessages) {\r\n if (!isHidden(m.seq)) bySeq.set(m.seq, m)\r\n }\r\n for (const m of windowMessages) {\r\n if (isHidden(m.seq)) continue\r\n const existing = bySeq.get(m.seq)\r\n if (existing === undefined) {\r\n bySeq.set(m.seq, m)\r\n continue\r\n }\r\n // A projected entry missing its anchor key (early events without a\r\n // durable id) borrows the window entry's real key so the row can jump.\r\n if (existing.anchorKey === undefined) {\r\n bySeq.set(m.seq, {\r\n ...existing,\r\n anchorKey: m.anchorKey,\r\n ...(existing.text === '' && m.text !== '' ? { text: m.text } : {}),\r\n })\r\n }\r\n }\r\n return [...bySeq.values()].sort((a, b) => a.seq - b.seq)\r\n}\r\n","/**\r\n * Styles for the legacy port of the official turn rail (legacy-rail.tsx).\r\n *\r\n * A faithful port of `TurnNavigator.module.css` from dsh-client-ui-chat, kept\r\n * on the plugin's own `dsbt-` prefix because this rail isportaled to body\r\n * instead of living inside the chat scrollport. Metrics are the official ones\r\n * verbatim (28px column, 10px tick spacing, 6px end inset, 12/18/20px tick\r\n * widths, 420px height ceiling, 100px preview, 300px preview width).\r\n *\r\n * Glass + edge fade (unlike stock chrome): the rail is background plugin\r\n * chrome over the user's art, so the hover preview joins the shared glass\r\n * recipe under body[data-dsh-bg-glass] (fill = the painter's composer token\r\n * + the shared blur/sheen — the same strength as the composer card), and\r\n * the marks column gets the DeepSeek-web edge dissolve (top/bottom ticks\r\n * fade instead of hard-clipping). The fade is paint-only, always on; the\r\n * glass follows the wallpaper/panel-opacity sliders exactly like every\r\n * other glassed surface. Both kernel generations render identically.\r\n */\r\n\r\n/** Unique id stamped on the injected style element (dedup key). */\r\nexport const TIMELINE_CSS_TAG = 'deepseek-harness-background/timeline'\r\n\r\n/** Inject the rail stylesheet once. Idempotent (dedup key on the tag). */\r\nexport function injectTimelineCss(): void {\r\n if (typeof document === 'undefined') return\r\n if (document.querySelector(`style[data-plugin-css=\"${TIMELINE_CSS_TAG}\"]`)) return\r\n const tag = document.createElement('style')\r\n tag.dataset.plugin = 'deepseek-harness-background'\r\n tag.dataset.pluginCss = TIMELINE_CSS_TAG\r\n tag.textContent = TIMELINE_CSS\r\n document.head.appendChild(tag)\r\n}\r\n\r\nexport const TIMELINE_CSS = `\r\n /* Portaled host: the official slot is a zero-height sticky box inside the\r\n chat scrollport; a fixed box centred on the transcript band is the\r\n equivalent outside it. */\r\n .dsbt-slot {\r\n position: fixed; top: 50%; right: 16px; transform: translateY(-50%);\r\n z-index: 6; display: flex; align-items: center; pointer-events: none;\r\n }\r\n @media (max-width: 767px) { .dsbt-slot { display: none; } }\r\n\r\n .dsbt-rail {\r\n position: relative; width: 28px; cursor: pointer; pointer-events: auto;\r\n transition: height 220ms cubic-bezier(.2, .8, .2, 1);\r\n height: min(var(--dsbt-natural-h, 12px), var(--dsbt-band, 420px), 420px);\r\n }\r\n\r\n .dsbt-marks {\r\n position: absolute; inset: var(--dsbt-inset, 6px) 0;\r\n /* No edge dissolve here: the first and last tick dashes straddle the\r\n marks box edges by 1px, so any fade zone sized off the box height\r\n swallows them (at a 10-turn rail the 8% recipe rendered the ends at\r\n ~14% opacity). The end ticks must stay fully painted. */\r\n }\r\n\r\n .dsbt-markPosition {\r\n position: absolute; top: min(var(--dsbt-natural, 0px), var(--dsbt-ratio, 0%));\r\n right: 0; left: 0; height: 10px; transform: translateY(-50%);\r\n transition: top 220ms cubic-bezier(.2, .8, .2, 1);\r\n animation: dsbt-mark-enter 150ms ease-out;\r\n }\r\n\r\n /* The rail owns pointer input for the whole column, so a mark is a keyboard\r\n destination that paints one tick — never a mouse target of its own. */\r\n .dsbt-mark {\r\n position: absolute; inset: 0 0 0 auto; width: 20px; padding: 0; border: 0;\r\n border-radius: 8px; background: transparent; cursor: pointer;\r\n pointer-events: none;\r\n }\r\n .dsbt-mark::before {\r\n position: absolute; top: 50%; right: 0; width: 12px; height: 2px;\r\n border-radius: 2px; background: var(--dsw-alias-border-l4);\r\n content: ''; transform: translateY(-50%);\r\n transition: width 140ms ease, background-color 140ms ease;\r\n }\r\n .dsbt-markPreview::before { width: 18px; background: var(--dsw-alias-label-tertiary); }\r\n .dsbt-markActive::before { width: 20px; background: var(--dsw-alias-label-primary); }\r\n .dsbt-mark:focus-visible::before {\r\n width: 20px; background: var(--dsw-alias-state-business-primary);\r\n }\r\n .dsbt-mark:focus-visible {\r\n outline: 1px solid var(--dsw-alias-state-business-primary);\r\n outline-offset: 2px;\r\n }\r\n /* Entries without an anchor key (early events lacking a durable id) cannot\r\n jump; dim them instead of silently eating the click. */\r\n .dsbt-mark:disabled::before { width: 8px; opacity: .5; }\r\n\r\n .dsbt-preview {\r\n --dsbt-preview-h: 100px;\r\n position: absolute;\r\n top: clamp(\r\n 0px,\r\n calc(min(var(--dsbt-natural, 0px), var(--dsbt-ratio, 0%))\r\n + var(--dsbt-inset, 6px) - var(--dsbt-preview-h) / 2),\r\n calc(100% - var(--dsbt-preview-h))\r\n );\r\n right: calc(100% + 10px); box-sizing: border-box;\r\n width: min(300px, calc(100vw - 120px)); max-height: var(--dsbt-preview-h);\r\n overflow: hidden; padding: 10px 12px;\r\n border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px;\r\n color: var(--dsw-alias-label-primary);\r\n background: var(--dsw-alias-bg-layer-1); box-shadow: var(--dsw-shadow-lv2);\r\n font: var(--dsw-font-xs-strong-13); pointer-events: none;\r\n display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4;\r\n animation: dsbt-preview-enter 120ms ease-out;\r\n transition: top 140ms cubic-bezier(.2, .8, .2, 1);\r\n /* Content dissolve: a clamped preview's last lines soften out instead of\r\n hard-cutting. No effect on short copy (the mask's fade zone rides the\r\n empty tail) and no effect on ticks — it lives on the preview only. */\r\n mask-image: linear-gradient(180deg, #000 86%, transparent 100%);\r\n -webkit-mask-image: linear-gradient(180deg, #000 86%, transparent 100%);\r\n }\r\n\r\n /* Hover preview is glass chrome just like the composer card: its official\r\n fill is the opaque bg-layer-1 token, so it needs the explicit-fill\r\n recipe (painter's composer token + shared blur/sheen chain). Gated on\r\n data-dsh-bg-glass like every other glassed surface. */\r\n body[data-dsh-bg-glass] .dsbt-preview {\r\n background-color: var(--dsw-specific-input-major);\r\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));\r\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);\r\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);\r\n }\r\n\r\n @keyframes dsbt-mark-enter { from { opacity: 0; } to { opacity: 1; } }\r\n @keyframes dsbt-preview-enter {\r\n from { opacity: 0; transform: translateX(4px); }\r\n to { opacity: 1; transform: translateX(0); }\r\n }\r\n\r\n @media (prefers-reduced-motion: reduce) {\r\n .dsbt-rail, .dsbt-markPosition, .dsbt-mark::before, .dsbt-preview {\r\n transition: none; animation: none;\r\n }\r\n }\r\n`\r\n","/**\r\n * Legacy turn rail — the official `TurnNavigator` UI ported into this plugin\r\n * for kernels that do not publish an official turn-navigation index\r\n * (dsh <= 0.1.1).\r\n *\r\n * Structure, metrics and paints mirror `TurnNavigator.tsx` /\r\n * `TurnNavigator.module.css` from dsh-client-ui-chat; styles live in\r\n * ./legacy-rail-css.ts. The only differences are the ones a portaled box\r\n * forces:\r\n * - The official slot is a zero-height sticky box inside the chat scrollport;\r\n * this one is a fixed box centred on the same band (the transcript minus\r\n * the composer), positioned from the scrollport's live rect.\r\n * - The official rail reads the turn index the kernel assembles; this one\r\n * reads the shared backend (./source.ts), i.e. the host projection when it\r\n * exists and the loaded node window otherwise. That is why it can offer\r\n * turns the chat view has not loaded yet.\r\n *\r\n * Behaviour is identical to the enhanced official rail because both run the\r\n * same jump engine (./jump.ts): a click glides instead of teleporting, and\r\n * reaching for the rail pages older history in up to its uncompressed\r\n * capacity with the reader's view held still.\r\n */\r\n\r\nimport react from 'react'\r\nimport reactDom from 'react-dom'\r\nimport { LEGACY_FOLLOW_ZONE_PX, jumpToMessage, warmHistory } from './jump.ts'\r\nimport { indexAtPointer, indexForEvent, railCapacityOf, RAIL_INSET_PX, TICK_SPACING_PX } from './rail-pointer.ts'\r\nimport { railMessages } from './source.ts'\r\nimport { TIMELINE_PROJECTION_KEY } from '../../settings.ts'\r\nimport { injectTimelineCss } from './legacy-rail-css.ts'\r\nimport type { TimelineEntry, TimelineSessionsService } from './types.ts'\r\n\r\nif (typeof document !== 'undefined') injectTimelineCss()\r\n\r\n/** Composer height assumed when neither the seat nor the kernel's own var is available. */\r\nconst FALLBACK_COMPOSER_PX = 152\r\n/** The official stylesheet's own ceiling and band deduction. */\r\nconst RAIL_MAX_HEIGHT_PX = 420\r\nconst RAIL_BAND_DEDUCTION_PX = 64\r\n/** How often the rail's host band and offset are re-measured. */\r\nconst MEASURE_INTERVAL_MS = 400\r\n\r\n/** Component props delivered by the dock slot registration. */\r\nexport interface LegacyTimelineRailProps {\r\n sessionId?: string\r\n sessionsService?: TimelineSessionsService\r\n /** Framework projection reader (SessionStandardProps seat; undefined-capable). */\r\n useProjection?: (key: string) => unknown\r\n /** Whether the persisted timeline toggle is on. */\r\n enabled: boolean\r\n /** Locale lookup; the rail's copy interpolates a `{turn}` placeholder. */\r\n t: (key: string, params?: Record<string, string | number>) => string\r\n}\r\n\r\n/** Read one px-valued custom property off an element (undefined when absent). */\r\nfunction cssPx(el: HTMLElement, name: string): number | undefined {\r\n const raw = el.style.getPropertyValue(name)\r\n if (raw === '') return undefined\r\n const px = Number.parseFloat(raw)\r\n return Number.isFinite(px) ? px : undefined\r\n}\r\n\r\n/**\r\n * The band the rail may occupy: the transcript the reader actually sees, i.e.\r\n * the scrollport minus the sticky composer stack — the official measurement,\r\n * taken from the kernel's own vars when it publishes them (0.1.2+) and from\r\n * the composer seat otherwise.\r\n * @param sp - the conversation scrollport.\r\n */\r\nfunction bandOf(sp: HTMLElement): number {\r\n const viewport = cssPx(sp, '--dsh-conversation-viewport-height') ?? sp.clientHeight\r\n const seat = sp.querySelector<HTMLElement>('[data-composer-seat]')\r\n const composer = cssPx(sp, '--dsh-composer-height') ?? seat?.offsetHeight ?? FALLBACK_COMPOSER_PX\r\n return Math.max(0, Math.min(RAIL_MAX_HEIGHT_PX, viewport - composer - RAIL_BAND_DEDUCTION_PX))\r\n}\r\n\r\n/** Tick metrics for one count, mirroring the official `railSize`. */\r\nfunction railSize(count: number): react.CSSProperties {\r\n return {\r\n ['--dsbt-natural-h' as string]: `${(count - 1) * TICK_SPACING_PX + 2 * RAIL_INSET_PX}px`,\r\n ['--dsbt-inset' as string]: `${RAIL_INSET_PX}px`,\r\n }\r\n}\r\n\r\n/** Position of one tick, mirroring the official `itemPosition`. */\r\nfunction tickPosition(index: number, count: number): react.CSSProperties {\r\n const ratio = count <= 1 ? 0 : index / (count - 1)\r\n return {\r\n ['--dsbt-natural' as string]: `${index * TICK_SPACING_PX}px`,\r\n ['--dsbt-ratio' as string]: `${ratio * 100}%`,\r\n }\r\n}\r\n\r\n/**\r\n * The legacy turn rail.\r\n * @param props - dock slot props plus the enabled gate.\r\n * @returns the portaled rail, or null when it must not render.\r\n */\r\nexport function LegacyTimelineRail(props: LegacyTimelineRailProps): react.ReactElement | null {\r\n const { sessionId, sessionsService, useProjection, enabled, t } = props\r\n\r\n const session = sessionId !== undefined && sessionsService !== undefined\r\n ? sessionsService.binding(sessionId)?.session\r\n : undefined\r\n // SessionFace methods are prototype methods reading \"this\"; React invokes\r\n // store callbacks as bare functions, so keep the receiver via closures.\r\n // Identity stays stable per session so uSES does not resubscribe per render.\r\n const subscribeSession = react.useCallback(\r\n (listener: () => void) => session === undefined ? () => {} : session.subscribe(listener),\r\n [session],\r\n )\r\n const snapshotSession = react.useCallback(\r\n () => session === undefined ? undefined : session.getSnapshot(),\r\n [session],\r\n )\r\n const nodeSnapshot = react.useSyncExternalStore(subscribeSession, snapshotSession)\r\n\r\n const projected = useProjection === undefined ? undefined : useProjection(TIMELINE_PROJECTION_KEY)\r\n const entries = react.useMemo(() => railMessages(nodeSnapshot, projected), [nodeSnapshot, projected])\r\n\r\n const [activeIndex, setActiveIndex] = react.useState(-1)\r\n const [previewIndex, setPreviewIndex] = react.useState(-1)\r\n const [rightOffset, setRightOffset] = react.useState(16)\r\n const [band, setBand] = react.useState(RAIL_MAX_HEIGHT_PX)\r\n const aliveRef = react.useRef(true)\r\n const warmingRef = react.useRef(false)\r\n const railRef = react.useRef<HTMLElement | null>(null)\r\n\r\n react.useEffect(() => {\r\n aliveRef.current = true\r\n return () => { aliveRef.current = false }\r\n }, [])\r\n\r\n const entriesRef = react.useRef(entries)\r\n entriesRef.current = entries\r\n\r\n // Reading position: the entry nearest the 40% line of the scrollport.\r\n react.useEffect(() => {\r\n if (entries.length === 0) return\r\n const indexByKey = new Map<string, number>()\r\n entries.forEach((entry, index) => {\r\n if (entry.anchorKey !== undefined) indexByKey.set(entry.anchorKey, index)\r\n })\r\n const sp = document.querySelector<HTMLElement>('[data-conversation-scroll]')\r\n if (sp === null) return\r\n const rows = sp.querySelectorAll<HTMLElement>('[data-chat-anchor-key]')\r\n let frame = 0\r\n const measure = (): void => {\r\n frame = 0\r\n const rect = sp.getBoundingClientRect()\r\n if (rect.height === 0) return\r\n const line = rect.top + rect.height * 0.4\r\n let best = -1\r\n let bestDist = Infinity\r\n for (const row of rows) {\r\n const key = row.getAttribute('data-chat-anchor-key')\r\n if (key === null) continue\r\n const index = indexByKey.get(key)\r\n if (index === undefined) continue\r\n const r = row.getBoundingClientRect()\r\n const dist = Math.abs(r.top + r.height / 2 - line)\r\n if (dist < bestDist) {\r\n bestDist = dist\r\n best = index\r\n }\r\n }\r\n setActiveIndex(best)\r\n }\r\n const schedule = (): void => {\r\n if (frame === 0) frame = window.requestAnimationFrame(measure)\r\n }\r\n sp.addEventListener('scroll', schedule, { passive: true })\r\n const timer = window.setInterval(schedule, 2000)\r\n schedule()\r\n return () => {\r\n sp.removeEventListener('scroll', schedule)\r\n window.clearInterval(timer)\r\n if (frame !== 0) cancelAnimationFrame(frame)\r\n }\r\n }, [entries])\r\n\r\n // Band and right offset follow the live conversation geometry.\r\n react.useEffect(() => {\r\n let raf = 0\r\n const measure = (): void => {\r\n raf = 0\r\n const sp = document.querySelector<HTMLElement>('[data-conversation-scroll]')\r\n if (sp === null) return\r\n const rect = sp.getBoundingClientRect()\r\n if (rect.width === 0 && rect.height === 0) return\r\n setBand(bandOf(sp))\r\n const next = Math.max(8, Math.round(window.innerWidth - rect.right + 12))\r\n setRightOffset((prev) => (Math.abs(prev - next) > 0.5 ? next : prev))\r\n }\r\n const schedule = (): void => {\r\n if (raf === 0) raf = window.requestAnimationFrame(measure)\r\n }\r\n const sp = document.querySelector<HTMLElement>('[data-conversation-scroll]')\r\n const ro = typeof ResizeObserver === 'function' && sp !== null ? new ResizeObserver(schedule) : null\r\n if (ro !== null && sp !== null) ro.observe(sp)\r\n window.addEventListener('resize', schedule)\r\n const timer = window.setInterval(schedule, MEASURE_INTERVAL_MS)\r\n measure()\r\n return () => {\r\n if (raf !== 0) cancelAnimationFrame(raf)\r\n ro?.disconnect()\r\n window.removeEventListener('resize', schedule)\r\n window.clearInterval(timer)\r\n }\r\n }, [])\r\n\r\n const navigate = react.useCallback((entry: TimelineEntry): void => {\r\n if (entry.anchorKey === undefined) return\r\n if (sessionsService === undefined || sessionId === undefined) return\r\n void jumpToMessage(sessionsService, sessionId, entry.anchorKey, {\r\n followZonePx: LEGACY_FOLLOW_ZONE_PX,\r\n }).catch(() => {})\r\n }, [sessionsService, sessionId])\r\n\r\n // Warm-up: page older history in up to the rail's uncompressed capacity so\r\n // turns behind \"load earlier\" become reachable. Triggered by the reader\r\n // reaching for the rail, never on open.\r\n const warm = react.useCallback(async (): Promise<void> => {\r\n if (session === undefined) return\r\n if (warmingRef.current) return\r\n const sp = document.querySelector<HTMLElement>('[data-conversation-scroll]')\r\n if (sp === null) return\r\n warmingRef.current = true\r\n try {\r\n await warmHistory(session, sp, {\r\n followZonePx: LEGACY_FOLLOW_ZONE_PX,\r\n alive: () => aliveRef.current,\r\n countOf: () => entriesRef.current.length,\r\n capacity: railCapacityOf(railRef.current),\r\n })\r\n } finally {\r\n warmingRef.current = false\r\n }\r\n }, [session])\r\n\r\n // The official rail renders from two turns; a lone question has nothing to\r\n // navigate between.\r\n if (!enabled || sessionId === undefined || sessionsService === undefined || entries.length < 2) {\r\n return null\r\n }\r\n\r\n const count = entries.length\r\n const preview = previewIndex >= 0 && previewIndex < count ? entries[previewIndex] : undefined\r\n\r\n return reactDom.createPortal(\r\n <div className=\"dsbt-slot\" style={{ right: rightOffset }}>\r\n <nav\r\n className=\"dsbt-rail\"\r\n ref={railRef}\r\n style={{ ...railSize(count), ['--dsbt-band' as string]: `${band}px` }}\r\n aria-label={t('timeline.railLabel')}\r\n onPointerMove={(event) => {\r\n setPreviewIndex(indexAtPointer(count, event.currentTarget, event.clientY))\r\n }}\r\n onPointerLeave={() => { setPreviewIndex(-1) }}\r\n onPointerEnter={() => { void warm() }}\r\n onFocus={() => { void warm() }}\r\n onClick={(event) => {\r\n const index = indexForEvent(event.currentTarget, event.nativeEvent)\r\n const entry = entries[index]\r\n if (entry !== undefined) navigate(entry)\r\n }}\r\n >\r\n <div className=\"dsbt-marks\">\r\n {entries.map((entry, index) => {\r\n const active = index === activeIndex\r\n const showingPreview = index === previewIndex\r\n const cls = active\r\n ? 'dsbt-mark dsbt-markActive'\r\n : showingPreview ? 'dsbt-mark dsbt-markPreview' : 'dsbt-mark'\r\n return (\r\n <div key={entry.seq} className=\"dsbt-markPosition\" style={tickPosition(index, count)}>\r\n <button\r\n type=\"button\"\r\n className={cls}\r\n disabled={entry.anchorKey === undefined}\r\n aria-label={t('timeline.jump', { turn: index + 1 })}\r\n aria-current={active ? 'true' : undefined}\r\n onFocus={() => { setPreviewIndex(index) }}\r\n onBlur={() => { setPreviewIndex(-1) }}\r\n />\r\n </div>\r\n )\r\n })}\r\n </div>\r\n {preview !== undefined && (\r\n <div\r\n role=\"tooltip\"\r\n className=\"dsbt-preview\"\r\n style={tickPosition(previewIndex, count)}\r\n >\r\n {preview.text === '' ? t('timeline.noText') : preview.text}\r\n </div>\r\n )}\r\n </nav>\r\n </div>,\r\n document.body,\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, `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 two behaviours:\r\n *\r\n * 1. Smooth jump. The official `navigateToTurn` is one assignment\r\n * (`el.scrollTop += flowTop(row, el) - 24`); a click across a long\r\n * transcript teleports. Clicks are intercepted in the CAPTURE phase and\r\n * re-run through the shared glide instead.\r\n * 2. Reachable history. The official rail only lists turns the client has\r\n * already loaded, so anything behind the \"load earlier\" button has no mark\r\n * at all. Hovering or focusing the rail pages older history in — only up\r\n * to the rail's own uncompressed capacity, so the marks never degrade into\r\n * a solid bar — with the scroll position compensated so the reader's view\r\n * never moves.\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)\r\n * `scheduleActiveTurn()` to re-derive the rail's active mark. The glide\r\n * animates the real scrollport, so all three keep converging to the target\r\n * row while it runs — the only thing the interception skips is the official\r\n * handler's synchronous `setActiveTurn(item.turn)`, which its own\r\n * scroll-driven active derivation re-settles by the time the glide lands.\r\n *\r\n * Both go through the shared backend (jump.ts), which is why the legacy port\r\n * behaves identically.\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 rail owns pointer input for its whole column (marks carry\r\n * `pointer-events: none`), so one listener catches mouse and keyboard alike.\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 { CHATVIEW_FOLLOW_ZONE_PX, jumpToMessage, officialTargetTopFor, warmHistory } from './jump.ts'\r\nimport { indexForEvent, normalizeNavigationItems, railCapacityOf } from './rail-pointer.ts'\r\nimport type { OfficialNavigationItem, TimelineSessionsService } from './types.ts'\r\n\r\n/** Structural anchor of the official rail (its inline style carries the rail's own metrics). */\r\nexport const OFFICIAL_RAIL_SELECTOR = '[data-conversation-scroll] nav[style*=\"--turn-natural-height\"]'\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 sessionId?: string\r\n sessionsService?: TimelineSessionsService\r\n /** Kernel selector hook over the Chat snapshot (absent before 0.1.2). */\r\n useChat?: (selector: (snapshot: unknown) => unknown) => 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 two behaviour fixes.\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 { sessionId, sessionsService, useChat, enabled } = props\r\n\r\n // The official navigation index — the very array the official rail renders,\r\n // 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\r\n const session = sessionId !== undefined && sessionsService !== undefined\r\n ? sessionsService.binding(sessionId)?.session\r\n : undefined\r\n const [rail, setRail] = react.useState<HTMLElement | null>(null)\r\n\r\n // Mirrors so the listeners below stay identity-stable: `items` 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 itemsRef = react.useRef(items)\r\n itemsRef.current = items\r\n const railRef = react.useRef<HTMLElement | null>(rail)\r\n railRef.current = rail\r\n const aliveRef = react.useRef(true)\r\n const enabledRef = react.useRef(enabled)\r\n enabledRef.current = enabled\r\n const warmingRef = react.useRef(false)\r\n\r\n react.useEffect(() => {\r\n aliveRef.current = true\r\n return () => { aliveRef.current = false }\r\n }, [])\r\n\r\n // Locate the official rail. It mounts with the chat view and is replaced\r\n // whenever the view remounts, so it is polled rather 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 = document.querySelector<HTMLElement>(OFFICIAL_RAIL_SELECTOR)\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: OfficialNavigationItem): void => {\r\n if (sessionsService === undefined || sessionId === undefined) return\r\n // Scope the bottom-follow detach to the column this rail belongs to: the\r\n // official rail is mounted, so its scrollport is known and the other\r\n // columns need no nudge.\r\n const scrollport = rail === null ? null : rail.closest<HTMLElement>('[data-conversation-scroll]')\r\n void jumpToMessage(sessionsService, sessionId, item.anchorKey, {\r\n followZonePx: CHATVIEW_FOLLOW_ZONE_PX,\r\n targetTop: officialTargetTopFor,\r\n scrollport,\r\n }).catch(() => {})\r\n }, [sessionsService, sessionId, rail])\r\n\r\n /**\r\n * Page older history in until the rail is full or the log is exhausted.\r\n * Each page is compensated so the reader's view stays put (see\r\n * compensatedLoadOlder for why the kernel cannot do this for us).\r\n */\r\n const warm = react.useCallback(async (): Promise<void> => {\r\n if (session === undefined || rail === null) return\r\n if (warmingRef.current) return\r\n const scrollport = rail.closest<HTMLElement>('[data-conversation-scroll]')\r\n if (scrollport === null) return\r\n warmingRef.current = true\r\n try {\r\n await warmHistory(session, scrollport, {\r\n followZonePx: CHATVIEW_FOLLOW_ZONE_PX,\r\n // Stop also when the persisted toggle flipped off mid-run (the\r\n // pointer/focus listeners are re-attached per enabled, but a run\r\n // already in flight has no other checkpoint).\r\n alive: () => aliveRef.current && enabledRef.current,\r\n countOf: () => itemsRef.current.length,\r\n capacity: railCapacityOf(rail),\r\n })\r\n } finally {\r\n warmingRef.current = false\r\n }\r\n }, [session, rail])\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.\r\n react.useEffect(() => {\r\n if (!enabled || rail === null) return\r\n const onClick = (event: MouseEvent): void => {\r\n const index = indexForEvent(rail, event)\r\n const item = itemsRef.current[index]\r\n if (item === 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 // Warm-up trigger: pointer or keyboard intent on the rail. Nothing pages\r\n // until the reader actually reaches for the rail.\r\n react.useEffect(() => {\r\n if (!enabled || rail === null) return\r\n const start = (): void => { void warm() }\r\n rail.addEventListener('pointerenter', start)\r\n rail.addEventListener('focusin', start)\r\n return () => {\r\n rail.removeEventListener('pointerenter', start)\r\n rail.removeEventListener('focusin', start)\r\n }\r\n }, [enabled, rail, warm])\r\n\r\n return null\r\n}\r\n","/**\r\n * Dock-slot entry for the conversation timeline: detects which frontend the\r\n * running kernel supports and mounts exactly one of them.\r\n *\r\n * Detection is a capability check on the slot props, not a version compare:\r\n * the official turn rail is rendered from the very index `ui-chat` publishes\r\n * as a session hook (`useChat(s => s.navigation.items())`, dsh >= 0.1.2), so\r\n * the presence of that hook IS the presence of the official rail. Comparing\r\n * version strings would break on pre-releases, forks, and deployments that\r\n * mount a different conversation target.\r\n *\r\n * - `useChat` present -> 'enhance': the official rail stays on screen and\r\n * OfficialTimelineEnhancer improves only its behaviour.\r\n * - `useChat` absent -> 'legacy': no official rail exists, so this plugin\r\n * renders its own port of it from the shared backend.\r\n *\r\n * The detected mode is published to ./mode-store.ts because the settings row\r\n * (root-scoped, no session) cannot probe the kernel itself but must switch its\r\n * copy between \"conversation timeline\" and \"conversation timeline enhancement\".\r\n */\r\n\r\nimport react from 'react'\r\nimport { settingsClient } from '../settings-client.ts'\r\nimport { LegacyTimelineRail } from './legacy-rail.tsx'\r\nimport { OfficialTimelineEnhancer } from './official-enhance.tsx'\r\nimport { reportTimelineMode } from './mode-store.ts'\r\nimport type { TimelineMode, TimelineSessionsService } from './types.ts'\r\n\r\n/** Props the dock slot delivers. */\r\nexport interface TimelineBridgeProps {\r\n sessionId?: string\r\n sessionsService?: TimelineSessionsService\r\n /** Kernel selector hook over the Chat snapshot; absent before 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 the legacy rail.\r\n */\r\nexport function TimelineBridge(props: TimelineBridgeProps): react.ReactElement {\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 rail renders\r\n // even if the section errored.\r\n const enabled = settings.value?.timeline !== false && settings.status !== 'loading'\r\n\r\n const mode: TimelineMode = useChat === undefined ? 'legacy' : 'enhance'\r\n react.useEffect(() => { reportTimelineMode(mode) }, [mode])\r\n\r\n if (mode === 'enhance') {\r\n return react.createElement(OfficialTimelineEnhancer, { ...rest, useChat, enabled, t })\r\n }\r\n return react.createElement(LegacyTimelineRail, { ...rest, enabled, t })\r\n}\r\n\r\nexport {\r\n LEGACY_FOLLOW_ZONE_PX,\r\n CHATVIEW_FOLLOW_ZONE_PX,\r\n centeredScrollTopFor,\r\n compensatedLoadOlder,\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 warmHistory,\r\n type JumpOptions,\r\n type TargetTopResolver,\r\n type WarmOptions,\r\n} from './jump.ts'\r\nexport {\r\n DEFAULT_RAIL_CAPACITY,\r\n RAIL_INSET_PX,\r\n TICK_SPACING_PX,\r\n indexAtPointer,\r\n indexForEvent,\r\n normalizeNavigationItems,\r\n railCapacityOf,\r\n railInsetOf,\r\n} from './rail-pointer.ts'\r\nexport {\r\n chatNodesOf,\r\n collectMessages,\r\n hiddenSeqsOfChat,\r\n inputAnchorKeyOf,\r\n normalizeProjectedTimeline,\r\n railMessages,\r\n rewindHiddenSeqsOfChat,\r\n rewindMarkedSeqsOfChat,\r\n rewindSpansOfChat,\r\n rewindTargetOfCommand,\r\n rewindTargetOfOutcome,\r\n TIMELINE_PROJECTION_KEY,\r\n} from './source.ts'\r\nexport {\r\n OFFICIAL_RAIL_SELECTOR,\r\n OfficialTimelineEnhancer,\r\n type OfficialTimelineEnhancerProps,\r\n} from './official-enhance.tsx'\r\nexport { LegacyTimelineRail, type LegacyTimelineRailProps } from './legacy-rail.tsx'\r\nexport { injectTimelineCss, TIMELINE_CSS, TIMELINE_CSS_TAG } from './legacy-rail-css.ts'\r\nexport { reportTimelineMode, subscribeTimelineMode, timelineMode } from './mode-store.ts'\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 OfficialNavigationItem,\r\n TimelineEntry,\r\n TimelineMode,\r\n TimelineSessionHandle,\r\n TimelineSessionsService,\r\n} from './types.ts'\r\n","/**\n * Browser half of the background plugin: paints the background behind the\n * whole app frame, provides the settings surface (a live row inside the\n * official General settings section), and renders the conversation timeline\n * rail (DeepSeek ScrollNav style) on the same frosted-glass system. Data\n * flows through the plugin's own host route (`/api/bg-wallpaper/*`), not the\n * settings RPC. Retraction discipline mirrors the skin plugins: every\n * property and DOM node the painter owns is restored on dispose.\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport { backgroundPainter, paintBackground } from './backdrop.ts'\nimport { installGlassBridge } from './glass-registry.ts'\nimport { en, zh } from './locales.ts'\nimport { BackgroundSettingsRow } from './SettingsRow.tsx'\nimport { settingsClient } from './settings-client.ts'\nimport { TimelineBridge, clearLegacyMarks } from './timeline/index.tsx'\nimport type {} from './types.ts'\n\n/**\n * Service injections this entry declares: `slots` + `locale` for the settings\n * surface and the timeline slot seat, and `sessions` so the timeline rail can\n * enumerate user messages and jump between them. The settings surface itself\n * talks to the plugin's own host route over plain same-origin fetch.\n */\nexport const inject = ['slots', 'locale', 'sessions']\n\n/**\n * Client plugin body: paint the persisted background live, register the\n * General-settings row, and mount the conversation timeline rail.\n * @param ctx - client cordis context (slots/locale/sessions injected).\n */\nexport function apply(ctx: Context): void {\n // Publish the third-party frosted-glass registry bridge FIRST so an\n // eager consumer can register before the first snapshot repaints; the\n // bridge lives for the whole fiber and dies with it (see the dispose below).\n const glassBridge = installGlassBridge()\n // One-way sweep of the bookmark rows pre-0.5 versions left in localStorage\n // (the rail reuses the official UI now, which has no bookmarks).\n clearLegacyMarks()\n // Paint from the plugin's own transport; repaint on every snapshot change.\n const paint = (): void => {\n const snapshot = settingsClient.getSnapshot()\n const value = snapshot.status === 'ready' ? snapshot.value : undefined\n if (value === undefined || !value.enabled) {\n backgroundPainter.dispose()\n return\n }\n paintBackground(value)\n }\n const unsubscribe = settingsClient.subscribe(paint)\n void settingsClient.load()\n\n ctx.effect(() => ctx.locale.register('ui-background', { zh, en }), 'deepseek-harness-background: dictionaries')\n\n // Live settings row in the General section (same slot as the Appearance row).\n ctx.slots.inject('settings.general.item', () => ctx.slots.register({\n name: 'settings.general.item',\n id: 'deepseek-harness-background',\n order: 600,\n locale: 'ui-background',\n }, BackgroundSettingsRow))\n\n // Conversation timeline: takes a per-session dock seat only to bind its\n // lifecycle — the frontend is chosen per running kernel (official rail\n // enhanced in place, or this plugin's own port of it). The sessions service\n // is handed to the component through the registration's inject face —\n // resolved lazily inside the factory so a runtime-side service rebuild can\n // never leave the rail holding a stale reference.\n ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: 'deepseek-harness-background.timeline',\n order: 45,\n locale: 'ui-background',\n inject: () => ({ sessionsService: ctx.sessions }),\n }, TimelineBridge))\n\n ctx.effect(() => () => {\n unsubscribe()\n glassBridge.dispose()\n backgroundPainter.dispose()\n }, 'deepseek-harness-background: background surface')\n}\n"],"x_google_ignoreList":[5],"mappings":"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"}
|