dsh-taskboard 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,16 @@
3
3
 
4
4
  # dsh-taskboard
5
5
 
6
- DeepSeek Harness 的**任务看板插件**:人建卡、agent 认领执行、人验收。任务挂项目(workspace)、可指定模型、支持手动与定时执行,全流程双向协作。
6
+ DeepSeek Harness 的**任务看板插件**:人建卡、agent 认领执行、人验收。任务挂项目(workspace)、可指定模型与 preset、支持手动与定时执行,全流程双向协作。
7
+
8
+ - **闭环协作**:人建卡 → agent 认领执行 → 结构化报告 → 人验收(✓ 完成 / ✗ 退回附原因)
9
+ - **10 个 `taskboard_*` agent 工具** + 代码级协议闸:agent 永远移不到 done、任务被持有时不可抢、跨项目不可认领
10
+ - **执行**:手动或 cron 定时(host 侧调度,浏览器关了照跑);每次执行在任务项目内开全新会话,可指定模型与 preset
11
+ - **Git Worktree 隔离**:每次执行独立 worktree + 任务分支,验收时一键合并;非 git 项目自动降级
12
+ - **验收效率**:DoD 验收清单(agent 勾选附证据)、结构化执行报告(摘要/改动文件/自验/产物/风险)、看板内 diff 查看器
13
+ - **实时看板**:SSE 实时刷新、五列流转、筛选排序持久化、JSON 导入导出、任务模板
14
+
15
+ **零配置**:安装即用——无需 Token、无需 API Key、无需额外服务或数据库。
7
16
 
8
17
  ## 界面
9
18
 
@@ -11,7 +20,108 @@ DeepSeek Harness 的**任务看板插件**:人建卡、agent 认领执行、
11
20
 
12
21
  <p align="center"><img src="https://raw.githubusercontent.com/cloader/dsh-taskboard/main/img/modal.png" alt="新建任务" width="440"></p>
13
22
 
14
- ## 核心功能
23
+ ## 目录
24
+
25
+ - [环境要求](#环境要求)
26
+ - [安装](#安装)
27
+ - [快速开始](#快速开始)
28
+ - [Agent 工具参考](#agent-工具参考)
29
+ - [功能特性](#功能特性)
30
+ - [安全](#安全)
31
+ - [配置与数据](#配置与数据)
32
+ - [常见问题](#常见问题)
33
+ - [开发](#开发)
34
+ - [升级日志](#升级日志)
35
+
36
+ ## 环境要求
37
+
38
+ | 依赖 | 要求 | 说明 |
39
+ | --- | --- | --- |
40
+ | DeepSeek Harness | ≥ 0.1.1 | 需要 `dsh plugin` 子命令与 web profile |
41
+ | Node.js | ≥ 20 | 仅 GitHub 源安装构建时需要 |
42
+ | git | 可选 | Worktree 隔离需要;缺失时自动降级原目录执行 |
43
+
44
+ ## 安装
45
+
46
+ ```bash
47
+ # 一键安装(npm,预构建、免构建授权 —— 推荐)
48
+ dsh plugin --profile web add dsh-taskboard
49
+
50
+ # 或从 GitHub 源安装
51
+ dsh plugin --profile web add github:cloader/dsh-taskboard
52
+ ```
53
+
54
+ 安装后**重启 `dsh web` 并刷新页面**:侧边栏出现「任务看板」入口即成功。无需任何后续配置。
55
+
56
+ <details>
57
+ <summary>GitHub 源安装卡在 prepare / allowBuilds?</summary>
58
+
59
+ git 源插件安装时经 prepare 脚本构建,pnpm 会先阻止——按报错提示把精确 key 加进 profile 目录 `pnpm-workspace.yaml` 的 `allowBuilds` 后重跑即可。npm 源是预构建产物,无此步骤。
60
+ </details>
61
+
62
+ <details>
63
+ <summary>开发模式安装(改代码即生效)</summary>
64
+
65
+ ```bash
66
+ git clone https://github.com/cloader/dsh-taskboard.git
67
+ cd dsh-taskboard
68
+ npm install && npm run build
69
+ dsh plugin --profile web add "link:/path/to/dsh-taskboard"
70
+ ```
71
+
72
+ link 安装后,仓库里 `npm run build` 重建、刷新页面即生效;改完宿主侧代码需重启 `dsh web`。
73
+ </details>
74
+
75
+ 卸载:`dsh plugin --profile web remove dsh-taskboard`(台账数据保留在 DSH 主目录,见[配置与数据](#配置与数据))。
76
+
77
+ > 官方 `@deepseek-ai/dsh-*` 包只写进 profile 的 `bundles` 列表,不要 `plugin add` 进 dependencies(避免 SDK 双实例遮蔽)。
78
+
79
+ ## 快速开始
80
+
81
+ **第 1 步 · 建卡**:看板右上「+ 新建任务」——选项目、紧急度、执行方式(认领/定时+cron)、模型与 preset、Git 隔离开关、验收清单;可勾「⚡ 立即执行」。
82
+
83
+ **第 2 步 · agent 执行**:三种触发方式任选——
84
+
85
+ 1. GUI「立即执行」/「↻ 续跑」(详情页或表单)
86
+ 2. cron 定时(host 侧调度,无需开浏览器)
87
+ 3. 任意会话里让 agent 用 `taskboard_*` 工具认领:`按看板上的任务 t-xxxxx 执行`
88
+
89
+ **第 3 步 · 人验收**:待验收列「✓ 完成」一键验收;「✗ 退回」退回待办并附原因(agent 下轮开工前会读)。
90
+
91
+ 一次完整的 agent 工作流(协议由插件在执行开场自动下达):
92
+
93
+ ```text
94
+ 你:执行看板任务 t-ab12cd
95
+ agent:
96
+ taskboard_list # 查板:项目内 todo 任务
97
+ taskboard_get t-ab12cd # 读需求、评论、验收清单
98
+ taskboard_move → in_progress # 认领(代码闸:被持有/跨项目会被拒绝)
99
+ ……编码 / 测试……
100
+ taskboard_checklist check # 逐项勾验收清单,附证据 note
101
+ taskboard_execution_report # 结构化报告:摘要/改动文件/自验/产物/风险
102
+ taskboard_comment_add # 交接说明
103
+ taskboard_move → in_review # 移待验收
104
+ 你:看板待验收列 ✓ 完成 # done 永远只属于人——agent 调用会被代码闸拒绝
105
+ ```
106
+
107
+ ## Agent 工具参考
108
+
109
+ 任何会话可用;项目边界:只有属于任务所在项目的会话才能认领或执行。
110
+
111
+ | 工具 | 作用 |
112
+ | --- | --- |
113
+ | `taskboard_list` | 查板(按项目/状态/紧急度过滤,紧凑摘要) |
114
+ | `taskboard_get` | 读单卡全文:描述、prompt、评论流、清单、执行记录 |
115
+ | `taskboard_comments` | 列出任务评论(视为最新需求,先读后动) |
116
+ | `taskboard_create` | 建卡(workspaceId、紧急度、清单、preset、隔离、定时) |
117
+ | `taskboard_update` | 改标题/描述/prompt/紧急度/清单(model/execution 只读) |
118
+ | `taskboard_move` | 移卡:todo→in_progress→in_review(**到不了 done**) |
119
+ | `taskboard_comment_add` | 追加评论(交接、风险、进展) |
120
+ | `taskboard_delete` | 软删除(可 purge;执行中不可删) |
121
+ | `taskboard_checklist` | 验收清单 add / check(附证据)/ uncheck |
122
+ | `taskboard_execution_report` | 提交结构化执行报告,自动挂到当前执行 |
123
+
124
+ ## 功能特性
15
125
 
16
126
  **看板协作**
17
127
  - 五列看板(待规划 / 待办 / 进行中 / 待验收 / 已完成)+ 受阻标记,SSE 实时刷新
@@ -33,33 +143,89 @@ DeepSeek Harness 的**任务看板插件**:人建卡、agent 认领执行、
33
143
 
34
144
  **执行**
35
145
  - 手动执行或 cron 定时:每次执行在任务项目内新建全新会话(干净上下文、可指定模型、可指定 preset);开场两条消息同一回合送达——插件上下文行携带任务框架与交接协议(含失败回退路径),卡片内容(提示词或标题+描述)以正常用户消息呈现
36
- - **任务级 preset(0.3.3)**:新建/编辑表单「执行模式(preset)」下拉——执行会话按该 preset 组合(工具集与人设由此而来,对齐 GUI 新会话的组合方式);默认预选部署默认 preset(本部署即标准模式),也可选「跟随部署默认」;preset 损坏时执行直接失败并把原因写进执行记录(不产出半组合会话);随时可改,下轮执行生效
146
+ - **任务级 preset(0.3.3)**:新建/编辑表单「执行模式(preset)」下拉——执行会话按该 preset 组合(工具集与人设由此而来,对齐 GUI 新会话的组合方式);默认预选部署默认 preset,也可选「跟随部署默认」;preset 损坏时执行直接失败并把原因写进执行记录(不产出半组合会话);随时可改,下轮执行生效
37
147
  - **Git Worktree 隔离执行(0.3.0)**:任务级开关(默认开、记住上次选择),每次执行在 `<项目>/.dsh-worktrees/<任务ID>` 独立 worktree 上进行,分支 `task/<标题>+<任务ID>`(首次创建后定死,改名不改);执行会话归属项目根目录(分组、工具与文件沙箱完整可用——DSH 要求会话 cwd 即工作区根,0.3.2 修正),worktree 路径与边界纪律在开场指令中明确下达;结算自动采集提交列表 / 未提交修改警告 / 改动统计;非 git 项目或 git 不可用时自动降级原目录执行(执行记录注明降级原因,台账与执行主流程永不因 git 失败而失败);验收时详情页一键 `--no-ff` 合并到主工作区(主区脏或冲突原样报告,不自动解决)、删除 worktree(有未提交修改时拒绝)、可选删分支;支持「↻ 续跑」在现有 worktree/分支上继续执行(保留上次改动与提交)
38
148
  > Worktree 隔离是协作约定而非沙箱:执行会话拥有完整工具权限,隔离依赖分支约定,不适用于运行不可信代码的场景。
39
149
  - host 侧调度:关掉浏览器照常触发;错过窗口跳过不补跑
40
150
  - 乐观并发(ifVersion)+ 完整归因(谁改的、哪个会话执行的)
41
151
  - ⚙ 健康诊断:台账基本项 + 遗留 worktree(台账无主但目录存在)一键清理
42
152
 
43
- ## 安装
153
+ ## 安全
44
154
 
45
- ```bash
46
- dsh plugin --profile <name> add dsh-taskboard # npm(推荐,预构建免授权)
47
- dsh plugin --profile <name> add github:cloader/dsh-taskboard # GitHub
48
- ```
155
+ - **验收权只属于人**:agent 把任务移到 done 的调用被代码级协议闸直接拒绝(不是提示词约定);任务被持有时不可抢占,跨项目不可认领。
156
+ - **Worktree 隔离是协作约定而非沙箱**:执行会话拥有完整工具权限,隔离依赖分支约定,不适用于运行不可信代码的场景。
157
+ - **数据本地**:台账、模板全部存本机 DSH 主目录;不外发任何数据,无需 Token / API Key。
49
158
 
50
- > 官方 `@deepseek-ai/dsh-*` 包只写进 profile 的 `bundles` 列表,不要 `plugin add` 进 dependencies(避免 SDK 双实例遮蔽)。
159
+ ## 配置与数据
160
+
161
+ 开箱即用,全部配置项如下(均为可选):
162
+
163
+ | 环境变量 | 默认 | 说明 |
164
+ | --- | --- | --- |
165
+ | `DSH_TASKBOARD_MAX_CONCURRENT` | `3` | 全局同时执行的会话数上限 |
166
+ | `DSH_HOME` | `~/.dsh` | DSH 主目录(跟随部署,插件数据随之) |
167
+ | `ATB_TRACE` | 未设置 | `=1` 时 host 打印工具调用跟踪(调试用) |
168
+
169
+ 数据文件(均在 DSH 主目录,卸载插件不删除):
170
+
171
+ | 文件 | 内容 |
172
+ | --- | --- |
173
+ | `dsh-taskboard.json` | 任务台账(全部任务/执行/评论) |
174
+ | `dsh-taskboard-templates.json` | 任务模板 |
175
+ | `dsh-taskboard.json.backup-<时间戳>` | 整册导入替换前的自动备份 |
176
+ | `<项目>/.dsh-worktrees/<任务ID>/` | 任务执行 worktree |
177
+
178
+ 随时可顶栏「⬇ JSON」全量备份、「⬇ CSV」导出任务清单(带 BOM,Excel 直接打开)。
179
+
180
+ ## 常见问题
181
+
182
+ **侧边栏没有「任务看板」入口?**
183
+ 刷新页面;仍没有则确认插件已装进当前 profile 并重启 `dsh web`(宿主半区在进程启动时加载)。0.4.2 起旧 `data-pane` shell 与新哈希类名 shell(DSH Desktop)都支持。
184
+
185
+ **任务数据存在哪?怎么备份?**
186
+ 见[配置与数据](#配置与数据)。GUI「⬇ JSON」随时全量导出;「⬆ 导入」可恢复。
187
+
188
+ **浏览器关了,定时任务还跑吗?**
189
+ 跑。调度在 host 进程侧,与浏览器无关;错过的时间窗口跳过不补跑。
190
+
191
+ **项目不是 git 仓库,能用吗?**
192
+ 能。Worktree 隔离自动降级为原目录执行,执行记录注明降级原因;其余功能不受影响。
193
+
194
+ **多个项目怎么协作?**
195
+ 任务挂项目(= DSH workspace)。认领校验会话归属:只有任务所在项目内的会话能认领/执行,跨项目不可抢。
196
+
197
+ **agent 会把任务直接标成「已完成」吗?**
198
+ 不会。这是代码级协议闸(不是提示词约定):`taskboard_move` 到 done 的调用被直接拒绝,验收永远由人在看板完成。
199
+
200
+ **GitHub 源安装报 prepare 被阻止?**
201
+ pnpm 的构建授权——按报错把 key 加进 profile 的 `pnpm-workspace.yaml` `allowBuilds` 后重跑;或改用 npm 源(预构建,无此步骤)。
51
202
 
52
203
  ## 开发
53
204
 
54
205
  ```bash
206
+ git clone https://github.com/cloader/dsh-taskboard.git
207
+ cd dsh-taskboard
55
208
  npm install && npm run build # host ESM + client CJS 双构建
56
- npm test # vitest 全量
209
+ npm test # vitest 全量(150 项)
57
210
  node tests/manual-git-e2e.mjs # 真 git 端到端手测(worktree 全链路 + 续跑 + diff 查看器)
58
211
  node scripts/screenshot.mjs # 重新生成 img/ 截图(需本机 Edge)
59
212
  ```
60
213
 
61
214
  ## 升级日志
62
215
 
216
+ ### 0.4.4
217
+
218
+ - **修复看板偶尔突然掉样式的问题([ #3](https://github.com/cloader/dsh-taskboard/issues/3))**。排查结论:浏览器里其他插件在后台热更新时,会按"样式归属"做清理,而看板自己的样式表没有归属标记,被误当成别的插件的样式连带删掉了。本次修复后:
219
+ - 看板的样式表明确打上了自己的归属标记,其他插件更新时不会再误删它
220
+ - 看板每 2 秒自动检查一次样式是否完好,即便被误删也会立刻自动恢复——以后遇到掉样式,最多闪一下就自动修好,不用再刷新页面
221
+ - 新增回归测试:样式归属标记生效、误删后 2 秒内自动恢复、样式不会重复注入
222
+
223
+ ### 0.4.3
224
+
225
+ - **修复折叠侧边栏时入口不收成纯图标(用户反馈)**:收起侧边栏后,shell 把侧栏收成 36×36 图标轨道(layout frame 出现 `data-sidebar-collapsed` 属性、侧栏根节点切换 CSS-Module `*_collapsed` 哈希类),而看板入口仍保持全宽——图标、「任务看板」文字、右侧计数角标挤在窄轨里。修复:折叠信号下入口镜像原生轨道几何(36×36、居中、与原生 newSession 折叠态同款间距),隐藏文字与角标,图标 14→16px 保证可读;双信号选择器(`[data-sidebar-collapsed]` 与 `[class*="_collapsed"]`)沿用 0.4.2 双选择器原则,新旧 shell 通吃;展开态完全不受影响,`aria-label` 保留无障碍提示
226
+ - 回归测试:断言两组折叠选择器与 36px 轨道几何都注入样式表、shell 真实产生的 `hHd-Xa_collapsed` 类名命中选择器模式(150 项)
227
+ - README 按 dsh.market 五维评分体系重构:新增目录 / 环境要求 / 快速开始(含完整 agent 工作流示例)/ Agent 工具参考表 / 安全 / 配置与数据 / 常见问题;仓库元数据补 homepage 与 topics
228
+
63
229
  ### 0.4.2
64
230
 
65
231
  - **修复 DSH Desktop 上看板不出现(用户反馈:入口能点、后端正常,但中间看板无变化)**:当前 DSH Desktop 的 Web shell(`dsh-client-ui-layout`)已完全移除 `data-pane` 属性,中间列改用 CSS Module 哈希类名(`pI_x6G_centerCol`)——而看板挂载选择器写死 `[data-pane="conversation"]`,`querySelector` 永远落空,看板容器从未创建(侧栏入口正常,是因为它一直有 `[class*="sidebarCol"]` 兜底)。修复:挂载选择器与隐藏规则双兼容 `'[data-pane="conversation"], [class*="centerCol"]'`(新旧 shell 通吃,与侧栏入口同款兜底策略);`.dsh-atb-view` 以 `height:100%` 撑满列容器,不依赖列内部结构
@@ -87,6 +253,7 @@ node scripts/screenshot.mjs # 重新生成 img/ 截图(需本机 Edge)
87
253
  - **任务级 preset 字段**:创建任务时可选「执行模式(preset)」,默认预选部署默认 preset(本部署即标准模式)——即按你的决定「默认标准模式」;可切任意 preset(如梁神模式)或选「跟随部署默认」(不落字段,随部署默认变);编辑随时可改(每轮执行现组合,无锁定);复制任务携带;`taskboard_create` 增 `presetId` 参数;详情页显示 preset chip
88
254
  - **坏 preset 防护**:preset 解析失败(不存在/损坏)→ 执行直接失败、原因写入执行记录、任务退回待办——不产出半组合会话(与 apiproxy 回滚语义一致)
89
255
  - preset 目录用来自 `agentPreset.list` 连接 RPC(无 roster 或离线时下拉隐藏,行为回落到裸组合);测试 111 → 116 项
256
+
90
257
  ### 0.3.2
91
258
 
92
259
  - **修复 worktree 执行会话脱离项目组、工具不可用**:0.3.0–0.3.1 把执行会话 cwd 指向 worktree 子目录,而 DSH 的会话模型要求 cwd === 工作区根目录(全等)——子目录导致会话无法挂入项目组(显示「未分组」),且文件沙箱边界与相对路径解析全部失效(agent 无法创建文件)。现改回:会话 cwd = 项目根(分组、工具、沙箱完整可用),worktree 绝对路径与边界纪律(命令 workdir 指向 worktree、文件读写用绝对路径、禁改主工作区其它文件、提交到任务分支)在开场框架行中明确下达。git 隔离与证据采集不受影响
package/lib/client.js CHANGED
@@ -942,6 +942,26 @@ window.__ModuleLoader__.load({
942
942
  @media (prefers-reduced-motion: reduce) {
943
943
  .dsh-atb-roll .dsh-atb-rn { transition: none; }
944
944
  }
945
+
946
+ /* 0.4.3: collapsed rail. Collapsing the sidebar narrows it to an icon rail
947
+ * (layout frame carries data-sidebar-collapsed; the sidebar root toggles its
948
+ * CSS-Module *_collapsed class — dual signals, per the 0.4.2 shell doctrine).
949
+ * The entry then mirrors the native rail geometry (36×36 icon button, no
950
+ * label/stats) — matches .hHd-Xa_collapsed .hHd-Xa_newSession. */
951
+ [data-sidebar-collapsed] [data-dsh-atb-entry],
952
+ [class*="_collapsed"] [data-dsh-atb-entry] {
953
+ width: 36px; height: 36px; min-width: 36px;
954
+ margin: 0 0 12px; padding: 0;
955
+ justify-content: center; gap: 0; text-align: center;
956
+ }
957
+ [data-sidebar-collapsed] [data-dsh-atb-entry] .dsh-atb-entry-label,
958
+ [data-sidebar-collapsed] [data-dsh-atb-entry] .dsh-atb-entry-stats,
959
+ [class*="_collapsed"] [data-dsh-atb-entry] .dsh-atb-entry-label,
960
+ [class*="_collapsed"] [data-dsh-atb-entry] .dsh-atb-entry-stats { display: none; }
961
+ /* Native rail icons render ~16-20px; scale ours up from 14px to read at parity. */
962
+ [data-sidebar-collapsed] [data-dsh-atb-entry] svg,
963
+ [class*="_collapsed"] [data-dsh-atb-entry] svg { width: 16px; height: 16px; }
964
+
945
965
  .dsh-atb-search { width: 130px; }
946
966
  .dsh-atb-badge[data-kind="stale"] { background: rgba(217,130,43,.15); color: #d9822b; }
947
967
 
@@ -1566,15 +1586,46 @@ window.__ModuleLoader__.load({
1566
1586
  .dsh-atb-imp-result { font-size: 12px; color: var(--dsw-alias-state-success-primary, #30a46c); margin-top: 10px; }
1567
1587
  .dsh-atb-badge[data-kind="checklist"] { color: var(--dsw-alias-label-secondary, inherit); }
1568
1588
  `;
1569
- let injected = false;
1570
- /** Inject the stylesheet once (idempotent). */
1589
+ /** Style element id (stable since 0.1.x: hook for tests and debugging). */
1590
+ const STYLE_ID = "dsh-taskboard-styles";
1591
+ /**
1592
+ * Ownership tag for the shell's client-module bookkeeping. The web shell
1593
+ * claims every UNTAGGED `<style>` for whichever plugin module materializes
1594
+ * next (claimStyles in dsh-client-modules), and the client HMR driver
1595
+ * deletes `<style>` tags by this attribute on every rebuilt entry
1596
+ * (removeOwnedStyles in dsh-client-hmr).
1597
+ */
1598
+ const PLUGIN_ID = "dsh-taskboard";
1599
+ /** Per-stylesheet identity, mirroring the shell's own data-plugin-css. */
1600
+ const CSS_TAG = "dsh-taskboard/styles";
1601
+ /**
1602
+ * Ensure the stylesheet lives in <head>: create when absent, re-attach when
1603
+ * removed, always carry the ownership tags.
1604
+ *
1605
+ * 0.4.4 fix (field report: CSS randomly dropped, board renders as unstyled
1606
+ * HTML until refresh): injection runs from cordis apply(), which resolves
1607
+ * AFTER this module's materialization, so the old untagged <style> could be
1608
+ * claimed by ANY sibling plugin module that materialized later (observed
1609
+ * with lazily-materializing profile bundles). That sibling's next HMR
1610
+ * rebuild then deleted OUR stylesheet, and the old module-level `injected`
1611
+ * flag blocked re-injection until a full page refresh. Pre-tagging pins
1612
+ * ownership to this plugin: sibling claims skip tagged styles, and only
1613
+ * THIS plugin's rebuild removes it — the fresh apply re-injects. Idempotency
1614
+ * is DOM-based for the same reason: a module flag cannot see removals,
1615
+ * getElementById can. A found element is re-tagged too, so a leftover
1616
+ * pre-0.4.4 element adopted across a hot swap heals its ownership.
1617
+ */
1571
1618
  function injectStyles() {
1572
- if (injected || typeof document === "undefined") return;
1573
- const style = document.createElement("style");
1574
- style.id = "dsh-taskboard-styles";
1575
- style.textContent = STYLES;
1576
- document.head.append(style);
1577
- injected = true;
1619
+ if (typeof document === "undefined") return;
1620
+ let style = document.getElementById(STYLE_ID);
1621
+ if (style === null) {
1622
+ style = document.createElement("style");
1623
+ style.id = STYLE_ID;
1624
+ style.textContent = STYLES;
1625
+ document.head.append(style);
1626
+ }
1627
+ style.dataset.plugin = PLUGIN_ID;
1628
+ style.dataset.pluginCss = CSS_TAG;
1578
1629
  }
1579
1630
 
1580
1631
  //#endregion
@@ -1814,7 +1865,7 @@ window.__ModuleLoader__.load({
1814
1865
  * @module dsh-taskboard/shared/version
1815
1866
  */
1816
1867
  /** The package version (must equal package.json "version"). */
1817
- const PLUGIN_VERSION = "0.4.2";
1868
+ const PLUGIN_VERSION = "0.4.4";
1818
1869
 
1819
1870
  //#endregion
1820
1871
  //#region src/client/board/TaskCard.tsx
@@ -4680,6 +4731,9 @@ window.__ModuleLoader__.load({
4680
4731
  function apply(ctx) {
4681
4732
  try {
4682
4733
  injectStyles();
4734
+ const styleWatch = setInterval(() => {
4735
+ injectStyles();
4736
+ }, 2e3);
4683
4737
  const controller = new BoardController(createClient());
4684
4738
  const connection = ctx.get?.("connection");
4685
4739
  if (connection !== void 0) {
@@ -4723,6 +4777,7 @@ window.__ModuleLoader__.load({
4723
4777
  console.error("[dsh-taskboard] mount failed:", error);
4724
4778
  }
4725
4779
  ctx.effect?.(() => () => {
4780
+ clearInterval(styleWatch);
4726
4781
  for (const d of disposers.splice(0)) d();
4727
4782
  controller.dispose();
4728
4783
  }, "dsh-taskboard: client mount");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-taskboard",
3
3
  "description": "Agent-first task board for the DSH web GUI: host-authoritative task ledger with taskboard_* agent tools, project (= workspace) claim boundaries, per-task model execution in fresh sessions, optional per-task git-worktree isolation (dedicated task branches, commit evidence, one-click merge), host-side cron scheduling, and a live SSE kanban view. Mounts via the official dsh plugin system — no DSH source changes.",
4
- "version": "0.4.2",
4
+ "version": "0.4.4",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -49,6 +49,11 @@ interface ClientContextFace {
49
49
  export function apply(ctx: ClientContextFace): void {
50
50
  try {
51
51
  injectStyles()
52
+ // Stylesheet watchdog (0.4.4): re-assert the stylesheet if anything
53
+ // removed it mid-session — defense-in-depth on top of the ownership
54
+ // tag (see styles.ts for the claim/reload mechanism it guards against).
55
+ // One getElementById per 2s tick; cleared with the plugin teardown.
56
+ const styleWatch = setInterval(() => { injectStyles() }, 2_000)
52
57
  const client = createClient()
53
58
  const controller = new BoardController(client)
54
59
 
@@ -104,7 +109,12 @@ export function apply(ctx: ClientContextFace): void {
104
109
  // cordis effect semantics: the callback runs immediately and its RETURN
105
110
  // VALUE is the disposer (family-plugin precedent: () => () => {...}).
106
111
  // A single-layer arrow here executes the teardown immediately.
112
+ // The stylesheet itself is NOT removed here: the HMR driver owns removal
113
+ // of tagged styles on this plugin's rebuild (and a self-removal could
114
+ // race a same-lifetime re-apply); its rules are dsh-atb-* scoped, so a
115
+ // leftover tag after a full disable is inert.
107
116
  ctx.effect?.(() => () => {
117
+ clearInterval(styleWatch)
108
118
  for (const d of disposers.splice(0)) d()
109
119
  controller.dispose()
110
120
  }, 'dsh-taskboard: client mount')
@@ -46,6 +46,26 @@ export const STYLES = `
46
46
  @media (prefers-reduced-motion: reduce) {
47
47
  .dsh-atb-roll .dsh-atb-rn { transition: none; }
48
48
  }
49
+
50
+ /* 0.4.3: collapsed rail. Collapsing the sidebar narrows it to an icon rail
51
+ * (layout frame carries data-sidebar-collapsed; the sidebar root toggles its
52
+ * CSS-Module *_collapsed class — dual signals, per the 0.4.2 shell doctrine).
53
+ * The entry then mirrors the native rail geometry (36×36 icon button, no
54
+ * label/stats) — matches .hHd-Xa_collapsed .hHd-Xa_newSession. */
55
+ [data-sidebar-collapsed] [data-dsh-atb-entry],
56
+ [class*="_collapsed"] [data-dsh-atb-entry] {
57
+ width: 36px; height: 36px; min-width: 36px;
58
+ margin: 0 0 12px; padding: 0;
59
+ justify-content: center; gap: 0; text-align: center;
60
+ }
61
+ [data-sidebar-collapsed] [data-dsh-atb-entry] .dsh-atb-entry-label,
62
+ [data-sidebar-collapsed] [data-dsh-atb-entry] .dsh-atb-entry-stats,
63
+ [class*="_collapsed"] [data-dsh-atb-entry] .dsh-atb-entry-label,
64
+ [class*="_collapsed"] [data-dsh-atb-entry] .dsh-atb-entry-stats { display: none; }
65
+ /* Native rail icons render ~16-20px; scale ours up from 14px to read at parity. */
66
+ [data-sidebar-collapsed] [data-dsh-atb-entry] svg,
67
+ [class*="_collapsed"] [data-dsh-atb-entry] svg { width: 16px; height: 16px; }
68
+
49
69
  .dsh-atb-search { width: 130px; }
50
70
  .dsh-atb-badge[data-kind="stale"] { background: rgba(217,130,43,.15); color: #d9822b; }
51
71
 
@@ -671,14 +691,47 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
671
691
  .dsh-atb-badge[data-kind="checklist"] { color: var(--dsw-alias-label-secondary, inherit); }
672
692
  `
673
693
 
674
- let injected = false
694
+ /** Style element id (stable since 0.1.x: hook for tests and debugging). */
695
+ const STYLE_ID = 'dsh-taskboard-styles'
696
+
697
+ /**
698
+ * Ownership tag for the shell's client-module bookkeeping. The web shell
699
+ * claims every UNTAGGED `<style>` for whichever plugin module materializes
700
+ * next (claimStyles in dsh-client-modules), and the client HMR driver
701
+ * deletes `<style>` tags by this attribute on every rebuilt entry
702
+ * (removeOwnedStyles in dsh-client-hmr).
703
+ */
704
+ const PLUGIN_ID = 'dsh-taskboard'
675
705
 
676
- /** Inject the stylesheet once (idempotent). */
706
+ /** Per-stylesheet identity, mirroring the shell's own data-plugin-css. */
707
+ const CSS_TAG = 'dsh-taskboard/styles'
708
+
709
+ /**
710
+ * Ensure the stylesheet lives in <head>: create when absent, re-attach when
711
+ * removed, always carry the ownership tags.
712
+ *
713
+ * 0.4.4 fix (field report: CSS randomly dropped, board renders as unstyled
714
+ * HTML until refresh): injection runs from cordis apply(), which resolves
715
+ * AFTER this module's materialization, so the old untagged <style> could be
716
+ * claimed by ANY sibling plugin module that materialized later (observed
717
+ * with lazily-materializing profile bundles). That sibling's next HMR
718
+ * rebuild then deleted OUR stylesheet, and the old module-level `injected`
719
+ * flag blocked re-injection until a full page refresh. Pre-tagging pins
720
+ * ownership to this plugin: sibling claims skip tagged styles, and only
721
+ * THIS plugin's rebuild removes it — the fresh apply re-injects. Idempotency
722
+ * is DOM-based for the same reason: a module flag cannot see removals,
723
+ * getElementById can. A found element is re-tagged too, so a leftover
724
+ * pre-0.4.4 element adopted across a hot swap heals its ownership.
725
+ */
677
726
  export function injectStyles(): void {
678
- if (injected || typeof document === 'undefined') return
679
- const style = document.createElement('style')
680
- style.id = 'dsh-taskboard-styles'
681
- style.textContent = STYLES
682
- document.head.append(style)
683
- injected = true
727
+ if (typeof document === 'undefined') return
728
+ let style = document.getElementById(STYLE_ID)
729
+ if (style === null) {
730
+ style = document.createElement('style')
731
+ style.id = STYLE_ID
732
+ style.textContent = STYLES
733
+ document.head.append(style)
734
+ }
735
+ style.dataset.plugin = PLUGIN_ID
736
+ style.dataset.pluginCss = CSS_TAG
684
737
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  /** The package version (must equal package.json "version"). */
9
- export const PLUGIN_VERSION = '0.4.2'
9
+ export const PLUGIN_VERSION = '0.4.4'