@lmzhen/dsh-evolution-settings-ui 0.16.1 → 0.17.0-rc.1

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/lib/client.js CHANGED
@@ -214,55 +214,6 @@ window.__ModuleLoader__.load({
214
214
  }
215
215
  ]
216
216
  },
217
- {
218
- namespace: "evolution-memory",
219
- fields: [
220
- {
221
- id: "memoryChars",
222
- group: "memory",
223
- doc: "Character budget the memory store enforces for MEMORY.md.",
224
- label: "记忆库上限",
225
- hint: "长期记忆文件(MEMORY.md)最多多少字符。",
226
- control: "number",
227
- unit: "字符",
228
- values: [],
229
- valueLabels: []
230
- },
231
- {
232
- id: "userChars",
233
- group: "memory",
234
- doc: "Character budget the memory store enforces for USER.md.",
235
- label: "用户画像上限",
236
- hint: "用户画像文件(USER.md)最多多少字符。",
237
- control: "number",
238
- unit: "字符",
239
- values: [],
240
- valueLabels: []
241
- },
242
- {
243
- id: "addDatePrefix",
244
- group: "memory",
245
- doc: "Prefix stored memory entries with their date heading.",
246
- label: "记忆条目加日期",
247
- hint: "写入时在条目开头加上日期。",
248
- control: "switch",
249
- unit: "",
250
- values: [],
251
- valueLabels: []
252
- },
253
- {
254
- id: "maxConsolidationFailures",
255
- group: "memory",
256
- doc: "Consolidation failures one turn tolerates before the tool gives up.",
257
- label: "合并失败重试",
258
- hint: "合并记忆连续失败多少次后停止。",
259
- control: "number",
260
- unit: "次",
261
- values: [],
262
- valueLabels: []
263
- }
264
- ]
265
- },
266
217
  {
267
218
  namespace: "evolution-review",
268
219
  fields: [
@@ -354,7 +305,70 @@ window.__ModuleLoader__.load({
354
305
  ]
355
306
  },
356
307
  {
357
- namespace: "evolution-skills",
308
+ namespace: "memory-files",
309
+ fields: [
310
+ {
311
+ id: "memoryChars",
312
+ group: "memory",
313
+ doc: "Character budget the memory store enforces for MEMORY.md.",
314
+ label: "记忆库上限",
315
+ hint: "长期记忆文件(MEMORY.md)最多多少字符。",
316
+ control: "number",
317
+ unit: "字符",
318
+ values: [],
319
+ valueLabels: []
320
+ },
321
+ {
322
+ id: "userChars",
323
+ group: "memory",
324
+ doc: "Character budget the memory store enforces for USER.md.",
325
+ label: "用户画像上限",
326
+ hint: "用户画像文件(USER.md)最多多少字符。",
327
+ control: "number",
328
+ unit: "字符",
329
+ values: [],
330
+ valueLabels: []
331
+ },
332
+ {
333
+ id: "addDatePrefix",
334
+ group: "memory",
335
+ doc: "Prefix stored memory entries with their date heading.",
336
+ label: "记忆条目加日期",
337
+ hint: "写入时在条目开头加上日期。",
338
+ control: "switch",
339
+ unit: "",
340
+ values: [],
341
+ valueLabels: []
342
+ },
343
+ {
344
+ id: "maxConsolidationFailures",
345
+ group: "memory",
346
+ doc: "Consolidation failures one turn tolerates before the tool gives up.",
347
+ label: "合并失败重试",
348
+ hint: "合并记忆连续失败多少次后停止。",
349
+ control: "number",
350
+ unit: "次",
351
+ values: [],
352
+ valueLabels: []
353
+ }
354
+ ]
355
+ },
356
+ {
357
+ namespace: "tool-memory",
358
+ fields: [{
359
+ id: "entryPreviewChars",
360
+ group: "memory",
361
+ doc: "Characters of one memory entry shown in a tool result preview.",
362
+ label: "记忆预览长度",
363
+ hint: "列表里每条记忆最多显示多少字符。",
364
+ control: "number",
365
+ unit: "字符",
366
+ values: [],
367
+ valueLabels: []
368
+ }]
369
+ },
370
+ {
371
+ namespace: "tool-skill-manage",
358
372
  fields: [
359
373
  {
360
374
  id: "skillContentChars",
@@ -475,20 +489,37 @@ window.__ModuleLoader__.load({
475
489
  valueLabels: []
476
490
  }
477
491
  ]
492
+ }
493
+ ];
494
+ /** Every (bundle, row) pair the Plugins page can open a configuration page for. */
495
+ const CLIENT_ROW_SEATS = [
496
+ {
497
+ bundle: "@lmzhen/dsh-evolution-all",
498
+ rows: [
499
+ "evolution-curator",
500
+ "evolution-review",
501
+ "memory-files",
502
+ "tool-memory",
503
+ "tool-skill-manage"
504
+ ]
478
505
  },
479
506
  {
480
- namespace: "evolution-tool-memory",
481
- fields: [{
482
- id: "entryPreviewChars",
483
- group: "memory",
484
- doc: "Characters of one memory entry shown in a tool result preview.",
485
- label: "记忆预览长度",
486
- hint: "列表里每条记忆最多显示多少字符。",
487
- control: "number",
488
- unit: "字符",
489
- values: [],
490
- valueLabels: []
491
- }]
507
+ bundle: "@lmzhen/dsh-evolution-host",
508
+ rows: [
509
+ "evolution-curator",
510
+ "evolution-review",
511
+ "memory-files"
512
+ ]
513
+ },
514
+ {
515
+ bundle: "@lmzhen/dsh-evolution-preset",
516
+ rows: [
517
+ "evolution-curator",
518
+ "evolution-review",
519
+ "memory-files",
520
+ "tool-memory",
521
+ "tool-skill-manage"
522
+ ]
492
523
  }
493
524
  ];
494
525
  //#endregion
@@ -774,6 +805,7 @@ window.__ModuleLoader__.load({
774
805
  deployment: "默认",
775
806
  loading: "正在读取设置…",
776
807
  unavailable: "这个功能没有启动(对应的插件没有安装)",
808
+ seatMissing: "这个界面没有提供设置服务,参数只能看不能改",
777
809
  readonly: "本次会话不允许写入设置(临时运行模式)",
778
810
  apply: "保存",
779
811
  reset: "恢复默认值",
@@ -819,6 +851,7 @@ window.__ModuleLoader__.load({
819
851
  deployment: "default",
820
852
  loading: "reading settings…",
821
853
  unavailable: "this feature is not running (its plugin is not installed)",
854
+ seatMissing: "this shell serves no settings surface, so the parameters cannot be edited here",
822
855
  readonly: "settings cannot be written in this session (temporary mode)",
823
856
  apply: "Save",
824
857
  reset: "Reset to default",
@@ -863,10 +896,10 @@ window.__ModuleLoader__.load({
863
896
  */
864
897
  const NAMESPACE_TITLES = Object.freeze({
865
898
  "evolution-review": "cardReview",
866
- "evolution-memory": "cardMemory",
867
- "evolution-tool-memory": "cardToolMemory",
899
+ "memory-files": "cardMemory",
900
+ "tool-memory": "cardToolMemory",
868
901
  "evolution-curator": "cardCurator",
869
- "evolution-skills": "cardSkills"
902
+ "tool-skill-manage": "cardSkills"
870
903
  });
871
904
  /**
872
905
  * Resolve one message without the locale seat (tests and the fallback path).
@@ -910,7 +943,7 @@ window.__ModuleLoader__.load({
910
943
  * settings scope and its snapshot are declared by the shape the renderer hands
911
944
  * over (\`getSnapshot\`/\`subscribe\`, status/value/user/writable).
912
945
  *
913
- * Layout and behaviour follow the platform's own settings fields: a collapsed card
946
+ * header (namespace title, change count, chevron), then one block per field —
914
947
  * header (namespace title, change count, chevron), then one block per field —
915
948
  * label with its unit and source chip, a control HOLDING THE CURRENT VALUE, the
916
949
  * Chinese hint — and one 放弃修改/保存 pair at the card's foot. The control is
@@ -944,7 +977,7 @@ window.__ModuleLoader__.load({
944
977
  }
945
978
  }
946
979
  /** Whether a value is a plain section object. */
947
- function isSection(value) {
980
+ function isSection$1(value) {
948
981
  return typeof value === "object" && value !== null && !Array.isArray(value);
949
982
  }
950
983
  /** One field's control, typed from the registry. */
@@ -1004,22 +1037,21 @@ window.__ModuleLoader__.load({
1004
1037
  }, t("reset"))) : null);
1005
1038
  }
1006
1039
  /**
1007
- * Render one namespace's card.
1008
- * @param props - the card data, its callbacks, and the bound section hook.
1040
+ * Render one row's card body from a supplied snapshot.
1041
+ * @param props - the card data, its callbacks, and the row state to render.
1009
1042
  * @returns the card.
1010
1043
  */
1011
- function ParamCard(props) {
1012
- const { t, fields, write, clear, namespace } = props;
1044
+ function ParamCardView(props) {
1045
+ const { t, fields, write, clear, namespace, snapshot } = props;
1013
1046
  const [open, setOpen] = (0, react.useState)(false);
1014
1047
  const [draft, setDraft] = (0, react.useState)({});
1015
1048
  const [busy, setBusy] = (0, react.useState)(false);
1016
1049
  const [error, setError] = (0, react.useState)("");
1017
1050
  const [pending, setPending] = (0, react.useState)(null);
1018
- const snapshot = props.useParamSection((state) => state);
1019
1051
  if (snapshot.status === "loading") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("loading"));
1020
- if (snapshot.status === "unavailable") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("unavailable"));
1021
- const user = isSection(snapshot.user) ? snapshot.user : {};
1022
- const value = isSection(snapshot.value) ? snapshot.value : {};
1052
+ if (snapshot.status === "unavailable") return (0, react.createElement)("p", { className: "evolution-param-note" }, t(snapshot.reason === "seat-missing" ? "seatMissing" : "unavailable"));
1053
+ const user = isSection$1(snapshot.user) ? snapshot.user : {};
1054
+ const value = isSection$1(snapshot.value) ? snapshot.value : {};
1023
1055
  const disabled = !snapshot.writable;
1024
1056
  const locked = disabled || busy;
1025
1057
  const titleKey = NAMESPACE_TITLES[namespace];
@@ -1105,10 +1137,160 @@ window.__ModuleLoader__.load({
1105
1137
  onClick: save
1106
1138
  }, busy ? t("saving") : t("apply")))) : null);
1107
1139
  }
1140
+ /**
1141
+ * The card as our own section renders it: the row state comes from the hook seat the
1142
+ * renderer binds out of the inject face's `hooks` compartment (components never hold a
1143
+ * subscription of their own).
1144
+ * @param props - the card data, its callbacks, and the bound row hook.
1145
+ * @returns the card.
1146
+ */
1147
+ function ParamCard(props) {
1148
+ return ParamCardView({
1149
+ namespace: props.namespace,
1150
+ fields: props.fields,
1151
+ t: props.t,
1152
+ write: props.write,
1153
+ clear: props.clear,
1154
+ snapshot: props.useParamSection((state) => state)
1155
+ });
1156
+ }
1157
+ //#endregion
1158
+ //#region src/client/source.ts
1159
+ /** What a card shows when this UI found no settings seat at all. */
1160
+ const SEAT_MISSING = Object.freeze({
1161
+ status: "unavailable",
1162
+ value: void 0,
1163
+ user: void 0,
1164
+ writable: false,
1165
+ reason: "seat-missing"
1166
+ });
1167
+ /**
1168
+ * Whether a value is a plain section object.
1169
+ * @param value - the value to test.
1170
+ * @returns true for a non-null, non-array object.
1171
+ */
1172
+ function isSection(value) {
1173
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1174
+ }
1175
+ /**
1176
+ * Project one platform snapshot into the card's own shape.
1177
+ * @param snapshot - the seat's snapshot.
1178
+ * @returns the card's snapshot, with the reason an unavailable one is unavailable.
1179
+ */
1180
+ function sectionSnapshotOf(snapshot) {
1181
+ return {
1182
+ status: snapshot.status,
1183
+ value: isSection(snapshot.value) ? snapshot.value : void 0,
1184
+ user: snapshot.user,
1185
+ writable: snapshot.writable,
1186
+ ...snapshot.status === "unavailable" ? { reason: "not-served" } : {}
1187
+ };
1188
+ }
1189
+ /**
1190
+ * The live source one row's card binds.
1191
+ * @param seat - the probed settings seat, when the deployment composed it.
1192
+ * @param namespace - the row's entry id.
1193
+ * @returns the source; without a seat it reports the stated empty state and never notifies.
1194
+ */
1195
+ function sourceFor(seat, namespace) {
1196
+ if (seat === void 0) return {
1197
+ getSnapshot: () => SEAT_MISSING,
1198
+ subscribe: () => () => {}
1199
+ };
1200
+ const form = seat.get(namespace);
1201
+ return {
1202
+ getSnapshot: () => sectionSnapshotOf(form.getSnapshot()),
1203
+ subscribe: (listener) => form.subscribe(listener)
1204
+ };
1205
+ }
1206
+ /**
1207
+ * The write pair for one row.
1208
+ * @param seat - the probed settings seat, when the deployment composed it.
1209
+ * @param namespace - the row's entry id.
1210
+ * @returns the two calls a card's save/reset path uses; without a seat they resolve without writing.
1211
+ */
1212
+ function writeFaceFor(seat, namespace) {
1213
+ const form = () => seat?.get(namespace);
1214
+ return {
1215
+ write: async (field, value) => {
1216
+ await form()?.set(field, value);
1217
+ },
1218
+ clear: async (field) => {
1219
+ await form()?.unset(field);
1220
+ }
1221
+ };
1222
+ }
1223
+ //#endregion
1224
+ //#region src/client/RowConfigCard.ts
1225
+ /**
1226
+ * The Plugins page's per-row configuration entry for a family row.
1227
+ *
1228
+ * The page owns the settings subscription — it re-renders the entry — and passes the row's
1229
+ * form in, so this component renders the SAME card body as our own section, fed by the
1230
+ * page's state instead of the bound hook and therefore holding no subscription of its own.
1231
+ * `summary` answers the page's one-line slot: a row's description fallback, which stays
1232
+ * empty until the row actually overrides something, exactly as the page expects.
1233
+ * @module @lmzhen/dsh-evolution-settings-ui/client
1234
+ */
1235
+ /**
1236
+ * Count the keys this row's user layer holds.
1237
+ * @param user - the raw user section, as the page reports it.
1238
+ * @param fields - the row's fields.
1239
+ * @returns how many of them the user overrode.
1240
+ */
1241
+ function overridden(user, fields) {
1242
+ const layer = isSection(user) ? user : {};
1243
+ return fields.filter((field) => Object.hasOwn(layer, field.id)).length;
1244
+ }
1245
+ /**
1246
+ * Render one row's configuration entry.
1247
+ * @param props - the page's view/form seat plus this row's face.
1248
+ * @returns the entry; `summary` renders nothing for an untouched row (the page then keeps the row's own description).
1249
+ */
1250
+ function RowConfigCard(props) {
1251
+ const { t, fields, namespace, view, form } = props;
1252
+ if (form === void 0) return view === "summary" ? null : (0, react.createElement)("p", { className: "evolution-param-note" }, t("seatMissing"));
1253
+ const changed = overridden(form.state.user, fields);
1254
+ if (view === "summary") return changed === 0 ? null : (0, react.createElement)("span", null, t("changed").replace("{n}", String(changed)));
1255
+ return ParamCardView({
1256
+ namespace,
1257
+ fields,
1258
+ t,
1259
+ snapshot: sectionSnapshotOf(form.state),
1260
+ write: async (field, value) => {
1261
+ await form.mutate([{
1262
+ op: "set",
1263
+ path: [field],
1264
+ value
1265
+ }], form.state.revision);
1266
+ },
1267
+ clear: async (field) => {
1268
+ await form.mutate([{
1269
+ op: "unset",
1270
+ path: [field]
1271
+ }], form.state.revision);
1272
+ }
1273
+ });
1274
+ }
1108
1275
  //#endregion
1109
1276
  //#region src/client/seam.ts
1110
1277
  /** Our card slot, hosted by the section this bundle registers. */
1111
1278
  const CARD_SLOT = "evolution.namespace.card";
1279
+ /** The Plugins page's per-row configuration seat (declared by `ui-plugin-manager`). */
1280
+ const ROW_CONFIG_SLOT = "plugins.row.config";
1281
+ /**
1282
+ * The key the Plugins page files a row's configuration under.
1283
+ *
1284
+ * Spelled here because the page's own helper cannot be imported (a value import from
1285
+ * another plugin's client half is forbidden):
1286
+ * `ui-plugin-manager/src/client/config-ledger.ts:36` is the definition this mirrors.
1287
+ * @param bundle - the bundle package name that declares the row.
1288
+ * @param rowId - the row id that bundle's patch declares.
1289
+ * @returns the `plugins.row.config` key.
1290
+ */
1291
+ function rowConfigKey(bundle, rowId) {
1292
+ return bundle + "#" + rowId;
1293
+ }
1112
1294
  /**
1113
1295
  * The pending-window card's slot key.
1114
1296
  *
@@ -1238,17 +1420,20 @@ window.__ModuleLoader__.load({
1238
1420
  }
1239
1421
  //#endregion
1240
1422
  //#region src/client/index.ts
1241
- /** Required client services: the slot registry, the settings transport, the locale seat. */
1242
- const inject = [
1243
- "slots",
1244
- "settingsScope",
1245
- "locale"
1246
- ];
1423
+ /**
1424
+ * Required client services: the slot registry and the locale seat.
1425
+ *
1426
+ * The settings seat (`configForms`) is deliberately NOT here. A required inject for a
1427
+ * service the deployment did not compose leaves this whole browser half PENDING forever —
1428
+ * no error, no section, no UI — and the family is also installed without a settings
1429
+ * surface. It is probed instead, and the cards say what is missing.
1430
+ */
1431
+ const inject = ["slots", "locale"];
1247
1432
  /** Section id and nav order (the platform's own sections sit at 0/10/15/20). */
1248
1433
  const SECTION_ID = "evolution";
1249
1434
  const SECTION_ORDER = 25;
1250
1435
  /**
1251
- * Register the locale namespace, the section and its cards.
1436
+ * Register the locale namespace, the section, its cards, and the per-row configuration entries.
1252
1437
  * @param ctx - client cordis context.
1253
1438
  */
1254
1439
  function apply(ctx) {
@@ -1277,6 +1462,7 @@ window.__ModuleLoader__.load({
1277
1462
  }
1278
1463
  return bound(key);
1279
1464
  };
1465
+ const configForms = ctx.get("configForms");
1280
1466
  seam.slots.inject("settings.section", function* () {
1281
1467
  yield seam.slots.register({
1282
1468
  name: "settings.section",
@@ -1292,14 +1478,14 @@ window.__ModuleLoader__.load({
1292
1478
  });
1293
1479
  seam.slots.inject(CARD_SLOT, function* () {
1294
1480
  for (const section of CLIENT_PARAM_SECTIONS) {
1295
- const scope = seam.settingsScope.bind({ namespace: section.namespace });
1296
- const source = scope;
1481
+ const source = sourceFor(configForms, section.namespace);
1482
+ const { write, clear } = writeFaceFor(configForms, section.namespace);
1297
1483
  const face = () => ({
1298
1484
  namespace: section.namespace,
1299
1485
  fields: section.fields,
1300
1486
  t,
1301
- write: (field, value) => scope.set(field, value),
1302
- clear: (field) => scope.unset(field),
1487
+ write,
1488
+ clear,
1303
1489
  hooks: { paramSection: source }
1304
1490
  });
1305
1491
  yield seam.slots.register({
@@ -1318,6 +1504,22 @@ window.__ModuleLoader__.load({
1318
1504
  })
1319
1505
  }, PendingCard);
1320
1506
  });
1507
+ seam.slots.inject(ROW_CONFIG_SLOT, function* () {
1508
+ for (const seat of CLIENT_ROW_SEATS) for (const rowId of seat.rows) {
1509
+ const section = CLIENT_PARAM_SECTIONS.find((candidate) => candidate.namespace === rowId);
1510
+ if (section === void 0) continue;
1511
+ const face = () => ({
1512
+ namespace: rowId,
1513
+ fields: section.fields,
1514
+ t
1515
+ });
1516
+ yield seam.slots.register({
1517
+ name: ROW_CONFIG_SLOT,
1518
+ key: rowConfigKey(seat.bundle, rowId),
1519
+ inject: face
1520
+ }, RowConfigCard);
1521
+ }
1522
+ });
1321
1523
  }
1322
1524
  //#endregion
1323
1525
  exports.PENDING_CARD_KEY = PENDING_CARD_KEY;
@@ -6,7 +6,7 @@
6
6
  * settings scope and its snapshot are declared by the shape the renderer hands
7
7
  * over (\`getSnapshot\`/\`subscribe\`, status/value/user/writable).
8
8
  *
9
- * Layout and behaviour follow the platform's own settings fields: a collapsed card
9
+ * header (namespace title, change count, chevron), then one block per field —
10
10
  * header (namespace title, change count, chevron), then one block per field —
11
11
  * label with its unit and source chip, a control HOLDING THE CURRENT VALUE, the
12
12
  * Chinese hint — and one 放弃修改/保存 pair at the card's foot. The control is
@@ -49,5 +49,28 @@ export type ParamCardProps = Omit<ParamCardFace, 'hooks'> & {
49
49
  * @param props - the card data, its callbacks, and the bound section hook.
50
50
  * @returns the card.
51
51
  */
52
+ /** Props of the shared body: the card's own data plus the row state it renders. */
53
+ export interface ParamCardViewProps {
54
+ namespace: string;
55
+ fields: readonly ClientParamField[];
56
+ t: (key: MessageKey) => string;
57
+ write: (field: string, value: unknown) => Promise<void>;
58
+ clear: (field: string) => Promise<void>;
59
+ /** The current row state, from whichever source owns it (the bound hook or the page). */
60
+ snapshot: ParamSectionSnapshot;
61
+ }
62
+ /**
63
+ * Render one row's card body from a supplied snapshot.
64
+ * @param props - the card data, its callbacks, and the row state to render.
65
+ * @returns the card.
66
+ */
67
+ export declare function ParamCardView(props: ParamCardViewProps): ReactNode;
68
+ /**
69
+ * The card as our own section renders it: the row state comes from the hook seat the
70
+ * renderer binds out of the inject face's `hooks` compartment (components never hold a
71
+ * subscription of their own).
72
+ * @param props - the card data, its callbacks, and the bound row hook.
73
+ * @returns the card.
74
+ */
52
75
  export declare function ParamCard(props: ParamCardProps): ReactNode;
53
76
  //# sourceMappingURL=ParamCard.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The Plugins page's per-row configuration entry for a family row.
3
+ *
4
+ * The page owns the settings subscription — it re-renders the entry — and passes the row's
5
+ * form in, so this component renders the SAME card body as our own section, fed by the
6
+ * page's state instead of the bound hook and therefore holding no subscription of its own.
7
+ * `summary` answers the page's one-line slot: a row's description fallback, which stays
8
+ * empty until the row actually overrides something, exactly as the page expects.
9
+ * @module @lmzhen/dsh-evolution-settings-ui/client
10
+ */
11
+ import { type ReactNode } from 'react';
12
+ import type { ClientParamField } from './generated-params.ts';
13
+ import type { MessageKey } from './messages.ts';
14
+ import type { RowConfigViewProps } from './seam.ts';
15
+ /** The inject face every row registration carries (the page supplies `view` and `form`). */
16
+ export interface RowConfigFace {
17
+ /** The row's entry id, for the card title. */
18
+ namespace: string;
19
+ fields: readonly ClientParamField[];
20
+ t: (key: MessageKey) => string;
21
+ }
22
+ /** Props one row entry is rendered with: the page's seat plus this row's own face. */
23
+ export type RowConfigCardProps = RowConfigViewProps & RowConfigFace;
24
+ /**
25
+ * Render one row's configuration entry.
26
+ * @param props - the page's view/form seat plus this row's face.
27
+ * @returns the entry; `summary` renders nothing for an untouched row (the page then keeps the row's own description).
28
+ */
29
+ export declare function RowConfigCard(props: RowConfigCardProps): ReactNode;
30
+ //# sourceMappingURL=RowConfigCard.d.ts.map
@@ -29,4 +29,15 @@ export interface ClientParamSection {
29
29
  fields: readonly ClientParamField[];
30
30
  }
31
31
  export declare const CLIENT_PARAM_SECTIONS: readonly ClientParamSection[];
32
+ /**
33
+ * One bundle that mounts family rows: the plugins.row.config key of a row is
34
+ * <bundle package name>#<row id>. The names are the SOURCE-plane spellings -
35
+ * the release rescopes them with every other package literal.
36
+ */
37
+ export interface ClientRowSeat {
38
+ bundle: string;
39
+ rows: readonly string[];
40
+ }
41
+ /** Every (bundle, row) pair the Plugins page can open a configuration page for. */
42
+ export declare const CLIENT_ROW_SEATS: readonly ClientRowSeat[];
32
43
  //# sourceMappingURL=generated-params.d.ts.map
@@ -1,23 +1,41 @@
1
1
  /**
2
- * Browser half: one settings section ("自进化") hosting one card per namespace.
2
+ * Browser half: one settings section ("自进化") hosting one card per family row, plus the
3
+ * Plugins page's per-row configuration entry for each of those rows.
3
4
  *
4
5
  * Until 0.7.0 the cards lived inside the platform's own 插件 section, because
5
6
  * this bundle injected `@deepseek-ai/dsh-client-ui-settings-plugins` and claimed
6
7
  * its keyed `settings.plugin.item` slot. The parameters are not a plugin
7
8
  * inventory though — the other feature areas (market, skins, Web plugins, side
8
- * cards) each register their OWN section, so this bundle now does the same:
9
- * inject the settings shell, register `settings.section`, and host the cards in
10
- * a keyed child slot of that section.
9
+ * cards) each register their OWN section, so this bundle does the same: inject
10
+ * the settings shell, register `settings.section`, and host the cards in a keyed
11
+ * child slot of that section.
11
12
  *
12
- * The join key is still the namespace itself: the Host half of each owning plugin
13
- * registers it, this half claims it in the keyed card slot, and the section pairs
14
- * them. A namespace the Host does not serve renders nothing, and a deployment that
15
- * drops this row shows no section at all: the pair carries no Host-side behaviour.
13
+ * Since G2 that section is one of TWO render sites for the same card body. The other is
14
+ * the platform's Plugins page: each family row can be opened there, and the page renders
15
+ * whatever a plugin registered under key `<bundle package name>#<row id>`
16
+ * (`plugins.row.config`). The page owns that entry's subscription and hands the row's form
17
+ * in, so that path needs no seat of ours; our own section binds the same card to the
18
+ * client settings seat (`configForms`).
19
+ *
20
+ * The join key is the row's ENTRY ID — the platform derives a settings namespace from the
21
+ * Loader entry id, and that id is the registry's owner name — so the card, the seat lookup
22
+ * and the page key all spell one id. The legacy namespace strings in the registry
23
+ * (`evolution-memory`, …) are the G3 migration source, never a live lookup key.
24
+ *
25
+ * A row the Host does not serve renders nothing, and a deployment that drops this row
26
+ * shows no section at all: the pair carries no Host-side behaviour.
16
27
  * @module @lmzhen/dsh-evolution-settings-ui/client
17
28
  */
18
29
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
19
30
  import { PENDING_CARD_KEY } from './seam.ts';
20
- /** Required client services: the slot registry, the settings transport, the locale seat. */
31
+ /**
32
+ * Required client services: the slot registry and the locale seat.
33
+ *
34
+ * The settings seat (`configForms`) is deliberately NOT here. A required inject for a
35
+ * service the deployment did not compose leaves this whole browser half PENDING forever —
36
+ * no error, no section, no UI — and the family is also installed without a settings
37
+ * surface. It is probed instead, and the cards say what is missing.
38
+ */
21
39
  export declare const inject: string[];
22
40
  /** Section id and nav order (the platform's own sections sit at 0/10/15/20). */
23
41
  export declare const SECTION_ID = "evolution";
@@ -25,7 +43,7 @@ export declare const SECTION_ORDER = 25;
25
43
  /** The pending-window card's key, re-exported from `seam.ts` (the section names it as well). */
26
44
  export { PENDING_CARD_KEY };
27
45
  /**
28
- * Register the locale namespace, the section and its cards.
46
+ * Register the locale namespace, the section, its cards, and the per-row configuration entries.
29
47
  * @param ctx - client cordis context.
30
48
  */
31
49
  export declare function apply(ctx: ClientContext): void;
@@ -18,6 +18,7 @@ export declare const zh: {
18
18
  readonly deployment: "默认";
19
19
  readonly loading: "正在读取设置…";
20
20
  readonly unavailable: "这个功能没有启动(对应的插件没有安装)";
21
+ readonly seatMissing: "这个界面没有提供设置服务,参数只能看不能改";
21
22
  readonly readonly: "本次会话不允许写入设置(临时运行模式)";
22
23
  readonly apply: "保存";
23
24
  readonly reset: "恢复默认值";
@@ -2,17 +2,30 @@
2
2
  * Structural view of the client seams this bundle uses.
3
3
  *
4
4
  * The package is distributed OUTSIDE the platform repository, so it declares the
5
- * two seams it consumes instead of importing another plugin's values (forbidden
6
- * by the client bundle-purity rule) or its types (which would drag the platform
7
- * sources into this project). The shapes are the shell's contract: a
8
- * `settings.section` registration hosting a keyed card slot, the inject faces
9
- * both hand back, the per-namespace settings scope the write path fences with the
10
- * revision it read, and the locale seat the section's copy resolves through.
5
+ * seams it consumes instead of importing another plugin's values (forbidden by the
6
+ * client bundle-purity rule) or its types (which would drag the platform sources
7
+ * into this project). The shapes are the shell's contract: a `settings.section`
8
+ * registration hosting a keyed card slot, the keyed `plugins.row.config` seat the
9
+ * Plugins page opens per row, the client settings seat (`configForms`, one form per
10
+ * Host plugin entry id), and the locale seat the section's copy resolves through.
11
11
  * @module @lmzhen/dsh-evolution-settings-ui/client
12
12
  */
13
13
  import type { ReactNode } from 'react';
14
14
  /** Our card slot, hosted by the section this bundle registers. */
15
15
  export declare const CARD_SLOT = "evolution.namespace.card";
16
+ /** The Plugins page's per-row configuration seat (declared by `ui-plugin-manager`). */
17
+ export declare const ROW_CONFIG_SLOT = "plugins.row.config";
18
+ /**
19
+ * The key the Plugins page files a row's configuration under.
20
+ *
21
+ * Spelled here because the page's own helper cannot be imported (a value import from
22
+ * another plugin's client half is forbidden):
23
+ * `ui-plugin-manager/src/client/config-ledger.ts:36` is the definition this mirrors.
24
+ * @param bundle - the bundle package name that declares the row.
25
+ * @param rowId - the row id that bundle's patch declares.
26
+ * @returns the `plugins.row.config` key.
27
+ */
28
+ export declare function rowConfigKey(bundle: string, rowId: string): string;
16
29
  /**
17
30
  * The pending-window card's slot key.
18
31
  *
@@ -22,28 +35,71 @@ export declare const CARD_SLOT = "evolution.namespace.card";
22
35
  * the section — one definition in the module both sides already share is what keeps that pair acyclic.
23
36
  */
24
37
  export declare const PENDING_CARD_KEY = "approval-pending";
25
- /** Snapshot of one settings namespace, as the client scope reports it. */
38
+ /** Snapshot of one settings row, as the client seat reports it. */
26
39
  export interface ParamSectionSnapshot {
27
40
  status: 'loading' | 'ready' | 'unavailable';
28
41
  value: Record<string, unknown> | undefined;
29
42
  /** Raw user section: a key's PRESENCE marks the override, not its value. */
30
43
  user: unknown;
31
44
  writable: boolean;
45
+ /**
46
+ * Why an `unavailable` snapshot is unavailable: this bundle found no settings seat at
47
+ * all, or the seat is there and serves no such row. The two read differently to the
48
+ * operator (one is the UI's own gap, the other is a row that is not composed).
49
+ */
50
+ reason?: 'seat-missing' | 'not-served';
32
51
  }
33
52
  /** The reactive source the renderer binds to a `use<Name>` seat. */
34
53
  export interface ParamSectionSource {
35
54
  getSnapshot(): ParamSectionSnapshot;
36
55
  subscribe(listener: () => void): () => void;
37
56
  }
38
- /** One namespace's write face. */
39
- export interface SettingsScopeLike {
40
- set(field: string, value: unknown): Promise<void>;
41
- unset(field: string): Promise<void>;
57
+ /** One ordered field operation, as the client settings seat takes them. */
58
+ export interface SettingsPathOpLike {
59
+ readonly op: 'set' | 'unset';
60
+ readonly path: readonly string[];
61
+ readonly value?: unknown;
62
+ }
63
+ /** One row's client form state, as `configForms.get(entryId)` reports it. */
64
+ export interface ConfigFormSnapshotLike<T = Record<string, unknown>> {
65
+ status: 'loading' | 'ready' | 'unavailable';
66
+ value: T | undefined;
67
+ /** Composition layer the Host resolved `value` over; what a cleared field reverts to. */
68
+ base: unknown;
69
+ /** Raw user layer: a key's PRESENCE marks the override. */
70
+ user: unknown;
71
+ revision: number | undefined;
72
+ writable: boolean;
73
+ mode: 'host' | 'memory';
74
+ }
75
+ /** One row's client write face (0.2.x `configForms`). */
76
+ export interface ConfigFormLike<T = Record<string, unknown>> {
77
+ getSnapshot(): ConfigFormSnapshotLike<T>;
78
+ subscribe(listener: () => void): () => void;
79
+ set(field: string, value: unknown): Promise<boolean>;
80
+ unset(field: string): Promise<boolean>;
81
+ mutate(ops: readonly SettingsPathOpLike[], expectedRevision?: number): Promise<boolean>;
82
+ }
83
+ /**
84
+ * The client settings seat, probed rather than injected: a deployment may compose the
85
+ * family without the settings surface, and a REQUIRED inject for a missing service
86
+ * leaves the whole browser half pending forever (no error, no UI).
87
+ */
88
+ export interface ConfigFormsSeat {
89
+ /** One Host plugin entry id to its form. */
90
+ get<T>(entryId: string): ConfigFormLike<T>;
42
91
  }
43
92
  /** Registration options the keyed card slot under our own section takes. */
44
93
  export interface SlotRegisterOptions {
45
94
  name: typeof CARD_SLOT;
46
- /** The settings namespace this card edits — the slot's key. */
95
+ /** The settings row this card edits — the slot's key. */
96
+ key: string;
97
+ inject: () => unknown;
98
+ }
99
+ /** Registration options the Plugins page's per-row config seat takes. */
100
+ export interface RowSeatRegisterOptions {
101
+ name: typeof ROW_CONFIG_SLOT;
102
+ /** `<bundle package name>#<row id>`, as the page's ledger builds it. */
47
103
  key: string;
48
104
  inject: () => unknown;
49
105
  }
@@ -73,6 +129,23 @@ export interface SectionFace {
73
129
  /** Render one child slot; `filter` selects keyed entries (e.g. `{ entryKey }`). */
74
130
  renderSlot: (name: string, props?: Record<string, unknown>, filter?: Record<string, unknown>) => ReactNode;
75
131
  }
132
+ /**
133
+ * The values and commands the Plugins page passes to a `plugins.row.config` entry:
134
+ * the page owns the subscription (it re-renders the entry), so the entry renders from
135
+ * `state` and writes through `mutate`. Both views receive this face; `summary` uses it
136
+ * only to say whether anything is overridden.
137
+ */
138
+ export interface RowConfigViewProps {
139
+ /** `summary` is the row's one-line description fallback; `page` is the whole form. */
140
+ readonly view: 'summary' | 'page';
141
+ /** Absent when the Host serves no configuration for this row. */
142
+ readonly form?: RowConfigForm | undefined;
143
+ }
144
+ /** The row form the page derived from its settings seat. */
145
+ export interface RowConfigForm {
146
+ readonly state: ConfigFormSnapshotLike;
147
+ readonly mutate: (ops: readonly SettingsPathOpLike[], revision?: number) => Promise<boolean>;
148
+ }
76
149
  /** The locale seat: register this bundle's namespace, bind a translator. */
77
150
  export interface LocaleSeat {
78
151
  register(namespace: string, dictionaries: {
@@ -85,12 +158,7 @@ export interface LocaleSeat {
85
158
  export interface ClientSeam {
86
159
  slots: {
87
160
  inject(name: string, callback: () => Generator<unknown, void, unknown>): unknown;
88
- register(options: SlotRegisterOptions | SectionRegisterOptions, component: (props: never) => unknown): () => void;
89
- };
90
- settingsScope: {
91
- bind(spec: {
92
- namespace: string;
93
- }): SettingsScopeLike;
161
+ register(options: SlotRegisterOptions | RowSeatRegisterOptions | SectionRegisterOptions, component: (props: never) => unknown): () => void;
94
162
  };
95
163
  locale: LocaleSeat;
96
164
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The adapter between the platform's client settings seat and this bundle's cards.
3
+ *
4
+ * One place maps `configForms.get(entryId)` to the three things a card needs — a
5
+ * snapshot, a subscription and the two write calls — so our own section's cards and the
6
+ * Plugins page's per-row entry cannot drift apart in how they read or write a row.
7
+ * @module @lmzhen/dsh-evolution-settings-ui/client
8
+ */
9
+ import type { ConfigFormSnapshotLike, ConfigFormsSeat, ParamSectionSnapshot, ParamSectionSource } from './seam.ts';
10
+ /** What a card shows when this UI found no settings seat at all. */
11
+ export declare const SEAT_MISSING: ParamSectionSnapshot;
12
+ /**
13
+ * Whether a value is a plain section object.
14
+ * @param value - the value to test.
15
+ * @returns true for a non-null, non-array object.
16
+ */
17
+ export declare function isSection(value: unknown): value is Record<string, unknown>;
18
+ /**
19
+ * Project one platform snapshot into the card's own shape.
20
+ * @param snapshot - the seat's snapshot.
21
+ * @returns the card's snapshot, with the reason an unavailable one is unavailable.
22
+ */
23
+ export declare function sectionSnapshotOf(snapshot: ConfigFormSnapshotLike): ParamSectionSnapshot;
24
+ /**
25
+ * The live source one row's card binds.
26
+ * @param seat - the probed settings seat, when the deployment composed it.
27
+ * @param namespace - the row's entry id.
28
+ * @returns the source; without a seat it reports the stated empty state and never notifies.
29
+ */
30
+ export declare function sourceFor(seat: ConfigFormsSeat | undefined, namespace: string): ParamSectionSource;
31
+ /**
32
+ * The write pair for one row.
33
+ * @param seat - the probed settings seat, when the deployment composed it.
34
+ * @param namespace - the row's entry id.
35
+ * @returns the two calls a card's save/reset path uses; without a seat they resolve without writing.
36
+ */
37
+ export declare function writeFaceFor(seat: ConfigFormsSeat | undefined, namespace: string): {
38
+ write: (field: string, value: unknown) => Promise<void>;
39
+ clear: (field: string) => Promise<void>;
40
+ };
41
+ //# sourceMappingURL=source.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-settings-ui",
3
3
  "description": "Web settings cards for the evolution family's parameter namespaces (community build)",
4
- "version": "0.16.1",
4
+ "version": "0.17.0-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },