@dsh-workbench/core 0.3.4 → 0.3.6
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 +1362 -85
- package/dist/client/client.body.js +1362 -85
- package/dist/client/index.js +1362 -85
- package/dist/host/index.js +1019 -869
- package/dist/types/app/client.d.ts +234 -60
- package/dist/types/modules/panels/client.d.ts +1 -1
- package/dist/types/modules/panels/model/approval.d.ts +8 -0
- package/dist/types/modules/panels/model/diy-context.d.ts +4 -5
- package/dist/types/modules/panels/model/diy-observers.d.ts +2 -2
- package/dist/types/modules/panels/model/diy-page.d.ts +1 -1
- package/dist/types/modules/panels/model/long-task-tools.d.ts +15 -0
- package/dist/types/modules/panels/model/long-task-tools.test.d.ts +1 -0
- package/dist/types/modules/panels/unit.d.ts +6 -5
- package/dist/types/shared/api/context-chips.d.ts +20 -0
- package/dist/types/shared/api/host/approval-call.d.ts +16 -0
- package/dist/types/shared/api/host/diy-components.d.ts +17 -6
- package/dist/types/shared/api/host/diy-workshop.d.ts +0 -3
- package/dist/types/shared/api/host/long-task-notice.d.ts +30 -0
- package/dist/types/shared/api/host/mount-units.d.ts +3 -1
- package/dist/types/shared/api/host/preset-scope.d.ts +16 -0
- package/dist/types/shared/api/host/tool-scope.d.ts +6 -2
- package/dist/types/shared/api/host/workshop-root.d.ts +7 -0
- package/dist/types/shared/components/BlockerState.d.ts +12 -0
- package/dist/types/shared/components/PanelFoot.d.ts +9 -0
- package/dist/types/shared/components/Tip.d.ts +8 -0
- package/dist/types/shared/hooks/bundle-queries.d.ts +12 -0
- package/dist/types/shared/hooks/fact-transports.d.ts +4 -0
- package/dist/types/shared/hooks/panel-state-report.d.ts +12 -0
- package/dist/types/shared/hooks/use-api-resource.d.ts +22 -0
- package/dist/types/shared/hooks/use-blocker-actions.d.ts +8 -0
- package/dist/types/shared/hooks/use-capabilities.d.ts +55 -0
- package/dist/types/shared/hooks/use-capability-gate.d.ts +16 -0
- package/dist/types/shared/hooks/use-card-summary.d.ts +7 -0
- package/dist/types/shared/hooks/use-diy-debug.d.ts +13 -0
- package/dist/types/shared/hooks/use-diy-record.d.ts +13 -0
- package/dist/types/shared/hooks/use-facts.d.ts +10 -0
- package/dist/types/shared/hooks/use-host-write.d.ts +12 -0
- package/dist/types/shared/hooks/use-long-task.d.ts +15 -1
- package/dist/types/shared/hooks/use-unit-board.d.ts +17 -0
- package/dist/types/shared/hooks/use-visible-busy.d.ts +17 -0
- package/package.json +2 -2
- package/skills/workbench-module-diy/DATASOURCE.md +2 -2
- package/skills/workbench-module-diy/FAMILY.md +5 -4
- package/skills/workbench-module-diy/KIT.md +105 -100
- package/skills/workbench-module-diy/PANEL.md +19 -11
- package/skills/workbench-module-diy/REVIEW.md +4 -4
- package/skills/workbench-module-diy/SKILL.md +5 -4
- package/skills/workbench-module-diy/TOOLING.md +7 -3
- package/skills/workbench-module-diy/TOOLS.md +20 -2
- package/skills/workbench-module-diy/WIRING.md +2 -1
- package/skills/workbench-module-diy/HOOKS.md +0 -95
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 卡片报给面板那一格的两个数:`refreshing` 还在不在忙、`at` 最近一次读到值的时间(没有是 `null`)。
|
|
3
|
+
*/
|
|
1
4
|
export interface CardSummary {
|
|
2
5
|
readonly refreshing: boolean;
|
|
3
6
|
readonly at: number | null;
|
|
4
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* 自己搭卡片、要把 `{ refreshing, at }` 在变化时交给调用点上报面板时调用;`onSummary` 要稳定引用
|
|
10
|
+
* (一般 `useCallback`),否则每次渲染都会上报一次。`useUnitBoard` 已经把这条接好。
|
|
11
|
+
*/
|
|
5
12
|
export declare function useCardSummary(onSummary: (summary: CardSummary) => void, summary: {
|
|
6
13
|
readonly refreshing: boolean;
|
|
7
14
|
readonly at: number | null | undefined;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import type { DebugState } from '@dsh-workbench/shared';
|
|
2
2
|
import type { HostWrite } from './use-host-write';
|
|
3
|
+
/**
|
|
4
|
+
* 调试约定的读路由(宿主侧那半同名)。
|
|
5
|
+
*/
|
|
3
6
|
export declare const DIY_DEBUG_ROUTE = "panels/diy-debug";
|
|
7
|
+
/**
|
|
8
|
+
* 调试约定那条事实的名字:写完调 `useFactInvalidate(DIY_DEBUG_FACT)`,读者自动重取。
|
|
9
|
+
*/
|
|
4
10
|
export declare const DIY_DEBUG_FACT = "diy-debug";
|
|
11
|
+
/**
|
|
12
|
+
* 要读“现在的调试约定是什么”时调用(左栏底部那枚状态);跟着事实变化自动重取,不上报面板;
|
|
13
|
+
* 还没读到或后端说没有约定时都是 `null`。
|
|
14
|
+
*/
|
|
5
15
|
export declare function useDiyDebugState(): DebugState | null;
|
|
16
|
+
/**
|
|
17
|
+
* 要发“开始 / 暂停 / 继续 / 结束调试”那几把动作时调用;写走 `diy-debug/act` 通道。
|
|
18
|
+
*/
|
|
6
19
|
export declare function useDiyDebugAct(): HostWrite<{
|
|
7
20
|
text?: string;
|
|
8
21
|
}>;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import type { RecordState } from '@dsh-workbench/shared';
|
|
2
2
|
import type { HostWrite } from './use-host-write';
|
|
3
|
+
/**
|
|
4
|
+
* 录制交接的读路由(宿主侧那半同名)。
|
|
5
|
+
*/
|
|
3
6
|
export declare const DIY_RECORD_ROUTE = "panels/diy-record";
|
|
7
|
+
/**
|
|
8
|
+
* 录制交接那条事实的名字:写完调 `useFactInvalidate(DIY_RECORD_FACT)`,读者自动重取。
|
|
9
|
+
*/
|
|
4
10
|
export declare const DIY_RECORD_FACT = "diy-record";
|
|
11
|
+
/**
|
|
12
|
+
* 要读“这一段录制交接进行到哪了”时调用(左栏底部那枚状态);跟着事实变化自动重取,不上报面板;
|
|
13
|
+
* 还没读到或后端说没有交接时都是 `null`。
|
|
14
|
+
*/
|
|
5
15
|
export declare function useDiyRecordState(): RecordState | null;
|
|
16
|
+
/**
|
|
17
|
+
* 要发录制交接那几把动作(准备 / 读回 / 收尾)时调用;写走 `diy-record/act` 通道。
|
|
18
|
+
*/
|
|
6
19
|
export declare function useDiyRecordAct(): HostWrite<{
|
|
7
20
|
text?: string;
|
|
8
21
|
}>;
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 渲染里要“这条事实的版本号”时调用:版本一变,喂给 `useApiResource` 的 key 就变、读自动重来。
|
|
3
|
+
* 卡片读数默认订的 `factOfRoute(route)` 已经接好,只有要订别的事实才显式传 `fact`。
|
|
4
|
+
*/
|
|
1
5
|
export declare function useFactRevision(fact: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* 不在渲染里的地方(宿主事件、定时器里看到的旧值)要听事实变化时用它;返回值就是退订函数,用完记得退。
|
|
8
|
+
*/
|
|
2
9
|
export declare function subscribeFactRevision(fact: string, listener: () => void): () => void;
|
|
10
|
+
/**
|
|
11
|
+
* 写完之后把这条事实标脏时调用:不标,面板就停在旧值;不传 `fact` 是把所有事实一起标脏。
|
|
12
|
+
*/
|
|
3
13
|
export declare function useFactInvalidate(): (fact?: string) => void;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type WriteChannel, type WriteOutcome } from '@dsh-workbench/shared';
|
|
2
|
+
/**
|
|
3
|
+
* 一个写通道的句柄:`busy` 在发、`failed` / `detail` 上一条人话失败(没有是 `null`)、
|
|
4
|
+
* `send(body, { silent })` 回 `WriteOutcome`、`fail(message, detail?)` 本地就知道不行时自己塞一条、`clear()` 清掉。
|
|
5
|
+
*/
|
|
2
6
|
export interface HostWrite<T> {
|
|
3
7
|
readonly busy: boolean;
|
|
4
8
|
readonly failed: string | null;
|
|
@@ -9,9 +13,17 @@ export interface HostWrite<T> {
|
|
|
9
13
|
fail(message: string, detail?: string): void;
|
|
10
14
|
clear(): void;
|
|
11
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* 写通道的配置:`channel` 用哪个前缀、`resource` 挂在它的哪一格(多数通道留空)、`networkText` 网络不通时那句人话。
|
|
18
|
+
*/
|
|
12
19
|
export interface HostWriteOptions {
|
|
13
20
|
readonly channel: WriteChannel;
|
|
14
21
|
readonly resource?: string | undefined;
|
|
15
22
|
readonly networkText: string;
|
|
16
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 组件里要写(发 POST)时调用 —— 写只走这条,别在组件里自己 `fetch`。
|
|
26
|
+
* `send` 失败自动落到 `failed` + `detail`(界面直接显示,别自己拼);`silent: true` 是不打扰:不动 `busy` / `failed`,
|
|
27
|
+
* 结果只从返回值拿。返回的 `busy` 是这一路写有没有在飞。
|
|
28
|
+
*/
|
|
17
29
|
export declare function useHostWrite<T>(options: HostWriteOptions): HostWrite<T>;
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
import { type LongTaskStatus } from '@dsh-workbench/shared';
|
|
1
|
+
import { type LongTaskStatus, type WriteOutcome } from '@dsh-workbench/shared';
|
|
2
|
+
/**
|
|
3
|
+
* 这一单元的长任务清单与停止口:`runs` 是这次读交回的那一批(读数还没回来时是空数组);
|
|
4
|
+
* `stop(id)` 请外壳停某一趟 —— 回执没成就带回一句人话(调用点自己决定说不说);
|
|
5
|
+
* `stopping` 是正在请求停止的那一趟 id(没有就是 `null`),`stopFailed` 是上一次停止请求失败那句话(没有就是 `null`)。
|
|
6
|
+
*/
|
|
2
7
|
export interface LongTaskFeed<T> {
|
|
3
8
|
readonly runs: readonly T[];
|
|
9
|
+
stop(id: string): Promise<WriteOutcome<unknown>>;
|
|
10
|
+
readonly stopping: string | null;
|
|
11
|
+
readonly stopFailed: string | null;
|
|
4
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* 卡片要轮询长任务那一行时调用:有在跑的(`running` / `needs-login`)按 `longTaskPollMs` 走快档、空闲走慢档;
|
|
15
|
+
* `workspace` 为 `null`、`mocked: true` 或 `enabled: false` 时不轮。换单元 / 换工作区就是换读数身份,
|
|
16
|
+
* 旧那批立刻清空、读数回来前 `runs` 是空数组。
|
|
17
|
+
* `stop(id)` 走 actions 通道的 `<单元>/runs/stop`;不在跑的那一趟会带回一句人话,不抛错。
|
|
18
|
+
*/
|
|
5
19
|
export declare function useLongTask<T extends {
|
|
6
20
|
readonly id: string;
|
|
7
21
|
readonly status: LongTaskStatus;
|
|
@@ -6,10 +6,19 @@ import type { ApiError } from '@dsh-workbench/shared';
|
|
|
6
6
|
import type { ResourceState } from './use-api-resource';
|
|
7
7
|
import type { CapabilityGate } from './use-capability-gate';
|
|
8
8
|
import { type CardSummary } from './use-card-summary';
|
|
9
|
+
/**
|
|
10
|
+
* 卡片读数的最小形状:`state` 是 `useApiResource` 那一格,加上 `refreshing` / `at`(来自 `CardSummary`)
|
|
11
|
+
* 与 `settling`(挂过最短可见时长的那截收尾)。
|
|
12
|
+
*/
|
|
9
13
|
export interface CardFeed extends CardSummary {
|
|
10
14
|
readonly state: ResourceState<unknown>;
|
|
11
15
|
readonly settling?: boolean | undefined;
|
|
12
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 起步一张卡片要交的东西:这一格的 `unit`、能力门 `gate`、`refreshToken`、`onRefresh` / `onSummary` 两个回调、
|
|
19
|
+
* 自己的读数函数 `read(gated)`(收到的是“放行没”,为真才去读)、翻译 `t`(`enabled: false` 时整格不读);
|
|
20
|
+
* `platformBlocker(error, t)` 把平台错误翻成 blocker。
|
|
21
|
+
*/
|
|
13
22
|
export interface UnitBoardOptions<TFeed extends CardFeed> {
|
|
14
23
|
readonly unit: string;
|
|
15
24
|
readonly gate: CapabilityGate;
|
|
@@ -21,6 +30,10 @@ export interface UnitBoardOptions<TFeed extends CardFeed> {
|
|
|
21
30
|
readonly enabled?: boolean | undefined;
|
|
22
31
|
readonly platformBlocker?: ((error: ApiError, t: Translate) => ModuleBlocker | undefined) | undefined;
|
|
23
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* `useUnitBoard` 叠出来的整组:`feed`(含 `state`)、`capability`、`gated`(放行没)、`refresh`、`forceProbe`、`summary`,
|
|
35
|
+
* 外加拆好的那几个 blocker 字段。
|
|
36
|
+
*/
|
|
24
37
|
export interface UnitBoard<TFeed extends CardFeed> extends UnitBlockers {
|
|
25
38
|
readonly feed: TFeed;
|
|
26
39
|
readonly capability: CapabilityPayload | null;
|
|
@@ -29,4 +42,8 @@ export interface UnitBoard<TFeed extends CardFeed> extends UnitBlockers {
|
|
|
29
42
|
readonly forceProbe: () => void;
|
|
30
43
|
readonly summary: CardSummary;
|
|
31
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* 卡片起步就用它:把“gate 不放行就不读”、忙 / 安定(按 `MIN_BUSY_MS` 挂最短可见时长)、读数报错时
|
|
47
|
+
* 自动 `forceProbe`、`useCardSummary` 上报面板、blocker 拆分一次叠好。`read(gated)` 里自己调 `useApiResource`。
|
|
48
|
+
*/
|
|
32
49
|
export declare function useUnitBoard<TFeed extends CardFeed>(options: UnitBoardOptions<TFeed>): UnitBoard<TFeed>;
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 读数的相位:`idle` 还没开读 · `loading` 在飞 · `ready` 有值 · `error` 失败。
|
|
3
|
+
*/
|
|
1
4
|
export type HeldPhase = 'idle' | 'loading' | 'ready' | 'error';
|
|
5
|
+
/**
|
|
6
|
+
* “在忙,还是只是收尾”的投影:`settling` 为真时读数已经回来、界面还挂着。
|
|
7
|
+
*/
|
|
2
8
|
export interface BusyView {
|
|
3
9
|
readonly busy: boolean;
|
|
4
10
|
readonly settling: boolean;
|
|
5
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* 把“正在刷新”折进相位:`refreshing` 且已是 `ready` 记作 `loading`,让“旧值还显示、新的在路上”有名字。
|
|
14
|
+
*/
|
|
6
15
|
export declare function heldPhase(phase: HeldPhase, refreshing: boolean): HeldPhase;
|
|
16
|
+
/**
|
|
17
|
+
* 由 `active` 与 `useVisibleBusy` 挂出来的 `visible` 投影出 `{ busy, settling }`;纯函数,好测。
|
|
18
|
+
*/
|
|
7
19
|
export declare function busyView(active: boolean, visible: boolean): BusyView;
|
|
20
|
+
/**
|
|
21
|
+
* 需要“最少转一会儿”的忙态时调用:`active` 变真立刻 `busy`;变假时若这次没转满 `floorMs`,`busy` 先挂着,
|
|
22
|
+
* 这期间 `settling` 为真表示读数在回来的路上。`floorMs` 传共享包的 `MIN_BUSY_MS`,别自己编 ——
|
|
23
|
+
* 取数几毫秒就回来时不挂这一会儿,用户看到的是“闪一下”,比不转更难读。
|
|
24
|
+
*/
|
|
8
25
|
export declare function useVisibleBusy(active: boolean, floorMs: number): BusyView;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"access": "public"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@dsh-workbench/shared": "0.3.
|
|
90
|
+
"@dsh-workbench/shared": "0.3.6"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
- 想长期用 → 给这个站点写 adapter:`opencli skills read opencli-adapter-author`(侦察 → 字段解码 →
|
|
43
43
|
写 → verify);以后 adapter 失效:`opencli skills read opencli-autofix`。
|
|
44
44
|
|
|
45
|
-
**④ 才轮到探** —— 用工具:`
|
|
45
|
+
**④ 才轮到探** —— 用工具:`workbench_diy_eval`(`read=<那套系统的地址>, args=["<一段回 JSON 的脚本>"]`;
|
|
46
46
|
页面里没有接口就给 `css=<选择器>`,走的是同一条固定 DOM 读,`{entries:[{text}]}` 直接回给你)。
|
|
47
47
|
它用**用户浏览器里现有的登录**去读,并把"读不到"归一成阻断屏认的那几个码 —— 两拍(不在站点上就自己
|
|
48
48
|
打开 `url` 再读一遍)、刚重启时的重试、平台码到阻断码的映射**都在工具里**,不用你记。
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
想给自己一条专线(不跟别的模块抢同一个 tab)就传自己的名字:`session: "<unit>"`。
|
|
73
73
|
- **页面里没有接口时(DOM 读)不要用 `eval` 去 `document.querySelectorAll`**:产物边界判据会拦
|
|
74
74
|
(宿主产物里不许出现 `document.` / `window.` 字样),而且那样解析只能窝在字符串里、测不了。
|
|
75
|
-
探的时候 `
|
|
75
|
+
探的时候 `workbench_diy_find`(`read=…, args=["--css", "<选择器>"]`)就是这个口子;
|
|
76
76
|
固化时走 `browserFind`,它内部用的就是 `opencli browser <session> find --css '<选择器>' --limit N --text-max M`,
|
|
77
77
|
回 `{ entries: [{ text, tag, nth }] }` —— **一行的单元格按换行拼在 `text` 里**(`美元\n668.3\n…`),
|
|
78
78
|
所以"切格 + 按列序取值"是两个纯函数,能单测。很多公开站点一条 XHR 都不发,就该走这条。
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
## 1 前置(别的都排它后面)
|
|
7
7
|
|
|
8
8
|
- 只在**工坊会话**里出现,且**那块板画在屏上**;看 / 动 / 网络 / 日志那一族还要**先立调试约定** `workbench_diy_debug_start`(请用户点头一次):**没有约定一律当场拒、零命令**;不看屏 `_debug_pause`、收工 `_debug_stop`(顺手收网络与日志钩子)。
|
|
9
|
-
- 会话钉死 `workbench`、每条带 `--window background`;`_list`/`_status`/`_panel`/`_components`/`_recon`/`
|
|
9
|
+
- 会话钉死 `workbench`、每条带 `--window background`;`_list`/`_status`/`_panel`/`_components`/`_recon`/`_spec`/`_install`/`_show` 不碰他那一屏,不用开;`_find`/`_eval` 这些带 `read=` 时也**不碰**(走只读线,见 §2 末)。**写那一族(录制交接,见 §5)不走这条路**:它自己开一条 `workbench-rec` 会话,`_record_prepare` 要点头一次,`_record_read` / `_record_detail` / `_record_stop` 不问。
|
|
10
10
|
|
|
11
11
|
## 2 14 把浏览器工具(工具名 → argv;前缀都是 `workbench_diy_`)
|
|
12
12
|
|
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
**参数**(`scope` / `in` 默认都是 `page`):
|
|
18
18
|
|
|
19
|
-
- 公共:`unit?`(哪张卡;可省 —— 只剩一个模块就用它。**要卡作用域就得给**:写 `scope: 'card'` / `in: 'card'` 或用 `$card` 却不给 ⇒ **当场拒、零命令**)· `args?: string[]`(原样接在 `opencli browser workbench <verb…>` 后)· `timeoutMs?`(默认 30000
|
|
19
|
+
- 公共:`unit?`(哪张卡;可省 —— 只剩一个模块就用它。**要卡作用域就得给**:写 `scope: 'card'` / `in: 'card'` 或用 `$card` 却不给 ⇒ **当场拒、零命令**)· `args?: string[]`(原样接在 `opencli browser workbench <verb…>` 后)· `timeoutMs?`(默认 30000)· `read?: string`(只读线档,见本节末)。
|
|
20
20
|
- `scope: 'card'` 只给吃 CSS 的**七把**:`_find`(`--css`)· `_html`(`--selector`,没给它我们补)· `_click`/`_hover`/`_type`/`_fill`(**位置参数 0**)· `_wait`(**只在 `wait selector`**,位置参数 1)—— `card` = 给那个目标挂 `#wb-card-<unit> .wb-card `。
|
|
21
|
+
- **只读线档**:任何一把带上 `read: <站点地址>` 就跑在**另一条会话** `workbench-read` 上 —— **不要调试约定、不要板在屏上、不认卡作用域**(`unit` / `scope: 'card'` / `in: 'card'` / `$card` 逐个当场拒、零命令),也**不要求会话工作区里有模块包**(它一行都没碰工作区);结果与屏那一档**同一组字段**(`ok`/`exitCode`/`timedOut`/`truncated`/`stdout`/`stderr` 原样),另带 `session` 与 `opened`。`opened` = 我替你开过一次站点:跑之前先 `get url` 比域名,不在那个站点上就 `open <read>` 一次再跑。`_eval` 上沿用读线约定:脚本回 `{ ok: false, code: "not-logged-in" }` 时输出后补一句「去 <read> 登一次」(只补说明,结果字段一个不改)。**这一档取代了老的 `_probe`**(2026-10-20;来龙去脉在本仓 `docs/adr/0062` §6 与 `.scratch/diy-read-line/spec.md` —— 那两份不随包出货,只给改外壳的人):老 `_probe` 那层结构化 `entries` 没有替代 —— `_find` 的 stdout 自己解析。
|
|
21
22
|
- `in: 'card'` 只 `_eval`(注入卡根 `root`,**脚本自己 `return`**);`clip`(CSS px)只 `_screenshot`(拍完本地裁);`$card` **任何 `args` 里**都替换成 `#wb-card-<unit> .wb-card`。
|
|
22
23
|
|
|
23
|
-
**拿回**:`{ ok, exitCode, timedOut, truncated, stdout, stderr }` + `workshop`/`unit`/`verb`/`argv
|
|
24
|
+
**拿回**:`{ ok, exitCode, timedOut, truncated, stdout, stderr }` + `workshop`/`unit`/`verb`/`argv`(`read=` 那一档改成 `session`/`opened`:只读线不碰工作区,所以没有 `workshop`/`unit`);`_screenshot` 另给 `file`/`cropped`/`cropReason`。`ok` 只管进程,事实全在 `stdout`(报错也是一份 `{"error":{…}}`,`ok` 可能仍 `true`);不抠 JSON / 不翻中文 / 不做 ok 归一,**要参数先问 `--help`**。**"原样"有折损**:`args` 里空串 / 纯空白 / 非字符串丢掉、留下的词前后 trim、`stdout`/`stderr` 也 trim ⇒ **`_fill` 清空不了输入框**、**`_type` 敲不了空格**,没有别的口子就如实说做不到。
|
|
24
25
|
|
|
25
26
|
## 3 老工具 → 新命令
|
|
26
27
|
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
- `screenshot` **没有上游 selector**:卡图先 `eval` 量盒子再填 `clip`;不给路径我们补到工坊同级的 `shots/<unit|page>-<时间戳>.png`(否则回 base64);要裁的块不在视口里就退回整屏图,原因在 `cropReason`。
|
|
38
39
|
- `state` **没有 `scope`**(卡内定位用 `find --css '$card …'`);**没有右键 / contextmenu**;ref 只在**一次快照**里有效,别跨调用存;`_dialog` 的 `accept|dismiss` 不校验;`_network --detail` **取不到 body**(缓存落在会话可写根之外)—— 走 `_eval` 里的 `fetch`。
|
|
39
40
|
- 录制交接那条**会话捕获**(`_record_prepare` 起的:先短暂 `network --follow` 只做"臂上",然后每 3 秒一次**非跟随** `browser <session> network --all --raw`,**落盘前先按 URL 去掉静态资源与 `data:`**(含 node 就过滤、没有就原样落),一轮一行追加到工坊同级的 `shots/rec-<unit>.jsonl`)**带 body**:非跟随那条会把 live 缓冲排空,连 `request.body`(入参)与 `body`(出参)一起打出来;跟随那条只打形状(只有方法 / URL / 状态 / ct / size / 时间),所以只拿它臂上、装完就杀。它盖**整场**、在浏览器那一层,比页面钩子全(钩子只认那一页自己的 `fetch`/`XHR` —— 应用提前把 `window.fetch` 存走、或在 iframe / Worker / WebSocket 里发,它就看不见)。**不许改成"用 opencli 默认过滤"**:那层是**按响应 ct 白名单**判的(只留 json/xml/text-plain/javascript),"按下写就立刻按结束"那条**回包还没到、ct 为空**的写会被当场丢掉 —— 实测 127 条里它只留 33 条、那条写没了;按 URL 去静态则是 127 → 30 条、那条写还在。
|
|
40
|
-
- 另外
|
|
41
|
+
- 另外 19 把:台账 `_list`/`_status`/`_install`/`_show`、组件与 kit 清单 `_components`(不填给清单与类型面根,`name=Button` 回它的类型文件与声明)、宿主运行态 `_panel`/`_status`、数据源 `_recon`、`_spec`(问一条 opencli 命令的形状,**插件也适用**)、`_logs_*`、约定本身 `_debug_*`、录制交接 `_record_*`(§5)。
|
|
41
42
|
|
|
42
43
|
## 5 录制交接(四把):抓那一次"在外站页面上点出来的写",清单 → 挑一条 → 看原文
|
|
43
44
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# kit 速查:要做什么 → 用哪一件 → 最小写法
|
|
2
2
|
|
|
3
|
-
`
|
|
4
|
-
|
|
3
|
+
`check --only kit` 列**名字**(这台机器上真有的,别照抄别的模块),**逐件用法在 `.d.ts` 的 JSDoc 里**
|
|
4
|
+
(`workbench_diy_components name=<名字>` 一回就是它)。这一份给的是**跨件**的:怎么拼、纪律、排布。
|
|
5
5
|
|
|
6
6
|
**能拿 kit 的就别自己画。** 理由只有一条:主题、语言、悬停、禁用、焦点环、空态、被裁的处理
|
|
7
7
|
都只维护一份。自己画的不会跟着换主题,也不会跟着我们改口径 —— 这就是"同一个东西长得不一样"的来源。
|
|
@@ -14,17 +14,12 @@
|
|
|
14
14
|
**`Card`** —— 一张卡的壳:标题 + 刷新钮 + 卡片体。面板上每一格都是它。
|
|
15
15
|
|
|
16
16
|
```tsx
|
|
17
|
-
h(Card, {
|
|
18
|
-
|
|
19
|
-
title: t('myunit.cardTitle'),
|
|
20
|
-
refreshing: resource.phase === 'loading',
|
|
21
|
-
onRefresh: props.onRefresh,
|
|
22
|
-
}, body)
|
|
17
|
+
h(Card, { t: props.t, title: t('myunit.cardTitle'),
|
|
18
|
+
refreshing: resource.phase === 'loading', onRefresh: props.onRefresh }, body)
|
|
23
19
|
```
|
|
24
20
|
|
|
25
|
-
**`CardHead`** —— 你自己拼卡头时用它(`Card`
|
|
26
|
-
`
|
|
27
|
-
那个系统:只给这一颗就够,那颗 ↗ 立刻画出来)。
|
|
21
|
+
**`CardHead`** —— 你自己拼卡头时用它(`Card` 内部就是它)。prop 见 `.d.ts`;
|
|
22
|
+
`platformHref` 只给这一颗就够,那颗 ↗ 立刻画出来。
|
|
28
23
|
|
|
29
24
|
**`CardAnchor`** —— 外壳自己盖的锚点(`#wb-card-<unit>`)。**别自己写**,也别删。
|
|
30
25
|
|
|
@@ -32,8 +27,7 @@ h(Card, {
|
|
|
32
27
|
|
|
33
28
|
## 2 状态:这是下限最集中的地方
|
|
34
29
|
|
|
35
|
-
先想清五态(`design.json` 的状态矩阵就是这五格):**待机 / 在读 / 就绪 / 就绪但空 /
|
|
36
|
-
每一态都要有东西画,一个不落。
|
|
30
|
+
先想清五态(`design.json` 的状态矩阵就是这五格):**待机 / 在读 / 就绪 / 就绪但空 / 读不到** —— 每一态都要有东西画。
|
|
37
31
|
|
|
38
32
|
| 什么时候 | 用 |
|
|
39
33
|
|---|---|
|
|
@@ -43,6 +37,8 @@ h(Card, {
|
|
|
43
37
|
| 在读、且你已经有大概形状 | `SkeletonRows`(骨架) |
|
|
44
38
|
| 一句说明 / 一句报错 | `Note`(`tone: 'problem'` 是报错) |
|
|
45
39
|
|
|
40
|
+
prop 与默认值看 `.d.ts`;下面是**怎么拼**。
|
|
41
|
+
|
|
46
42
|
```tsx
|
|
47
43
|
h(CenteredState, { icon: null, title: t('myunit.loading'), hint: t('myunit.loadingHint'), compact: true })
|
|
48
44
|
h(CenteredState, { icon: null, title: t('myunit.errorTitle'), detail: `${code}: ${message}`, hint: t('myunit.errorHint'), compact: true })
|
|
@@ -51,7 +47,7 @@ h(StateBlock, { title: rows.title, hint: `3 条 · ${when}`, resolving: t('myuni
|
|
|
51
47
|
h(Note, { children: t('myunit.hint') })
|
|
52
48
|
```
|
|
53
49
|
|
|
54
|
-
**读不到就说读不到**:`detail` 里放原始错误码与那句话,**绝不显示 0**(0 是"真的是 0"
|
|
50
|
+
**读不到就说读不到**:`detail` 里放原始错误码与那句话,**绝不显示 0**(0 是"真的是 0")。
|
|
55
51
|
|
|
56
52
|
## 3 一行一行
|
|
57
53
|
|
|
@@ -63,137 +59,146 @@ h(Rows, null,
|
|
|
63
59
|
...items.map((one) => h(Row, { key: one.name, label: one.name, value: `${size} · ${when}` })))
|
|
64
60
|
```
|
|
65
61
|
|
|
66
|
-
**`ActivityBlock` / `ActivityLine` / `ActivityAction`** —— "它做过什么"
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
**`ActivityBlock` / `ActivityLine` / `ActivityAction`** —— "它做过什么"那一块(prop 见 `.d.ts`)。时间一律用它。
|
|
63
|
+
|
|
64
|
+
**`LongTaskRow`** —— 长任务那一行(五态,prop 见 `.d.ts`);别自己画进度。传 `stop` 要宿主认 `signal`、peer 抬到带它那版([TOOLS.md](TOOLS.md) §9)。
|
|
69
65
|
|
|
70
|
-
**`
|
|
71
|
-
`raw`。跑得久的事(发布、扫描、迁移)都用它,别自己画进度。
|
|
66
|
+
**`ApprovalDetail` / `ApprovalPlan`** —— 审批屏那两格(写操作才要点头);口径与那张 ASCII 见 [DESIGN.md](DESIGN.md) §3 末节。
|
|
72
67
|
|
|
73
|
-
|
|
68
|
+
## 4 表单
|
|
74
69
|
|
|
75
|
-
**`
|
|
70
|
+
**`Form`** —— **整张表单**(面那一层):`fields` 配置进,行怎么排、错误摆哪、什么时候校验、提交怎么被挡住、
|
|
71
|
+
第一处错误怎么带进视口、操作区怎么排全归它。**一张完整的表单就用它**(口径见 §5,逐件看 `.d.ts`)。
|
|
76
72
|
|
|
77
73
|
```tsx
|
|
78
|
-
h(
|
|
79
|
-
|
|
74
|
+
h(Form, {
|
|
75
|
+
fields: [
|
|
76
|
+
{ key: 'branch', label: t('myunit.branch'), value: branch, // 值只读给校验看,读写仍归你
|
|
77
|
+
validate: (v) => (v === '' ? t('myunit.branchRequired') : undefined),
|
|
78
|
+
control: (b) => h(TextField, { id: b.id, name: b.name, invalid: b.invalid,
|
|
79
|
+
disabled: b.disabled, value: branch, onChange: (e) => setBranch(e.currentTarget.value) }) },
|
|
80
|
+
],
|
|
81
|
+
submit: { label: t('myunit.save'), busyLabel: t('myunit.saving') },
|
|
82
|
+
cancel: { label: t('myunit.cancel'), onClick: onCancel }, // 排在提交钮左边
|
|
83
|
+
extra: [{ key: 'retry', label: t('myunit.retry'), onClick: onRetry }], // 其余动作排最左
|
|
84
|
+
onSubmit: save, // 有错就不调它
|
|
85
|
+
})
|
|
80
86
|
```
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
**值仍归调用点**(`Form` 一个值都不存):`binding` 只递 `id` / `name` / `invalid` / `disabled`,
|
|
89
|
+
`value` 与 `onChange` 由 `control` 里自己接。**控件永远由 `control` 给** —— 没有"给个 `options` 自己选控件"的 DSL。
|
|
90
|
+
|
|
91
|
+
**控件那几件** —— `Field`(没进 `FormRow` 时兜住标签 + 说明)· `TextField`(`invalid` 标红)·
|
|
92
|
+
`ChoiceGroup`(`multi` 区分多选)· `SelectField`(默认常规下拉框,`variant: 'ghost'` 是无边框那档,
|
|
93
|
+
**不是原生 `<select>`**)· `SaveBar`(设置面底下那条"取消 / 保存"):都是只给数据与语义 prop 的哑组件,
|
|
94
|
+
**逐件看 `.d.ts`**。
|
|
95
|
+
|
|
96
|
+
**`FormActions`** —— **操作区**(一组按钮,形状见 `.d.ts`;**整张表单里由 `Form` 组**)。
|
|
90
97
|
**顺序 / 间距 / 换行都归它** —— `primary` 永远在右、`danger` 紧挨它左侧、一个区只许一颗 `primary`、
|
|
91
|
-
窄了自动折行且 `primary` 落在右下角;`busy` 自带 `Spinner
|
|
92
|
-
|
|
98
|
+
窄了自动折行且 `primary` 落在右下角;`busy` 自带 `Spinner`。**别自己写 `gap` / `margin` / 顺序,
|
|
99
|
+
也别借 `.wb-formActions` 槽类手摆**(F3 红)。`danger` 只标"破坏性"(**永远不当填充的 `primary`**);
|
|
100
|
+
**二次确认那半归调用点**。
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{ key: 'retry', label: t('myunit.retry'), onClick: onRetry, disabled: busy },
|
|
97
|
-
{ key: 'confirm', label: t('myunit.confirm'), kind: 'primary', onClick: submit, disabled: busy },
|
|
98
|
-
] })
|
|
99
|
-
```
|
|
102
|
+
**别手写 `<button>` / `<input>` / `<select>`**(dsh 有 `Button`,kit 有上面这几件;手写的判据会红)。
|
|
103
|
+
`<textarea>` 例外(dsh 与 kit 都没有多行输入)。
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
(先问一句或弹窗都行)。
|
|
105
|
+
怎么排成一行(标签位置、错误态、尺寸)见 §5。
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
kit 有上面这几件;手写的会拿到浏览器默认样式,而且**判据会红**(`check --only style`)。
|
|
106
|
-
`<textarea>` 例外(dsh 与 kit 都没有多行输入)——真要用就直接写,其余形态别开这个口子。
|
|
107
|
+
## 4′ hook 怎么搭(逐件看 `.d.ts`)
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
kit 的 hook 也分四个场合,**别自己 `useState` + `setTimeout` 造一套**(面板要同一套口径):
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
| 场合 | 用它 |
|
|
112
|
+
|---|---|
|
|
113
|
+
| 卡片里的一次读(自带同 key 合流 / 事实订阅 / 上报面板) | `useApiResource`(非 hook 的本体是 `requestResource`,**用了就一定要 `release`**) |
|
|
114
|
+
| 写完让面板重取 / 听事实变化 | `useFactInvalidate` · `useFactRevision` · `subscribeFactRevision`(非 hook,记得退订) |
|
|
115
|
+
| "至少转一会儿"的忙态 | `useVisibleBusy`(投影那两件 `busyView` / `heldPhase`) |
|
|
116
|
+
| 一格**卡片**起步(gate + 忙 + 探针 + 上报 + blocker 一次叠好) | `useUnitBoard`(`read(gated)` 里自己调 `useApiResource`) |
|
|
117
|
+
| 只要一格的能力门 / 整张能力表 | `useCapabilityGate` / `useCapabilityMap` |
|
|
118
|
+
| 写(**别在组件里 `fetch`**) / 轮询长任务 | `useHostWrite` / `useLongTask`(画用 `LongTaskRow`、点用 `longTaskDot`) |
|
|
119
|
+
|
|
120
|
+
取数那条链的图见 [LINKAGE.md](LINKAGE.md) §8,写通道与配置见 [PANEL.md](PANEL.md) §5。
|
|
121
|
+
|
|
122
|
+
## 5 表单这一层:`Form`(面)→ 行 → 控件
|
|
111
123
|
|
|
112
|
-
表单三层:**面**(`
|
|
113
|
-
|
|
124
|
+
表单三层:**面**(`Form`)→ **行**(`FormRows` + `FormRow`)→ **控件**(`TextField` / `SelectField` /
|
|
125
|
+
`ChoiceGroup`)。**一张表单一个面**。
|
|
126
|
+
|
|
127
|
+
**整张表单用 `Form`**(写法见 §4):`fields` 配置进;**值仍归调用点**(`value` / `onChange` 由 `control`
|
|
128
|
+
里自己接,`binding` 只递 `id` / `name` / `invalid` / `disabled`);**控件永远由 `control` 给**(没有"给个
|
|
129
|
+
`options` 自己选控件"的 DSL);`validate` 是同步纯函数;`visible: false` 的那格当它不存在(不渲染 /
|
|
130
|
+
不校验 / 不进 `values`)。**别再自己摆操作区** —— 它自己组 `[...extra, ...cancel, submit(primary)]`
|
|
131
|
+
(提交钮永远在右;「一会在左一会在右」就是自己摆的结果)。其余口径(时机 / 第一处错误 / `busy` /
|
|
132
|
+
`disabled` / `key`)看它的 JSDoc 与 ADR-0068。
|
|
133
|
+
|
|
134
|
+
`FormRows` + `FormRow` + `FormActions` 留给**不是整张表单**的地方:单行(一格字段嵌在别处,比如卡片里
|
|
135
|
+
那一条设定)、非表单的一组动作(工具栏、卡头那几颗钮)。那里才自己拼这三件:
|
|
114
136
|
|
|
115
137
|
```tsx
|
|
116
138
|
h(FormRows, null,
|
|
117
139
|
h(FormRow, { label: t('myunit.mode'), hint: t('myunit.modeHint') },
|
|
118
|
-
h(SelectField, { value, options, onChange: setMode }))
|
|
119
|
-
h(FormRow, { label: t('myunit.env'), problem: problemOfEnv },
|
|
120
|
-
h(ChoiceGroup, { label: t('myunit.env'), name: 'env', options, value: env, onChange: setEnv })))
|
|
140
|
+
h(SelectField, { value, options, onChange: setMode })))
|
|
121
141
|
```
|
|
122
142
|
|
|
123
|
-
- **标签归 `FormRow
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
- **`hint` 与 `problem` 二选一**:给了 `problem` 就不画 `hint` —— 同一格不叠两段话,有错先显示错。
|
|
143
|
+
- **标签归 `FormRow`**(默认在左,`layout: 'stacked'` 才上下堆叠);**控件只给数据与语义 prop**,
|
|
144
|
+
进了 `FormRow` 就别再给控件自己的 `label` / `hint`(会多一个标签、还退回上下堆叠)。
|
|
145
|
+
- **`hint` 与 `problem` 二选一**:有错先显示错。
|
|
127
146
|
|
|
128
|
-
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
不做联动、不持有值)。提交失败后调 `kit.lib.revealFirstProblem(root)`(`root` = 表单容器)——
|
|
132
|
-
把第一处错误带进视口、并聚焦它里面的控件。
|
|
147
|
+
**错误态**:位置与长相归 `FormRow.problem` + 控件的 `invalid`;**算错的逻辑留在模块**(纯函数 + 文案,
|
|
148
|
+
交给 `FormField.validate`)—— **别指望外壳给校验引擎**(不做 `rules` / 异步 / 联动、不持有值),
|
|
149
|
+
**"提交过没有"与聚焦第一处错也归 `Form`**(别自己记布尔、别自己调 `revealFirstProblem`)。
|
|
133
150
|
|
|
134
|
-
**选择类用哪件**:候选多 / 要搜索 / 要分组 / 名单可能读不到 ⇒ `SelectField
|
|
135
|
-
`
|
|
136
|
-
候选少、要一眼看全 / 多选 ⇒ `ChoiceGroup`(`multi`、`variant`、`loading` + `loadingText`、
|
|
137
|
-
`emptyText`、`options[].description`)。两件的 `loading` / `loadingText` / `emptyText` / `variant`
|
|
138
|
-
是一个意思;**loading 那颗一律是 `Spinner`**(`h(Spinner, { size: 14 })`)—— **别自己写转圈**。
|
|
151
|
+
**选择类用哪件**:候选多 / 要搜索 / 要分组 / 名单可能读不到 ⇒ `SelectField`;候选少、要一眼看全 / 多选 ⇒
|
|
152
|
+
`ChoiceGroup`(`loading` / `emptyText` / `variant` 两件一个意思)。**loading 那颗一律是 `Spinner`**。
|
|
139
153
|
|
|
140
|
-
**尺寸口径**:字段控件默认 **28
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
(非行的子项,比如分组标题 `<p>`,自动通栏)。
|
|
154
|
+
**尺寸口径**:字段控件默认 **28**,别自己调高;行距只有一档、标签列宽也归 `FormRow(s)`
|
|
155
|
+
(长标签不折行、同一组共用一个标签列,别自己给标签写宽度);**控件的宽度也别自己写**
|
|
156
|
+
(字段控件自己吃满控件槽那一格,ADR-0052 §13)。非行的子项(如分组标题)自动通栏。
|
|
144
157
|
|
|
145
158
|
**反例(别这么写)**:
|
|
146
159
|
|
|
147
160
|
```tsx
|
|
148
|
-
h('span', { className: styles.label },
|
|
149
|
-
h('div', { className: styles.form }, …)
|
|
161
|
+
h('span', { className: styles.label }, …) // ← 手写标签行:F1 红
|
|
162
|
+
h('div', { className: styles.form }, …) // ← 自己管行距(.form { gap }):F2 红
|
|
163
|
+
h('form', …) // ← 裸 <form> / 手写行槽类:F4 红(表单归 Form)
|
|
150
164
|
```
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
**时点**:这一层随外壳**发版**后模块才能用 —— 模块的 `peerDependencies` 下限得先有它
|
|
156
|
-
(`check --only kit` 拿 peer 下限那份外壳比,不抬必红)。包下限是 `^0.2.14`。
|
|
166
|
+
**时点**:这一层随外壳发版后模块才能用(`check --only kit` 拿 peer 下限那份外壳比,不抬必红)。
|
|
167
|
+
**`Form` 是新成员**:要用它得等外壳**发一版含它的版本**,再把 `@dsh-workbench/core` 的 peer
|
|
168
|
+
下限抬到**那一版**(版本号随发版填,别照抄别的件);`FormRow(s)` / `FormActions` 的下限是另一件事。
|
|
157
169
|
|
|
158
170
|
## 6 别的
|
|
159
171
|
|
|
160
|
-
- **`Link`** ——
|
|
161
|
-
|
|
162
|
-
- **`SettingsSection
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
`
|
|
168
|
-
不断言"画的是哪一态那一屏")。
|
|
169
|
-
- **图标** —— `IconPanelScreen`(面板/卡片那类)、`IconModuleMark`(我们的模块类型标)、
|
|
170
|
-
`IconNpm` / `IconOpencli`(包类型);其余图形一律从 dsh 的 `Icon*Regular` 拿,**别自己画图标**。
|
|
172
|
+
- **`Link`** —— **唯一**的外链写法(`h(Link, { href, children })`):**别去掉箭头、别在文案里自带 `→`、
|
|
173
|
+
别手写 `<a>`**(判据红)。
|
|
174
|
+
- **`SettingsSection` / `Stack` / `BlockerState` / `Tip`** —— 一屏设置里的一节 / 竖排间距 / 阻断屏 /
|
|
175
|
+
Tooltip(图标钮与只有图的地方**必须**有它或 `aria-label`);prop 看 `.d.ts`。
|
|
176
|
+
- **真机上造不出来的那几态**:拿你自己那份 `plugin.test.ts` 把五态各喂一遍(`check --only cards`
|
|
177
|
+
拿七种外壳侧输入各喂一遍,只问"退化得干不干净")。
|
|
178
|
+
- **图标** —— `IconPanelScreen` / `IconModuleMark` / `IconNpm` / `IconOpencli`;其余图形一律从 dsh 的
|
|
179
|
+
`Icon*Regular` 拿,**别自己画图标**。
|
|
171
180
|
|
|
172
181
|
## 7 三条纪律
|
|
173
182
|
|
|
174
|
-
1.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
数字加 `font-variant-numeric: tabular-nums`(见 [PANEL.md](PANEL.md) §6)。
|
|
179
|
-
刻度外非要一个字面值(比如更大的标题数)时,在模块 `README.md` 里写一句为什么。
|
|
183
|
+
1. **调用点只给数据与语义**。别从外面塞外观类名 —— 只有布局槽例外(通栏、外边距、网格位置)。
|
|
184
|
+
2. **字号与颜色走 kit 与令牌**(`--dsw-*` / `--wb-*`),不写字面值。**重点可以大**:要突出的数字用
|
|
185
|
+
`--dsw-font-xl-24`(24/32、600)加 `font-variant-numeric: tabular-nums`(见 [PANEL.md](PANEL.md) §6);
|
|
186
|
+
刻度外非要字面值时,在模块 `README.md` 里写一句为什么。
|
|
180
187
|
3. **自己写 CSS 只写布局**(flex / grid / gap / 对齐)。要一个新的"长得像控件"的东西时,
|
|
181
188
|
先问一句:kit 里是不是已经有了?
|
|
182
189
|
|
|
183
190
|
## 8 kit 里没有、但你确定要标准控件
|
|
184
191
|
|
|
185
192
|
**先问工具**:DIY 会话里调 `workbench_diy_components`(不填给清单与类型面根;`name=Button`
|
|
186
|
-
|
|
193
|
+
回它的类型文件与声明)。工程域用 `dsh-wb-module check --only primitives`
|
|
187
194
|
(`--all` 全表 / `--json` 机器档)—— 分桶导出 + 与 kit 求交(`kit 里已有` 的优先用)+
|
|
188
|
-
该写进 `package.json`
|
|
189
|
-
|
|
190
|
-
`check --only kit --file Card`),`--files | grep <名字>` 出全表 —— 每件都有一个同名(或就近)的 `.d.ts`。
|
|
195
|
+
该写进 `package.json` 的那两行;骨架默认已备好。
|
|
196
|
+
**要看某一件**:`--file <名字>`(`--files | grep <名字>` 全表)。
|
|
191
197
|
|
|
192
198
|
```jsonc
|
|
193
199
|
"dsh": { "client": { "external": [ "@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives" ] } },
|
|
194
200
|
"devDependencies": { "@deepseek-ai/dsh-client-ui-primitives": "<外壳钉的那版,精确版>" }
|
|
195
201
|
```
|
|
196
202
|
|
|
197
|
-
改完 `pnpm install`
|
|
198
|
-
|
|
199
|
-
宿主侧的契约按需自己声明([PANEL.md](PANEL.md) §9)。
|
|
203
|
+
改完 `pnpm install` 一次;三条会红的边界(只在浏览器侧引 / 版本精确对齐 / 能拿 kit 的别引)与
|
|
204
|
+
宿主侧的契约都见 [PANEL.md](PANEL.md) §9。
|