dsh-style-tweaks 0.1.7 → 0.1.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.en.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](README.md)
4
4
 
5
- > Dependency version: deepseek-harness v0.1.7-rc.2
5
+ > Dependency version: deepseek-harness 0.1.7-rc.2 ~ <0.3.0 (running baseline and compile-time deps both 0.2.1-rc.2; affected tweaks stay inert on older hosts)
6
6
 
7
7
  A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) web plugin that bundles a precise conversation column-width control with a collection of opt-in style tweaks for DSH's UI — including small fixes for the sidebar and the settings panel.
8
8
 
@@ -15,7 +15,7 @@ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) web
15
15
  - **Side margin** — whitespace in px kept on each side of the conversation area while plugin width control is on. The column is clamped to the dialog width and narrows when the sidebar opens or the window shrinks, never hugging the edges. Minimum 32 px. Shown only while plugin width control is on; when it is off, the margin does not apply and DSH's native behavior stands.
16
16
  - **Right sidebar initial width (default off, 45%)** — when ON, owns the right sidebar's first-open width: it is written once, the first time the sidebar opens in a page load, as the session frame's width × a percentage (15–70, with 30 / 40 / 45 / 55 presets). A manual drag, and every later open, keeps your own width; a reload applies the percentage again. OFF by default, which leaves DSH's own 45% completely untouched; once ON, the default percentage is that same 45, so nothing changes until the number is edited. DSH still clamps the result into its own range (at least 300 px, at most 70% of the frame), so a conversion below 300 px renders 300 px wide. On pre-0.1.5 hosts the tweak stays inert.
17
17
 
18
- - **Desktop Settings launcher (default off)** — the preference is shown only in the Desktop Style tweaks settings. When enabled, the sidebar reads `[More] [⚙ Settings]`; clicking the gear activates the host's native Settings action through a short-lived portalled-menu bridge, so the original full Settings window is reused and the duplicate Settings row is removed from More. Feedback, sign-in, sign-out, and the other account actions remain. When disabled, the native More → Settings path is restored; Web does not show this preference or the external gear. The bridge depends on the current account-menu semantics, menu placement, and CSS Modules local-name fragments; if those change, it falls back to the untouched native menu.
18
+ - **External Settings launcher (default off)** — the preference appears in the Style tweaks settings in both Web and Desktop. When enabled, the sidebar reads `[More] [⚙ Settings]`; clicking the gear activates the host's native Settings action through a short-lived portalled-menu bridge, so the original full Settings window is reused and the duplicate Settings row is removed from More. Feedback, sign-in, sign-out, and the other account actions remain. When disabled, the native More → Settings path is restored. Both renderers put the same account menu in the sidebar's launcher slot — the host's own Settings button is never rendered — so the entry point applies to both. The bridge depends on the account-menu semantics, menu placement, and CSS Modules local-name fragments; if those change, it falls back to the untouched native menu.
19
19
 
20
20
  ### Tweaks
21
21
 
@@ -51,7 +51,7 @@ style-tweaks:
51
51
  sideMargin: 50 # ≥ 32 px; shown/effective only while usePluginWidth is on
52
52
  rightbarInitialWidth: false # default false; true lets the plugin own the right sidebar's first-open width
53
53
  rightbarWidthPercent: 45 # 15–70; right sidebar first-open width as a percentage of the session frame
54
- desktopSettingsLauncher: false # default false; Desktop-only; true shows [More] [Settings gear]
54
+ desktopSettingsLauncher: false # default false; applies to Web and Desktop; true shows [More] [Settings gear]
55
55
  # History loading
56
56
  historyPageSizeEnabled: false # default false; true sends the size below on every history request (the panel's master switch)
57
57
  historyPageSize: 500 # 50–1000; shown/effective only while historyPageSizeEnabled is on
@@ -99,8 +99,8 @@ The package spec after `add` is forwarded to pnpm verbatim, so versions can be
99
99
  pinned — `@version` for the npm package, `#tag` for the GitHub source:
100
100
 
101
101
  ```bash
102
- dsh plugin --profile web add dsh-style-tweaks@0.1.7 # pin the npm version
103
- dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.7 # pin a git tag
102
+ dsh plugin --profile web add dsh-style-tweaks@0.1.8 # pin the npm version
103
+ dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.8 # pin a git tag
104
104
  ```
105
105
 
106
106
  Restart DSH web once after installing (bundle plugins are scanned at process start).
@@ -224,10 +224,14 @@ Test-Path "$link\src" # expect True, proving it really points at the sourc
224
224
 
225
225
  **How to confirm the new code is really loaded**:
226
226
 
227
- - The page should carry injected styles such as `style[data-tweak-css="cst-*"]`, plus the
228
- `__cst_*_cleanup__` guards each tweak leaves on `window` (e.g.
229
- `__cst_project_running_indicator_cleanup__`). A guard that is `undefined` means that tweak never
230
- mounted.
227
+ - The page should carry injected styles as `style[data-tweak-css]` (their number follows the switches
228
+ of the last settings save; **do not count them with a `cst-*` glob**: `dsh-style-tweaks-base`,
229
+ `-conversation-width`, `-pills-decimals` and `-turn-time-pill` are not `cst-`-prefixed — 17 nodes
230
+ were measured on 2026-10-17, 13 of them `cst-*`), plus the `__cst_*_cleanup__` guards each tweak
231
+ leaves on `window` (e.g. `__cst_project_running_indicator_cleanup__`). A guard that is `undefined`
232
+ means that tweak never mounted. A `__cst_reported__*` key on `window` means the matching one-shot
233
+ diagnostic already fired (the topic is in the key, e.g. `__cst_reported__wire-drift:session/page__`)
234
+ — that is the trace a silently dead setting leaves behind, and it is worth following up.
231
235
  - To see which build the page actually got: take the full URL of the
232
236
  `/plugins/??…dsh-style-tweaks…` entry from `performance.getEntriesByType('resource')`, then
233
237
  `fetch(url, { cache: 'no-store' })` and look for an identifier you just added. The page does
@@ -258,7 +262,7 @@ source change has to be copied over — exactly the step the `link:` setup spare
258
262
  ```bash
259
263
  pnpm build
260
264
  pnpm pack # produces dsh-style-tweaks-<version>.tgz (gitignored; do not commit)
261
- dsh plugin --profile web add ./dsh-style-tweaks-0.1.7.tgz
265
+ dsh plugin --profile web add ./dsh-style-tweaks-0.1.8.tgz
262
266
  ```
263
267
 
264
268
  **Restart `dsh web` once** after installing (bundle plugins are scanned at process start).
@@ -303,7 +307,7 @@ path dependency:
303
307
 
304
308
  ```bash
305
309
  dsh plugin --profile web remove dsh-style-tweaks
306
- dsh plugin --profile web add ./dsh-style-tweaks-0.1.7.tgz
310
+ dsh plugin --profile web add ./dsh-style-tweaks-0.1.8.tgz
307
311
  ```
308
312
 
309
313
  The only difference between the two setups is whether the artifact lands in place automatically: both
@@ -314,7 +318,7 @@ to verify the published package itself.
314
318
 
315
319
  - **Server** (`src/index.ts` / `src/store.ts`) declares the `style-tweaks` settings namespace and mounts a same-origin route (`/_dsh/style-tweaks/settings`). Since DSH 0.1.7 that namespace IS this plugin's own entry Config (the entry id is the namespace, its fields carry the `.volatile()` marker), but **values are read and written through the plugin's own store, `<profile>/.dsh-style-tweaks/store.json`** — the route reads one file per request and writes it back with a revision CAS, skipping the host's settings pipeline (measured 905–1114ms per write vs ~10ms for the store; see `.docs/PLAN-DIAGNOSIS-index.md`), seeded once from the entry Config's explicit fields when the file is missing; older hosts go through `ctx.settings.register` and the original settings path, with the same namespace.
316
320
  - **Browser** (`src/client/index.tsx`) reads/writes that route, renders the Settings section, and mounts / unmounts each tweak live based on its toggle (pure-CSS tweaks inject runtime `<style>` elements; JS-level tweaks also read the app's own state stores and patch the DOM).
317
- - **Desktop Settings launcher** (`src/client/tweaks/desktop-settings-launcher.ts`) observes the Desktop account menu only when the Desktop-only `desktopSettingsLauncher` preference is enabled, inserts a gear immediately after the More trigger's row, and produces `[More] [⚙ Settings]`. The gear is not hard-coded to a pixel size: it measures the live More trigger and matches it exactly (44px in the wide sidebar), keeping the host's own centring with no added height and no negative margin, so the two hover backgrounds come out the same height. The gear uses a short-lived portalled-menu bridge to activate the host's native Settings action, so the original full Settings window is reused; the duplicate Settings row is hidden while feedback, sign-in, sign-out, and other account actions remain. The preference defaults off, restores native More → Settings when disabled, and is not shown or mounted in Web. The module falls back to the native menu if the account-menu semantics, portal placement, or CSS Modules fragments no longer match.
321
+ - **External Settings launcher** (`src/client/tweaks/desktop-settings-launcher.ts`) runs in both renderers: Web and Desktop both put the same account menu in the sidebar's launcher slot, so neither renders the host's own Settings button and both hide Settings inside More. When the `desktopSettingsLauncher` preference is on it observes that menu, inserts a gear immediately after the More trigger's row, and produces `[More] [⚙ Settings]`. The gear is not hard-coded to a pixel size: it measures the live More trigger and matches it exactly (44px in the wide sidebar), keeping the host's own centring with no added height and no negative margin, so the two hover backgrounds come out the same height. The gear uses a short-lived portalled-menu bridge to activate the host's native Settings action, so the original full Settings window is reused; the duplicate Settings row is hidden while feedback, sign-in, sign-out, and other account actions remain. The preference defaults off and restores native More → Settings when disabled. The module falls back to the native menu if the account-menu semantics, portal placement, or CSS Modules fragments no longer match; the config field keeps its `desktopSettingsLauncher` name so existing documents stay valid.
318
322
  - **Column-width engine** (`src/client/conversation-width.ts`) writes the `--dsh-chat-user-width` CSS variable and hides DSH's native `[data-width-handle]` drag strips while plugin width control is on; the chosen px is also mirrored into the localStorage slot the native handles read, so flipping the switch round-trips cleanly.
319
323
  - **Tweak registry** (`src/client/tweaks/registry.ts`) centralises each tweak's metadata (id, settings field name, default value, i18n keys); adding a new tweak means appending one entry here and dropping a new injector file under `src/client/tweaks/`.
320
324
  - **Project running indicator** (`src/client/tweaks/project-running-indicator.ts`) reads session running state and directory membership from `ctx.get('sessions')` / `ctx.get('workspaces')`, and uses a MutationObserver to mount the app's own `StateDot` (mounted from `@deepseek-ai/dsh-client-ui-primitives`, but the ongoing state's look is replaced wholesale by this tweak. The host's own ongoing dot is a breathing arc on a ring, and since 0.1.7 it is frozen into a still ring under `prefers-reduced-motion: reduce` — with that preference set, **every** running dot in the app, the title's own included, simply stops. A still ring cannot say "work in flight", so every ongoing dot in the app is redrawn as ZCode's spinner: lucide's `loader` icon — eight evenly spaced spokes, radius 6..10 of a 24 viewBox, ~2 units thick, round caps, all equal, with **no** opacity trail — turned at `animate-spin`'s constant 1s per revolution, regardless of the system setting. CSS can only reach the host's `svg`, `g` and two circles, so the spokes are an SVG mask on the svg element: lucide's own `loader` path data turned into a `data:` URI (`mask-size: 100% 100%`, so any StateDot size works), with the svg itself filled `currentColor` so the colour still comes from the host's CSS module (tertiary grey); everything the host draws inside the svg is switched off (`> *`, not just today's two circles, so a host that redraws its spinner with other elements stays covered), and `:has([class*="spinnerMotion"])` keeps it to real StateDots. A mask rather than a `conic-gradient` because gradient wedges rasterise differently per angle and end up looking uneven in weight, while an SVG stroke (round caps) rasterises exactly like the icon ZCode itself draws. The override stops at that component: the host's other reduced-motion answers — tool-row sweeps, text shimmers, progress bars — keep honouring the setting) on two kinds of sidebar row: the right side of each project directory header row (`role="treeitem"[aria-expanded]`), and the status slot of a session row (`role="treeitem"` plus the CSS-Modules local name `sessionRow`) that the app left empty. "Busy" merges three sources: the session's own `SessionSummary.running` (a turn in flight); the background jobs under it — `jobsBySession` in the `sessions` store snapshot, mirrored by Session Controller's control stream, where a status of `running` or `stopping` counts (the same `isLive` predicate DSH's own `ui-jobs` session-header action uses, so a `run_in_background` command stays lit from launch to settlement instead of going dark once the turn that started it answers); and a running subagent anywhere below it in the lineage (the same upward walk as `indexSubagentDescendants`). Header rows match workspaces by group key (`ProjectRowItem`'s `props.group.key`, i.e. `GroupNode.key`) rather than by title text: `GroupNode.label` is the directory basename, so two same-named directories under different parents collapse to one shared label (the Host only rejects a colliding *rename*, not a duplicate directory name) and title matching would light both — which is exactly why two `pi-web` directories once showed the indicator together. A session row is only decorated while its slot is empty — the moment the app renders its own dot there (turn running, subagent running, approval or question pending, unviewed completion) the injected dot steps aside instead of sitting next to it. Session rows expose no plugin slot and carry no session id in the DOM, so the id is read off React's fiber chain (`SessionNodeItem`'s `props.node.id`): the walk is defensive (an unreadable id just leaves that row alone) and re-resolved every pass, because the sidebar recycles its row elements. On a host without the `jobs` service `jobsBySession` is absent or empty and the tweak quietly falls back to the two cases the app already covers.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  简体中文 | [English](README.en.md)
4
4
 
5
- > 依赖版本:deepseek-harness v0.1.7-rc.2
5
+ > 依赖版本:deepseek-harness 0.1.7-rc.2 ~ <0.3.0(运行基线与编译依赖均为 0.2.1-rc.2;旧宿主上相关调整项保持惰性)
6
6
 
7
7
  [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)Web UI 插件:为 DSH 界面提供一套可选的样式调整——精确的对话列宽控制,以及侧边栏与设置面板的一系列小幅度修复。
8
8
 
@@ -15,7 +15,7 @@
15
15
  - **两侧边距**:插件宽度控制开启时,对话区域两侧保留的空白(px),列宽被钳制为对话框宽度,侧边栏打开或窗口缩小时内容会收窄,不会贴住边缘。最低 32 px。仅在"插件宽度控制"开启时显示;关闭后边距不生效,保持 DSH 原生行为。
16
16
  - **右侧边栏初始宽度(默认关闭,45%)**:开启后接管右侧边栏首次打开时的宽度——仅在本次页面加载后的第一次打开时按"会话窗口宽度 × 百分比"写入一次(15–70 可选,含 30 / 40 / 45 / 55 四个预设)。之后手动拖拽、关闭再打开都保留你自己拖出的宽度;刷新页面后该百分比重新生效。默认关闭时保持 DSH 自身的 45%,插件完全不介入;开启后默认百分比同样取 45,因此不改数值时观感与原生一致。宿主自身还会把结果钳制到它的范围内(最小 300 px、最大窗口的 70%),换算结果不足 300 px 时按 300 px 显示。0.1.5 之前的宿主上本项保持惰性。
17
17
 
18
- - **Desktop 设置入口外置(默认关闭)**:仅在 Desktop 的“样式调整”设置页显示开关;开启后侧栏底部排列为 `[更多] [设置齿轮]`,点击齿轮复用宿主原有完整 Settings 窗口,“更多”里不再显示重复的“设置”,但反馈、登录、退出等账号操作保留。关闭时恢复原生“更多 → 设置”;Web 版不显示此设置和外置齿轮。该入口依赖当前 Desktop 的账号菜单 DOM 与 portal 菜单结构,宿主结构变化时会安全回退到原生入口。
18
+ - **外置设置入口(默认关闭)**:Web 与 Desktop 的“样式调整”设置页都显示这个开关;开启后侧栏底部排列为 `[更多] [设置齿轮]`,点击齿轮复用宿主原有完整 Settings 窗口,“更多”里不再显示重复的“设置”,但反馈、登录、退出等账号操作保留。关闭时恢复原生“更多 → 设置”。两端的侧栏底部本来就是同一个账号菜单(原生的 Settings 按钮已被账号菜单占据),所以这个入口在两端同样成立;它依赖账号菜单的 DOM 与 portal 菜单结构,宿主结构变化时会安全回退到原生入口。
19
19
 
20
20
  ### 调整项
21
21
 
@@ -51,7 +51,7 @@ style-tweaks:
51
51
  sideMargin: 50 # ≥ 32 px;仅 usePluginWidth 开启时显示/生效
52
52
  rightbarInitialWidth: false # 默认 false;true 则由插件按下方百分比接管右栏首开宽度
53
53
  rightbarWidthPercent: 45 # 15–70;右侧边栏首次打开时占会话窗口宽度的百分比
54
- desktopSettingsLauncher: false # 默认 false;仅 Desktop 设置页显示;true 时显示 [更多] [设置齿轮]
54
+ desktopSettingsLauncher: false # 默认 false;Web 与 Desktop 都适用;true 时显示 [更多] [设置齿轮]
55
55
  # 历史加载
56
56
  historyPageSizeEnabled: false # 默认 false;true 则每次历史请求按下方条数携带(面板中的主开关)
57
57
  historyPageSize: 500 # 50–1000;仅 historyPageSizeEnabled 开启时显示/生效
@@ -98,8 +98,8 @@ dsh plugin --profile web add github:zhj9709/dsh-style-tweaks
98
98
  `add` 后面的包说明会**原样转发给 pnpm**,因此可以指定版本——npm 包用 `@版本号`,GitHub 源码用 `#tag`:
99
99
 
100
100
  ```bash
101
- dsh plugin --profile web add dsh-style-tweaks@0.1.7 # 锁定 npm 版本
102
- dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.7 # 锁定 git tag
101
+ dsh plugin --profile web add dsh-style-tweaks@0.1.8 # 锁定 npm 版本
102
+ dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.8 # 锁定 git tag
103
103
  ```
104
104
 
105
105
  安装完成后**重启一次 `dsh web`**(bundle 插件在进程启动时扫描)。
@@ -213,9 +213,13 @@ Test-Path "$link\src" # 期望 True,证明确实指到源码
213
213
 
214
214
  **怎么确认新代码真的加载了**:
215
215
 
216
- - 页面里应当存在 `style[data-tweak-css="cst-*"]` 之类的注入样式,以及各调整项留在 `window` 上的
217
- `__cst_*_cleanup__` 守卫(例如 `__cst_project_running_indicator_cleanup__`)。守卫是 `undefined`
218
- 说明该调整项没挂上。
216
+ - 页面里应当存在注入样式 `style[data-tweak-css]`(数量随最近一次设置保存里的开关变化;**不要用
217
+ `cst-*` 通配去数**:`dsh-style-tweaks-base` / `-conversation-width` / `-pills-decimals` /
218
+ `-turn-time-pill` 这四个 id 不是 `cst-` 前缀,2026-10-17 实测共 17 个节点、其中 13 个是 `cst-*`),
219
+ 以及各调整项留在 `window` 上的 `__cst_*_cleanup__` 守卫(例如
220
+ `__cst_project_running_indicator_cleanup__`)。守卫是 `undefined` 说明该调整项没挂上。
221
+ `window` 上若出现 `__cst_reported__*` 键,说明对应的一次性诊断已经触发过(键名带 topic,如
222
+ `__cst_reported__wire-drift:session/page__`)——那是「设置静默失效」留下的痕迹,值得顺着查。
219
223
  - 想知道页面拿到的是哪一版 bundle:从 `performance.getEntriesByType('resource')` 里取
220
224
  `/plugins/??…dsh-style-tweaks…` 那条的完整 URL,再 `fetch(url, { cache: 'no-store' })`,
221
225
  检查里面有没有你刚加进去的标识符。构建后页面**不会**主动重新请求这个 URL,这本身也是判断
@@ -243,7 +247,7 @@ dsh plugin --profile web add github:zhj9709/dsh-style-tweaks
243
247
  ```bash
244
248
  pnpm build
245
249
  pnpm pack # 产出 dsh-style-tweaks-<版本>.tgz(在 .gitignore 里,不要提交)
246
- dsh plugin --profile web add ./dsh-style-tweaks-0.1.7.tgz
250
+ dsh plugin --profile web add ./dsh-style-tweaks-0.1.8.tgz
247
251
  ```
248
252
 
249
253
  装完**重启一次 `dsh web`**(bundle 插件在进程启动时扫描)。
@@ -284,7 +288,7 @@ dsh plugin --profile web add ./dsh-style-tweaks-0.1.7.tgz
284
288
 
285
289
  ```bash
286
290
  dsh plugin --profile web remove dsh-style-tweaks
287
- dsh plugin --profile web add ./dsh-style-tweaks-0.1.7.tgz
291
+ dsh plugin --profile web add ./dsh-style-tweaks-0.1.8.tgz
288
292
  ```
289
293
 
290
294
  两个方案的差别只有「产物是否自动落到位」这一条:都要 `pnpm build`,都要刷新页面。
@@ -294,7 +298,7 @@ dsh plugin --profile web add ./dsh-style-tweaks-0.1.7.tgz
294
298
 
295
299
  - **服务端**(`src/index.ts` / `src/store.ts`):声明 `style-tweaks` 设置命名空间,并挂载同源路由 `/_dsh/style-tweaks/settings`。DSH 0.1.7 起命名空间就是本插件 entry 自己的 Config(entry id 即命名空间,字段带 `.volatile()` 标记),但**取值读写走插件自有存储 `<profile>/.dsh-style-tweaks/store.json`**——路由每请求现读现写 + revision CAS,绕开宿主 settings 全管线(该管线一次写实测 905–1114ms,自有存储 ~10ms;见 `.docs/PLAN-DIAGNOSIS-index.md`),store 缺失时首次请求从 entry Config 的显式字段播种一次;更早的宿主走 `ctx.settings.register` + 原 settings 路径,命名空间仍是同一个。
296
300
  - **浏览器端**(`src/client/index.tsx`):读写该路由、渲染设置页,并根据每个开关的状态实时挂载 / 卸载对应的调整项(纯 CSS 调整项注入运行时 `<style>` 元素;JS 级调整项还会读写应用自身的状态 store 并修补 DOM)。
297
- - **Desktop 设置入口**(`src/client/tweaks/desktop-settings-launcher.ts`):仅在 Desktop renderer 中、且 `desktopSettingsLauncher` 开关开启时观察侧栏账号菜单,把设置齿轮插入“更多”所在的 trigger row 右侧,形成 `[更多] [设置齿轮]`。齿轮尺寸不是写死的:它实测“更多”按钮高度后**完全等高**(展开态 44px),沿用宿主原有的居中对齐,不加额外高度也不加负外边距。这样 hover 时两个按钮的背景块高度一致,宿主按钮尺寸变化时齿轮也自动跟随。点击齿轮通过一次性的 portal 菜单 DOM 桥接激活宿主原生 Settings 动作,复用完整设置窗口;“更多”中的原生设置项被隐藏,反馈、登录、退出等操作保留。开关默认关闭,关闭时恢复原生“更多 → 设置”;Web renderer 不显示该开关也不挂载入口。实现依赖账号菜单的语义角色、菜单定位和 CSS Modules 局部类名片段,结构失配时回退到原生菜单。
301
+ - **外置设置入口**(`src/client/tweaks/desktop-settings-launcher.ts`):在 Web 与 Desktop 两个 renderer 中都生效——两端侧栏底部都由同一个账号菜单占据 `settings.launcher` 槽位,宿主自己的 Settings 按钮因此都不渲染、设置项都藏在“更多”菜单里。开启 `desktopSettingsLauncher` 后观察该账号菜单,把设置齿轮插入“更多”所在的 trigger row 右侧,形成 `[更多] [设置齿轮]`。齿轮尺寸不是写死的:它实测“更多”按钮高度后**完全等高**(展开态 44px),沿用宿主原有的居中对齐,不加额外高度也不加负外边距。这样 hover 时两个按钮的背景块高度一致,宿主按钮尺寸变化时齿轮也自动跟随。点击齿轮通过一次性的 portal 菜单 DOM 桥接激活宿主原生 Settings 动作,复用完整设置窗口;“更多”中的原生设置项被隐藏,反馈、登录、退出等操作保留。开关默认关闭,关闭时恢复原生“更多 → 设置”。实现依赖账号菜单的语义角色、菜单定位和 CSS Modules 局部类名片段,结构失配时回退到原生菜单;配置字段名保留 `desktopSettingsLauncher` 原名,以免已有配置失效。
298
302
  - **列宽样式引擎**(`src/client/conversation-width.ts`):写入 `--dsh-chat-user-width` CSS 变量,并在插件接管列宽时隐藏原生 `[data-width-handle]` 拖拽手柄;宽度值同时镜像到原生手柄读取的 localStorage 槽位,开关切换时无缝往返。
299
303
  - **调整项注册表**(`src/client/tweaks/registry.ts`):每个调整项的元数据(id、settings 字段名、默认值、i18n 键)集中登记;新增调整项只需在注册表里加一条,并在 `src/client/tweaks/` 下新增一个注入文件。
300
304
  - **项目目录运行指示**(`src/client/tweaks/project-running-indicator.ts`):从 `ctx.get('sessions')` / `ctx.get('workspaces')` 读取会话运行状态与目录归属,用 MutationObserver 在侧边栏的两类行上挂载应用自身的 `StateDot`(复用 `@deepseek-ai/dsh-client-ui-primitives` 的同一份模块挂载,但 ongoing 这个状态的外观被本项整体改掉了。宿主自己的 ongoing 是一个"呼吸弧线圆环",而且 0.1.7 起在 `prefers-reduced-motion: reduce` 下会被直接冻成静止圆环——系统开了这一项时**所有**运行动画圆点,对话标题自己的点也在内,全都停住。静止的圆环说不出"有活在跑",所以本项把全应用的 ongoing 圆点都重画成 ZCode 那种 spinner:lucide 的 `loader` 图标,24 视口里半径 6→10、约 2 单位粗、圆头,8 根等长等粗、**没有**透明度渐变,按 `animate-spin` 的 1 秒一圈匀速转,与系统设置无关。CSS 只能碰到宿主的 `svg` / `g` / 两个 `circle`,所以短棒是把 lucide 那份 `loader` 的路径数据原样做成一个 `data:` URI 的 SVG 遮罩,贴在 `svg` 元素上(`mask-size: 100% 100%`,任何尺寸的 StateDot 都成立),`svg` 自己填 `currentColor`,因此颜色仍由宿主的 CSS Modules 决定(tertiary 灰);宿主在 `svg` 里画的东西整个关掉(`> *`,不是只点名今天这两个 `circle`,宿主哪天换成别的元素也照样盖住)。用遮罩而不是用 `conic-gradient` 画扇形,是因为渐变画出来的楔形会随角度出现抗锯齿差异、看起来深浅不一,而 SVG 描边(圆头)的光栅化与 ZCode 自己渲染这个图标时完全一致。作用范围只到这个圆点组件——宿主其它按"减少动态效果"关掉的动效(工具行扫光、文字 shimmer、进度条等)不受影响):项目目录头行(`role="treeitem"[aria-expanded]`)右侧,以及会话行(`role="treeitem"` + CSS Modules 局部名 `sessionRow`)原本空着的状态槽里。"忙"的判定合并三个来源:会话自己的 `SessionSummary.running`(正在跑一轮);会话名下的后台任务——`sessions` store 快照里由 Session Controller 控制流镜像出来的 `jobsBySession`,状态为 `running` / `stopping` 即算(与 DSH 原生会话头部"后台任务"入口 `ui-jobs` 的 `isLive` 同一判据,`run_in_background` 的命令因此从启动亮到结束,不会因为发起它的那一轮已经答完而熄灭);以及血缘上有子代理在跑(与 `indexSubagentDescendants` 同一条上行遍历)。目录头行按工作区 id 匹配(从 `ProjectRowItem` 的 `props.group.key` 读,与 `GroupNode.key` 一致),不按标题文本——`GroupNode.label` 是目录 basename,两个不同父目录下的同名目录会得到同一个标题(DSH 只拦重名的*重命名*,不拦目录本身重名),按标题匹配会让两个同名目录同时亮,这正是"两个 pi-web 目录都出现运行指示"的原因;会话行只在它那一格**空着**时补点——原生已经渲染了自己的圆点(对话进行中、子代理在跑、等待审批/回答、完成未读)就让位,避免两个点并排。会话行没有可用的插件槽位,DOM 上也不带 session id,因此 id 从 React fiber 链上读(`SessionNodeItem` 的 `props.node.id`):读取全程防御式,读不到就不处理该行,且每一轮都重新解析,因为侧栏的行会被回收复用。宿主没有 `jobs` 服务时 `jobsBySession` 缺失或为空,自动退回只看原生覆盖的两种情况。
package/lib/client.js CHANGED
@@ -17,11 +17,11 @@ div:has([data-conversation-scroll]){--dsh-chat-user-width:min(${widthPx}px, max(
17
17
  }
18
18
  function installConversationWidthStyles(widthPx, sideMargin) {
19
19
  const id = 'dsh-style-tweaks-conversation-width';
20
- let style = document.querySelector(`style[data-plugin-css="${id}"]`);
20
+ let style = document.querySelector(`style[data-tweak-css="${id}"]`);
21
21
  if (style === null) {
22
22
  style = document.createElement('style');
23
- style.dataset.plugin = 'dsh-style-tweaks';
24
- style.dataset.pluginCss = id;
23
+ style.dataset.tweak = 'cst';
24
+ style.dataset.tweakCss = id;
25
25
  document.head.appendChild(style);
26
26
  }
27
27
  const owner = (0, style_node_ts_1.claimStyleNode)(style);
@@ -72,10 +72,10 @@ exports.en = {
72
72
  settingsTitle: 'Style tweaks',
73
73
  settingsIntro: 'Opt-in style tweaks for DSH: precise conversation column-width control (with presets and side margin), and a set of small fixes for the sidebar and the settings panel. Each toggle applies immediately and persists to your settings document.',
74
74
  sectionLayout: 'Layout',
75
- sectionDesktop: 'Desktop',
75
+ sectionSidebar: 'Sidebar',
76
76
  sectionTweaks: 'Tweaks',
77
77
  desktopSettingsLauncher: 'External Settings launcher',
78
- desktopSettingsLauncherHint: 'Show a Settings gear beside More in the Desktop sidebar. When off, More → Settings remains the only entry. This option is only shown in Desktop.',
78
+ desktopSettingsLauncherHint: 'Show a Settings gear beside More in the sidebar. When off, More → Settings remains the only entry.',
79
79
  dialogWidth: 'Dialog width',
80
80
  dialogWidthHint: 'Number between 600 and 1600 px; 748 is DSH\'s default column width, larger values widen it.',
81
81
  presetDefault: 'Default',
@@ -215,10 +215,10 @@ exports.zh = {
215
215
  settingsTitle: '样式调整',
216
216
  settingsIntro: 'DSH 界面的可选样式调整:对话列宽精确控制(含预设与两侧边距),以及侧边栏与设置面板的一组小幅修复。每个开关立即生效并持久化到设置文档。',
217
217
  sectionLayout: '布局',
218
- sectionDesktop: '桌面端',
218
+ sectionSidebar: '侧栏',
219
219
  sectionTweaks: '调整项',
220
220
  desktopSettingsLauncher: '外置设置入口',
221
- desktopSettingsLauncherHint: '在 Desktop 侧栏的“更多”右侧显示设置齿轮。关闭时只保留“更多 → 设置”原生入口;此项仅在 Desktop 显示。',
221
+ desktopSettingsLauncherHint: '在侧栏的“更多”右侧显示设置齿轮。关闭时只保留“更多 → 设置”原生入口。',
222
222
  dialogWidth: '对话框宽度',
223
223
  dialogWidthHint: '取值 600–1600 px;748 为 DSH 默认列宽,数字越大越宽。',
224
224
  presetDefault: '默认',
@@ -484,11 +484,11 @@ const BASE_CSS = `
484
484
  `;
485
485
  function installBaseStyles() {
486
486
  const id = 'dsh-style-tweaks-base';
487
- let style = document.querySelector(`style[data-plugin-css="${id}"]`);
487
+ let style = document.querySelector(`style[data-tweak-css="${id}"]`);
488
488
  if (style === null) {
489
489
  style = document.createElement('style');
490
- style.dataset.plugin = 'dsh-style-tweaks';
491
- style.dataset.pluginCss = id;
490
+ style.dataset.tweak = 'cst';
491
+ style.dataset.tweakCss = id;
492
492
  document.head.appendChild(style);
493
493
  }
494
494
  const owner = (0, style_node_ts_1.claimStyleNode)(style, BASE_CSS);
@@ -506,10 +506,9 @@ function apply(ctx) {
506
506
  let cleanup;
507
507
  const sync = () => {
508
508
  const value = controller.getSnapshot().value;
509
- const enabled = (0, settings_value_ts_1.isDesktopRuntime)()
510
- && (value === undefined
511
- ? tweak_config_ts_1.DEFAULT_DESKTOP_SETTINGS_LAUNCHER
512
- : (0, settings_value_ts_1.resolveValue)(value).desktopSettingsLauncher);
509
+ const enabled = value === undefined
510
+ ? tweak_config_ts_1.DEFAULT_DESKTOP_SETTINGS_LAUNCHER
511
+ : (0, settings_value_ts_1.resolveValue)(value).desktopSettingsLauncher;
513
512
  if (enabled && cleanup === undefined) {
514
513
  cleanup = (0, desktop_settings_launcher_ts_1.setupDesktopSettingsLauncher)(() => t('desktopSettingsLabel'), () => t('desktopSettingsHint'));
515
514
  }
@@ -840,7 +839,7 @@ function SettingsSection({ controller, t }) {
840
839
  return ((0, jsx_runtime_1.jsxs)("div", { className: "cst-settings", children: [(0, jsx_runtime_1.jsxs)("header", { className: "cst-settings-header", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-logo", children: "\uD83C\uDFA8" }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { children: t('settingsTitle') }), (0, jsx_runtime_1.jsx)("p", { children: t('settingsIntro') })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-status", children: snack === undefined || (snack.kind === 'saving' && !savingShown) ? null : ((0, jsx_runtime_1.jsxs)("div", { className: "cst-snack-stack", role: "status", "aria-live": "polite", children: [(0, jsx_runtime_1.jsxs)("div", { className: 'cst-snack cst-snack-' + snack.kind, children: [(0, jsx_runtime_1.jsx)("span", { className: "cst-snack-dot", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { children: t(snack.kind) })] }), snack.detail === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "cst-snack-detail", children: snack.detail }), snack.hint === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "cst-snack-detail", children: snack.hint })] })) }), !writable ? (0, jsx_runtime_1.jsx)("div", { className: "cst-alert warning", children: t('readOnly') }) : null, (0, jsx_runtime_1.jsxs)("section", { className: "cst-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-section-label", children: t('sectionLayout') }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('usePluginWidth'), (0, jsx_runtime_1.jsx)(Hint, { text: t('usePluginWidthHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.usePluginWidth ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { setUsePluginWidth(true); }, children: t('usePluginWidthOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.usePluginWidth ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { setUsePluginWidth(false); }, children: t('usePluginWidthOff') })] }) })] }) }), resolved.usePluginWidth ? ((0, jsx_runtime_1.jsxs)("div", { className: "cst-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('dialogWidth'), (0, jsx_runtime_1.jsx)(Hint, { text: t('dialogWidthHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.dialogWidth <= tweak_config_ts_1.MIN_DIALOG_WIDTH, onClick: () => { stepDialogWidth(-20); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: tweak_config_ts_1.MIN_DIALOG_WIDTH, max: tweak_config_ts_1.MAX_DIALOG_WIDTH, step: 20, value: widthDraft, disabled: !writable, onChange: (event) => { setWidthDraft(event.target.value); }, onBlur: (event) => { commitDialogWidth(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
841
840
  commitDialogWidth(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.dialogWidth >= tweak_config_ts_1.MAX_DIALOG_WIDTH, onClick: () => { stepDialogWidth(20); }, children: "+" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-presets", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 880 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(880); }, children: [t('presetWide'), " \u00B7 880"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 1024 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(1024); }, children: [t('presetWideXl'), " \u00B7 1024"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 748 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(748); }, children: [t('presetDefault'), " \u00B7 748"] })] }) })] })) : null, resolved.usePluginWidth ? ((0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('sideMargin'), (0, jsx_runtime_1.jsx)(Hint, { text: t('sideMarginHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.sideMargin <= tweak_config_ts_1.MIN_SIDE_MARGIN, onClick: () => { stepSideMargin(-4); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: tweak_config_ts_1.MIN_SIDE_MARGIN, step: 4, value: marginDraft, disabled: !writable, onChange: (event) => { setMarginDraft(event.target.value); }, onBlur: (event) => { commitSideMargin(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
842
841
  commitSideMargin(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable, onClick: () => { stepSideMargin(4); }, children: "+" })] }) })] }) })) : null, (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('rightbarInitialWidth'), (0, jsx_runtime_1.jsx)(Hint, { text: t('rightbarInitialWidthHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.rightbarInitialWidth ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { setRightbarInitialWidth(true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.rightbarInitialWidth ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { setRightbarInitialWidth(false); }, children: t('tweakOff') })] }) })] }) }), resolved.rightbarInitialWidth ? ((0, jsx_runtime_1.jsxs)("div", { className: "cst-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('rightbarWidthPercent'), (0, jsx_runtime_1.jsx)(Hint, { text: t('rightbarWidthPercentHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.rightbarWidthPercent <= tweak_config_ts_1.MIN_RIGHTBAR_WIDTH_PERCENT, onClick: () => { stepRightbarWidth(-5); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: tweak_config_ts_1.MIN_RIGHTBAR_WIDTH_PERCENT, max: tweak_config_ts_1.MAX_RIGHTBAR_WIDTH_PERCENT, step: 5, value: rightbarWidthDraft, disabled: !writable, onChange: (event) => { setRightbarWidthDraft(event.target.value); }, onBlur: (event) => { commitRightbarWidth(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
843
- commitRightbarWidth(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.rightbarWidthPercent >= tweak_config_ts_1.MAX_RIGHTBAR_WIDTH_PERCENT, onClick: () => { stepRightbarWidth(5); }, children: "+" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-presets", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.rightbarWidthPercent === 30 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(30); }, children: "30%" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.rightbarWidthPercent === 40 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(40); }, children: "40%" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.rightbarWidthPercent === 45 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(45); }, children: [t('presetDefault'), " \u00B7 45%"] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.rightbarWidthPercent === 55 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(55); }, children: "55%" })] }) })] })) : null] }), (0, settings_value_ts_1.isDesktopRuntime)() ? ((0, jsx_runtime_1.jsxs)("section", { className: "cst-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-section-label", children: t('sectionDesktop') }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('desktopSettingsLauncher'), (0, jsx_runtime_1.jsx)(Hint, { text: t('desktopSettingsLauncherHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.desktopSettingsLauncher ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('desktopSettingsLauncher', true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.desktopSettingsLauncher ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('desktopSettingsLauncher', false); }, children: t('tweakOff') })] }) })] }) })] })) : null, (0, jsx_runtime_1.jsxs)("section", { className: "cst-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-section-label", children: t('sectionHistory') }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('historyPageSizeEnabled'), (0, jsx_runtime_1.jsx)(Hint, { text: t('historyPageSizeEnabledHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.historyPageSizeEnabled ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('historyPageSizeEnabled', true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.historyPageSizeEnabled ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('historyPageSizeEnabled', false); }, children: t('tweakOff') })] }) })] }) }), resolved.historyPageSizeEnabled ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('historyPageSize'), (0, jsx_runtime_1.jsx)(Hint, { text: t('historyPageSizeHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.historyPageSize <= tweak_config_ts_1.MIN_HISTORY_PAGE_SIZE, onClick: () => { stepHistoryPageSize(-tweak_config_ts_1.STEP_HISTORY_PAGE_SIZE); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: tweak_config_ts_1.MIN_HISTORY_PAGE_SIZE, max: tweak_config_ts_1.MAX_HISTORY_PAGE_SIZE, step: tweak_config_ts_1.STEP_HISTORY_PAGE_SIZE, value: historyPageSizeDraft, disabled: !writable, onChange: (event) => { setHistoryPageSizeDraft(event.target.value); }, onBlur: (event) => { commitHistoryPageSize(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
842
+ commitRightbarWidth(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.rightbarWidthPercent >= tweak_config_ts_1.MAX_RIGHTBAR_WIDTH_PERCENT, onClick: () => { stepRightbarWidth(5); }, children: "+" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-presets", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.rightbarWidthPercent === 30 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(30); }, children: "30%" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.rightbarWidthPercent === 40 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(40); }, children: "40%" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.rightbarWidthPercent === 45 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(45); }, children: [t('presetDefault'), " \u00B7 45%"] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.rightbarWidthPercent === 55 ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { applyRightbarWidthPreset(55); }, children: "55%" })] }) })] })) : null] }), (0, jsx_runtime_1.jsxs)("section", { className: "cst-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-section-label", children: t('sectionSidebar') }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('desktopSettingsLauncher'), (0, jsx_runtime_1.jsx)(Hint, { text: t('desktopSettingsLauncherHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.desktopSettingsLauncher ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('desktopSettingsLauncher', true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.desktopSettingsLauncher ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('desktopSettingsLauncher', false); }, children: t('tweakOff') })] }) })] }) })] }), (0, jsx_runtime_1.jsxs)("section", { className: "cst-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-section-label", children: t('sectionHistory') }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('historyPageSizeEnabled'), (0, jsx_runtime_1.jsx)(Hint, { text: t('historyPageSizeEnabledHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.historyPageSizeEnabled ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('historyPageSizeEnabled', true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.historyPageSizeEnabled ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('historyPageSizeEnabled', false); }, children: t('tweakOff') })] }) })] }) }), resolved.historyPageSizeEnabled ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('historyPageSize'), (0, jsx_runtime_1.jsx)(Hint, { text: t('historyPageSizeHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.historyPageSize <= tweak_config_ts_1.MIN_HISTORY_PAGE_SIZE, onClick: () => { stepHistoryPageSize(-tweak_config_ts_1.STEP_HISTORY_PAGE_SIZE); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: tweak_config_ts_1.MIN_HISTORY_PAGE_SIZE, max: tweak_config_ts_1.MAX_HISTORY_PAGE_SIZE, step: tweak_config_ts_1.STEP_HISTORY_PAGE_SIZE, value: historyPageSizeDraft, disabled: !writable, onChange: (event) => { setHistoryPageSizeDraft(event.target.value); }, onBlur: (event) => { commitHistoryPageSize(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
844
843
  commitHistoryPageSize(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.historyPageSize >= tweak_config_ts_1.MAX_HISTORY_PAGE_SIZE, onClick: () => { stepHistoryPageSize(tweak_config_ts_1.STEP_HISTORY_PAGE_SIZE); }, children: "+" })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('historyPageSizeColdStart'), (0, jsx_runtime_1.jsx)(Hint, { text: t('historyPageSizeColdStartHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.historyPageSizeColdStart ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { setHistoryPageSizeColdStart(true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.historyPageSizeColdStart ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { setHistoryPageSizeColdStart(false); }, children: t('tweakOff') })] }) })] }) })] })) : null] }), (0, jsx_runtime_1.jsxs)("section", { className: "cst-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-section-label", children: t('sectionSidebarList') }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('sidebarSessionCountEnabled'), (0, jsx_runtime_1.jsx)(Hint, { text: t('sidebarSessionCountEnabledHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.sidebarSessionCountEnabled ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('sidebarSessionCountEnabled', true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.sidebarSessionCountEnabled ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('sidebarSessionCountEnabled', false); }, children: t('tweakOff') })] }) })] }) }), resolved.sidebarSessionCountEnabled ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('sidebarSessionInitialCount'), (0, jsx_runtime_1.jsx)(Hint, { text: t('sidebarSessionInitialCountHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.sidebarSessionInitialCount <= tweak_config_ts_1.MIN_SIDEBAR_SESSION_INITIAL_COUNT, onClick: () => { stepSidebarCount('sidebarSessionInitialCount', -tweak_config_ts_1.STEP_SIDEBAR_SESSION_COUNT, setSidebarSessionInitialCountDraft, resolved.sidebarSessionInitialCount, tweak_config_ts_1.MIN_SIDEBAR_SESSION_INITIAL_COUNT); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: tweak_config_ts_1.MIN_SIDEBAR_SESSION_INITIAL_COUNT, step: tweak_config_ts_1.STEP_SIDEBAR_SESSION_COUNT, value: sidebarSessionInitialCountDraft, disabled: !writable, onChange: (event) => { setSidebarSessionInitialCountDraft(event.target.value); }, onBlur: (event) => { commitSidebarCount('sidebarSessionInitialCount', event.target.value, setSidebarSessionInitialCountDraft, resolved.sidebarSessionInitialCount, tweak_config_ts_1.MIN_SIDEBAR_SESSION_INITIAL_COUNT); }, onKeyDown: (event) => { if (event.key === 'Enter')
845
844
  commitSidebarCount('sidebarSessionInitialCount', event.target.value, setSidebarSessionInitialCountDraft, resolved.sidebarSessionInitialCount, tweak_config_ts_1.MIN_SIDEBAR_SESSION_INITIAL_COUNT); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable, onClick: () => { stepSidebarCount('sidebarSessionInitialCount', tweak_config_ts_1.STEP_SIDEBAR_SESSION_COUNT, setSidebarSessionInitialCountDraft, resolved.sidebarSessionInitialCount, tweak_config_ts_1.MIN_SIDEBAR_SESSION_INITIAL_COUNT); }, children: "+" })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('sidebarSessionExpandStep'), (0, jsx_runtime_1.jsx)(Hint, { text: t('sidebarSessionExpandStepHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.sidebarSessionExpandStep <= tweak_config_ts_1.MIN_SIDEBAR_SESSION_EXPAND_STEP, onClick: () => { stepSidebarCount('sidebarSessionExpandStep', -tweak_config_ts_1.STEP_SIDEBAR_SESSION_COUNT, setSidebarSessionExpandStepDraft, resolved.sidebarSessionExpandStep, tweak_config_ts_1.MIN_SIDEBAR_SESSION_EXPAND_STEP); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: tweak_config_ts_1.MIN_SIDEBAR_SESSION_EXPAND_STEP, step: tweak_config_ts_1.STEP_SIDEBAR_SESSION_COUNT, value: sidebarSessionExpandStepDraft, disabled: !writable, onChange: (event) => { setSidebarSessionExpandStepDraft(event.target.value); }, onBlur: (event) => { commitSidebarCount('sidebarSessionExpandStep', event.target.value, setSidebarSessionExpandStepDraft, resolved.sidebarSessionExpandStep, tweak_config_ts_1.MIN_SIDEBAR_SESSION_EXPAND_STEP); }, onKeyDown: (event) => { if (event.key === 'Enter')
846
845
  commitSidebarCount('sidebarSessionExpandStep', event.target.value, setSidebarSessionExpandStepDraft, resolved.sidebarSessionExpandStep, tweak_config_ts_1.MIN_SIDEBAR_SESSION_EXPAND_STEP); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable, onClick: () => { stepSidebarCount('sidebarSessionExpandStep', tweak_config_ts_1.STEP_SIDEBAR_SESSION_COUNT, setSidebarSessionExpandStepDraft, resolved.sidebarSessionExpandStep, tweak_config_ts_1.MIN_SIDEBAR_SESSION_EXPAND_STEP); }, children: "+" })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "cst-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "cst-label", children: [t('sidebarSessionExpandAnimation'), (0, jsx_runtime_1.jsx)(Hint, { text: t('sidebarSessionExpandAnimationHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "cst-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "cst-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.sidebarSessionExpandAnimation ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('sidebarSessionExpandAnimation', true); }, children: t('tweakOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.sidebarSessionExpandAnimation ? 'cst-seg-active' : '', disabled: !writable, onClick: () => { save('sidebarSessionExpandAnimation', false); }, children: t('tweakOff') })] }) })] }) })] })) : null] }), (0, jsx_runtime_1.jsxs)("section", { className: "cst-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "cst-section-label", children: t('sectionTweaks') }), registry_ts_1.TWEAKS.map(tweak => {
@@ -857,6 +856,20 @@ function SettingsSection({ controller, t }) {
857
856
  }, children: t('workspaceCloseRestore') }) }) })] }) }, entry.workspaceId)))] }))] }));
858
857
  }
859
858
  };
859
+ __modules["./report-once.js"] = function(module, exports, require, __load_) {
860
+ "use strict";
861
+ Object.defineProperty(exports, "__esModule", { value: true });
862
+ exports.reportOnce = reportOnce;
863
+ const REPORTED_PREFIX = '__cst_reported__';
864
+ function reportOnce(topic, report) {
865
+ const host = globalThis;
866
+ const key = `${REPORTED_PREFIX}${topic}__`;
867
+ if (host[key] === true)
868
+ return;
869
+ host[key] = true;
870
+ report();
871
+ }
872
+ };
860
873
  __modules["./settings-client.js"] = function(module, exports, require, __load_) {
861
874
  "use strict";
862
875
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1236,13 +1249,9 @@ function setupSettingsNavIcon(label) {
1236
1249
  __modules["./settings-value.js"] = function(module, exports, require, __load_) {
1237
1250
  "use strict";
1238
1251
  Object.defineProperty(exports, "__esModule", { value: true });
1239
- exports.isDesktopRuntime = isDesktopRuntime;
1240
1252
  exports.resolveValue = resolveValue;
1241
1253
  exports.sameMountInputs = sameMountInputs;
1242
1254
  const tweak_config_ts_1 = __load_("./tweak-config.js");
1243
- function isDesktopRuntime() {
1244
- return typeof globalThis !== 'undefined' && 'dshDesktop' in globalThis;
1245
- }
1246
1255
  function resolveValue(value) {
1247
1256
  return (0, tweak_config_ts_1.resolveClientConfig)(value);
1248
1257
  }
@@ -1813,9 +1822,6 @@ function installStyles() {
1813
1822
  const owner = (0, style_node_ts_1.claimStyleNode)(style, STYLE_TEXT);
1814
1823
  return () => { (0, style_node_ts_1.releaseStyleNode)(style, owner); };
1815
1824
  }
1816
- function isDesktop() {
1817
- return typeof globalThis !== 'undefined' && 'dshDesktop' in globalThis;
1818
- }
1819
1825
  function accountContext() {
1820
1826
  const areas = document.querySelectorAll(ACCOUNT_AREA_SELECTOR);
1821
1827
  for (const area of areas) {
@@ -1961,8 +1967,6 @@ function setupDesktopSettingsLauncher(label, hint) {
1961
1967
  previous();
1962
1968
  setGlobalCleanup(undefined);
1963
1969
  }
1964
- if (!isDesktop())
1965
- return () => { };
1966
1970
  const observerTarget = document.body ?? document.documentElement;
1967
1971
  if (observerTarget === null)
1968
1972
  return () => { };
@@ -2199,9 +2203,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
2199
2203
  exports.setHistoryPageSizeTargets = setHistoryPageSizeTargets;
2200
2204
  exports.resetHistoryPageSizeTargets = resetHistoryPageSizeTargets;
2201
2205
  exports.installHistoryPageSizeTransport = installHistoryPageSizeTransport;
2206
+ const report_once_ts_1 = __load_("../report-once.js");
2202
2207
  const tweak_config_ts_1 = __load_("../tweak-config.js");
2203
2208
  const STORAGE_KEY = 'dsh-style-tweaks.history-page-size';
2204
2209
  const SHARED_KEY = '__dsh_style_tweaks_history_page_size__';
2210
+ function warnWireDrift(kind, error) {
2211
+ (0, report_once_ts_1.reportOnce)(`wire-drift:${kind}`, () => {
2212
+ console.error(`[dsh-style-tweaks] ${kind} request no longer parses — the host wire shape changed; the configured history page size is not applied (${error instanceof Error ? error.message : String(error)})`);
2213
+ });
2214
+ }
2205
2215
  let myGeneration = 0;
2206
2216
  function sharedState() {
2207
2217
  const host = globalThis;
@@ -2313,6 +2323,8 @@ function applyPageSize(request, targets) {
2313
2323
  return changed;
2314
2324
  }
2315
2325
  function rewritePageBody(body, targets) {
2326
+ if (targets.pageSize === null)
2327
+ return null;
2316
2328
  try {
2317
2329
  const msg = JSON.parse(body);
2318
2330
  const request = msg?.payload?.args?.request;
@@ -2320,7 +2332,8 @@ function rewritePageBody(body, targets) {
2320
2332
  return null;
2321
2333
  return JSON.stringify(msg);
2322
2334
  }
2323
- catch {
2335
+ catch (error) {
2336
+ warnWireDrift('session/page', error);
2324
2337
  return null;
2325
2338
  }
2326
2339
  }
@@ -2339,7 +2352,8 @@ function rewriteFollowFrame(data, targets) {
2339
2352
  return null;
2340
2353
  return JSON.stringify(msg);
2341
2354
  }
2342
- catch {
2355
+ catch (error) {
2356
+ warnWireDrift('session/follow', error);
2343
2357
  return null;
2344
2358
  }
2345
2359
  }
@@ -2509,11 +2523,11 @@ div:has(> div[class*="_dock"]) [class*="_trailing"] > button[class*="_primary"]{
2509
2523
  `;
2510
2524
  function installMeterStyles() {
2511
2525
  const id = 'dsh-style-tweaks-legacy-context-meter';
2512
- let style = document.querySelector(`style[data-plugin-css="${id}"]`);
2526
+ let style = document.querySelector(`style[data-tweak-css="${id}"]`);
2513
2527
  if (style === null) {
2514
2528
  style = document.createElement('style');
2515
- style.dataset.plugin = 'dsh-style-tweaks';
2516
- style.dataset.pluginCss = id;
2529
+ style.dataset.tweak = 'cst';
2530
+ style.dataset.tweakCss = id;
2517
2531
  document.head.appendChild(style);
2518
2532
  }
2519
2533
  const owner = (0, style_node_ts_1.claimStyleNode)(style, METER_CSS);
@@ -2764,11 +2778,11 @@ const LEGACY_STATS_CSS = `
2764
2778
  `;
2765
2779
  function installLegacyStatsStyles() {
2766
2780
  const id = 'dsh-style-tweaks-legacy-stats';
2767
- let style = document.querySelector(`style[data-plugin-css="${id}"]`);
2781
+ let style = document.querySelector(`style[data-tweak-css="${id}"]`);
2768
2782
  if (style === null) {
2769
2783
  style = document.createElement('style');
2770
- style.dataset.plugin = 'dsh-style-tweaks';
2771
- style.dataset.pluginCss = id;
2784
+ style.dataset.tweak = 'cst';
2785
+ style.dataset.tweakCss = id;
2772
2786
  document.head.appendChild(style);
2773
2787
  }
2774
2788
  const owner = (0, style_node_ts_1.claimStyleNode)(style, LEGACY_STATS_CSS);
@@ -3631,11 +3645,11 @@ button.cst-pills-pill:hover,button.cst-pills-pill[aria-expanded='true']{backgrou
3631
3645
  `;
3632
3646
  function installPillsStyles() {
3633
3647
  const id = 'dsh-style-tweaks-pills-decimals';
3634
- let style = document.querySelector(`style[data-plugin-css="${id}"]`);
3648
+ let style = document.querySelector(`style[data-tweak-css="${id}"]`);
3635
3649
  if (style === null) {
3636
3650
  style = document.createElement('style');
3637
- style.dataset.plugin = 'dsh-style-tweaks';
3638
- style.dataset.pluginCss = id;
3651
+ style.dataset.tweak = 'cst';
3652
+ style.dataset.tweakCss = id;
3639
3653
  document.head.appendChild(style);
3640
3654
  }
3641
3655
  const owner = (0, style_node_ts_1.claimStyleNode)(style, PILLS_CSS);
@@ -4745,6 +4759,7 @@ __modules["./tweaks/sidebar-middle-click-close.js"] = function(module, exports,
4745
4759
  "use strict";
4746
4760
  Object.defineProperty(exports, "__esModule", { value: true });
4747
4761
  exports.setupSidebarMiddleClickClose = setupSidebarMiddleClickClose;
4762
+ const report_once_ts_1 = __load_("../report-once.js");
4748
4763
  const MIDDLE_BUTTON = 1;
4749
4764
  const CHIP_SELECTOR = '[data-dockkit-tab]';
4750
4765
  const FLOAT_GRIP_SELECTOR = '[data-dockkit-float-grip]';
@@ -4776,6 +4791,10 @@ function floatGripOf(target) {
4776
4791
  function isMiddleTarget(target) {
4777
4792
  return chipOf(target) !== undefined || floatGripOf(target) !== undefined;
4778
4793
  }
4794
+ const NO_SURFACE_MESSAGE = 'no session surface is mounted';
4795
+ function isExpectedCloseRejection(error) {
4796
+ return (error instanceof Error ? error.message : String(error)).includes(NO_SURFACE_MESSAGE);
4797
+ }
4779
4798
  const GLOBAL_KEY = '__cst_sidebar_middle_click_close_cleanup__';
4780
4799
  function getGlobalCleanup() {
4781
4800
  return window[GLOBAL_KEY];
@@ -4819,7 +4838,12 @@ function setupSidebarMiddleClickClose(ctx) {
4819
4838
  try {
4820
4839
  face.close(tabId);
4821
4840
  }
4822
- catch {
4841
+ catch (error) {
4842
+ if (!isExpectedCloseRejection(error)) {
4843
+ (0, report_once_ts_1.reportOnce)('sidebar-close-failure', () => {
4844
+ console.warn('[dsh-style-tweaks] closing a sidebar tab failed', error);
4845
+ });
4846
+ }
4823
4847
  }
4824
4848
  return;
4825
4849
  }
@@ -4922,6 +4946,7 @@ function injectStyles() {
4922
4946
  let style = document.querySelector(`style[data-tweak-css="${id}"]`);
4923
4947
  if (style === null) {
4924
4948
  style = document.createElement('style');
4949
+ style.dataset.tweak = 'cst';
4925
4950
  style.dataset.tweakCss = id;
4926
4951
  document.head.appendChild(style);
4927
4952
  }
@@ -5567,6 +5592,7 @@ __modules["./tweaks/stats-cache-hit.js"] = function(module, exports, require, __
5567
5592
  Object.defineProperty(exports, "__esModule", { value: true });
5568
5593
  exports.billedInputTokens = billedInputTokens;
5569
5594
  exports.formatCacheHitPercent = formatCacheHitPercent;
5595
+ const report_once_ts_1 = __load_("../report-once.js");
5570
5596
  function billedInputTokens(usage) {
5571
5597
  return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
5572
5598
  }
@@ -5580,9 +5606,22 @@ function displayPercentUnits(units, decimalPlaces) {
5580
5606
  return frac === 0 ? String(whole) : `${whole}.${frac}`;
5581
5607
  return `${whole}.${String(frac).padStart(2, '0')}`;
5582
5608
  }
5609
+ function reportUnusableReading(cacheReadTokens, promptTokens) {
5610
+ (0, report_once_ts_1.reportOnce)('cache-hit-reading', () => {
5611
+ console.error(`[dsh-style-tweaks] cache-hit reading dropped — cacheRead=${cacheReadTokens} against prompt=${promptTokens} is not a usable pair; the host's token projection changed shape`);
5612
+ });
5613
+ }
5583
5614
  function formatCacheHitPercent(cacheReadTokens, promptTokens, decimalPlaces) {
5584
5615
  if (promptTokens === 0)
5585
5616
  return null;
5617
+ if (!Number.isFinite(promptTokens) ||
5618
+ !Number.isFinite(cacheReadTokens) ||
5619
+ promptTokens < 0 ||
5620
+ cacheReadTokens < 0 ||
5621
+ cacheReadTokens > promptTokens) {
5622
+ reportUnusableReading(cacheReadTokens, promptTokens);
5623
+ return null;
5624
+ }
5586
5625
  const missedInputTokens = promptTokens - cacheReadTokens;
5587
5626
  if (missedInputTokens === 0)
5588
5627
  return '100';
@@ -6101,11 +6140,11 @@ const TURN_TIME_PILL_CSS = `
6101
6140
  `;
6102
6141
  function installTurnTimePillStyles() {
6103
6142
  const id = 'dsh-style-tweaks-turn-time-pill';
6104
- let style = document.querySelector(`style[data-plugin-css="${id}"]`);
6143
+ let style = document.querySelector(`style[data-tweak-css="${id}"]`);
6105
6144
  if (style === null) {
6106
6145
  style = document.createElement('style');
6107
- style.dataset.plugin = 'dsh-style-tweaks';
6108
- style.dataset.pluginCss = id;
6146
+ style.dataset.tweak = 'cst';
6147
+ style.dataset.tweakCss = id;
6109
6148
  document.head.appendChild(style);
6110
6149
  }
6111
6150
  const owner = (0, style_node_ts_1.claimStyleNode)(style, TURN_TIME_PILL_CSS);