@dsh-workbench/core 0.3.10 → 0.3.11

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.
Files changed (82) hide show
  1. package/README.md +19 -7
  2. package/bin/dsh-workbench +2 -2
  3. package/client.js +1648 -973
  4. package/cordis.patch.yml +60 -35
  5. package/dist/client/client.body.js +1648 -973
  6. package/dist/client/index.js +1333 -658
  7. package/dist/host/index.js +2896 -2334
  8. package/dist/types/app/client.d.ts +2 -1
  9. package/dist/types/app/layouts/env-gate/boot-gate.d.ts +1 -1
  10. package/dist/types/app/layouts/market/use-module-versions.d.ts +13 -2
  11. package/dist/types/app/layouts/rightbar/MarketView.d.ts +6 -3
  12. package/dist/types/app/layouts/rightbar/market-act.d.ts +6 -3
  13. package/dist/types/app/layouts/rightbar/market-settle-hold.d.ts +2 -0
  14. package/dist/types/app/layouts/rightbar/market-settle.d.ts +20 -0
  15. package/dist/types/app/layouts/rightbar/market-update-verdict.d.ts +41 -0
  16. package/dist/types/app/layouts/rightbar/market-view-text.d.ts +4 -1
  17. package/dist/types/app/layouts/rightbar/store.d.ts +14 -0
  18. package/dist/types/app/layouts/sidebar/LongTaskDot.d.ts +12 -0
  19. package/dist/types/app/layouts/sidebar/LongTaskHover.d.ts +12 -0
  20. package/dist/types/app/layouts/sidebar/edition-boot.d.ts +24 -0
  21. package/dist/types/app/layouts/sidebar/edition.d.ts +22 -0
  22. package/dist/types/app/layouts/workbench-settings/ConfigSyncItem.d.ts +5 -0
  23. package/dist/types/app/layouts/workbench-settings/SettingsItem.d.ts +17 -0
  24. package/dist/types/app/layouts/workbench-settings/SidebarEditionItem.d.ts +11 -0
  25. package/dist/types/app/layouts/workbench-settings/WorkbenchSettingsSection.d.ts +11 -0
  26. package/dist/types/modules/panels/model/approval.d.ts +12 -4
  27. package/dist/types/modules/panels/model/diy-browser-tools.d.ts +1 -1
  28. package/dist/types/modules/panels/model/diy-context.d.ts +31 -14
  29. package/dist/types/modules/panels/model/diy-page.d.ts +3 -2
  30. package/dist/types/modules/panels/model/diy-record.d.ts +3 -3
  31. package/dist/types/modules/panels/model/diy-report.d.ts +6 -7
  32. package/dist/types/modules/panels/model/market-dependency-update.d.ts +21 -0
  33. package/dist/types/modules/panels/model/market-dispatch.d.ts +1 -0
  34. package/dist/types/modules/panels/model/market-types.d.ts +1 -0
  35. package/dist/types/modules/panels/model/market-update-bundle.d.ts +25 -0
  36. package/dist/types/modules/panels/model/market-verify.d.ts +31 -0
  37. package/dist/types/modules/panels/unit.d.ts +3 -4
  38. package/dist/types/shared/api/bundle-install.d.ts +13 -0
  39. package/dist/types/shared/api/host/approval-gate.d.ts +39 -0
  40. package/dist/types/shared/api/host/bundle-routes.d.ts +2 -1
  41. package/dist/types/shared/api/host/diy-artifacts.d.ts +14 -0
  42. package/dist/types/shared/api/host/diy-modules.d.ts +41 -0
  43. package/dist/types/shared/api/host/host-routes.d.ts +13 -0
  44. package/dist/types/shared/api/host/permission-mode.d.ts +9 -0
  45. package/dist/types/shared/api/host/runtime-seed.d.ts +2 -0
  46. package/dist/types/shared/api/host/self-update.d.ts +9 -2
  47. package/dist/types/shared/api/host/sidebar-edition.d.ts +46 -0
  48. package/dist/types/shared/api/host/workspace-root.d.ts +13 -0
  49. package/dist/types/shared/api/sidebar-edition.d.ts +14 -0
  50. package/dist/types/shared/api/unit-board.d.ts +20 -0
  51. package/dist/types/shared/api/write-outcome.d.ts +15 -0
  52. package/dist/types/shared/components/IconModules.d.ts +8 -0
  53. package/dist/types/shared/components/blocker-action.d.ts +16 -0
  54. package/dist/types/shared/hooks/sidebar-edition.d.ts +7 -0
  55. package/dist/types/shared/hooks/use-capabilities.d.ts +5 -0
  56. package/dist/types/shared/hooks/use-capability-gate.d.ts +1 -2
  57. package/dist/types/shared/hooks/use-host-write.d.ts +1 -1
  58. package/dist/types/shared/hooks/use-unit-board.d.ts +1 -1
  59. package/docs/HANDOFF.md +3 -3
  60. package/docs/OPERATIONS.md +16 -5
  61. package/package.json +3 -3
  62. package/preset/diy.patch.yml +2 -2
  63. package/profile/isolation.patch.yml +11 -0
  64. package/skills/workbench-module-diy/BUILD.md +62 -131
  65. package/skills/workbench-module-diy/COPY.md +89 -0
  66. package/skills/workbench-module-diy/DATASOURCE.md +84 -112
  67. package/skills/workbench-module-diy/DESIGN.md +75 -109
  68. package/skills/workbench-module-diy/FAMILY.md +70 -54
  69. package/skills/workbench-module-diy/KIT.md +77 -133
  70. package/skills/workbench-module-diy/LINKAGE.md +66 -105
  71. package/skills/workbench-module-diy/PANEL.md +79 -171
  72. package/skills/workbench-module-diy/REVIEW.md +67 -81
  73. package/skills/workbench-module-diy/SKILL.md +57 -96
  74. package/skills/workbench-module-diy/STEPS.md +66 -69
  75. package/skills/workbench-module-diy/TOOLING.md +34 -51
  76. package/skills/workbench-module-diy/TOOLS.md +110 -103
  77. package/skills/workbench-module-diy/WIRING.md +51 -83
  78. package/skills/workbench-module-diy/WRITES.md +89 -142
  79. package/dist/types/app/layouts/config-sync/ConfigSyncSection.d.ts +0 -5
  80. package/dist/types/shared/api/host/diy-workshop.d.ts +0 -23
  81. package/dist/types/shared/api/host/workshop-root.d.ts +0 -16
  82. /package/dist/types/shared/{api/host/diy-workshop.test.d.ts → components/blocker-action.test.d.ts} +0 -0
@@ -1,219 +1,127 @@
1
- # 面板:他看什么、他配什么、画成什么样
1
+ # 面板:内容、配置、状态、样式
2
2
 
3
- 步骤 1 的参考:问什么、他看到什么。正文用**他的视角**写,唯一的例外是 §5.1 ——
4
- "这份配置存不存"有代码形状,那一节给写法。
3
+ 步骤 1 的参考。
5
4
 
6
- ## 1 面板上该有的东西只有三类
5
+ ## 1 只放三类东西
7
6
 
8
- | 类别 | 例子 | 谁来承接 |
7
+ | 类 | 例 | 承接 |
9
8
  |---|---|---|
10
- | **导航** | 换项目、切面板、折叠 | 界面自己(只改他看到的视图) |
11
- | **呈现** | 状态、数字、最近几条历史 | 只读呈现给他 |
12
- | **交代** | "交给 agent"、预填一句指令、一个动作按钮 | 转给 agent |
9
+ | 导航 | 换项目、切面板、折叠 | 界面自身 |
10
+ | 呈现 | 状态、数字、最近几条 | 只读 |
11
+ | 交代 | "交给 agent"、预填指令、动作按钮 | 转给 agent |
13
12
 
14
- **他不该在面板上做的事**:
13
+ 不放:平台写操作(评论、流转、发布)→ agent;详情 / 评论 / 日志 / 翻页 → "去平台看";
14
+ 多步、长等、中途要人、失败要续 → 封成一个动作(一个按钮 + 一个交代入口)。
15
15
 
16
- - 平台上的写(评论、流转、发布)→ 交给 agent;
17
- - 详情、评论、日志、翻页 → 给他一个"去平台看"的去处;
18
- - 一次要动好几下、要等很久、中途要他登录或点头、失败还要接着来的 → **封成一个动作**,面板上只给他一个按钮 + 一个交代入口;中间那些来回留在对话里。
16
+ ## 2 封动作还是交 agent
19
17
 
20
- ## 2 判"封成一个动作"还是"交给 agent"
18
+ 任一为是则封动作:要动平台好几下;要等分钟级;中途需用户本人(登录、点头、冲突);失败要续。
19
+ 都不是 → 交给 agent。
21
20
 
22
- 问他四个问题,任一为"是"就封成一个动作:
21
+ ## 3 什么算配置
23
22
 
24
- 1. 一次要动平台上好几下?
25
- 2. 要等很久(分钟级)?
26
- 3. 中途需要他本人(登录、点头、撞上冲突)?
27
- 4. 失败了要能接着来?
23
+ 只有用户在本面板的偏好:看哪份、显示哪些、钉哪个环境、绑定哪个对象。碰平台的不算配置。
24
+ 形态:一屏选完 + 保存前校验;不做分页向导、多步流程。
28
25
 
29
- 都不是(单步、马上、能先看一遍)→ 交给 agent 就够了。
26
+ ## 4 展示清单
30
27
 
31
- ## 3 什么算"配置"
28
+ 每行三项:
32
29
 
33
- 只有**他自己在这块面板上的偏好**算配置:看哪一份、显示哪些、钉哪个环境、绑定到哪个对象。
34
-
35
- 碰平台的一律不是配置。
36
-
37
- 配置的形态是一个面里**一次选完 + 保存前替他校验**;不做分页向导、不做好几步的流程(他看不到全局,出错也难查)。
38
-
39
- ## 4 展示清单怎么写
40
-
41
- 每条一行,就这三样:
42
-
43
- | 写什么 | 判据 |
30
+ | 项 | 标准 |
44
31
  |---|---|
45
- | 这一格是什么 | 一句他会说的话;出现平台内部术语就重写 |
46
- | 一眼能得出什么结论 | 他要拿它做决定;说不出决定的格子就别放 |
47
- | 点一下去哪 | 去平台看详情,或者交给 agent —— 两者至少有一个 |
48
-
49
- - 一屏不要塞太多:他要的结论放最上面,历史、明细沉到下面。
50
- - 同一列里几张卡要长得一样(标题、刷新、设置、"去平台"的位置一致)。
51
- - 面板上的每个数字,agent 都要能通过自己的能力拿到;每个按钮都要有一句等价的话。做不到就别放。
32
+ | 这一格是什么 | 用户会说的话;有平台术语就重写 |
33
+ | 一眼得出什么结论 | 能支撑决定;不能就不放 |
34
+ | 点一下去哪 | 去平台或交给 agent,至少一个 |
52
35
 
53
- ## 5 配置清单怎么写
36
+ 同列卡片结构一致(标题、刷新、设置、"去平台"位置)。每个数字都要有 agent 能力能取到;每个按钮都要有等价的一句话。
54
37
 
55
- 每项说清这七件事:
38
+ ## 5 配置清单
56
39
 
57
- | 项 | 说清什么 |
58
- |---|---|
59
- | 管什么 | 一句话,用他的词 |
60
- | 默认 | 不给偏好时他看到的会是什么 |
61
- | 能选什么 | 候选从**现在真实存在**的里列;过期的候选不列 |
62
- | 选错会怎样 | 拒绝并说清(还认的候选有哪些),不悄悄换成一个别的 |
63
- | 记多久、记在哪 | 按项目分别记;换项目不串;换仓库/换对象时旧选择作废并让他重选 |
64
- | 怎么生效 | 他按下保存才生效;保存成功直接回到看板并立刻按新的显示;保存失败留在原地告诉他为什么 |
65
- | 怎么清 | 他自己显式清;收起或卸载这块面板**不删**他的配置 |
66
-
67
- ### 5.1 两种配置写法,别混
40
+ 每项七栏:管什么 / 默认 / 候选(只列现存) / 选错(拒绝并列出仍可选项,不静默替换) /
41
+ 记录范围(按项目,换项目不串;换仓库或对象时作废旧选择并要求重选) / 生效(保存后生效,成功回看板刷新,失败留在原地说原因) /
42
+ 清除(用户显式清;收起或卸载面板不删)。
68
43
 
69
- 配置要分清**存不存** —— 它决定代码怎么写,也决定他刷新之后看到什么。`design.json` 的
70
- `config[].persistence` 这一格写的就是这个(`prefs` = 存起来;`view` = 只选一下)。
44
+ ### 5.1 persistence:`view` 还是 `prefs`
71
45
 
72
- | 写法 | 什么时候用 | 刷新后 | 怎么实现 |
46
+ | 写法 | 场景 | 刷新后 | 实现 |
73
47
  |---|---|---|---|
74
- | `view` | 卡片底部一次性的选择(看哪种口径、看几天) | 回默认 | 当 **route 参数**传给模块自己的只读口;非法的退路写进 `wrongChoice` |
75
- | `prefs` | 他配一次、以后一直算数(钉哪个环境、忽略哪些、超时多久) | **还在** | `mount` 返回 `prefs: { resource, handle }`,`handle` 里用 `deps.store` 落盘 |
48
+ | `view` | 卡片底部一次性选择(口径、天数) | 回默认 | 作 route 参数传给只读口;非法值写进 `wrongChoice` |
49
+ | `prefs` | 配一次长期有效(环境、忽略项、超时) | 保留 | `mount` 返回 `prefs: { resource, handle }`,`handle` 用 `deps.store` 落盘 |
76
50
 
77
- **宿主半侧**(最小可抄):
51
+ 宿主侧:
78
52
 
79
53
  ```ts
80
- // mount(deps) 里。按项目分开记:这个项目一份,换项目不串
54
+ // mount(deps) 内;按项目分文件
81
55
  const store = deps.store.for(UNIT_ID, workspace);
82
56
  return {
83
- // …
84
57
  prefs: {
85
58
  resource: 'your-module',
86
- // 校验 + 落盘 + 把新的整份偏好回给调用方(别只回 ok)
87
- handle: (payload) => applyPref(store, payload),
59
+ handle: (payload) => applyPref(store, payload), // 校验 + 落盘 + 返回整份新偏好
88
60
  },
89
61
  };
90
62
  ```
91
63
 
92
- **浏览器半侧**:写走 `prefs` 通道,读还是模块自己的只读口(别为配置另开一条)。
93
- 排版与内边距都是外壳给的默认(`Field` / `TextField`),**模块不需要为"能看"写 CSS**:
64
+ 浏览器侧:写走 `prefs`,读取仍走模块只读口。布局与内边距用外壳默认(`Field` / `TextField`),不必为可读性写 CSS。
94
65
 
95
66
  ```tsx
96
67
  const write = useHostWrite({ channel: 'prefs', resource: 'your-module' });
97
- // 控件用 kit 的(Field + TextField / ChoiceGroup),外面套 SettingsSection,保存用 SaveBar;失败留在原地
98
- write.send({ ignored, timeoutSeconds, workspace });
68
+ write.send({ ignored, timeoutSeconds, workspace }); // 控件:Field + TextField / ChoiceGroup;容器 SettingsSection;保存 SaveBar
99
69
  ```
100
70
 
101
- `handle` 里那几件事(成熟模块都这么做):
102
-
103
- - **未设置 ≠ 空**:没写过就出"第一次选"那一屏;显式选空是"空态",两者不能混;
104
- - **清空 = 回到默认,并把文件收掉**(不留一个空对象);
105
- - **按项目 / 工坊分开记**,换项目不串;
106
- - **不认识的值报错,并把还认的候选列出来**(不悄悄换一个别的);
107
- - 去重、按他给的顺序存。
108
-
109
- **他会看到什么**:面板上多一段"他能自己配的"(填 / 选 + 保存 + 回到默认);存成功卡上立刻按新的显示
110
- (写偏好会 bump 这个单元声明过的事实,见 [LINKAGE.md](LINKAGE.md) §8);**存失败留在原地并说清为什么**。
111
- agent 那一侧也要能通过模块自己的能力读到同一份偏好。
112
-
113
- ## 6 界面的几个习惯
114
-
115
- - **有不打扰的默认就别弹设置**:能用默认值读出他关心的那份,就直接显示,别先问他。真的分不清是哪一份,才把选择摆到他面前(不是先让他读一句话再点一次)。
116
- - **能选的直接摆出来**:候选就两三条时,平铺一行一行给他点,别藏进下拉。
117
- - **量词写在值旁边**("3 条"、"6 个文件"),别让他猜单位。
118
- - **时间写成他看得懂的样子**,不要原始时间戳。
119
- - **默认收起**:历史、明细折叠,露出最近一条;只有一条时不给折叠三角(没有可展开的东西)。
120
- - **屏幕上的字越少越好**:他能看懂的(红点绿点、数字)就不用再写一段规则解释;删掉的东西不要留一行说明。
121
- - **默认状态点**只有四种意思:在做 / 做完了 / 出错 / 没在跑。
122
- - **外链一律用 kit 的 `Link`**(`h(Link, { href: … }, 文案)` —— `h` 从 `react` 引,**不在 kit 面里**):新窗口 + `rel` + 自带外链箭头,
123
- **它自带的样子就是唯一一份,别去掉箭头、别换箭头、别自己调**(没有 `icon` 这个口子)——
124
- 全仓的链接必须长得一样。
125
- 模块源码里手写 `<a>` / `h('a', …)` 会被「样式归属」判据**直接判红**。
126
- **文案里也不许自带箭头**(`'去网关看账单 →'` 这种)—— 箭头由组件给,自带的话页面上就是两颗。
127
- 它内部已经把 dsh 的图标原语包好了,所以模块**不用**为它声明 `client.external`。
128
- - **哪几种手写 `<a>` 不算 `Link`(判据不该咬、也别硬迁)**:判据钉的是**"手写 `<a>` 这件事本身"**
129
- (`Link` 是唯一出处),下面三类**有意**留着,因为它们**不是"正文链接"**——
130
- - **图标链**(icon-only 的 `<a>`,靠 `title` / `aria-label` 顶着):`Link` 一定会多画一颗 ↗,
131
- 套进去就是两颗,尺寸与槽位它也表达不了 ⇒ 留 `<a>`。
132
- - **按钮皮的链**(`.anchorButton` 那类 28 高 / 描边的"看起来像按钮、实际是链接"):**必须是链接的动作
133
- 留真 `<a>`** —— `href` 带来的是中键新开、右键复制链接、悬停看 URL;塞成
134
- `onClick: () => window.open(…)` 的按钮,这三件一起丢。
135
- - **整行链**(一行整体可点,`flex: 1` / `color: inherit`):它是**那条行的布局槽**,不是正文里的字。
136
- **要放宽这一条就走棘轮**:那张已知豁免表(`module-tools` 的 `style` 判据里,`ANCHOR_EXEMPT`)
137
- **只许减不许加**(迁一处删一行);真要加一格,那是**改判据口径**,得写清"为什么 `Link` 表达不了它"。
138
- 口径的权威那份是 `style.mjs` 里 `BARE_ANCHOR` 那一段注释(`ANCHOR_EXEMPT` 上面)
139
- 与 `module-tools/README.md` 的 `style` 那一行。
140
- - **"去看某处"用哪个**:跳**整张卡对应的系统** → 卡头那颗图标按钮(`CardHead.platformHref`,给这一个就够);
141
- 跳**一个具体页面 / 一条具体记录** → `Link`(正文里的文字链接);**触发一个动作**(刷新、重建、去登录)→
142
- dsh 的 `Button`,或阻断屏里的 `action`。区别在"跳去哪"和"它是不是一个动作",不在好看不好看。
143
- - **标签 + 输入框用 `Field` + `TextField`**(排布与内边距是外壳的默认,别自己写 CSS,见 §5.1)。
144
- - **要做什么用哪件,照 [KIT.md](KIT.md) 抄**("要做什么 → 用哪件 → 最小写法"的表)。
145
- 下面几条是口径,KIT.md 是写法。
146
- - **字号与重点**:走那把刻度(`--dsw-font-*`:11 / 12 / 13 / 14 / 16 / 20 / 24),
147
- **但不许"整张卡一样大"** —— 要有重点。最该大的那个数字(余额、总数、还剩多少)
148
- 就用刻度里最大那档 `--dsw-font-xl-24`(24/32、600 重),它旁边的说明 12。
149
- 这一档是**给重点留的、鼓励用**:卡里最大的字比正文大一倍,比全卡 12 号好读得多。
150
- 刻度外非要一个字面值(28/32 那种)时,在模块 `README.md` 里写一句为什么,别静悄悄写。
151
- 成列的数字加 `font-variant-numeric: tabular-nums`。
152
- **别为了"统一"把重点压回正文大小**——那才是不合口径。
153
- - **颜色与间距也走令牌**(`--dsw-*` / `--wb-*`);自己写 CSS 只写布局(flex / grid / gap / 对齐)。
154
-
155
- ## 7 状态矩阵怎么写
156
-
157
- 把每种状态都走一遍,一个不落:
158
-
159
- 没有绑定 / 还没接入 / **分不清是哪一份** / 正在读 / 有数据 / 正在做 / 卡住了(要登录、没权限、超时、能力没装好)/ 读到了但是空 / 数字是上一轮的。
160
-
161
- 每一格写三样:
162
-
163
- | 列 | 写法 |
164
- |---|---|
165
- | 他看到什么 | 屏幕上是什么(哪句话、哪个画面、哪颗点) |
166
- | 他能做什么 | 一个动作(去哪补、点哪个、重试) |
167
- | agent 能接着做什么 | 一句 agent 能照做的话 |
71
+ `handle` 规则:未设置 ≠ 空(首次进入"第一次选");清空 = 回默认并删文件;按工作区分文件;
72
+ 未知值报错并列出候选;去重、保留用户顺序。
73
+ 保存成功后卡片按新偏好显示(写偏好 bump 该单元的事实,[LINKAGE.md](LINKAGE.md) §8);失败留在原地说原因。
74
+
75
+ ## 6 界面规则
76
+
77
+ 文案(主句 / 原文、通俗、zh / en)见 [COPY.md](COPY.md) §3。
78
+
79
+ - 有默认可读的数据就直接显示,不先弹设置;只有分不清时才摆选择。
80
+ - 候选 ≤3 个时平铺,不放进下拉。
81
+ - 历史、明细默认折叠,露出最近一条;只有一条时不给折叠控件。
82
+ - 状态点只有四义:在做 / 完成 / 出错 / 未运行。
83
+ - 外链一律 `Link`(`h(Link, { href }, 文案)`;`h` 从 `react` 引):新窗口 + `rel` + 箭头,唯一形态,
84
+ 没有 `icon` 口子;文案里不自带箭头。手写 `<a>` / `h('a')` 会被 `style` 判据判红。
85
+ 例外仅三类非正文链接:图标链、按钮样式的链、整行链。放宽走 `ANCHOR_EXEMPT`,只减不加。
86
+ - 跳整张卡对应的系统 → 卡头 `CardHead.platformHref`;跳具体页面 / 记录 → `Link`;触发动作 → dsh `Button` 或阻断屏 `action`。
87
+ - 标签 + 输入框用 `Field` + `TextField`,不自写 CSS。
88
+ - 组件选型照 [KIT.md](KIT.md)。
89
+ - **字号与重点**:走刻度(`--dsw-font-*`:11 / 12 / 13 / 14 / 16 / 20 / 24),不整卡同字号。
90
+ 最重点的数字用 `--dsw-font-xl-24`(24/32、600),旁注 12;刻度外字面值需在模块 `README.md` 说明。
91
+ 成列数字加 `font-variant-numeric: tabular-nums`。
92
+ - 颜色与间距走令牌(`--dsw-*` / `--wb-*`);自写 CSS 只写布局(flex / grid / gap / 对齐)。
168
93
 
169
- 规矩:
94
+ ## 7 状态矩阵
170
95
 
171
- - 每句都要**能照做**:缺什么、去哪补、谁补。
172
- - **平台没给的那一格就不占位**(不留孤零零的字段名、不留空的一行)。
173
- - **读不到就说读不到**,并说清为什么(例:手上这份能力太旧);**绝不显示 0**。
174
- - 有确定答案就不打扰他(能按默认读出来就别弹设置面)。
175
- - 真机上造不出来的状态也要**在设计上**画出来(照这一格写清)—— 模块自己的 `plugin.test.ts` 把五态各喂一遍就够(`check --only cards` 会拿**七种外壳侧输入**各喂一遍 —— 它只问"退化得干不干净",**不断言**"画的是哪一态那一屏")。
96
+ 状态全集:没有绑定 / 未接入 / 分不清是哪一份 / 正在读 / 有数据 / 正在做 / 卡住(登录、权限、超时、能力未装) /
97
+ 读到但空 / 数字是上一轮。
176
98
 
177
- ### 7.1 图例就是把屏幕画成 ASCII
99
+ 每格三列:看到什么(哪句话、哪个画面、哪颗点)/ 能做什么(去哪补、点哪、重试)/ agent 能接着做什么(可照做的一句)。
178
100
 
179
- **跟用户沟通主要就靠这些图。** 矩阵左列写**图号**(A / B / C…),图放在矩阵前面,一张 ASCII = 那一种状态在屏幕上是什么样。至少要有这几屏:
101
+ 规则:每句可照做;平台没给的格不留占位;读不到就说读不到并给原因,不显示 0;有确定答案就不打扰。
102
+ 真机造不出的状态也要在设计里画;`plugin.test.ts` 覆盖五态(`check --only cards` 用七种外壳侧输入各跑一遍,
103
+ 只判"是否干净退化",不判画的是哪一态)。
180
104
 
181
- - **有数据**(正常那张卡)
182
- - **空态**(图标 + 一句话,居中,**没有按钮**)
183
- - **阻断 / 卡住**(**整张卡换成这一屏**:图标 + 一句话 + 按钮)
184
- - **提示**(卡片照常画数据,只在最上面多一条细提示行)
185
- - **骨架**(几行灰条:正在读、还没有数据)
186
- - **在转**(列表照常在,只有卡片头那颗刷新在转)
187
- - **一行里那些记号挨着的样子**(状态点 + 编号 + 元信息 + 行尾两颗)
188
- - **状态点的几档**(只有颜色不同;**撞色的那两档要在图下点明靠什么分开**)
189
- - **可点的筛选**(几颗、选中的那颗什么样)
190
- - **只在某种模式出现的东西**
191
- - **审批弹窗**(哪一行是套件给的、哪一行是系统给的、按钮在哪)
105
+ ### 7.1 图例
192
106
 
193
- 三条写法:
107
+ 矩阵左列写图号,图按图号排。至少这些屏:有数据 · 空态(图标 + 一句话,无按钮)· 阻断(整卡替换:图标 + 话 + 按钮)·
108
+ 提示(数据照常,顶部一条)· 骨架 · 在转(列表在,卡头刷新转)· 行内记号(状态点 + 编号 + 元信息 + 行尾两颗)·
109
+ 状态点各档(撞色两档在图下注明区分方式)· 可点筛选 · 仅某模式出现 · 审批弹窗(每行谁给的)。
194
110
 
195
- - **用真字**:图里的文案用界面上真实那句(例「没有指派给你的缺陷。」),别写"一句空态文案"。
196
- - **别拿图标代替画面**:给状态起个名字、配个 `⛔` 这类符号,用户还是分不清;一眼能认的是**形状**(整卡换掉 vs 多一行)与**位置**(行首的点、行尾的两颗)。
197
- - **画不成的才写词**:动作、agent 的下一步、时间性的"不变 / 自己变",写成词并说清意思。
198
- - **真机上造不出来的状态**:沟通时照样画(画的是设计意图);实现时**要让它能用真实输入走到**(换个空目录、换个坏路径、先不选),模块自己的 `plugin.test.ts` 把五态各喂一遍就够。
111
+ 图里用界面真实文案;不用符号代替画面(形状与位置才是识别点);画不成的写词并定义。
112
+ 真机造不出的照样画;实现时用真实输入走到(空目录、坏路径、未选择)。
199
113
 
200
- ## 8 范例(真实产出的形状)
114
+ ## 8 范例
201
115
 
202
- 要一个"同形状、能跑"的样子:[SKILL.md](SKILL.md) 的「找不到一件事的时候」—— 模板
203
- (`template-diy/` / `template/`)与装机模块的源码都可以直接读。
116
+ `template-diy/`(带数据源)与 `template/`(空壳)。
204
117
 
205
- ## 9 界面边界:kit 不够用时怎么办
118
+ ## 9 kit 不够时
206
119
 
207
- **先从 kit 拿**(`@dsh-workbench/core/client`,`check --only kit` 会列全这份外壳有什么)。
208
- kit 里确实没有、而你要的又是标准控件(按钮、选择器、设置区块那种)时,可以引 dsh 的客户端原语 ——
209
- **有哪些**问 `dsh-wb-module check --only primitives`(它还会把"kit 里已有"的那批挑出来):
120
+ 先查 kit(`check --only kit`)。确无且需要标准控件时,可引 dsh 客户端原语(清单:`check --only primitives`):
210
121
 
211
- * 只在**浏览器侧**引(`src/client.tsx` 与 `src/components/**` 那一侧);
212
- * 在 `package.json` 的 `dsh.client.external` 里**声明** `@deepseek-ai/dsh-client-ui-primitives`,
213
- 同时把 `devDependencies` 里那行**精确版**对齐到外壳钉的那版(骨架默认已经两处都写好了;
214
- 声明完记得 `pnpm install`,否则 `pnpm typecheck` 找不到它);
215
- * 宿主侧(`src/host.ts`)**一个字都不许碰** —— `check` 的源码边界判据会红;宿主侧 import
216
- `src/components/**` 还会被 `arch` 按跨运行时(S23)判红 —— 组件整片算 client 面。
122
+ - 只在浏览器侧引(`src/client.tsx`、`src/components/**`)。
123
+ - `dsh.client.external` 声明 `@deepseek-ai/dsh-client-ui-primitives`;`devDependencies` 用与外壳一致的精确版。
124
+ 改完 `pnpm install`。
125
+ - 宿主侧不引:`source` 判据红;宿主 import `src/components/**` 还会被 `arch` 的 S23 判红。
217
126
 
218
- 理由不是"能省事":装机成熟模块里有一半的界面是这么搭的。但**能拿 kit 的时候别自己画** —— kit 那套跟着外壳一起换主题、跟着语言走,
219
- 自己画的不会。
127
+ kit 优先:kit 跟随外壳主题与语言。
@@ -1,95 +1,82 @@
1
- # 门禁红了怎么办:十三条判据的人话对照表
1
+ # 门禁:判据、修法、取证
2
2
 
3
- 跑 `dsh-wb-module check`(或单跑一条 `--only <名字>`)。**每条红都在讲一件真出过事的事**;红了照下表修,别抛给用户。
3
+ 运行 `dsh-wb-module check` 或 `check --only <判据>`。红即修,不转给用户。
4
4
 
5
- | 判据 | 它防什么 | 怎么修 |
5
+ | 判据 | 检查 | 修法 |
6
6
  |---|---|---|
7
- | `contracts` | dsh 组合期**不校验** `package.json` 声明;清单 ↔ 代码 / 底稿 ↔ 清单漂了没人拦 | 报哪条改哪条:`unit` 撞了换名;`dsh.workbenchFaces` 补两个**源码**入口;四个脚本补全;私有就 `private: true`。`dsh.workbench.tools` 与 `registerTools()` 注册的**不重不漏**;写了 `design.json` 就对**键集合**(只对名字);生成物过期跑 `dsh-wb-module sync` |
8
- | `protocol` | `dsh.workbench` 缺版本 / 形状不对 / **多一个键** —— 外壳会**静默忽略**生字段 | 补 `"apiVersion": 1`;`unit`/`card`/`tools`/`host` 写全(没卡片 `null`、没工具 `[]`);删协议外的键(`faces` 住 `dsh.workbenchFaces`,`scope` **不要写**);字段清单见 module-tools README「协议」 |
9
- | `bundles` | 两个运行时互串:浏览器产物带 node 内置 / 宿主产物带 DOM、react | 各自别 import 对方那侧;只通过服务名相接 |
10
- | `source` | 非种子词的 `@deepseek-ai/*`、宿主侧碰 dsh 浏览器包、`@/` 指到工作台源码 | 宿主侧只用 Node 侧;dsh 的浏览器包(种子词)只在**只被 client 入口可达**的文件里用;`@/` 只指自己 `src/` |
11
- | `primitives` | 声明 `external` 却没产物物证 ⇒ 原语被**内联**成第二份实例 | `dsh.client.external` 里声明;`devDependencies` 精确版对齐外壳钉的那份;产物里要是 `require(...)`(清单:`check --only primitives`) |
12
- | `kit` | 用了 peer 下限那份外壳**还没有**的成员 ⇒ 卡片首帧 `Element type is invalid`,整块面板被卸 | 只用 peer 范围里真有的成员;下限提到你实际用的那档 |
13
- | `cards` | 卡片在外壳侧输入下崩 / 空白 / 变脏(八态 × `zh`/`en` 各一遍;走**真壳**取数) | 五态都画得出(加载 / 空 / 阻断);外套 `kit.components.Card`;`titleKey` 与卡片问过的每个键 `zh`/`en` 都有落点。判据**不断言**画的是哪一态 —— 那归 `plugin.test.ts` |
14
- | `standalone` | "只有工作台源码在场才编得过" ⇒ 发出去就散架 | 别引用工作台源码;`tsconfig` 的 `paths` 用**相对路径**(`"./src/*"`) |
15
- | `markers` | 内网域名 / 企业内部名泄进要公开发布的包 | 别写进包里;要配就落到模块自己的配置面 |
16
- | `alias` | `@/` 伸进工作台源码 ⇒ 装的人手上没那份源码 | `@/` 只许指自己 `src/`;跨包走包名 |
17
- | `style` | 模块定义 `.wb-*` 覆盖外壳类;手写行 / 操作区 / **整张表单**(F1–F4) | 样式走 `*.module.css`(不带 `wb-` 前缀);借外壳类写 `:global(.wb-…)`;行与操作区用 kit 的 `FormRow(s)` / `FormActions`,整张表单用 `Form`([KIT.md](KIT.md) §5)。**F4**:源码里出现裸 `<form`、或手写行槽类(`wb-formRow` / `wb-formRows` / `wb-field`)⇒ 红(口径见 ADR-0068) |
18
- | `arch` | 结构退化(巨文件 / 反向依赖 / 跨运行时 / 表外目录 / 模型层没测试) | 见「第十二条 `arch`」 |
19
- | `lint` | 单文件语法卫生(`as any` / 残留 `console`、`debugger` / 内联颜色、`!important` / react-hooks) | 见「第十三条 `lint`」 |
20
-
21
- ## 第十二条 `arch`:模块自己的分层形状
22
-
23
- **先分清跑没跑**:只跑带 `arch.config.mjs` 的模块;没有那份文件 = `○ 不适用`(skip),**不是红**。上车就在包根加一行工厂:
7
+ | `contracts` | 清单 ↔ 代码 ↔ `design.json` 漂移;dsh 组合期不校验清单 | 按报错改:`unit` 重名换名;`dsh.workbenchFaces` 两个源码入口;六个脚本;`private: true`。`dsh.workbench.tools` 与 `registerTools()` 一致;`design.json` 对键名;生成物过期跑 `sync` |
8
+ | `protocol` | `dsh.workbench` 版本 / 形状 / 多余键(外壳静默忽略生字段) | 补 `"apiVersion": 1`;`unit` / `card` / `tools` / `host` 写全(无卡片 `null`、无工具 `[]`);删协议外键(`faces` 在 `dsh.workbenchFaces`;不写 `scope`) |
9
+ | `bundles` | 运行时互串:浏览器产物含 node 内置 / 宿主产物含 DOM、react | 不 import 对方那侧 |
10
+ | `source` | 非种子 `@deepseek-ai/*`;宿主碰 dsh 浏览器包;`@/` 指外壳;根入口取运行值;客户端引 `/host` | 宿主只用 Node 侧;种子词仅在 client 可达文件;`@/` 只指自己 `src/`;运行值:浏览器 `kit`、宿主 `shared/host` |
11
+ | `primitives` | 声明 `external` 但产物无物证(原语被内联) | `dsh.client.external` 声明;devDependency 精确版对齐;产物中为 `require(...)` |
12
+ | `kit` | 用了 peer 下限外壳没有的成员(首帧 `Element type is invalid`) | 只用 peer 范围内成员;下限抬到实际使用版 |
13
+ | `cards` | 七种外壳侧输入 × `zh` / `en` 下崩溃 / 空白 / 脏值 | 五态可画;外层 `Card`;`titleKey` 与卡片用到的键双语齐。不断言具体态(归 `plugin.test.ts`) |
14
+ | `standalone` | 依赖工作台源码才能构建 | 不引用工作台源码;`tsconfig.paths` 用相对路径 |
15
+ | `markers` | 内网域名 / 企业名进入公开包 | 不进包;配置落到模块配置面 |
16
+ | `alias` | `@/` 指向工作台源码 | `@/` 只指自己 `src/` |
17
+ | `style` | 模块定义 `.wb-*`;手写行 / 操作区 / 表单(F1–F4) | 样式走 `*.module.css`(无 `wb-` 前缀);借外壳类用 `:global(.wb-…)`;行 / 操作区用 `FormRow(s)` / `FormActions`,表单用 `Form`([KIT.md](KIT.md) §5)。F4:裸 `<form` 或手写 `wb-formRow` / `wb-formRows` / `wb-field` |
18
+ | `arch` | 结构:巨文件 / 反向依赖 / 跨运行时 / 表外目录 / 模型无测试 | 见「第十二条」 |
19
+ | `lint` | 语法:`as any` / `console` / `debugger` / 颜色字面量 / `!important` / react-hooks | 见「第十三条」 |
20
+
21
+ ## 第十二条 `arch`
22
+
23
+ 只跑带 `arch.config.mjs` 的模块;无文件 = 不适用(skip),不是红。
24
24
 
25
25
  ```js
26
26
  import { moduleArch } from '@dsh-workbench/module-tools/arch';
27
27
  export default moduleArch();
28
28
  ```
29
29
 
30
- **怎么读**:`dsh-wb-module check --only arch`。通过长这样(数字随模块走):
30
+ 报错格式 `<包名>: <文件>:<行> <说明>(<规则>)—— <hint>`;仅 `error` 阻断(`warn` 提示);配置 import / 语法错也红。
31
31
 
32
- ```
33
- ✔ 模块架构(分层 / 边界 / 设计系统) 1/1 个模块包带 arch.config.mjs · 7 个源文件 · 58 条规则在跑
34
- ```
35
-
36
- 红了逐条是 `<包名>: <文件>:<行> <说明>(<规则>) —— <hint>`;括号里那个规则号是下表的键;只有 `error` 挡门禁(`warn` 只提示);配置自己跑不起来(import / 语法错)也判红。
37
-
38
- | 常见红(规则) | 它在说什么 | 怎么修 |
32
+ | 规则 | 含义 | 修法 |
39
33
  |---|---|---|
40
- | `[S01] 文件不在目录契约内` | 文件放到层序表外的目录(如 `src/utils/`) | 挪进 `model/` / `components/`,或用 `layers`/`roles` 明写这一层。`src/messages.ts` / `src/declared-tools.ts` / `src/**/__tests__/**` 已有内置角色 |
41
- | `[S21] 反向依赖` | 底层文件引了更上层(如组件引 `../host`) | 被引的下沉到 `model/`;只许依赖层号 ≤ 自己的 |
42
- | `[S23] 跨运行时走了「client」面` | **宿主侧 import 了 `src/components/**`**(或反过来) | 组件整片算 client 面;两侧只通过服务名相接 |
43
- | `[S16] 文件 N 行,超过上限 200` | 文件太长(默认文件 200 / 函数 80)。**测试文件不吃这条**(`.test.`/`.spec.` 与 `src/**/__tests__/**` 豁免) | 拆;确实该长的传 `fileLines`/`viewLines`/`functionLines`,或给这一个文件开 `exceptions` |
44
- | `[M08] 没有配对测试` | `src/model/**` 有文件却没测试 | 补 `src/model/X.test.ts` |
45
- | `[D12] padding: 13px 不在长度的刻度里` | 手搓了不在刻度上的数值 | 引用 `--dsw-*` 令牌或走刻度(`0` 本来就被跳过) |
46
- | `[D09] … 用了 !important` | `!important` 只许写在 `src/components/vendor/` | 用选择器 / 层级解决 |
47
- | `[D17] 定义的类没人用` | `.module.css` 里定义了没人用的类 | 删掉 |
34
+ | `S01` 表外目录 | 文件不在层序表(如 `src/utils/`) | 移入 `model/` / `components/`,或用 `layers` / `roles` 声明 |
35
+ | `S21` 反向依赖 | 低层引高层(组件引 `../host`) | 被引项下沉 `model/`;只依赖层号 ≤ 自身 |
36
+ | `S23` 跨运行时 | 宿主 import `src/components/**` 或反向 | 组件归 client 面;两侧经服务名 |
37
+ | `S16` 超长 | 文件 >200 行 / 函数 >80(测试豁免) | 拆;或传 `fileLines` / `functionLines`;单文件用 `exceptions` |
38
+ | `M08` 无测试 | `src/model/**` 无配对测试 | 补 `src/model/X.test.ts` |
39
+ | `D12` 刻度外值 | 手写不在刻度的尺寸 | 用 `--dsw-*` 或刻度值 |
40
+ | `D09` `!important` | 只允许 `src/components/vendor/` | 改选择器 / 层级 |
41
+ | `D17` 未使用类 | `*.module.css` 定义未使用 | 删除 |
48
42
 
49
- 要放宽**从参数进**(别改判据):`moduleArch({ fileLines, deps, layers, roles, structure, exceptions })`,`exceptions` 每条**必须写 `reason`**(后缀 / 单文件都行,glob 是 picomatch):
43
+ 放宽从参数进:`moduleArch({ fileLines, deps, layers, roles, structure, exceptions })`;`exceptions` 每条需 `reason`:
50
44
 
51
45
  ```js
52
46
  export default moduleArch({
53
47
  layers: { host: 10 },
54
48
  roles: [{ id: 'lib:platform', pattern: 'src/platform/**', layer: 1 }],
55
- exceptions: [{ rule: 'S16', glob: 'src/host/service.ts', reason: '…为什么这一个该长…' }],
49
+ exceptions: [{ rule: 'S16', glob: 'src/host/service.ts', reason: '…' }],
56
50
  });
57
51
  ```
58
52
 
59
- ⚠️ 层序表与两个面(`dsh.workbenchFaces` 指的那两个文件)不是可调项 —— 改了先红 `contracts`/`source`(换面要整体替换 `structure` 并改清单)。内置已有三条豁免,不必重复声明。
60
-
61
- ## 第十三条 `lint`:模块自己的语法卫生
53
+ 层序表与 `dsh.workbenchFaces` 所指两个面不可调;改动会先红 `contracts` / `source`。
62
54
 
63
- `arch` 管**文件之间**的结构,`lint` 管**文件之内**的写法。
55
+ ## 第十三条 `lint`
64
56
 
65
- **先分清跑没跑**:只跑带 `eslint.config.mjs` 的模块;没有 = 汇总行里 `○ 模块代码卫生(eslint):(不适用)…`(skip),**不是红**。上车就在模块根放一份:
57
+ `arch` 管文件间,`lint` 管文件内。只跑带 `eslint.config.mjs` 的模块;无文件 = 不适用,不是红。
66
58
 
67
59
  ```js
68
60
  import { moduleLint } from '@dsh-workbench/module-tools/lint';
69
61
  export default moduleLint({ rootDir: import.meta.dirname });
70
62
  ```
71
63
 
72
- `rootDir` 把 typescript-eslint 的 `tsconfigRootDir` 钉在这个模块上 —— 本仓根上还有一份配置,多份并存时不传就猜不出来(每个文件报 `No tsconfigRootDir was set…`),所以模板与骨架都带着它。
73
-
74
- **怎么读**:`dsh-wb-module check --only lint`。通过长这样(部分模块上车时多一句 `其余 N 个没有 eslint.config.mjs(不适用)`):
64
+ `rootDir` 钉 typescript-eslint 的 `tsconfigRootDir`(工作区可能有其他 eslint 配置;不传会报
65
+ `No tsconfigRootDir was set…`)。
75
66
 
76
- ```
77
- ✔ 模块代码卫生(eslint):1/1 个模块包带 eslint.config.mjs · 8 个文件 · 0 error · 0 warn
78
- ```
79
-
80
- 红了逐条是 `名字: 文件:行:列 规则 —— 一句话`(压成单行、截 200 字)。**只有 `error` 算红**,`warn` 只计数(单跑时列在 `warn(不挡门禁):` 下);聚合最多列 40 条,`--only lint` 列全;配置跑不起来也判红(同 `arch`)。
67
+ 仅 `error` 算红;`warn` 计数。聚合最多 40 条,`--only lint` 全列;配置跑不起来也红。
81
68
 
82
- | 常见红(规则) | 它在说什么 | 怎么修 |
69
+ | 规则 | 含义 | 修法 |
83
70
  |---|---|---|
84
- | `@typescript-eslint/no-explicit-any` | `as any` / `: any` 绕开契约 | 写得出真类型就写;要放行从 `moduleLint({ rules })` 开口子,或就地 `// eslint-disable-next-line` |
85
- | `no-console` | `src/**` 里留了 `console.log`/`info`/`debug`(`warn`/`error` 放行) | 删掉;`scripts/**` 不判这条 |
86
- | `no-debugger` | 留了 `debugger` | 删掉 |
87
- | `no-restricted-syntax` | 内联颜色字面量(`#ff0000`/`rgb(`/`hsl(`),或字符串里带 `!important` | 外观走 `--dsw-*` 与 `*.module.css`;`!important` 只许 `src/components/vendor/**` |
88
- | `react-hooks/*`(`rules-of-hooks` · `refs` · `exhaustive-deps` · `immutability`) | 条件调 hook / 渲染期写 ref / 漏依赖 | hook 提到顶层、副作用放 `useEffect`、依赖补全 |
89
- | `@typescript-eslint/no-unused-vars` | 死代码(`^_` 开头放行) | 删掉,或改名 `_x` |
90
- | 其余 `recommended` / `react/*` | 语法级那批(`no-unreachable` / 空 `catch` / `jsx-key`…) | 照 eslint 的原话修 |
71
+ | `no-explicit-any` | `as any` / `: any` | 写真实类型;或在 `moduleLint({ rules })` 放行 |
72
+ | `no-console` | `src/**` 的 `console.log` / `info` / `debug`(`warn` / `error` 放行) | 删除;`scripts/**` 不判 |
73
+ | `no-debugger` | 残留 `debugger` | 删除 |
74
+ | `no-restricted-syntax` | 颜色字面量(`#ff0000` / `rgb(` / `hsl(`)、字符串 `!important` | 用令牌与 `*.module.css` |
75
+ | `react-hooks/*` | 条件 hook / 渲染期写 ref / 漏依赖 | 提到顶层;副作用进 `useEffect`;补依赖 |
76
+ | `no-unused-vars` | 死代码(`^_` 放行) | 删除或改 `_x` |
77
+ | 其他 `recommended` / `react/*` | 语法规则 | 按 eslint 原文 |
91
78
 
92
- 要放宽**从命名参数进**(`ignores`/`rules`);按目录细调就展开再追一块:
79
+ 放宽从命名参数(`ignores` / `rules`);按目录追加一块:
93
80
 
94
81
  ```js
95
82
  export default [
@@ -98,31 +85,30 @@ export default [
98
85
  ];
99
86
  ```
100
87
 
101
- ⚠️ **别改方言**:判据拿 `moduleLint()` 产出的**整份 8 块**(`@eslint/js/recommended` · 三块 `typescript-eslint/*` · `dsh-wb/module-lint` 那四块)与你的配置**逐块比**(名字 / `files` / `rules`;`/overrides`、`/root` 不比),全局 ignores 按**超集**比:多忽略可以,少一条点名缺哪条 glob。少一块、同名两次、删一条规则、改一个规则值都当场红;**只抄一个名字**(`[{ name: 'dsh-wb/module-lint', rules: {} }]`)、`export default []`、`moduleLint().filter(…)` 这类筛法也红。**残余只剩一条**:自己 import 方言再照抄整份分不出来 —— 这是抬高门槛、让改动明着写,不是"防住伪造"。
102
-
103
- **`--fix`**:`dsh-wb-module lint --fix`(模块里是 `pnpm lint -- --fix`)写回能自动修的、打印「修了 N 条 / 还剩 M 条」。**`check` 永远不动手改文件**。
104
-
105
- ## 什么时候该怀疑门禁本身
106
-
107
- 判据自己也有正反例,正常不误报。但**骨架刚 `init` 出来、一行没改却红了** —— 那是模板 / 工具坏了,不是你的。
88
+ 不改方言:判据将 `moduleLint()` 的整份 8 块与配置逐块比(名字 / `files` / `rules`;`/overrides`、`/root` 不比),
89
+ 全局 ignores 按超集比。少块、重名、改规则值、只抄名字、`export default []`、`.filter(…)` 均红。
108
90
 
109
- ## 四条"不是判据、但更常踩"的
91
+ `dsh-wb-module lint --fix` 写回可自动修项;`check` 不改文件。
110
92
 
111
- (细节在 [WIRING.md](WIRING.md) / [BUILD.md](BUILD.md))
93
+ ## 怀疑门禁
112
94
 
113
- 1. **装上去没反应** → 先问 `workbench_diy_status`(`unit=…`)。[WIRING.md](WIRING.md) §3
114
- 2. **改完没热换** → 宿主侧秒级热换;客户端刷新页面。[WIRING.md](WIRING.md) §4
115
- 3. **"再装一遍"** → 日常迭代只重建、不重装。[WIRING.md](WIRING.md) §3
116
- 4. **引了 dsh 原语却 `tsc` 报"找不到模块"** → 两处声明 + `pnpm install`([KIT.md](KIT.md) §8)。
95
+ 骨架刚 `init`、未改动却红 → 模板或工具问题,不是模块问题。
117
96
 
118
- ## 用户说"卡片不对"时:四步取证
97
+ ## 卡片故障:四步取证
119
98
 
120
- 他说"一片空白 / 一直转圈 / 数字不对"时,**先取证再改代码**;前一步有结论就停在那儿修:
99
+ 先取证再改代码;某步有结论就停在对应侧修。
121
100
 
122
- 1. **`workbench_diy_panel`** —— 这个工作区面板现在什么样:哪几张卡、每张最后一次取数是读到了还是读不到(带宿主回的码与原文)、崩过没(原因 + 次数)。有答案就别往下走。
123
- 1.5 **看这一张卡**(`workbench_diy_screenshot` / `_html` / `_find` / `_eval`)—— 打的是**用户当前那一页**上工坊那块板里这张卡(`unit=…`;四把的入参与拿回见 [FAMILY.md](FAMILY.md) §2),配合上一步把"看起来不对"说成"哪一处不对"。**这一族(浏览器那 14 把 + `logs_*`)要先 `workbench_diy_debug_start`**(`unit=…`;会请用户点头一次)—— 没有这场约定一律当场拒;不看屏先 `_pause`,收工 `_stop`。(`_panel` / `_status` 不碰他那一屏,不用开;`_find` / `_eval` 带 `read=` 时也不碰。)
124
- 2. **`workbench_diy_status`(`unit=…`)** —— 看"起得来"那一行:模块起不来时原因就在那里(装配抛的错 / 声明被拒的原文)。
125
- 3. **自己把那条读跑一遍**:卡片画的就是模块那条宿主路由的返回值 —— `curl '<工作台地址>/workbench/api/<unit>/<route>'`(默认 `http://127.0.0.1:3081`)。接口对而卡片不对 ⇒ 渲染侧;接口就错 ⇒ 宿主侧(照 [DATASOURCE.md](DATASOURCE.md) 重探)。要**浏览器里那一次**的原文,用 `workbench_diy_eval`(`read=<站点地址>`,脚本里 `fetch` 自己那条读)。
126
- 4. **最后才看用户那一页**:要控制台走三拍 —— `workbench_diy_logs_watch` → 做那件你怀疑会报错的事 → `workbench_diy_logs_read`(读完 `_logs_stop` 卸掉;上限:每页 200 条 / 单条 400 字、随刷新消失)。**别用 `opencli browser <会话> console`** —— 那是个 stub(恒回空数组、还 exit 0)。要在页面里跑自己的代码用 `workbench_diy_eval` 的 `read=` 档;看**用户那一页**有什么可点的用 `workbench_diy_state`。宿主侧热换 / 装载的报错在**起工作台那个终端**里。
101
+ 1. `workbench_diy_panel`:每卡的读取状态(宿主码与原文)、是否崩过(原因、次数)。有结论即止。
102
+ 2. 看卡片:`workbench_diy_screenshot` / `_html` / `_find` / `_eval`,作用于用户当前页的该卡(`unit=…`)。
103
+ 这一族(14 把 + `logs_*`)需先 `workbench_diy_debug_start`(`unit=…`,审批一次),否则拒绝;
104
+ `_panel` / `_status` / `read=` 档不需要。用完 `_pause`,收工 `_stop`。
105
+ 3. `workbench_diy_status`(`unit=…`):看"起得来"行(装配错误 / 声明被拒原文)。
106
+ 4. 直接请求路由:`curl '<工作台地址>/workbench/api/<unit>/<route>'`(默认 `http://127.0.0.1:3081`)。
107
+ 接口对 → 渲染侧;接口错 → 宿主侧(按 [DATASOURCE.md](DATASOURCE.md) 重探)。需要在页面内请求时用
108
+ `_eval`(`read=<站点>`)。控制台:`_logs_watch` → 操作 → `_logs_read`(每页 200 条 / 单条 400 字,
109
+ 刷新即失;用 `_logs_stop` 卸载)。`opencli browser … console` 是空桩(恒空、exit 0),不用。
110
+ 宿主热换 / 装载错误在启动工作台的终端。
127
111
 
128
- 两条纪律:**别让用户截图或替你转述报错**;**验收就是你自己拿这一族走** —— 逐条对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」,`screenshot`/`html`/`find`/`eval` 的读数、动作族的返回、`workbench_diy_network` 的收发包、`logs_*` 的报错都算证据;走通再 `design accept`,走不通的如实说没验过(用户不必在场,也不用请他逐条点头)。工具参数与返回体都是 opencli 的原样(见 [FAMILY.md](FAMILY.md))。面板运行态是**用户那一页自己报上来的**,第 1 步没有内容时,先让他把工作台切到工坊那个工作区、刷新一次页面再看。
112
+ 不要求用户截图或转述报错。验收由 agent 用上述工具逐条走三张表:读数、动作返回、收发包、日志均算证据;
113
+ 走通才 `design accept`,走不通记"未验"。参数与返回为 opencli 原样([FAMILY.md](FAMILY.md))。
114
+ 面板运行态由用户页面上报;第 1 步为空时,先让用户切到该工作区并刷新一次。