dsh-style-tweaks 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +9 -6
- package/README.md +9 -6
- package/lib/client.js +1007 -111
- package/lib/client.js.map +1 -1
- package/lib/config.js +22 -0
- package/lib/config.js.map +1 -1
- package/lib/types/client/index.d.ts +23 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/tweak-config.d.ts +9 -0
- package/lib/types/client/tweak-config.d.ts.map +1 -1
- package/lib/types/client/tweak-types.d.ts +4 -0
- package/lib/types/client/tweak-types.d.ts.map +1 -1
- package/lib/types/client/tweaks/locate-current-session.d.ts +4 -1
- package/lib/types/client/tweaks/locate-current-session.d.ts.map +1 -1
- package/lib/types/client/tweaks/project-running-indicator.d.ts +21 -15
- package/lib/types/client/tweaks/project-running-indicator.d.ts.map +1 -1
- package/lib/types/client/tweaks/registry.d.ts.map +1 -1
- package/lib/types/client/tweaks/workspace-close.d.ts +99 -0
- package/lib/types/client/tweaks/workspace-close.d.ts.map +1 -0
- package/lib/types/config.d.ts +47 -6
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/web.d.ts.map +1 -1
- package/lib/web.js +10 -1
- package/lib/web.js.map +1 -1
- package/package.json +3 -2
package/README.en.md
CHANGED
|
@@ -29,6 +29,7 @@ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) web
|
|
|
29
29
|
- **Legacy stats line (default off)** — since DSH 0.1.5 the stats under the input box are two icon pills that open dialogs; this tweak brings back the pre-0.1.5 centered text line (turns/steps · timings · speed · cache hit · tokens). It reads the same durable projections (`sessionStats` / `tokenUsage`) so every figure matches the pills; an overlong line truncates with an ellipsis and reveals itself on hover, and turning the tweak off hands the row straight back to the pills.
|
|
30
30
|
- **Cache hit with two decimals (default off)** — shows the composer stats' cache-hit share with two decimal places (e.g. `87.35%`) instead of integer rounding; the token-usage dialog follows. Applies to both presentations — the new icon pills and the legacy text line alike; with both toggles on, the legacy line renders and takes the flag.
|
|
31
31
|
- **Turn speed & TTFT (default off)** — since 0.1.5, cold sessions no longer rebuild per-token timing, so each turn's time dialog keeps only the wall-clock duration. Clicking a turn's time pill refills that dialog with the output speed and TTFT rows, rebuilt from the model stream embedded in the session log; history loads get the same figures a live session did.
|
|
32
|
+
- **Closable workspaces (default off)** — hide a Workspace from the sidebar list and the New Session picker instead of deleting it: the host registry row, its session account, the files on disk and every session log are all kept, the Workspace and every session keep their account position, and **its sessions are hidden with it** (they never fall into Ungrouped, and search stops listing them); restoring the Workspace brings the whole group back. Restore it by re-adding the same folder (the host resolves it by canonical path and returns the same Workspace) or from the "Closed workspaces" list in the Style tweaks settings panel (each entry reveals its folder on hover). The entry sits in each Workspace row's `...` menu, above "Delete workspace", behind a confirmation that spells out what is kept. Three boundaries: a closed Workspace's manual session order is not preserved (sessions show in host account order); closing the Workspace a session is currently open in hands you straight to a new session (in the most recent visible Workspace); and "New session" triggered from a session inside a closed Workspace falls back to the most recent **visible** Workspace (the host's own fallback).
|
|
32
33
|
|
|
33
34
|
```yaml
|
|
34
35
|
style-tweaks:
|
|
@@ -52,6 +53,8 @@ style-tweaks:
|
|
|
52
53
|
legacyStatsLine: false # default false; true swaps the pills for the 0.1.2 text line
|
|
53
54
|
pillsCacheHitDecimals: false # default false; true shows the cache hit with two decimals (pills and legacy line)
|
|
54
55
|
turnSpeedMetrics: false # default false; true refills the turn-time dialog with output speed and TTFT
|
|
56
|
+
workspaceClose: false # default false; true enables closing (hiding) a Workspace from its row menu
|
|
57
|
+
closedWorkspaces: [] # ids of closed Workspaces (internal data; managed by the panel's list)
|
|
55
58
|
```
|
|
56
59
|
|
|
57
60
|
Settings entry: **Settings → Style tweaks**.
|
|
@@ -70,8 +73,8 @@ The package spec after `add` is forwarded to pnpm verbatim, so versions can be
|
|
|
70
73
|
pinned — `@version` for the npm package, `#tag` for the GitHub source:
|
|
71
74
|
|
|
72
75
|
```bash
|
|
73
|
-
dsh plugin --profile web add dsh-style-tweaks@0.1.
|
|
74
|
-
dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.
|
|
76
|
+
dsh plugin --profile web add dsh-style-tweaks@0.1.4 # pin the npm version
|
|
77
|
+
dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.4 # pin a git tag
|
|
75
78
|
```
|
|
76
79
|
|
|
77
80
|
Restart DSH web once after installing (bundle plugins are scanned at process start).
|
|
@@ -229,7 +232,7 @@ source change has to be copied over — exactly the step the `link:` setup spare
|
|
|
229
232
|
```bash
|
|
230
233
|
pnpm build
|
|
231
234
|
pnpm pack # produces dsh-style-tweaks-<version>.tgz (gitignored; do not commit)
|
|
232
|
-
dsh plugin --profile web add ./dsh-style-tweaks-0.1.
|
|
235
|
+
dsh plugin --profile web add ./dsh-style-tweaks-0.1.4.tgz
|
|
233
236
|
```
|
|
234
237
|
|
|
235
238
|
**Restart `dsh web` once** after installing (bundle plugins are scanned at process start).
|
|
@@ -274,7 +277,7 @@ path dependency:
|
|
|
274
277
|
|
|
275
278
|
```bash
|
|
276
279
|
dsh plugin --profile web remove dsh-style-tweaks
|
|
277
|
-
dsh plugin --profile web add ./dsh-style-tweaks-0.1.
|
|
280
|
+
dsh plugin --profile web add ./dsh-style-tweaks-0.1.4.tgz
|
|
278
281
|
```
|
|
279
282
|
|
|
280
283
|
The only difference between the two setups is whether the artifact lands in place automatically: both
|
|
@@ -287,8 +290,8 @@ to verify the published package itself.
|
|
|
287
290
|
- **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).
|
|
288
291
|
- **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.
|
|
289
292
|
- **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/`.
|
|
290
|
-
- **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` (re-using `@deepseek-ai/dsh-client-ui-primitives` so the animation keyframes and style tokens are byte-identical to the conversation title's dot) 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
|
|
291
|
-
- **Locate current session** (`src/client/tweaks/locate-current-session.ts`) injects a new button into the sidebar's workspaces section header, to the left of the native search button. All anchors are i18n-safe: the search button's aria-label and the breadcrumb nav's aria-label are resolved through `ctx.locale.bind()` (the `workspace` / `conversation` namespace keys DSH itself uses), with structural class-fragment fallbacks (`searchButton`, `crumbs`) when the locale service is unavailable. Clicking it reads the current session title from the breadcrumb's disabled crumb, resolves the parent workspace via `ctx.get('sessions')` / `ctx.get('workspaces')` app stores (works even when the workspace's sidebar group is collapsed), finds the workspace row by exact title match, then pierces both collapse layers — clicking the collapsed workspace row to expand it, and auto-clicking the "Show {n} more sessions" overflow button (`[class*="sessionOverflowButton"][aria-expanded="false"]`) when the target row hides behind it — before `scrollIntoView({ block: 'center' })`. The hover tooltip replicates DSH's native `<Tooltip>` primitive (fixed-position bubble, theme tokens, 500 ms delay, viewport flip) instead of the browser-native `title`. A MutationObserver watches `aria-selected` / `aria-expanded` attribute changes to keep the button's enabled state and mount in sync.
|
|
293
|
+
- **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` (re-using `@deepseek-ai/dsh-client-ui-primitives` so the animation keyframes and style tokens are byte-identical to the conversation title's dot) 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.
|
|
294
|
+
- **Locate current session** (`src/client/tweaks/locate-current-session.ts`) injects a new button into the sidebar's workspaces section header, to the left of the native search button. All anchors are i18n-safe: the search button's aria-label and the breadcrumb nav's aria-label are resolved through `ctx.locale.bind()` (the `workspace` / `conversation` namespace keys DSH itself uses), with structural class-fragment fallbacks (`searchButton`, `crumbs`) when the locale service is unavailable. Clicking it reads the current session title from the breadcrumb's disabled crumb, resolves the parent workspace **id** via `ctx.get('sessions')` / `ctx.get('workspaces')` app stores (works even when the workspace's sidebar group is collapsed), finds the workspace row by that id (`ProjectRowItem`'s `props.group.key`, read off the fiber chain) rather than by exact title match — two Workspaces whose directories share a basename share a label, and a title match then opens whichever one comes first — then pierces both collapse layers — clicking the collapsed workspace row to expand it, and auto-clicking the "Show {n} more sessions" overflow button (`[class*="sessionOverflowButton"][aria-expanded="false"]`) when the target row hides behind it — before `scrollIntoView({ block: 'center' })`. The hover tooltip replicates DSH's native `<Tooltip>` primitive (fixed-position bubble, theme tokens, 500 ms delay, viewport flip) instead of the browser-native `title`. A MutationObserver watches `aria-selected` / `aria-expanded` attribute changes to keep the button's enabled state and mount in sync.
|
|
292
295
|
- **Scrollable settings nav** (`src/client/tweaks/settings-nav-scroll.ts`) turns the settings dialog's left nav list into the scroll container of the nav rail: DSH's settings panel (`SettingsRoot`) has one fixed height with `overflow: hidden` and only gave the right content column (`.options`) an `overflow-y: auto`; the left `.navList` has neither `min-height: 0` nor an overflow treatment, so extra entries spill out and get clipped. Two CSS rules plus a small JS driver do it: the base rule `flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-bottom: 12px; margin-inline-end: -12px` makes the list scrollable and parks the classic scrollbar in the nav rail's full 12px right padding gutter (8px gap + 4px strip), so the thumb floats over the spare padding 8px clear of the entries; a cell cap `max-width: 164px` (DSH's stock 188 nav − 2×12 padding) pins the entries to stock width in every state — bar present or not, hovering or scrolling — and `::-webkit-scrollbar { width: 4px }` is two notches thinner than DSH's global skin. The show timing is JS-driven: the first cut used `:not(:hover)` to blank the thumb, but Chromium does not reliably repaint custom scrollbar pseudo-elements when only the host's `:hover` state changes (in practice the bar appeared when clicking a nav cell — the React re-render forced the repaint), so the driver listens for `scroll` events on the list instead: scrolling adds a `cst-nav-scroll-show` class that reveals the thumb (coloured from the panel's inherited l2 tokens), and the class is removed ~0.8 s after scrolling stops; a MutationObserver re-attaches the listener whenever the dialog unmounts and reopens. The selector pairs the modal structure with a CSS Modules class fragment (DSH hashes classes as `[hash]_[local]`; the `navList` local name is unique across DSH), and needs no `!important`.
|
|
293
296
|
- **Middle-click closes sidebar tabs** (`src/client/tweaks/sidebar-middle-click-close.ts`): a right-Sidebar tab chip is `div[role="tab"][data-dockkit-tab="<tabId>"]`, rendered by `ui-dockkit`'s `TabPanel`, and the docking kit is adopted only by the right Sidebar (docked panel plus floating layer), so "chip" is exactly "Sidebar tab". The tweak hangs three listeners on `document` in the capture phase (React 17+ listens at the root container, so document capture precedes the whole React tree and the interception is total): `pointerdown` (middle) stops propagation so the chip's own press→drag gesture never begins — holding the middle button and drifting ≥4px can no longer place, split, or float the tab; `mousedown` (middle) calls `preventDefault()`, suppressing the browser's middle-click autoscroll (the `auxclick` that follows still fires); `auxclick` (middle — browsers deliver non-primary clicks as `auxclick`, never `click`) reads the chip's `TabId` and hands it to `ctx.sidebarRight.close()`, so every closing rule is the host store's own planner's (missing tab → no-op, the sole docked guide is unclosable, closing the last docked tab collapses the column) and the plugin duplicates none of them. `ctx.sidebarRight` only exists on 0.1.5+ hosts, and it is provided by `ui-sidebar-right` in that package's own plugin fiber — a plain property read resolves services only along the ancestor-fiber chain, so from a sibling plugin it throws ("cannot get property … without inject") even on hosts that have the face, while declaring the service in this plugin's `inject` would make it required and dead-lock the boot on pre-0.1.5 hosts; so the face is resolved at every middle close through `ctx.reflect.get('sidebarRight')` — the reflect instance and its store are shared per root and `provide` allocates the isolation key at the root, so `get` reads sibling-provided services across fibers with no inject requirement, and an unprovided name answers `undefined` instead of throwing (pre-0.1.5 hosts → `undefined`, where the tweak keeps its inert form — side-effect suppression only); a `close()` throw with no mounted seat is swallowed. Floating panels render no chip: their middle close forwards to the header's own ✕ control (absent — a no-op — when the embedder's `canCloseTab` withholds it), while the header's dock button is explicitly excluded; a press and a release on different elements hand the `auxclick` to their common ancestor, which answers neither helper and closes nothing.
|
|
294
297
|
- **Right sidebar initial width** (`src/client/tweaks/rightbar-initial-width.ts`): the right sidebar's width belongs to the host's `ui-layout` — its `openRightbar` action fills an unset preference with `rightbar ??= max(300, round(viewportWidth × 0.45))` (the `RIGHTBAR_DEFAULT_RATIO` constant), a drag overwrites it through `setRightbar` (clamped to `[300, window × 70%]`), and every later open/close keeps that px; the preference lives only in the in-memory store, so a reload drops it back to `null`. `ctx.layout` exposes only `openRightbar(track, fullscreen)` / `closeRightbar()` / `toggleSidebar()` / `selectPanel()` — the width action lives on the controller's own `panels` set (declared `private` in TypeScript, a plain runtime property). The tweak therefore wraps the controller **instance**'s `openRightbar`: on the FIRST open of a page load it measures the parent of `[data-rightbar-col]` (the very frame `ui-layout` measures for `viewportWidth`), writes `round(width × percent)` through the host's own `panels.setRightbar`, and only then forwards the original call — the write and the open land in the same React commit (never a first frame at 45%); neither the grid template nor the panel's inline width is touched, so the column solve, the drag handle's position and the panel width stay in agreement by construction. Two page-level markers hold the semantics: `__cst_rightbar_initial_width_seeded__` allows one write per load, and `__cst_rightbar_initial_width_dragged__` is set by a **one-shot** probe on the first `pointerdown` over `[data-side="rightbar"]` (the host's right-column drag handle) — once the user has dragged by hand, this tweak writes nothing for the rest of the load, since every settings change re-mounts every tweak and would otherwise overwrite the width just dragged. With the sidebar already open as the tweak mounts (the user editing the percentage in Settings) the new ratio is written immediately — the live preview. It stays inert when the host provides no `layout` service or its controller no longer carries a callable `setRightbar`, and disposal restores the host method only while the installed wrapper is still the current value, so another plugin's wrapper is never clobbered.
|
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
- **经典统计行(默认关闭)**:DSH 0.1.5 起,输入框下方的统计信息从一行居中文本(轮数/步数 · 耗时 · 速度 · 缓存命中 · Token 用量)改成了两个图标胶囊(点开弹窗查看)。开启本项可恢复 0.1.2 的文本行样式:数据仍读同一批持久化投影(`sessionStats` / `tokenUsage`),数值与胶囊一致,超宽时省略号截断、悬停显示完整内容;关闭后立即恢复新版胶囊。
|
|
30
30
|
- **缓存命中两位小数(默认关闭)**:统计信息的缓存命中率按两位小数显示(如 87.35%),不再取整;点开的 Token 用量弹窗同步生效。无论统计信息以新版图标胶囊还是经典文本行展示,均适用;两个开关同时为开时由经典统计行生效。
|
|
31
31
|
- **轮次速度与首 token 用时(默认关闭)**:0.1.5 起冷会话不再重建逐 token 时序,每轮"本轮用时和速度"弹窗只剩总用时。开启本项后点击每轮的用时胶囊,弹窗会回填输出速度与首 token 用时两行(由会话日志内嵌的模型流重建,历史会话同样生效)。
|
|
32
|
+
- **工作区可关闭(默认关闭)**:把工作区从侧边栏列表与"新建会话"选择器中**隐藏**,而不是删除——宿主的注册记录、会话账目、磁盘文件与会话日志全部保留,工作区 id 与每个会话的账目位置不变;**它名下的会话也一并隐藏**(不会落入"未分组",搜索里同样不再出现),恢复工作区即整组回来。恢复有两条路:**重新添加同一文件夹**(宿主按规范路径幂等返回原工作区),或在**样式调整设置面板**的"已关闭的工作区"列表中逐项点"恢复"(条目悬停可见其目录路径)。入口在每个工作区行的 `...` 菜单里(排在"删除工作区"之前),点击后有确认框说明数据会保留。三条边界:关闭期间该工作区的手动会话顺序不保留(恢复后按宿主账目序显示);关闭**你当前所在会话所属的**工作区时,会自动把你交接给一个新会话(落在最近的可见工作区);选中被关闭工作区内的会话后触发"新建会话"时,会落到最近的**可见**工作区(宿主自身的回退行为)。
|
|
32
33
|
|
|
33
34
|
```yaml
|
|
34
35
|
style-tweaks:
|
|
@@ -52,6 +53,8 @@ style-tweaks:
|
|
|
52
53
|
legacyStatsLine: false # 默认 false;true 则用 0.1.2 的文本统计行替换新版胶囊
|
|
53
54
|
pillsCacheHitDecimals: false # 默认 false;true 则缓存命中率显示两位小数(胶囊与经典行均适用)
|
|
54
55
|
turnSpeedMetrics: false # 默认 false;true 则用时弹窗回填输出速度与首 token 用时
|
|
56
|
+
workspaceClose: false # 默认 false;true 则可在侧边栏工作区行菜单中关闭(隐藏)工作区
|
|
57
|
+
closedWorkspaces: [] # 已关闭工作区 id 列表(内部数据,由面板"已关闭的工作区"区块管理)
|
|
55
58
|
```
|
|
56
59
|
|
|
57
60
|
设置入口:**设置 → 样式调整**。
|
|
@@ -69,8 +72,8 @@ dsh plugin --profile web add github:zhj9709/dsh-style-tweaks
|
|
|
69
72
|
`add` 后面的包说明会**原样转发给 pnpm**,因此可以指定版本——npm 包用 `@版本号`,GitHub 源码用 `#tag`:
|
|
70
73
|
|
|
71
74
|
```bash
|
|
72
|
-
dsh plugin --profile web add dsh-style-tweaks@0.1.
|
|
73
|
-
dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.
|
|
75
|
+
dsh plugin --profile web add dsh-style-tweaks@0.1.4 # 锁定 npm 版本
|
|
76
|
+
dsh plugin --profile web add github:zhj9709/dsh-style-tweaks#v0.1.4 # 锁定 git tag
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
安装完成后**重启一次 `dsh web`**(bundle 插件在进程启动时扫描)。
|
|
@@ -214,7 +217,7 @@ dsh plugin --profile web add github:zhj9709/dsh-style-tweaks
|
|
|
214
217
|
```bash
|
|
215
218
|
pnpm build
|
|
216
219
|
pnpm pack # 产出 dsh-style-tweaks-<版本>.tgz(在 .gitignore 里,不要提交)
|
|
217
|
-
dsh plugin --profile web add ./dsh-style-tweaks-0.1.
|
|
220
|
+
dsh plugin --profile web add ./dsh-style-tweaks-0.1.4.tgz
|
|
218
221
|
```
|
|
219
222
|
|
|
220
223
|
装完**重启一次 `dsh web`**(bundle 插件在进程启动时扫描)。
|
|
@@ -255,7 +258,7 @@ dsh plugin --profile web add ./dsh-style-tweaks-0.1.3.tgz
|
|
|
255
258
|
|
|
256
259
|
```bash
|
|
257
260
|
dsh plugin --profile web remove dsh-style-tweaks
|
|
258
|
-
dsh plugin --profile web add ./dsh-style-tweaks-0.1.
|
|
261
|
+
dsh plugin --profile web add ./dsh-style-tweaks-0.1.4.tgz
|
|
259
262
|
```
|
|
260
263
|
|
|
261
264
|
两个方案的差别只有「产物是否自动落到位」这一条:都要 `pnpm build`,都要刷新页面。
|
|
@@ -267,8 +270,8 @@ dsh plugin --profile web add ./dsh-style-tweaks-0.1.3.tgz
|
|
|
267
270
|
- **浏览器端**(`src/client/index.tsx`):读写该路由、渲染设置页,并根据每个开关的状态实时挂载 / 卸载对应的调整项(纯 CSS 调整项注入运行时 `<style>` 元素;JS 级调整项还会读写应用自身的状态 store 并修补 DOM)。
|
|
268
271
|
- **列宽样式引擎**(`src/client/conversation-width.ts`):写入 `--dsh-chat-user-width` CSS 变量,并在插件接管列宽时隐藏原生 `[data-width-handle]` 拖拽手柄;宽度值同时镜像到原生手柄读取的 localStorage 槽位,开关切换时无缝往返。
|
|
269
272
|
- **调整项注册表**(`src/client/tweaks/registry.ts`):每个调整项的元数据(id、settings 字段名、默认值、i18n 键)集中登记;新增调整项只需在注册表里加一条,并在 `src/client/tweaks/` 下新增一个注入文件。
|
|
270
|
-
- **项目目录运行指示**(`src/client/tweaks/project-running-indicator.ts`):从 `ctx.get('sessions')` / `ctx.get('workspaces')` 读取会话运行状态与目录归属,用 MutationObserver 在侧边栏的两类行上挂载应用自身的 `StateDot`(复用 `@deepseek-ai/dsh-client-ui-primitives` 的同一份模块,动画 keyframes 与样式 token 与对话标题处完全一致):项目目录头行(`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`
|
|
271
|
-
- **定位当前会话**(`src/client/tweaks/locate-current-session.ts`):在 DSH 侧边栏"工作区"段头部的搜索按钮左边注入一个新按钮。锚点全部走 i18n 与语义片段:通过 `ctx.locale.bind()` 解析当前语言的搜索按钮 aria-label(`workspace` 命名空间的 `search.sessions.aria` 键)与顶部面包屑的 aria-label(`conversation` 命名空间的 `session.hierarchy` 键),locale 服务不可用时退回 `[class*="searchButton"]` / `[class*="crumbs"]` 语义片段兜底。点击后从面包屑(`button[disabled]` 的当前项)读出会话标题,用 `ctx.get('sessions')` 与 `ctx.get('workspaces')` 两个应用级 store
|
|
273
|
+
- **项目目录运行指示**(`src/client/tweaks/project-running-indicator.ts`):从 `ctx.get('sessions')` / `ctx.get('workspaces')` 读取会话运行状态与目录归属,用 MutationObserver 在侧边栏的两类行上挂载应用自身的 `StateDot`(复用 `@deepseek-ai/dsh-client-ui-primitives` 的同一份模块,动画 keyframes 与样式 token 与对话标题处完全一致):项目目录头行(`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` 缺失或为空,自动退回只看原生覆盖的两种情况。
|
|
274
|
+
- **定位当前会话**(`src/client/tweaks/locate-current-session.ts`):在 DSH 侧边栏"工作区"段头部的搜索按钮左边注入一个新按钮。锚点全部走 i18n 与语义片段:通过 `ctx.locale.bind()` 解析当前语言的搜索按钮 aria-label(`workspace` 命名空间的 `search.sessions.aria` 键)与顶部面包屑的 aria-label(`conversation` 命名空间的 `session.hierarchy` 键),locale 服务不可用时退回 `[class*="searchButton"]` / `[class*="crumbs"]` 语义片段兜底。点击后从面包屑(`button[disabled]` 的当前项)读出会话标题,用 `ctx.get('sessions')` 与 `ctx.get('workspaces')` 两个应用级 store 反查所在工作区的 **id**(即使其工作区目录收起也能定位),再按 id 匹配工作区行——id 从 `ProjectRowItem` 的 `props.group.key` 顺着 fiber 链读,不按标题文本,因为两个同名目录的工作区标题完全一样,按标题会展开错的那一个;依次穿透两级折叠——工作区收起时 click 展开、"展开其余 x 个会话"溢出按钮(`[class*="sessionOverflowButton"][aria-expanded="false"]`)挡住目标行时自动点开——然后 `scrollIntoView({ block: 'center' })` 滚动居中。悬停提示复刻了 DSH 原生 `<Tooltip>` 的自研气泡(fixed 定位 + 主题 token + 500ms 延迟 + 视口翻转),而非浏览器原生 `title`。MutationObserver 监听 `aria-selected` / `aria-expanded` 属性变化同步按钮可用性与挂载状态。
|
|
272
275
|
- **设置菜单可滚动**(`src/client/tweaks/settings-nav-scroll.ts`):DSH 设置面板(`SettingsRoot`)高度固定且 `overflow: hidden`,原生样式只让右侧内容列(`.options`)滚动;左侧菜单列表 `.navList` 没有 `min-height: 0` 与 overflow 处理,条目多时被面板直接裁掉。本调整项用"两条 CSS 规则 + 一个小型 JS 驱动"实现:基础规则 `flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-bottom: 12px; margin-inline-end: -12px` 让列表成为滚动容器,并把滚动条"停靠"进导航栏右侧的 12px 留白里(8px 间距 + 4px 条位,恰好占满留白),滑块悬浮在留白上、与菜单项保持 8px 间距;菜单项上限 `max-width: 164px`(DSH 原生 188 导航 − 2×12 padding)钉住原生宽度,无论滚动条出现与否、悬停还是滚动,菜单项都与原生完全同宽;`::-webkit-scrollbar { width: 4px }` 比全局皮肤细两档。显示时机由 JS 驱动:第一版用 `:not(:hover)` 隐藏滑块,但 Chromium 在宿主 `:hover` 变化时不会可靠重绘自定义滚动条伪元素(实测表现为"点击菜单才出现"),因此改为监听列表 `scroll` 事件——滚动时给列表加 `cst-nav-scroll-show` 类显示滑块(颜色沿用面板继承的 l2 主题 token),停止滚动约 0.8 秒后移除类淡出;MutationObserver 在设置弹窗卸载/重开时保持监听器挂在当前列表上。选择器用"弹窗结构 + CSS Modules 语义片段"双保险(DSH 类名按 `[hash]_[local]` 哈希,`navList` 局部名全 DSH 唯一),无需 `!important`。
|
|
273
276
|
- **中键关闭侧边栏标签页**(`src/client/tweaks/sidebar-middle-click-close.ts`):右侧边栏的标签芯片是 `div[role="tab"][data-dockkit-tab="<tabId>"]`,由 `ui-dockkit` 的 `TabPanel` 渲染,而 dockkit 只有右侧边栏(停靠面板与浮动层)采用,因此"芯片"即"侧边栏标签页"。本项在 `document` 捕获阶段挂三个监听(React 17+ 把监听挂在根容器上,document 捕获先于整棵 React 树,拦截因此彻底):`pointerdown`(中键)拦截传播,芯片自带的按压→拖拽手势无从启动,按住中键移动 ≥4px 也不会再把标签拖走、分屏或浮出;`mousedown`(中键)`preventDefault`,抑制浏览器在标签条上的自动滚动(不影响其后仍会派发的 `auxclick`);`auxclick`(中键,浏览器对非主键点击只派发 `auxclick`、不派发 `click`)读出芯片上的 `TabId` 交给 `ctx.sidebarRight.close()`——关闭完全走宿主 store 自己的规划规则(缺失标签无事发生、唯一驻留引导页不可关、关闭最后一个停靠标签时侧边栏收起),插件不复制任何一条。`ctx.sidebarRight` 是 0.1.5+ 宿主才提供的服务,且由 `ui-sidebar-right` 在**它自己的插件 fiber** 里 provide——cordis 的属性访问只沿祖先 fiber 链解析,从兄弟插件读会直接抛错("cannot get property … without inject"),而把它加进本插件的 `inject` 会让服务变成必需、在 0.1.2 宿主上卡死启动;因此该 face 在每次中键关闭时经 `ctx.reflect.get('sidebarRight')` 解析——reflect 实例与 store 为根共享、provide 的隔离键也登记在根,`get` 无需 inject 声明即可跨 fiber 读到兄弟提供的服务,未提供时返回 `undefined` 而非抛错(0.1.2 宿主 → `undefined`,本项保持惰性,仅保留副作用抑制);`close()` 在无挂载 seat 时抛错,统一吞掉。浮动面板的头部不渲染芯片,其中键关闭转发给头部自带的 ✕ 按钮(`canCloseTab` 不允许时按钮不存在,无事发生),头部的"停靠回面板"按钮被显式排除;按压与释放不在同一芯片上时,浏览器把 `auxclick` 派发到两者的公共祖先,命中不了任何芯片,同样不会关闭。
|
|
274
277
|
- **右侧边栏初始宽度**(`src/client/tweaks/rightbar-initial-width.ts`):右侧边栏的宽度由宿主 `ui-layout` 决定——其 `openRightbar` action 在首次打开时用 `rightbar ??= max(300, round(viewportWidth × 0.45))` 填入 45% 的 px 值(常量 `RIGHTBAR_DEFAULT_RATIO`),拖拽经 `setRightbar` 覆盖(钳制到 `[300, 窗口 × 70%]`),其后关闭 / 再打开都保留该 px;宽度偏好只存在内存 store 里,刷新即回到 `null`。`ctx.layout` 只暴露 `openRightbar(track, fullscreen)` / `closeRightbar()` / `toggleSidebar()` / `selectPanel()`,改宽度的 `setRightbar` 挂在控制器自己的 `panels` 动作集上(TS 层是 `private`,运行时是普通字段)。本项因此包装控制器**实例**的 `openRightbar`:本次页面加载的第一次打开时,先按 `[data-rightbar-col]` 的父元素(宿主测量 `viewportWidth` 的同一个 frame 元素)实测宽度算出 `round(宽 × 百分比)`,经宿主自己的 `panels.setRightbar` 写入,再转发原调用——写入与打开落在同一次 React 提交里,不会先闪一帧 45%;全程不碰 grid 模板与面板 inline 宽度,因此列解算、拖拽手柄位置与面板宽度天然一致。两个页面级标记守住语义:`__cst_rightbar_initial_width_seeded__` 保证每次加载只写一次;`__cst_rightbar_initial_width_dragged__` 由挂在 `[data-side="rightbar"]`(宿主右列拖拽手柄)上的**一次性**探针在首次 `pointerdown` 时置位——用户一旦亲手拖过,本次加载内本项彻底不再写入,否则每次设置变更触发的整体重挂载都会覆盖刚拖出来的宽度。右栏在本项挂载时已打开(用户在设置面板改百分比)则立即按新比例写入,即所见即得的实时预览。宿主未提供 `layout` 服务、或控制器不再携带可调用的 `setRightbar` 时保持惰性;卸载只在当前值仍是自己的包装时才还原宿主方法,不踩其它插件的包装。
|