@lmzhen/dsh-evolution-settings-ui 0.17.1 → 0.18.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/README.md +44 -2
- package/lib/client.js +159 -41
- package/lib/types/client/ParamCard.d.ts +8 -3
- package/lib/types/client/api.d.ts +10 -2
- package/lib/types/client/generated-params.d.ts +7 -1
- package/lib/types/client/messages.d.ts +5 -0
- package/lib/types/client/seam.d.ts +10 -0
- package/lib/types/client/settle.d.ts +19 -9
- package/lib/types/client/source.d.ts +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,12 +42,54 @@ Its states are deliberately distinct: **reading** ("正在读取待批项…"),
|
|
|
42
42
|
**failed** (the reason plus a retry). Merging the first two is what once made a slow answer read as lost data
|
|
43
43
|
(E7), so the state machine lives in `pending-state.ts` and the three are asserted without a browser.
|
|
44
44
|
|
|
45
|
+
It reads BOTH windows the route serves — `?status=pending` and `?status=executing` — and orders them the way
|
|
46
|
+
the CLI does. A record stuck in `executing` (an approve that died between the claim and the decision, or
|
|
47
|
+
another process running it) is NOT waiting for a decision: its row carries no approve/reject pair, says the
|
|
48
|
+
write may already have landed, and the header counts it apart ("{n} 项执行中"). Reading only the pending window
|
|
49
|
+
is what made such a record invisible here while `/evolution pending` and doctor both listed it.
|
|
50
|
+
|
|
51
|
+
The card re-reads the window on demand: a **refresh** control in the body and a window `focus`. It reads once
|
|
52
|
+
per mount otherwise — deliberately no timer, since the family has no polling precedent — because an agent can
|
|
53
|
+
stage a write while the reader is looking at the card, and the empty sentence standing there afterwards is the
|
|
54
|
+
"empty vs. actually something" lie.
|
|
55
|
+
|
|
45
56
|
A decision re-reads the window instead of editing the list locally (the host owns the state) and shows the
|
|
46
|
-
host's own sentence when it refuses. **
|
|
57
|
+
host's own sentence when it refuses. **Restore to default** (`unset`) is judged the same way as a save: the
|
|
58
|
+
reset's verdict is the next snapshot — the id must be GONE from the user layer, the opposite evidence a `set`
|
|
59
|
+
needs — so a host refusal prints "恢复默认值没有生效…" instead of leaving a chip that looks like a broken button,
|
|
60
|
+
the control reports its in-flight state, and a throwing settings service reaches the reader rather than
|
|
61
|
+
escaping as an unhandled rejection. `settle.ts` holds that one verdict function for both ops. **Preview** opens one row at a time: the facts line (`n 行新增` /
|
|
47
62
|
`n 行删除` / truncated) and the source block with `-`/`+` lines, the same shape the history panel's diff
|
|
48
63
|
uses; an unavailable preview prints the host's reason. A late answer for a row the reader already left is
|
|
49
64
|
dropped rather than painted under another row's heading.
|
|
50
65
|
|
|
66
|
+
## A seat that arrives late revives the card
|
|
67
|
+
|
|
68
|
+
The seat is probed lazily (never captured at apply time), but probing alone only answers the NEXT read —
|
|
69
|
+
a card that already painted 「本部署没有这个座位」 never asks again, and its row subscription was never
|
|
70
|
+
attached at all. `apply()` therefore subscribes to the platform's own arrival channel
|
|
71
|
+
(`ctx.inject(['configForms'], () => sources.revive())`): `revive` force-re-binds the form (a REPLACED
|
|
72
|
+
seat is picked up too), attaches the subscription and wakes every listener, so the card turns into a form
|
|
73
|
+
without a re-render and without a timer. The mechanism sentence this replaced — "the platform has no
|
|
74
|
+
service arrival signal" — was wrong: `ctx.inject` is exactly that signal (v46 S5.2 / T5-11).
|
|
75
|
+
|
|
76
|
+
## One save is one write, on both faces
|
|
77
|
+
|
|
78
|
+
A save hands the seat ONE op list (`mutateOnce(ops)`) instead of one call per field. The Plugins page
|
|
79
|
+
fences that list with the revision the render read, which is the platform's own form model ("apply
|
|
80
|
+
ordered field edits in one revision-fenced write"); the section path passes no fence, exactly as before.
|
|
81
|
+
Per-field calls with one shared render-time revision were the T5-01 bug: the host accepted the first
|
|
82
|
+
field, the revision moved, and every later field of the SAME save was refused with a conflict — the
|
|
83
|
+
change silently did not land while the card showed one generic refusal.
|
|
84
|
+
|
|
85
|
+
The stylesheet carries two structural invariants, both pinned by `tests/styles.client.spec.ts`: one
|
|
86
|
+
selector never carries two rules (the row chip and the preview's diff block were both
|
|
87
|
+
`.evolution-param-source`, so the diff rule's padding/border/overflow landed on a 20px chip and the
|
|
88
|
+
chip's `inline-flex` laid every diff line out in one row), and the family scale is declared on the
|
|
89
|
+
CARD root as well as the section root (the Plugins page renders the same card outside
|
|
90
|
+
`.evolution-params`, where a section-only declaration left every `--evo-*` undefined — no border, zero
|
|
91
|
+
padding, square corners).
|
|
92
|
+
|
|
51
93
|
## The `hooks` compartment never reaches the component
|
|
52
94
|
|
|
53
95
|
A card's inject face carries `hooks: { paramSection: source }` — that is the SHELL's seat, not a prop. The renderer binds each entry to a `use<Name>` seat and hands the component the face with `hooks` REMOVED (the slot contract is `PropsHooks<face['hooks']>` plus `Omit<face, 'hooks'>`). Reading `props.hooks` therefore reads a prop that never exists, and the failure only shows up at runtime, inside the save path.
|
|
@@ -82,4 +124,4 @@ The browser half ships as the client module system's lazy CJS factory artifact (
|
|
|
82
124
|
- The section renders plain controls styled with the design tokens rather than the platform's `@deepseek-ai/dsh-client-ui-primitives` kit: that module IS available to out-of-repo bundles (the market plugin requires it), but its prop shapes are not published, and guessing them would break the live GUI.
|
|
83
125
|
- An unsaved draft lives in the card's own component state and the settings shell renders only the active section, so switching to another section drops a draft that was not saved yet. Values already written are unaffected; moving the draft into an apply-time store is the 0.7.x follow-up.
|
|
84
126
|
- A deployment that overrides a field through the `evolution-policy` row does not show on the card: the card reports the deployment value the settings scope serves, while the policy snapshot can differ. That divergence stays a doctor / `/evolution params` matter.
|
|
85
|
-
- The parameter card
|
|
127
|
+
- The parameter card's spec covers the **wiring**, not the **assembly**. `tests/section.client.spec.ts` renders the real card component with the props the renderer actually builds — no `hooks`, the settings seat stubbed — so a wrong prop name or a missing `useParamSection` binding fails in CI, on top of `tests/settle.spec.ts` (the pure `landedWrites` decision, including the already-overridden refusal case). What no Node-level spec can see is the assembly the loader performs: the framework's real kit, the style scoping, the module identity handed to the bundle. Defects of that layer — two same-named classes overwriting each other, styles scoped to the wrong root — still surface only in a live pass on the installed artifact (0.7.0's save defect was found exactly there).
|
package/lib/client.js
CHANGED
|
@@ -68,7 +68,7 @@ window.__ModuleLoader__.load({
|
|
|
68
68
|
body: JSON.stringify({ id })
|
|
69
69
|
});
|
|
70
70
|
return {
|
|
71
|
-
pending: async () => await read(doFetch, APPROVAL_CLIENT_ROUTES.pending),
|
|
71
|
+
pending: async (status) => await read(doFetch, status === void 0 ? APPROVAL_CLIENT_ROUTES.pending : APPROVAL_CLIENT_ROUTES.pending + "?status=" + status),
|
|
72
72
|
approve: async (id) => await post(APPROVAL_CLIENT_ROUTES.approve, id),
|
|
73
73
|
reject: async (id) => await post(APPROVAL_CLIENT_ROUTES.reject, id),
|
|
74
74
|
preview: async (id) => await read(doFetch, APPROVAL_CLIENT_ROUTES.preview + "?id=" + encodeURIComponent(id))
|
|
@@ -79,6 +79,7 @@ window.__ModuleLoader__.load({
|
|
|
79
79
|
const CLIENT_PARAM_SECTIONS = [
|
|
80
80
|
{
|
|
81
81
|
namespace: "evolution-curator",
|
|
82
|
+
seatKey: "evolution-curator",
|
|
82
83
|
fields: [
|
|
83
84
|
{
|
|
84
85
|
id: "curatorIntervalHours",
|
|
@@ -216,6 +217,7 @@ window.__ModuleLoader__.load({
|
|
|
216
217
|
},
|
|
217
218
|
{
|
|
218
219
|
namespace: "evolution-review",
|
|
220
|
+
seatKey: "evolution-review",
|
|
219
221
|
fields: [
|
|
220
222
|
{
|
|
221
223
|
id: "reviewSkillInterval",
|
|
@@ -306,6 +308,7 @@ window.__ModuleLoader__.load({
|
|
|
306
308
|
},
|
|
307
309
|
{
|
|
308
310
|
namespace: "memory-files",
|
|
311
|
+
seatKey: "memory-files",
|
|
309
312
|
fields: [
|
|
310
313
|
{
|
|
311
314
|
id: "memoryChars",
|
|
@@ -355,6 +358,7 @@ window.__ModuleLoader__.load({
|
|
|
355
358
|
},
|
|
356
359
|
{
|
|
357
360
|
namespace: "tool-memory",
|
|
361
|
+
seatKey: "tool-memory",
|
|
358
362
|
fields: [{
|
|
359
363
|
id: "entryPreviewChars",
|
|
360
364
|
group: "memory",
|
|
@@ -369,6 +373,7 @@ window.__ModuleLoader__.load({
|
|
|
369
373
|
},
|
|
370
374
|
{
|
|
371
375
|
namespace: "tool-skill-manage",
|
|
376
|
+
seatKey: "tool-skill-manage",
|
|
372
377
|
fields: [
|
|
373
378
|
{
|
|
374
379
|
id: "skillContentChars",
|
|
@@ -654,13 +659,37 @@ window.__ModuleLoader__.load({
|
|
|
654
659
|
latest.current = value;
|
|
655
660
|
setState(value);
|
|
656
661
|
};
|
|
662
|
+
/**
|
|
663
|
+
* Read the window the card is about: the writes WAITING and the writes RUNNING.
|
|
664
|
+
*
|
|
665
|
+
* T4-05/A47: the route has always taken `?status=` — the CLI asks for both lists — but this card
|
|
666
|
+
* asked for neither, so a record whose approve died between the claim and the decision showed as
|
|
667
|
+
* "nothing is waiting for a decision" while `/evolution pending` and doctor listed it. The two
|
|
668
|
+
* answers are ordered the way the CLI orders them (waiting first); either one failing fails the
|
|
669
|
+
* read, because a half-list would read as a complete window.
|
|
670
|
+
*/
|
|
657
671
|
const load = async () => {
|
|
658
672
|
apply(loading);
|
|
659
|
-
const
|
|
660
|
-
|
|
673
|
+
const [waiting, running] = await Promise.all([api.pending(), api.pending("executing")]);
|
|
674
|
+
if (!waiting.ok) {
|
|
675
|
+
apply((current) => failed(current, waiting.message));
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (!running.ok) {
|
|
679
|
+
apply((current) => failed(current, running.message));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
apply((current) => loaded(current, [...waiting.data, ...running.data]));
|
|
661
683
|
};
|
|
662
684
|
(0, react.useEffect)(() => {
|
|
663
685
|
load();
|
|
686
|
+
const onFocus = () => {
|
|
687
|
+
load();
|
|
688
|
+
};
|
|
689
|
+
window.addEventListener("focus", onFocus);
|
|
690
|
+
return () => {
|
|
691
|
+
window.removeEventListener("focus", onFocus);
|
|
692
|
+
};
|
|
664
693
|
}, []);
|
|
665
694
|
const decide = (id, action) => {
|
|
666
695
|
apply((current) => beginAction(current, id));
|
|
@@ -738,9 +767,13 @@ window.__ModuleLoader__.load({
|
|
|
738
767
|
}, "+ " + line));
|
|
739
768
|
}
|
|
740
769
|
}
|
|
741
|
-
return (0, react.createElement)("div", null, (0, react.createElement)("p", { className: "evolution-param-facts" }, ...facts), (0, react.createElement)("pre", { className: "evolution-param-
|
|
770
|
+
return (0, react.createElement)("div", null, (0, react.createElement)("p", { className: "evolution-param-facts" }, ...facts), (0, react.createElement)("pre", { className: "evolution-param-diff" }, ...lines.length === 0 ? ["±"] : lines));
|
|
742
771
|
};
|
|
743
772
|
const row = (entry) => {
|
|
773
|
+
if (entry.status === "executing") return (0, react.createElement)("div", {
|
|
774
|
+
className: "evolution-param-field",
|
|
775
|
+
key: entry.id
|
|
776
|
+
}, (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));
|
|
744
777
|
const busy = state.busy.includes(entry.id);
|
|
745
778
|
const button = (label, action) => (0, react.createElement)("button", {
|
|
746
779
|
className: "evolution-param-button",
|
|
@@ -773,7 +806,9 @@ window.__ModuleLoader__.load({
|
|
|
773
806
|
}, t("approvalRetry")));
|
|
774
807
|
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));
|
|
775
808
|
};
|
|
776
|
-
const
|
|
809
|
+
const rows = state.view.kind === "ready" ? state.view.rows : [];
|
|
810
|
+
const waiting = rows.filter((entry) => entry.status !== "executing").length;
|
|
811
|
+
const running = rows.length - waiting;
|
|
777
812
|
return (0, react.createElement)("section", { className: "evolution-param-card" }, (0, react.createElement)("button", {
|
|
778
813
|
className: "evolution-param-head",
|
|
779
814
|
type: "button",
|
|
@@ -781,7 +816,13 @@ window.__ModuleLoader__.load({
|
|
|
781
816
|
setOpen(!open);
|
|
782
817
|
},
|
|
783
818
|
"aria-expanded": open
|
|
784
|
-
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("approvalTitle")),
|
|
819
|
+
}, (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", {
|
|
820
|
+
className: "evolution-param-button",
|
|
821
|
+
type: "button",
|
|
822
|
+
onClick: () => {
|
|
823
|
+
load();
|
|
824
|
+
}
|
|
825
|
+
}, t("approvalRefresh"))), body()) : null);
|
|
785
826
|
}
|
|
786
827
|
//#endregion
|
|
787
828
|
//#region src/client/messages.ts
|
|
@@ -814,7 +855,9 @@ window.__ModuleLoader__.load({
|
|
|
814
855
|
changed: "{n} 项已改",
|
|
815
856
|
discard: "放弃修改",
|
|
816
857
|
saving: "保存中…",
|
|
858
|
+
resetting: "恢复中…",
|
|
817
859
|
refused: "这个值没有被接受(界面上保持原样)。想看原因:运行 /evolution params 或 /evolution doctor。",
|
|
860
|
+
resetRefused: "恢复默认值没有生效(这一行还是你的值)。想看原因:运行 /evolution params 或 /evolution doctor。",
|
|
818
861
|
cardReview: "会话回顾",
|
|
819
862
|
cardMemory: "长期记忆",
|
|
820
863
|
cardToolMemory: "记忆写入",
|
|
@@ -828,6 +871,9 @@ window.__ModuleLoader__.load({
|
|
|
828
871
|
approvalApprove: "批准",
|
|
829
872
|
approvalReject: "拒绝",
|
|
830
873
|
approvalCount: "{n} 项待批",
|
|
874
|
+
approvalRunningCount: "{n} 项执行中",
|
|
875
|
+
approvalRefresh: "刷新",
|
|
876
|
+
approvalExecuting: "执行中:这条的写效果可能已经落地,不要重复批准;要清掉它请用拒绝。",
|
|
831
877
|
approvalKindMemory: "记忆",
|
|
832
878
|
approvalKindSkill: "技能",
|
|
833
879
|
approvalKindCapability: "能力",
|
|
@@ -861,7 +907,9 @@ window.__ModuleLoader__.load({
|
|
|
861
907
|
changed: "{n} changed",
|
|
862
908
|
discard: "Discard",
|
|
863
909
|
saving: "Saving…",
|
|
910
|
+
resetting: "Resetting…",
|
|
864
911
|
refused: "The value was not accepted (the page keeps what you see). For the reason run /evolution params or /evolution doctor.",
|
|
912
|
+
resetRefused: "The reset did not take (this row still holds your value). For the reason run /evolution params or /evolution doctor.",
|
|
865
913
|
cardReview: "Session review",
|
|
866
914
|
cardMemory: "Long-term memory",
|
|
867
915
|
cardToolMemory: "Writing memory",
|
|
@@ -875,6 +923,9 @@ window.__ModuleLoader__.load({
|
|
|
875
923
|
approvalApprove: "Approve",
|
|
876
924
|
approvalReject: "Reject",
|
|
877
925
|
approvalCount: "{n} waiting",
|
|
926
|
+
approvalRunningCount: "{n} running",
|
|
927
|
+
approvalRefresh: "Refresh",
|
|
928
|
+
approvalExecuting: "Running: this write may already have landed — do not approve it twice; reject it to clear it.",
|
|
878
929
|
approvalKindMemory: "memory",
|
|
879
930
|
approvalKindSkill: "skill",
|
|
880
931
|
approvalKindCapability: "capability",
|
|
@@ -916,24 +967,38 @@ window.__ModuleLoader__.load({
|
|
|
916
967
|
//#region src/client/settle.ts
|
|
917
968
|
/**
|
|
918
969
|
* Structural equality for the values a parameter can hold.
|
|
970
|
+
*
|
|
971
|
+
* T5-10: the comparison walks the value instead of comparing serializations, because key ORDER is
|
|
972
|
+
* not part of an object's value — `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` are the same value, but
|
|
973
|
+
* their `JSON.stringify` output differs, and the card then read a save that HAD landed as refused
|
|
974
|
+
* (draft kept, error line shown). Array order IS part of an array's value, so arrays compare
|
|
975
|
+
* element-wise. `NaN` equals itself through the `Object.is` fast path; other primitives use `===`,
|
|
976
|
+
* which keeps `0` and `-0` equal exactly as the serialization comparison did.
|
|
919
977
|
* @param a - one side of the comparison.
|
|
920
978
|
* @param b - the other side.
|
|
921
979
|
* @returns true when the two are the same value.
|
|
922
980
|
*/
|
|
923
981
|
function sameValue(a, b) {
|
|
924
982
|
if (Object.is(a, b)) return true;
|
|
925
|
-
if (
|
|
926
|
-
|
|
983
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return a === b;
|
|
984
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
985
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
986
|
+
return a.every((item, index) => sameValue(item, b[index]));
|
|
987
|
+
}
|
|
988
|
+
const left = a;
|
|
989
|
+
const right = b;
|
|
990
|
+
const keys = Object.keys(left);
|
|
991
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && sameValue(left[key], right[key]));
|
|
927
992
|
}
|
|
928
993
|
/**
|
|
929
|
-
* Whether every staged
|
|
930
|
-
* @param pending - the
|
|
994
|
+
* Whether every staged operation is visible in the user layer.
|
|
995
|
+
* @param pending - the operations awaiting a verdict.
|
|
931
996
|
* @param user - the user layer of the post-write snapshot; a non-section reads as empty.
|
|
932
|
-
* @returns true when each staged id now holds the value
|
|
997
|
+
* @returns true when each staged id now holds the requested value, or is gone after an `unset`.
|
|
933
998
|
*/
|
|
934
|
-
function
|
|
999
|
+
function landedSettlements(pending, user) {
|
|
935
1000
|
const section = typeof user === "object" && user !== null && !Array.isArray(user) ? user : {};
|
|
936
|
-
return pending.every((entry) => sameValue(section[entry.id], entry.want));
|
|
1001
|
+
return pending.every((entry) => entry.op === "unset" ? !Object.hasOwn(section, entry.id) : sameValue(section[entry.id], entry.want));
|
|
937
1002
|
}
|
|
938
1003
|
//#endregion
|
|
939
1004
|
//#region src/client/ParamCard.ts
|
|
@@ -1032,11 +1097,11 @@ window.__ModuleLoader__.load({
|
|
|
1032
1097
|
}, 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", {
|
|
1033
1098
|
type: "button",
|
|
1034
1099
|
className: "evolution-param-button",
|
|
1035
|
-
disabled: props.disabled,
|
|
1100
|
+
disabled: props.disabled || props.clearing,
|
|
1036
1101
|
onClick: () => {
|
|
1037
|
-
props.
|
|
1102
|
+
props.onClear(field.id);
|
|
1038
1103
|
}
|
|
1039
|
-
}, t("reset"))) : null);
|
|
1104
|
+
}, props.clearing ? t("resetting") : t("reset"))) : null);
|
|
1040
1105
|
}
|
|
1041
1106
|
/**
|
|
1042
1107
|
* Render one row's card body from a supplied snapshot.
|
|
@@ -1044,18 +1109,19 @@ window.__ModuleLoader__.load({
|
|
|
1044
1109
|
* @returns the card.
|
|
1045
1110
|
*/
|
|
1046
1111
|
function ParamCardView(props) {
|
|
1047
|
-
const { t, fields,
|
|
1112
|
+
const { t, fields, mutateOnce, clear, namespace, snapshot } = props;
|
|
1048
1113
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1049
1114
|
const [draft, setDraft] = (0, react.useState)({});
|
|
1050
1115
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1051
1116
|
const [error, setError] = (0, react.useState)("");
|
|
1052
1117
|
const [pending, setPending] = (0, react.useState)(null);
|
|
1118
|
+
const [clearing, setClearing] = (0, react.useState)(null);
|
|
1053
1119
|
if (snapshot.status === "loading") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("loading"));
|
|
1054
1120
|
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"));
|
|
1055
1121
|
const user = isSection$1(snapshot.user) ? snapshot.user : {};
|
|
1056
1122
|
const value = isSection$1(snapshot.value) ? snapshot.value : {};
|
|
1057
1123
|
const disabled = !snapshot.writable;
|
|
1058
|
-
const locked = disabled || busy;
|
|
1124
|
+
const locked = disabled || busy || clearing !== null;
|
|
1059
1125
|
const titleKey = NAMESPACE_TITLES[namespace];
|
|
1060
1126
|
const title = titleKey === void 0 ? namespace : t(titleKey);
|
|
1061
1127
|
const changed = fields.filter((field) => Object.hasOwn(user, field.id)).length;
|
|
@@ -1084,20 +1150,54 @@ window.__ModuleLoader__.load({
|
|
|
1084
1150
|
if (pending === null) return;
|
|
1085
1151
|
setPending(null);
|
|
1086
1152
|
setBusy(false);
|
|
1087
|
-
|
|
1088
|
-
|
|
1153
|
+
setClearing(null);
|
|
1154
|
+
if (!landedSettlements(pending, snapshot.user)) {
|
|
1155
|
+
setError(t(pending.every((entry) => entry.op === "unset") ? "resetRefused" : "refused"));
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (pending.some((entry) => entry.op === "set")) setDraft({});
|
|
1089
1159
|
}, [
|
|
1090
1160
|
pending,
|
|
1091
1161
|
snapshot.user,
|
|
1092
1162
|
t
|
|
1093
1163
|
]);
|
|
1164
|
+
/**
|
|
1165
|
+
* Restore one field's deployment default (T5-05/A60).
|
|
1166
|
+
*
|
|
1167
|
+
* The scope's own boolean is NOT the verdict — same reason the save path judges the settled
|
|
1168
|
+
* snapshot instead: a host refusal leaves the user layer untouched, which is precisely what a
|
|
1169
|
+
* structural check sees and a dropped return value does not. A throw (the settings service
|
|
1170
|
+
* rejecting a non-volatile path) used to escape as an unhandled rejection.
|
|
1171
|
+
*/
|
|
1172
|
+
const reset = (field) => {
|
|
1173
|
+
setClearing(field);
|
|
1174
|
+
setError("");
|
|
1175
|
+
(async () => {
|
|
1176
|
+
try {
|
|
1177
|
+
await clear(field);
|
|
1178
|
+
} catch (caught) {
|
|
1179
|
+
setClearing(null);
|
|
1180
|
+
setError(caught instanceof Error && caught.message !== "" ? caught.message : t("resetRefused"));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
setPending([{
|
|
1184
|
+
op: "unset",
|
|
1185
|
+
id: field
|
|
1186
|
+
}]);
|
|
1187
|
+
})();
|
|
1188
|
+
};
|
|
1094
1189
|
const save = () => {
|
|
1095
1190
|
setBusy(true);
|
|
1096
1191
|
setError("");
|
|
1097
1192
|
(async () => {
|
|
1098
1193
|
try {
|
|
1099
|
-
|
|
1194
|
+
await mutateOnce(dirty.map((field) => ({
|
|
1195
|
+
op: "set",
|
|
1196
|
+
path: [field.id],
|
|
1197
|
+
value: parseFor(field.control, textOf(field))
|
|
1198
|
+
})));
|
|
1100
1199
|
setPending(dirty.map((field) => ({
|
|
1200
|
+
op: "set",
|
|
1101
1201
|
id: field.id,
|
|
1102
1202
|
want: parseFor(field.control, textOf(field))
|
|
1103
1203
|
})));
|
|
@@ -1120,9 +1220,10 @@ window.__ModuleLoader__.load({
|
|
|
1120
1220
|
text: textOf(field),
|
|
1121
1221
|
overridden: Object.hasOwn(user, field.id),
|
|
1122
1222
|
disabled: locked,
|
|
1223
|
+
clearing: clearing === field.id,
|
|
1123
1224
|
t,
|
|
1124
1225
|
onChange: change,
|
|
1125
|
-
|
|
1226
|
+
onClear: reset
|
|
1126
1227
|
})), 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", {
|
|
1127
1228
|
type: "button",
|
|
1128
1229
|
className: "evolution-param-button",
|
|
@@ -1135,7 +1236,7 @@ window.__ModuleLoader__.load({
|
|
|
1135
1236
|
type: "button",
|
|
1136
1237
|
className: "evolution-param-button",
|
|
1137
1238
|
"data-primary": "true",
|
|
1138
|
-
disabled:
|
|
1239
|
+
disabled: locked || dirty.length === 0,
|
|
1139
1240
|
onClick: save
|
|
1140
1241
|
}, busy ? t("saving") : t("apply")))) : null);
|
|
1141
1242
|
}
|
|
@@ -1151,7 +1252,7 @@ window.__ModuleLoader__.load({
|
|
|
1151
1252
|
namespace: props.namespace,
|
|
1152
1253
|
fields: props.fields,
|
|
1153
1254
|
t: props.t,
|
|
1154
|
-
|
|
1255
|
+
mutateOnce: props.mutateOnce,
|
|
1155
1256
|
clear: props.clear,
|
|
1156
1257
|
snapshot: props.useParamSection((state) => state)
|
|
1157
1258
|
});
|
|
@@ -1244,11 +1345,20 @@ window.__ModuleLoader__.load({
|
|
|
1244
1345
|
let form;
|
|
1245
1346
|
let projected;
|
|
1246
1347
|
let offForm;
|
|
1247
|
-
|
|
1248
|
-
|
|
1348
|
+
/**
|
|
1349
|
+
* Bind the seat's form for this row.
|
|
1350
|
+
* @param force - re-read the seat even when one is already bound, so a REPLACED seat (the shell
|
|
1351
|
+
* re-composed, a different form object for the same row) is picked up instead of kept stale.
|
|
1352
|
+
*/
|
|
1353
|
+
const bind = (force = false) => {
|
|
1354
|
+
if (form !== void 0 && !force) return;
|
|
1249
1355
|
const seat = probe();
|
|
1250
1356
|
if (seat === void 0) return;
|
|
1251
|
-
|
|
1357
|
+
const next = seat.get(namespace);
|
|
1358
|
+
if (next === form) return;
|
|
1359
|
+
offForm?.();
|
|
1360
|
+
offForm = void 0;
|
|
1361
|
+
form = next;
|
|
1252
1362
|
projected = stableProjection(form);
|
|
1253
1363
|
};
|
|
1254
1364
|
const attach = () => {
|
|
@@ -1262,6 +1372,11 @@ window.__ModuleLoader__.load({
|
|
|
1262
1372
|
bind();
|
|
1263
1373
|
return projected === void 0 ? SEAT_MISSING : projected();
|
|
1264
1374
|
},
|
|
1375
|
+
revive: () => {
|
|
1376
|
+
bind(true);
|
|
1377
|
+
attach();
|
|
1378
|
+
for (const listener of listeners) listener();
|
|
1379
|
+
},
|
|
1265
1380
|
subscribe: (listener) => {
|
|
1266
1381
|
listeners.add(listener);
|
|
1267
1382
|
bind();
|
|
@@ -1285,8 +1400,8 @@ window.__ModuleLoader__.load({
|
|
|
1285
1400
|
function writeFaceFor(probe, namespace) {
|
|
1286
1401
|
const form = () => probe()?.get(namespace);
|
|
1287
1402
|
return {
|
|
1288
|
-
|
|
1289
|
-
await form()?.
|
|
1403
|
+
mutateOnce: async (ops) => {
|
|
1404
|
+
await form()?.mutate(ops);
|
|
1290
1405
|
},
|
|
1291
1406
|
clear: async (field) => {
|
|
1292
1407
|
await form()?.unset(field);
|
|
@@ -1312,6 +1427,9 @@ window.__ModuleLoader__.load({
|
|
|
1312
1427
|
sources.set(namespace, source);
|
|
1313
1428
|
return source;
|
|
1314
1429
|
},
|
|
1430
|
+
revive: () => {
|
|
1431
|
+
for (const source of sources.values()) source.revive();
|
|
1432
|
+
},
|
|
1315
1433
|
dispose: () => {
|
|
1316
1434
|
sources.clear();
|
|
1317
1435
|
}
|
|
@@ -1354,12 +1472,8 @@ window.__ModuleLoader__.load({
|
|
|
1354
1472
|
fields,
|
|
1355
1473
|
t,
|
|
1356
1474
|
snapshot: sectionSnapshotOf(form.state),
|
|
1357
|
-
|
|
1358
|
-
await form.mutate(
|
|
1359
|
-
op: "set",
|
|
1360
|
-
path: [field],
|
|
1361
|
-
value
|
|
1362
|
-
}], form.state.revision);
|
|
1475
|
+
mutateOnce: async (ops) => {
|
|
1476
|
+
await form.mutate(ops, form.state.revision);
|
|
1363
1477
|
},
|
|
1364
1478
|
clear: async (field) => {
|
|
1365
1479
|
await form.mutate([{
|
|
@@ -1459,7 +1573,7 @@ window.__ModuleLoader__.load({
|
|
|
1459
1573
|
const CSS_TAG_ID = "@lmzhen/dsh-evolution-settings-ui/settings.css";
|
|
1460
1574
|
/** The stylesheet the section injects once. */
|
|
1461
1575
|
const CSS = [
|
|
1462
|
-
tokenVars(".evolution-params"),
|
|
1576
|
+
tokenVars(".evolution-params,.evolution-param-card"),
|
|
1463
1577
|
".evolution-params{display:flex;flex-direction:column}",
|
|
1464
1578
|
".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)}",
|
|
1465
1579
|
".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)}",
|
|
@@ -1497,7 +1611,7 @@ window.__ModuleLoader__.load({
|
|
|
1497
1611
|
".evolution-param-error{margin:0;color:var(--dsw-alias-state-error-primary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
|
|
1498
1612
|
".evolution-param-facts{display:flex;flex-wrap:wrap;align-items:baseline;margin:0;color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
|
|
1499
1613
|
".evolution-param-fact+.evolution-param-fact::before{content:\"·\";margin:0 var(--evo-space-6);color:var(--dsw-alias-label-tertiary)}",
|
|
1500
|
-
".evolution-param-
|
|
1614
|
+
".evolution-param-diff{margin:0;padding:var(--evo-space-12) var(--evo-space-16);max-height:var(--evo-cap-block);overflow:auto;border:var(--evo-hairline-width) solid var(--dsw-alias-border-l1);border-radius:var(--evo-radius-surface);background:var(--dsw-alias-markdown-code-block);color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block-small);white-space:pre-wrap}",
|
|
1501
1615
|
".evolution-param-line-add{color:var(--evo-tone-success)}",
|
|
1502
1616
|
".evolution-param-line-del{color:var(--evo-tone-danger)}"
|
|
1503
1617
|
].join("\n");
|
|
@@ -1545,7 +1659,8 @@ window.__ModuleLoader__.load({
|
|
|
1545
1659
|
});
|
|
1546
1660
|
registered = true;
|
|
1547
1661
|
return dispose;
|
|
1548
|
-
} catch {
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
ctx.logger.warn(`evolution-settings-ui: the locale seat refused the "${NS}" dictionary registration (${error instanceof Error ? error.message : String(error)}) — the cards fall back to the bundle's own copy`);
|
|
1549
1664
|
return () => {};
|
|
1550
1665
|
}
|
|
1551
1666
|
}, "evolution-settings: dictionaries");
|
|
@@ -1564,6 +1679,9 @@ window.__ModuleLoader__.load({
|
|
|
1564
1679
|
ctx.effect(() => () => {
|
|
1565
1680
|
sources.dispose();
|
|
1566
1681
|
}, "evolution-settings: sources");
|
|
1682
|
+
ctx.inject?.(["configForms"], () => {
|
|
1683
|
+
sources.revive();
|
|
1684
|
+
});
|
|
1567
1685
|
seam.slots.inject("settings.section", function* () {
|
|
1568
1686
|
yield seam.slots.register({
|
|
1569
1687
|
name: "settings.section",
|
|
@@ -1579,13 +1697,13 @@ window.__ModuleLoader__.load({
|
|
|
1579
1697
|
});
|
|
1580
1698
|
seam.slots.inject(CARD_SLOT, function* () {
|
|
1581
1699
|
for (const section of CLIENT_PARAM_SECTIONS) {
|
|
1582
|
-
const source = sources.sourceFor(section.
|
|
1583
|
-
const {
|
|
1700
|
+
const source = sources.sourceFor(section.seatKey);
|
|
1701
|
+
const { mutateOnce, clear } = writeFaceFor(probe, section.namespace);
|
|
1584
1702
|
const face = () => ({
|
|
1585
1703
|
namespace: section.namespace,
|
|
1586
1704
|
fields: section.fields,
|
|
1587
1705
|
t,
|
|
1588
|
-
|
|
1706
|
+
mutateOnce,
|
|
1589
1707
|
clear,
|
|
1590
1708
|
hooks: { paramSection: source }
|
|
1591
1709
|
});
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { type ReactNode } from 'react';
|
|
17
17
|
import type { ClientParamField } from './generated-params.ts';
|
|
18
18
|
import { type MessageKey } from './messages.ts';
|
|
19
|
-
import type { ParamSectionSnapshot, ParamSectionSource } from './seam.ts';
|
|
19
|
+
import type { ParamSectionSnapshot, ParamSectionSource, SettingsPathOpLike } from './seam.ts';
|
|
20
20
|
/**
|
|
21
21
|
* Injected face: plain data and callbacks, plus the hook seat.
|
|
22
22
|
*
|
|
@@ -29,7 +29,8 @@ export interface ParamCardFace {
|
|
|
29
29
|
namespace: string;
|
|
30
30
|
fields: readonly ClientParamField[];
|
|
31
31
|
t: (key: MessageKey) => string;
|
|
32
|
-
|
|
32
|
+
/** ONE call per save, carrying every dirty field as one op list (T5-01/A56). */
|
|
33
|
+
mutateOnce: (ops: readonly SettingsParamOp[]) => Promise<void>;
|
|
33
34
|
clear: (field: string) => Promise<void>;
|
|
34
35
|
hooks: {
|
|
35
36
|
paramSection: ParamSectionSource;
|
|
@@ -44,6 +45,8 @@ export type ParamCardProps = Omit<ParamCardFace, 'hooks'> & {
|
|
|
44
45
|
/** Bound from \`hooks.paramSection\` by the renderer. */
|
|
45
46
|
readonly useParamSection: <T>(selector: (state: ParamSectionSnapshot) => T) => T;
|
|
46
47
|
};
|
|
48
|
+
/** The op list a save hands to the seat — the seam's own shape, never a second vocabulary. */
|
|
49
|
+
export type SettingsParamOp = SettingsPathOpLike;
|
|
47
50
|
/**
|
|
48
51
|
* Render one namespace's card.
|
|
49
52
|
* @param props - the card data, its callbacks, and the bound section hook.
|
|
@@ -54,7 +57,9 @@ export interface ParamCardViewProps {
|
|
|
54
57
|
namespace: string;
|
|
55
58
|
fields: readonly ClientParamField[];
|
|
56
59
|
t: (key: MessageKey) => string;
|
|
57
|
-
|
|
60
|
+
/** ONE call per save, carrying every dirty field as one op list (T5-01/A56). */
|
|
61
|
+
mutateOnce: (ops: readonly SettingsParamOp[]) => Promise<void>;
|
|
62
|
+
/** Ask the scope to drop one override; resolves when the scope answered, not when it landed. */
|
|
58
63
|
clear: (field: string) => Promise<void>;
|
|
59
64
|
/** The current row state, from whichever source owns it (the bound hook or the page). */
|
|
60
65
|
snapshot: ParamSectionSnapshot;
|
|
@@ -41,10 +41,18 @@ export type ApprovalAnswer<T> = {
|
|
|
41
41
|
readonly ok: false;
|
|
42
42
|
readonly message: string;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Which window the pending route is asked for. The host route has always taken `?status=` (the CLI
|
|
46
|
+
* asks for both); this half asked for neither, so a record stuck in `executing` — an approve that
|
|
47
|
+
* died between the claim and the decision, or another process running it — was invisible in the GUI
|
|
48
|
+
* while `/evolution pending` and doctor both listed it (T4-05/A47). `PendingRow.status` even declared
|
|
49
|
+
* the member, so the type face expected data the data face could not reach.
|
|
50
|
+
*/
|
|
51
|
+
export type PendingWindow = 'pending' | 'executing';
|
|
44
52
|
/** How this bundle reaches the host. Injectable so a spec drives it without a server. */
|
|
45
53
|
export interface ApprovalApi {
|
|
46
|
-
/** The staged writes
|
|
47
|
-
pending: () => Promise<ApprovalAnswer<readonly PendingRow[]>>;
|
|
54
|
+
/** The staged writes in one window; `pending` when the caller does not say. */
|
|
55
|
+
pending: (status?: PendingWindow) => Promise<ApprovalAnswer<readonly PendingRow[]>>;
|
|
48
56
|
/** Ask the host to replay one staged write. */
|
|
49
57
|
approve: (id: string) => Promise<ApprovalAnswer<unknown>>;
|
|
50
58
|
/** Close one staged write without replaying it. */
|
|
@@ -23,9 +23,15 @@ export interface ClientParamField {
|
|
|
23
23
|
/** Display names for `values`, same order ([] shows the raw value). */
|
|
24
24
|
valueLabels: readonly string[];
|
|
25
25
|
}
|
|
26
|
-
/** One card: the settings namespace
|
|
26
|
+
/** One card: the settings namespace it writes through, the SEAT key its source is looked up by,
|
|
27
|
+
* and the fields it exposes. A73 (`ClientParamSection.namespace` 一名字两义): the two roles are
|
|
28
|
+
* spelled separately so a Host may key them differently — `namespace` is what a save writes into
|
|
29
|
+
* (`update(namespace, patch)`) and what the card title is looked up by; `seatKey` is what the
|
|
30
|
+
* shell's `sources.sourceFor(...)` probes. Both are the LOADER ROW ID today. */
|
|
27
31
|
export interface ClientParamSection {
|
|
28
32
|
namespace: string;
|
|
33
|
+
/** The settings-card seat key the shell probes for this section's source. */
|
|
34
|
+
seatKey: string;
|
|
29
35
|
fields: readonly ClientParamField[];
|
|
30
36
|
}
|
|
31
37
|
export declare const CLIENT_PARAM_SECTIONS: readonly ClientParamSection[];
|
|
@@ -27,7 +27,9 @@ export declare const zh: {
|
|
|
27
27
|
readonly changed: "{n} 项已改";
|
|
28
28
|
readonly discard: "放弃修改";
|
|
29
29
|
readonly saving: "保存中…";
|
|
30
|
+
readonly resetting: "恢复中…";
|
|
30
31
|
readonly refused: "这个值没有被接受(界面上保持原样)。想看原因:运行 /evolution params 或 /evolution doctor。";
|
|
32
|
+
readonly resetRefused: "恢复默认值没有生效(这一行还是你的值)。想看原因:运行 /evolution params 或 /evolution doctor。";
|
|
31
33
|
readonly cardReview: "会话回顾";
|
|
32
34
|
readonly cardMemory: "长期记忆";
|
|
33
35
|
readonly cardToolMemory: "记忆写入";
|
|
@@ -41,6 +43,9 @@ export declare const zh: {
|
|
|
41
43
|
readonly approvalApprove: "批准";
|
|
42
44
|
readonly approvalReject: "拒绝";
|
|
43
45
|
readonly approvalCount: "{n} 项待批";
|
|
46
|
+
readonly approvalRunningCount: "{n} 项执行中";
|
|
47
|
+
readonly approvalRefresh: "刷新";
|
|
48
|
+
readonly approvalExecuting: "执行中:这条的写效果可能已经落地,不要重复批准;要清掉它请用拒绝。";
|
|
44
49
|
readonly approvalKindMemory: "记忆";
|
|
45
50
|
readonly approvalKindSkill: "技能";
|
|
46
51
|
readonly approvalKindCapability: "能力";
|
|
@@ -53,6 +53,16 @@ export interface ParamSectionSnapshot {
|
|
|
53
53
|
export interface ParamSectionSource {
|
|
54
54
|
getSnapshot(): ParamSectionSnapshot;
|
|
55
55
|
subscribe(listener: () => void): () => void;
|
|
56
|
+
/**
|
|
57
|
+
* T5-11/A66: the seat ARRIVED (or was replaced) — bind it and wake every listener.
|
|
58
|
+
*
|
|
59
|
+
* The source re-probes on every READ, which covers a card that has not rendered yet; it does not
|
|
60
|
+
* cover a card that already rendered its "no settings surface" state, because nothing tells React to
|
|
61
|
+
* read again. `ctx.inject` is the platform's own arrival channel (it runs the callback when the
|
|
62
|
+
* service appears and re-runs it when the service changes), so the bundle subscribes there instead of
|
|
63
|
+
* inventing a timer.
|
|
64
|
+
*/
|
|
65
|
+
revive(): void;
|
|
56
66
|
}
|
|
57
67
|
/** One ordered field operation, as the client settings seat takes them. */
|
|
58
68
|
export interface SettingsPathOpLike {
|
|
@@ -8,18 +8,28 @@
|
|
|
8
8
|
* too — so the verdict compares what each staged id holds now against what was requested.
|
|
9
9
|
* @module @lmzhen/dsh-evolution-settings-ui
|
|
10
10
|
*/
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/**
|
|
12
|
+
* One staged operation awaiting its verdict, in the platform's own two op kinds.
|
|
13
|
+
*
|
|
14
|
+
* `unset` is here because "restore the default" had NO verdict at all (T5-05/A60): the reset path
|
|
15
|
+
* called the scope and dropped its answer, so a host refusal changed nothing on screen — the row kept
|
|
16
|
+
* its "edited by you" chip and the reader concluded the button was broken. The two ops need different
|
|
17
|
+
* evidence (a set landed when the id holds the value; an unset landed when the id is GONE), which is
|
|
18
|
+
* exactly why they are one union judged by one function instead of two look-alike helpers.
|
|
19
|
+
*/
|
|
20
|
+
export type Settlement = {
|
|
21
|
+
readonly op: 'set';
|
|
14
22
|
readonly id: string;
|
|
15
|
-
/** The value handed to the scope's `set`. */
|
|
16
23
|
readonly want: unknown;
|
|
17
|
-
}
|
|
24
|
+
} | {
|
|
25
|
+
readonly op: 'unset';
|
|
26
|
+
readonly id: string;
|
|
27
|
+
};
|
|
18
28
|
/**
|
|
19
|
-
* Whether every staged
|
|
20
|
-
* @param pending - the
|
|
29
|
+
* Whether every staged operation is visible in the user layer.
|
|
30
|
+
* @param pending - the operations awaiting a verdict.
|
|
21
31
|
* @param user - the user layer of the post-write snapshot; a non-section reads as empty.
|
|
22
|
-
* @returns true when each staged id now holds the value
|
|
32
|
+
* @returns true when each staged id now holds the requested value, or is gone after an `unset`.
|
|
23
33
|
*/
|
|
24
|
-
export declare function
|
|
34
|
+
export declare function landedSettlements(pending: readonly Settlement[], user: unknown): boolean;
|
|
25
35
|
//# sourceMappingURL=settle.d.ts.map
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* apply time would capture `undefined` for the whole life of the fiber.
|
|
23
23
|
* @module @lmzhen/dsh-evolution-settings-ui/client
|
|
24
24
|
*/
|
|
25
|
-
import type { ConfigFormLike, ConfigFormSnapshotLike, ConfigFormsSeat, ParamSectionSnapshot, ParamSectionSource } from './seam.ts';
|
|
25
|
+
import type { ConfigFormLike, ConfigFormSnapshotLike, ConfigFormsSeat, ParamSectionSnapshot, ParamSectionSource, SettingsPathOpLike } from './seam.ts';
|
|
26
26
|
/** What a card shows when this UI found no settings seat at all. */
|
|
27
27
|
export declare const SEAT_MISSING: ParamSectionSnapshot;
|
|
28
28
|
/** What a card shows when the seat served the row but projecting it failed. A frozen constant
|
|
@@ -70,7 +70,7 @@ export declare function sourceFor(probe: SeatProbe, namespace: string): ParamSec
|
|
|
70
70
|
* @returns the two calls a card's save/reset path uses; without a seat they resolve without writing.
|
|
71
71
|
*/
|
|
72
72
|
export declare function writeFaceFor(probe: SeatProbe, namespace: string): {
|
|
73
|
-
|
|
73
|
+
mutateOnce: (ops: readonly SettingsPathOpLike[]) => Promise<void>;
|
|
74
74
|
clear: (field: string) => Promise<void>;
|
|
75
75
|
};
|
|
76
76
|
/**
|
|
@@ -84,6 +84,9 @@ export declare function writeFaceFor(probe: SeatProbe, namespace: string): {
|
|
|
84
84
|
*/
|
|
85
85
|
export declare function createSourceCache(probe: SeatProbe): {
|
|
86
86
|
sourceFor: (namespace: string) => ParamSectionSource;
|
|
87
|
+
/** T5-11/A66: the seat arrived — wake every source this cache built (the cached ones are exactly the
|
|
88
|
+
* ones whose cards are already mounted and waiting). */
|
|
89
|
+
revive: () => void;
|
|
87
90
|
dispose: () => void;
|
|
88
91
|
};
|
|
89
92
|
//# sourceMappingURL=source.d.ts.map
|
package/package.json
CHANGED