@morlay/dsh-client-ui-primitives 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -266
- package/dist/client.cjs +11 -612
- package/dist/client.d.cts +33 -658
- package/dist/client.d.mts +33 -658
- package/dist/index.mjs +1 -1
- package/dist/{reference-Dxy9uQYz.mjs → reference-Cjcn21uW.mjs} +0 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +18 -18
- package/src/client/index.ts +7 -9
- package/src/client/schema-form/Editor.tsx +25 -38
- package/src/client/schema-form/SchemaForm.tsx +6 -12
- package/src/client/schema-form/controller.ts +65 -123
- package/src/client/schema-form/draft.ts +55 -138
- package/src/client/schema-form/fields.tsx +5 -14
- package/src/client/schema-form/hints.ts +31 -131
- package/src/client/schema-form/index.ts +15 -20
- package/src/client/schema-form/labels.ts +7 -9
- package/src/client/schema-form/lines.ts +36 -60
- package/src/client/schema-form/locales.ts +6 -8
- package/src/client/schema-form/rows.ts +22 -41
- package/src/client/schema-form/schema-node.ts +87 -150
- package/src/client/schema-form/slot-contract.ts +55 -76
- package/src/client/schema-form/styles.ts +39 -51
- package/src/client/schema-form/testing/fake-describe.ts +3 -9
- package/src/client/schema-form/testing/fake-scope.ts +8 -10
- package/src/client/schema-form/value.tsx +14 -26
- package/src/client/settings-form/SettingsForm.tsx +13 -17
- package/src/client/settings-form/fields.tsx +25 -35
- package/src/client/settings-form/form-model.ts +63 -145
- package/src/client/styling/styled.tsx +3 -5
- package/src/client/theme.generated.ts +18 -7
- package/src/reference-markdown.tsx +1 -1
- package/src/reference.ts +1 -1
package/README.md
CHANGED
|
@@ -1,300 +1,52 @@
|
|
|
1
1
|
# @morlay/dsh-client-ui-primitives
|
|
2
2
|
|
|
3
|
-
对话 UI
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**设置表单原语**(暂存式表单模型 `SettingsFormModel` + 字段控件 + 表单框,样式同样走本包的 `styled` / `dsw`)。
|
|
3
|
+
对话 UI 与设置页共用的**基础面**:**css-in-js 样式层**(`styled` / `Token` / `dsw`,消费官方主题注入的
|
|
4
|
+
`--dsw-*`,源码可直接加载)、**引用解析与渲染转换**(host 与 client 共用唯一一份源码,
|
|
5
|
+
[`@morlay/dsh-reference`](../../context/dsh-reference/README.md) 的引用展开复用 host 那半),以及**设置面原语**
|
|
6
|
+
(从上游 fork 的暂存式表单原语,与按 schema 自动生成的行配置页)。
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
[ADR-css-in-js样式层与官方token消费](./.agents/adrs/20260917-css-in-js样式层与官方token消费.md);
|
|
11
|
-
设置表单原语的搬迁与同步纪律见
|
|
12
|
-
[ADR-设置表单原语搬进client半](./.agents/adrs/20260923-设置表单原语搬进client半.md)。
|
|
13
|
-
|
|
14
|
-
## 能力
|
|
15
|
-
|
|
16
|
-
| 导出 | 用途 |
|
|
17
|
-
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
-
| `styled('div')(style, …)` | 生成带 `data-css-*` 的组件;多个样式对象深合并(变体叠基样式) |
|
|
19
|
-
| `styling` | `props(style)` 取 `data-css-*` 属性、`keyframes`、`injectGlobals`、`sheets()` |
|
|
20
|
-
| `Token` | 变量引用代理(`vars`)、局部变量赋值(`assignVars`)、变体选择器(`variants`)、组合子(`calc` / `min` / `max` / `url` / `colorMix` / `colorScale` / `fallbackVar` / `val`) |
|
|
21
|
-
| `dsw` | 官方主题变量引用:`dsw.alias.bg.base` → `var(--dsw-alias-bg-base)` |
|
|
22
|
-
| `SettingsFormModel` | 暂存式表单模型:草稿(`edit` / `resetField` / `save` / `discard`)、字段状态(`field`)、表单状态(`shell`)、快照投影(`bind`);`settingsNumberField` / `settingsTextField` 给字段的转换 spec |
|
|
23
|
-
| `SettingsForm` | 设置表单框:不可用 / 只读 / 保存中 / 保存失败各状态,离开页面时丢弃草稿 |
|
|
24
|
-
| `SettingsValueField` / `SettingsSecretField` | 字段控件:标签、草稿文本、覆盖徽标与重置、非法提示、可选说明面板;控件只上报用户输入,不写盘;口令框要求浏览器不做已存登录密码的自动填充 |
|
|
25
|
-
| `findReferences` / `parseReferenceToken` | host 面:`@` 引用的统一解析与形态归一(`@path` / `@"path"` / `skill:name` → 结构化引用与 span),`Reference` / `ReferenceSpan` 类型 |
|
|
26
|
-
| `designTokens` | 生成的 token 树,叶子是官方默认值(可作 fallback 与类型推导来源) |
|
|
27
|
-
|
|
28
|
-
样式注入是**惰性立即注入**(某条规则第一次生成时就 append `<style data-css="…">`,
|
|
29
|
-
按 id 去重),不提供 Provider。
|
|
30
|
-
|
|
31
|
-
## token 树
|
|
32
|
-
|
|
33
|
-
`designTokens`(`src/client/theme.generated.ts`)由 `scripts/gen-design-tokens.mts` 从
|
|
34
|
-
上游主题源码生成,共 **367 个 `--dsw-*`**:叶子是该变量在 light 主题里的默认值。
|
|
35
|
-
生成机制、`"$"` 键语义与守卫判据见
|
|
36
|
-
[ADR-css-in-js样式层与官方token消费](./.agents/adrs/20260917-css-in-js样式层与官方token消费.md)。
|
|
37
|
-
|
|
38
|
-
## 设置表单原语
|
|
39
|
-
|
|
40
|
-
上游 `@deepseek-ai/dsh-client-ui-primitives` 的 settings-form 原语搬在 `src/client/settings-form/`
|
|
41
|
-
(`form-model.ts` / `fields.tsx` / `SettingsForm.tsx`,导出面同形同名)。消费方从 client 出口取:
|
|
8
|
+
## 用法
|
|
42
9
|
|
|
43
10
|
```ts
|
|
44
|
-
import {
|
|
45
|
-
SettingsFormModel,
|
|
46
|
-
settingsNumberField,
|
|
47
|
-
SettingsForm,
|
|
48
|
-
SettingsValueField,
|
|
49
|
-
} from "@morlay/dsh-client-ui-primitives/client";
|
|
50
|
-
|
|
51
|
-
const form = new SettingsFormModel(scope, [settingsNumberField("timeoutMs")]);
|
|
52
|
-
const store = form.bind(() => ({ ...form.shell(), timeoutMs: form.field("timeoutMs") }));
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
控件只上报用户输入,`save` 是唯一写盘点;判据(暂存、校验与非法输入、可用性与只读、revision 栅栏、
|
|
56
|
-
保存失败保留草稿)见[如何验证](./.agents/standards/how-to-verify.md),搬迁与同步纪律见
|
|
57
|
-
[ADR-设置表单原语搬进client半](./.agents/adrs/20260923-设置表单原语搬进client半.md)。
|
|
58
|
-
|
|
59
|
-
## 装配
|
|
60
|
-
|
|
61
|
-
本包没有自己的 bundle patch(不作为独立 bundle 安装):装配入口是聚合层
|
|
62
|
-
[`@morlay/better-session`](../../bundles/better-session/README.md) 的一行 insert(行 id `ui-primitives-fork`),
|
|
63
|
-
client 半按本包 `dsh.client` 声明加载。
|
|
64
|
-
|
|
65
|
-
两处 `inject` 不是一回事:包清单里的 `dsh.client.inject` 列的是**装配行 id**(谁先到,模块系统据此排 arrival
|
|
66
|
-
顺序),模块自己导出的 `inject`(`src/client/index.ts`)列的是 **cordis 服务名**(谁已 provide,Loader 据此判定
|
|
67
|
-
这一行何时 active)。后者写成包名,这一行会永远 pending——前端起不来,只在页面报
|
|
68
|
-
`@morlay/dsh-client-ui-primitives: pending (waiting for services: …)`。所以源码那份直接取自 schema 表单那一面
|
|
69
|
-
(`[...schemaForm.inject]`),不另抄一份清单。
|
|
70
|
-
|
|
71
|
-
## 已知限制
|
|
72
|
-
|
|
73
|
-
- token 值只在生成物里(默认值),运行时不重新定义官方变量;主题切换仍由官方 `--dsw-*` 覆盖完成。
|
|
74
|
-
- `styled` 不做 polymorphic(无 `as` / `asChild`):需要换渲染元素时直接 `styled('a')` 或包一层组件。
|
|
75
|
-
- 设置表单原语是上游三份源的对照副本,**没有漂移守卫**:上游升级后要人工 diff(纪律见
|
|
76
|
-
[ADR-设置表单原语搬进client半](./.agents/adrs/20260923-设置表单原语搬进client半.md))。
|
|
77
|
-
|
|
78
|
-
## schema 表单(2026-09-28 从 `@morlay/dsh-client-ui-schema-form` 合并进来)
|
|
79
|
-
|
|
80
|
-
两者都是"对话 UI / 设置页的基础面":拆成两行会各自注册一遍,还要求每个 bundle 都插齐两行。合并后仍只有
|
|
81
|
-
extbf{一行}(`ui-primitives-fork`),各 bundle 都插那一行——同 id 重复插入由 Loader 幂等处理。
|
|
82
|
-
|
|
83
|
-
把各插件 Config 里标了 `.volatile()` 的字段,按 schema 自动渲染成**那一行**的配置页(浏览器半)。插件只声明
|
|
84
|
-
schema,页面不用写客户端代码;字段级想换值控件或换文案,经一个 chain 槽注入。
|
|
85
|
-
|
|
86
|
-
配置页是**行式 JSON 结构视图**(不是逐字段的上下表单):
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
1 │ {
|
|
90
|
-
2 │ // 新会话用哪个模式
|
|
91
|
-
3 │ default: "coding"
|
|
92
|
-
4 │ modes: {
|
|
93
|
-
5 │ coding: {
|
|
94
|
-
6 │ // 能用的工具
|
|
95
|
-
7 │ allowTools: [ "read" ] ← 闭合行自带添加入口
|
|
96
|
-
8 │ }
|
|
97
|
-
9 │ }
|
|
98
|
-
10 │ models: {} ← 还没配的键在这个入口的候选里
|
|
99
|
-
11 │ access: [ ] ▾ ← 值本身就是下拉的触发
|
|
100
|
-
12 │ cwd: "/Users/morlay/repo" // 相对路径的基准目录 · 只读
|
|
101
|
-
13 │ }
|
|
11
|
+
import { styled, dsw, findReferences } from "@morlay/dsh-client-ui-primitives/client";
|
|
102
12
|
```
|
|
103
13
|
|
|
104
|
-
|
|
105
|
-
它出现在所在容器闭合行的添加入口候选里,选中才加成(模式清单这类候选键同样如此)。
|
|
106
|
-
判据就一条:**`undefined` = 这一层没有这个字段**(不渲染,去候选里);**`null` = 有字段、值还没填**——那一格直接
|
|
107
|
-
是一个等着输入的位子(不是 `""` / `0` 这种看着像已经填过的值)。加进来的项初值因此是:有默认值给默认值、容器给
|
|
108
|
-
空的容器、其余标量给 `null`;host 侧也认这个语义(非必填拿到 `null` 回退默认值,必填拿到 `null` 就是"必须填")。
|
|
109
|
-
- **行**:行号 + 折进 + `key: value`。**缩进只作用在行内容上**(行号列固定在左边,第一层就缩进一格);容器是
|
|
110
|
-
`{` / `[` 的开启行与 `}` / `]` 的闭合行,可折叠(`collapse()` 决定默认,点 chevron 反转)。点行号选中这一行。
|
|
111
|
-
- **改过的行看得出来**:本页改了还没保存的字段左边一道强调色,悬停里是**「撤回」**;已经存进用户层的用一道浅灰,
|
|
112
|
-
悬停里是**「恢复默认」**。两种状态之外还有「复制」与成员行的「移除」。
|
|
113
|
-
- **值**:语法色 token(字符串带引号、数字、布尔、`null`),**点开即改**——点开后行里只留输入框(原值不再在旁边画
|
|
114
|
-
一遍):官方 `Input`,旁边常显**确认(✓)/ 取消(✕)**两个按钮(确认收起这一格的编辑、取消把值退回去),
|
|
115
|
-
Enter 提交 / Esc 撤销同理;**带换行的值给同一观感的多行输入**(Enter 换行、高度跟着内容长)。非法输入在行内给出
|
|
116
|
-
消息并挡住保存。细线与描边取 theme 的 token(`dsw.elevation.stroke` 那一档),不自己写宽度与颜色。
|
|
117
|
-
- **注释行**:`// …` 画在对应行上方——`description` / `comment` 与业务文案(提示面给的 label/hint),以及「只读」
|
|
118
|
-
这类一眼看不出的状态;「已覆盖」不在这里(它是行尾那个「恢复默认」按钮的事)。
|
|
119
|
-
- **添加入口跟容器的闭合括号同一行**(输入框与行内编辑同一观感):对象列的是**还没配的声明字段**(带说明),
|
|
120
|
-
字典列的是业务注册的候选键(也能自己敲键名),数组是「追加一个空项」;粘一整段 `{…}` / `[…]` 则整层覆盖。对象
|
|
121
|
-
只认 schema 声明过的字段——敲一个没声明的键不会被写下去(host 也会挡)。
|
|
122
|
-
- **选择器**:字段有候选(字面量集合的 `union`,或提示面注册的候选值)时,**值本身就是下拉的触发**(同一个值不画
|
|
123
|
-
两遍),点开是官方菜单;候选依赖的兄弟字段一变就换一批。
|
|
124
|
-
- **变体切换**:`union` 的字段行尾给一个小触发——判别式 union 挂在**标签行**上(`type: "sqlite" ▾`,切到 `postgres`
|
|
125
|
-
就换一整套字段),没有标签的按值的形状切(`access` 在 `[ ]` 与 `""` 之间)。
|
|
126
|
-
- **只读字段**:`.disabled()` 的字段(装配事实这类)照常画出值、注释里标「只读」,但点不开编辑。
|
|
127
|
-
- **控件都是官方的**:行内输入用 `Input`、下拉与候选用 `Menu`,视觉与键盘行为跟设置页其余部分一致。
|
|
128
|
-
|
|
129
|
-
## 用法
|
|
130
|
-
|
|
131
|
-
业务插件:在自己的 `Config` 上标 `.volatile()` 就够了(页面自动出现在该行的配置入口里)。
|
|
14
|
+
按 schema 生成的那一页:
|
|
132
15
|
|
|
133
16
|
```ts
|
|
17
|
+
// 插件侧:Config 标 volatile,页面自动出现在该行的配置入口里
|
|
134
18
|
export const Config = z.object({
|
|
135
19
|
maxDepth: z.number().step(1).min(0).default(1).volatile(),
|
|
136
20
|
});
|
|
137
21
|
```
|
|
138
22
|
|
|
139
|
-
想给某个字段换控件或只改文案:注册到 chain 槽 `settings.schema-form.field`,按 `role` 或 `ns + path` 认领。
|
|
140
|
-
`select` 返回认领结果或 **`null`**(`null` 才是不认领,`undefined` 不是);命中后由注册方的组件渲染——只想改
|
|
141
|
-
文案就拿本包的默认控件包一层。
|
|
142
|
-
|
|
143
23
|
```tsx
|
|
24
|
+
// 想给某个字段换控件或只改文案:注册 chain 槽(select 返回 null 才是不认领)
|
|
144
25
|
import {
|
|
145
26
|
SchemaFieldDefault,
|
|
146
27
|
type SchemaFieldComponentProps,
|
|
147
|
-
} from "@morlay/dsh-client-ui-
|
|
148
|
-
|
|
149
|
-
/** 认领时给出的文案:与 `select` 的返回类型逐字一致(框架据此把 `matched` 认成 selector 的结果)。 */
|
|
150
|
-
type Match = { label: string; hint: string };
|
|
28
|
+
} from "@morlay/dsh-client-ui-primitives/client";
|
|
151
29
|
|
|
152
30
|
ctx.slots.inject("settings.schema-form.field", () =>
|
|
153
31
|
ctx.slots.register(
|
|
154
32
|
{
|
|
155
33
|
name: "settings.schema-form.field",
|
|
156
|
-
|
|
157
|
-
select: (field): Match | null =>
|
|
158
|
-
field.ns === "session-mode" && field.path[0] === "models" && field.path[2] === "provider"
|
|
159
|
-
? { label: "服务商", hint: "llm-openai-compatible 的 providers 里的键" }
|
|
160
|
-
: null,
|
|
34
|
+
select: (field) => (claim(field) ? { label: "服务商" } : null),
|
|
161
35
|
},
|
|
162
|
-
function ProviderField(field: SchemaFieldComponentProps<
|
|
163
|
-
return
|
|
164
|
-
<SchemaFieldDefault
|
|
165
|
-
owner={{ ...field, label: field.matched.label, hint: field.matched.hint }}
|
|
166
|
-
/>
|
|
167
|
-
);
|
|
36
|
+
function ProviderField(field: SchemaFieldComponentProps<{ label: string }>) {
|
|
37
|
+
return <SchemaFieldDefault owner={{ ...field, ...field.matched }} />;
|
|
168
38
|
},
|
|
169
39
|
),
|
|
170
40
|
);
|
|
171
41
|
```
|
|
172
42
|
|
|
173
|
-
|
|
174
|
-
动作(`field.actions`)只在字段是数组或字典时有意义;分组节点还有 `field.group`(开合)。
|
|
175
|
-
|
|
176
|
-
## 接缝
|
|
177
|
-
|
|
178
|
-
| 面 | 形状 | 谁用 |
|
|
179
|
-
| ----------------------------- | ------------------------------------------------------------------------------------------------------- | ------------ |
|
|
180
|
-
| `plugins.row.config`(keyed) | 本包为每个可渲染的行各注册一项,key = `<bundle 包名>#<行 id>`,`priority: 100` | 插件管理页 |
|
|
181
|
-
| `settings.schema-form.field` | chain 槽:`select(field)` 按 `role` 或 `ns + path` 认领字段,命中即渲染自己的组件(未命中落默认控件) | 业务插件 |
|
|
182
|
-
| `settings.schema-form.form` | 本包注册的 Factory:行入口经 `renderFactorySlot` 渲染它(字段槽的 children 声明收在它上面,只声明一次) | 本包(内部) |
|
|
183
|
-
| `./client` 出口 | `apply` + 槽契约类型 + `SchemaFieldDefault`(默认控件,供只改文案时复用) | 浏览器半 |
|
|
184
|
-
| `.` 出口 | 空 `apply`(占 Loader 行;本包没有 host 侧行为) | 装配 |
|
|
185
|
-
|
|
186
|
-
手写卡片用默认 `priority: 0` 注册,按 slots 的 cell winner 规则自然遮住这里的自动项,所以两种页面可以并存、
|
|
187
|
-
迁移不必一次做完。
|
|
188
|
-
|
|
189
|
-
## 能力
|
|
190
|
-
|
|
191
|
-
- **类型覆盖**:`object` / `dict` / `array` / `tuple` / `string` / `number` / `boolean` / `bitset` / `const` /
|
|
192
|
-
`union` / `intersect` / `any` / `never` / `transform` / `function` / 递归引用(收口成只读一行)。
|
|
193
|
-
- **分组折叠**:`collapse()` 的分组默认收起来,点标题行展开;折叠的组不渲染它的子树。
|
|
194
|
-
- **dict 的候选键**:schema 里写不出来的键(运行期清单,例如「会话模式」那一行的模式 id)由业务在客户端注册——
|
|
195
|
-
候选里有、值里没有的键进的是这一层的**添加入口候选**,选中才加成。用法见下面「提示面」一节。
|
|
196
|
-
- **动态容器的成员行**:数组的每一项、字典的每一个键都有自己的行——名字(数组项优先用项身份 `id`,否则 `#序号`;
|
|
197
|
-
字典用键名)加移除按钮,成员自己的字段排在它下面一级;空容器给一行占位(「还没有项 / 还没有键」)加添加入口。
|
|
198
|
-
按值展开时,成员的路径与名字都换成真实的键/序号(占位段 `*` 不会出现在页面上)。
|
|
199
|
-
- **数组的项身份**:item 是对象且带 `id`(或 `role('items', { mergeKey: 'x' })` 显式声明)时,页面上用它的值
|
|
200
|
-
当项标题,并在保存前校验同一层不重名——写盘仍按索引(host 的 path op 就是索引语义)。
|
|
201
|
-
- **判别式 union**:两种表达都认——① `z.union([z.object({ kind: z.const("a"), … }), …])`(分支自带标记字段);
|
|
202
|
-
② 共享标记字段的 `z.intersect([z.object({ shared, type: z.union([...]) }), z.union([z.object({ type: z.const("a"), … }), …])])`
|
|
203
|
-
(标记字段在共享层,因此它在页面上是**可切换的选择器**,分支字段跟着换)。tag 取显式声明
|
|
204
|
-
`role('union', { tag: 'kind' })`,否则取所有对象成员共有的唯一常量字段。
|
|
205
|
-
schemastery 没有 schema 层的查表式判别(`union` 按声明顺序逐个 try,第一个通过即胜出;`zod` 的
|
|
206
|
-
`discriminatedUnion` 只出现在浏览器侧的 RPC 校验里),所以 tag 是**渲染与本地校验**的选支依据:页面选变体不
|
|
207
|
-
依赖分支顺序,host 侧校验仍按顺序尝试。相交成员描述同一字段时(共享层的选择器与分支里的 `const` 标记)按路径
|
|
208
|
-
去重,先出现的那个留下。没有标签的 `union` 按**值的形状**选支(`z.union([z.array(z.string()), z.string()])` 这类
|
|
209
|
-
——`access` 就是它),页面上是同一个切换控件,切过去写目标变体的空值;带标签的写标签值(`type: "postgres"`)。
|
|
210
|
-
|
|
211
|
-
## 提示面:文案、候选键、候选值
|
|
212
|
-
|
|
213
|
-
业务知道、schema 表达不了的三类信息都放这里(`ctx.schemaFormHints`):**字段文案**(`describe`,行式视图画成注释
|
|
214
|
-
行)、**dict 的候选键**(`suggestKeys`,进的是这一层添加入口的候选)、**字段的候选值**(`select`,字段画成
|
|
215
|
-
选择器)。
|
|
216
|
-
|
|
217
|
-
三类注册都按路径定位,路径里的动态段写 `*`(模板路径)就覆盖每个模式、每个键——业务不必知道运行期有哪些键;
|
|
218
|
-
精确路径优先于模板。
|
|
219
|
-
|
|
220
|
-
```ts
|
|
221
|
-
const hints = ctx.get("schemaFormHints");
|
|
222
|
-
|
|
223
|
-
// 文案
|
|
224
|
-
hints?.describe("session-mode", ["models", "*", "provider"], () => ({
|
|
225
|
-
label: "服务商",
|
|
226
|
-
hint: "providers 里的键",
|
|
227
|
-
}));
|
|
228
|
-
|
|
229
|
-
// dict 的候选键(取到清单之后再注册,注册本身会通知一次)
|
|
230
|
-
hints?.suggestKeys("session-mode", ["models"], () => roster.modes.map((row) => row.id));
|
|
231
|
-
|
|
232
|
-
// 候选值:`dependsOn` 里声明的兄弟字段一变,候选就重算
|
|
233
|
-
hints?.select("session-mode", ["models", "*", "model"], {
|
|
234
|
-
dependsOn: [["provider"]],
|
|
235
|
-
options: (read) => modelsOf(read(["provider"])),
|
|
236
|
-
});
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
读数必须**同步**(投影发生在渲染帧里):异步来源(HTTP / remote)由业务自己取好再注册;数据变了但没重新注册时
|
|
240
|
-
主动调一次 `hints.refresh()`。选模型那一套(provider 目录 + 模型清单)的落点见
|
|
241
|
-
[`dsh-session-mode`](../../profile/dsh-session-mode/README.md)。
|
|
242
|
-
|
|
243
|
-
### 按 role 认领的候选源
|
|
244
|
-
|
|
245
|
-
字段自己说清"这是什么选择",业务不去记行 id 与路径:schema 上写 `role('select', { source })`,客户端注册那个
|
|
246
|
-
**具名源**(源可以被多行、多个字段复用):
|
|
43
|
+
设置表单原语是暂存式的:控件只上报用户输入,`save` 是唯一写盘点。
|
|
247
44
|
|
|
248
45
|
```ts
|
|
249
|
-
|
|
250
|
-
provider: z.string().role("select", { source: "llm-providers" }),
|
|
251
|
-
model: z.string().role("select", { source: "llm-models" }),
|
|
252
|
-
|
|
253
|
-
// client 侧(一次注册,两个源各管一件事)
|
|
254
|
-
hints?.source("llm-providers", { options: () => providers.map((p) => ({ value: p.id })) });
|
|
255
|
-
hints?.source("llm-models", {
|
|
256
|
-
dependsOn: [["provider"]],
|
|
257
|
-
options: (read) => modelsOf(read(["provider"])),
|
|
258
|
-
});
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
`kind` 是 `unknown` 的值、字段名换了行,源照样对得上——**认领靠 role,不靠 `ns + path`**。没写 `source`、或那个源
|
|
262
|
-
没注册时,退回按路径登记的那份(`hints.select(ns, path, …)` 仍然有效);两者都没有就还是文本编辑。
|
|
46
|
+
import { SettingsFormModel, settingsNumberField } from "@morlay/dsh-client-ui-primitives/client";
|
|
263
47
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
schemastery 的自定义属性位是 **`meta.extra`**(`any`),公开设置方法是 `.role(名字, extra)`——上游自己也这么用
|
|
267
|
-
(`percent()` 给 `role: 'slider'`、`regExp()` 给 `role: 'regexp'` + `extra.flag`)。本包认的两个约定都走这个通道:
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
z.array(z.object({ id: z.string() })).role("items", { mergeKey: "id" }); // 数组项的项身份
|
|
271
|
-
z.union([z.object({ kind: z.const("a") })]).role("union", { tag: "kind" }); // union 的判别标签
|
|
48
|
+
const form = new SettingsFormModel(scope, [settingsNumberField("timeoutMs")]);
|
|
272
49
|
```
|
|
273
50
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
想注册**自定义类型**(新的 `type` + 解析器)是另一件事:`Schema.extend(type, resolve)`,进程级全局注册。
|
|
278
|
-
|
|
279
|
-
## 判据与限制
|
|
280
|
-
|
|
281
|
-
- **Config 只能是 schemastery**(本包吃的就是它的形状):cordis 侧只要求 Standard Schema v1(zod / valibot 也
|
|
282
|
-
行),但 host 的 settings 描述只认带 `toJSON()` 的 schema(`packages/settings/settings/src/index.ts:425`),
|
|
283
|
-
客户端再用 `new Schema(json)` 重建——所以别的校验库写的 Config 进不了设置页,也就进不了这里的自动表单。
|
|
284
|
-
顺带一处 schemastery 特有:loader 写回配置时用 `Config.simplify`(`vendor/loader/src/index.ts:117`)。
|
|
285
|
-
- **什么能当页面根**:对象段、相交段(判别式那种),以及带 tag 的 `union`(成员都是对象);字面量 `union`
|
|
286
|
-
(整段是标量)不行——那会把写盘变成整段替换。注意 host 的 `volatileForm` 只认对象段与顶层 volatile,所以判别式
|
|
287
|
-
行 Config 要写成 `z.intersect([...]).volatile()`(或包一层 `z.object`)才会被描述出来——原因与销账条件见
|
|
288
|
-
[债务 判别式行 Config 要靠顶层 volatile 才被 describe](./.agents/debts/20260924-判别式行Config要靠顶层volatile才被describe.md)。
|
|
289
|
-
- **什么能上页面**:插件行 `Config` 里标了 `.volatile()` 的字段——host 的 `describe()` 只投影那部分。
|
|
290
|
-
**生效靠 Loader 重挂被改的行**(settings 写完经 `reconcileProfilePatches` 让 Loader 重装 entry),所以插件
|
|
291
|
-
装配时取一次配置快照即可;要在不重挂的前提下改(长生命周期对象)才需要现场读 `.get()`。
|
|
292
|
-
装配期决定的项(能力清单、子插件传参、进程 cwd 之类)想**只读可见**就用 `.volatile().disabled()`:`volatile`
|
|
293
|
-
让它进投影,`disabled` 让页面只画值不给编辑。注意 `volatile` 会把解析后的值变成**引用**(读它要 `.get()`),
|
|
294
|
-
所以交给上游基类的那份配置要先解包(落点见 [`dsh-sandbox-local`](../../sandbox/dsh-sandbox-local/README.md))
|
|
295
|
-
——判据与各包落点见[设计记录](./.agents/designs/20260924-schema自动生成行配置表单与字段槽注入.md)。
|
|
296
|
-
- 字段值的读写、暂存语义、schema 支持面与测试落点见[如何验证](./.agents/standards/how-to-verify.md)。
|
|
297
|
-
- 设计与取舍(为什么用 Factory、为什么字段槽只声明一次、为什么不引第二份表单原语)见
|
|
298
|
-
[设计记录](./.agents/designs/20260924-schema自动生成行配置表单与字段槽注入.md)。
|
|
299
|
-
- 装配是 profile bundle 的一行 insert:`packages/session/better-session/cordis.patch.yml` 的 `ui-schema-form`。
|
|
300
|
-
- `transform` 字段只读呈现(callback 不可逆,写回会让语义漂移);`function` / 构造器类型同样只读。
|
|
51
|
+
装配:一行 insert(行 id `ui-primitives-fork`,各 bundle 都插同一行,同 id 重复插入由 Loader 幂等处理),见
|
|
52
|
+
[`@morlay/better-session`](../../bundles/better-session/cordis.patch.yml) 的 patch。
|