@dsh-workbench/core 0.3.2 → 0.3.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/client.js +689 -149
- package/cordis.patch.yml +32 -0
- package/dist/client/client.body.js +689 -149
- package/dist/client/index.js +689 -149
- package/dist/host/index.js +3638 -2104
- package/dist/types/app/client.d.ts +120 -3
- package/dist/types/app/layouts/approval-detail.d.ts +13 -0
- package/dist/types/app/layouts/panel-hint.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/market-hint.d.ts +16 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +2 -3
- package/dist/types/app/layouts/sidebar/RecordFooterAction.d.ts +16 -0
- package/dist/types/app/layouts/sidebar/record-footer.d.ts +4 -0
- package/dist/types/modules/panels/client.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-browser-act.d.ts +2 -39
- package/dist/types/modules/panels/model/diy-browser-tools.d.ts +39 -0
- package/dist/types/modules/panels/model/diy-browser.d.ts +1 -14
- package/dist/types/modules/panels/model/diy-components.d.ts +3 -0
- package/dist/types/modules/panels/model/diy-components.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-observers.d.ts +11 -0
- package/dist/types/modules/panels/model/diy-observers.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-page.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-bar.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-capture.d.ts +27 -0
- package/dist/types/modules/panels/model/diy-record-capture.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-failure.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-routes.d.ts +2 -0
- package/dist/types/modules/panels/model/diy-record-routes.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-scripts.d.ts +6 -0
- package/dist/types/modules/panels/model/diy-record-text.d.ts +89 -0
- package/dist/types/modules/panels/model/diy-record-text.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-tools.d.ts +2 -0
- package/dist/types/modules/panels/model/diy-record.d.ts +76 -0
- package/dist/types/modules/panels/model/diy-record.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-scripts.d.ts +0 -63
- package/dist/types/modules/panels/model/diy-spec.d.ts +5 -0
- package/dist/types/modules/panels/model/diy-spec.test.d.ts +1 -0
- package/dist/types/modules/panels/model/handoff.d.ts +1 -0
- package/dist/types/modules/panels/unit.d.ts +20 -12
- package/dist/types/shared/api/host/diy-acts.d.ts +4 -0
- package/dist/types/shared/api/host/diy-components.d.ts +54 -0
- package/dist/types/shared/api/host/diy-debug-act.d.ts +24 -7
- package/dist/types/shared/api/host/diy-recon.d.ts +10 -0
- package/dist/types/shared/api/host/diy-record-act.d.ts +19 -0
- package/dist/types/shared/api/host/diy-workshop.d.ts +1 -0
- package/dist/types/shared/api/host/mount-units.d.ts +4 -0
- package/dist/types/shared/hooks/use-diy-record.d.ts +8 -0
- package/package.json +3 -3
- package/scripts/seed-workshop.sh +42 -2
- package/skills/workbench-module-diy/BUILD.md +39 -17
- package/skills/workbench-module-diy/DATASOURCE.md +4 -1
- package/skills/workbench-module-diy/DESIGN.md +40 -8
- package/skills/workbench-module-diy/FAMILY.md +55 -0
- package/skills/workbench-module-diy/HOOKS.md +95 -0
- package/skills/workbench-module-diy/KIT.md +18 -6
- package/skills/workbench-module-diy/PANEL.md +9 -5
- package/skills/workbench-module-diy/REVIEW.md +117 -49
- package/skills/workbench-module-diy/SKILL.md +42 -9
- package/skills/workbench-module-diy/STEPS.md +1 -1
- package/skills/workbench-module-diy/TOOLING.md +10 -4
- package/skills/workbench-module-diy/TOOLS.md +56 -204
- package/skills/workbench-module-diy/WIRING.md +17 -8
- package/skills/workbench-module-diy/WRITES.md +131 -0
- package/dist/types/modules/panels/model/diy-act.d.ts +0 -4
- package/dist/types/modules/panels/model/diy-look.d.ts +0 -15
- package/dist/types/modules/panels/model/diy-net.d.ts +0 -3
- /package/dist/types/modules/panels/model/{diy-act.test.d.ts → diy-browser-act.test.d.ts} +0 -0
- /package/dist/types/modules/panels/model/{diy-look.test.d.ts → diy-browser-tools-shot.test.d.ts} +0 -0
- /package/dist/types/modules/panels/model/{diy-net.test.d.ts → diy-browser-tools.test.d.ts} +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# HOOKS:kit 里那些 hook 怎么用
|
|
2
|
+
|
|
3
|
+
**为什么有这份**:外壳与共享包的源码按"零注释"规矩写,emit 出来的 `<名字>.d.ts` 里**没有说明文字** ——
|
|
4
|
+
只有参数名与类型。组件的 prop 一般看得出来,hook 的语义看不出来(什么时候调、取数什么时候重来、
|
|
5
|
+
"在忙"为什么还要挂一会儿)。这份就是那些语义的出处。
|
|
6
|
+
**原语那 5 个 hook 不在这里**(`useAnchoredPosition` / `useAnchoredMaxHeight` / `useCodeHighlighter` /
|
|
7
|
+
`useDismissOnOutsidePointer` / `useModalLayer`):它们的 `.d.ts` 自带 JSDoc。
|
|
8
|
+
|
|
9
|
+
问清单、或要某一件的类型文件:`workbench_diy_components`(`name=useVisibleBusy`)。
|
|
10
|
+
|
|
11
|
+
## 0 两条路,别绕开
|
|
12
|
+
|
|
13
|
+
- **取数**:卡片里所有读都走 `useApiResource`。它自带三件事 —— 同 key 合流、事实通道订阅("这条变了"自动重取)、
|
|
14
|
+
把读数上报面板(`workbench_diy_panel` 看的就是它)。
|
|
15
|
+
- **状态**:`useVisibleBusy`(在忙 / 安定)+ `useCapabilityGate`(被能力挡住没)+ `useUnitBoard`(把两者与
|
|
16
|
+
卡片读数叠成一组)。**别自己 `useState` + `setTimeout` 造一套**:面板要的是同一套口径。
|
|
17
|
+
|
|
18
|
+
## 1 取数与事实
|
|
19
|
+
|
|
20
|
+
### `useApiResource(route, query, refreshToken, options?)` → `{ phase, value?, error?, at? }`
|
|
21
|
+
|
|
22
|
+
详见 [LINKAGE.md](LINKAGE.md) §8。`phase` 四态 `idle` / `loading` / `ready` / `error`;`value` 是
|
|
23
|
+
`ApiResponse<T>`(`ok` / `data`);`options`:`enabled`(前置条件不满足就别发)、`key`(自定义身份)、
|
|
24
|
+
`fact`(订哪条事实,默认 `factOfRoute(route)`)、`report: false`(不上报面板 —— 夹具 / 后台探针用)。
|
|
25
|
+
|
|
26
|
+
### `useFactRevision(fact)` → 版本号 | `useFactInvalidate()` → `(fact?) => void`
|
|
27
|
+
|
|
28
|
+
详见 [LINKAGE.md](LINKAGE.md) §8。前者是"这条事实的版本",喂给 `useApiResource`(默认订阅已经接好);
|
|
29
|
+
后者是**写完之后**把这条标脏 —— 不标,面板就停在旧值。
|
|
30
|
+
|
|
31
|
+
### `subscribeFactRevision(fact, listener)` → 退订函数
|
|
32
|
+
|
|
33
|
+
非 hook:给"不在渲染里"的地方用(宿主事件、定时器里看到的旧值)。返回值就是退订函数,**记得退订**。
|
|
34
|
+
渲染里要版本号用 `useFactRevision`,别自己 `useEffect` + `subscribe` 绕一圈。
|
|
35
|
+
|
|
36
|
+
### `resourceIdentity(route, query, override?)` | `readKeyOf(identity, refreshToken, factRev)`
|
|
37
|
+
|
|
38
|
+
都不是 hook:前者是"这次读的身份"(`route` + 排序后的 query;给了 `override` 就用它);后者把身份与两个
|
|
39
|
+
版本号拼成**合流的 key**。要自己管缓存时才用得上。
|
|
40
|
+
|
|
41
|
+
### `requestResource(route, query, key)` → `{ task, release }`
|
|
42
|
+
|
|
43
|
+
非 hook 的取数本体:**同 key 只发一次**(并发调用共享同一个 `task`),最后一个 `release()` 才 abort。
|
|
44
|
+
`useApiResource` 内部就是它 + 事实订阅 + 上报。用它就**一定要 `release`**(放 `finally`),否则那条请求永不取消。
|
|
45
|
+
|
|
46
|
+
## 2 忙与安定
|
|
47
|
+
|
|
48
|
+
### `useVisibleBusy(active, floorMs)` → `{ busy, settling }`
|
|
49
|
+
|
|
50
|
+
**最短可见时长**:`active` 变真立刻 `busy`;变假时若这次忙还没到 `floorMs`,`busy` 先挂着,这期间
|
|
51
|
+
`settling: true`("读数在回来的路上")。`floorMs` 传共享包的 `MIN_BUSY_MS`,别自己编。
|
|
52
|
+
为什么:取数几毫秒就回来时不挂一会儿,用户看到的是"闪一下",比不转还难读(ADR-0022)。
|
|
53
|
+
|
|
54
|
+
### `busyView(active, visible)` | `heldPhase(phase, refreshing)`
|
|
55
|
+
|
|
56
|
+
两个纯函数(不是 hook,好测):`busyView` 是"真忙还是在收尾"的投影,`useVisibleBusy` 用的就是它;
|
|
57
|
+
`heldPhase` 把"正在刷新"折进 `phase`(`refreshing && phase === 'ready'` ⇒ `'loading'`),
|
|
58
|
+
让"旧值还显示着、新的在路上"这种态有名字。
|
|
59
|
+
|
|
60
|
+
## 3 卡片那一格与能力门
|
|
61
|
+
|
|
62
|
+
### `useUnitBoard({ unit, gate, refreshToken, onRefresh, onSummary, read, t, enabled?, platformBlocker? })`
|
|
63
|
+
|
|
64
|
+
**卡片起步就用它**:把"gate 挡着就不读"、忙 / 安定、探针(被能力挡住或读数报错时自动 `forceProbe`)、
|
|
65
|
+
`useCardSummary`(把 `{ refreshing, at }` 交给调用点上报面板)、以及 blocker 的拆分
|
|
66
|
+
(`capabilityBlocker` / `errorBlocker`)一次叠好。`read(gated)` 是你自己的读数函数(里面调 `useApiResource`);
|
|
67
|
+
返回 `feed`(含 `state`)、`capability`、`gated`、`refresh`、`forceProbe`、`summary` 与那几个 blocker 字段。
|
|
68
|
+
|
|
69
|
+
### `useCapabilityGate(unit, refreshToken, options?)` → `{ capability, gated, forceProbe }`
|
|
70
|
+
|
|
71
|
+
这一格的**能力**(`design.json` 里声明的那份)与"挡住了没"。`options.mode` 换门的口径(默认 `'capability'`),
|
|
72
|
+
`options.fixture` 给夹具。`forceProbe()` 是"现在就去探一次"。
|
|
73
|
+
|
|
74
|
+
### `useCapabilityMap(refreshToken)` → `{ capabilities, checkedAt, checking, forceProbe, reload }`
|
|
75
|
+
|
|
76
|
+
**全部**单元的能力表(不是一格):设置页 / 体检那一类界面用。`checking` 是探针在跑,`reload()` 重取整张表。
|
|
77
|
+
|
|
78
|
+
### `useCardSummary(onSummary, summary)`
|
|
79
|
+
|
|
80
|
+
把 `{ refreshing, at }` 在变化时交给调用点(`onSummary` 要**稳定引用**,一般 `useCallback`)——
|
|
81
|
+
面板那行"最近一次读是什么时候"就是这么上去的。`useUnitBoard` 已经接好;自己搭卡片才直接用。
|
|
82
|
+
|
|
83
|
+
## 4 写与长任务
|
|
84
|
+
|
|
85
|
+
### `useHostWrite({ channel, resource?, networkText })` → `{ busy, failed, detail, send, fail, clear }`
|
|
86
|
+
|
|
87
|
+
详见 [PANEL.md](PANEL.md) §5(写通道与配置持久化)。`send(body, { silent })` 回 `WriteOutcome`
|
|
88
|
+
(`ok` / 人话 message / detail),失败自动落到 `failed` + `detail`(界面直接显示,别自己拼);`silent` 是不打扰;
|
|
89
|
+
`fail` 是"本地就知道不行"时自己塞一条。**写只走这条**:别在组件里直接 `fetch`。
|
|
90
|
+
|
|
91
|
+
### `useLongTask({ unit, workspace, mocked?, enabled?, pollMs? })` → `{ runs }`
|
|
92
|
+
|
|
93
|
+
详见 [TOOLS.md](TOOLS.md) §9。轮询这一单元的长任务:有在跑的(`running` / `needs-login`)按
|
|
94
|
+
`longTaskPollMs` 快档轮,空闲慢档;`workspace` 为 null 或 `mocked` 时不轮。画用 `<LongTaskRow …/>`,
|
|
95
|
+
点用 `longTaskDot(status)`。
|
|
@@ -70,9 +70,7 @@ h(Rows, null,
|
|
|
70
70
|
**`LongTaskRow`** —— 长任务那一行:`status`(四态)+ `headline` + `problem` + `links` + `steps` +
|
|
71
71
|
`raw`。跑得久的事(发布、扫描、迁移)都用它,别自己画进度。
|
|
72
72
|
|
|
73
|
-
**`ApprovalDetail` / `ApprovalPlan`** ——
|
|
74
|
-
|
|
75
|
-
## 4 表单
|
|
73
|
+
**`ApprovalDetail` / `ApprovalPlan`** —— 审批屏那两格(写操作才要点头);口径与那张 ASCII 见 [DESIGN.md](DESIGN.md) §3 末节。## 4 表单
|
|
76
74
|
|
|
77
75
|
**`Field`** —— 标签 + 输入的排布(`label` / `hint`),输入框放里面。
|
|
78
76
|
|
|
@@ -85,6 +83,8 @@ h(Field, { label: t('myunit.mode'), hint: t('myunit.modeHint') },
|
|
|
85
83
|
**`ChoiceGroup`** —— 单选 / 多选(`multi` 区分;`value` 或 `values` + `onChange`;选项 `{ value, label, description? }`;`disabled` 在整组上)。
|
|
86
84
|
**`SelectField`** —— 下拉:默认是**常规下拉框**(跟 `TextField` 一套),无边框那种给 `variant: 'ghost'`;
|
|
87
85
|
搜索、分组、禁用项、加载态、空态、页脚都是它的 prop。
|
|
86
|
+
(它是 dsh 那套按钮 + portal 菜单,**不是原生 `<select>`** —— 验收时 agent 要驱动它得走两拍:
|
|
87
|
+
卡内点触发器 → `scope: page` + `--role menuitem` 按名字点;`workbench_diy_select` 那把工具已经不在了。)
|
|
88
88
|
**`SaveBar`** —— 设置面底下那条"取消 / 保存"(`busy` / `disabled` / `labels` / `onCancel` / `onSave`)。
|
|
89
89
|
**`FormActions`** —— **操作区**(一组按钮):`actions: { key, label, kind?: 'default' | 'danger' | 'primary', onClick, disabled?, busy? }[]`。
|
|
90
90
|
**顺序 / 间距 / 换行都归它** —— `primary` 永远在右、`danger` 紧挨它左侧、一个区只许一颗 `primary`、
|
|
@@ -182,6 +182,18 @@ h('div', { className: styles.form }, …) // ← .form { gap:
|
|
|
182
182
|
|
|
183
183
|
## 8 kit 里没有、但你确定要标准控件
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
**先问工具**:DIY 会话里调 `workbench_diy_components`(不填给清单与类型面根;`name=Button`
|
|
186
|
+
直接回它的类型文件与那份声明)。工程域用 `dsh-wb-module check --only primitives`
|
|
187
|
+
(`--all` 全表 / `--json` 机器档)—— 分桶导出 + 与 kit 求交(`kit 里已有` 的优先用)+
|
|
188
|
+
该写进 `package.json` 的那两行。骨架默认已备好。
|
|
189
|
+
**要看某一件的 prop / 用法**:`--file <名字>` 给绝对路径(`kit` 那条同形:
|
|
190
|
+
`check --only kit --file Card`),`--files | grep <名字>` 出全表 —— 每件都有一个同名(或就近)的 `.d.ts`。
|
|
191
|
+
|
|
192
|
+
```jsonc
|
|
193
|
+
"dsh": { "client": { "external": [ "@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives" ] } },
|
|
194
|
+
"devDependencies": { "@deepseek-ai/dsh-client-ui-primitives": "<外壳钉的那版,精确版>" }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
改完 `pnpm install` 一次(不装 `pnpm typecheck` 报"找不到模块")。三条会红的边界:只在浏览器侧引
|
|
198
|
+
(宿主侧碰它 `source` 判红);版本对齐外壳钉的那份(`init` 盖的就是它,别用 `^`);能拿 kit 的别引。
|
|
199
|
+
宿主侧的契约按需自己声明([PANEL.md](PANEL.md) §9)。
|
|
@@ -197,11 +197,15 @@ agent 那一侧也要能通过模块自己的能力读到同一份偏好。
|
|
|
197
197
|
## 9 界面边界:kit 不够用时怎么办
|
|
198
198
|
|
|
199
199
|
**先从 kit 拿**(`@dsh-workbench/core/client`,`check --only kit` 会列全这份外壳有什么)。
|
|
200
|
-
kit
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
*
|
|
204
|
-
*
|
|
200
|
+
kit 里确实没有、而你要的又是标准控件(按钮、选择器、设置区块那种)时,可以引 dsh 的客户端原语 ——
|
|
201
|
+
**有哪些**问 `dsh-wb-module check --only primitives`(它还会把"kit 里已有"的那批挑出来):
|
|
202
|
+
|
|
203
|
+
* 只在**浏览器侧**引(`src/client.tsx` 与 `src/components/**` 那一侧);
|
|
204
|
+
* 在 `package.json` 的 `dsh.client.external` 里**声明** `@deepseek-ai/dsh-client-ui-primitives`,
|
|
205
|
+
同时把 `devDependencies` 里那行**精确版**对齐到外壳钉的那版(骨架默认已经两处都写好了;
|
|
206
|
+
声明完记得 `pnpm install`,否则 `pnpm typecheck` 找不到它);
|
|
207
|
+
* 宿主侧(`src/host.ts`)**一个字都不许碰** —— `check` 的源码边界判据会红;宿主侧 import
|
|
208
|
+
`src/components/**` 还会被 `arch` 按跨运行时(S23)判红 —— 组件整片算 client 面。
|
|
205
209
|
|
|
206
210
|
理由不是"能省事":装机成熟模块里有一半的界面是这么搭的。但**能拿 kit 的时候别自己画** —— kit 那套跟着外壳一起换主题、跟着语言走,
|
|
207
211
|
自己画的不会。
|
|
@@ -1,60 +1,128 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
跑 `dsh-wb-module check`(或单跑一条 `--only <名字>`)。
|
|
4
|
-
**每一条红的都在讲一件真出过事的事**,不是形式主义。红了就照下面修,别抛给用户。
|
|
5
|
-
|
|
6
|
-
| 判据 | 它防的事故 | 红了的典型说法 | 怎么修 |
|
|
7
|
-
|---|---|---|---|
|
|
8
|
-
| `contracts` | dsh 组合阶段**不校验**你 `package.json` 里的声明 —— 错的 `external` 要到浏览器 `require` 那一刻才炸;清单与代码、底稿与清单之间漂了也没人拦 | `单元 id "x" 与 … 撞了` / `dsh.workbenchFaces.client 指的文件不存在` / `dsh.workbenchFaces.host 没声明` / `没有对 @dsh-workbench/core 的 peer 依赖` / `没有 typecheck 脚本` / `它不是 private,却既没声明 publishConfig.access=public…` / `src/declared-tools.ts 与清单对不上(生成物过期…)` / `工具名的字面量又写回了源码` / `…没有 key` / `dsh.workbench 声明的能力与代码真正注册的对不上` / `design.json 的能力行里有「x」,清单的 tools[] 里却没有它` | 按报的那一条改 `package.json`(生成物对不上就跑 `dsh-wb-module sync`;工具名的字面量只许留在清单里);`unit` 换个不撞的名字;`dsh.workbenchFaces` 补上两个**源码**入口;四个脚本补全;私有不发就保持 `private: true`。**工具只声明在清单里**(`dsh.workbench.tools`)且必须与 `mount()` 里 `registerTools()` 真正注册的**不重不漏**;写了 `design.json` 的话,它的 `capabilities[].name` 与清单的 `tools[].name` 也要不重不漏(**键集合对账**,只对名字 —— 内容各写一处);`gate.deniedTools` 是可选的运行期收窄 |
|
|
9
|
-
| `protocol` | `dsh.workbench` 是外壳读你得懂的那份声明:缺版本、形状不对、**多一个键**、枚举值不认都要在装机前拦下来。多出来的键最阴 —— 外壳会**静默忽略**它(拼错一个键 = 那句话白写) | `没有 apiVersion` / `apiVersion 是 99,这份只认 1` / `声明了 apiVersion 就要把静态半写全 —— 缺 dsh.workbench.tools` / `dsh.workbench 里有协议 v1 不认识的键 "faces"` / `card 里有不认的键 "enabledByDefault"` / `tools[0] 里有协议不认的键 "scopes"` / `tools[0] 里有不认的键 "scope"` | 补 `"apiVersion": 1`;把 `unit` / `card` / `tools` / `host` 四个键写全(没卡片写 `"card": null`、没工具写 `"tools": []`、一个服务都不要写 `"host": { "inject": [] }`);删掉协议外的键(`faces` 住在 `dsh.workbenchFaces`,写回 `dsh.workbench` 里就是生字段);`scope` **不要写**。字段清单见 `dsh-wb-module` 的 README「协议」那一节 |
|
|
10
|
-
| `bundles` | 两个运行时互串:浏览器产物里带 node 内置、宿主产物里带 DOM / react | `浏览器产物里出现了 node 内置 …` / `宿主产物里出现了 react` | 宿主面别 import 浏览器侧的东西;浏览器面别 import `node:*`。两侧只通过服务名相接 |
|
|
11
|
-
| `source` | 非种子词的 `@deepseek-ai/*`、宿主侧碰 dsh 的浏览器包、`@/` 指到工作台源码 | `…在宿主侧可达` / `不是平台种子词` | 宿主侧只用 Node 侧的东西;dsh 原语一律走 `kit`;`@/` 只指自己的 `src/` |
|
|
12
|
-
| `primitives` | 声明了 `external` 却没有产物物证 ⇒ dsh 原语被**内联**成第二份实例 | `…用了却没声明 external` / `宿主产物里出现了` | 在 `dsh.client.external` 里显式声明它;确认产物里是 `require(...)` 而不是被打包进来 |
|
|
13
|
-
| `kit` | 用了 peer 下限那份外壳**还没有**的 kit 成员 ⇒ 卡片首帧 `Element type is invalid`,整块面板被 React 卸掉 | `kit.components.X 在这份外壳里没有` / `peer 下限比 devDependency 还旧` | 只用 peer 范围里真有的成员;把 `peerDependencies` 的下限提到你实际用的那档 |
|
|
14
|
-
| `cards` | 卡片在某一种**外壳侧输入**下崩 / 空白 / 变脏 ⇒ 用户看到"卡片一直不出来 / 点一下白屏 / 屏上冒出 `undefined`"。判据拿七种输入各喂一遍(待机 / 在读 / 在读但保留旧值 / 有值 / 有值但空容器 / 读不到 / 能力闸缺插件),`zh`、`en` 各一遍;取数走**你装的那份真壳**的 `useUnitBoard` | `loading(zh) 卡片抛了` / `loading(zh) 卡片返回空` / `屏上出现了 undefined`(`NaN` / `[object Object]` / `null` 同形) / `t('x') 在 zh 里没有落点` / `卡片没套外壳的卡壳` / `清单的 card.titleKey「x」在模块字典的 en 里没有` | 让卡片在五态下都画得出来(已有的加载 / 空 / 阻断态);外面套 `kit.components.Card`;清单写了 `titleKey`,`registerUnit` 交的 `messages` 里 `zh` / `en` 都得有那一条;**卡片与它用的 kit 组件问过的每个键也都要有落点**(判据自己当 `t`,没命中就报原始键名)。判据**不断言**"画的是哪一态那一屏" —— 那归你自己那份 `plugin.test.ts` |
|
|
15
|
-
| `standalone` | "只有工作台源码在场才编得过" ⇒ 发布出去就散架 | `build 在外壳源码不在场时失败` / `typecheck 在外壳源码不在场时失败` | 别引用工作台源码;`tsconfig` 里 `paths` 的值必须是**相对路径**(`"./src/*"`,不是 `"src/*"`) |
|
|
16
|
-
| `markers` | 内网域名 / 企业内部名泄进要公开发布的包 | `命中内网 npm 源` / `命中内网域名` | 那些值不该写进包里;要配就落到模块自己的配置面,或走用户自己声明的来源 |
|
|
17
|
-
| `alias` | `@/` 伸进工作台源码 ⇒ 装的人手上没有那份源码 | `…指向外壳源码` | `@/` 只许指自己的 `src/`;跨包的东西走包名 |
|
|
18
|
-
| `style` | 模块定义 `.wb-*` 去覆盖外壳的类(全局类名,两份规则按注入顺序打架) | `定义了外壳前缀 wb-* 的 CSS 规则` / `借用了契约里没有的类` | 自己的样式走 `*.module.css`(类名**不带** `wb-` 前缀,构建时按文件路径哈希);外壳给的类只能用契约里列出的那些,要借就写 `:global(.wb-…)`;被 `样式归属` 判红的手写「标签+控件」行或模块自己管的行距 ⇒ 改用 `FormRow(s)`([KIT.md](KIT.md) §5) |
|
|
1
|
+
# 门禁红了怎么办:十三条判据的人话对照表
|
|
19
2
|
|
|
20
|
-
|
|
3
|
+
跑 `dsh-wb-module check`(或单跑一条 `--only <名字>`)。**每条红都在讲一件真出过事的事**;红了照下表修,别抛给用户。
|
|
4
|
+
|
|
5
|
+
| 判据 | 它防什么 | 怎么修 |
|
|
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-*` 覆盖外壳类;手写「标签+控件」行 / 操作区 | 样式走 `*.module.css`(不带 `wb-` 前缀);借外壳类写 `:global(.wb-…)`;行与操作区用 kit 的 `FormRow(s)` / `FormActions`([KIT.md](KIT.md) §5) |
|
|
18
|
+
| `arch` | 结构退化(巨文件 / 反向依赖 / 跨运行时 / 表外目录 / 模型层没测试) | 见「第十二条 `arch`」 |
|
|
19
|
+
| `lint` | 单文件语法卫生(`as any` / 残留 `console`、`debugger` / 内联颜色、`!important` / react-hooks) | 见「第十三条 `lint`」 |
|
|
20
|
+
|
|
21
|
+
## 第十二条 `arch`:模块自己的分层形状
|
|
22
|
+
|
|
23
|
+
**先分清跑没跑**:只跑带 `arch.config.mjs` 的模块;没有那份文件 = `○ 不适用`(skip),**不是红**。上车就在包根加一行工厂:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { moduleArch } from '@dsh-workbench/module-tools/arch';
|
|
27
|
+
export default moduleArch();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**怎么读**:`dsh-wb-module check --only arch`。通过长这样(数字随模块走):
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
✔ 模块架构(分层 / 边界 / 设计系统) 1/1 个模块包带 arch.config.mjs · 7 个源文件 · 58 条规则在跑
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
红了逐条是 `<包名>: <文件>:<行> <说明>(<规则>) —— <hint>`;括号里那个规则号是下表的键;只有 `error` 挡门禁(`warn` 只提示);配置自己跑不起来(import / 语法错)也判红。
|
|
37
|
+
|
|
38
|
+
| 常见红(规则) | 它在说什么 | 怎么修 |
|
|
39
|
+
|---|---|---|
|
|
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` 里定义了没人用的类 | 删掉 |
|
|
48
|
+
|
|
49
|
+
要放宽**从参数进**(别改判据):`moduleArch({ fileLines, deps, layers, roles, structure, exceptions })`,`exceptions` 每条**必须写 `reason`**(后缀 / 单文件都行,glob 是 picomatch):
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
export default moduleArch({
|
|
53
|
+
layers: { host: 10 },
|
|
54
|
+
roles: [{ id: 'lib:platform', pattern: 'src/platform/**', layer: 1 }],
|
|
55
|
+
exceptions: [{ rule: 'S16', glob: 'src/host/service.ts', reason: '…为什么这一个该长…' }],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
⚠️ 层序表与两个面(`dsh.workbenchFaces` 指的那两个文件)不是可调项 —— 改了先红 `contracts`/`source`(换面要整体替换 `structure` 并改清单)。内置已有三条豁免,不必重复声明。
|
|
60
|
+
|
|
61
|
+
## 第十三条 `lint`:模块自己的语法卫生
|
|
62
|
+
|
|
63
|
+
`arch` 管**文件之间**的结构,`lint` 管**文件之内**的写法。
|
|
21
64
|
|
|
22
|
-
|
|
65
|
+
**先分清跑没跑**:只跑带 `eslint.config.mjs` 的模块;没有 = 汇总行里 `○ 模块代码卫生(eslint):(不适用)…`(skip),**不是红**。上车就在模块根放一份:
|
|
23
66
|
|
|
24
|
-
|
|
67
|
+
```js
|
|
68
|
+
import { moduleLint } from '@dsh-workbench/module-tools/lint';
|
|
69
|
+
export default moduleLint({ rootDir: import.meta.dirname });
|
|
70
|
+
```
|
|
25
71
|
|
|
26
|
-
|
|
72
|
+
`rootDir` 把 typescript-eslint 的 `tsconfigRootDir` 钉在这个模块上 —— 本仓根上还有一份配置,多份并存时不传就猜不出来(每个文件报 `No tsconfigRootDir was set…`),所以模板与骨架都带着它。
|
|
27
73
|
|
|
28
|
-
|
|
74
|
+
**怎么读**:`dsh-wb-module check --only lint`。通过长这样(部分模块上车时多一句 `其余 N 个没有 eslint.config.mjs(不适用)`):
|
|
29
75
|
|
|
30
|
-
|
|
31
|
-
|
|
76
|
+
```
|
|
77
|
+
✔ 模块代码卫生(eslint):1/1 个模块包带 eslint.config.mjs · 8 个文件 · 0 error · 0 warn
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
红了逐条是 `名字: 文件:行:列 规则 —— 一句话`(压成单行、截 200 字)。**只有 `error` 算红**,`warn` 只计数(单跑时列在 `warn(不挡门禁):` 下);聚合最多列 40 条,`--only lint` 列全;配置跑不起来也判红(同 `arch`)。
|
|
81
|
+
|
|
82
|
+
| 常见红(规则) | 它在说什么 | 怎么修 |
|
|
83
|
+
|---|---|---|
|
|
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 的原话修 |
|
|
91
|
+
|
|
92
|
+
要放宽**从命名参数进**(`ignores`/`rules`);按目录细调就展开再追一块:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
export default [
|
|
96
|
+
...moduleLint({ rootDir: import.meta.dirname, rules: { 'no-console': 'off' } }),
|
|
97
|
+
{ files: ['src/legacy/**'], rules: { '@typescript-eslint/no-explicit-any': 'off' } },
|
|
98
|
+
];
|
|
99
|
+
```
|
|
100
|
+
|
|
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` 出来、一行没改却红了** —— 那是模板 / 工具坏了,不是你的。
|
|
108
|
+
|
|
109
|
+
## 四条"不是判据、但更常踩"的
|
|
110
|
+
|
|
111
|
+
(细节在 [WIRING.md](WIRING.md) / [BUILD.md](BUILD.md))
|
|
112
|
+
|
|
113
|
+
1. **装上去没反应** → 先问 `workbench_diy_status`(`unit=…`)。[WIRING.md](WIRING.md) §3
|
|
114
|
+
2. **改完没热换** → 宿主侧秒级热换;客户端刷新页面。[WIRING.md](WIRING.md) §4
|
|
32
115
|
3. **"再装一遍"** → 日常迭代只重建、不重装。[WIRING.md](WIRING.md) §3
|
|
116
|
+
4. **引了 dsh 原语却 `tsc` 报"找不到模块"** → 两处声明 + `pnpm install`([KIT.md](KIT.md) §8)。
|
|
33
117
|
|
|
34
118
|
## 用户说"卡片不对"时:四步取证
|
|
35
119
|
|
|
36
|
-
他说"一片空白 / 一直转圈 / 数字不对"
|
|
37
|
-
|
|
38
|
-
1. **`workbench_diy_panel
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
3. **自己把那条读跑一遍**:卡片画的就是模块自己那条宿主路由的返回值 ——
|
|
44
|
-
`curl '<工作台地址>/workbench/api/<unit>/<route>'`(地址就是你起工作台那个,默认 `http://127.0.0.1:3081`)。
|
|
45
|
-
接口回对了而卡片不对,问题在渲染那一侧;接口就回错,问题在宿主半侧(回头照着 [DATASOURCE.md](DATASOURCE.md) 重探一次)。
|
|
46
|
-
要的是**浏览器里那一次**的原文(带凭据的那种),用 `workbench_diy_probe`(`script` 里 `fetch` 自己那条读)。
|
|
47
|
-
4. **最后才看用户那一页**:要这一页的**控制台**,走三拍 —— `workbench_diy_logs_watch`(装上带钩子的收听)→
|
|
48
|
-
做那件你怀疑会报错的事 → `workbench_diy_logs_read`(最近 50 条,读完 `workbench_diy_logs_stop` 卸掉)。
|
|
49
|
-
钩子有上限(每页 200 条、单条截 400 字、随刷新消失),不会把用户那一页吃胖。要在页面里跑一段自己的代码
|
|
50
|
-
(读接口 / 抄一个 DOM 值),用 `workbench_diy_probe`。**别用 `opencli browser <会话> console`** ——
|
|
51
|
-
那条命令拿到的是个 stub(恒回空数组、还 exit 0),看上去像"没有报错"。
|
|
52
|
-
要看这一页是什么、有哪些可点的,用 `state`;要一张图用 `screenshot <路径>`。
|
|
53
|
-
宿主侧热换 / 装载的报错在**起工作台那个终端**里,不在浏览器里。
|
|
54
|
-
|
|
55
|
-
两条纪律:**别让用户截图或替你转述报错**(第 1 步就是干这个的);**截图与 probe 只用来定位,不用来验收** ——
|
|
56
|
-
验收仍是他对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」。
|
|
57
|
-
|
|
58
|
-
面板运行态是**用户那一页自己报上来的**(页面关了就停在最后一次,带时间与新鲜度),所以第 1 步没有内容时,
|
|
59
|
-
先让他把工作台切到工坊那个工作区、刷新一次页面再看。
|
|
120
|
+
他说"一片空白 / 一直转圈 / 数字不对"时,**先取证再改代码**;前一步有结论就停在那儿修:
|
|
121
|
+
|
|
122
|
+
1. **`workbench_diy_panel`** —— 这个工作区面板现在什么样:哪几张卡、每张最后一次取数是读到了还是读不到(带宿主回的码与原文)、崩过没(原因 + 次数)。有答案就别往下走。
|
|
123
|
+
1.5 **看这一张卡**(`workbench_diy_screenshot` / `workbench_diy_html` / `workbench_diy_find` / `workbench_diy_eval`)—— 打的是**用户当前那一页**上工坊那块板里这张卡(`unit=…`):`screenshot` 给图(要卡那一块先量盒子、填 `clip`)、`html --as json` 给结构树、`find` 给元素与 `ref`、`eval` 算派生读数(对比度 / 可见文本;`in: 'card'` 注入卡根 `root`,脚本自己 `return`)。配合上一步把"看起来不对"说成"哪一处不对"。**这一族(浏览器那 14 把 + `logs_*`)要先 `workbench_diy_debug_start`**(`unit=…`;会请用户点头一次)—— 没有这场约定一律当场拒;不看屏先 `_pause`,收工 `_stop`。(`_panel` / `_status` / `_probe` 不碰他那一屏,不用开。)
|
|
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_probe`(`script` 里 `fetch` 自己那条读)。
|
|
126
|
+
4. **最后才看用户那一页**:要控制台走三拍 —— `workbench_diy_logs_watch` → 做那件你怀疑会报错的事 → `workbench_diy_logs_read`(读完 `workbench_diy_logs_stop` 卸掉;钩子上限:每页 200 条、单条 400 字、随刷新消失)。**别用 `opencli browser <会话> console`** —— 那是个 stub(恒回空数组、还 exit 0)。要在页面里跑自己的代码用 `workbench_diy_probe`(**读会话**那页);看**用户那一页**是什么、有哪些可点的用 `workbench_diy_state`。宿主侧热换 / 装载的报错在**起工作台那个终端**里。
|
|
60
127
|
|
|
128
|
+
两条纪律:**别让用户截图或替你转述报错**;**验收就是你自己拿这一族走** —— 逐条对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」,`screenshot`/`html`/`find`/`eval` 的读数、动作族的返回、`workbench_diy_network` 的收发包、`logs_*` 的报错都算证据;走通再 `design accept`,走不通的如实说没验过(用户不必在场,也不用请他逐条点头)。工具参数与返回体都是 opencli 的原样(见 [FAMILY.md](FAMILY.md))。面板运行态是**用户那一页自己报上来的**,第 1 步没有内容时,先让他把工作台切到工坊那个工作区、刷新一次页面再看。
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: workbench-module-diy
|
|
3
|
-
description: 在工作台里凭需求造一个模块(面板上的一块内容 + 它背后的能力)—— 用在「我想在面板上看到 X / 加一块卡」「让 agent 替我读或写某个东西」「agent 动完面板哪一格跟着变」这类话上。用户只说需求,实现由 agent
|
|
3
|
+
description: 在工作台里凭需求造一个模块(面板上的一块内容 + 它背后的能力)—— 用在「我想在面板上看到 X / 加一块卡」「让 agent 替我读或写某个东西」「agent 动完面板哪一格跟着变」这类话上。用户只说需求,实现由 agent 自己做、自己过门禁、自己在真卡片上逐条验过,最后给他一个能点开看的东西。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# 造一个工作台模块:他说需求,你做完自己验过再交给他
|
|
7
7
|
|
|
8
8
|
交付物是**面板上能点开的一块内容**,不是一份要他读的技术方案。
|
|
9
9
|
|
|
@@ -16,23 +16,50 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
16
16
|
3. **先定数据从哪来** —— [DATASOURCE.md](DATASOURCE.md):干净机器上没有现成 skill 可抄,
|
|
17
17
|
用 `workbench_diy_probe`(`url=…, script=…`)复用他的登录去探 —— 两拍、重试、失败码归一
|
|
18
18
|
都在那把工具里;探明后**固化成一条读**(`@dsh-workbench/shared/host` 的 `browserRead`)。顺手把三态写掉。
|
|
19
|
+
要 agent **真去改东西**(提交 / 发布 / 建单 / 改配置)的,读完 [WRITES.md](WRITES.md) 再动手:
|
|
20
|
+
四步(现成写命令 → 入参 / 出参来源 → 没有预览档的不许试写 → 写完后回读确认)。
|
|
21
|
+
有写工具就**顺手把审批屏那一屏也画进图里**([DESIGN.md](DESIGN.md) §3 末尾那节 + KIT.md §3):
|
|
22
|
+
标题一句 + `ApprovalPlan` 详情表 —— 那两格的形状是固定的(仓内 ADR-0065,这份指引不链仓内路径)。
|
|
19
23
|
4. **从模板改** —— [BUILD.md](BUILD.md) §1–§2:在工坊里 `dsh-wb-module init` 一个骨架,
|
|
20
24
|
把 `design.json` 里的**三张表与数据源记录先改成你问出来的那份**(`design check` 会点名叫出还空着哪一格),
|
|
21
25
|
再改数据源与渲染。
|
|
22
26
|
**不从零写**。
|
|
23
27
|
命令写成 `pnpm exec dsh-wb-module …`(工坊里 PATH 上也有同名入口,但 `pnpm exec` 到处都对)。
|
|
28
|
+
`init` 完先在工坊里跑一次 `pnpm install`:模板默认带了 dsh 原语那个 devDependency
|
|
29
|
+
([KIT.md](KIT.md) §8),不装 `pnpm typecheck` 会报"找不到模块"。
|
|
24
30
|
5. **自己过门禁** —— `dsh-wb-module build` + `check` + `design check`;
|
|
25
31
|
红了自己修([REVIEW.md](REVIEW.md) 有对照),不抛给他、也不贴报错给他看。
|
|
26
|
-
6.
|
|
27
|
-
|
|
32
|
+
6. **装上、上板** —— `workbench_diy_install`(`unit=<单元 id>`)装上:
|
|
33
|
+
它只认工坊里造好的那个模块,装完顺手在**这个项目**的面板上打开(装的是这台机器上的东西;**这一步不问审批**,见 [WIRING.md](WIRING.md) §3 —— 别对他说"请你点一下确认",那一拍不会出现)。
|
|
28
34
|
**这一跳永远只走一次**:之后只重建、不重装(宿主侧秒级热换;改了 client.js 刷新一下页面)。
|
|
29
35
|
拿不准它现在走到哪一步(装了没 / 面板认得没 / 这个项目上开着没 / 产物齐不齐 / 源码比产物新没),
|
|
30
36
|
就问工具:`workbench_diy_status`(`unit=<单元 id>`)会把这几件事一次说清并给出下一步 ——
|
|
31
37
|
**别自己拼,也别再装一遍**;已经装过只是想上板,用 `workbench_diy_show`。
|
|
32
|
-
7.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
7. **自己验收** —— 逐条对着三张表,在**真卡片**上走「打开哪 → 看什么 → 点什么 → 应该看到什么」。
|
|
39
|
+
要看 / 动**他那一屏**(浏览器那 14 把 + `logs_*`)得先开约定:
|
|
40
|
+
`workbench_diy_debug_start`(`unit=…`;会请他点头一次,他会被告知「调试中请留在那一屏」)——
|
|
41
|
+
**没有这场约定,上面那一族一律当场拒**;不看屏了先 `workbench_diy_debug_pause`,收工 `…_stop`。
|
|
42
|
+
(`workbench_diy_panel` / `_status` / `_probe` 不碰他那一屏,不用开。)
|
|
43
|
+
走法(全表与参数在 [FAMILY.md](FAMILY.md)):
|
|
44
|
+
- **这一张卡长什么样**:`workbench_diy_screenshot`(要卡那一块就先 `eval` 量盒子、填 `clip`)+
|
|
45
|
+
`workbench_diy_html`(`--as json` 给结构树);
|
|
46
|
+
- **元素与语义**:`workbench_diy_find`(`--css '$card …'`、或 `--role` / `--name`)、`workbench_diy_state`;
|
|
47
|
+
- **动手**:`workbench_diy_click` / `_hover` / `_type` / `_fill` / `_keys` / `_scroll` / `_wait`。
|
|
48
|
+
吃 CSS 的那几把有 `scope`(默认 `page`,`card` 才是这张卡里);portal 出来的那一层(下拉 / 菜单 /
|
|
49
|
+
模态)要用 `scope: 'page'` + `--role menuitem` 按名字点;
|
|
50
|
+
- **派生读数**(对比度 / 被裁 / 可见文本):`workbench_diy_eval`(`in: 'card'` 注入卡根 `root`,脚本自己 `return`);
|
|
51
|
+
- **那一次读的收发包**:`workbench_diy_network`(`--follow` 臂上 → 做那件事 → `--detail <key>` 取 body);
|
|
52
|
+
- **控制台**:`workbench_diy_logs_watch` → 做那件事 → `workbench_diy_logs_read`。
|
|
53
|
+
|
|
54
|
+
这些工具的参数**原样转发**给 opencli、返回体也是它的原样 —— 不知道某个子命令收什么,就
|
|
55
|
+
`args: ['<verb>', '--help']` 问它;事实在 `stdout` 里(`ok` 只管进程)。
|
|
56
|
+
哪一条不对,**先取证再改**(`workbench_diy_panel` 一次看清面板上每张卡是读到了、读不到还是崩了;
|
|
57
|
+
四步取证见 [REVIEW.md](REVIEW.md)),改完再走**同一处**。
|
|
58
|
+
逐条都走通了,再 `dsh-wb-module design accept` 把验收打勾(**没走通的不许打勾**;走不通的如实说没验过)。
|
|
59
|
+
审批那一格是**他的**:模块里要用户点头的写能力,得他真用一次时点 —— 你代不了,清单里写清
|
|
60
|
+
「这条要你真用一次」,别打勾说验过。
|
|
61
|
+
验完把结论告诉他:**装好了、面板上哪一格**(不用请他逐条点头 —— 他随时能自己点开看;
|
|
62
|
+
他说哪不对,回到同一处取证再改)。不给他看 diff、日志、代码。
|
|
36
63
|
|
|
37
64
|
## 每个问题都带推荐答案
|
|
38
65
|
|
|
@@ -47,9 +74,13 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
47
74
|
| 两条分工、三张表、怎么用 ASCII 跟他说话 | [DESIGN.md](DESIGN.md) |
|
|
48
75
|
| 步骤 1 / 2 / 3 各自问什么、什么时候算做完 | [STEPS.md](STEPS.md) |
|
|
49
76
|
| **数据从哪来**(干净机器上怎么找到那一条读) | [DATASOURCE.md](DATASOURCE.md) |
|
|
77
|
+
| **要 agent 真去改东西**(提交 / 发布 / 建单 / 改配置:写通道四步) | [WRITES.md](WRITES.md) |
|
|
50
78
|
| 面板上该有什么、状态矩阵怎么写、界面习惯 | [PANEL.md](PANEL.md) |
|
|
51
79
|
| **要做什么 → 用哪件 kit → 最小写法**(照抄面) | [KIT.md](KIT.md) |
|
|
80
|
+
| **kit / dsh 原语里到底有什么、某一件怎么用**(DIY 会话里直接调这把工具) | `workbench_diy_components`(`name=Button` 回它的类型文件与声明;工程域用 `check --only kit` · `--only primitives`)· [KIT.md](KIT.md) §8 |
|
|
81
|
+
| **kit 那些 hook 怎么用**(什么时候调、取数什么时候重来、"在忙"为什么还要挂一会儿) | [HOOKS.md](HOOKS.md) |
|
|
52
82
|
| 能力怎么划、传什么拿回什么、哪几件要点头 | [TOOLS.md](TOOLS.md) |
|
|
83
|
+
| **工坊会话里那 33 把的全表**(枚举 opencli 的 14 把 + 看/装/探 + 调试约定 + 录制交接、参数与如实边界) | [FAMILY.md](FAMILY.md) |
|
|
53
84
|
| agent 动完面板哪一格变、他正在干的时候看哪儿 | [LINKAGE.md](LINKAGE.md) |
|
|
54
85
|
| 落点、文件形状、命令、两个面 | [BUILD.md](BUILD.md) |
|
|
55
86
|
| 依赖、会被拦的写法、装、热换、发布 | [WIRING.md](WIRING.md) |
|
|
@@ -60,7 +91,9 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
60
91
|
## 找不到一件事的时候,按这个顺序找
|
|
61
92
|
|
|
62
93
|
1. **先跑门禁**:`dsh-wb-module check --only <那一条>` 的报错原文最准。
|
|
63
|
-
「外壳的 kit 里到底有什么组件 / hooks
|
|
94
|
+
「外壳的 kit 里到底有什么组件 / hooks」「dsh 原语里有什么、某一件怎么用」在工坊会话里直接调
|
|
95
|
+
`workbench_diy_components`(不填给清单,`name=Button` 回它的类型文件与声明);工程域用
|
|
96
|
+
`check --only kit` · `check --only primitives`(`--all` 全表、`--json` 机器档)。
|
|
64
97
|
2. **再看模板**:已经有一个同形状、能跑的最小实现,比任何描述都准 ——
|
|
65
98
|
在你工坊的 `node_modules/@dsh-workbench/module-tools/template-diy/`(带真数据源的样板)
|
|
66
99
|
与 `template/`(空壳)里,原文都是可读的。
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[DESIGN.md](DESIGN.md) 是三张表的来历,本文是每一步的**问法**与**完成判据**。
|
|
4
4
|
|
|
5
5
|
每一步定下来的东西都**当场落进**模块里的 `design.json`:面板 = `panel` / `states`、配置 = `config`、
|
|
6
|
-
能力 = `capabilities`、联动 = `linkage`、数据源 = `datasource` / `reads[]
|
|
6
|
+
能力 = `capabilities`、联动 = `linkage`、数据源 = `datasource` / `reads[]`、写通道 = `writes[]`(单条可写 `write{}`)、验收 = `acceptance`。
|
|
7
7
|
`dsh-wb-module design check` 会把空着的格列出来 —— **只留在对话里等于没定**。
|
|
8
8
|
|
|
9
9
|
## 步骤 1 — 面板:他看什么,他配什么
|
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
| 纪律(出处) | 固化形态 |
|
|
14
14
|
|---|---|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
15
|
+
| 十三条判据([REVIEW.md](REVIEW.md)) | `dsh-wb-module check`(`--only <判据>` 单跑;`--only kit` 列这份外壳的 kit 成员;`--only arch` 跑模块自己的分层形状;`--only lint` 跑模块自己的语法卫生) |
|
|
16
|
+
| 落点、分层文件形状、六个脚本([BUILD.md](BUILD.md) §1–§2) | `dsh-wb-module init --template diy`(骨架带分层目录 + 一行 `arch.config.mjs` + 两行 `eslint.config.mjs`(`moduleLint({ rootDir: import.meta.dirname })`)+ 一条真读盘的数据源)+ `scripts/smoke.mjs` |
|
|
17
|
+
| 语法卫生红了、要自动修([REVIEW.md](REVIEW.md) 第十三条) | `dsh-wb-module lint [--fix]`(**`check` 永不动手改文件**;`--fix` 才写回) |
|
|
17
18
|
| 装、上板、不重装([WIRING.md](WIRING.md) §3) | `workbench_diy_install` / `workbench_diy_show` / `workbench_diy_status`(工坊那一跳自己上板) |
|
|
19
|
+
| 想知道 dsh 原语 / 这份外壳的 kit 里有什么、某一件怎么用([KIT.md](KIT.md) §8) | `workbench_diy_components`(不填给清单 + 类型面根;`name=Button` 回它的类型文件与那份声明——JSDoc 里写着默认值与语义;工程域用 `dsh-wb-module check --only primitives --file <名字>`) |
|
|
18
20
|
| 它到底起得来没(装配抛的错 / 声明被拒的原文) | `workbench_diy_status`("起得来"那一行,见 [REVIEW.md](REVIEW.md) 四步取证的第二步) |
|
|
19
|
-
|
|
|
21
|
+
| 看 / 动**用户那一屏**之前要先取得约定(ADR-0055 §7「调试是一次约定」) | `workbench_diy_debug_start`(`unit=…`,**要用户点头一次**)—— 没有它,浏览器那 14 把 / `logs_*` **一律当场拒**(拒的时候一条命令都不发);不看屏了 `_pause`、收工 `_stop`(**收工顺手把网络捕获与日志钩子也收了**)。`_panel` / `_status` / `_probe` 不碰他那一屏,不用开。**全表与参数见 [FAMILY.md](FAMILY.md)** |
|
|
22
|
+
| 抓那一次**在外站页面上点出来的写**(他的写只发生一次,也不在我们那条会话里) | `_record_prepare`(`unit` / `url` / `doing`,**要用户点头一次**)→ 请他按「开始录制」→ 做**一次**那动作 → 按「结束录制」→ `_record_read` 取那一段 → `_record_stop` 收工(**先 read 再 stop**)。它不走调试约定,只碰自己的 `workbench-rec` 会话;三颗键、边界与实话见 [FAMILY.md](FAMILY.md) §5 · [WRITES.md](WRITES.md) 步骤 3 |
|
|
23
|
+
| 看 / 动那一屏的浏览器动作怎么发(枚举 opencli,argv 与结果原样转发) | 14 把:`workbench_diy_state` / `_html` / `_find` / `_click` / `_hover` / `_type` / `_fill` / `_keys` / `_scroll` / `_wait` / `_eval` / `_screenshot` / `_network` / `_dialog` —— 公共参数只有 `unit?` / `args?` / `timeoutMs?`(另有 `scope` / `in` / `clip` 只长在该长的那几把上;`$card` 在任何 `args` 里都替换)。**不知道参数就问 `--help`**(也照样转发);返回体是 opencli 的原样,事实在 `stdout` 里 |
|
|
24
|
+
| 这一页的控制台 / 网络怎么取证([REVIEW.md](REVIEW.md) 四步取证) | `workbench_diy_logs_watch` / `_read` / `_stop`(钩子有上限:每页 200 条、单条 400 字)—— **opencli 的 `console` 是空桩**(恒回空数组、还 exit 0),控制台只能我们自己做;网络是 `workbench_diy_network` 一把(`--follow` 臂上、`--failed` / `--since` 这些原样转发;收工 `_debug_stop` 顺手收),但**`--detail` 取不到 body** —— 缓存落在会话可写根之外,只能列请求 key;要 body 走 `_eval` 里的 `fetch`。网络那一头工作台走的是**隔离 opencli**(自己的 `HOME`),它的缓存与你自己 shell 里 `~/.opencli` 那份不是同一个 —— 核验要用工具,别看错文件 |
|
|
20
25
|
| 面板上现在是什么样(每张卡读到没 / 崩没崩) | `workbench_diy_panel`(浏览器半侧上报,宿主按 unit 留最新一条) |
|
|
21
26
|
| 空着的格就是还没定的事([DESIGN.md](DESIGN.md) §5) | `design.json` + `design check`(点名叫出空格)/ `render` / `accept` |
|
|
22
27
|
| 发货前的清单([WIRING.md](WIRING.md) §5) | `dsh-wb-module publish`(门禁 + 打包清单 `files` 漏项,只 dry run) |
|
|
@@ -26,7 +31,8 @@
|
|
|
26
31
|
| 这个能力要靠什么才跑得起来([TOOLS.md](TOOLS.md) §8) | `manifest.env` + `libInstallCommand`,`check --only contracts` 查 |
|
|
27
32
|
| 配置存不存([PANEL.md](PANEL.md) §5.1) | `config[].persistence`(`prefs` / `view`)+ `check --only contracts` |
|
|
28
33
|
| agent 动完面板哪一格变([LINKAGE.md](LINKAGE.md) §8) | `factsOf` + `factOfRoute` / `context` / `warmReads` / `values`,`check --only contracts` 只查形状 |
|
|
29
|
-
| kit 不够用时能不能引 dsh 的界面原语([PANEL.md](PANEL.md) §9) | 只在客户端侧引 + `dsh.client.external`
|
|
34
|
+
| kit 不够用时能不能引 dsh 的界面原语([PANEL.md](PANEL.md) §9 · [KIT.md](KIT.md) §8) | 只在客户端侧引 + `dsh.client.external` 里声明 + `devDependencies` 精确版对齐外壳钉的那份;宿主侧一个字都不许碰(`check --only source` / `--only primitives`) |
|
|
35
|
+
| dsh 原语里到底有什么(名字 / 哪几件 kit 已有 / 该写哪两行 / **这一件在哪份类型文件**) | `dsh-wb-module check --only primitives`(单跑当查询口;`--all` 全表、`--file <名字>` 给绝对路径、`--files \| grep` 全表、`--json` 机器档);kit 那条同形(`--only kit --file Card`)—— 取装机那份 `lib/types/**/*.d.ts`,不手抄清单 |
|
|
30
36
|
| DOM 读怎么读([DATASOURCE.md](DATASOURCE.md) 第 2 步) | `probe`(探)+ `browserFind`(固化)—— 切格 / 按列取值是两个纯函数 |
|
|
31
37
|
| 链接只有一副样子([PANEL.md](PANEL.md) §6) | kit 的 `Link` 是唯一一份(**没有 `icon` 口子**),`check --only style` 拦裸 `<a>` |
|
|
32
38
|
| 输入框不用自己配样式([PANEL.md](PANEL.md) §5.1) | `TextField` 的默认内边距 + kit 的 `Field`(标签 + 提示的排布) |
|