dsh-group-status 0.5.1 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -6,6 +6,52 @@
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## 0.5.2 — 2026-11-05
10
+
11
+ - **fix** 内核 `0.2.2` 线前瞻适配(Issues #43,经 `0.2.2-alpha.2` 发布产物对照,无破坏性变更):
12
+ ① `startSession` 的草稿意图判定不再只看 `prompt` / `clearPreviousDraft: true` —— `whenDraftExists`
13
+ (`replace | append | keep`,`DraftInitializationOptions` 在 0.2.2 新增的三态)单独出现时同样初始化
14
+ 草稿,并把两代字段一并原样透传给 `requestDraftInitialization`(旧宿主忽略、新宿主生效),两字段语义
15
+ 分叉后导航层不再漏判;② 空白会话复用改为 preset 感知(与 rc.4→alpha.2 定稿措辞一致):列出其它
16
+ preset 的空白不再被无 `agentPreset` 的请求复用,命名 preset 的请求只命中列同值或无 preset 的空白;
17
+ ③ peer 的 `^0.2.1-rc.2` 按 semver 预发布规则匹配不到 `0.2.2-alpha.x`(`0.2.2` 正式版会自动纳入),
18
+ alpha 线提前验证需临时补 `|| ^0.2.2-alpha.2` 且**不入仓** —— 不变式与原因见升级手册「兼容声明
19
+ 为什么长这样」一节。新增本仓库 spec `tests/navigation-draft-blank.client.spec.ts`(8 用例,纯对象
20
+ stub 驱动真实 `UiWorkspaceService`)看守 ①②;spec 实跑数 183 → 191,README 数字同步。
21
+ - **docs** 升级手册「上游代次跟踪」的漂移表按 2026-11-02 复核更新:两处已对齐、一处(`connectBlank`
22
+ 的不可达早退)确认无行为差不跟;「约定」一节的前提修正 —— `navigation.ts` 从此有本地用例覆盖,
23
+ 逐 rc 跟改不再受「无法证明没坏」约束,但升基线仍按触发清单走。「测试基线」的合计数改为可核实的
24
+ 实跑值,跳过的上游 spec 数 7 → 9(与 `vitest.config.ts`、README 对齐)。
25
+ - **docs** 中英两份 README 按「装 → 认徽标 → 配 → 出问题查什么」重排:安装回到使用之前,新增
26
+ 「徽标不出现时」排错表(没重启、内核在窗口外、四档计数全为 0、低于 `countThreshold`、搭档
27
+ 插件未注册),并补上此前只写在代码注释里的两条用户可见事实:计数超过 99 显示 `99+`、外部
28
+ 来源之间的次序由各方的 `order` 决定。本机 `link:` 安装从用户路径移进「开发」。
29
+ 同时修掉与实现不符的陈述:设置项是 5 个(曾写「7 个」)、外部来源在本页没有专用开关
30
+ (同篇另一处曾说「两个来源各有一个专用设置项」)、Node 与 spec 用例数按实跑值、跳过的上游
31
+ spec 是 9 个(曾写 7)、`status.waitingReview` / `status.scheduled` 等 5 个文案键已随
32
+ `legacy/` 删除不再列为本包新增、`rc.4` 的用例通过不等于行为一致(差异见升级手册)。
33
+ - **docs** 一处事实一个家:facet 契约细节归 [`docs/facet-protocol.md`](docs/facet-protocol.md),
34
+ README 只留方向、最小注册示例与指针;12 条 peer 的因果链归 [`docs/upgrading.md`](docs/upgrading.md)
35
+ 新增的「兼容声明为什么长这样」一节,README 兼容性段只给结论。
36
+ - **test** 新增 [`tests/readme-facts.node.test.mjs`](tests/readme-facts.node.test.mjs)(8 例):
37
+ README 的版本徽章 == `package.json` 的 `version`;设置表的字段与默认值 == `src/index.ts` 的
38
+ `Config`;用例数 == 实跑数量(Node 侧静态计数再用 `node --test` 交叉验证,Vitest 侧读 JSON
39
+ reporter);用户文档不含工单号、已删除的实现名与内网地址;相对引用存在且图片落在 `files`
40
+ 白名单内;`extract-notes.mjs` 依赖的特性小节仍在。
41
+ - **chore** `package.json`:`description` 不再把「定时任务」「等待评审」列成本插件的档位
42
+ (它们是提供方注册的状态源,本包不含对应字段);`files` 白名单补 `docs`,此前发布 tarball 里
43
+ 没有截图与两份文档,README 的图片在包内视角是断链。
44
+
45
+ - **docs** 徽标读法表加「图标」列:三枚 SVG(待审批黄点、进行中转圈、已完成绿点)按
46
+ `StateDot` 的几何复刻(实心点是 10px 槽位里的 6px 圆,转圈是 14px 的环 + 约 72° 的弧、
47
+ 轨道 25% 不透明度),颜色取自仓库自带实机截图的像素(`#F59E0B` / `#9AA0A5` / `#22C55E`),
48
+ 与截图同为暗色主题。产物由 [`scripts/make-badge-glyphs.mjs`](scripts/make-badge-glyphs.mjs)
49
+ 生成,形状与取色的出处写在脚本头注释里。「叫法」列保留黄点/转圈/绿点这些说法,图标不做
50
+ 唯一信息源(纯文本视图与读屏都还要能读)。外部来源的图标不复刻:形状归提供方,本包不替别家存资产。
51
+ - **test** `tests/readme-facts.node.test.mjs` 加一条:README 用到的三枚 SVG 必须能由脚本逐字节
52
+ 复现(比对后无论是否一致都把工作区还原,`npm test` 不留改动)。手改 SVG 会让图标与组件脱钩,
53
+ 这条把它挡在提交前。Node 用例数 49 → 50。
54
+
9
55
  ## 0.5.1 — 2026-10-27
10
56
 
11
57
  - **refactor** 会话列表面板拆成四个组件文件:`rows/WorkspaceBrowser.tsx` 从 1562 行降到 685 行,
package/README.en.md CHANGED
@@ -2,193 +2,180 @@
2
2
 
3
3
  <img src="icon.svg" width="72" alt="dsh-group-status"/>
4
4
 
5
- # dsh-group-status — live status on collapsed folders
5
+ # dsh-group-status: aggregated status badge for collapsed groups
6
6
 
7
7
  [中文](./README.md) | **English**
8
8
 
9
- [![version](https://img.shields.io/badge/version-0.3.0-0EA5E9)](https://github.com/dsh-plugin-collection/dsh-group-status)
9
+ [![version](https://img.shields.io/badge/version-0.5.1-0EA5E9)](https://github.com/dsh-plugin-collection/dsh-group-status)
10
10
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
11
11
  [![DSH](https://img.shields.io/badge/DeepSeek%20Harness-0.2.1--rc.2-4D6BFE)](https://github.com/deepseek-ai/deepseek-harness)
12
12
 
13
13
  </div>
14
14
 
15
- > In the DSH sidebar, **folding a workspace group takes every session status inside down with it** — running, approvals, reviews, all gone. This plugin gives the collapsed folder row its own aggregated badge: **yellow = pending approval, spinner = running, green = completed**, plus clock and review icons fed by other plugins, with a count and hover details. Expanded groups are untouched — child rows carry their own statuses.
15
+ > In the DSH sidebar, collapsing a workspace group hides the status of every session inside it: running sessions, sessions awaiting approval, sessions awaiting an answer. This plugin adds one aggregated badge to the collapsed folder row (yellow dot for pending, spinner for running, green dot for unread completed, optional external status glyphs, plus a count). Hovering the row lists each tier separately. Nothing is drawn while the group is expanded; its child rows already show their own state.
16
+
17
+ ---
18
+
19
+ ## Feature overview
20
+
21
+ - **Visible while collapsed**: the badge appears only on collapsed groups; once expanded, each child row carries its own state. Nested groups aggregate the whole subtree.
22
+ - **Same source of truth as session rows**: the three state dots reuse the stock `StateDot` and the row-level derivation. Blank and archived sessions are skipped.
23
+ - **Optional cross-plugin aggregation**: other plugins register their own status sources (scheduled tasks, waiting PR reviews). This plugin contains no branch keyed on a specific source.
24
+ - **Usable with zero config**: 5 settings cover the three tiers, hover details, and the count threshold. All of them default to "show".
25
+ - **No stock files modified**: this package registers the same slot and disables the stock row in the profile patch. Not one file of the published upstream package is touched.
26
+ - **Localized**: follows the DSH UI language (中文 / English). Wording for an external status source comes from that source's own plugin.
27
+ - **Accessible**: the badge is `role="img"`, its `aria-label` matches the hover `title`, and state differences are not carried by color alone.
16
28
 
17
29
  ---
18
30
 
19
31
  ## Preview
20
32
 
21
- **Collapsed folder rows carry the aggregate badge** — `tmp` has one running session (spinner), one completed (green dot), one scheduled task (clock), count `3`:
33
+ The `tmp` group below holds 1 running session (spinner), 1 completed (green dot), and 1 scheduled task (clock), counting `3`:
22
34
 
23
35
  <p align="center">
24
- <img src="docs/media/screenshot-folders.png" width="267" alt="Collapsed sidebar group tmp: spinner, green dot, clock icon and count 3"/>
36
+ <img src="docs/media/screenshot-folders.png" width="267" alt="Collapsed tmp group in the sidebar: spinner, green dot, clock glyph and the number 3"/>
25
37
  </p>
26
38
 
27
- **Hover for details** — the hover card appends aggregated detail lines under the path and creation time:
39
+ Hovering the folder row appends the per-tier lines under the path and creation time:
28
40
 
29
41
  <p align="center">
30
- <img src="docs/media/screenshot-hover.png" width="300" alt="Hover card: 1 Waiting for answer · 1 Running"/>
42
+ <img src="docs/media/screenshot-hover.png" width="300" alt="Hover card: 1 waiting for answer · 1 running"/>
31
43
  </p>
32
44
 
33
- > The badge reuses DSH's native `StateDot`, so colors stay identical to the session rows. Both images are real screenshots.
34
-
35
45
  ---
36
46
 
37
- ## How to use
47
+ ## Install
38
48
 
39
- Fold any group that has live sessions; a badge appears after the folder name:
49
+ ```bash
50
+ dsh plugin --profile web add dsh-group-status
51
+ ```
40
52
 
41
- | Glyph | Meaning | Color |
42
- |---|---|---|
43
- | Yellow dot | Pending approval / plan review / answer inside | warning (same as rows) |
44
- | Spinner | Running sessions inside, incl. running subagents | ongoing (`StateDot` animation) |
45
- | Green dot | Completed-unread sessions inside | done (same as the row) |
46
- | Clock | Scheduled tasks inside (data from dsh-later) | purple · orange (≤5 min) · red (overdue) |
47
- | Hourglass | Sessions waiting for a PR review (data from dsh-gitea-dispatch) | amber · red (the CI wait window lapsed) |
53
+ The sidebar is replaced as a whole bundle, so **`dsh web` must restart** after an install. Saving a setting applies immediately and needs no restart.
48
54
 
49
- Rules:
55
+ To remove it (both profile patch entries are withdrawn together and the stock session row comes back; restart again):
50
56
 
51
- - **Completed reaches the badge**: `done` (completed-unread) now shows a green dot while folded, reusing the same `StateDot` state as the session row; a group with nothing but completed sessions shows a badge just like any other.
52
- - **The count** = approvals + running (**including running subagents**) + completed + sessions with scheduled tasks + sessions waiting for review. Whether the number renders at all is gated by `countThreshold` (default 2 — a lone item shows dots only).
53
- - **Hover details**: the folder row's hover card appends lines in the order `approval · plan review · answer · running · subagents · waiting review · scheduled tasks · completed`, e.g. `1 Waiting for answer · 1 Running`. The waiting-review line carries **which PR** and **how long is left**, e.g. `1 Waiting for review (3min left) · #2783`.
54
- - **Expanding defers to the rows**: the aggregate badge only renders while collapsed; expanded groups keep their per-row statuses with no duplication.
55
- - **Accessible**: the badge is `role="img"` with a localized `aria-label` (e.g. `1 Waiting for answer · 1 Running`) and the same text as its `title`. Waiting and overdue are **two different sentences** (`Waiting for review` / `Waiting for review (overdue)`), not one sentence in two colours.
56
- - **Only scheduled reminders pulse.** A lapsed review wait does not animate, matching how dsh-gitea-dispatch draws its own session-row icon for that state: it stops the animation on purpose (its words: "stillness means no longer expecting"), and its `overdue` means only that CI stopped waiting, not that the review failed. A pulsing folder next to a still row icon would state the same fact twice. The red colour stays, same token as the publisher.
57
+ ```bash
58
+ dsh plugin --profile web remove dsh-group-status
59
+ ```
57
60
 
58
- ### Settings
61
+ This package changes no file inside the published upstream package: it registers the same slot and disables the stock `ui-workspace` row in the profile patch. For the local `link:` install used while editing code, see [Development](#development).
59
62
 
60
- **Plugins** in the sidebar → **dsh-group-status** (the config form on the bundle's page, between its description and the component rows):
63
+ ---
61
64
 
62
- | Field | Default | Meaning |
63
- |---|---|---|
64
- | `showRunningInBadge` | `true` | Whether the folded badge shows the "running" spinning dot |
65
- | `showPendingInBadge` | `true` | Whether the folded badge shows the "pending" yellow dot (approval / plan review / waiting for answer) |
66
- | `showCompletedInBadge` | `true` | Whether the folded badge shows a green "completed" dot |
67
- | `showHoverDetails` | `true` | Whether the hover card shows the aggregated status detail lines |
68
- | `countThreshold` | `2` | The count is shown on the badge only when the total is >= this value (default 2, i.e. omits the count when it equals 1 — the dots already convey "something is happening"; set to 1 to always show) |
65
+ ## Verify
69
66
 
70
- Switching a tier off takes it out of the folded row's aggregate entirely — no dot/icon, no contribution to the badge count, no hover detail line, and it no longer keeps the badge alive. **The state dot on a session row itself is unaffected**: that is row-level state, not a folder hint. With `showHoverDetails` off, the hover card shows only the title, path, and creation time.
67
+ 1. Collapse a group that contains a running or pending session: the badge appears after the folder name.
68
+ 2. Hover the folder row: the detail lines appear, with the same wording as the badge `aria-label`.
69
+ 3. Expand the group: the badge disappears.
70
+ 4. The browser console reports no errors.
71
71
 
72
- **External sources (e.g. "waiting for review", "scheduled") have no switch on this page, on purpose.** This plugin knows no concrete source, so its config face must not carry another plugin's domain names or icon shapes either. To silence a source, use **that plugin's own** settings card: when it is off, it simply does not register its facet, and the badge loses that glyph. See "Who owns visibility" in [`docs/facet-protocol.md`](docs/facet-protocol.md).
72
+ ---
73
73
 
74
- > ⚠️ As of this change the `showScheduledInBadge` / `showWaitingReviewInBadge` fields are removed (they hard-coded names and icon shapes on behalf of dsh-later / dsh-gitea-dispatch). Values left in the profile are no longer read and behave as the default "show".
74
+ ## When the badge does not show
75
75
 
76
- Saving applies it live, with no refresh; the Host writes it into the profile patch's `dsh-group-status` entry `config`, so it survives a page reload. When the settings face cannot be read (e.g. the Host serves no settings service), it falls back to the default "show", matching the schema default — failing to read a config is not the user turning it off.
76
+ | Symptom | Cause |
77
+ |---|---|
78
+ | Nothing changed after install | `dsh web` was not restarted (the sidebar is replaced as a whole bundle) |
79
+ | The package was never loaded | The DSH kernel is outside `engines.dsh`, so the kernel answers `skipping profile bundle` (verified on `0.2.0-rc.2`) and the sidebar keeps stock behavior |
80
+ | The group does have running or pending sessions, but no badge while collapsed | All four counted tiers are 0 (pending, running, completed, external contributions). Blank and archived sessions are not counted |
81
+ | Dots but no number | The count is below `countThreshold` (default 2) |
82
+ | No scheduled-task or waiting-review glyph | The companion plugin is not installed, not enabled, or it turned that status source off itself (visibility of an external source belongs to its owner) |
77
83
 
78
84
  ---
79
85
 
80
- ## Feature overview
86
+ ## Reading the badge
81
87
 
82
- - **Visible when folded**: the aggregate only exists while collapsed; an expanded sidebar is pixel-identical to stock. Nested groups are supported too — when a parent group is folded, its badge aggregates sessions from all descendant groups.
83
- - **Same source of truth as rows**: priority, colors and wording all reuse the row derivation and `StateDot`; blank and archived sessions are skipped.
84
- - **Cross-plugin aggregation** (optional): scheduled tasks from dsh-later, waiting PR reviews from dsh-gitea-dispatch — works fine with neither installed.
85
- - **Usable with zero config**: sensible defaults out of the box; 7 settings let you fine-tune which sources appear in the folded badge, hover details, and count display.
86
- - **No stock files touched**: not a single file of the published `dsh-client-ui-workspace` is modified; all changes live in this repo's source, and the profile patch is two lines (add this package, disable the stock row).
87
- - **Localized**: follows the DSH UI language (中文 / English) by reusing the stock locale keys. This package adds `status.waitingReview`, `status.scheduled`, a fallback `status.needsAttention` for facets that ship no wording, and the three waiting-review states `status.waitingReviewOverdue` / `status.waitingReviewImminent` / `status.waitingReviewLeft`.
88
+ This plugin draws three session state tiers:
88
89
 
89
- ---
90
+ | Icon | Shape | Meaning | Color source |
91
+ |---|---|---|---|
92
+ | <img src="docs/media/glyph-pending.svg" width="10" height="10" alt=""/> | Yellow dot | The group has a session awaiting approval, plan review, or an answer | `StateDot` `warning`, same as session rows |
93
+ | <img src="docs/media/glyph-running.svg" width="14" height="14" alt=""/> | Spinner | The group has a running session (including running subagents) | `StateDot` `ongoing` |
94
+ | <img src="docs/media/glyph-completed.svg" width="10" height="10" alt=""/> | Green dot | The group has a completed session that is still unread | `StateDot` `done`, same as session rows |
90
95
 
91
- ## Cross-plugin status: the facet protocol
96
+ The icons are reconstructions: geometry follows `StateDot` (a 6px core inside a 10px slot, and a 14px ring with an arc of about 72 degrees), colors are sampled from the two real screenshots above, both taken in the dark theme. [scripts/make-badge-glyphs.mjs](scripts/make-badge-glyphs.mjs) generates them. In the UI the spinner keeps rotating (one turn per 1.5s), so a static image can only be one frame. The running UI is the authority on colors; this package keeps no color table of its own.
92
97
 
93
- A folded group badge can show state owned by **another plugin**. The direction is inverted: **the owner registers its facts in**, and this plugin's aggregation and render paths **know no specific source** (not one branch keyed on who is reporting). The full contract lives in [docs/facet-protocol.md](docs/facet-protocol.md).
98
+ External status sources supply their own glyph and color tier. This plugin decides neither their shape nor whether they pulse, so those two glyphs are not reconstructed here: they belong to the plugins that own them.
94
99
 
95
- One exception remains open: the two current sources each have a dedicated setting, so adding a **third** one still touches 14 places in this plugin (cost and the way to close it are in the protocol doc's Known trade-offs). The transitional hosting is closed: `legacy/` and its two `dsh.client.external` entries are deleted, and both dsh-later and dsh-gitea-dispatch now register their own facets.
100
+ - **Count** = pending + running (including running subagents) + completed + whatever each external source reports. Above 99 it shows `99+`; below `countThreshold` the number is omitted and only the dots are drawn.
101
+ - **Order**: pending, running, external sources, completed. The relative order among external sources comes from their own `order`. Hover details use the same order as the dots, one line per tier, for example `1 waiting for answer · 1 running`. The waiting-review line carries the PR number and remaining time, supplied by its owner.
102
+ - **Collapsed only**: no badge is drawn while a group is expanded. Nested groups aggregate the whole subtree, so a collapsed parent reports the sum over all descendant groups.
103
+ - **Accessibility**: the badge is `role="img"`, the `aria-label` and the hover `title` carry the same localized wording, and states such as "overdue" are distinguished by wording, not by color alone.
104
+ - **Pulse is per source**: whether an external source pulses is declared by that source. It never affects the whole badge, the count, or the host session state dot.
96
105
 
97
- ```js
98
- const gs = require('dsh-group-status/client')
99
- const unregister = gs?.registerGroupStatusFacet?.({
100
- protocol: gs.GROUP_STATUS_FACET_PROTOCOL_VERSION,
101
- id: 'my-plugin.review', // '<package>.<state>'
102
- subscribe: store.subscribe, // an external-store face, not a React hook
103
- getSnapshot: store.getSnapshot,
104
- fold: (snapshot, sessionIds, now) => ({ count, tone, pulse, data }),
105
- merge, label, renderIcon, order, // all optional
106
- })
107
- ctx.effect(() => () => unregister?.(), 'my-plugin: group status facet')
108
- ```
106
+ ---
109
107
 
110
- - The five required members alone already render: `N Needs attention` plus a tone-coloured generic dot. Your internal id is **never** read out to the user.
111
- - Icon and wording belong to the owner (`renderIcon` / `label`): what a state looks like, and whether a lapsed wait should sit still or pulse, is its knowledge. Previously this plugin inferred it from state names and ended up contradicting the owner, which draws `overdue` **statically** while the badge pulsed.
112
- - A collapsed parent spans the whole subtree, so same-facet folds get merged again. That is why `fold` returns structured `data` rather than text: text cannot be merged. Without `merge` the default rule applies (counts add, the more urgent tone wins, `data` is dropped).
113
- - When this plugin is absent or disabled `require` throws: catch it and carry on, your own features are unaffected.
114
- - Same-id conflicts are settled **by origin, not by registration order** (the host does not order plugin `apply`): the owner always wins over a custodial (`legacy`) registrant.
108
+ ## Settings
115
109
 
116
- ### Why `subscribe` / `getSnapshot` instead of a React hook
110
+ Sidebar **Plugins** → **dsh-group-status** (the config form on the bundle page):
117
111
 
118
- The badge is a React component, and hook order must be constant across renders of one component. Facets register and unregister at runtime: calling each owner's hook would change the hook count the moment the registry moves, and React throws "Rendered more hooks than during the previous render". This plugin subscribes once and reads one version number, however many sources stand behind it.
112
+ | Field | Default | Description |
113
+ |---|---|---|
114
+ | `showRunningInBadge` | `true` | Whether the collapsed badge shows the running spinner |
115
+ | `showPendingInBadge` | `true` | Whether it shows the yellow dot for approval / plan review / waiting for answer |
116
+ | `showCompletedInBadge` | `true` | Whether it shows the green completed dot |
117
+ | `showHoverDetails` | `true` | Whether the hover card shows the aggregated detail lines |
118
+ | `countThreshold` | `2` | Show the number only when the count reaches this value; set 1 to always show it |
119
119
 
120
- ### Who is connected today
120
+ Turning a tier off removes it from the collapsed row entirely: no dot, no contribution to the count, no hover line, and it no longer keeps the badge visible. The state dot on a session row is unaffected, because that is row-level state. With `showHoverDetails` off, the hover card shows only the title, path, and creation time.
121
121
 
122
- | facet id | Owner |
123
- |---|---|
124
- | `dsh-gitea-dispatch.waiting-review` | registered by dsh-gitea-dispatch itself (its issue #47) |
125
- | `dsh-later.schedule` | registered by dsh-later itself (its issue #36) |
122
+ Saving applies immediately. The Host writes the values into the `config` of the `dsh-group-status` entry in the profile patch, so they survive a reload. When the settings cannot be read (for example the Host exposes no settings service), the defaults apply: unread is not the same as turned off. Unknown fields left over in a profile are not read, so their effect equals the default.
126
123
 
127
- This plugin keeps no field types, tone mapping or wording for either. A third source follows the contract below and needs no change here, unless it also wants its own toggle.
124
+ External sources deliberately have no switch on this page: this plugin knows no specific source, so its config surface must not carry another plugin's domain names or icon shapes either. To silence a source, use that plugin's own settings card; when it turns the source off it stops registering, and the badge loses that item. See "Who owns visibility" in [`docs/facet-protocol.md`](docs/facet-protocol.md) (Chinese only).
128
125
 
129
- > ⚠️ Cross-plugin **value imports** do not work in this project (measured: `Could not resolve "dsh-group-status"`: a client bundle is a self-contained closure factory). Owners therefore use only the runtime symbols from `require('dsh-group-status/client')` and never `import` this package's types; copying the structural declarations from the protocol doc is enough.
130
126
  ---
131
127
 
132
- ## Quick start
133
-
134
- ### Install into DSH
135
-
136
- **npm install (recommended, pre-built, no auth required):**
128
+ ## Companion plugins (optional)
137
129
 
138
- ```bash
139
- dsh plugin --profile web add dsh-group-status
140
- # Takes effect after restarting dsh web (the whole sidebar package is swapped)
141
- ```
130
+ | Plugin | What the collapsed badge gains | Availability |
131
+ |---|---|---|
132
+ | [dsh-later](https://github.com/dsh-plugin-collection/dsh-later) | Scheduled tasks in the group (clock glyph; color tier and wording owned by dsh-later) | Public npm |
133
+ | `dsh-gitea-dispatch` | PRs awaiting review in the group (hourglass glyph; color tier and wording owned by dsh-gitea-dispatch) | Not published to public npm, see its own repository |
142
134
 
143
- Or install from GitHub:
135
+ The plugin works normally with neither installed. The waiting-review data comes entirely from the facet that `dsh-gitea-dispatch` registers; this plugin maintains no wording or color tier for it.
144
136
 
145
- ```bash
146
- dsh plugin --profile web add "github:dsh-plugin-collection/dsh-group-status"
147
- # restart dsh web
148
- ```
137
+ ---
149
138
 
150
- For local development, link a checkout and rebuild as you edit `src/`:
139
+ ## Compatibility
151
140
 
152
- ```bash
153
- git clone https://github.com/dsh-plugin-collection/dsh-group-status.git
154
- cd dsh-group-status
155
- pnpm install && pnpm build
141
+ | Item | Value |
142
+ |---|---|
143
+ | DSH kernel | `engines.dsh: >=0.2.1-rc.2 <0.3.0-0` |
144
+ | Verified generations | `0.2.1-rc.2` / `rc.3` / `rc.4` (gating, cold boot assembly, type check and the full suite pass on all three; real GUI acceptance was additionally done on rc.2) |
145
+ | Peer dependencies | `@deepseek-ai/cordis ~4.0.5` plus 12 `@deepseek-ai/dsh-*` packages (range `^0.2.1-rc.2`) |
146
+ | Upstream baseline | `@deepseek-ai/dsh-client-ui-workspace 0.2.1-rc.2`, source taken from the published package into this repo's `src/` |
156
147
 
157
- dsh plugin --profile web add "link:$PWD"
158
- # restart dsh web
159
- ```
148
+ On `rc.4` the **behavior** has drifted from the baseline (blank session reuse and draft initialization semantics). This package's fork still follows the baseline generation, so those improvements do not reach users: loading cleanly is not the same as behaving identically. The per-item differences and the triggers to act on them live in [docs/upgrading.md](docs/upgrading.md), section "上游代次跟踪".
160
149
 
161
- > Dev tip: `pnpm watch` rebuilds on every `src/` change — but the sidebar is swapped as a whole, so **a dsh web restart is still required**.
150
+ The 12 peer entries are not metadata. Kernels that do not read `engines.dsh` decide compatibility from the peer ranges alone, and a missing entry lets an old kernel load this package and fail the whole page. The mechanism is recorded in [docs/upgrading.md](docs/upgrading.md), section "兼容声明为什么长这样".
162
151
 
163
- ### Verify
152
+ ---
164
153
 
165
- 1. Fold a group with running / pending sessions → a badge appears after the folder name;
166
- 2. Hover the folder row → the detail lines appear (matching the badge's wording);
167
- 3. Expand the group → the badge disappears and child rows show their own statuses;
168
- 4. No errors in the browser console.
154
+ ## For plugin authors: registering a status source
169
155
 
170
- ### Roll back
156
+ A collapsed group badge can show state owned by another plugin, and the direction is inverted: **the owner registers its facts in**, while this plugin's aggregation and render paths know no specific source.
171
157
 
172
- ```bash
173
- dsh plugin --profile web remove dsh-group-status
174
- # Both patch lines are withdrawn together; the stock row comes back. Restart to apply.
158
+ ```js
159
+ const gs = require('dsh-group-status/client')
160
+ const unregister = gs?.registerGroupStatusFacet?.({
161
+ protocol: gs.GROUP_STATUS_FACET_PROTOCOL_VERSION,
162
+ id: 'my-plugin.review', // '<package>.<state>'
163
+ subscribe: store.subscribe, // an external store surface, not a React hook
164
+ getSnapshot: store.getSnapshot,
165
+ fold: (snapshot, sessionIds, now) => ({ count, tone, pulse, data }),
166
+ label, renderIcon, order, // optional: wording, glyph and order belong to the provider
167
+ })
168
+ ctx.effect(() => () => unregister?.(), 'my-plugin: group status facet')
175
169
  ```
176
170
 
177
- ---
178
-
179
- ## Compatibility
180
-
181
- | Item | Value |
182
- |---|---|
183
- | DSH core | declares `>=0.2.1-rc.2 <0.3.0-0`; verified on `0.2.1-rc.2` / `rc.3` / `rc.4` |
184
- | peer dependencies | `@deepseek-ai/cordis ~4.0.5` plus 12 `@deepseek-ai/dsh-*` at `^0.2.1-rc.2` |
185
- | Upstream base | `@deepseek-ai/dsh-client-ui-workspace 0.2.1-rc.2` (source vendored into `src/`) |
186
- | Install shape | fork override: same slots registered, stock row disabled via `cordis.patch.yml` |
187
- | Optional partners | dsh-later (schedule summary), dsh-gitea-dispatch (waiting-review summary) |
171
+ - The five required members (`protocol` / `id` / `subscribe` / `getSnapshot` / `fold`) already render: `N Needs attention` plus a generic dot colored by tone. Your internal id is never read out to the user.
172
+ - Glyph and wording belong to the provider: what the state looks like, and whether an overdue one should stay still or pulse, is the provider's knowledge.
173
+ - `fold` returns structured `data`, not text, because a collapsed parent group covers the whole subtree and same-named folds have to be merged again. Text cannot be merged.
174
+ - When this plugin is not installed or not enabled, `require` throws. Catching that error is enough.
175
+ - Same-id conflicts resolve by origin, independent of registration order: the state owner always wins over a stand-in registrant.
176
+ - Cross-plugin **value imports** do not work in this project (the client bundle is a self-contained closure factory), so providers use only the runtime symbols from `require('dsh-group-status/client')` and never import this package's types.
188
177
 
189
- Those 12 peers are not metadata. Cores at or below 0.2.0 **do not read `engines.dsh`** — they judge compatibility purely from `@deepseek-ai/dsh*` peer ranges. Without the enumeration, 0.2.0 considers this package compatible and installs it; that generation never published `@deepseek-ai/dsh-api-remote-access`, so the client `inject` for `remoteAccess` never resolves, the fiber stays pending, `uiWorkspace` is never provided, and `ui-sidebar` / `ui-conversation` / `ui-chat` — 12 entries in all — fail to activate. The page becomes "Failed to load plugins" and the user loses the entire sidebar. With the peers declared, 0.2.0-rc.2 measured as `skipping profile bundle`, and the host is unharmed; from 0.2.1 on, `engines.dsh` decides alone and peers are ignored, so in-window generations are unaffected.
190
-
191
- Every upstream generation requires a manual rebase — see the [upgrade runbook](./docs/upgrading.md). If upstream renames a slot, changes a component signature or the context contract, this package will not follow on its own.
178
+ The full contract, every `FacetFold` field, the default merge, lifecycle and degradation, and the version rules are in [docs/facet-protocol.md](docs/facet-protocol.md) (Chinese only), including why the surface is `subscribe` / `getSnapshot` rather than a React hook.
192
179
 
193
180
  ---
194
181
 
@@ -196,34 +183,50 @@ Every upstream generation requires a manual rebase — see the [upgrade runbook]
196
183
 
197
184
  ```sh
198
185
  pnpm install
199
- pnpm build # tsc typecheck + tsdown bundle → lib/
186
+ pnpm build # tsc type check + tsdown bundle → lib/
200
187
  pnpm watch # rebuild on src/ changes (a dsh web restart is still required)
201
- pnpm test # = test:node (node --test, 27 cases) + test:spec (vitest + jsdom, 165 cases)
202
- pnpm test:spec # the upstream spec suite only
188
+ pnpm test # test:node (node --test, 50 cases) + test:spec (vitest + jsdom, 191 cases)
189
+ pnpm test:spec # spec suite only
190
+ pnpm check-upstream # upstream generation tracking (add --latest for the highest published version)
203
191
  ```
204
192
 
205
- `tests/` is a copy of the upstream suite plus this repository's own [tests/group-status.client.spec.ts](tests/group-status.client.spec.ts), which covers the fold aggregation itself (counts, priority, cross-plugin sources, blank/archived skipping).
193
+ ### Local install while developing
194
+
195
+ You do not need to publish a package to test changes; mount this checkout into the local profile with `link:`:
196
+
197
+ ```bash
198
+ git clone https://github.com/dsh-plugin-collection/dsh-group-status.git
199
+ cd dsh-group-status
200
+ pnpm install && pnpm build
201
+ dsh plugin --profile web add "link:$PWD"
202
+ ```
203
+
204
+ `pnpm watch` rebuilds on `src/` changes, and each rebuild still requires a `dsh web` restart.
205
+
206
+ Besides the vendored upstream copies, `tests/` holds this repository's own cases for the aggregation: counting, mutual exclusion among pending kinds, subagents, blank and archived skipping, external source registration and unregistration, hover wording. 9 upstream specs only run inside the DSH monorepo; the reasons are listed one by one at the top of [vitest.config.ts](vitest.config.ts).
206
207
 
207
- Seven further upstream specs only run inside the DSH monorepo: they either need `dsh-client-test-runtime`'s deep imports of **unpublished sources**, or take runtime values out of a published `…/client` bundle whose closure expects the host app to preload its whole dependency set. They are explicitly skipped at the top of [vitest.config.ts](vitest.config.ts) with the reason spelled out per group — **do not light them up with stubs**; that would only test the stub.
208
+ The part of the host contract that can **fail silently** (the pending-kind set this plugin accepts, and the two state facts read from the host) is compared against the locked host implementation by [tests/host-pending-kinds.node.test.mjs](tests/host-pending-kinds.node.test.mjs), so a host change turns it red. README facts that drift with the code (version badge, setting fields and defaults, test case counts, forbidden wording) are guarded by [tests/readme-facts.node.test.mjs](tests/readme-facts.node.test.mjs).
208
209
 
209
210
  ---
210
211
 
211
212
  ## Known limitations
212
213
 
213
- - **Upstream bumps need a manual rebase**: the upstream source is vendored (`src/`, `tests/`) and this is not a stable public API; slot names, component signatures and context contracts may shift under it. Steps are in the [upgrade runbook](./docs/upgrading.md).
214
- - **One extra full measurement pass per status change**: aggregation shares the expanded derivation, and to make collapsed parents count correctly **expanded groups get measured too** — they draw no badge themselves, but a collapsed ancestor needs their numbers. Huge groups (hundreds of sessions) cost one extra traverse plus a fold up the tree per status change — same order as expanded rendering, acceptable. A perf spec guards this cost.
215
- - **It produces no status itself**: the cross-plugin icons depend entirely on the summary APIs published by dsh-later / dsh-gitea-dispatch. Without them you simply lose those two icons.
216
- - **The count is coarser than the details**: scheduled tasks are counted per *session*, not per task; the exact task count lives in dsh-later's own UI.
217
- - **Collapsed sidebar only**: this plugin only adds an aggregate display — it does not touch the session state machine or the per-row badge predicates.
214
+ - **An upstream generation change needs a manual rebase**: this package vendors upstream source and does not use a stable public API. When the host changes a slot name, component signature, or context contract, follow [docs/upgrading.md](docs/upgrading.md).
215
+ - **One more full measurement pass per state change**: the aggregate count shares its derivation with the expanded state, so an expanded group must be measured too, because its ancestors use its numbers. On very large groups (hundreds of sessions) that pass costs one more traversal plus an upward roll, guarded by the performance cases in `tests/group-status.client.spec.ts`.
216
+ - **External glyphs depend entirely on their providers**: a missing companion plugin simply means one fewer glyph; it never affects the core badge. When a provider ships no wording, the line falls back to the generic "needs attention".
217
+ - **The count is coarser than the details**: an external source contributes the number it reports, and this plugin does not redefine another source's counting basis.
218
+ - **Sidebar collapsed state only**: neither the session state machine nor the row-level badge rules change.
219
+ - **Countdown precision is one minute**: the badge re-renders only when a provider's store notifies, so a second-level readout would sit stale on an old value.
218
220
 
219
221
  ---
220
222
 
221
- ## Technical documentation
223
+ ## Documentation
222
224
 
223
- Developer and maintainer material, kept out of this README:
225
+ - [docs/facet-protocol.md](docs/facet-protocol.md): the cross-plugin status source protocol (required reading for providers)
226
+ - [docs/upgrading.md](docs/upgrading.md): upstream baseline, local change list, rebase and verification, generation tracking, why the compatibility declaration looks like this
227
+ - [src/client/cross-plugin-summary.ts](src/client/cross-plugin-summary.ts): the provider-side template, with its design notes in the file header
224
228
 
225
- - [Upstream upgrade runbook](./docs/upgrading.md) — where the baseline came from, what changed locally, how to rebase and verify on the next upstream release
226
- - [Cross-plugin summary template](./src/client/cross-plugin-summary.ts) — the provider-side skeleton itself; its header is the design note and carries the measured "copy, do not import" evidence
229
+ Both documents are currently Chinese only.
227
230
 
228
231
  ## License
229
232