@lmzhen/dsh-evolution-settings-ui 0.22.0 → 0.23.0

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
@@ -89,7 +89,27 @@ window.__ModuleLoader__.load({
89
89
  control: "number",
90
90
  unit: "毫秒",
91
91
  values: [],
92
- valueLabels: []
92
+ valueLabels: [],
93
+ atLeast: ""
94
+ }, {
95
+ id: "approvalConfirm",
96
+ group: "write-caps",
97
+ doc: "What the confirmation before an APPROVE does: write straight through, ask first, or treat an unanswered prompt as a cancel. The same three words skillWriteConfirm uses for a skill write.",
98
+ label: "批准前先确认",
99
+ hint: "点「批准」时要不要再确认一次:直接执行 / 先确认 / 等待超时即取消。默认直接执行(不打断批量批准)。",
100
+ control: "select",
101
+ unit: "",
102
+ values: [
103
+ "auto",
104
+ "ask",
105
+ "timeout"
106
+ ],
107
+ valueLabels: [
108
+ "直接执行",
109
+ "先确认",
110
+ "等待超时即取消"
111
+ ],
112
+ atLeast: ""
93
113
  }]
94
114
  },
95
115
  {
@@ -104,7 +124,8 @@ window.__ModuleLoader__.load({
104
124
  control: "switch",
105
125
  unit: "",
106
126
  values: [],
107
- valueLabels: []
127
+ valueLabels: [],
128
+ atLeast: ""
108
129
  }]
109
130
  },
110
131
  {
@@ -120,7 +141,8 @@ window.__ModuleLoader__.load({
120
141
  control: "number",
121
142
  unit: "小时",
122
143
  values: [],
123
- valueLabels: []
144
+ valueLabels: [],
145
+ atLeast: ""
124
146
  },
125
147
  {
126
148
  id: "staleAfterDays",
@@ -131,7 +153,8 @@ window.__ModuleLoader__.load({
131
153
  control: "number",
132
154
  unit: "天",
133
155
  values: [],
134
- valueLabels: []
156
+ valueLabels: [],
157
+ atLeast: ""
135
158
  },
136
159
  {
137
160
  id: "archiveAfterDays",
@@ -142,7 +165,8 @@ window.__ModuleLoader__.load({
142
165
  control: "number",
143
166
  unit: "天",
144
167
  values: [],
145
- valueLabels: []
168
+ valueLabels: [],
169
+ atLeast: "staleAfterDays"
146
170
  },
147
171
  {
148
172
  id: "minIdleHours",
@@ -153,7 +177,8 @@ window.__ModuleLoader__.load({
153
177
  control: "number",
154
178
  unit: "小时",
155
179
  values: [],
156
- valueLabels: []
180
+ valueLabels: [],
181
+ atLeast: ""
157
182
  },
158
183
  {
159
184
  id: "minIdleFailOpen",
@@ -164,7 +189,8 @@ window.__ModuleLoader__.load({
164
189
  control: "switch",
165
190
  unit: "",
166
191
  values: [],
167
- valueLabels: []
192
+ valueLabels: [],
193
+ atLeast: ""
168
194
  },
169
195
  {
170
196
  id: "llmReview",
@@ -175,7 +201,8 @@ window.__ModuleLoader__.load({
175
201
  control: "switch",
176
202
  unit: "",
177
203
  values: [],
178
- valueLabels: []
204
+ valueLabels: [],
205
+ atLeast: ""
179
206
  },
180
207
  {
181
208
  id: "curatorReviewMaxTokens",
@@ -186,7 +213,8 @@ window.__ModuleLoader__.load({
186
213
  control: "number",
187
214
  unit: "token",
188
215
  values: [],
189
- valueLabels: []
216
+ valueLabels: [],
217
+ atLeast: ""
190
218
  },
191
219
  {
192
220
  id: "curatorReviewTimeoutMs",
@@ -197,7 +225,8 @@ window.__ModuleLoader__.load({
197
225
  control: "number",
198
226
  unit: "毫秒",
199
227
  values: [],
200
- valueLabels: []
228
+ valueLabels: [],
229
+ atLeast: ""
201
230
  },
202
231
  {
203
232
  id: "healthSoftBodyChars",
@@ -208,7 +237,8 @@ window.__ModuleLoader__.load({
208
237
  control: "number",
209
238
  unit: "字符",
210
239
  values: [],
211
- valueLabels: []
240
+ valueLabels: [],
241
+ atLeast: ""
212
242
  },
213
243
  {
214
244
  id: "healthStampDensityPerKb",
@@ -219,7 +249,8 @@ window.__ModuleLoader__.load({
219
249
  control: "number",
220
250
  unit: "处/KB",
221
251
  values: [],
222
- valueLabels: []
252
+ valueLabels: [],
253
+ atLeast: ""
223
254
  },
224
255
  {
225
256
  id: "healthChurnMinPatches",
@@ -230,7 +261,8 @@ window.__ModuleLoader__.load({
230
261
  control: "number",
231
262
  unit: "处",
232
263
  values: [],
233
- valueLabels: []
264
+ valueLabels: [],
265
+ atLeast: ""
234
266
  }
235
267
  ]
236
268
  },
@@ -247,7 +279,8 @@ window.__ModuleLoader__.load({
247
279
  control: "number",
248
280
  unit: "次",
249
281
  values: [],
250
- valueLabels: []
282
+ valueLabels: [],
283
+ atLeast: ""
251
284
  },
252
285
  {
253
286
  id: "reviewMemoryInterval",
@@ -258,7 +291,8 @@ window.__ModuleLoader__.load({
258
291
  control: "number",
259
292
  unit: "次",
260
293
  values: [],
261
- valueLabels: []
294
+ valueLabels: [],
295
+ atLeast: ""
262
296
  },
263
297
  {
264
298
  id: "skillReviewTrigger",
@@ -277,7 +311,8 @@ window.__ModuleLoader__.load({
277
311
  "按间隔",
278
312
  "按任务完成",
279
313
  "两者"
280
- ]
314
+ ],
315
+ atLeast: ""
281
316
  },
282
317
  {
283
318
  id: "skillReviewCompletionMinToolCalls",
@@ -288,7 +323,8 @@ window.__ModuleLoader__.load({
288
323
  control: "number",
289
324
  unit: "次",
290
325
  values: [],
291
- valueLabels: []
326
+ valueLabels: [],
327
+ atLeast: ""
292
328
  },
293
329
  {
294
330
  id: "reviewEnabled",
@@ -299,7 +335,8 @@ window.__ModuleLoader__.load({
299
335
  control: "switch",
300
336
  unit: "",
301
337
  values: [],
302
- valueLabels: []
338
+ valueLabels: [],
339
+ atLeast: ""
303
340
  },
304
341
  {
305
342
  id: "reviewMode",
@@ -310,7 +347,8 @@ window.__ModuleLoader__.load({
310
347
  control: "select",
311
348
  unit: "",
312
349
  values: ["subagent", "inject"],
313
- valueLabels: ["交给子代理", "在当前会话里"]
350
+ valueLabels: ["交给子代理", "在当前会话里"],
351
+ atLeast: ""
314
352
  },
315
353
  {
316
354
  id: "reviewWakeInject",
@@ -321,7 +359,8 @@ window.__ModuleLoader__.load({
321
359
  control: "switch",
322
360
  unit: "",
323
361
  values: [],
324
- valueLabels: []
362
+ valueLabels: [],
363
+ atLeast: ""
325
364
  }
326
365
  ]
327
366
  },
@@ -338,7 +377,8 @@ window.__ModuleLoader__.load({
338
377
  control: "number",
339
378
  unit: "字符",
340
379
  values: [],
341
- valueLabels: []
380
+ valueLabels: [],
381
+ atLeast: ""
342
382
  },
343
383
  {
344
384
  id: "userChars",
@@ -349,7 +389,8 @@ window.__ModuleLoader__.load({
349
389
  control: "number",
350
390
  unit: "字符",
351
391
  values: [],
352
- valueLabels: []
392
+ valueLabels: [],
393
+ atLeast: ""
353
394
  },
354
395
  {
355
396
  id: "addDatePrefix",
@@ -360,7 +401,8 @@ window.__ModuleLoader__.load({
360
401
  control: "switch",
361
402
  unit: "",
362
403
  values: [],
363
- valueLabels: []
404
+ valueLabels: [],
405
+ atLeast: ""
364
406
  },
365
407
  {
366
408
  id: "maxConsolidationFailures",
@@ -371,7 +413,8 @@ window.__ModuleLoader__.load({
371
413
  control: "number",
372
414
  unit: "次",
373
415
  values: [],
374
- valueLabels: []
416
+ valueLabels: [],
417
+ atLeast: ""
375
418
  }
376
419
  ]
377
420
  },
@@ -387,7 +430,8 @@ window.__ModuleLoader__.load({
387
430
  control: "number",
388
431
  unit: "字符",
389
432
  values: [],
390
- valueLabels: []
433
+ valueLabels: [],
434
+ atLeast: ""
391
435
  }]
392
436
  },
393
437
  {
@@ -403,7 +447,8 @@ window.__ModuleLoader__.load({
403
447
  control: "number",
404
448
  unit: "字符",
405
449
  values: [],
406
- valueLabels: []
450
+ valueLabels: [],
451
+ atLeast: ""
407
452
  },
408
453
  {
409
454
  id: "maxSkillFileBytes",
@@ -414,7 +459,8 @@ window.__ModuleLoader__.load({
414
459
  control: "number",
415
460
  unit: "字节",
416
461
  values: [],
417
- valueLabels: []
462
+ valueLabels: [],
463
+ atLeast: ""
418
464
  },
419
465
  {
420
466
  id: "maxSkillNameLength",
@@ -425,7 +471,8 @@ window.__ModuleLoader__.load({
425
471
  control: "number",
426
472
  unit: "字符",
427
473
  values: [],
428
- valueLabels: []
474
+ valueLabels: [],
475
+ atLeast: ""
429
476
  },
430
477
  {
431
478
  id: "maxDescriptionLength",
@@ -436,7 +483,8 @@ window.__ModuleLoader__.load({
436
483
  control: "number",
437
484
  unit: "字符",
438
485
  values: [],
439
- valueLabels: []
486
+ valueLabels: [],
487
+ atLeast: ""
440
488
  },
441
489
  {
442
490
  id: "descriptionStrict",
@@ -447,7 +495,8 @@ window.__ModuleLoader__.load({
447
495
  control: "switch",
448
496
  unit: "",
449
497
  values: [],
450
- valueLabels: []
498
+ valueLabels: [],
499
+ atLeast: ""
451
500
  },
452
501
  {
453
502
  id: "strictCrossSource",
@@ -458,7 +507,8 @@ window.__ModuleLoader__.load({
458
507
  control: "switch",
459
508
  unit: "",
460
509
  values: [],
461
- valueLabels: []
510
+ valueLabels: [],
511
+ atLeast: ""
462
512
  },
463
513
  {
464
514
  id: "citationPolicy",
@@ -469,7 +519,8 @@ window.__ModuleLoader__.load({
469
519
  control: "select",
470
520
  unit: "",
471
521
  values: ["verify", "refuse"],
472
- valueLabels: ["先检查", "直接拒绝"]
522
+ valueLabels: ["先检查", "直接拒绝"],
523
+ atLeast: ""
473
524
  },
474
525
  {
475
526
  id: "supportFileCharPolicy",
@@ -480,7 +531,8 @@ window.__ModuleLoader__.load({
480
531
  control: "select",
481
532
  unit: "",
482
533
  values: ["report", "enforce"],
483
- valueLabels: ["只提醒", "拒绝写入"]
534
+ valueLabels: ["只提醒", "拒绝写入"],
535
+ atLeast: ""
484
536
  },
485
537
  {
486
538
  id: "skillWriteConfirm",
@@ -499,7 +551,8 @@ window.__ModuleLoader__.load({
499
551
  "不弹窗直接写入",
500
552
  "一直等待回答",
501
553
  "超时自动取消"
502
- ]
554
+ ],
555
+ atLeast: ""
503
556
  },
504
557
  {
505
558
  id: "skillWriteConfirmTimeoutSeconds",
@@ -510,7 +563,8 @@ window.__ModuleLoader__.load({
510
563
  control: "number",
511
564
  unit: "秒",
512
565
  values: [],
513
- valueLabels: []
566
+ valueLabels: [],
567
+ atLeast: ""
514
568
  }
515
569
  ]
516
570
  }
@@ -655,6 +709,17 @@ window.__ModuleLoader__.load({
655
709
  * refusal is shown with the host's own sentence.
656
710
  * @module @lmzhen/dsh-evolution-settings-ui/client/PendingCard
657
711
  */
712
+ /**
713
+ * U-6: the registry's gate word, read out of a section's user layer. ONE definition, exported so the
714
+ * mapping itself is judgeable: the entry does the wiring, this does the reading. Anything that is not
715
+ * one of the two explicit words falls back to the shipped default (`auto`) — a document written by a
716
+ * future version must not silently turn the confirmation ON, and a typo must not turn it off. The
717
+ * independent review flagged that this reader had no case at all (P2-11).
718
+ */
719
+ function confirmModeFrom(user) {
720
+ const value = typeof user === "object" && user !== null ? user.approvalConfirm : void 0;
721
+ return value === "ask" || value === "timeout" ? value : "auto";
722
+ }
658
723
  /** Which word one age bucket gets; an unknown bucket falls back to the count form. */
659
724
  const AGE_KEYS = Object.freeze({
660
725
  now: "approvalAgeNow",
@@ -676,6 +741,8 @@ window.__ModuleLoader__.load({
676
741
  */
677
742
  function PendingCard(props) {
678
743
  const { t, api } = props;
744
+ const confirmMode = props.confirmMode?.() ?? "auto";
745
+ const [armed, setArmed] = (0, react.useState)(null);
679
746
  const [state, setState] = (0, react.useState)(INITIAL_PENDING_STATE);
680
747
  const [open, setOpen] = (0, react.useState)(true);
681
748
  const latest = (0, react.useRef)(state);
@@ -731,7 +798,7 @@ window.__ModuleLoader__.load({
731
798
  const age = (row) => {
732
799
  const key = AGE_KEYS[row.age.unit];
733
800
  if (key === void 0) return String(row.age.n);
734
- return t(key).replace("{n}", String(row.age.n));
801
+ return t(key, { n: row.age.n });
735
802
  };
736
803
  /** Ask the host what this record would store; the answer lands only if the row is still open. */
737
804
  const loadPreview = async (id) => {
@@ -758,18 +825,27 @@ window.__ModuleLoader__.load({
758
825
  };
759
826
  /** The facts line, then the source view: the same shape the history panel's diff uses. */
760
827
  const preview = (view) => {
761
- if (view.kind === "loading") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("approvalPreviewReading"));
762
- if (view.kind === "failed") return (0, react.createElement)("p", { className: "evolution-param-error" }, view.message);
763
- if (view.kind === "unavailable") return (0, react.createElement)("p", { className: "evolution-param-error" }, t("approvalPreviewNone") + view.reason);
828
+ if (view.kind === "loading") return (0, react.createElement)("p", {
829
+ className: "evolution-param-note",
830
+ role: "status"
831
+ }, t("approvalPreviewReading"));
832
+ if (view.kind === "failed") return (0, react.createElement)("p", {
833
+ className: "evolution-param-error",
834
+ role: "status"
835
+ }, view.message);
836
+ if (view.kind === "unavailable") return (0, react.createElement)("p", {
837
+ className: "evolution-param-error",
838
+ role: "status"
839
+ }, t("approvalPreviewNone") + view.reason);
764
840
  const facts = [
765
841
  (0, react.createElement)("span", {
766
842
  className: "evolution-param-fact",
767
843
  key: "added"
768
- }, t("approvalDiffAdded").replace("{n}", String(view.answer.linesAdded))),
844
+ }, t("approvalDiffAdded", { n: view.answer.linesAdded })),
769
845
  (0, react.createElement)("span", {
770
846
  className: "evolution-param-fact",
771
847
  key: "removed"
772
- }, t("approvalDiffRemoved").replace("{n}", String(view.answer.linesRemoved))),
848
+ }, t("approvalDiffRemoved", { n: view.answer.linesRemoved })),
773
849
  ...view.answer.truncated ? [(0, react.createElement)("span", {
774
850
  className: "evolution-param-fact",
775
851
  key: "cut"
@@ -798,38 +874,79 @@ window.__ModuleLoader__.load({
798
874
  if (entry.status === "executing") return (0, react.createElement)("div", {
799
875
  className: "evolution-param-field",
800
876
  key: entry.id
801
- }, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", { className: "evolution-param-value" }, entry.summary)), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), (0, react.createElement)("p", { className: "evolution-param-note" }, t("approvalExecuting")), entry.claimedBy === void 0 ? null : (0, react.createElement)("p", { className: "evolution-param-hint" }, entry.claimedBy));
877
+ }, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", { className: "evolution-param-value" }, entry.summary)), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), (0, react.createElement)("p", {
878
+ className: "evolution-param-note",
879
+ role: "status"
880
+ }, t("approvalExecuting")), entry.claimedBy === void 0 ? null : (0, react.createElement)("p", { className: "evolution-param-hint" }, entry.claimedBy));
802
881
  const busy = state.busy.includes(entry.id);
803
- const button = (label, action) => (0, react.createElement)("button", {
882
+ const button = (label, action, tone) => (0, react.createElement)("button", {
804
883
  className: "evolution-param-button",
805
884
  type: "button",
806
885
  disabled: busy,
886
+ ...tone === "primary" ? { "data-primary": "true" } : { "data-tone": tone },
807
887
  onClick: () => {
888
+ if (action === "approve" && confirmMode === "ask" && armed !== entry.id) {
889
+ setArmed(entry.id);
890
+ return;
891
+ }
892
+ setArmed(null);
808
893
  decide(entry.id, action);
809
894
  }
810
895
  }, busy ? t("approvalLoading") : t(label));
811
896
  return (0, react.createElement)("div", {
812
897
  className: "evolution-param-field",
813
898
  key: entry.id
814
- }, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", { className: "evolution-param-value" }, entry.summary)), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), (0, react.createElement)("div", { className: "evolution-param-actions" }, button("approvalApprove", "approve"), button("approvalReject", "reject"), (0, react.createElement)("button", {
899
+ }, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", { className: "evolution-param-value" }, entry.summary)), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), armed === entry.id ? (0, react.createElement)("p", {
900
+ className: "evolution-param-note",
901
+ role: "status"
902
+ }, (0, react.createElement)("span", { className: "evolution-param-value" }, t("approvalConfirmNamed", { n: entry.summary })), (0, react.createElement)("button", {
903
+ className: "evolution-param-button",
904
+ type: "button",
905
+ "data-confirm": "yes",
906
+ "data-primary": "true",
907
+ onClick: () => {
908
+ setArmed(null);
909
+ decide(entry.id, "approve");
910
+ }
911
+ }, t("approvalConfirmYes")), (0, react.createElement)("button", {
912
+ className: "evolution-param-button",
913
+ type: "button",
914
+ "data-tone": "ghost",
915
+ onClick: () => {
916
+ setArmed(null);
917
+ }
918
+ }, t("approvalConfirmNo"))) : null, (0, react.createElement)("div", { className: "evolution-param-actions" }, button("approvalApprove", "approve", "primary"), button("approvalReject", "reject", "outline"), (0, react.createElement)("button", {
815
919
  className: "evolution-param-button",
816
920
  type: "button",
921
+ "data-tone": "ghost",
817
922
  onClick: () => {
818
923
  togglePreview(entry.id);
819
924
  }
820
925
  }, state.open === entry.id ? t("approvalPreviewClose") : t("approvalPreview"))), state.open === entry.id && state.preview !== null ? preview(state.preview) : null);
821
926
  };
822
927
  const body = () => {
823
- if (state.view.kind === "loading") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("approvalLoading"));
824
- if (state.view.kind === "empty") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("approvalEmpty"));
825
- if (state.view.kind === "failed") return (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("p", { className: "evolution-param-error" }, t("approvalFailed") + ": " + state.view.message), (0, react.createElement)("button", {
928
+ if (state.view.kind === "loading") return (0, react.createElement)("p", {
929
+ className: "evolution-param-note",
930
+ role: "status"
931
+ }, t("approvalLoading"));
932
+ if (state.view.kind === "empty") return (0, react.createElement)("p", {
933
+ className: "evolution-param-note",
934
+ role: "status"
935
+ }, t("approvalEmpty"));
936
+ if (state.view.kind === "failed") return (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("p", {
937
+ className: "evolution-param-error",
938
+ role: "status"
939
+ }, t("approvalFailed") + ": " + state.view.message), (0, react.createElement)("button", {
826
940
  className: "evolution-param-button",
827
941
  type: "button",
828
942
  onClick: () => {
829
943
  load();
830
944
  }
831
945
  }, t("approvalRetry")));
832
- return (0, react.createElement)("div", null, ...state.view.rows.map(row), state.notice === null ? null : (0, react.createElement)("p", { className: "evolution-param-error" }, state.notice));
946
+ return (0, react.createElement)("div", null, ...state.view.rows.map(row), state.notice === null ? null : (0, react.createElement)("p", {
947
+ className: "evolution-param-error",
948
+ role: "status"
949
+ }, state.notice));
833
950
  };
834
951
  const rows = state.view.kind === "ready" ? state.view.rows : [];
835
952
  const waiting = rows.filter((entry) => entry.status !== "executing").length;
@@ -841,7 +958,7 @@ window.__ModuleLoader__.load({
841
958
  setOpen(!open);
842
959
  },
843
960
  "aria-expanded": open
844
- }, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("approvalTitle")), waiting === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalCount").replace("{n}", String(waiting))), running === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalRunningCount").replace("{n}", String(running))), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
961
+ }, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("approvalTitle")), waiting === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalCount", { n: waiting })), running === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalRunningCount", { n: running })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
845
962
  className: "evolution-param-button",
846
963
  type: "button",
847
964
  onClick: () => {
@@ -867,7 +984,7 @@ window.__ModuleLoader__.load({
867
984
  const zh = {
868
985
  title: "自进化",
869
986
  subtitle: "待你决定的写入在最上面;下面按功能列出你能改的参数,没有列出的由安装时的配置决定。",
870
- overridden: "我改过",
987
+ overridden: "已覆盖",
871
988
  deployment: "默认",
872
989
  loading: "正在读取设置…",
873
990
  unavailable: "这个功能没有启动(对应的插件没有安装)",
@@ -877,12 +994,21 @@ window.__ModuleLoader__.load({
877
994
  apply: "保存",
878
995
  reset: "恢复默认值",
879
996
  empty: "这里没有可改项",
880
- changed: "{n} 项已改",
997
+ changed: "{n} 项已覆盖",
881
998
  discard: "放弃修改",
882
999
  saving: "保存中…",
883
1000
  resetting: "恢复中…",
884
- refused: "这个值没有被接受(界面上保持原样)。想看原因:运行 /evolution params 或 /evolution doctor。",
885
- resetRefused: "恢复默认值没有生效(这一行还是你的值)。想看原因:运行 /evolution params 或 /evolution doctor。",
1001
+ destHost: "保存去向:本机配置",
1002
+ invalidNumber: "需要一个数字",
1003
+ invalidSelect: "不在允许的取值里",
1004
+ invalidAtLeast: "不能小于同组的 {n}",
1005
+ approvalConfirmNamed: "即将批准:{n}",
1006
+ approvalConfirmYes: "确认批准",
1007
+ approvalConfirmNo: "取消",
1008
+ destMemory: "保存去向:仅本次会话",
1009
+ resetTo: "复位为 {value}",
1010
+ refused: "这个值没有被接受(界面上保持原样)。想看原因:在对话里让它说明。",
1011
+ resetRefused: "恢复默认值没有生效(这一行还是你的值)。想看原因:在对话里让它说明。",
886
1012
  cardReview: "会话回顾",
887
1013
  cardMemory: "长期记忆",
888
1014
  cardToolMemory: "记忆写入",
@@ -921,7 +1047,7 @@ window.__ModuleLoader__.load({
921
1047
  const en = {
922
1048
  title: "Self-evolution",
923
1049
  subtitle: "Writes waiting for your decision come first; the changeable parameters are listed by feature below, and anything not listed comes from the deployment configuration.",
924
- overridden: "edited by you",
1050
+ overridden: "Overridden",
925
1051
  deployment: "default",
926
1052
  loading: "reading settings…",
927
1053
  unavailable: "this feature is not running (its plugin is not installed)",
@@ -931,12 +1057,21 @@ window.__ModuleLoader__.load({
931
1057
  apply: "Save",
932
1058
  reset: "Reset to default",
933
1059
  empty: "nothing to change here",
934
- changed: "{n} changed",
1060
+ changed: "{n} overridden",
935
1061
  discard: "Discard",
936
1062
  saving: "Saving…",
937
1063
  resetting: "Resetting…",
938
- refused: "The value was not accepted (the page keeps what you see). For the reason run /evolution params or /evolution doctor.",
939
- resetRefused: "The reset did not take (this row still holds your value). For the reason run /evolution params or /evolution doctor.",
1064
+ destHost: "Saved to: this machine",
1065
+ invalidNumber: "Needs a number",
1066
+ invalidSelect: "Not one of the allowed values",
1067
+ invalidAtLeast: "must not be below {n}",
1068
+ approvalConfirmNamed: "About to approve: {n}",
1069
+ approvalConfirmYes: "Approve",
1070
+ approvalConfirmNo: "Cancel",
1071
+ destMemory: "Saved to: this session only",
1072
+ resetTo: "Restores {value}",
1073
+ refused: "The value was not accepted (the page keeps what you see). For the reason, ask in the conversation.",
1074
+ resetRefused: "The reset did not take (this row still holds your value). For the reason, ask in the conversation.",
940
1075
  cardReview: "Session review",
941
1076
  cardMemory: "Long-term memory",
942
1077
  cardToolMemory: "Writing memory",
@@ -991,45 +1126,33 @@ window.__ModuleLoader__.load({
991
1126
  * @param language - which dictionary to read; defaults to Chinese.
992
1127
  * @returns the copy this bundle carries.
993
1128
  */
994
- function message(key, language = "zh") {
1129
+ function message(key, language = "en") {
995
1130
  return language === "zh" ? zh[key] : en[key];
996
1131
  }
997
- //#endregion
998
- //#region src/client/settle.ts
999
1132
  /**
1000
- * Structural equality for the values a parameter can hold.
1001
- *
1002
- * T5-10: the comparison walks the value instead of comparing serializations, because key ORDER is
1003
- * not part of an object's value — `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` are the same value, but
1004
- * their `JSON.stringify` output differs, and the card then read a save that HAD landed as refused
1005
- * (draft kept, error line shown). Array order IS part of an array's value, so arrays compare
1006
- * element-wise. `NaN` equals itself through the `Object.is` fast path; other primitives use `===`,
1007
- * which keeps `0` and `-0` equal exactly as the serialization comparison did.
1008
- * @param a - one side of the comparison.
1009
- * @param b - the other side.
1010
- * @returns true when the two are the same value.
1133
+ * Fill one TEMPLATE's `{slot}` placeholders (G5 C4). The seat's translator takes the values, so the
1134
+ * rule lives here instead of at every call site — `t('changed').replace('{n}', …)` used to be
1135
+ * written seven times across three components. An UNKNOWN slot is left standing: a missing value
1136
+ * must not print "undefined" into the operator's UI.
1137
+ * @param template - the resolved copy, with `{slot}` placeholders.
1138
+ * @param values - substitutions keyed by the slot name inside the braces.
1139
+ * @returns the filled copy.
1011
1140
  */
1012
- function sameValue(a, b) {
1013
- if (Object.is(a, b)) return true;
1014
- if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return a === b;
1015
- if (Array.isArray(a) || Array.isArray(b)) {
1016
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1017
- return a.every((item, index) => sameValue(item, b[index]));
1018
- }
1019
- const left = a;
1020
- const right = b;
1021
- const keys = Object.keys(left);
1022
- return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && sameValue(left[key], right[key]));
1141
+ function fill(template, values) {
1142
+ return template.replace(/\{(\w+)\}/g, (match, slot) => {
1143
+ const value = values[slot];
1144
+ return value === void 0 ? match : String(value);
1145
+ });
1023
1146
  }
1147
+ //#endregion
1148
+ //#region src/client/guards.ts
1024
1149
  /**
1025
- * Whether every staged operation is visible in the user layer.
1026
- * @param pending - the operations awaiting a verdict.
1027
- * @param user - the user layer of the post-write snapshot; a non-section reads as empty.
1028
- * @returns true when each staged id now holds the requested value, or is gone after an `unset`.
1150
+ * Is this a non-null, non-array object (what JSON calls an object)?
1151
+ * @param value - the value to test.
1152
+ * @returns whether it is a plain object.
1029
1153
  */
1030
- function landedSettlements(pending, user) {
1031
- const section = typeof user === "object" && user !== null && !Array.isArray(user) ? user : {};
1032
- return pending.every((entry) => entry.op === "unset" ? !Object.hasOwn(section, entry.id) : sameValue(section[entry.id], entry.want));
1154
+ function isRecord(value) {
1155
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1033
1156
  }
1034
1157
  //#endregion
1035
1158
  //#region src/client/ParamCard.ts
@@ -1074,17 +1197,62 @@ window.__ModuleLoader__.load({
1074
1197
  return trimmed;
1075
1198
  }
1076
1199
  }
1077
- /** Whether a value is a plain section object. */
1078
- function isSection$1(value) {
1079
- return typeof value === "object" && value !== null && !Array.isArray(value);
1200
+ /**
1201
+ * U-4: whether one field's CURRENT text is a value the Host can take. The controls constrain their own
1202
+ * domains (a number input sanitizes, a select only offers listed values), so the reachable case is a
1203
+ * value the registry no longer lists — carried in the user layer and re-staged by any other edit.
1204
+ * @param field - the generated field.
1205
+ * @param text - the text the control holds right now.
1206
+ * @param peer - U-4: the text the row this field declares `atLeast` against currently holds (absent
1207
+ * when the field declares no relation, or when that row is not on this card).
1208
+ * @returns the message key explaining the refusal, or null when the value is acceptable.
1209
+ */
1210
+ function invalidReason(field, text, peer) {
1211
+ const trimmed = text.trim();
1212
+ if (trimmed === "") return null;
1213
+ if (field.control === "number") {
1214
+ if (!Number.isFinite(Number(trimmed))) return "invalidNumber";
1215
+ if (field.atLeast !== "" && peer !== void 0 && peer.trim() !== "" && Number.isFinite(Number(peer))) {
1216
+ if (Number(trimmed) < Number(peer)) return "invalidAtLeast";
1217
+ }
1218
+ return null;
1219
+ }
1220
+ if (field.control === "select") return field.values.includes(trimmed) ? null : "invalidSelect";
1221
+ return null;
1222
+ }
1223
+ /**
1224
+ * U-5: the value a reset would restore, spelled for a reader. Only primitives are shown: the seat's
1225
+ * `base` may be a nested object, and `[object Object]` is worse than saying nothing.
1226
+ * @param value - the seat's base value for one field.
1227
+ * @returns its display string, or null when it is not a primitive.
1228
+ */
1229
+ function resetToOf(value) {
1230
+ if (value === null || value === void 0) return null;
1231
+ if (typeof value === "string") return value;
1232
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1233
+ return null;
1234
+ }
1235
+ /**
1236
+ * The DOM id a field's control, its label and its explanation node all share. ONE definition: the
1237
+ * three call sites used to spell the string out, and the third one (the explanation node) referred to
1238
+ * a local that only existed inside the control — a ReferenceError on every invalid value, which the
1239
+ * unit-level fixture could not see because it never rendered one (independent review, P1-1).
1240
+ */
1241
+ function controlId(field) {
1242
+ return "evolution-param-" + field.id;
1080
1243
  }
1081
1244
  /** One field's control, typed from the registry. */
1082
1245
  function FieldControl(props) {
1083
1246
  const { field, text } = props;
1247
+ const id = controlId(field);
1084
1248
  const common = {
1085
- id: "evolution-param-" + field.id,
1249
+ id,
1086
1250
  disabled: props.disabled,
1087
- className: "evolution-param-input"
1251
+ className: "evolution-param-input",
1252
+ ...props.invalid === null ? {} : {
1253
+ "aria-invalid": "true",
1254
+ "aria-describedby": id + "-invalid"
1255
+ }
1088
1256
  };
1089
1257
  if (field.control === "switch") return (0, react.createElement)("input", {
1090
1258
  ...common,
@@ -1122,17 +1290,21 @@ window.__ModuleLoader__.load({
1122
1290
  function FieldBlock(props) {
1123
1291
  const { field, t } = props;
1124
1292
  const unit = field.unit === "" ? null : (0, react.createElement)("span", { className: "evolution-param-unit" }, "(" + field.unit + ")");
1125
- return (0, react.createElement)("div", { className: "evolution-param-field" }, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("label", { htmlFor: "evolution-param-" + field.id }, field.label === "" ? field.id : field.label), unit, (0, react.createElement)("span", {
1293
+ return (0, react.createElement)("div", { className: "evolution-param-field" }, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("label", { htmlFor: controlId(field) }, field.label === "" ? field.id : field.label), unit, props.overridden ? (0, react.createElement)("span", {
1126
1294
  className: "evolution-param-source",
1127
- "data-user": props.overridden ? "true" : "false"
1128
- }, props.overridden ? t("overridden") : t("deployment"))), FieldControl(props), (0, react.createElement)("p", { className: "evolution-param-hint" }, field.hint === "" ? field.doc : field.hint), props.overridden ? (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
1295
+ "data-user": "true"
1296
+ }, t("overridden")) : null), FieldControl(props), (0, react.createElement)("p", { className: "evolution-param-hint" }, field.hint === "" ? field.doc : field.hint), props.invalid === null ? null : (0, react.createElement)("p", {
1297
+ id: controlId(field) + "-invalid",
1298
+ className: "evolution-param-error",
1299
+ role: "status"
1300
+ }, t(props.invalid)), props.overridden ? (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
1129
1301
  type: "button",
1130
1302
  className: "evolution-param-button",
1131
1303
  disabled: props.disabled || props.clearing,
1132
1304
  onClick: () => {
1133
1305
  props.onClear(field.id);
1134
1306
  }
1135
- }, props.clearing ? t("resetting") : t("reset"))) : null);
1307
+ }, props.clearing ? t("resetting") : t("reset")), props.resetTo === null ? null : (0, react.createElement)("span", { className: "evolution-param-hint" }, t("resetTo", { value: props.resetTo }))) : null);
1136
1308
  }
1137
1309
  /**
1138
1310
  * Render one row's card body from a supplied snapshot.
@@ -1147,10 +1319,16 @@ window.__ModuleLoader__.load({
1147
1319
  const [error, setError] = (0, react.useState)("");
1148
1320
  const [pending, setPending] = (0, react.useState)(null);
1149
1321
  const [clearing, setClearing] = (0, react.useState)(null);
1150
- if (snapshot.status === "loading") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("loading"));
1151
- if (snapshot.status === "unavailable") return (0, react.createElement)("p", { className: "evolution-param-note" }, t(snapshot.reason === "seat-missing" ? "seatMissing" : snapshot.reason === "projection-failed" ? "projectionFailed" : "unavailable"));
1152
- const user = isSection$1(snapshot.user) ? snapshot.user : {};
1153
- const value = isSection$1(snapshot.value) ? snapshot.value : {};
1322
+ if (snapshot.status === "loading") return (0, react.createElement)("p", {
1323
+ className: "evolution-param-note",
1324
+ role: "status"
1325
+ }, t("loading"));
1326
+ if (snapshot.status === "unavailable") return (0, react.createElement)("p", {
1327
+ className: "evolution-param-note",
1328
+ role: "status"
1329
+ }, t(snapshot.reason === "seat-missing" ? "seatMissing" : snapshot.reason === "projection-failed" ? "projectionFailed" : "unavailable"));
1330
+ const user = isRecord(snapshot.user) ? snapshot.user : {};
1331
+ const value = isRecord(snapshot.value) ? snapshot.value : {};
1154
1332
  const disabled = !snapshot.writable;
1155
1333
  const locked = disabled || busy || clearing !== null;
1156
1334
  const titleKey = NAMESPACE_TITLES[namespace];
@@ -1158,6 +1336,12 @@ window.__ModuleLoader__.load({
1158
1336
  const changed = fields.filter((field) => Object.hasOwn(user, field.id)).length;
1159
1337
  const textOf = (field) => draft[field.id] ?? format(value[field.id]);
1160
1338
  const dirty = fields.filter((field) => draft[field.id] !== void 0 && draft[field.id] !== format(value[field.id]));
1339
+ const peerTextOf = (field) => {
1340
+ if (field.atLeast === "") return void 0;
1341
+ const related = fields.find((candidate) => candidate.id === field.atLeast);
1342
+ return related === void 0 ? void 0 : textOf(related);
1343
+ };
1344
+ const blocked = fields.some((field) => invalidReason(field, textOf(field), peerTextOf(field)) !== null);
1161
1345
  const change = (id, next) => {
1162
1346
  setDraft({
1163
1347
  ...draft,
@@ -1182,11 +1366,11 @@ window.__ModuleLoader__.load({
1182
1366
  setPending(null);
1183
1367
  setBusy(false);
1184
1368
  setClearing(null);
1185
- if (!landedSettlements(pending, snapshot.user)) {
1186
- setError(t(pending.every((entry) => entry.op === "unset") ? "resetRefused" : "refused"));
1369
+ if (!pending.accepted) {
1370
+ setError(t(pending.resets ? "resetRefused" : "refused"));
1187
1371
  return;
1188
1372
  }
1189
- if (pending.some((entry) => entry.op === "set")) setDraft({});
1373
+ if (!pending.resets) setDraft({});
1190
1374
  }, [
1191
1375
  pending,
1192
1376
  snapshot.user,
@@ -1204,17 +1388,18 @@ window.__ModuleLoader__.load({
1204
1388
  setClearing(field);
1205
1389
  setError("");
1206
1390
  (async () => {
1391
+ let accepted;
1207
1392
  try {
1208
- await clear(field);
1393
+ accepted = await clear(field);
1209
1394
  } catch (caught) {
1210
1395
  setClearing(null);
1211
1396
  setError(caught instanceof Error && caught.message !== "" ? caught.message : t("resetRefused"));
1212
1397
  return;
1213
1398
  }
1214
- setPending([{
1215
- op: "unset",
1216
- id: field
1217
- }]);
1399
+ setPending({
1400
+ accepted,
1401
+ resets: true
1402
+ });
1218
1403
  })();
1219
1404
  };
1220
1405
  const save = () => {
@@ -1222,16 +1407,14 @@ window.__ModuleLoader__.load({
1222
1407
  setError("");
1223
1408
  (async () => {
1224
1409
  try {
1225
- await mutateOnce(dirty.map((field) => ({
1226
- op: "set",
1227
- path: [field.id],
1228
- value: parseFor(field.control, textOf(field))
1229
- })));
1230
- setPending(dirty.map((field) => ({
1231
- op: "set",
1232
- id: field.id,
1233
- want: parseFor(field.control, textOf(field))
1234
- })));
1410
+ setPending({
1411
+ accepted: await mutateOnce(dirty.map((field) => ({
1412
+ op: "set",
1413
+ path: [field.id],
1414
+ value: parseFor(field.control, textOf(field))
1415
+ }))),
1416
+ resets: false
1417
+ });
1235
1418
  } catch (caught) {
1236
1419
  setBusy(false);
1237
1420
  setError(caught instanceof Error && caught.message !== "" ? caught.message : t("refused"));
@@ -1245,20 +1428,34 @@ window.__ModuleLoader__.load({
1245
1428
  onClick: () => {
1246
1429
  setOpen(!open);
1247
1430
  }
1248
- }, (0, react.createElement)("span", { className: "evolution-param-card-title" }, title), changed === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("changed").replace("{n}", String(changed))), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, disabled ? (0, react.createElement)("p", { className: "evolution-param-note" }, t("readonly")) : null, fields.length === 0 ? (0, react.createElement)("p", { className: "evolution-param-note" }, t("empty")) : fields.map((field) => (0, react.createElement)(FieldBlock, {
1431
+ }, (0, react.createElement)("span", { className: "evolution-param-card-title" }, title), changed === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("changed", { n: changed })), (0, react.createElement)("span", { className: "evolution-param-dest" }, snapshot.mode === "memory" ? t("destMemory") : t("destHost")), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, disabled ? (0, react.createElement)("p", {
1432
+ className: "evolution-param-note",
1433
+ role: "status"
1434
+ }, t("readonly")) : null, fields.length === 0 ? (0, react.createElement)("p", {
1435
+ className: "evolution-param-note",
1436
+ role: "status"
1437
+ }, t("empty")) : fields.flatMap((field, index) => [index === 0 || fields[index - 1]?.group !== field.group ? (0, react.createElement)("h3", {
1438
+ key: "group-" + field.group,
1439
+ className: "evolution-param-group"
1440
+ }, field.group) : null, (0, react.createElement)(FieldBlock, {
1249
1441
  key: field.id,
1250
1442
  field,
1251
1443
  text: textOf(field),
1252
1444
  overridden: Object.hasOwn(user, field.id),
1253
1445
  disabled: locked,
1254
1446
  clearing: clearing === field.id,
1447
+ resetTo: resetToOf(snapshot.base?.[field.id]),
1448
+ invalid: invalidReason(field, textOf(field), peerTextOf(field)),
1255
1449
  t,
1256
1450
  onChange: change,
1257
1451
  onClear: reset
1258
- })), error === "" ? null : (0, react.createElement)("p", { className: "evolution-param-error" }, error), fields.length === 0 ? null : (0, react.createElement)("div", { className: "evolution-param-footer" }, (0, react.createElement)("button", {
1452
+ })]), error === "" ? null : (0, react.createElement)("p", {
1453
+ className: "evolution-param-error",
1454
+ role: "status"
1455
+ }, error), fields.length === 0 ? null : (0, react.createElement)("div", { className: "evolution-param-footer" }, (0, react.createElement)("button", {
1259
1456
  type: "button",
1260
1457
  className: "evolution-param-button",
1261
- disabled: busy || dirty.length === 0,
1458
+ disabled: busy || dirty.length === 0 || blocked,
1262
1459
  onClick: () => {
1263
1460
  setDraft({});
1264
1461
  setError("");
@@ -1296,6 +1493,8 @@ window.__ModuleLoader__.load({
1296
1493
  value: void 0,
1297
1494
  user: void 0,
1298
1495
  writable: false,
1496
+ base: void 0,
1497
+ mode: "host",
1299
1498
  reason: "seat-missing"
1300
1499
  });
1301
1500
  /** What a card shows when the seat served the row but projecting it failed. A frozen constant
@@ -1306,17 +1505,11 @@ window.__ModuleLoader__.load({
1306
1505
  value: void 0,
1307
1506
  user: void 0,
1308
1507
  writable: false,
1508
+ base: void 0,
1509
+ mode: "host",
1309
1510
  reason: "projection-failed"
1310
1511
  });
1311
1512
  /**
1312
- * Whether a value is a plain section object.
1313
- * @param value - the value to test.
1314
- * @returns true for a non-null, non-array object.
1315
- */
1316
- function isSection(value) {
1317
- return typeof value === "object" && value !== null && !Array.isArray(value);
1318
- }
1319
- /**
1320
1513
  * Project one platform snapshot into the card's own shape.
1321
1514
  * @param snapshot - the seat's snapshot.
1322
1515
  * @returns the card's snapshot, with the reason an unavailable one is unavailable.
@@ -1324,9 +1517,11 @@ window.__ModuleLoader__.load({
1324
1517
  function sectionSnapshotOf(snapshot) {
1325
1518
  return {
1326
1519
  status: snapshot.status,
1327
- value: isSection(snapshot.value) ? snapshot.value : void 0,
1520
+ value: isRecord(snapshot.value) ? snapshot.value : void 0,
1328
1521
  user: snapshot.user,
1329
1522
  writable: snapshot.writable,
1523
+ base: isRecord(snapshot.base) ? snapshot.base : void 0,
1524
+ mode: snapshot.mode,
1330
1525
  ...snapshot.status === "unavailable" ? { reason: "not-served" } : {}
1331
1526
  };
1332
1527
  }
@@ -1432,10 +1627,16 @@ window.__ModuleLoader__.load({
1432
1627
  const form = () => probe()?.get(namespace);
1433
1628
  return {
1434
1629
  mutateOnce: async (ops) => {
1435
- await form()?.mutate(ops);
1630
+ const seat = form();
1631
+ if (seat === void 0) return false;
1632
+ await seat.mutate(ops);
1633
+ return true;
1436
1634
  },
1437
1635
  clear: async (field) => {
1438
- await form()?.unset(field);
1636
+ const seat = form();
1637
+ if (seat === void 0) return false;
1638
+ await seat.unset(field);
1639
+ return true;
1439
1640
  }
1440
1641
  };
1441
1642
  }
@@ -1485,7 +1686,7 @@ window.__ModuleLoader__.load({
1485
1686
  * @returns how many of them the user overrode.
1486
1687
  */
1487
1688
  function overridden(user, fields) {
1488
- const layer = isSection(user) ? user : {};
1689
+ const layer = isRecord(user) ? user : {};
1489
1690
  return fields.filter((field) => Object.hasOwn(layer, field.id)).length;
1490
1691
  }
1491
1692
  /**
@@ -1495,23 +1696,22 @@ window.__ModuleLoader__.load({
1495
1696
  */
1496
1697
  function RowConfigCard(props) {
1497
1698
  const { t, fields, namespace, view, form } = props;
1498
- if (form === void 0) return view === "summary" ? null : (0, react.createElement)("p", { className: "evolution-param-note" }, t("seatMissing"));
1699
+ if (form === void 0) return view === "summary" ? null : (0, react.createElement)("p", {
1700
+ className: "evolution-param-note",
1701
+ role: "status"
1702
+ }, t("seatMissing"));
1499
1703
  const changed = overridden(form.state.user, fields);
1500
- if (view === "summary") return changed === 0 ? null : (0, react.createElement)("span", null, t("changed").replace("{n}", String(changed)));
1704
+ if (view === "summary") return changed === 0 ? null : (0, react.createElement)("span", null, t("changed", { n: changed }));
1501
1705
  return ParamCardView({
1502
1706
  namespace,
1503
1707
  fields,
1504
1708
  t,
1505
1709
  snapshot: sectionSnapshotOf(form.state),
1506
- mutateOnce: async (ops) => {
1507
- await form.mutate(ops, form.state.revision);
1508
- },
1509
- clear: async (field) => {
1510
- await form.mutate([{
1511
- op: "unset",
1512
- path: [field]
1513
- }], form.state.revision);
1514
- }
1710
+ mutateOnce: async (ops) => form.mutate(ops, form.state.revision),
1711
+ clear: async (field) => form.mutate([{
1712
+ op: "unset",
1713
+ path: [field]
1714
+ }], form.state.revision)
1515
1715
  });
1516
1716
  }
1517
1717
  //#endregion
@@ -1560,7 +1760,7 @@ window.__ModuleLoader__.load({
1560
1760
  * @returns the section element.
1561
1761
  */
1562
1762
  function SettingsSection(face) {
1563
- return (0, react.createElement)("div", { className: "evolution-params" }, (0, react.createElement)("h2", { className: "evolution-params-title" }, face.t("title")), (0, react.createElement)("p", { className: "evolution-params-subtitle" }, face.t("subtitle")), (0, react.createElement)("div", {
1763
+ return (0, react.createElement)("div", { className: "evolution-params" }, (0, react.createElement)("h2", { className: "evolution-params-title" }, face.t("title")), (0, react.createElement)("p", { className: "evolution-params-subtitle" }, face.t("subtitle")), (0, react.createElement)("p", { className: "evolution-params-about" }, "0.23.0 · Web settings cards for the evolution family's parameter namespaces"), (0, react.createElement)("div", {
1564
1764
  key: PENDING_CARD_KEY,
1565
1765
  className: "evolution-params-group"
1566
1766
  }, face.renderSlot(CARD_SLOT, {}, { entryKey: PENDING_CARD_KEY })), ...CLIENT_PARAM_SECTIONS.map((section_) => (0, react.createElement)("div", {
@@ -1608,6 +1808,7 @@ window.__ModuleLoader__.load({
1608
1808
  ".evolution-params{display:flex;flex-direction:column}",
1609
1809
  ".evolution-params-title{margin:0 0 var(--evo-space-4);color:var(--dsw-alias-label-primary);font-size:var(--evo-type-lg);font-weight:600;line-height:var(--evo-leading-base)}",
1610
1810
  ".evolution-params-subtitle{margin:0 0 var(--evo-space-16);color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
1811
+ ".evolution-params-about{margin:0 0 var(--evo-space-16);color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-xs)}",
1611
1812
  ".evolution-param-card{border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2);border-radius:var(--evo-radius-control);background:var(--dsw-alias-bg-layer-1);margin-bottom:var(--evo-space-12)}",
1612
1813
  ".evolution-param-head{display:flex;align-items:center;gap:var(--evo-space-8);width:100%;padding:var(--evo-space-12) var(--evo-space-14);background:none;border:0;text-align:left;cursor:pointer;color:inherit;font:inherit}",
1613
1814
  ".evolution-param-head:hover{background:var(--dsw-alias-interactive-bg-hover)}",
@@ -1615,6 +1816,8 @@ window.__ModuleLoader__.load({
1615
1816
  ".evolution-param-count{color:var(--dsw-alias-brand-primary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
1616
1817
  ".evolution-param-chevron{color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
1617
1818
  ".evolution-param-body{padding:0 var(--evo-space-14) var(--evo-space-12);border-top:var(--evo-hairline-width) solid var(--dsw-alias-border-l2)}",
1819
+ ".evolution-param-dest{color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-xs)}",
1820
+ ".evolution-param-group{margin:var(--evo-space-12) 0 0;font-size:var(--evo-type-sm);font-weight:600;color:var(--dsw-alias-label-secondary);text-transform:none}",
1618
1821
  ".evolution-param-field{display:flex;flex-direction:column;gap:var(--evo-space-6);padding:var(--evo-space-12) 0}",
1619
1822
  ".evolution-param-field+.evolution-param-field{border-top:var(--evo-hairline-width) solid var(--dsw-alias-border-l2)}",
1620
1823
  ".evolution-param-label{display:flex;align-items:center;gap:var(--evo-space-6);color:var(--dsw-alias-label-primary);font-size:var(--evo-type-body);font-weight:500;line-height:var(--evo-leading-base)}",
@@ -1622,18 +1825,20 @@ window.__ModuleLoader__.load({
1622
1825
  ".evolution-param-hint{margin:0;color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-sm);line-height:var(--evo-leading-base)}",
1623
1826
  ".evolution-param-value{color:var(--dsw-alias-label-secondary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
1624
1827
  ".evolution-param-input{width:100%;box-sizing:border-box;padding:var(--evo-space-8) var(--evo-space-10);border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2);border-radius:var(--evo-radius-control);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:var(--evo-type-body);line-height:var(--evo-leading-base)}",
1625
- ".evolution-param-input:focus{outline:var(--evo-focus-ring);outline-offset:var(--evo-focus-offset)}",
1828
+ ".evolution-param-input:focus-visible{outline:var(--evo-focus-ring);outline-offset:var(--evo-focus-offset)}",
1626
1829
  ".evolution-param-input:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}",
1627
1830
  ".evolution-param-source{display:inline-flex;align-items:center;height:var(--evo-space-20);padding:0 var(--evo-space-6);border-radius:var(--evo-radius-chip);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary);font-size:var(--evo-type-xs);line-height:var(--evo-space-20)}",
1628
1831
  ".evolution-param-source[data-user=\"true\"]{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary-foreground)}",
1629
1832
  ".evolution-param-actions{display:flex;justify-content:flex-end;gap:var(--evo-space-8);padding-top:var(--evo-space-12)}",
1630
1833
  ".evolution-param-button{padding:var(--evo-space-6) var(--evo-space-12);border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2);border-radius:var(--evo-radius-control);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:var(--evo-type-sm);line-height:var(--evo-leading-base);cursor:pointer}",
1631
1834
  ".evolution-param-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}",
1835
+ ".evolution-param-button[data-tone=\"outline\"]{border-color:var(--dsw-alias-border-l2)}",
1836
+ ".evolution-param-button[data-tone=\"ghost\"]{border-color:transparent;background:transparent}",
1632
1837
  ".evolution-param-button[data-primary=\"true\"]{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:transparent}",
1633
1838
  ".evolution-param-button[data-primary=\"true\"]:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}",
1634
1839
  ".evolution-param-button:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}",
1635
1840
  ".evolution-param-select{width:100%;box-sizing:border-box;padding:var(--evo-space-8) var(--evo-space-10);border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2);border-radius:var(--evo-radius-control);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:var(--evo-type-sm);line-height:var(--evo-leading-base)}",
1636
- ".evolution-param-select:focus{outline:var(--evo-focus-ring);outline-offset:var(--evo-focus-offset)}",
1841
+ ".evolution-param-select:focus-visible{outline:var(--evo-focus-ring);outline-offset:var(--evo-focus-offset)}",
1637
1842
  ".evolution-param-select:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}",
1638
1843
  ".evolution-param-check{width:var(--evo-space-16);height:var(--evo-space-16);margin:0;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}",
1639
1844
  ".evolution-param-check:disabled{cursor:default;opacity:.5}",
@@ -1696,7 +1901,7 @@ window.__ModuleLoader__.load({
1696
1901
  }
1697
1902
  }, "evolution-settings: dictionaries");
1698
1903
  let bound = null;
1699
- const t = (key) => {
1904
+ const translate = (key) => {
1700
1905
  if (!registered) return message(key);
1701
1906
  if (bound === null) try {
1702
1907
  bound = seam.locale.bind(NS);
@@ -1705,11 +1910,14 @@ window.__ModuleLoader__.load({
1705
1910
  }
1706
1911
  return bound(key);
1707
1912
  };
1913
+ const t = (key, values) => values === void 0 ? translate(key) : fill(translate(key), values);
1708
1914
  const probe = () => ctx.get("configForms");
1709
1915
  const sources = createSourceCache(probe);
1710
1916
  ctx.effect(() => () => {
1711
1917
  sources.dispose();
1712
1918
  }, "evolution-settings: sources");
1919
+ const approvalSource = sources.sourceFor("evolution-approval");
1920
+ const confirmMode = () => confirmModeFrom(approvalSource.getSnapshot().user);
1713
1921
  ctx.inject?.(["configForms"], () => {
1714
1922
  sources.revive();
1715
1923
  });
@@ -1750,7 +1958,8 @@ window.__ModuleLoader__.load({
1750
1958
  key: PENDING_CARD_KEY,
1751
1959
  inject: () => ({
1752
1960
  t,
1753
- api
1961
+ api,
1962
+ confirmMode
1754
1963
  })
1755
1964
  }, PendingCard);
1756
1965
  });