dsh-any-background 0.3.7 → 0.3.8

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
@@ -4,7 +4,7 @@
4
4
  <a href="https://www.npmjs.com/package/dsh-any-background"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-any-background?color=4d6bfe"></a>
5
5
  <a href="https://www.npmjs.com/package/dsh-any-background"><img alt="npm monthly downloads" src="https://img.shields.io/npm/dm/dsh-any-background?color=4d6bfe"></a>
6
6
  <a href="https://github.com/Tkingxiao/dsh-any-background/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/npm/l/dsh-any-background?color=4d6bfe"></a>
7
- <a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions: 0.2.0-rc.1 ~ 0.2.1-alpha.2 and above" src="https://img.shields.io/badge/DSH-0.2.0--rc.1%20~%200.2.1--alpha.2%2B-4d6bfe" /></a>
7
+ <a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="Supported DSH versions: 0.2.0-rc.1 ~ 0.2.1-rc.2 and above" src="https://img.shields.io/badge/DSH-0.2.0--rc.1%20~%200.2.1--rc.2%2B-4d6bfe" /></a>
8
8
  <a href="https://github.com/topics/dsh-better-sidebar"><img alt="Plugin ecosystem: GitHub topic dsh-better-sidebar" src="https://img.shields.io/badge/plugin%20ecosystem-topic%20dsh--better--sidebar-4d6bfe" /></a><br /><br />
9
9
  <a href="https://github.com/Tkingxiao/dsh-any-background"><img src="https://img.shields.io/github/stars/Tkingxiao/dsh-any-background?style=social" alt="GitHub stars"></a>
10
10
  <a href="https://dsh.directory/plugins/tkingxiao/dsh-any-background"><img src="https://dsh.directory/badges/listed.svg" alt="dsh.directory listed"></a>
@@ -12,7 +12,7 @@
12
12
 
13
13
  English | [中文](README.zh.md)
14
14
 
15
- A **DeepSeek Harness** appearance plugin: custom theme color, background wallpaper (image / video / algorithmically generated), and fine-grained per-surface opacity & blur controls. Compatible with **DSH `0.2.0-rc.1` through `0.2.1-alpha.2` — and every host release above it** (official-Sidebar UI such as the "Theme" card enables itself where the host exposes the Sidebar registry extension point, and is skipped silently where it does not).
15
+ A **DeepSeek Harness** appearance plugin: custom theme color, background wallpaper (image / video / algorithmically generated), and fine-grained per-surface opacity & blur controls. Compatible with **DSH `0.2.0-rc.1` through `0.2.1-rc.2` — and every host release above it** (official-Sidebar UI such as the "Theme" card enables itself where the host exposes the Sidebar registry extension point, and is skipped silently where it does not).
16
16
 
17
17
  ---
18
18
 
@@ -73,11 +73,13 @@ A **DeepSeek Harness** appearance plugin: custom theme color, background wallpap
73
73
  - **Generated Dynamic Backgrounds** — Choose mesh gradient, Shader, or geometric patterns with adjustable spread, intensity, and seed locking.
74
74
  - **Per-surface Interface Opacity** — Independent sliders for the main background, sidebar, cards & panels (including the dropdowns and menus around the dialog), the input & controls (composer box, Cordis panel), the settings panel, the conversation text frame, the trajectory view, the right sidebar (or bettersidebar), produced / highlighted content, the header popovers (Agent Team panel, background-job list and the session-header dropdowns), and the header bar (the conversation's title / view-tab row).
75
75
  - **Per-surface Interface Blur** — Frosted-glass `backdrop-filter` blur (0–60 px) per surface, including a real backdrop on the composer, the Cordis panel and popover surfaces via stable host selectors. A slider at 0 leaves the surface's OWN host frost in place rather than clearing it: the host frosts its menus by default (`--dsw-menu-backdrop-filter: blur(40px) saturate(150%)`), and a plugin value only takes over once the slider moves — the same "compete for nothing at 0" rule the composer capsule follows.
76
- - **Left sidebar + the caption band above it** — On the Windows desktop shell the sidebar's opacity + blur pair drives the sidebar column AND the segment of the window caption strip that sits directly above it. The host paints that strip as one full-width box; the plugin cuts it at the sidebar's own published width (`--dsh-windows-sidebar-width`), so the left part carries the sidebar's material and frost and the rest stays the 「标题栏」 card's. Drag the sidebar wider, or collapse it to `0px`, and the cut moves with it.
76
+ - **Left sidebar + the caption band above it** — On the Windows desktop shell the sidebar's opacity + blur pair drives the sidebar column AND the segment of the window caption strip that sits directly above it. The host paints that strip as one full-width box; the plugin cuts it at the sidebar's own published width (`--dsh-windows-sidebar-width`), so the left part carries the sidebar's material and frost and the rest stays the 「标题栏」 card's. Drag the sidebar wider, or collapse it to `0px`, and the cut moves with it. The split is deliberate (issue #29, item 3): the left strip IS the sidebar column's own surface, so the header-bar sliders are not meant to reach it.
77
77
  - **Produced / Highlights** — Code blocks in conversation content (with their language banner), inline `code` highlight chips and produced chips share one opacity + blur slider. The opacity is the alpha of **each surface's own background color** (no second color stacked on top of the original), and the blur frosts that same layer so the wallpaper shows through the content.
78
78
  - **Right sidebar / bettersidebar surface** — One slider pair (`panelOpacity` / `blurs.panel`), two identities: without dsh-better-sidebar it reads "右方侧边栏" (Right sidebar) and drives the official right Sidebar's surface tokens and frosted blur (works on 0.1.5-rc.2 through 0.1.7); with dsh-better-sidebar installed it reads "bettersidebar" and takes over that plugin's bottom workbench panel (the official sidebar keeps responding too). The row is always visible.
79
79
  - **Header popovers** — The session-header dropdowns get their own opacity + blur pair: the Agent Team panel, the background-job list, the open-in-app / session-log menus and the subagent lineage tree. On 0.1.7 the open-in-app picker moved to a portal and the session-row menu became a dynamic slot, so the plugin observes the stable `conversation.session.header*` slot anchors and tags the open popover at runtime instead of relying on class shapes.
80
- - **Header bar (the session title row)** — The row carrying the session title, its actions and the view tabs gets a slider pair of its own. The host paints nothing there (the centre column's background shows straight through), so once a wallpaper is up the title and tabs sat on the raw picture while every masked surface around them did not. It now paints as a region tint, the same recipe as the chat column's card: the material is the plugin's main-background surface at this part's own opacity, so the bar reads as one band over the column rather than a second colour stacked on it, and the frost rides the row's own `backdrop-filter`, and because that filter turns the row into a stacking context the row also carries a compensating `z-index:10` — the overlays it hosts (the background-job list through `0.2.1-alpha.1`, the schedule task menu on every release) hang out of it with `z-index:100` and would otherwise be confined under the conversation. 10 clears the in-column chrome it overlaps (seat 9, back-to-bottom 8) and stays under every page-level surface (100 / 1000 / 1100), so nothing about the part's look changes when a menu opens. No `position` is written — the row is a flex item, where `z-index` applies on its own. See v0.3.7. The anchor is expressed in CSS (the row is the `conversation.session.header` slot's direct parent) instead of guessing a hashed class or tagging a host element at runtime; before `0.1.7-alpha.1` the host mounts that slot bare with no such element, so there the rule matches nothing and the part is simply inert. On the Windows desktop shell the same slider pair also drives the window caption band — except for the segment that sits above the left sidebar, which belongs to that sidebar's own pair (see **Left sidebar + the caption band above it**) — and the native minimize/maximize/close block is handed `transparent` so Windows paints no background of its own, leaving the band itself showing through. A blank session marks the row `headerBlank` (a placeholder left with only its padding) and the plugin skips claiming it.
80
+ - **Header bar (the session title row)** — The row carrying the session title, its actions and the view tabs gets a slider pair of its own. The host paints nothing there (the centre column's background shows straight through), so once a wallpaper is up the title and tabs sat on the raw picture while every masked surface around them did not. It now paints as a region tint, the same recipe as the chat column's card: the material is the plugin's main-background surface at this part's own opacity, so the bar reads as one band over the column rather than a second colour stacked on it, and the frost rides the row's own `backdrop-filter`, and because that filter turns the row into a stacking context the row also carries a compensating `z-index:10` — the overlays it hosts (the background-job list through `0.2.1-alpha.1`, the schedule task menu on every release) hang out of it with `z-index:100` and would otherwise be confined under the conversation. 10 clears the in-column chrome it overlaps (seat 9, back-to-bottom 8) and stays under every page-level surface (100 / 1000 / 1100), so nothing about the part's look changes when a menu opens. No `position` is written — the row is a flex item, where `z-index` applies on its own. See v0.3.7. On the Windows desktop shell the band it drives is split with the sidebar (previous bullet), dims itself while a modal is open (the host mask deliberately stops below the caption), and its generated segment is paint-only — `pointer-events:none` and no `app-region` declaration — so it can never swallow clicks meant for the native caption menubar (issue #29). It also paints below the
81
+ controls (`z-index:-1`) and carries its own ink (`--dsh-any-caption-ink`), so the sidebar toggle and the
82
+ 应用 / 编辑 menubar stay readable even when a low sidebar opacity makes the strip light. The anchor is expressed in CSS (the row is the `conversation.session.header` slot's direct parent) instead of guessing a hashed class or tagging a host element at runtime; before `0.1.7-alpha.1` the host mounts that slot bare with no such element, so there the rule matches nothing and the part is simply inert. On the Windows desktop shell the same slider pair also drives the window caption band — except for the segment that sits above the left sidebar, which belongs to that sidebar's own pair (see **Left sidebar + the caption band above it**) — and the native minimize/maximize/close block is handed `transparent` so Windows paints no background of its own, leaving the band itself showing through. A blank session marks the row `headerBlank` (a placeholder left with only its padding) and the plugin skips claiming it.
81
83
  - **Sidebar "Theme" page (dual mode)** — The same five pages (Color / Interface / Font / Background / Profiles) register into two surfaces: without dsh-better-sidebar, a "Theme" card is contributed to the **official right Sidebar's guide page** through its public extension points (`sidebarRightTabs` + the `sidebar.right.pane.tab` keyed seat); with dsh-better-sidebar installed, the page registers in that plugin's sidebar instead and the official guide card withdraws itself, so the two never duplicate. Settings panel, official sidebar and better-sidebar all share one page implementation and one state store — a change in any of them shows up everywhere. The shell adapts to the panel width, and a narrow panel tightens padding and falls back to a single column. On a host without the right Sidebar the registration silently never happens.
82
84
  - **Conversation View Cards** — The message list is wrapped in a translucent card automatically, and the trajectory page gets whole-page opacity & blur controls, letting the wallpaper shine through the content.
83
85
  - **Theme Export / Import** — One-click export to a self-contained `dsh-any-theme.json` (config + wallpaper, video embedded as a data URL) and import to restore it anywhere.
@@ -93,6 +95,17 @@ A **DeepSeek Harness** appearance plugin: custom theme color, background wallpap
93
95
 
94
96
  ## Changelog (latest two releases)
95
97
 
98
+ ### v0.3.8 (Issues #28/#29 and the desktop sidebar: the header-bar card persists, its band stops eating clicks, the mask reaches the caption, and the sidebar is one surface again)
99
+
100
+ - **The 「标题栏」 settings survive a restart now.** The host half whitelists every key before it writes (`normalizeConfig` → `theme-config.json`), and its copy of the config shape had never learned that card's three keys: `blurs.headerBar` fell back to 30 px, `strokes.headerBar` to width 0, and `headerBarOpacity` was dropped outright at write time — while the browser half restored the file with its own defaults (`clamp01(undefined, .5) → 50%`), so the card looked like it saved and then reset on every reload. The profile sanitizer had the same hole, and was also dropping `blurs.header` from saved profiles. All three keys now exist in the host's `PartBlurs`, `DEFAULT_CONFIG`, `STROKE_GROUPS`, both normalizers and `ProfileAppearance`; `npm test` boots the built host half against a throwaway `$DSH_HOME` and asserts the whole round trip (disk + reload + profile), including the "ignoring unknown config field" warning staying silent.
101
+ - **The caption band's left segment no longer swallows clicks.** Chromium collects `-webkit-app-region` items in paint order, and the drag annotation 0.3.7 put on the generated band segment (`::after`) overrode the `no-drag` holes the native caption menubar punches for its own buttons — the top-left navigation stopped responding while dragging still worked. The segment is paint-only now: `pointer-events:none` and no app-region declaration at all; the host's own `.frame::before` still carries the band's drag region.
102
+ - **A modal's dim now reaches the caption strip.** The host's mask deliberately starts below the caption (`--dsh-frame-chrome-top` = the titlebar height) so the native drag strip keeps working, which left the top 40 px bright once a wallpaper was showing. The band is the plugin's own paint, so it dims itself while a modal exists — `[data-windows-titlebar]:has([role="dialog"][aria-modal="true"])`, with the host's own `--dsw-alias-bg-mask-1` and no interaction change. Measured in a browser: `box-shadow: none` closed → inset mask while open → none again.
103
+ - **The sidebar toggle over the caption is readable again — and no longer painted over.** Two faults stacked on the same control. First, the band's left segment (0.3.7) was a positioned box painted AFTER the sidebar column in tree order, and that column becomes a stacking context whenever the sidebar blur is on, so the fixed toggle the host floats over the strip was being washed out by our own plate. Second, the toggle's ink comes from `--dsw-alias-label-secondary`, which the plugin flips white against the PICKED PALETTE, while the strip shows that palette at the **sidebar opacity** over the wallpaper: at 22% over a bright wallpaper the strip is light, and white ink disappeared (the reported desktop regression). The segment now paints at `z-index:-1` — above the wallpaper, below every control — and the caption strip derives its own ink from what it is actually painted on: past ~85% opacity the surface dominates and the palette direction decides, below it the wallpaper's brightness verdict does. It is published as `--dsh-any-caption-ink`, with a collapsed variant switched by the host's own `data-sidebar-collapsed` marker (a collapsed sidebar hands the whole strip back to the 标题栏 card), applied to the native caption menubar host and to the sidebar's fixed caption controls — the toggle and, while collapsed, the new-session button. Nothing else is re-inked, and the whole thing is paint only.
104
+ - **The band has two owners ON PURPOSE.** The segment above the left sidebar follows the 「侧栏」 pair (same material literal, same blur mirror); everything right of the sidebar width follows the 「标题栏」 pair. That is why moving the header-bar sliders only changes the right part (issue #29 item 3) — the boundary is `--dsh-windows-sidebar-width`, published by the host, so it follows a dragged or collapsed sidebar by itself.
105
+ - **The sidebar reads as ONE surface now, so its top strip and its body stop splitting.** `--dsw-specific-sidebar-fill` is a theme token the host ships OPAQUE, and three elements inside the sidebar's box each declare `background:var(--dsw-specific-sidebar-fill)` on top of one another: `AppFrame`'s `.sidebarCol` (the grid track), `SidebarRoot`'s `.root` (the shell inside it) and the workspace tree's `.fade` (a 24 px bottom gradient into that same fill). Over an opaque color that stacking is invisible; with the 「侧栏」 opacity slider it is not, and on the desktop it became a hard line — the caption strip above the sidebar is the plugin's own plate, painted with the same variable at the same alpha exactly once, so the strip sat at one density and the column under it at another (the reported desktop split). The token is now re-declared `transparent` for the column's descendants (`SIDEBAR_ONE_LAYER_RULE`), which leaves the outermost box as the single painter — the same owner the host already picked on macOS, where `SidebarRoot.module.css` clears its own `.root` fill for precisely this reason. Measured in a mock built from the installed bundles: before, column 0.22 over root 0.22 (≈0.39) against a 0.22 strip; after, one 0.22 everywhere. Bound by the CSS-Module class shape (`[class*="_sidebarCol"]`, identical in `0.2.0-rc.1` / `0.2.1-alpha.3` / `0.2.1-rc.2`), so a build that renames the track just keeps the untouched host look.
106
+ - **The plugin-install dialog's npm-registry panel keeps the host's opacity.** `<fieldset data-install-registry>` is `createPortal`ed to `document.body` as a `position:fixed; z-index:1100` panel and paints from `--dsw-alias-bg-layer-2` (itself and its toggle) and `--dsw-alias-bg-layer-3` (the expanded toggle, the typed-address input) — tokens the 「卡片」 slider owns globally, so the panel faded with the card slider while the host's own comment on that rule says the layer is opaque *"to keep the registry form controls readable over the dialog"*. It joins the exemption set `applyExemptDefaults` already pins for confirm dialogs and the session-row menu: the current theme color at alpha 1, no translucency, nothing else re-inked or re-tinted, and the pin spelled for the subtree as well because a custom property travels downward and the options live inside it.
107
+ - **The `0.2.1` line is one channel, and every checked build is named.** `0.2.1-alpha.3` moved the background-job list onto the shared `MenuSurface` — the surface root carries no `role`, and the fill plus the frost live on its `.material` child — so the runtime tagger now also claims body-level `[data-menu-material]` surfaces and the header selector set gained the matching arm; the older self-painting `<ul class*="_menu">` shape stays matched. Verified per tag from `alpha.3` through `rc.2`: no file the adapter speaks for moved (`design-platform.css`, `MenuSurface`, `MenuGroup`, `AppFrame`, `ConversationRoot`, `TaskMenu`, `preload-windows`, the five header slots). `SidebarRight.module.css` only gained `overflow:clip` on the panel wrapper (fixed floating children still escape), and the plugin-manager page was refactored with all three list anchors intact. The `0.2.1-alpha` and `0.2.1-rc` channels therefore collapse into one `0.2.1` channel covering `alpha.1`…`rc.2`, with a `SUPPORTED_RELEASES` row per build (each `exact`), `engines`/peers/`dsh.compatibility` naming them, and the folder renamed `v0-2-1`.
108
+
96
109
  ### v0.3.7 (Menus keep the host's frost at blur 0, the left sidebar owns the band above it, and the release table is 0.2.x only)
97
110
 
98
111
  - **The reported "the background-job list is under the conversation" was caused by the 「标题栏」 card's own frost, and the fix is a stacking order, not a state switch.** The session header row is not an empty strip: the host mounts its header chrome INTO it (`conversation.session.header.actions|.utilities`), and that chrome raises its overlays from inside the row. `ui-jobs/JobListAction` hangs `.menu{position:absolute; top:calc(100% + 5px); z-index:100}` off a `.root{position:relative}` on every release through `0.2.1-alpha.1` — the captured DOM's `style="left: 0px"` with no `top` and no `role` IS that in-place shape, and the `0.2.1-alpha.2` portal is what hid the symptom on the newest tag, not what fixed it. `ui-schedule/TaskMenu` (`role="menu"`, `position:absolute; z-index:100` whenever the primitive is not in portal mode) sits in the same row on every release in range. Putting `backdrop-filter` on the row makes the row a STACKING CONTEXT, so those `z-index:100` values stop being page-level and only order the overlays WITHIN the row; the row then paints at its own level — under the conversation body, the composer seat and `.root`'s background. The menu was never demoted by the host: it was confined by us. The row now carries a compensating `z-index:10` — above the in-column chrome the menu hangs over (`.seat` 9, back-to-bottom 8, footer 7, `.tabs` 1) and far below every page-level surface (portaled menus 100, modals 1000, stat / context panels 1100) — so the frost, the tint and the drag region never change state, and a portaled list still covers the title row exactly as the host intends. No `position` is written: the row is a flex item of `ConversationRoot .root{display:flex}`, where `z-index` applies on its own, which keeps the plugin out of deciding a host box's positioning (issue #24). The first attempt at this in the same release was a `:has()` valve that dropped the frost while a header overlay was open — it cleared the occlusion but re-tinted the whole title bar every time anyone opened a menu, which reads as the part's two sliders moving themselves. That attempt is gone.
@@ -105,14 +118,7 @@ A **DeepSeek Harness** appearance plugin: custom theme color, background wallpap
105
118
 
106
119
  - **The custom font now wins the cascade it was silently losing on `0.2.1-alpha.2`.** That tag moved the user font list into `--dsh-font-family-text` and made the host compose `body{--dsw-font-family:var(--dsh-font-family-text,…)}` in its own `base.css` — the same token, the same `body` selector and the same specificity as the rule this plugin's custom font writes, so which one applied became a question of which stylesheet landed last. The font rule now carries `!important`, the way the token sheet already does; code typography is left alone, as always.
107
120
 
108
- - **The release table now speaks only for `0.2.x`.** The `0.1.5-rc`, `0.1.6-alpha`, `0.1.7-alpha` and `0.1.7-rc` folders, channels and rows are gone: nothing in the plugin still asserts a fact checked against them, and a table nobody verifies is worse than no table. `engines.dsh`, the gated peers and `dsh.compatibility` now start at `0.2.0-rc.1`, which means an older host is refused at profile composition instead of being quietly styled with the wrong adapter — the DSHA launcher's bundled `0.1.5-rc.2` among them. `0.2.1-alpha.2` was verified by diffing its tag against the folder's facts (`design-platform.css`, `MenuSurface`, `MenuGroup`, the `--dsw-menu-backdrop-filter` consumers, `AppFrame`, the header slot keys and the right-sidebar panel — all unchanged) and joins that folder, renamed `v0-2-1-alpha-1-2`, so its verdict reads `exact`.
109
-
110
- ### v0.3.6 (The 「标题栏」 card takes over the Windows caption band and the native button block — issue #23)
111
-
112
- - **The 「标题栏」 (Header bar) card now drives the desktop shell's caption band too.** That band is `AppFrame.module.css`'s `.frame::before` — full width, exactly `--dsh-windows-titlebar-height` tall — and it filled from the host's sidebar token, outside the plugin's reach, so moving the sidebar slider turned the window's caption clear and dropped the title and the Application / Edit menubar onto the raw wallpaper (issue #23, symptom 1). The band now paints from this card's material at this card's opacity and frosts with the same blur variable, so one slider pair drives the session title row and the window caption and the two can never disagree.
113
- - **The native minimize/maximize/close block is no longer a block of its own.** Windows draws it from a single colour the desktop preload measures off a hidden probe (`--dsw-specific-sidebar-fill`, shipped over `dsh-desktop:windows-appearance` to `setTitleBarOverlay`). v0.3.5 pinned that probe to the material's opaque colour, which then disagreed with a band that had started following its sliders — a washed-out pale rectangle in light mode. The probe now reports `transparent`, so Windows paints **no** background there: the band already covers exactly those 40 px, so the block IS the band — one continuous surface, matching at every opacity. (Feeding the probe the card's paint along with its alpha was tried first: Windows then gives **each button its own background patch**, and the three stack into a lighter, visibly segmented block.) The glyphs are still measured separately off the same probe (`--dsw-alias-label-primary`), so they stay opaque and readable.
114
- - **The content column's top-left corner is squared.** `AppFrame` rounds `.centerCol`'s top-left by 16 px and clips its overflow; once the frame is cleared to show the wallpaper, that notch let the wallpaper through under the conversation header's first pixels. The plugin now zeroes that corner (`dab-center-flat`).
115
- - **A blank session is no longer claimed.** For a session-less or empty conversation the host marks the row `headerBlank` (`min-height:0`, no bottom rule), leaving only its padding and the corner control — deliberately a row that paints nothing. The plugin claimed it anyway and tinted it, so a box appeared exactly where there was meant to be no header bar. Claiming now skips that marker, and only one element can hold the class: a row that goes blank, or one the host replaces, has the class removed.
121
+ - **The release table now speaks only for `0.2.x`.** The `0.1.5-rc`, `0.1.6-alpha`, `0.1.7-alpha` and `0.1.7-rc` folders, channels and rows are gone: nothing in the plugin still asserts a fact checked against them, and a table nobody verifies is worse than no table. `engines.dsh`, the gated peers and `dsh.compatibility` now start at `0.2.0-rc.1`, which means an older host is refused at profile composition instead of being quietly styled with the wrong adapter — the DSHA launcher's bundled `0.1.5-rc.2` among them. `0.2.1-alpha.2` … `0.2.1-rc.2` were verified by diffing each tag against the folder's facts (`design-platform.css`, `MenuSurface`, `MenuGroup`, the `--dsw-menu-backdrop-filter` consumers, `AppFrame`, `ConversationRoot`, `TaskMenu`, the header slot keys and the right-sidebar panel — none of them moved) and join that folder, renamed `v0-2-1`, so each verdict reads `exact`.
116
122
 
117
123
  ## Installation
118
124
 
@@ -157,7 +163,7 @@ pnpm dsh web
157
163
  ## Compatibility
158
164
 
159
165
  - **[`dsh web`](https://github.com/deepseek-ai/deepseek-harness) `0.2.0-rc.1` ~ `0.2.1-alpha.2`, and everything above it with it** — `engines.dsh`, the gated `@deepseek-ai/dsh-*` `peerDependencies` and `dsh.compatibility.dsh` name the builds this plugin was checked against one by one (`0.2.0-rc.1`, `0.2.0-rc.2`, `0.2.1-alpha.1`, `0.2.1-alpha.2`) and close with an uncapped `>=0.2.0-rc.1`, so every later release loads. The uncapped umbrella is the deliberate choice: the host enforces exactly this peer list — a release it does not name is disabled before a single module is imported — so a range that runs out is a wall in front of a plugin that would have gone on working. An unseen release gets the closest verified adapter plus a log line saying how far from `exact` that is, and the worst a stale guess can cost is one slider no longer fading a surface the host has since moved; a version check that failed closed would have cost the whole plugin. `dsh.compatibility.dshReleases` records which builds were checked and how: every release in the list was checked by diffing its tag against the adapter folder that answers for it. That enforcement still means a `dsh web` restart when the plugin is installed or updated — the check runs while the profile is composed, not when the plugin is updated — but the uncapped umbrella is what spares a machine that has already moved to a newer host from needing a plugin release or a manual exact-version exemption (`dsh plugin allow-version`) first. The host release is resolved on the Node half at runtime, and features that depend on a specific host release channel (the right Sidebar's panel blur, the official Sidebar's "Theme" card) enable themselves only where the corresponding host structure exists; everything else behaves identically across the range. THE `0.1.5` / `0.1.6` / `0.1.7` ERA IS OUT OF THE SUPPORTED RANGE: those adapter folders asserted facts nothing re-checks any more, and the range above now starts at `0.2.0-rc.1`, so an older host is refused at composition rather than quietly styled with the wrong adapter.
160
- - **Isolated per-version adaptation**: the release is resolved on the Node half from the app manifest the process was composed from (`ctx.profileContext.installAnchor`, with the launcher's on-disk layout behind it — the client context exposes no version) and, once handed down through the `read` RPC, is routed only by the front-layer adapter — `src/host-compat/` detects and buckets channels, while `src/client/host-compat/versions/` holds **one folder per set of verified facts** (`v0-2-0-rc-1` / `v0-2-0-rc-2` / `v0-2-1-alpha-1-2` / `unknown`), each describing that version's panel mechanics, header slot keys, plugin-page shape and where a menu's paint lives. A folder is keyed to what its facts cover, not to the shape of the version number: `0.2.0-rc.1` and `0.2.0-rc.2` are one prerelease channel, but rc.2 renamed the token a sticky menu group heading paints from, so the line carries two folders and the newer one is named for the build that opened it. A newer build whose facts did NOT move joins the folder that held instead of opening one of its own — `0.2.1-alpha.2` sits in `v0-2-1-alpha-1-2` for exactly that reason, and gains a `SUPPORTED_RELEASES` row so its verdict reads `exact` rather than `line`. Base code just asks the adapter questions (who owns the guide surface, which layer carries the blur, which token fades a menu) and never compares version strings. Another build on a verified patch line (`0.2.1-alpha.3` against a table checked through `alpha.2`) keeps that line's adapter, and a patch line nothing was checked against clamps to the nearest one with a log line saying so — which is the arm that now serves every release above `0.2.1-alpha.2`. Only a release that will not parse at all falls into `unknown` and probes the DOM shape instead of guessing (`:has()` dual arms); supporting a new host means adding one folder and registering it.
166
+ - **Isolated per-version adaptation**: the release is resolved on the Node half from the app manifest the process was composed from (`ctx.profileContext.installAnchor`, with the launcher's on-disk layout behind it — the client context exposes no version) and, once handed down through the `read` RPC, is routed only by the front-layer adapter — `src/host-compat/` detects and buckets channels, while `src/client/host-compat/versions/` holds **one folder per set of verified facts** (`v0-2-0-rc-1` / `v0-2-0-rc-2` / `v0-2-1` / `unknown`), each describing that version's panel mechanics, header slot keys, plugin-page shape and where a menu's paint lives. A folder is keyed to what its facts cover, not to the shape of the version number: `0.2.0-rc.1` and `0.2.0-rc.2` are one prerelease channel, but rc.2 renamed the token a sticky menu group heading paints from, so the line carries two folders and the newer one is named for the build that opened it. A newer build whose facts did NOT move joins the folder that held instead of opening one of its own — the whole `0.2.1` line (`alpha.1` … `rc.2`) sits in `v0-2-1` for exactly that reason, with one `SUPPORTED_RELEASES` row per checked build so each verdict reads `exact` rather than `line`. Base code just asks the adapter questions (who owns the guide surface, which layer carries the blur, which token fades a menu) and never compares version strings. Another build on a verified patch line (`0.2.1-rc.3` against a table checked through `rc.2`) keeps that line's adapter, and a patch line nothing was checked against clamps to the nearest one with a log line saying so — which is the arm that now serves every release above `0.2.1-rc.2`. Only a release that will not parse at all falls into `unknown` and probes the DOM shape instead of guessing (`:has()` dual arms); supporting a new host means adding one folder and registering it.
161
167
  - **[DSHA](https://github.com/DSH-APP/DSHA)** — DeepSeek Harness Android launcher (ROOT-free, Termux-free). Its bundled `dsh` is `0.1.5-rc.2`, BELOW this plugin's supported floor since v0.3.7: the peer gate refuses it at composition, so the plugin never loads there rather than restyling that shell with 0.2.x facts.
162
168
  - **[deepseek-harness-desktop](https://github.com/anywhere-labs/deepseek-harness-desktop)** — Supported
163
169
 
package/README.zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <a href="https://www.npmjs.com/package/dsh-any-background"><img alt="npm 版本" src="https://img.shields.io/npm/v/dsh-any-background?color=4d6bfe"></a>
5
5
  <a href="https://www.npmjs.com/package/dsh-any-background"><img alt="npm 月下载量" src="https://img.shields.io/npm/dm/dsh-any-background?color=4d6bfe"></a>
6
6
  <a href="https://github.com/Tkingxiao/dsh-any-background/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/npm/l/dsh-any-background?color=4d6bfe"></a>
7
- <a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本:0.2.0-rc.1 ~ 0.2.1-alpha.2 及其上" src="https://img.shields.io/badge/DSH-0.2.0--rc.1%20~%200.2.1--alpha.2%2B-4d6bfe" /></a>
7
+ <a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本:0.2.0-rc.1 ~ 0.2.1-rc.2 及其上" src="https://img.shields.io/badge/DSH-0.2.0--rc.1%20~%200.2.1--rc.2%2B-4d6bfe" /></a>
8
8
  <a href="https://github.com/topics/dsh-better-sidebar"><img alt="插件生态:GitHub topic dsh-better-sidebar" src="https://img.shields.io/badge/%E6%8F%92%E4%BB%B6%E7%94%9F%E6%80%81-topic%20dsh--better--sidebar-4d6bfe" /></a><br /><br />
9
9
  <a href="https://github.com/Tkingxiao/dsh-any-background"><img src="https://img.shields.io/github/stars/Tkingxiao/dsh-any-background?style=social" alt="GitHub stars"></a>
10
10
  <a href="https://dsh.directory/plugins/tkingxiao/dsh-any-background"><img src="https://dsh.directory/badges/listed.svg" alt="dsh.directory listed"></a>
@@ -12,7 +12,7 @@
12
12
 
13
13
  [English](README.md) | 中文
14
14
 
15
- 一个 **DeepSeek Harness** 外观插件:自定义主题色、背景壁纸(图片 / 视频 / 算法动态生成),以及逐表面的透明度与模糊度控制。兼容 **DSH `0.2.0-rc.1` 直至 `0.2.1-alpha.2`,其上所有版本一并放行**(官方右侧栏「主题」卡片等界面按宿主是否提供右侧栏注册表扩展点自动启用,缺失时静默跳过)。
15
+ 一个 **DeepSeek Harness** 外观插件:自定义主题色、背景壁纸(图片 / 视频 / 算法动态生成),以及逐表面的透明度与模糊度控制。兼容 **DSH `0.2.0-rc.1` 直至 `0.2.1-rc.2`,其上所有版本一并放行**(官方右侧栏「主题」卡片等界面按宿主是否提供右侧栏注册表扩展点自动启用,缺失时静默跳过)。
16
16
 
17
17
  ---
18
18
 
@@ -73,11 +73,11 @@
73
73
  - **动态生成背景** — 支持网格渐变、Shader、几何图案,可调节扩散范围、色彩强度并锁定种子。
74
74
  - **分部位界面透明度** — 主背景、侧边栏、卡片面板(含对话框周围的选项框/菜单)、输入框与控件(发送框、Cordis 插件面板)、设置面板、对话文本框、轨迹页、右方侧边栏(或 bettersidebar)、产出物/高亮内容、顶栏选项(Agent Team 面板、后台任务列表与会话头部下拉菜单),以及标题栏(会话顶部那条标题行 + 视图页签)各自独立滑块。
75
75
  - **分部位界面模糊度** — 每个界面部位可独立调整毛玻璃 `backdrop-filter` 模糊(0–60 px),并通过宿主的稳定选择器为发送框、Cordis 面板与弹出层提供真实背景模糊。滑块停在 0 时保留该表面**宿主自己的**磨砂,而不是把它抹掉:宿主默认就给菜单上 `--dsw-menu-backdrop-filter: blur(40px) saturate(150%)`,插件的数值只在滑块离开 0 后才接管——与发送框那条「为 0 就不抢」的规矩一致。
76
- - **左侧栏 + 它上方的标题条带** — 桌面壳(Windows)上,「侧栏」这对滑块现在同时驱动侧栏列,以及那条压在侧栏正上方的窗口标题条带分段。宿主把整条条带画成一个全宽的盒子(`.frame::before`),而宿主自己就把侧栏列宽发在帧元素上(`--dsh-windows-sidebar-width`);插件就按这个宽度切开条带:`::before` 被裁到该宽度右侧(继续保留「标题栏」卡片的材质、模糊与拖拽区),新增的 `::after` 用 `--dsh-any-op-sidebar`——也就是侧栏列本身用的那份颜色——画出左侧那段,并按侧栏模糊的根级镜像上霜。于是一对滑块管住侧栏与它上方那一条,拖动或折叠侧栏时切口跟着移动(宽度 `0px` 时整条条带重新归「标题栏」)。
76
+ - **左侧栏 + 它上方的标题条带** — 桌面壳(Windows)上,「侧栏」这对滑块现在同时驱动侧栏列,以及那条压在侧栏正上方的窗口标题条带分段。宿主把整条条带画成一个全宽的盒子(`.frame::before`),而宿主自己就把侧栏列宽发在帧元素上(`--dsh-windows-sidebar-width`);插件就按这个宽度切开条带:`::before` 被裁到该宽度右侧(继续保留「标题栏」卡片的材质、模糊与拖拽区),新增的 `::after` 用 `--dsh-any-op-sidebar`——也就是侧栏列本身用的那份颜色——画出左侧那段,并按侧栏模糊的根级镜像上霜。于是一对滑块管住侧栏与它上方那一条,拖动或折叠侧栏时切口跟着移动(宽度 `0px` 时整条条带重新归「标题栏」)。这个切分是有意的(issue #29 第 3 条):左段本就是侧栏列自己的表面,标题栏滑块不该够到它。
77
77
  - **产出物 / 高亮内容** — 对话正文里的代码块(含语言顶栏)、行内 `code` 高亮芯片与产出物 chip 共用一支滑块:透明度只**调制这些表面原本的背景色**(不会在旧背景上再叠一层新色),模糊度给同一层表面加磨砂,壁纸从内容后方透出来。
78
78
  - **右方侧边栏 / bettersidebar 表面** — 同一对滑块(`panelOpacity` / `blurs.panel`)按宿主环境自动切换目标:未安装 dsh-better-sidebar 时叫「右方侧边栏」,驱动官方右侧栏的表面令牌与毛玻璃模糊(范围内每个 release 均适用);安装了 dsh-better-sidebar 时改叫「bettersidebar」,接管其底部工作台面板(官方右侧栏同样生效)。该行现在始终显示。
79
79
  - **顶栏选项** — 会话头部的下拉浮层获得了独立的透明度 + 模糊滑块:Agent Team 面板、后台任务列表、「用其它应用打开」与「会话日志」菜单、子代理血缘树。由于这些浮层被 portal 到 `<body>`(与头部 DOM 断开),且 0.1.7 把「用其它应用打开」改成 portal、把会话行菜单改成动态槽位,插件改为在运行时监听稳定的 `conversation.session.header*` 槽位锚点来标记当前展开的浮层,不再依赖类名形状。
80
- - **标题栏(会话标题行)** — 会话顶部那条标题 / 操作 / 视图页签行终于有了自己的一对滑块。宿主自身不给它任何底色(中心列的底色直接透上来),所以壁纸一起来,标题与页签就直接压在图上、和周围被蒙住的表面割裂。现在它按「区块淡色」作画,配方与对话文本框那张卡一致:材质取插件主背景面的颜色、按本部件自己的透明度上色,读起来是压在列上的一条带,而不是叠上去的第二层颜色;模糊落在该行自己的 `backdrop-filter` 上;而这道霜会把行变成层叠上下文,所以行同时带上补偿性的 `z-index:10`——行内挂出的浮层(直到 `0.2.1-alpha.1` 的后台任务列表、每个 release 上都可能就地的日程任务菜单)本来用 `z-index:100`,否则会被关在行里、压到对话之下。10 高于它会重叠到的列内部件(输入位 9、回到底部 8),又远低于所有页面级表面(100 / 1000 / 1100),因此打开菜单时这一条的颜色和模糊都不会变;也不写 `position`——该行本是 flex 子项,`z-index` 天然生效。详见 v0.3.7。这一行是**运行时认领**的:插件从 `conversation.session.header*` 槽位锚点向上找到最近的那个 `<header>` 再打上自己的类(与卡片、帧同款做法),不猜哈希类名、也不依赖宿主把槽位摆在哪个层级;`0.1.7-alpha.1` 之前宿主把槽位裸挂在会话根上、没有这一行元素,那种宿主上插件什么都不会认领,部件自然无效果。桌面壳(Windows)上这一对滑块同时驱动窗口标题条带——**但左侧栏正上方的那一段除外**,那一段归「侧栏」这对滑块管(见下条);原生最小化/最大化/关闭那块改由取色探针报 `transparent`,让 Windows 不画自己的背景,露出的正是条带本身。空会话时宿主把该行标为 `headerBlank`(一条只留 padding 的占位),插件跳过认领。
80
+ - **标题栏(会话标题行)** — 会话顶部那条标题 / 操作 / 视图页签行终于有了自己的一对滑块。宿主自身不给它任何底色(中心列的底色直接透上来),所以壁纸一起来,标题与页签就直接压在图上、和周围被蒙住的表面割裂。现在它按「区块淡色」作画,配方与对话文本框那张卡一致:材质取插件主背景面的颜色、按本部件自己的透明度上色,读起来是压在列上的一条带,而不是叠上去的第二层颜色;模糊落在该行自己的 `backdrop-filter` 上;而这道霜会把行变成层叠上下文,所以行同时带上补偿性的 `z-index:10`——行内挂出的浮层(直到 `0.2.1-alpha.1` 的后台任务列表、每个 release 上都可能就地的日程任务菜单)本来用 `z-index:100`,否则会被关在行里、压到对话之下。10 高于它会重叠到的列内部件(输入位 9、回到底部 8),又远低于所有页面级表面(100 / 1000 / 1100),因此打开菜单时这一条的颜色和模糊都不会变;也不写 `position`——该行本是 flex 子项,`z-index` 天然生效。详见 v0.3.7。Windows 上它驱动的条带与侧栏分段(见上一条);模态打开时条带会跟着宿主蒙版自行变暗(宿主蒙版故意停在标题区下方);生成出来的那一段只负责绘制——`pointer-events:none` 且不声明 app-region——因此不会吞掉本该给原生标题栏菜单的点击(issue #29)。它还画在控件之下(`z-index:-1`),并自带墨色(`--dsh-any-caption-ink`):即使侧栏透明度很低、条带发浅,开合按钮与「应用 / 编辑」菜单也清楚可读。这一行是**运行时认领**的:插件从 `conversation.session.header*` 槽位锚点向上找到最近的那个 `<header>` 再打上自己的类(与卡片、帧同款做法),不猜哈希类名、也不依赖宿主把槽位摆在哪个层级;`0.1.7-alpha.1` 之前宿主把槽位裸挂在会话根上、没有这一行元素,那种宿主上插件什么都不会认领,部件自然无效果。桌面壳(Windows)上这一对滑块同时驱动窗口标题条带——**但左侧栏正上方的那一段除外**,那一段归「侧栏」这对滑块管(见下条);原生最小化/最大化/关闭那块改由取色探针报 `transparent`,让 Windows 不画自己的背景,露出的正是条带本身。空会话时宿主把该行标为 `headerBlank`(一条只留 padding 的占位),插件跳过认领。
81
81
  - **侧边栏「主题」页面(双模式)** — 同一套五页(色彩 / 界面 / 字体 / 背景 / 配置)注册到两处:未安装 dsh-better-sidebar 时,通过官方右侧栏的公开扩展点(`sidebarRightTabs` + `sidebar.right.pane.tab`)在**官方侧边栏**的引导页挂一张「主题」卡片,点开即五页;安装了 dsh-better-sidebar 时,改为在它的侧边栏注册同名页面,官方引导页上的卡片自动撤下,避免重复。两处(加设置面板共三处)共用同一套页面代码与同一份状态,改一处处处同步;外壳按面板宽度自适应,窄面板收紧内边距并回到单列。宿主没有右侧栏扩展点时静默不注册,不影响任何其它功能。
82
82
  - **对话视图卡片** — 消息列表自动包裹为半透明卡片,轨迹页可整页调节透明度与模糊,让壁纸从内容后方透出来。
83
83
  - **主题导出 / 导入** — 一键导出为自包含的 `dsh-any-theme.json`(配置 + 壁纸,视频以 data URL 内嵌),可随时导入还原。
@@ -93,6 +93,17 @@
93
93
 
94
94
  ## 更新日志(只保留最近两个版本)
95
95
 
96
+ ### v0.3.8(issue #28/#29 与桌面端侧栏:「标题栏」设置能落盘了,条带不再吞点击,模态蒙版够到标题区,侧栏重新是一个面)
97
+
98
+ - **「标题栏」的三项设置现在重启后还在。** 宿主半侧在写盘前会按白名单归一化(`normalizeConfig` → `theme-config.json`),而它那份配置形状一直没学会这张卡的三把键:`blurs.headerBar` 回落到 30px、`strokes.headerBar` 回落到 0 宽、`headerBarOpacity` 在写盘时被整条丢掉——浏览器半侧读回时又用自己的默认值回填(`clamp01(undefined,.5) → 50%`),于是看起来"保存成功、刷新即还原"。保存 Profile 的归一化器有同一个洞,还额外丢了 `blurs.header`。现在三把键都补进了宿主的 `PartBlurs`、`DEFAULT_CONFIG`、`STROKE_GROUPS`、两个归一化器和 `ProfileAppearance`;`npm test` 会用一个临时 `$DSH_HOME` 启起真正的宿主半侧,把「落盘 → 重读 → Profile」整条往返跑一遍,并断言不再出现 `ignoring unknown config field` 警告。
99
+ - **条带左段不再吞掉点击。** Chromium 按绘制顺序收集 `-webkit-app-region` 区域,0.3.7 给生成出来的那一段(`::after`)加的 drag 标注盖过了原生标题栏菜单为自己的按钮打出的 `no-drag` 洞——左上角导航点不动,而拖动窗口仍正常。这一段现在只负责绘制:`pointer-events:none`,并且完全不再声明 app-region;整条条带的拖拽区仍由宿主自己的 `.frame::before` 提供。
100
+ - **模态蒙版现在也能盖住标题条带。** 宿主的蒙版故意从标题栏下方开始(`--dsh-frame-chrome-top` = 标题栏高度),为的是保住原生拖拽条;结果壁纸一开,顶部 40px 就成了一条亮带。条带是本插件自己的绘制,所以模态存在时它自己变暗——`[data-windows-titlebar]:has([role="dialog"][aria-modal="true"])`,用的是宿主自己的 `--dsw-alias-bg-mask-1`,不改变任何交互。浏览器实测:关闭时 `box-shadow: none` → 打开时 inset 蒙版 → 关闭后恢复 none。
101
+ - **压在这条条带上的侧栏开合按钮重新看得见了,也不再被自身涂盖。** 同一个控件上叠了两个问题:其一,0.3.7 画的条带左段是一个定位盒子,在树序上**排在侧栏列之后**,而侧栏一开模糊就会成为层叠上下文——于是宿主固定在条带上的那个按钮被我们自己的板子冲淡;其二,按钮的墨色来自 `--dsw-alias-label-secondary`,插件是按**选定的主题色**把它翻成白色的,可条带显示的是该调色板在**侧栏透明度**下叠在壁纸上的结果:22% 不透明度压在明亮壁纸上,条带是浅色,白字就消失了(这次桌面端反馈的现象)。现在这一段以 `z-index:-1` 绘制——高于壁纸、低于所有控件;同时条带按它**实际被画在什么上**推导自己的墨色:不透明度约 85% 以上由表面色说了算(跟随调色板方向),低于此值由壁纸明暗判定说了算。结果发布为 `--dsh-any-caption-ink`,并带一个折叠态变体(用宿主自己的 `data-sidebar-collapsed` 标记切换——侧栏折叠后整条条带交还「标题栏」卡片),应用到原生标题栏菜单宿主,以及侧栏自己固定在条带上的控件(开合按钮,折叠时的新建会话按钮)。其它任何地方都不重新上色,且全程只是绘制层面。
102
+ - **条带是「两个主人」,这是设计。** 左侧栏上方那一段归「侧栏」这一对滑块(同一份材质、同一份模糊镜像);侧栏宽度右侧全部归「标题栏」。所以移动标题栏滑块只会改变右段(issue #29 第 3 条)——分界就是宿主发布的 `--dsh-windows-sidebar-width`,拖动或折叠侧栏时它自己会跟着走。
103
+ - **侧栏重新读作「一个表面」,它的顶部与下方不再分层。** `--dsw-specific-sidebar-fill` 是宿主以**不透明**发布的主题令牌,而侧栏这个盒子里有三个元素各自声明了 `background:var(--dsw-specific-sidebar-fill)` 互相叠涂:`AppFrame` 的 `.sidebarCol`(网格列)、其内 `SidebarRoot` 的 `.root`(外壳本身)、以及工作区树那条 `.fade`(24px 渐变到底色)。底色不透明时叠几层看不出来,一交给「侧栏」透明度滑块就不是了;桌面端尤其明显——侧栏上方那条标题带是本插件自己的板子,用同一个变量、同一个不透明度只画一次,于是条带一个浓度、下面的列另一个浓度(就是这次桌面端反馈的分层)。现在这个令牌在列的**后代**上被重新声明为 `transparent`(`SIDEBAR_ONE_LAYER_RULE`),让最外层那个盒子成为唯一的绘制者——这也是宿主自己在 macOS 上的选择,`SidebarRoot.module.css` 正是为此把 `.root` 的底色清空。用宿主安装产物搭的 mock 实测:修前列 0.22 叠 `.root` 0.22(≈0.39)对 0.22 的条带;修后处处都是同一个 0.22。绑定用的是 CSS-Module 的类名形状(`[class*="_sidebarCol"]`,`0.2.0-rc.1` / `0.2.1-alpha.3` / `0.2.1-rc.2` 三处一致),所以哪个构建改了名,就只是回到未改动的宿主样子。
104
+ - **插件安装对话框里的「npm 源」面板保持宿主的不透明度。** `<fieldset data-install-registry>` 被 `createPortal` 到 `document.body`,是一个 `position:fixed; z-index:1100` 的面板,底色取自 `--dsw-alias-bg-layer-2`(面板自身与它的开关)和 `--dsw-alias-bg-layer-3`(展开态开关、手填地址的输入框)——这些都是「卡片」滑块全局接管的令牌,于是这块面板跟着卡片滑块一起变透明;而宿主写在那条规则上的注释恰恰相反:这层之所以不透明,是为了"让源选择表单控件压在对话框上仍然可读"。它因此并入 `applyExemptDefaults` 已经在维护的那组豁免(确认对话框、会话行菜单):保持当前主题色、把 alpha 钉在 1,不撤模糊也不改字色,其它一律不动;钉的值同样要覆盖其子树,因为自定义属性只会向下传播,选项就在里面。
105
+ - **`0.2.1` 整条线合成一个渠道,并逐构建点名。** `0.2.1-alpha.3` 把后台任务列表改画到共享的 `MenuSurface` 上——表面根没有 `role`,底色与磨砂落到了它的 `.material` 子层——所以运行时标记器现在也会认领 body 级的 `[data-menu-material]` 表面,顶栏选择器组同步加了对应分支;更早那种自己上色的 `<ul class*="_menu">` 形状继续匹配。从 `alpha.3` 到 `rc.2` 逐 tag 核对:适配层发言的文件一个都没动(`design-platform.css`、`MenuSurface`、`MenuGroup`、`AppFrame`、`ConversationRoot`、`TaskMenu`、`preload-windows`、五个头部槽位)。`SidebarRight.module.css` 只给面板外层加了 `overflow:clip`(fixed 浮动子节点仍会逃逸),插件页重构过但三个列表锚点都在。于是 `0.2.1-alpha` 与 `0.2.1-rc` 合为一个 `0.2.1` 渠道,覆盖 `alpha.1`…`rc.2`,每个构建一条 `SUPPORTED_RELEASES` 行(均为 `exact`),`engines`/peer/`dsh.compatibility` 逐一点名,事实目录改名为 `v0-2-1`。
106
+
96
107
  ### v0.3.7(菜单在模糊为 0 时保留宿主磨砂;左侧栏接管其上方条带;版本表只讲 0.2.x)
97
108
 
98
109
  - **「后台任务列表被对话内容压住」的真正原因,是「标题栏」卡片自己给标题行加的霜;修法是补层级,不是切状态。** 会话标题行不是一条空带:宿主把头部控件挂进行内(`conversation.session.header.actions|.utilities`),而这些控件就从行内升起浮层。`ui-jobs/JobListAction` 在直到 `0.2.1-alpha.1` 的每个 release 上都是就地弹出——`.root{position:relative}` 下挂 `.menu{position:absolute; top:calc(100% + 5px); z-index:100}`;你抓的那段 DOM 里 `style="left: 0px"` 既没有 `top` 也没有 `role`,正是这个就地形状,而 `0.2.1-alpha.2` 的 portal 只是把症状在最新构建上藏住,并没有修好它。`ui-schedule/TaskMenu`(`role="menu"`,非 portal 模式时 `position:absolute; z-index:100` 就地)在范围内每个 release 都挂在同一行里。给这一行加 `backdrop-filter` 会把行变成 STACKING CONTEXT,于是那些 `z-index:100` 不再是页面级取值,只能在行内部相互排序;整行随后按自身层级绘制,落在对话正文、输入位和 `.root` 底色之下。菜单不是被宿主降级的,是被我们关起来的。现在行带上补偿性的 `z-index:10`——高于菜单会盖到的列内部件(`.seat` 9、回到底部 8、页脚 7、`.tabs` 1),又远低于所有页面级表面(portal 菜单 100、模态 1000、统计 / 上下文面板 1100)——于是磨砂、底色、拖拽区都不再切换状态,而 portal 出去的列表仍像宿主预期那样盖住标题行。不写 `position`:该行本来就是 `ConversationRoot .root{display:flex}` 的 flex 子项,`z-index` 天然生效,这样插件就不去替宿主的盒子决定定位(issue #24)。本 releases 里第一次的尝试是 `:has()` 让位门控——行内持有头部浮层时就把霜撤掉:遮挡确实解了,但每次有人打开头部菜单整条标题栏都会重新上色,看起来像那两个滑块自己在动。那个做法已删除。
@@ -105,14 +116,7 @@
105
116
 
106
117
  - **自定义字体不再在 `0.2.1-alpha.2` 上被宿主悄悄抢走。** 那个 tag 把用户字体列表搬进 `--dsh-font-family-text`,并让宿主在自己的 `base.css` 里合成 `body{--dsw-font-family:var(--dsh-font-family-text,…)}` —— 与本插件写的是同一个令牌、同一个 `body` 选择器、同一个优先级,于是谁生效变成了「谁的样式表排在后面」。字体规则现在带 `!important`,与令牌表的做法一致;代码字体照旧不碰。
107
118
 
108
- - **版本表现在只对 `0.2.x` 发言。** `0.1.5-rc`、`0.1.6-alpha`、`0.1.7-alpha`、`0.1.7-rc` 四个目录、渠道与行都删掉了:插件里已经没有任何一条事实是按它们核对的,而一张没人再核对的表比没有表更糟。`engines.dsh`、受门禁的 peer 与 `dsh.compatibility` 现在从 `0.2.0-rc.1` 起步,也就是说更旧的宿主会在 profile 组装时被**明确拒绝**,而不是被一份错的事实悄悄改版式——DSHA 里包的 `0.1.5-rc.2` 也一样。`0.2.1-alpha.2` 是按 tag 逐包比对核实的(`design-platform.css`、`MenuSurface`、`MenuGroup`、`--dsw-menu-backdrop-filter` 的消费者集合、`AppFrame`、头部槽位键与右侧栏面板——全部未变),它并入那个仍然成立的事实目录 `v0-2-1-alpha-1-2`,于是它的判定是 `exact`。
109
-
110
- ### v0.3.6(「标题栏」卡片接管 Windows 标题条带与原生按钮区,修 issue #23)
111
-
112
- - **「标题栏」卡片现在也驱动桌面壳的标题条带**:那条条带是 `AppFrame.module.css` 的 `.frame::before`,全宽、高度正好等于 `--dsh-windows-titlebar-height`,此前它按宿主的侧栏令牌上色、完全不归插件管——于是「侧栏」滑杆一动,窗口标题区就跟着变透明,标题与「应用 / 编辑」菜单栏直接压在裸壁纸上(issue #23 现象一)。现在条带的底色就是本卡片的材质在该卡片透明度下的取值,模糊取同一份模糊变量:一对滑块同时驱动会话标题行与窗口标题条带,两处不可能再对不上。
113
- - **原生最小化 / 最大化 / 关闭那块不再是自己一块**:那块由 Windows 绘制,颜色来自桌面 preload 从一个隐藏探针量到的 `--dsw-specific-sidebar-fill`(经 `dsh-desktop:windows-appearance` 交给主进程 `setTitleBarOverlay`)。v0.3.5 把探针钉回材质实色,于是它和已经会跟随滑杆变淡的条带对不上——浅色模式下是一块偏白的方块。这里换了更彻底的做法:探针直接报 `transparent`,让 Windows **不画**这块背景。条带本来就严丝合缝地覆盖这 40 px,于是按钮区露出来的就是条带本身,一整块连续,任何透明度下都自动一致。(中途试过把卡片材质连同 alpha 喂给探针:Windows 会改成给**每个按钮各画一块自己的背景**,三块半透明叠在条带上,反而变成三块更亮的小方块。)字形颜色仍单独取 `--dsw-alias-label-primary`,保持不透明可读。
114
- - **内容列左上角改成直角**:`AppFrame` 给 `.centerCol` 左上角 16 px 圆角并裁掉溢出,帧一旦被清成透明让壁纸透出来,那个缺口就在会话标题行的头几个像素下露出壁纸。插件现在把该角归零(`dab-center-flat`)。
115
- - **空白会话不再被误认领**:无会话 / 空会话时宿主把这一行标成 `headerBlank`(`min-height:0`、去掉下边框),只留 padding 与右侧那个角落控件——本意就是一条什么都不画的占位行。插件此前照样认领并给它上底色,于是「本该没有标题栏」的时候凭空冒出一个框。现在认领前先排除该标记,并且认领改为「只有一个持有者」:切到空白态或宿主换掉这一行时,旧类会被摘掉。
119
+ - **版本表现在只对 `0.2.x` 发言。** `0.1.5-rc`、`0.1.6-alpha`、`0.1.7-alpha`、`0.1.7-rc` 四个目录、渠道与行都删掉了:插件里已经没有任何一条事实是按它们核对的,而一张没人再核对的表比没有表更糟。`engines.dsh`、受门禁的 peer 与 `dsh.compatibility` 现在从 `0.2.0-rc.1` 起步,也就是说更旧的宿主会在 profile 组装时被**明确拒绝**,而不是被一份错的事实悄悄改版式——DSHA 里包的 `0.1.5-rc.2` 也一样。`0.2.1-alpha.2` … `0.2.1-rc.2` 逐个 tag 比对核实(`design-platform.css`、`MenuSurface`、`MenuGroup`、`--dsw-menu-backdrop-filter` 的消费者集合、`AppFrame`、`ConversationRoot`、`TaskMenu`、头部槽位键与右侧栏面板——一个都没动),全部并入那个仍然成立的事实目录 `v0-2-1`,于是每个构建的判定都是 `exact`。
116
120
 
117
121
  ## 安装
118
122
 
@@ -157,7 +161,7 @@ pnpm dsh web
157
161
  ## 兼容性
158
162
 
159
163
  - **[`dsh web`](https://github.com/deepseek-ai/deepseek-harness) `0.2.0-rc.1` ~ `0.2.1-alpha.2`,其上所有版本一并放行** — `engines.dsh`、受门禁的 `@deepseek-ai/dsh-*` `peerDependencies` 与 `dsh.compatibility.dsh` 先逐一列出本插件核对过的构建(`0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-alpha.1`、`0.2.1-alpha.2`),再以不设上界的 `>=0.2.0-rc.1` 收尾,让其后每一个 release 都能加载。不设上界是有意选的:宿主强制的正是这份 peer 列表——范围没写到的版本会在导入任何模块之前被整行禁用——一条写到头的范围等于在一款还能正常工作的插件前面砌墙。没见过的 release 会取最近的一份事实,并在日志里写清它离 `exact` 有多远;一份过期事实最坏的代价是某个滑块不再淡出宿主已经搬走的那个表面,而一道失败关闭的版本检查代价是整个插件。`dsh.compatibility.dshReleases` 记录哪些构建被核对过、怎么核对的:列表里的每个 release 都是按 tag 逐包比对其事实目录核实的。这道门禁也意味着安装或更新插件后仍需重启 `dsh web`——检查发生在 profile 组装时,而不是插件更新时——但不设上界的总括,正让已经升到更新宿主的机器不再需要先等插件发版(或手工授予精确版本豁免 `dsh plugin allow-version`)。宿主版本在运行时由 Node 半侧解析,依赖特定宿主版本渠道的功能(右侧栏面板模糊、官方侧栏「主题」卡片)只在对应宿主结构存在时启用,其余功能在整个范围内表现一致。**`0.1.5` / `0.1.6` / `0.1.7` 这一档已移出兼容范围**:那些事实目录断言的东西已经没有再被核对,而一张没人核对的表比没有表更糟;范围从 `0.2.0-rc.1` 起步,意味着更旧的宿主会在组装期被明确拒绝,而不是被一份错的适配事实悄悄改版式。
160
- - **版本适配隔离**:release 由 Node 半侧从进程自身那份 `@deepseek-ai/dsh/package.json` 读出(客户端上下文不暴露版本;`ctx.profileContext.installAnchor` 指不到时退回启动器的磁盘布局),经 `read` RPC 下发后只由前置适配层路由——`src/host-compat/` 负责探测与渠道归类,`src/client/host-compat/versions/` 下**每个事实集合一个目录**(`v0-2-0-rc-1` / `v0-2-0-rc-2` / `v0-2-1-alpha-1-2` / `unknown`),各自描述该版本的面板力学、头部槽位键、插件页形状,以及菜单的底色与磨砂落在哪一层。目录按它覆盖的事实命名,不按版本号的形状:`0.2.0-rc.1` 与 `0.2.0-rc.2` 同属一个预发布渠道,但 rc.2 改了吸顶菜单分组标题取色的令牌,于是这条补丁行上并着两个目录,较新的那个按开启它的那个构建命名。**事实没变的更新版本不新开目录,而是并入那份仍然成立的事实目录**——`0.2.1-alpha.2` 正因如此坐在 `v0-2-1-alpha-1-2` 里,并新增一条 `SUPPORTED_RELEASES` 行,让它的判定是 `exact` 而不是 `line`。底码只向适配层提问(引导页表面归谁、模糊挂在哪一层、菜单该重写哪些令牌),不比较版本字符串。同一补丁行内的其它构建(`0.2.1-alpha.3` 之于核对基准 `alpha.2`)用该行的档位;整条补丁行都不在已核对范围内才收边取最近的一档并写日志——`0.2.1-alpha.2` 之上的每一个 release 如今就是靠这一档加载的。只有解析不到 release 时归入 `unknown`,回退到按 DOM 形状探测(`:has()` 双臂)而不是猜一个版本;适配新宿主 = 新增一个版本目录并在注册表登记。
164
+ - **版本适配隔离**:release 由 Node 半侧从进程自身那份 `@deepseek-ai/dsh/package.json` 读出(客户端上下文不暴露版本;`ctx.profileContext.installAnchor` 指不到时退回启动器的磁盘布局),经 `read` RPC 下发后只由前置适配层路由——`src/host-compat/` 负责探测与渠道归类,`src/client/host-compat/versions/` 下**每个事实集合一个目录**(`v0-2-0-rc-1` / `v0-2-0-rc-2` / `v0-2-1` / `unknown`),各自描述该版本的面板力学、头部槽位键、插件页形状,以及菜单的底色与磨砂落在哪一层。目录按它覆盖的事实命名,不按版本号的形状:`0.2.0-rc.1` 与 `0.2.0-rc.2` 同属一个预发布渠道,但 rc.2 改了吸顶菜单分组标题取色的令牌,于是这条补丁行上并着两个目录,较新的那个按开启它的那个构建命名。**事实没变的更新版本不新开目录,而是并入那份仍然成立的事实目录**——整条 `0.2.1` 线(`alpha.1` … `rc.2`)正因如此都坐在 `v0-2-1` 里,每个核对过的构建各占一条 `SUPPORTED_RELEASES` 行,判定都是 `exact` 而不是 `line`。底码只向适配层提问(引导页表面归谁、模糊挂在哪一层、菜单该重写哪些令牌),不比较版本字符串。同一补丁行内的其它构建(`0.2.1-rc.3` 之于核对基准 `rc.2`)用该行的档位;整条补丁行都不在已核对范围内才收边取最近的一档并写日志——`0.2.1-rc.2` 之上的每一个 release 如今就是靠这一档加载的。只有解析不到 release 时归入 `unknown`,回退到按 DOM 形状探测(`:has()` 双臂)而不是猜一个版本;适配新宿主 = 新增一个版本目录并在注册表登记。
161
165
  - **[DSHA](https://github.com/DSH-APP/DSHA)** — DeepSeek Harness 安卓启动器(免 ROOT、免 Termux)。其包内 dsh 为 `0.1.5-rc.2`,自 v0.3.7 起**低于本插件的兼容下界**:peer 门禁会在组装期拒绝它,所以插件在那上面根本不加载,而不是拿 0.2.x 的事实去改版那个壳。
162
166
  ## 权限、副作用与边界
163
167
 
package/lib/client.js CHANGED
@@ -1032,12 +1032,32 @@ window.__ModuleLoader__.load({
1032
1032
  release: "0.2.0-rc.2"
1033
1033
  },
1034
1034
  {
1035
- channel: "0.2.1-alpha",
1035
+ channel: "0.2.1",
1036
1036
  release: "0.2.1-alpha.1"
1037
1037
  },
1038
1038
  {
1039
- channel: "0.2.1-alpha",
1039
+ channel: "0.2.1",
1040
1040
  release: "0.2.1-alpha.2"
1041
+ },
1042
+ {
1043
+ channel: "0.2.1",
1044
+ release: "0.2.1-alpha.3"
1045
+ },
1046
+ {
1047
+ channel: "0.2.1",
1048
+ release: "0.2.1-alpha.4"
1049
+ },
1050
+ {
1051
+ channel: "0.2.1",
1052
+ release: "0.2.1-alpha.5"
1053
+ },
1054
+ {
1055
+ channel: "0.2.1",
1056
+ release: "0.2.1-rc.1"
1057
+ },
1058
+ {
1059
+ channel: "0.2.1",
1060
+ release: "0.2.1-rc.2"
1041
1061
  }
1042
1062
  ];
1043
1063
  const UNKNOWN_HOST_INFO = {
@@ -1094,7 +1114,7 @@ window.__ModuleLoader__.load({
1094
1114
  * release is outside everything this plugin has seen.
1095
1115
  *
1096
1116
  * A release is matched by PATCH LINE before anything else: `0.2.1-alpha.3` and a
1097
- * future `0.2.1` stable both belong to the line verified at `0.2.1-alpha.1/2`, and
1117
+ * future `0.2.1` stable both belong to the line verified at `0.2.1-alpha.1…rc.2`, and
1098
1118
  * handing them that adapter beats giving up — the DOM probes only cover panel
1099
1119
  * mechanics, not the surface decisions the line is known for.
1100
1120
  *
@@ -1510,10 +1530,10 @@ window.__ModuleLoader__.load({
1510
1530
  };
1511
1531
  }
1512
1532
  //#endregion
1513
- //#region src/client/host-compat/versions/v0-2-1-alpha-1-2/adapter.ts
1533
+ //#region src/client/host-compat/versions/v0-2-1/adapter.ts
1514
1534
  /**
1515
- * Adapter for the DSH `0.2.1-alpha` line, verified at `0.2.1-alpha.1` AND
1516
- * `0.2.1-alpha.2`.
1535
+ * Adapter for the DSH `0.2.1` patch line, verified at `0.2.1-alpha.1`,
1536
+ * `alpha.2`, `alpha.3`, `alpha.4`, `alpha.5`, `rc.1` and `rc.2`.
1517
1537
  *
1518
1538
  * A new MINOR line opened with a prerelease, and the answer is the `0.2.0-rc.2`
1519
1539
  * folder's — which is a statement this file has to earn, because "same as the
@@ -1537,6 +1557,21 @@ window.__ModuleLoader__.load({
1537
1557
  * · `TextShimmer` still marks its root `data-shimmer`, so the stroke exemption
1538
1558
  * written for rc.1 stands.
1539
1559
  *
1560
+ * Re-checked again across the rest of the line (`alpha.3` … `rc.2`, `git diff
1561
+ * <tag> rc.2` on every file above): the menu token contract, both menu stylesheets
1562
+ * (`MenuSurface`, `MenuGroup`), `gradient-shadow-text.css` / `base.css`,
1563
+ * `AppFrame.*`, `ConversationRoot`, `TaskMenu`, `preload-windows` and the five
1564
+ * header slot keys are unchanged through `rc.2`. Two deltas, neither a fact here:
1565
+ * · `ui-sidebar-right/SidebarRight.module.css` gained `overflow:clip` on the
1566
+ * panel wrapper (hidden docked items no longer widen the frame's scrollable
1567
+ * overflow; fixed floating children still escape). The slide owner and the
1568
+ * element the frost rides are untouched, and the plugin never promotes the
1569
+ * wrapper on this line.
1570
+ * · `PluginManagerPage.tsx` was refactored and now emits `data-plugin-scope`
1571
+ * groups for `global`, `preset` and `more`. The anchors this plugin frames
1572
+ * (`[data-plugin-panel]`, `[data-plugin-scope] > ul`, `[data-plugin-loading] >
1573
+ * ul`) are still all there, and the rule is scope-value agnostic.
1574
+ *
1540
1575
  * Re-checked at `0.2.1-alpha.2`, same list: `design-platform.css` still byte-identical
1541
1576
  * (`--dsw-menu-surface-fill` / `--dsw-specific-menu` / `--dsw-alias-menu-group-header-fill`
1542
1577
  * on both scheme blocks); `MenuSurface.tsx`/`.module.css` byte-identical (blob hashes
@@ -1613,8 +1648,8 @@ window.__ModuleLoader__.load({
1613
1648
  }
1614
1649
  function createAdapter$1(host) {
1615
1650
  return {
1616
- id: "0.2.1-alpha.1/alpha.2",
1617
- channel: "0.2.1-alpha",
1651
+ id: "0.2.1-alpha.1…rc.2",
1652
+ channel: "0.2.1",
1618
1653
  host,
1619
1654
  panelFragments: panelFragments$1,
1620
1655
  pluginPageRule: PLUGIN_PAGE_FROST_RULE,
@@ -1672,7 +1707,7 @@ window.__ModuleLoader__.load({
1672
1707
  const ADAPTERS = {
1673
1708
  "0.2.0-rc": createAdapter$3,
1674
1709
  "0.2.0-rc.2": createAdapter$2,
1675
- "0.2.1-alpha": createAdapter$1,
1710
+ "0.2.1": createAdapter$1,
1676
1711
  unknown: createAdapter
1677
1712
  };
1678
1713
  /** Pick the adapter for a resolved host. `host.channel` is already narrowed to
@@ -2137,12 +2172,24 @@ window.__ModuleLoader__.load({
2137
2172
  * dropdowns are transient (opening one closes the others), so this is a guard
2138
2173
  * rather than the common path.
2139
2174
  *
2175
+ * THE SURFACE MARKER IS PART OF THE CANDIDATE SET (`0.2.1-alpha.3` and up).
2176
+ * That release moved the background-job list onto the shared `MenuSurface`, whose
2177
+ * root carries NO role — the `<ul>` inside it is now a paint-free `.menuBody`
2178
+ * (`JobListAction.module.css` dropped its own `background`/`backdrop-filter`), and
2179
+ * the fill plus the frost live on the surface's `.material` child. Keyed on roles
2180
+ * alone the tagger went blind exactly when the paint moved one level up, and the
2181
+ * job list kept the stock look. `data-menu-material` is the surface's own semantic
2182
+ * marker (`MenuSurface.tsx`), so the tag rides it: the tokens land on the surface
2183
+ * root, which is the nearest ancestor of `.material`, and inherit down into it.
2184
+ * The pre-migration shape (a self-painting `<ul class*="_menu">`) stays matched by
2185
+ * the header group's class-shape arm, so both eras are styled by one rule set.
2186
+ *
2140
2187
  * @module
2141
2188
  */
2142
2189
  /** Marker attribute the stylesheet keys on. */
2143
2190
  const HEADER_POPOVER_ATTR = "data-dsh-any-header-popover";
2144
2191
  /** Portaled popovers a header trigger can own. */
2145
- const POPOVER_SELECTOR = "[role=\"menu\"], [role=\"tree\"], [role=\"dialog\"]";
2192
+ const POPOVER_SELECTOR = "[role=\"menu\"], [role=\"tree\"], [role=\"dialog\"], [data-menu-material]";
2146
2193
  /** Which session-header slot anchors to watch — supplied by the host adapter,
2147
2194
  * because the set is not the same on every release: 0.1.6-alpha.2 registers a
2148
2195
  * sixth (`.leading`) that neither the line before nor the line after has.
@@ -3858,6 +3905,70 @@ window.__ModuleLoader__.load({
3858
3905
  const CAPTION_FRAME_CLASS = "dab-frame-caption";
3859
3906
  const FLAT_CENTER_CLASS = "dab-center-flat";
3860
3907
  const WCO_PROBE_SEL = "body > span[style*=\"--dsw-specific-sidebar-fill\"]";
3908
+ /** Marks the sidebar's own caption controls — the toggle and, while collapsed,
3909
+ * the new-session button. Both are `position:fixed` and float over the band, so
3910
+ * they inherit the band's ink instead of the sidebar's. */
3911
+ const CAPTION_INK_ATTR = "data-dab-caption-ink";
3912
+ /** The strip's chrome ink, and its collapsed-state variant.
3913
+ *
3914
+ * WHY THIS IS NOT THE LABEL INK: the plugin flips the label tokens against the
3915
+ * PICKED PALETTE (white on a dark pick), but the caption band is that palette at
3916
+ * the sidebar/header-bar opacity OVER the wallpaper. At a low opacity the strip
3917
+ * is mostly wallpaper, so white ink lands on a light band and the toggle
3918
+ * disappears (reported on the desktop build, on a 22%-opacity sidebar over a bright
3919
+ * wallpaper). The strip therefore derives its own ink from what it is actually
3920
+ * painted on: past ~85% opacity the surface dominates and the palette direction
3921
+ * is the truth; below it the wallpaper's brightness verdict decides.
3922
+ *
3923
+ * Returned as `{ ink, collapsedInk }` because the strip's material changes with
3924
+ * the sidebar: expanded it is the sidebar segment, collapsed it is the 「标题栏」
3925
+ * card's band. `null` means "no verdict and no palette direction" — leave the
3926
+ * host's ink alone. */
3927
+ function captionInkPair() {
3928
+ const verdict = rBgDark();
3929
+ const inkIsWhite = rSchemeOverride() === "auto" ? rHasColor() ? rColorScheme() === "dark" : verdict : rScheme() === "dark";
3930
+ if (inkIsWhite === null || inkIsWhite === void 0) return null;
3931
+ const stripInk = (alpha) => {
3932
+ return (verdict === null || verdict === void 0 || alpha >= .85 ? inkIsWhite : verdict) ? "#fff" : "#000";
3933
+ };
3934
+ return {
3935
+ ink: stripInk(rOps().sidebar),
3936
+ collapsedInk: stripInk(rHeaderBarOpacity())
3937
+ };
3938
+ }
3939
+ /** The fixed caption controls of the sidebar root, re-inked (and released) as the
3940
+ * strip's material changes. Throttled: this runs on every caption re-apply, and
3941
+ * the header-bar slider drags through that path frame by frame. */
3942
+ let captionInkEls = [];
3943
+ let captionInkSidebar = null;
3944
+ let captionScanAt = 0;
3945
+ function releaseCaptionInk() {
3946
+ for (const el of captionInkEls) {
3947
+ el.style.removeProperty("--dsw-alias-label-secondary");
3948
+ el.removeAttribute(CAPTION_INK_ATTR);
3949
+ }
3950
+ captionInkEls = [];
3951
+ }
3952
+ function applyCaptionInk(enabled) {
3953
+ if (!enabled || sidebarEl === null) {
3954
+ releaseCaptionInk();
3955
+ return;
3956
+ }
3957
+ const now = typeof performance === "undefined" ? Date.now() : performance.now();
3958
+ if (captionInkEls.some((el) => !el.isConnected) || sidebarEl !== captionInkSidebar || now - captionScanAt > 250) {
3959
+ captionScanAt = now;
3960
+ captionInkSidebar = sidebarEl;
3961
+ releaseCaptionInk();
3962
+ for (const btn of sidebarEl.querySelectorAll("button")) {
3963
+ if (getComputedStyle(btn).position !== "fixed") continue;
3964
+ captionInkEls.push(btn);
3965
+ }
3966
+ }
3967
+ for (const el of captionInkEls) {
3968
+ el.style.setProperty("--dsw-alias-label-secondary", "var(--dsh-any-caption-ink, #fff)");
3969
+ el.setAttribute(CAPTION_INK_ATTR, "");
3970
+ }
3971
+ }
3861
3972
  let wcoStyleEl = null;
3862
3973
  function ensureWcoStyle() {
3863
3974
  if (wcoStyleEl?.isConnected) return wcoStyleEl;
@@ -3899,6 +4010,13 @@ window.__ModuleLoader__.load({
3899
4010
  * that left segment from `--dsh-any-op-sidebar` — the same literal the sidebar
3900
4011
  * column is painted with — and frosts from the sidebar blur's root mirror, so the
3901
4012
  * column and the strip above it are one surface under one opacity + blur pair.
4013
+ * It is PAINT ONLY: `pointer-events:none`, and deliberately NO
4014
+ * `-webkit-app-region: drag`. Chromium collects app-region items in paint order,
4015
+ * and a drag annotation on a generated layer overrides the `no-drag` holes the
4016
+ * native caption menubar punched for itself (`preload-menu.ts` marks its host
4017
+ * `no-drag`), which left the top-left controls unclickable while the window still
4018
+ * dragged (issue #29). The host's own `.frame::before` keeps its drag region, so
4019
+ * the band remains movable.
3902
4020
  * `--dsh-windows-sidebar-width` is published INLINE ON THE FRAME by the host
3903
4021
  * (`AppFrame.tsx`, Windows builds only, `${cols.sidebar}px`) and both pseudo-elements
3904
4022
  * inherit it, so the split tracks a dragged or collapsed sidebar with no observer of
@@ -3912,10 +4030,20 @@ window.__ModuleLoader__.load({
3912
4030
  const fill = captionFill(src);
3913
4031
  const paint = headerBarPaint(src) || fill;
3914
4032
  const width = "var(--dsh-windows-sidebar-width,0px)";
3915
- const css = fill === "" ? "" : `${WCO_PROBE_SEL}{--dsw-specific-sidebar-fill:transparent}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::before{background:var(--dsh-any-op-header-bar,${paint})!important;backdrop-filter:var(--dsh-any-blur-header-bar,none);-webkit-backdrop-filter:var(--dsh-any-blur-header-bar,none);clip-path:inset(0 0 0 ${width});-webkit-clip-path:inset(0 0 0 ${width})}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::after{content:"";position:absolute;top:0;left:0;width:${width};height:var(--dsh-windows-titlebar-height,40px);background:var(--dsh-any-op-sidebar,${fill});backdrop-filter:var(--dsh-any-blur-sidebar-band,none);-webkit-backdrop-filter:var(--dsh-any-blur-sidebar-band,none);-webkit-app-region:drag}[data-windows-titlebar] .${FLAT_CENTER_CLASS}{border-top-left-radius:0!important}`;
4033
+ const css = fill === "" ? "" : `${WCO_PROBE_SEL}{--dsw-specific-sidebar-fill:transparent}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::before{background:var(--dsh-any-op-header-bar,${paint})!important;backdrop-filter:var(--dsh-any-blur-header-bar,none);-webkit-backdrop-filter:var(--dsh-any-blur-header-bar,none);clip-path:inset(0 0 0 ${width});-webkit-clip-path:inset(0 0 0 ${width})}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::after{content:"";position:absolute;top:0;left:0;pointer-events:none;z-index:-1;width:${width};height:var(--dsh-windows-titlebar-height,40px);background:var(--dsh-any-op-sidebar,${fill});backdrop-filter:var(--dsh-any-blur-sidebar-band,none);-webkit-backdrop-filter:var(--dsh-any-blur-sidebar-band,none)}[data-windows-titlebar]:has([role="dialog"][aria-modal="true"]) .${CAPTION_FRAME_CLASS}::before,[data-windows-titlebar]:has([role="dialog"][aria-modal="true"]) .${CAPTION_FRAME_CLASS}::after{box-shadow:inset 0 0 0 9999px var(--dsw-alias-bg-mask-1,rgba(0,0,0,.24))}:root:has([data-sidebar-collapsed="true"]){--dsh-any-caption-ink:var(--dsh-any-caption-ink-collapsed,#fff)!important}[data-windows-titlebar] [data-windows-menu]{--dsw-alias-label-secondary:var(--dsh-any-caption-ink,#fff);--dsw-alias-label-primary:var(--dsh-any-caption-ink,#fff)}[data-windows-titlebar] .${FLAT_CENTER_CLASS}{border-top-left-radius:0!important}`;
3916
4034
  const el = ensureWcoStyle();
3917
4035
  if (el.textContent !== css) el.textContent = css;
4036
+ const ink = css === "" ? null : captionInkPair();
4037
+ const root = document.documentElement;
4038
+ if (ink === null) {
4039
+ root.style.removeProperty("--dsh-any-caption-ink");
4040
+ root.style.removeProperty("--dsh-any-caption-ink-collapsed");
4041
+ } else {
4042
+ root.style.setProperty("--dsh-any-caption-ink", ink.ink);
4043
+ root.style.setProperty("--dsh-any-caption-ink-collapsed", ink.collapsedInk);
4044
+ }
3918
4045
  discoverParts();
4046
+ applyCaptionInk(ink !== null);
3919
4047
  if (frameEl === null) return;
3920
4048
  if (css === "") {
3921
4049
  frameEl.classList.remove(CAPTION_FRAME_CLASS);
@@ -3960,7 +4088,11 @@ window.__ModuleLoader__.load({
3960
4088
  * appears in the sheet. That is exactly how the tagged open-in-app menu kept
3961
4089
  * taking the card blur. `[role]` is a no-op presence test that raises only the
3962
4090
  * tagged arm to (0,2,0); the other three already qualify at (0,2,0)+. */
3963
- const HEADER_SURFACES_PADDED = [`${HEADER_TAG_SELECTOR}[role]`, ...HEADER_SURFACES.slice(1)];
4091
+ const HEADER_SURFACES_PADDED = [
4092
+ `${HEADER_TAG_SELECTOR}[role]`,
4093
+ `${HEADER_TAG_SELECTOR}[data-menu-material]`,
4094
+ ...HEADER_SURFACES.slice(1)
4095
+ ];
3964
4096
  /** The header blur variable, and the rule that frosts the header's dropdown set.
3965
4097
  * The declaration comes from the same adapter answer as the card group's menu arm:
3966
4098
  * every surface in this set paints its skin from the host's own menu-blur token
@@ -4043,8 +4175,9 @@ window.__ModuleLoader__.load({
4043
4175
  return;
4044
4176
  }
4045
4177
  const dialog = "[role=\"dialog\"][aria-modal=\"true\"]:not([aria-labelledby])";
4178
+ const registry = "[data-install-registry]";
4046
4179
  const body = decls.join(";");
4047
- const css = `${dialog},${dialog} *{${body}}[role="menu"]:has([class*="_danger"]){${body}}`;
4180
+ const css = `${dialog},${dialog} *{${body}}[role="menu"]:has([class*="_danger"]){${body}}${registry},${registry} *{${body}}`;
4048
4181
  if (el.textContent !== css) el.textContent = css;
4049
4182
  }
4050
4183
  let exemptStyleEl = null;
@@ -4431,6 +4564,43 @@ window.__ModuleLoader__.load({
4431
4564
  * clear cannot be re-clobbered and there is nothing to re-apply. */
4432
4565
  const PART_FRAME_CLASS = "dab-frame-clear";
4433
4566
  const FRAME_CLEAR_RULE = `.${PART_FRAME_CLASS}{background:transparent!important}`;
4567
+ /** The sidebar column paints its fill ONCE, no matter how many nested elements
4568
+ * ask for it.
4569
+ *
4570
+ * WHY THIS IS NOT COSMETICS: `--dsw-specific-sidebar-fill` is a theme token the
4571
+ * host ships OPAQUE (`design-platform.css`: light `--dsw-static-neutral-bluish-50`,
4572
+ * dark `-900`), and three elements stacked in the sidebar's region each declare
4573
+ * `background:var(--dsw-specific-sidebar-fill)` on top of one another —
4574
+ * `AppFrame.module.css` `.sidebarCol` (the grid track), `ui-sidebar/SidebarRoot.module.css:16`
4575
+ * `.root` (the shell it fills) and `ui-workspace/WorkspaceBrowser.module.css:335`
4576
+ * `.fade` (the tree's 24px bottom gradient). Over an opaque color that layering is
4577
+ * invisible; the moment this plugin's 「侧栏」 slider makes the token translucent
4578
+ * every copy adds its alpha again, so the region stops reading as one surface. On
4579
+ * the desktop build it is outright visible: the caption strip above the sidebar is
4580
+ * the plugin's own `::after`, painted with the SAME variable at the SAME alpha but
4581
+ * exactly once (`applyNativeCaption`), so the strip and the column under it come out
4582
+ * at two different densities and the sidebar looks split horizontally.
4583
+ *
4584
+ * The column is the one that keeps the paint — it is the outermost box, it is the
4585
+ * box whose width the caption strip is cut to, and the host already treats it as the
4586
+ * owner on macOS, where `SidebarRoot.module.css:27` sets `.root{background:transparent}`
4587
+ * for exactly this reason ("the AppFrame column owns the translucent sidebar tint").
4588
+ * The host retires the gradient the same way for the same reason:
4589
+ * `WorkspaceBrowser.module.css:341` hides `.fade` on darwin because "the opaque fill
4590
+ * gradient would paint a solid smear instead of a fade" once the sidebar is
4591
+ * translucent over the window — which is the state this slider puts the sidebar in on
4592
+ * every platform. So the token is re-declared `transparent` for the column's
4593
+ * DESCENDANTS only: an element's own declaration beats what it inherits, and `body`'s
4594
+ * `!important` re-scope reaches these elements by inheritance only, so no `!important`
4595
+ * is needed here and none of the three consumers can win it back. The column itself
4596
+ * still resolves the faded value it inherits, which is why `*` is a descendant
4597
+ * combinator and not part of the column's own selector.
4598
+ *
4599
+ * Bound by the CSS-Module class shape, same as `[class*="_ioCard"]`: the built
4600
+ * bundle names these `pI_x6G_sidebarCol` — prefix first, local name last, and the
4601
+ * pair is identical across `0.2.0-rc.1`, `0.2.1-alpha.3` and `0.2.1-rc.2`. A build
4602
+ * that renames the track simply stops matching, which is the untouched host look. */
4603
+ const SIDEBAR_ONE_LAYER_RULE = "[class*=\"_sidebarCol\"] *{--dsw-specific-sidebar-fill:transparent}";
4434
4604
  const PART_BLUR_RULE = `.${PART_BLUR_CLASS}{isolation:isolate}.${PART_BLUR_CLASS}:has([role="dialog"]){isolation:auto}.${PART_UNDERLAY_CLASS}{position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;backdrop-filter:var(--dsh-any-part-blur,none);-webkit-backdrop-filter:var(--dsh-any-part-blur,none)}`;
4435
4605
  let partBlurStyleEl = null;
4436
4606
  function ensurePartBlurStyle() {
@@ -5266,6 +5436,9 @@ window.__ModuleLoader__.load({
5266
5436
  document.documentElement.style.removeProperty("--dsh-any-op-header-bar");
5267
5437
  document.documentElement.style.removeProperty("--dsh-any-blur-header-bar");
5268
5438
  document.documentElement.style.removeProperty("--dsh-any-blur-sidebar-band");
5439
+ document.documentElement.style.removeProperty("--dsh-any-caption-ink");
5440
+ document.documentElement.style.removeProperty("--dsh-any-caption-ink-collapsed");
5441
+ releaseCaptionInk();
5269
5442
  document.querySelectorAll(`.${HEADER_BAR_CLASS}`).forEach((el) => el.classList.remove(HEADER_BAR_CLASS));
5270
5443
  headerBarEl = null;
5271
5444
  frameEl?.classList.remove(CAPTION_FRAME_CLASS);
@@ -5364,7 +5537,7 @@ window.__ModuleLoader__.load({
5364
5537
  function buildStaticStyles() {
5365
5538
  const adapter = rHostAdapter();
5366
5539
  const panel = adapter.panelFragments();
5367
- return DARK_GRADIENT + FRAME_CLEAR_RULE + SETTINGS_STYLE_RULE + popoverBlurRule() + TRAJECTORY_STYLE_RULE + inputBlurRule() + MOBILE_HEADER_RULE + HEADER_BAR_RULE + PANEL_TOKEN_RULE + panel.promotion + panel.blur + adapter.pluginPageRule + PRODUCED_RULE + STROKE_RULE + headerPopoverRule() + exemptDefaultRule() + PLACEHOLDER_RULE;
5540
+ return DARK_GRADIENT + FRAME_CLEAR_RULE + SIDEBAR_ONE_LAYER_RULE + SETTINGS_STYLE_RULE + popoverBlurRule() + TRAJECTORY_STYLE_RULE + inputBlurRule() + MOBILE_HEADER_RULE + HEADER_BAR_RULE + PANEL_TOKEN_RULE + panel.promotion + panel.blur + adapter.pluginPageRule + PRODUCED_RULE + STROKE_RULE + headerPopoverRule() + exemptDefaultRule() + PLACEHOLDER_RULE;
5368
5541
  }
5369
5542
  /** Append the plugin's stylesheet and keep it matched to the host.
5370
5543
  * Returns a teardown for the plugin's effect scope. */