deepseek-harness-background 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,168 +1,172 @@
1
- <p align="center">
2
- <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="docs/banner-dark.svg">
4
- <img src="docs/banner.svg" alt="DeepSeek Harness Background" width="720">
5
- </picture>
6
- </p>
7
-
8
- # DeepSeek Harness Background
9
-
10
- [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
11
- [![GitHub stars](https://img.shields.io/github/stars/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/stargazers)
12
- [![GitHub release](https://img.shields.io/github/v/release/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/releases)
13
- [![npm version](https://img.shields.io/npm/v/deepseek-harness-background?style=flat-square&logo=npm&label=npm)](https://www.npmjs.com/package/deepseek-harness-background)
14
- [![npm downloads](https://img.shields.io/npm/dt/deepseek-harness-background?style=flat-square&logo=npm)](https://www.npmjs.com/package/deepseek-harness-background)
15
- [![CI](https://img.shields.io/github/actions/workflow/status/HaoyueQin/deepseek-harness-background/ci.yml?style=flat-square&logo=githubactions&logoColor=white)](https://github.com/HaoyueQin/deepseek-harness-background/actions)
16
- [![GitHub issues](https://img.shields.io/github/issues/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/issues)
17
- [![GitHub last commit](https://img.shields.io/github/last-commit/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/commits)
18
- [![Commit activity](https://img.shields.io/github/commit-activity/t/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/graphs/commit-activity)
19
- [![Top language](https://img.shields.io/github/languages/top/HaoyueQin/deepseek-harness-background?style=flat-square&logo=typescript)](https://github.com/HaoyueQin/deepseek-harness-background)
20
- [![Repo size](https://img.shields.io/github/repo-size/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background)
21
- [![License](https://img.shields.io/github/license/HaoyueQin/deepseek-harness-background?style=flat-square)](LICENSE)
22
-
23
- [English](README.md) | [中文](README.zh.md)
24
-
25
- A custom **background image plugin** for the DeepSeek Harness Web GUI (`dsh web`): upload a local picture, or paste an image URL, and render it behind the whole app surface with adjustable **opacity**, **readability scrim**, **panel transparency** and **frosted-glass blur** — everything live-previewed inside the settings panel and committed automatically on release.
26
-
27
- The look (fixed wallpaper layer + theme-aware scrim + translucent glass panels driven by `--dsw-*` design tokens) is modeled on the community `dsh-wallpaper-engine` implementation.
28
-
29
- <p align="center">
30
- <img src="docs/demo.svg" alt="demo: upload a wallpaper, the whole app turns to frosted glass, then jump between turns on the timeline">
31
- </p>
32
-
33
- ## Screenshots
34
-
35
- | | |
36
- | --- | --- |
37
- | **Home** | <img src="docs/images/home.jpg" alt="Custom background on the home screen" width="640"> |
38
- | **Conversation** | <img src="docs/images/conversation.jpg" alt="Custom background behind the conversation" width="640"> |
39
- | **Settings** | <img src="docs/images/settings.jpg" alt="Background settings row with live preview" width="640"> |
40
-
41
- ## Features
42
-
43
- - **Local upload** — pick a JPG / PNG / WebP / GIF from your computer; the plugin stores it under the harness home and serves it over a same-origin route (admitted only when the declared MIME, detected signature and extension all agree).
44
- - **Paste a URL** — drop an `http(s)` image link and press Enter.
45
- - **In-panel live preview** — a preview surface at the top of the row renders the image + scrim + a frosted glass bubble; dragging any slider repaints it instantly.
46
- - **Stepped sliders** — ratio controls snap in **5% steps**, blur radii in 1/2px steps; dragging only repaints, **release commits** (one write per gesture, no jank).
47
- - **Five controls** — wallpaper opacity, readability scrim, panel opacity, frosted-glass blur, and wallpaper blur.
48
- - **Fit modes** — `cover` (fill, crop) or `contain` (whole image).
49
- - **Theme-aware scrim** — the light theme uses a white veil (lifts the art so dark text keeps contrast); the dark theme automatically switches to a black veil (dims the art so light text keeps contrast).
50
- - **Frosted glass (whitelisted)** — while a background is active, only the surfaces that float as small cards over the wallpaper turn into translucent glass (specular sheen + `backdrop-filter`): the composer card and message bubbles, code blocks / terminal / diff / tool-IO cards / skill & MCP call cards and inline code, the agent task strips and their takeover panels (approval / question / plan review), the chrome buttons (new session, composer plus, scroll-to-bottom), the load-earlier history button, the subagent lineage popover, the sidebar build badge, and the home hero "preview" badge — every glassed surface carries the full recipe (fill + sheen + blur), never translucency without frost. Reading surfaces — dialogs, the settings UI, menus, tooltips, toasts, hover fills and every accent (the send button stays blue) — keep their **official opaque paints** so nothing legible turns washy (the one documented exception: the turn rail's hover preview, which is rail chrome over the art and joins the glass sheet). The blur radius is driven by the glass-blur slider; `panelOpacity` at 100% restores the official paints on the whitelisted list too.
51
- - **Third-party glass registry** — any plugin can register its own panels into the same frosted-glass system through the published bridge (`window.__DSH_BACKGROUND_GLASS__` global + the `dsh-background-glass:ready` event): *token* mode adds the missing sheen + blur chain when the panel already fills with an overridden `--dsw-*` token, *fill* mode takes the fill over as well; every rule sits under the `data-dsh-bg-glass` gate so it toggles with the glass automatically. Consumers take zero dependencies and degrade gracefully when this plugin is absent; the whole bridge tears down with it. See [docs/GLASS_API.md](docs/GLASS_API.md).
52
- - **Conversation timeline** — the DeepSeek-web turn-navigation rail at the right edge of long conversations, one tick per question, with a hover/focus preview of that turn's prompt. Two frontends, one backend, chosen by what the running kernel supports:
53
- - **dsh ≥ 0.1.2 (enhancement)** — the kernel ships this rail already, so the plugin renders nothing of its own and only fixes its behaviour, scoped to what the running rail still lacks (a capability check on the rail's own published metrics, never a version compare). Clicks on a **loaded** mark become a **smooth glide** instead of the stock instant jump (ease-in-out, distance-paced, cancelled the moment the reader scrolls, `prefers-reduced-motion` places directly). On **0.1.2-alpha.1/2** the rail lists only loaded turns, so reaching for it also pages older history in — turns behind "load earlier" get a tick and become reachable, stopping at the rail's own uncompressed capacity, past which the official stylesheet would squeeze the marks into an unaimable bar. On **0.1.2-alpha.3+** the official rail rebuilt itself (fixed-pitch marks scrolling inside a frame, fed by the whole-log turn outline: every turn renders, unloaded ones page on demand), so the plugin narrows to the smooth glide alone — click indexes are resolved against the same merged ladder the kernel renders, marks outside the loaded window are left to the kernel's own jump, and the warm-up does not run. Because the official hover preview card paints an opaque fill, it joins the glass sheet under the wallpaper gate instead (same explicit-fill recipe as the composer card, driven by the glass-blur slider). The rail's active-mark state stays the kernel's own — the glide scrolls the real scrollport, so the stock scroll handler keeps updating it and the persisted scroll position.
54
- - **dsh ≤ 0.1.1 (ported rail)** — no official rail exists, so the plugin renders its own port of it (identical metrics, plus the same glass treatment), fed by the same backend.
55
- In both cases the bottom-follow state is detached one frame before the glide (streaming growth / turn-end re-renders cannot yank it back to the floor), messages withdrawn by a rewind are dropped automatically (the target parser accepts `seq 42` / `#42` / english-chinese verb forms plus structured fields, and recognizes third-party `data-dsh-rewind-hidden` node markers), and wherever this plugin pages history itself (the ported rail and the alpha.1/2 rail) every page compensates the prepended height so the reader's view never moves. Toggle it off with the timeline switch in the row.
56
- - **Persisted in the official settings document** (`$DSH_HOME/settings.yaml`), waits out restarts.
57
- - **Clean teardown** — disabling, clearing or uninstalling restores the original background exactly; the plugin only ever removes what it wrote.
58
-
59
- ## Install
60
-
61
- The plugin is a standard out-of-tree dsh bundle, published on npm:
62
-
63
- ```sh
64
- dsh plugin --profile web add deepseek-harness-background
65
- ```
66
-
67
- From a local checkout (development):
68
-
69
- ```sh
70
- dsh plugin --profile web add /path/to/deepseek-harness-background
71
- ```
72
-
73
- From a source checkout:
74
-
75
- ```sh
76
- pnpm dsh plugin --profile web add /path/to/deepseek-harness-background
77
- ```
78
-
79
- Or from git:
80
-
81
- ```sh
82
- dsh plugin --profile web add github:<you>/deepseek-harness-background#<commit>
83
- ```
84
-
85
- Restart to load it:
86
-
87
- ```sh
88
- dsh --profile web
89
- ```
90
-
91
- ## Usage
92
-
93
- 1. Start the Web UI (`dsh --profile web`) and open it in a browser.
94
- 2. Open **Settings** (bottom-left) → **General** → the **Custom Background** row (in the same area as the Appearance row).
95
- 3. **Upload** an image or **paste a URL** — the background applies immediately and the preview surface above updates in sync.
96
- 4. Tune the controls — sliders snap in steps and **commit on release**:
97
-
98
- | Control | Meaning |
99
- | --- | --- |
100
- | 不透明度 / Opacity | `0..100%` image opacity (5% steps); lowering it fades the wallpaper toward the surface. |
101
- | 遮罩 / Scrim | `0..95%` readability veil over the image (5% steps); white in light mode, black in dark mode. |
102
- | 面板不透明度 / Panel opacity | `0..100%` surface transparency (5% steps); at `100%` the official panels stay opaque (no glass). |
103
- | 毛玻璃模糊 / Glass blur | `0..40px` `backdrop-filter` blur on the translucent surfaces (1px steps). |
104
- | 壁纸模糊 / Wallpaper blur | `0..60px` blur of the wallpaper image itself (2px steps). |
105
- | 填充方式 / Fit | `cover` or `contain`. |
106
- | 会话时间线 / Timeline | on/off switch for the conversation timeline (default on). On dsh ≥ 0.1.2 the row is labelled 会话时间线增强 / *enhancement*: the official rail stays and this only improves its behaviour; turning it off restores stock behaviour. The copy follows the kernel generation — full enhancement on alpha.1/2, smooth-jump-only on alpha.3+. On older kernels the row is 会话时间线 / *timeline* and the switch controls the plugin's own ported rail. |
107
-
108
- 5. **清除背景** removes the background and restores the stock look.
109
-
110
- ## How it works
111
-
112
- - The **settings row** lives in the official General settings section (`settings.general.item` slot), next to the Appearance row. Its chrome uses only `--dsw-alias-*` design tokens (buttons / pills / segmented control / slider track match the official shell); sliders are native `input[type=range]` with 5% / 1–2px steps and release-commit.
113
- - The plugin's own host routes (`/api/bg-wallpaper/*`: `settings`, `upload`, `image/<id>`) read/write the section and serve uploads with same-origin + size caps + MIME/signature checks + a path-escape fence. A custom route family is used because the api-proxy settings allowlist does not expose third-party namespaces over the settings RPC.
114
- - The background is drawn as a fixed `z-index:-2` wallpaper layer plus a `z-index:-1` scrim on `body`, toggled by the `data-dsh-bg` attribute; the scrim switches white/black by `data-ds-dark-theme` in the injected stylesheet. Frosted glass is whitelist-scoped: only the whitelisted surfaces get `--dsw-*` surface-token overrides, while everything else is painted by explicit `data-dsh-bg-glass`-gated rules carrying the FULL recipe (fill + sheen + blur chain): the three chrome buttons, the load-earlier history button, the composer dock family (agent task strips TodoPanel / GoalBar / QueueDock and their takeover panels — approval, question, plan review — whose tokens turn translucent and need the blur added), the subagent lineage popover, the home hero preview badge and the sidebar build badge — every other official token and reading surface (menus, dialogs, tooltips, toasts) stays untouched.
115
- - The **timeline** is registered into the `conversation.input.dock` slot (per-session lifecycle). Mode detection is a capability check on the slot props rather than a version compare: the official rail is rendered from the very index `ui-chat` publishes as a session hook (`useChat(s => s.navigation.items())`, dsh >= 0.1.2), so the presence of that hook *is* the presence of the official rail — and it survives pre-releases, forks and deployments that mount a different conversation target. With the hook the plugin renders nothing and intercepts the official rail clicks in the **capture** phase (React 18 dispatches `onClick` from the root container during the bubble phase, so a capture listener on the rail runs first and `stopImmediatePropagation()` keeps the stock handler from ever firing) — resolving the click index against the ladder the kernel actually renders: the loaded items alone on the compressing alpha.1/2 rail, and on the alpha.3 frame-style rail the same outline-plus-loaded merge the kernel renders, with marks outside the loaded window (no anchor key) left to the kernel's own load-through jump — and while one of those kernel jumps is still paging (a mark pulses `aria-busy`) the plugin stands down entirely, because the kernel's loaded branch cancels its own pending jump before landing and an interception would bypass that cancellation; without the hook the plugin renders its own port of the rail, portaled to `body`. Both run the same jump engine. Data comes, fastest first, from a host-side session projection (`bgTimeline`, registered in `src/projection.ts`) that enumerates every user message of the whole session — including turns the conversation has not paged in yet — with the loaded chat-node window as fallback; that window is read duck-typed across both kernel generations, because 0.1.2 moved the Chat snapshot from `session.getSnapshot().chat` to `useChat()`, the one breaking change for this plugin. History warm-up runs only when the reader reaches for the rail — and only where it is still wanted: the compressing alpha.1/2 rail and the ported one, stopping at the rail uncompressed tick capacity (`floor((height - 12) / 10) + 1`); the alpha.3 frame-style rail lists every turn already and pages on demand itself, so warming would only fight the kernel's pager. Every page compensates the prepended height, because the host own prepend anchor is unreachable from outside. The ported rail carries the same **glass treatment as the enhanced official one** — the hover preview joins the frosted-glass sheet under the wallpaper gate (same explicit-fill recipe as the composer card) — so both frontends look identical to the reader.
116
- - The **third-party glass registry** (`src/client/glass-registry.ts`) publishes its bridge on client apply — before any repaint can race it: `register({ plugin, selectors, mode })` is idempotent per (plugin, mode, selector) triple and synthesizes explicit `body[data-dsh-bg-glass]`-gated recipe rules into a dedicated `<style data-plugin-css>` tag; selectors are structurally validated first (no `{ } ; @ < > ,`, no backslashes, 500-char cap) with per-selector warn-and-drop that never blocks valid siblings; disposing the client fiber retracts the whole bridge (stylesheet, entries, window key). Contract docs in `docs/GLASS_API.md` / `GLASS_API.zh.md`; behavior locked by `tests/glass-registry.spec.ts`.
117
- - Uploads live under `$DSH_HOME/deepseek-harness-background/` (content-addressed ids). Switching to a new image or clearing the background deletes the superseded upload file, so the directory does not accumulate dead images in normal use. (An upload that is never saved into the section — e.g. the tab closes right after an upload — can leave one orphaned file behind.) Disable / uninstall leaves nothing behind.
118
-
119
- ## Development
120
-
121
- ```sh
122
- pnpm install # first time; runs prepare (build)
123
- pnpm run typecheck # tsc
124
- pnpm test # vitest contract tests
125
- pnpm run build # tsdown: lib/index.js (host) + lib/client.js (browser bundle)
126
- ```
127
-
128
- ```
129
- deepseek-harness-background/ # the plugin repo (package name stays the npm-style id)
130
- ├── package.json # dsh.bundle.patch + dsh.client.inject declarations
131
- ├── cordis.patch.yml # inserts the deepseek-harness-background row into the web roster
132
- ├── tsdown.config.ts # official clientBundle preset
133
- ├── src/
134
- │ ├── index.ts # host half: ui-background namespace + API routes
135
- │ ├── routes.ts # /api/bg-wallpaper/{settings,upload,image/<id>}
136
- │ ├── schema.ts # host-side schemastery schema
137
- │ ├── settings.ts # constants/types shared with the client
138
- │ ├── projection.ts # bgTimeline session projection (full user-message index for the rail)
139
- │ ├── harness-home.ts # $DSH_HOME / ~/.dsh resolution
140
- │ └── client/
141
- │ ├── index.ts # painter lifecycle + settings row registration
142
- │ ├── backdrop.ts # fixed wallpaper layer + scrim + glass surface + preview vars
143
- │ ├── background-css.ts # injected stylesheet (layers, glass, light/dark scrim, variables)
144
- │ ├── glass-registry.ts # third-party glass registry (window bridge + gated rule synthesis)
145
- │ ├── timeline/
146
- │ │ ├── index.tsx # dock entry: mode dispatch + re-exports
147
- │ │ ├── official-enhance.tsx # dsh>=0.1.2: smooth-jump interception (+ warm-up on the alpha.1/2 rail)
148
- │ │ ├── legacy-rail.tsx # dsh<=0.1.1: ported official turn rail
149
- │ │ ├── legacy-rail-css.ts # rail stylesheet (dsbt- prefixed, official metrics)
150
- │ │ ├── jump.ts # shared jump engine (paging + glide + compensation)
151
- │ │ ├── source.ts # shared data source (projection + node window)
152
- │ │ ├── rail-pointer.ts # tick geometry & capacity (official metrics)
153
- │ │ ├── mode-store.ts # detected mode, for the settings row
154
- │ │ └── types.ts # shared shapes
155
- │ ├── SettingsRow.tsx # the General-settings row (preview surface + stepped sliders)
156
- │ ├── SettingsRow.module.css # row styles (official tokens)
157
- │ ├── settings-client.ts# fetch transport (read/write/upload)
158
- │ └── locales.ts # zh/en copy
159
- └── tests/ # schema, routes, apply (painter), settings-row contracts
160
- ```
161
-
162
- ## Activity
163
-
164
- [![HaoyueQin/deepseek-harness-background GitStock K-Line Chart](https://gitstock.org/HaoyueQin/deepseek-harness-background/stock.svg)](https://gitstock.org/HaoyueQin/deepseek-harness-background)
165
-
166
- ## License
167
-
168
- MIT
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/banner-dark.svg">
4
+ <img src="docs/banner.svg" alt="DeepSeek Harness Background" width="720">
5
+ </picture>
6
+ </p>
7
+
8
+ # DeepSeek Harness Background
9
+
10
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
11
+ [![GitHub stars](https://img.shields.io/github/stars/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/stargazers)
12
+ [![GitHub release](https://img.shields.io/github/v/release/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/releases)
13
+ [![npm version](https://img.shields.io/npm/v/deepseek-harness-background?style=flat-square&logo=npm&label=npm)](https://www.npmjs.com/package/deepseek-harness-background)
14
+ [![npm downloads](https://img.shields.io/npm/dt/deepseek-harness-background?style=flat-square&logo=npm)](https://www.npmjs.com/package/deepseek-harness-background)
15
+ [![CI](https://img.shields.io/github/actions/workflow/status/HaoyueQin/deepseek-harness-background/ci.yml?style=flat-square&logo=githubactions&logoColor=white)](https://github.com/HaoyueQin/deepseek-harness-background/actions)
16
+ [![GitHub issues](https://img.shields.io/github/issues/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/issues)
17
+ [![GitHub last commit](https://img.shields.io/github/last-commit/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/commits)
18
+ [![Commit activity](https://img.shields.io/github/commit-activity/t/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background/graphs/commit-activity)
19
+ [![Top language](https://img.shields.io/github/languages/top/HaoyueQin/deepseek-harness-background?style=flat-square&logo=typescript)](https://github.com/HaoyueQin/deepseek-harness-background)
20
+ [![Repo size](https://img.shields.io/github/repo-size/HaoyueQin/deepseek-harness-background?style=flat-square&logo=github)](https://github.com/HaoyueQin/deepseek-harness-background)
21
+ [![License](https://img.shields.io/github/license/HaoyueQin/deepseek-harness-background?style=flat-square)](LICENSE)
22
+
23
+ [English](README.md) | [中文](README.zh.md)
24
+
25
+ A custom **background image plugin** for the DeepSeek Harness Web GUI (`dsh web`): upload a local picture, or paste an image URL, and render it behind the whole app surface with adjustable **opacity**, **readability scrim**, **panel transparency** and **frosted-glass blur** — everything live-previewed inside the settings panel and committed automatically on release.
26
+
27
+ The look (fixed wallpaper layer + theme-aware scrim + translucent glass panels driven by `--dsw-*` design tokens) is modeled on the community `dsh-wallpaper-engine` implementation.
28
+
29
+ <p align="center">
30
+ <img src="docs/demo.svg" alt="demo: upload a wallpaper, the whole app turns to frosted glass, then jump between turns on the timeline">
31
+ </p>
32
+
33
+ ## Screenshots
34
+
35
+ | | |
36
+ | --- | --- |
37
+ | **Home** | <img src="docs/images/home.jpg" alt="Custom background on the home screen" width="640"> |
38
+ | **Conversation** | <img src="docs/images/conversation.jpg" alt="Custom background behind the conversation" width="640"> |
39
+ | **Settings** | <img src="docs/images/settings.jpg" alt="Background settings row with live preview" width="640"> |
40
+
41
+ ## Features
42
+
43
+ - **Local upload** — pick a JPG / PNG / WebP / GIF from your computer; the plugin stores it under the harness home and serves it over a same-origin route (admitted only when the declared MIME, detected signature and extension all agree).
44
+ - **Paste a URL** — drop an `http(s)` image link and press Enter.
45
+ - **In-panel live preview** — a preview surface at the top of the row renders the image + scrim + a frosted glass bubble; dragging any slider repaints it instantly.
46
+ - **Stepped sliders** — ratio controls snap in **5% steps**, blur radii in 1/2px steps; dragging only repaints, **release commits** (one write per gesture, no jank).
47
+ - **Five controls** — wallpaper opacity, readability scrim, panel opacity, frosted-glass blur, and wallpaper blur.
48
+ - **Fit modes** — `cover` (fill, crop) or `contain` (whole image).
49
+ - **Theme-aware scrim** — the light theme uses a white veil (lifts the art so dark text keeps contrast); the dark theme automatically switches to a black veil (dims the art so light text keeps contrast).
50
+ - **Frosted glass (whitelisted)** — while a background is active, only the surfaces that float as small cards over the wallpaper turn into translucent glass (specular sheen + `backdrop-filter`): the composer card and message bubbles, code blocks / terminal / diff / tool-IO cards / skill & MCP call cards and inline code, the agent task strips and their takeover panels (approval / question / plan review), the chrome buttons (new session, composer plus, scroll-to-bottom), the load-earlier history button, the subagent lineage popover, the sidebar build badge, and the home hero "preview" badge — every glassed surface carries the full recipe (fill + sheen + blur), never translucency without frost. Reading surfaces — dialogs, the settings UI, menus, tooltips, toasts, hover fills and every accent (the send button stays blue) — keep their **official opaque paints** so nothing legible turns washy (the one documented exception: the turn rail's hover preview, which is rail chrome over the art and joins the glass sheet). The blur radius is driven by the glass-blur slider; `panelOpacity` at 100% restores the official paints on the whitelisted list too.
51
+ - **Third-party glass registry** — any plugin can register its own panels into the same frosted-glass system through the published bridge (`window.__DSH_BACKGROUND_GLASS__` global + the `dsh-background-glass:ready` event): *token* mode adds the missing sheen + blur chain when the panel already fills with an overridden `--dsw-*` token, *fill* mode takes the fill over as well; every rule sits under the `data-dsh-bg-glass` gate so it toggles with the glass automatically. Consumers take zero dependencies and degrade gracefully when this plugin is absent; the whole bridge tears down with it. See [docs/GLASS_API.md](docs/GLASS_API.md).
52
+ - **Conversation timeline** — the DeepSeek-web turn-navigation rail at the right edge of long conversations, one tick per question, with a hover/focus preview of that turn's prompt. Two frontends, one backend, chosen by what the running kernel supports:
53
+ - **dsh ≥ 0.1.2-rc.1 (enhancement)** — the kernel ships this rail already, so the plugin renders nothing of its own and only fixes its behaviour (a capability check on the rail's own published metrics, never a version compare). Clicks on a **loaded** mark become a **smooth glide** instead of the stock instant jump (ease-in-out, distance-paced, cancelled the moment the reader scrolls, `prefers-reduced-motion` places directly). The rail's full ladder (`turnOutline` outline + loaded window) leaves **unloaded** marks to the kernel's own loadThrough jump — and while one of those kernel jumps is still paging (the mark pulses `aria-busy`) the plugin stands down entirely. Because the official hover preview card paints an opaque fill, it joins the glass sheet under the wallpaper gate instead (same explicit-fill recipe as the composer card, driven by the glass-blur slider); the tick column carries the DeepSeek-web edge dissolve (paint layer only, hit-testing untouched). The rail's active-mark state stays the kernel's own — the glide scrolls the real scrollport, so the stock scroll handler keeps updating it and the persisted scroll position. The bottom-follow state is detached one frame before the glide (streaming growth / turn-end re-renders cannot yank it back to the floor). Toggle it off with the timeline switch in the row.
54
+ - **Persisted in the official settings document** (`$DSH_HOME/settings.yaml`), waits out restarts.
55
+ - **Clean teardown** — disabling, clearing or uninstalling restores the original background exactly; the plugin only ever removes what it wrote.
56
+
57
+ ## Version compatibility
58
+
59
+ | Plugin release | Supported dsh versions |
60
+ | --- | --- |
61
+ | **Current (≥ 0.5.3)** | **dsh 0.1.2-rc.1 and 0.1.3-alpha.x (verified on alpha.2), plus 0.1.5-alpha.1** |
62
+ | 0.5.2 | dsh 0.1.1-rc.2 and 0.1.2-alpha.1 ~ alpha.5 |
63
+
64
+ ⚠️ **On older dsh (0.1.1-rc.2, 0.1.2-alpha.x or earlier) install an older
65
+ plugin release** (`dsh plugin --profile web add deepseek-harness-background@0.5.2`);
66
+ this plugin no longer adapts to the pre-rc.1 kernels.
67
+
68
+ ## Install
69
+
70
+ The plugin is a standard out-of-tree dsh bundle, published on npm:
71
+
72
+ ```sh
73
+ dsh plugin --profile web add deepseek-harness-background
74
+ ```
75
+
76
+ From a local checkout (development):
77
+
78
+ ```sh
79
+ dsh plugin --profile web add /path/to/deepseek-harness-background
80
+ ```
81
+
82
+ From a source checkout:
83
+
84
+ ```sh
85
+ pnpm dsh plugin --profile web add /path/to/deepseek-harness-background
86
+ ```
87
+
88
+ Or from git:
89
+
90
+ ```sh
91
+ dsh plugin --profile web add github:<you>/deepseek-harness-background#<commit>
92
+ ```
93
+
94
+ Restart to load it:
95
+
96
+ ```sh
97
+ dsh --profile web
98
+ ```
99
+
100
+ ## Usage
101
+
102
+ 1. Start the Web UI (`dsh --profile web`) and open it in a browser.
103
+ 2. Open **Settings** (bottom-left) → **General** → the **Custom Background** row (in the same area as the Appearance row).
104
+ 3. **Upload** an image or **paste a URL** — the background applies immediately and the preview surface above updates in sync.
105
+ 4. Tune the controls — sliders snap in steps and **commit on release**:
106
+
107
+ | Control | Meaning |
108
+ | --- | --- |
109
+ | 不透明度 / Opacity | `0..100%` image opacity (5% steps); lowering it fades the wallpaper toward the surface. |
110
+ | 遮罩 / Scrim | `0..95%` readability veil over the image (5% steps); white in light mode, black in dark mode. |
111
+ | 面板不透明度 / Panel opacity | `0..100%` surface transparency (5% steps); at `100%` the official panels stay opaque (no glass). |
112
+ | 毛玻璃模糊 / Glass blur | `0..40px` `backdrop-filter` blur on the translucent surfaces (1px steps). |
113
+ | 壁纸模糊 / Wallpaper blur | `0..60px` blur of the wallpaper image itself (2px steps). |
114
+ | 填充方式 / Fit | `cover` or `contain`. |
115
+ | 会话时间线 / Timeline | on/off switch for the conversation timeline (default on). On dsh ≥ 0.1.2-rc.1 the row is labelled 会话时间线增强 / *enhancement*: the official rail stays and this only improves its behaviour (smooth jumps for loaded turns); turning it off restores stock behaviour. |
116
+
117
+ 5. **清除背景** removes the background and restores the stock look.
118
+
119
+ ## How it works
120
+
121
+ - The **settings row** lives in the official General settings section (`settings.general.item` slot), next to the Appearance row. Its chrome uses only `--dsw-alias-*` design tokens (buttons / pills / segmented control / slider track match the official shell); sliders are native `input[type=range]` with 5% / 1–2px steps and release-commit.
122
+ - The plugin's own host routes (`/api/bg-wallpaper/*`: `settings`, `upload`, `image/<id>`) read/write the section and serve uploads with same-origin + size caps + MIME/signature checks + a path-escape fence. A custom route family is used because the api-proxy settings allowlist does not expose third-party namespaces over the settings RPC.
123
+ - The background is drawn as a fixed `z-index:-2` wallpaper layer plus a `z-index:-1` scrim on `body`, toggled by the `data-dsh-bg` attribute; the scrim switches white/black by `data-ds-dark-theme` in the injected stylesheet. Frosted glass is whitelist-scoped: only the whitelisted surfaces get `--dsw-*` surface-token overrides, while everything else is painted by explicit `data-dsh-bg-glass`-gated rules carrying the FULL recipe (fill + sheen + blur chain): the three chrome buttons, the load-earlier history button, the composer dock family (agent task strips TodoPanel / GoalBar / QueueDock and their takeover panels — approval, question, plan review — whose tokens turn translucent and need the blur added), the subagent lineage popover, the home hero preview badge and the sidebar build badge — every other official token and reading surface (menus, dialogs, tooltips, toasts) stays untouched.
124
+ - The **timeline** is registered into the `conversation.input.dock` slot (per-session lifecycle). Detection is a capability check on the slot props rather than a version compare: the official rail is rendered from the very index `ui-chat` publishes as a session hook (`useChat(s => s.navigation.items())`, dsh >= 0.1.2), so the presence of that hook *is* the presence of the official rail — and it survives pre-releases, forks and deployments that mount a different conversation target. With the hook the plugin renders nothing and intercepts clicks on the official rail's **loaded** marks in the **capture** phase (React 18 dispatches `onClick` from the root container during the bubble phase, so a capture listener on the rail runs first and `stopImmediatePropagation()` keeps the stock handler from ever firing) — resolving the click index against the merged ladder the kernel actually renders (`turnOutline` outline + loaded window, mirroring `mergeTurnRailItems` in ui-chat), with marks outside the loaded window (no anchor key) left to the kernel's own load-through jump — and while one of those kernel jumps is still paging (a mark pulses `aria-busy`) the plugin stands down entirely, because the kernel's loaded branch cancels its own pending jump before landing and an interception would bypass that cancellation. The official hover preview joins the glass sheet under the wallpaper gate (same explicit fill recipe as the composer card), and the tick column carries the DeepSeek-web edge dissolve. Supported baseline: dsh >= `0.1.2-rc.1`; older dsh (0.1.2-alpha.x and 0.1.1-rc.2 or earlier) installs an older plugin release (see Version compatibility).
125
+ - The **third-party glass registry** (`src/client/glass-registry.ts`) publishes its bridge on client apply — before any repaint can race it: `register({ plugin, selectors, mode })` is idempotent per (plugin, mode, selector) triple and synthesizes explicit `body[data-dsh-bg-glass]`-gated recipe rules into a dedicated `<style data-plugin-css>` tag; selectors are structurally validated first (no `{ } ; @ < > ,`, no backslashes, 500-char cap) with per-selector warn-and-drop that never blocks valid siblings; disposing the client fiber retracts the whole bridge (stylesheet, entries, window key). Contract docs in `docs/GLASS_API.md` / `GLASS_API.zh.md`; behavior locked by `tests/glass-registry.spec.ts`.
126
+ - Uploads live under `$DSH_HOME/deepseek-harness-background/` (content-addressed ids). Switching to a new image or clearing the background deletes the superseded upload file, so the directory does not accumulate dead images in normal use. (An upload that is never saved into the section — e.g. the tab closes right after an upload — can leave one orphaned file behind.) Disable / uninstall leaves nothing behind.
127
+
128
+ ## Development
129
+
130
+ ```sh
131
+ pnpm install # first time; runs prepare (build)
132
+ pnpm run typecheck # tsc
133
+ pnpm test # vitest contract tests
134
+ pnpm run build # tsdown: lib/index.js (host) + lib/client.js (browser bundle)
135
+ ```
136
+
137
+ ```
138
+ deepseek-harness-background/ # the plugin repo (package name stays the npm-style id)
139
+ ├── package.json # dsh.bundle.patch + dsh.client.inject declarations
140
+ ├── cordis.patch.yml # inserts the deepseek-harness-background row into the web roster
141
+ ├── tsdown.config.ts # official clientBundle preset
142
+ ├── src/
143
+ │ ├── index.ts # host half: ui-background namespace + API routes
144
+ │ ├── routes.ts # /api/bg-wallpaper/{settings,upload,image/<id>}
145
+ │ ├── schema.ts # host-side schemastery schema
146
+ │ ├── settings.ts # constants/types shared with the client
147
+ │ ├── harness-home.ts # $DSH_HOME / ~/.dsh resolution
148
+ │ └── client/
149
+ │ ├── index.ts # painter lifecycle + settings row registration
150
+ │ ├── backdrop.ts # fixed wallpaper layer + scrim + glass surface + preview vars
151
+ │ ├── background-css.ts # injected stylesheet (layers, glass, light/dark scrim, variables, read_image card)
152
+ │ ├── glass-registry.ts # third-party glass registry (window bridge + gated rule synthesis)
153
+ │ ├── timeline/
154
+ │ │ ├── index.tsx # dock entry: official-rail capability probe + re-exports
155
+ │ │ ├── official-enhance.tsx # dsh>=0.1.2-rc.1: smooth-jump interception for loaded marks
156
+ │ │ ├── jump.ts # shared jump engine (paging + glide)
157
+ │ │ ├── rail-pointer.ts # frame-rail tick geometry & full-ladder merge
158
+ │ │ └── types.ts # shared shapes
159
+ │ ├── SettingsRow.tsx # the General-settings row (preview surface + stepped sliders)
160
+ │ ├── SettingsRow.module.css # row styles (official tokens)
161
+ │ ├── settings-client.ts# fetch transport (read/write/upload)
162
+ │ └── locales.ts # zh/en copy
163
+ └── tests/ # schema, routes, apply (painter), settings-row contracts
164
+ ```
165
+
166
+ ## Activity
167
+
168
+ [![HaoyueQin/deepseek-harness-background GitStock K-Line Chart](https://gitstock.org/HaoyueQin/deepseek-harness-background/stock.svg)](https://gitstock.org/HaoyueQin/deepseek-harness-background)
169
+
170
+ ## License
171
+
172
+ MIT