@lmzhen/dsh-evolution-settings-ui 0.26.15 → 0.27.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 +401 -111
- package/lib/types/client/DiagnosticsCard.d.ts +26 -0
- package/lib/types/client/ParamCard.d.ts +14 -0
- package/lib/types/client/PendingCard.d.ts +3 -0
- package/lib/types/client/api.d.ts +3 -8
- package/lib/types/client/contracts.generated.d.ts +37 -0
- package/lib/types/client/diagnostics-state.d.ts +28 -0
- package/lib/types/client/fallbacks.generated.d.ts +8 -0
- package/lib/types/client/messages.d.ts +22 -3
- package/lib/types/client/pending-state.d.ts +5 -4
- package/lib/types/client/route-client.generated.d.ts +45 -0
- package/lib/types/client/routes.generated.d.ts +31 -0
- package/lib/types/client/seam.d.ts +2 -3
- package/lib/types/client/states.generated.d.ts +43 -0
- package/lib/types/client/styles.d.ts +11 -1
- package/package.json +5 -2
- package/lib/types/client/approval-routes.d.ts +0 -17
package/lib/client.js
CHANGED
|
@@ -5,73 +5,138 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
//#region src/client/
|
|
8
|
+
//#region src/client/routes.generated.ts
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* them against `APPROVAL_ROUTES` in `evolution-approval` — a rename on one side would otherwise surface
|
|
15
|
-
* as a 404 in the GUI instead of in CI.
|
|
16
|
-
* @module @lmzhen/dsh-evolution-settings-ui/client/approval-routes
|
|
10
|
+
* Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
|
|
11
|
+
* A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
|
|
12
|
+
* external), so the table is mirrored here mechanically; a rename in core regenerates all three
|
|
13
|
+
* client copies, and the generator's --check makes a stale copy red.
|
|
17
14
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
const ROUTES = {
|
|
16
|
+
approval: {
|
|
17
|
+
pending: "/api/dsh-evolution/approval/pending",
|
|
18
|
+
preview: "/api/dsh-evolution/approval/preview",
|
|
19
|
+
approve: "/api/dsh-evolution/approval/approve",
|
|
20
|
+
reject: "/api/dsh-evolution/approval/reject"
|
|
21
|
+
},
|
|
22
|
+
"skill-history": {
|
|
23
|
+
skills: "/api/dsh-evolution/skill-history/skills",
|
|
24
|
+
versions: "/api/dsh-evolution/skill-history/versions",
|
|
25
|
+
diff: "/api/dsh-evolution/skill-history/versions/diff",
|
|
26
|
+
body: "/api/dsh-evolution/skill-history/versions/body",
|
|
27
|
+
undo: "/api/dsh-evolution/skill-history/undo",
|
|
28
|
+
health: "/api/dsh-evolution/skill-history/health"
|
|
29
|
+
},
|
|
30
|
+
"skill-health": { rows: "/api/dsh-evolution/skill-health/rows" },
|
|
31
|
+
diagnostics: { view: "/api/dsh-evolution/diagnostics" }
|
|
24
32
|
};
|
|
25
33
|
//#endregion
|
|
26
|
-
//#region src/client/
|
|
34
|
+
//#region src/client/route-client.generated.ts
|
|
35
|
+
/** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
|
|
36
|
+
function classify(status) {
|
|
37
|
+
if (status === 401) return {
|
|
38
|
+
kind: "unauthenticated",
|
|
39
|
+
status: 401
|
|
40
|
+
};
|
|
41
|
+
if (status === 403) return {
|
|
42
|
+
kind: "forbidden",
|
|
43
|
+
status: 403
|
|
44
|
+
};
|
|
45
|
+
if (status === 404) return {
|
|
46
|
+
kind: "unmounted",
|
|
47
|
+
status: 404
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
kind: "server",
|
|
51
|
+
status
|
|
52
|
+
};
|
|
53
|
+
}
|
|
27
54
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* @module @lmzhen/dsh-evolution-settings-ui/client/api
|
|
55
|
+
* One request, no retries and no body parsing for the platform's admission answers.
|
|
56
|
+
* @param doFetch - the fetch in use (the browser's, or a spec's stub).
|
|
57
|
+
* @param path - the exact route path from the generated ROUTES table.
|
|
58
|
+
* @param init - standard RequestInit.
|
|
59
|
+
* @returns the data, or the classified failure (network/parse failures read as 'server').
|
|
34
60
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
async function requestJson(doFetch, path, init) {
|
|
62
|
+
let response;
|
|
37
63
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
response = await doFetch(path, init);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return {
|
|
40
67
|
ok: false,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
data: body.data
|
|
68
|
+
failure: {
|
|
69
|
+
kind: "server",
|
|
70
|
+
status: 0,
|
|
71
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
72
|
+
}
|
|
47
73
|
};
|
|
74
|
+
}
|
|
75
|
+
if (!response.ok) return {
|
|
76
|
+
ok: false,
|
|
77
|
+
failure: classify(response.status)
|
|
78
|
+
};
|
|
79
|
+
let body;
|
|
80
|
+
try {
|
|
81
|
+
body = await response.json();
|
|
82
|
+
} catch {
|
|
48
83
|
return {
|
|
49
84
|
ok: false,
|
|
50
|
-
|
|
85
|
+
failure: {
|
|
86
|
+
kind: "server",
|
|
87
|
+
status: response.status
|
|
88
|
+
}
|
|
51
89
|
};
|
|
52
|
-
}
|
|
53
|
-
|
|
90
|
+
}
|
|
91
|
+
if (typeof body === "object" && body !== null) {
|
|
92
|
+
const record = body;
|
|
93
|
+
if (record.ok === true) return {
|
|
94
|
+
ok: true,
|
|
95
|
+
data: record.data
|
|
96
|
+
};
|
|
97
|
+
if (record.ok === false && typeof record.message === "string" && record.message !== "") return {
|
|
54
98
|
ok: false,
|
|
55
|
-
|
|
99
|
+
failure: {
|
|
100
|
+
kind: "refused",
|
|
101
|
+
code: typeof record.code === "string" && record.code !== "" ? record.code : "refused",
|
|
102
|
+
message: record.message
|
|
103
|
+
}
|
|
56
104
|
};
|
|
57
105
|
}
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
failure: {
|
|
109
|
+
kind: "server",
|
|
110
|
+
status: response.status
|
|
111
|
+
}
|
|
112
|
+
};
|
|
58
113
|
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/client/api.ts
|
|
116
|
+
/**
|
|
117
|
+
* The approval routes this bundle calls, and the ONE place a request is made.
|
|
118
|
+
*
|
|
119
|
+
* Structural types on purpose: the browser half cannot import the host package at runtime, so the row
|
|
120
|
+
* shape is declared by what the route hands over. Every answer — including a refusal — is parsed the
|
|
121
|
+
* same way, because a refusal is a business answer the card shows as a sentence, not a transport error.
|
|
122
|
+
* @module @lmzhen/dsh-evolution-settings-ui/client/api
|
|
123
|
+
*/
|
|
59
124
|
/**
|
|
60
125
|
* Build the approval face over one fetch implementation.
|
|
61
126
|
* @param doFetch - the fetch to use (the platform's global in the browser, a fake in a spec).
|
|
62
127
|
* @returns the three calls the pending card makes.
|
|
63
128
|
*/
|
|
64
129
|
function createApprovalApi(doFetch) {
|
|
65
|
-
const post = (path, id) =>
|
|
130
|
+
const post = async (path, id) => await requestJson(doFetch, path, {
|
|
66
131
|
method: "POST",
|
|
67
132
|
headers: { "content-type": "application/json" },
|
|
68
133
|
body: JSON.stringify({ id })
|
|
69
134
|
});
|
|
70
135
|
return {
|
|
71
|
-
pending: async (status) => await
|
|
72
|
-
approve: async (id) => await post(
|
|
73
|
-
reject: async (id) => await post(
|
|
74
|
-
preview: async (id) => await
|
|
136
|
+
pending: async (status) => await requestJson(doFetch, status === void 0 ? ROUTES.approval.pending : ROUTES.approval.pending + "?status=" + status),
|
|
137
|
+
approve: async (id) => await post(ROUTES.approval.approve, id),
|
|
138
|
+
reject: async (id) => await post(ROUTES.approval.reject, id),
|
|
139
|
+
preview: async (id) => await requestJson(doFetch, ROUTES.approval.preview + "?id=" + encodeURIComponent(id))
|
|
75
140
|
};
|
|
76
141
|
}
|
|
77
142
|
//#endregion
|
|
@@ -607,6 +672,35 @@ window.__ModuleLoader__.load({
|
|
|
607
672
|
}
|
|
608
673
|
];
|
|
609
674
|
//#endregion
|
|
675
|
+
//#region src/client/states.generated.ts
|
|
676
|
+
/** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
|
|
677
|
+
function Block(props) {
|
|
678
|
+
const tone = props.tone ?? "neutral";
|
|
679
|
+
return (0, react.createElement)("div", {
|
|
680
|
+
"data-tone": tone,
|
|
681
|
+
...tone === "fail" ? { role: "alert" } : {}
|
|
682
|
+
}, props.title === void 0 ? null : (0, react.createElement)("div", { "data-tone-title": tone }, props.title), props.children);
|
|
683
|
+
}
|
|
684
|
+
/** One classified failure as a reader-facing sentence. */
|
|
685
|
+
function failureText(failure, t) {
|
|
686
|
+
switch (failure.kind) {
|
|
687
|
+
case "unauthenticated": return t("failureUnauthorized");
|
|
688
|
+
case "forbidden": return t("failureForbidden");
|
|
689
|
+
case "unmounted": return t("failureUnmounted");
|
|
690
|
+
case "server": return failure.status === 0 ? t("failureServer") : t("failureServer") + " (HTTP " + String(failure.status) + ")";
|
|
691
|
+
case "refused": return failure.message;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/** The failed-state block: sentence + (when the surface exists but is unreachable) the command fallback. */
|
|
695
|
+
function FailureBlock(props) {
|
|
696
|
+
const hint = props.fallback === void 0 ? null : (0, react.createElement)("div", { "data-tone-hint": "true" }, props.t("fallbackHint", { command: props.fallback.command }));
|
|
697
|
+
return Block({
|
|
698
|
+
tone: "fail",
|
|
699
|
+
title: failureText(props.failure, props.t),
|
|
700
|
+
children: hint
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
//#endregion
|
|
610
704
|
//#region src/client/pending-state.ts
|
|
611
705
|
/** The state a card mounts in: still loading, nothing in flight, nothing said yet. */
|
|
612
706
|
const INITIAL_PENDING_STATE = {
|
|
@@ -662,13 +756,13 @@ window.__ModuleLoader__.load({
|
|
|
662
756
|
}
|
|
663
757
|
};
|
|
664
758
|
}
|
|
665
|
-
/** The list could not be read: the
|
|
666
|
-
function failed(state,
|
|
759
|
+
/** The list could not be read: the classified failure stays on screen next to a retry control. */
|
|
760
|
+
function failed(state, failure) {
|
|
667
761
|
return {
|
|
668
762
|
...state,
|
|
669
763
|
view: {
|
|
670
764
|
kind: "failed",
|
|
671
|
-
|
|
765
|
+
failure
|
|
672
766
|
}
|
|
673
767
|
};
|
|
674
768
|
}
|
|
@@ -741,6 +835,7 @@ window.__ModuleLoader__.load({
|
|
|
741
835
|
*/
|
|
742
836
|
function PendingCard(props) {
|
|
743
837
|
const { t, api } = props;
|
|
838
|
+
const translate = (key, values) => t(key, values);
|
|
744
839
|
const confirmMode = props.confirmMode?.() ?? "auto";
|
|
745
840
|
const [armed, setArmed] = (0, react.useState)(null);
|
|
746
841
|
const [state, setState] = (0, react.useState)(INITIAL_PENDING_STATE);
|
|
@@ -760,15 +855,19 @@ window.__ModuleLoader__.load({
|
|
|
760
855
|
* answers are ordered the way the CLI orders them (waiting first); either one failing fails the
|
|
761
856
|
* read, because a half-list would read as a complete window.
|
|
762
857
|
*/
|
|
858
|
+
const readTicket = (0, react.useRef)(0);
|
|
763
859
|
const load = async () => {
|
|
764
860
|
apply(loading);
|
|
861
|
+
const ticket = readTicket.current + 1;
|
|
862
|
+
readTicket.current = ticket;
|
|
765
863
|
const [waiting, running] = await Promise.all([api.pending(), api.pending("executing")]);
|
|
864
|
+
if (readTicket.current !== ticket) return;
|
|
766
865
|
if (!waiting.ok) {
|
|
767
|
-
apply((current) => failed(current, waiting.
|
|
866
|
+
apply((current) => failed(current, waiting.failure));
|
|
768
867
|
return;
|
|
769
868
|
}
|
|
770
869
|
if (!running.ok) {
|
|
771
|
-
apply((current) => failed(current, running.
|
|
870
|
+
apply((current) => failed(current, running.failure));
|
|
772
871
|
return;
|
|
773
872
|
}
|
|
774
873
|
apply((current) => loaded(current, [...waiting.data, ...running.data]));
|
|
@@ -788,7 +887,7 @@ window.__ModuleLoader__.load({
|
|
|
788
887
|
(async () => {
|
|
789
888
|
const answer = action === "approve" ? await api.approve(id) : await api.reject(id);
|
|
790
889
|
if (!answer.ok) {
|
|
791
|
-
apply((current) => noticed(endAction(current, id), answer.
|
|
890
|
+
apply((current) => noticed(endAction(current, id), failureText(answer.failure, translate)));
|
|
792
891
|
return;
|
|
793
892
|
}
|
|
794
893
|
apply((current) => noticed(endAction(current, id), null));
|
|
@@ -811,7 +910,7 @@ window.__ModuleLoader__.load({
|
|
|
811
910
|
reason: answer.data.reason
|
|
812
911
|
} : {
|
|
813
912
|
kind: "failed",
|
|
814
|
-
|
|
913
|
+
failure: answer.failure
|
|
815
914
|
};
|
|
816
915
|
apply((current) => previewArrived(current, id, view));
|
|
817
916
|
};
|
|
@@ -832,7 +931,7 @@ window.__ModuleLoader__.load({
|
|
|
832
931
|
if (view.kind === "failed") return (0, react.createElement)("p", {
|
|
833
932
|
className: "evolution-param-error",
|
|
834
933
|
role: "status"
|
|
835
|
-
}, view.
|
|
934
|
+
}, failureText(view.failure, translate));
|
|
836
935
|
if (view.kind === "unavailable") return (0, react.createElement)("p", {
|
|
837
936
|
className: "evolution-param-error",
|
|
838
937
|
role: "status"
|
|
@@ -852,18 +951,18 @@ window.__ModuleLoader__.load({
|
|
|
852
951
|
}, t("approvalDiffCut"))] : []
|
|
853
952
|
];
|
|
854
953
|
const lines = [];
|
|
855
|
-
for (const hunk of view.answer.hunks) {
|
|
954
|
+
for (const [hunkIndex, hunk] of view.answer.hunks.entries()) {
|
|
856
955
|
for (const [index, line] of hunk.oldText.split("\n").entries()) {
|
|
857
956
|
if (hunk.oldText === "" && line === "") continue;
|
|
858
957
|
lines.push((0, react.createElement)("div", {
|
|
859
|
-
key: "old-" + String(index),
|
|
958
|
+
key: "old-" + String(hunkIndex) + "-" + String(index),
|
|
860
959
|
className: "evolution-param-line-del"
|
|
861
960
|
}, "- " + line));
|
|
862
961
|
}
|
|
863
962
|
for (const [index, line] of hunk.newText.split("\n").entries()) {
|
|
864
963
|
if (hunk.newText === "" && line === "") continue;
|
|
865
964
|
lines.push((0, react.createElement)("div", {
|
|
866
|
-
key: "new-" + String(index),
|
|
965
|
+
key: "new-" + String(hunkIndex) + "-" + String(index),
|
|
867
966
|
className: "evolution-param-line-add"
|
|
868
967
|
}, "+ " + line));
|
|
869
968
|
}
|
|
@@ -933,10 +1032,11 @@ window.__ModuleLoader__.load({
|
|
|
933
1032
|
className: "evolution-param-note",
|
|
934
1033
|
role: "status"
|
|
935
1034
|
}, t("approvalEmpty"));
|
|
936
|
-
if (state.view.kind === "failed") return (0, react.createElement)("div", { className: "evolution-param-actions" }, (
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1035
|
+
if (state.view.kind === "failed") return (0, react.createElement)("div", { className: "evolution-param-actions" }, FailureBlock({
|
|
1036
|
+
failure: state.view.failure,
|
|
1037
|
+
t: translate,
|
|
1038
|
+
fallback: props.fallback
|
|
1039
|
+
}), (0, react.createElement)("button", {
|
|
940
1040
|
className: "evolution-param-button",
|
|
941
1041
|
type: "button",
|
|
942
1042
|
onClick: () => {
|
|
@@ -967,6 +1067,120 @@ window.__ModuleLoader__.load({
|
|
|
967
1067
|
}, t("approvalRefresh"))), body()) : null);
|
|
968
1068
|
}
|
|
969
1069
|
//#endregion
|
|
1070
|
+
//#region src/client/DiagnosticsCard.ts
|
|
1071
|
+
/**
|
|
1072
|
+
* The diagnostics card (W7 S7): the browser face of the SAME DiagnosticsView the CLI and the
|
|
1073
|
+
* `/api/dsh-evolution/diagnostics` route answer.
|
|
1074
|
+
*
|
|
1075
|
+
* Read-only by construction: it fetches once on mount and only refetches on an explicit Refresh; a
|
|
1076
|
+
* failed read renders the shared FailureBlock with the command-face fallback. Facts and runtime rows
|
|
1077
|
+
* are rendered in the view's own order — the client owns no key list and no status vocabulary.
|
|
1078
|
+
* @module @lmzhen/dsh-evolution-settings-ui/client/DiagnosticsCard
|
|
1079
|
+
*/
|
|
1080
|
+
/** Which word one status gets (the frozen W1 status set). */
|
|
1081
|
+
const STATUS_KEYS = Object.freeze({
|
|
1082
|
+
ok: "diagStatusOk",
|
|
1083
|
+
warn: "diagStatusWarn",
|
|
1084
|
+
fail: "diagStatusFail",
|
|
1085
|
+
unknown: "diagStatusUnknown"
|
|
1086
|
+
});
|
|
1087
|
+
/**
|
|
1088
|
+
* Render the card.
|
|
1089
|
+
* @param props - the copy seat, the host call and the command fallback.
|
|
1090
|
+
* @returns the card.
|
|
1091
|
+
*/
|
|
1092
|
+
function DiagnosticsCard(props) {
|
|
1093
|
+
const { t, api } = props;
|
|
1094
|
+
const translate = (key, values) => t(key, values);
|
|
1095
|
+
const [state, setState] = (0, react.useState)({ phase: "loading" });
|
|
1096
|
+
const ticket = (0, react.useRef)(0);
|
|
1097
|
+
const read = (keepView) => {
|
|
1098
|
+
const current = ticket.current + 1;
|
|
1099
|
+
ticket.current = current;
|
|
1100
|
+
if (!keepView) setState({ phase: "loading" });
|
|
1101
|
+
api.view().then((answer) => {
|
|
1102
|
+
if (ticket.current !== current) return;
|
|
1103
|
+
setState(answer.ok ? {
|
|
1104
|
+
phase: "ready",
|
|
1105
|
+
view: answer.data
|
|
1106
|
+
} : {
|
|
1107
|
+
phase: "failed",
|
|
1108
|
+
failure: answer.failure
|
|
1109
|
+
});
|
|
1110
|
+
});
|
|
1111
|
+
};
|
|
1112
|
+
(0, react.useEffect)(() => {
|
|
1113
|
+
read(false);
|
|
1114
|
+
}, []);
|
|
1115
|
+
const refresh = () => {
|
|
1116
|
+
read(true);
|
|
1117
|
+
};
|
|
1118
|
+
const header = (0, react.createElement)("div", { className: "evolution-param-card-head" }, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("diagTitle")), (0, react.createElement)("button", {
|
|
1119
|
+
type: "button",
|
|
1120
|
+
className: "evolution-param-button",
|
|
1121
|
+
onClick: refresh,
|
|
1122
|
+
"data-action": "diag-refresh"
|
|
1123
|
+
}, state.phase === "loading" ? t("diagRefreshing") : t("diagRefresh")));
|
|
1124
|
+
const body = () => {
|
|
1125
|
+
if (state.phase === "loading") return (0, react.createElement)("p", { className: "evolution-param-loading" }, t("stateLoading"));
|
|
1126
|
+
if (state.phase === "failed") return FailureBlock({
|
|
1127
|
+
failure: state.failure,
|
|
1128
|
+
t: translate,
|
|
1129
|
+
fallback: props.fallback
|
|
1130
|
+
});
|
|
1131
|
+
const view = state.view;
|
|
1132
|
+
const factRows = view.facts.map((fact) => (0, react.createElement)("div", {
|
|
1133
|
+
key: "fact-" + fact.key,
|
|
1134
|
+
className: "evo-diag-row",
|
|
1135
|
+
"data-kind": "fact"
|
|
1136
|
+
}, (0, react.createElement)("span", { className: "evo-diag-key" }, fact.label), (0, react.createElement)("span", { className: "evo-diag-value" }, fact.value), (0, react.createElement)("span", { className: "evo-diag-home" }, t("diagHomeCite", { home: fact.home }))));
|
|
1137
|
+
const runtimeRows = view.runtime.map((row) => (0, react.createElement)("div", {
|
|
1138
|
+
key: "runtime-" + row.key,
|
|
1139
|
+
className: "evo-diag-row",
|
|
1140
|
+
"data-kind": "runtime",
|
|
1141
|
+
"data-status": row.status
|
|
1142
|
+
}, (0, react.createElement)("span", { className: "evo-diag-key" }, row.label), (0, react.createElement)("span", { className: "evo-diag-status" }, t(STATUS_KEYS[row.status])), row.detail === void 0 ? null : (0, react.createElement)("span", { className: "evo-diag-detail" }, row.detail), row.remedy === void 0 ? null : (0, react.createElement)("span", { className: "evo-diag-remedy" }, t("diagRemedy") + row.remedy)));
|
|
1143
|
+
return (0, react.createElement)("div", { className: "evo-diag-body" }, (0, react.createElement)("p", { className: "evolution-params-subtitle" }, t("diagSubtitle")), (0, react.createElement)("p", { className: "evo-diag-meta" }, t("diagSchema", { n: view.schemaVersion }) + " · " + t("diagGeneratedAt", { at: view.generatedAt })), (0, react.createElement)("h3", { className: "evo-diag-section" }, t("diagFactsTitle")), ...factRows, (0, react.createElement)("h3", { className: "evo-diag-section" }, t("diagRuntimeTitle")), ...runtimeRows);
|
|
1144
|
+
};
|
|
1145
|
+
return (0, react.createElement)("div", {
|
|
1146
|
+
className: "evolution-param-card",
|
|
1147
|
+
"data-card": "diagnostics"
|
|
1148
|
+
}, header, body());
|
|
1149
|
+
}
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/client/diagnostics-state.ts
|
|
1152
|
+
/**
|
|
1153
|
+
* The diagnostics card's data face (W7 S7): ONE request to `HOST_ROUTES.diagnostics.view` through the
|
|
1154
|
+
* GENERATED route client, and the four-state answer the card renders.
|
|
1155
|
+
*
|
|
1156
|
+
* The Host route is the SAME `buildDoctorView` the CLI prints (W6/W8), so this client owns no facts
|
|
1157
|
+
* and no computation — it only renders.
|
|
1158
|
+
* @module @lmzhen/dsh-evolution-settings-ui/client/diagnostics-state
|
|
1159
|
+
*/
|
|
1160
|
+
/**
|
|
1161
|
+
* Build the diagnostics face over one fetch implementation.
|
|
1162
|
+
* @param doFetch - the browser fetch, or a spec stub.
|
|
1163
|
+
*/
|
|
1164
|
+
function createDiagnosticsApi(doFetch = fetch) {
|
|
1165
|
+
return { view: async () => await requestJson(doFetch, ROUTES.diagnostics.view, { headers: { accept: "application/json" } }) };
|
|
1166
|
+
}
|
|
1167
|
+
//#endregion
|
|
1168
|
+
//#region src/client/fallbacks.generated.ts
|
|
1169
|
+
/**
|
|
1170
|
+
* The command-face fallback for every Host surface, generated from evolution-commands'
|
|
1171
|
+
* COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
|
|
1172
|
+
* panels render this hint instead of a bare HTTP failure — same use case, command face.
|
|
1173
|
+
*/
|
|
1174
|
+
const FALLBACKS = {
|
|
1175
|
+
approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
|
|
1176
|
+
"skill-history": { command: "/evolution restore <name> [v](查看:/evolution skills history <name>)" },
|
|
1177
|
+
"skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
|
|
1178
|
+
diagnostics: {
|
|
1179
|
+
command: "/evolution doctor",
|
|
1180
|
+
platformCli: "dsh --json /evolution doctor"
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
//#endregion
|
|
970
1184
|
//#region src/client/messages.ts
|
|
971
1185
|
/**
|
|
972
1186
|
* Copy for the evolution settings section and its parameter cards.
|
|
@@ -998,14 +1212,13 @@ window.__ModuleLoader__.load({
|
|
|
998
1212
|
discard: "放弃修改",
|
|
999
1213
|
saving: "保存中…",
|
|
1000
1214
|
resetting: "恢复中…",
|
|
1001
|
-
|
|
1215
|
+
writeTargetHost: "保存去向:宿主配置(对所有会话生效)",
|
|
1002
1216
|
invalidNumber: "需要一个数字",
|
|
1003
1217
|
invalidSelect: "不在允许的取值里",
|
|
1004
1218
|
invalidAtLeast: "不能小于同组的 {n}",
|
|
1005
1219
|
approvalConfirmNamed: "即将批准:{n}",
|
|
1006
1220
|
approvalConfirmYes: "确认批准",
|
|
1007
1221
|
approvalConfirmNo: "取消",
|
|
1008
|
-
destMemory: "保存去向:仅本次会话",
|
|
1009
1222
|
resetTo: "复位为 {value}",
|
|
1010
1223
|
refused: "这个值没有被接受(界面上保持原样)。想看原因:在对话里让它说明。",
|
|
1011
1224
|
resetRefused: "恢复默认值没有生效(这一行还是你的值)。想看原因:在对话里让它说明。",
|
|
@@ -1041,7 +1254,27 @@ window.__ModuleLoader__.load({
|
|
|
1041
1254
|
approvalPreviewNone: "看不了预览:",
|
|
1042
1255
|
approvalDiffAdded: "{n} 行新增",
|
|
1043
1256
|
approvalDiffRemoved: "{n} 行删除",
|
|
1044
|
-
approvalDiffCut: "改动区域已截断"
|
|
1257
|
+
approvalDiffCut: "改动区域已截断",
|
|
1258
|
+
diagTitle: "诊断",
|
|
1259
|
+
diagSubtitle: "安装形态、平台线与运行面的当前读数;只读,不改任何东西。",
|
|
1260
|
+
diagFactsTitle: "事实",
|
|
1261
|
+
diagRuntimeTitle: "运行时",
|
|
1262
|
+
diagSchema: "视图 schema v{n}",
|
|
1263
|
+
diagGeneratedAt: "生成于 {at}",
|
|
1264
|
+
diagRefresh: "刷新",
|
|
1265
|
+
diagRefreshing: "刷新中…",
|
|
1266
|
+
diagStatusOk: "正常",
|
|
1267
|
+
diagStatusWarn: "注意",
|
|
1268
|
+
diagStatusFail: "异常",
|
|
1269
|
+
diagStatusUnknown: "未知",
|
|
1270
|
+
diagRemedy: "下一步:",
|
|
1271
|
+
diagHomeCite: "出处:{home}",
|
|
1272
|
+
stateLoading: "正在读取…",
|
|
1273
|
+
failureUnauthorized: "需要登录:重新打开这个页面完成登录后再试。",
|
|
1274
|
+
failureForbidden: "平台拒绝了这个请求(身份不是 owner)。",
|
|
1275
|
+
failureUnmounted: "该功能没有挂载。",
|
|
1276
|
+
failureServer: "读取失败,可以手动重试。",
|
|
1277
|
+
fallbackHint: "可以改用命令:{command}"
|
|
1045
1278
|
};
|
|
1046
1279
|
/** English copy, key for key with {@link zh}. */
|
|
1047
1280
|
const en = {
|
|
@@ -1061,14 +1294,13 @@ window.__ModuleLoader__.load({
|
|
|
1061
1294
|
discard: "Discard",
|
|
1062
1295
|
saving: "Saving…",
|
|
1063
1296
|
resetting: "Resetting…",
|
|
1064
|
-
|
|
1297
|
+
writeTargetHost: "Saved to: host configuration (applies to every session)",
|
|
1065
1298
|
invalidNumber: "Needs a number",
|
|
1066
1299
|
invalidSelect: "Not one of the allowed values",
|
|
1067
1300
|
invalidAtLeast: "must not be below {n}",
|
|
1068
1301
|
approvalConfirmNamed: "About to approve: {n}",
|
|
1069
1302
|
approvalConfirmYes: "Approve",
|
|
1070
1303
|
approvalConfirmNo: "Cancel",
|
|
1071
|
-
destMemory: "Saved to: this session only",
|
|
1072
1304
|
resetTo: "Restores {value}",
|
|
1073
1305
|
refused: "The value was not accepted (the page keeps what you see). For the reason, ask in the conversation.",
|
|
1074
1306
|
resetRefused: "The reset did not take (this row still holds your value). For the reason, ask in the conversation.",
|
|
@@ -1104,7 +1336,27 @@ window.__ModuleLoader__.load({
|
|
|
1104
1336
|
approvalPreviewNone: "no preview: ",
|
|
1105
1337
|
approvalDiffAdded: "{n} lines added",
|
|
1106
1338
|
approvalDiffRemoved: "{n} lines removed",
|
|
1107
|
-
approvalDiffCut: "the changed region is truncated"
|
|
1339
|
+
approvalDiffCut: "the changed region is truncated",
|
|
1340
|
+
diagTitle: "Diagnostics",
|
|
1341
|
+
diagSubtitle: "Current readings for the install form, platform line and runtime surfaces; read-only.",
|
|
1342
|
+
diagFactsTitle: "Facts",
|
|
1343
|
+
diagRuntimeTitle: "Runtime",
|
|
1344
|
+
diagSchema: "view schema v{n}",
|
|
1345
|
+
diagGeneratedAt: "generated at {at}",
|
|
1346
|
+
diagRefresh: "Refresh",
|
|
1347
|
+
diagRefreshing: "refreshing…",
|
|
1348
|
+
diagStatusOk: "ok",
|
|
1349
|
+
diagStatusWarn: "warn",
|
|
1350
|
+
diagStatusFail: "fail",
|
|
1351
|
+
diagStatusUnknown: "unknown",
|
|
1352
|
+
diagRemedy: "Next: ",
|
|
1353
|
+
diagHomeCite: "source: {home}",
|
|
1354
|
+
stateLoading: "reading…",
|
|
1355
|
+
failureUnauthorized: "Sign-in is required: reopen this page and sign in, then retry.",
|
|
1356
|
+
failureForbidden: "The platform refused this request (not the owner principal).",
|
|
1357
|
+
failureUnmounted: "This feature is not mounted.",
|
|
1358
|
+
failureServer: "The read failed; you can retry manually.",
|
|
1359
|
+
fallbackHint: "Command fallback: {command}"
|
|
1108
1360
|
};
|
|
1109
1361
|
/**
|
|
1110
1362
|
* Card titles per settings namespace. A namespace without an entry falls back to
|
|
@@ -1123,7 +1375,7 @@ window.__ModuleLoader__.load({
|
|
|
1123
1375
|
/**
|
|
1124
1376
|
* Resolve one message without the locale seat (tests and the fallback path).
|
|
1125
1377
|
* @param key - the message key.
|
|
1126
|
-
* @param language - which dictionary to read; defaults to
|
|
1378
|
+
* @param language - which dictionary to read; defaults to English (the platform FALLBACK_LOCALE).
|
|
1127
1379
|
* @returns the copy this bundle carries.
|
|
1128
1380
|
*/
|
|
1129
1381
|
function message(key, language = "en") {
|
|
@@ -1171,6 +1423,23 @@ window.__ModuleLoader__.load({
|
|
|
1171
1423
|
* typed from the registry (\`control\`), so booleans are switches, enums are
|
|
1172
1424
|
* selects and numbers are numeric inputs with the unit shown beside the label.
|
|
1173
1425
|
*/
|
|
1426
|
+
/**
|
|
1427
|
+
* The NAMESPACE-RELATIVE key of one registry field.
|
|
1428
|
+
*
|
|
1429
|
+
* The E3 registry writes family ids in canonical form (`evolution-approval.pendingTtlMs`),
|
|
1430
|
+
* while the platform settings seat speaks a namespace's own field keys (`pendingTtlMs`) —
|
|
1431
|
+
* its snapshot user layer, its base and its op paths are all relative to the namespace. Sending
|
|
1432
|
+
* the canonical id as an op path makes the Host refuse the write
|
|
1433
|
+
* ("Config field evolution-approval.pendingTtlMs is not volatile", measured 2026-10-19), so the
|
|
1434
|
+
* card translates once, here, and every read/write goes through it.
|
|
1435
|
+
* @param namespace - the settings namespace (Loader row id) the card writes.
|
|
1436
|
+
* @param field - one generated field.
|
|
1437
|
+
* @returns the field's key inside that namespace.
|
|
1438
|
+
*/
|
|
1439
|
+
function fieldKeyOf(namespace, field) {
|
|
1440
|
+
const prefix = namespace + ".";
|
|
1441
|
+
return field.id.startsWith(prefix) ? field.id.slice(prefix.length) : field.id;
|
|
1442
|
+
}
|
|
1174
1443
|
/** One cell's text: absent reads as an empty control, containers as JSON. */
|
|
1175
1444
|
function format(value) {
|
|
1176
1445
|
if (value === void 0) return "";
|
|
@@ -1319,35 +1588,6 @@ window.__ModuleLoader__.load({
|
|
|
1319
1588
|
const [error, setError] = (0, react.useState)("");
|
|
1320
1589
|
const [pending, setPending] = (0, react.useState)(null);
|
|
1321
1590
|
const [clearing, setClearing] = (0, react.useState)(null);
|
|
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 : {};
|
|
1332
|
-
const disabled = !snapshot.writable;
|
|
1333
|
-
const locked = disabled || busy || clearing !== null;
|
|
1334
|
-
const titleKey = NAMESPACE_TITLES[namespace];
|
|
1335
|
-
const title = titleKey === void 0 ? namespace : t(titleKey);
|
|
1336
|
-
const changed = fields.filter((field) => Object.hasOwn(user, field.id)).length;
|
|
1337
|
-
const textOf = (field) => draft[field.id] ?? format(value[field.id]);
|
|
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);
|
|
1345
|
-
const change = (id, next) => {
|
|
1346
|
-
setDraft({
|
|
1347
|
-
...draft,
|
|
1348
|
-
[id]: next
|
|
1349
|
-
});
|
|
1350
|
-
};
|
|
1351
1591
|
/**
|
|
1352
1592
|
* Settle a staged write against the Host's answer.
|
|
1353
1593
|
*
|
|
@@ -1376,6 +1616,36 @@ window.__ModuleLoader__.load({
|
|
|
1376
1616
|
snapshot.user,
|
|
1377
1617
|
t
|
|
1378
1618
|
]);
|
|
1619
|
+
if (snapshot.status === "loading") return (0, react.createElement)("p", {
|
|
1620
|
+
className: "evolution-param-note",
|
|
1621
|
+
role: "status"
|
|
1622
|
+
}, t("loading"));
|
|
1623
|
+
if (snapshot.status === "unavailable") return (0, react.createElement)("p", {
|
|
1624
|
+
className: "evolution-param-note",
|
|
1625
|
+
role: "status"
|
|
1626
|
+
}, t(snapshot.reason === "seat-missing" ? "seatMissing" : snapshot.reason === "projection-failed" ? "projectionFailed" : "unavailable"));
|
|
1627
|
+
const user = isRecord(snapshot.user) ? snapshot.user : {};
|
|
1628
|
+
const value = isRecord(snapshot.value) ? snapshot.value : {};
|
|
1629
|
+
const disabled = !snapshot.writable;
|
|
1630
|
+
const locked = disabled || busy || clearing !== null;
|
|
1631
|
+
const titleKey = NAMESPACE_TITLES[namespace];
|
|
1632
|
+
const title = titleKey === void 0 ? namespace : t(titleKey);
|
|
1633
|
+
const keyOf = (field) => fieldKeyOf(namespace, field);
|
|
1634
|
+
const changed = fields.filter((field) => Object.hasOwn(user, keyOf(field))).length;
|
|
1635
|
+
const textOf = (field) => draft[field.id] ?? format(value[keyOf(field)]);
|
|
1636
|
+
const dirty = fields.filter((field) => draft[field.id] !== void 0 && draft[field.id] !== format(value[keyOf(field)]));
|
|
1637
|
+
const peerTextOf = (field) => {
|
|
1638
|
+
if (field.atLeast === "") return void 0;
|
|
1639
|
+
const related = fields.find((candidate) => candidate.id === field.atLeast);
|
|
1640
|
+
return related === void 0 ? void 0 : textOf(related);
|
|
1641
|
+
};
|
|
1642
|
+
const blocked = fields.some((field) => invalidReason(field, textOf(field), peerTextOf(field)) !== null);
|
|
1643
|
+
const change = (id, next) => {
|
|
1644
|
+
setDraft({
|
|
1645
|
+
...draft,
|
|
1646
|
+
[id]: next
|
|
1647
|
+
});
|
|
1648
|
+
};
|
|
1379
1649
|
/**
|
|
1380
1650
|
* Restore one field's deployment default (T5-05/A60).
|
|
1381
1651
|
*
|
|
@@ -1390,7 +1660,7 @@ window.__ModuleLoader__.load({
|
|
|
1390
1660
|
(async () => {
|
|
1391
1661
|
let accepted;
|
|
1392
1662
|
try {
|
|
1393
|
-
accepted = await clear(field);
|
|
1663
|
+
accepted = await clear(fieldKeyOf(namespace, { id: field }));
|
|
1394
1664
|
} catch (caught) {
|
|
1395
1665
|
setClearing(null);
|
|
1396
1666
|
setError(caught instanceof Error && caught.message !== "" ? caught.message : t("resetRefused"));
|
|
@@ -1410,7 +1680,7 @@ window.__ModuleLoader__.load({
|
|
|
1410
1680
|
setPending({
|
|
1411
1681
|
accepted: await mutateOnce(dirty.map((field) => ({
|
|
1412
1682
|
op: "set",
|
|
1413
|
-
path: [field
|
|
1683
|
+
path: [keyOf(field)],
|
|
1414
1684
|
value: parseFor(field.control, textOf(field))
|
|
1415
1685
|
}))),
|
|
1416
1686
|
resets: false
|
|
@@ -1428,7 +1698,7 @@ window.__ModuleLoader__.load({
|
|
|
1428
1698
|
onClick: () => {
|
|
1429
1699
|
setOpen(!open);
|
|
1430
1700
|
}
|
|
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" },
|
|
1701
|
+
}, (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" }, t("writeTargetHost")), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, disabled ? (0, react.createElement)("p", {
|
|
1432
1702
|
className: "evolution-param-note",
|
|
1433
1703
|
role: "status"
|
|
1434
1704
|
}, t("readonly")) : null, fields.length === 0 ? (0, react.createElement)("p", {
|
|
@@ -1441,10 +1711,10 @@ window.__ModuleLoader__.load({
|
|
|
1441
1711
|
key: field.id,
|
|
1442
1712
|
field,
|
|
1443
1713
|
text: textOf(field),
|
|
1444
|
-
overridden: Object.hasOwn(user, field
|
|
1714
|
+
overridden: Object.hasOwn(user, keyOf(field)),
|
|
1445
1715
|
disabled: locked,
|
|
1446
1716
|
clearing: clearing === field.id,
|
|
1447
|
-
resetTo: resetToOf(snapshot.base?.[field
|
|
1717
|
+
resetTo: resetToOf(snapshot.base?.[keyOf(field)]),
|
|
1448
1718
|
invalid: invalidReason(field, textOf(field), peerTextOf(field)),
|
|
1449
1719
|
t,
|
|
1450
1720
|
onChange: change,
|
|
@@ -1494,7 +1764,6 @@ window.__ModuleLoader__.load({
|
|
|
1494
1764
|
user: void 0,
|
|
1495
1765
|
writable: false,
|
|
1496
1766
|
base: void 0,
|
|
1497
|
-
mode: "host",
|
|
1498
1767
|
reason: "seat-missing"
|
|
1499
1768
|
});
|
|
1500
1769
|
/** What a card shows when the seat served the row but projecting it failed. A frozen constant
|
|
@@ -1506,7 +1775,6 @@ window.__ModuleLoader__.load({
|
|
|
1506
1775
|
user: void 0,
|
|
1507
1776
|
writable: false,
|
|
1508
1777
|
base: void 0,
|
|
1509
|
-
mode: "host",
|
|
1510
1778
|
reason: "projection-failed"
|
|
1511
1779
|
});
|
|
1512
1780
|
/**
|
|
@@ -1521,7 +1789,6 @@ window.__ModuleLoader__.load({
|
|
|
1521
1789
|
user: snapshot.user,
|
|
1522
1790
|
writable: snapshot.writable,
|
|
1523
1791
|
base: isRecord(snapshot.base) ? snapshot.base : void 0,
|
|
1524
|
-
mode: snapshot.mode,
|
|
1525
1792
|
...snapshot.status === "unavailable" ? { reason: "not-served" } : {}
|
|
1526
1793
|
};
|
|
1527
1794
|
}
|
|
@@ -1629,14 +1896,12 @@ window.__ModuleLoader__.load({
|
|
|
1629
1896
|
mutateOnce: async (ops) => {
|
|
1630
1897
|
const seat = form();
|
|
1631
1898
|
if (seat === void 0) return false;
|
|
1632
|
-
await seat.mutate(ops);
|
|
1633
|
-
return true;
|
|
1899
|
+
return await seat.mutate(ops);
|
|
1634
1900
|
},
|
|
1635
1901
|
clear: async (field) => {
|
|
1636
1902
|
const seat = form();
|
|
1637
1903
|
if (seat === void 0) return false;
|
|
1638
|
-
await seat.unset(field);
|
|
1639
|
-
return true;
|
|
1904
|
+
return await seat.unset(field);
|
|
1640
1905
|
}
|
|
1641
1906
|
};
|
|
1642
1907
|
}
|
|
@@ -1685,9 +1950,9 @@ window.__ModuleLoader__.load({
|
|
|
1685
1950
|
* @param fields - the row's fields.
|
|
1686
1951
|
* @returns how many of them the user overrode.
|
|
1687
1952
|
*/
|
|
1688
|
-
function overridden(user, fields) {
|
|
1953
|
+
function overridden(user, fields, namespace) {
|
|
1689
1954
|
const layer = isRecord(user) ? user : {};
|
|
1690
|
-
return fields.filter((field) => Object.hasOwn(layer, field
|
|
1955
|
+
return fields.filter((field) => Object.hasOwn(layer, fieldKeyOf(namespace, field))).length;
|
|
1691
1956
|
}
|
|
1692
1957
|
/**
|
|
1693
1958
|
* Render one row's configuration entry.
|
|
@@ -1700,7 +1965,7 @@ window.__ModuleLoader__.load({
|
|
|
1700
1965
|
className: "evolution-param-note",
|
|
1701
1966
|
role: "status"
|
|
1702
1967
|
}, t("seatMissing"));
|
|
1703
|
-
const changed = overridden(form.state.user, fields);
|
|
1968
|
+
const changed = overridden(form.state.user, fields, namespace);
|
|
1704
1969
|
if (view === "summary") return changed === 0 ? null : (0, react.createElement)("span", null, t("changed", { n: changed }));
|
|
1705
1970
|
return ParamCardView({
|
|
1706
1971
|
namespace,
|
|
@@ -1742,6 +2007,8 @@ window.__ModuleLoader__.load({
|
|
|
1742
2007
|
* the section — one definition in the module both sides already share is what keeps that pair acyclic.
|
|
1743
2008
|
*/
|
|
1744
2009
|
const PENDING_CARD_KEY = "approval-pending";
|
|
2010
|
+
/** The diagnostics card's slot key: read-only, no settings namespace (its data comes from HOST_ROUTES.diagnostics). */
|
|
2011
|
+
const DIAGNOSTICS_CARD_KEY = "diagnostics";
|
|
1745
2012
|
//#endregion
|
|
1746
2013
|
//#region src/client/SettingsSection.ts
|
|
1747
2014
|
/**
|
|
@@ -1763,7 +2030,10 @@ window.__ModuleLoader__.load({
|
|
|
1763
2030
|
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.24.0 · Web settings cards for the evolution family's parameter namespaces"), (0, react.createElement)("div", {
|
|
1764
2031
|
key: PENDING_CARD_KEY,
|
|
1765
2032
|
className: "evolution-params-group"
|
|
1766
|
-
}, face.renderSlot(CARD_SLOT, {}, { entryKey: PENDING_CARD_KEY })),
|
|
2033
|
+
}, face.renderSlot(CARD_SLOT, {}, { entryKey: PENDING_CARD_KEY })), (0, react.createElement)("div", {
|
|
2034
|
+
key: DIAGNOSTICS_CARD_KEY,
|
|
2035
|
+
className: "evolution-params-group"
|
|
2036
|
+
}, face.renderSlot(CARD_SLOT, {}, { entryKey: DIAGNOSTICS_CARD_KEY })), ...CLIENT_PARAM_SECTIONS.map((section_) => (0, react.createElement)("div", {
|
|
1767
2037
|
key: section_.namespace,
|
|
1768
2038
|
className: "evolution-params-group"
|
|
1769
2039
|
}, face.renderSlot(CARD_SLOT, {}, { entryKey: section_.namespace }))));
|
|
@@ -1853,17 +2123,26 @@ window.__ModuleLoader__.load({
|
|
|
1853
2123
|
].join("\n");
|
|
1854
2124
|
/**
|
|
1855
2125
|
* Inject the stylesheet once per document.
|
|
2126
|
+
*
|
|
2127
|
+
* S5-08: it RETURNS the disposer. The tag check alone answered only half the question - "is it
|
|
2128
|
+
* there" - and left "who takes it away" to nobody, so a hot reload (unmount, remount) found the old
|
|
2129
|
+
* tag, skipped the injection, and kept serving the PREVIOUS bundle CSS. skill-history hands its
|
|
2130
|
+
* disposer back to the fiber; this is the same shape for the same concern.
|
|
1856
2131
|
* @param host - the document to inject into; defaults to the global one.
|
|
2132
|
+
* @returns the disposal; a no-op when there is no document or the tag was already present.
|
|
1857
2133
|
*/
|
|
1858
2134
|
function injectStyles(host) {
|
|
1859
2135
|
const doc = host ?? globalThis.document;
|
|
1860
|
-
if (doc === void 0) return;
|
|
1861
|
-
if (doc.querySelector("style[data-plugin-css=\"@lmzhen/dsh-evolution-settings-ui/settings.css\"]") !== null) return;
|
|
2136
|
+
if (doc === void 0) return () => {};
|
|
2137
|
+
if (doc.querySelector("style[data-plugin-css=\"@lmzhen/dsh-evolution-settings-ui/settings.css\"]") !== null) return () => {};
|
|
1862
2138
|
const tag = doc.createElement("style");
|
|
1863
2139
|
tag.dataset.plugin = "@lmzhen/dsh-evolution-settings-ui";
|
|
1864
2140
|
tag.dataset.pluginCss = CSS_TAG_ID;
|
|
1865
2141
|
tag.textContent = CSS;
|
|
1866
2142
|
doc.head.appendChild(tag);
|
|
2143
|
+
return () => {
|
|
2144
|
+
tag.remove();
|
|
2145
|
+
};
|
|
1867
2146
|
}
|
|
1868
2147
|
//#endregion
|
|
1869
2148
|
//#region src/client/index.ts
|
|
@@ -1884,7 +2163,7 @@ window.__ModuleLoader__.load({
|
|
|
1884
2163
|
* @param ctx - client cordis context.
|
|
1885
2164
|
*/
|
|
1886
2165
|
function apply(ctx) {
|
|
1887
|
-
injectStyles();
|
|
2166
|
+
ctx.effect(() => injectStyles(), "evolution-settings-ui: styles");
|
|
1888
2167
|
const seam = ctx;
|
|
1889
2168
|
let registered = false;
|
|
1890
2169
|
ctx.effect(() => {
|
|
@@ -1959,9 +2238,20 @@ window.__ModuleLoader__.load({
|
|
|
1959
2238
|
inject: () => ({
|
|
1960
2239
|
t,
|
|
1961
2240
|
api,
|
|
1962
|
-
confirmMode
|
|
2241
|
+
confirmMode,
|
|
2242
|
+
fallback: FALLBACKS.approval
|
|
1963
2243
|
})
|
|
1964
2244
|
}, PendingCard);
|
|
2245
|
+
const diagApi = createDiagnosticsApi((input, init) => fetch(input, init));
|
|
2246
|
+
yield seam.slots.register({
|
|
2247
|
+
name: CARD_SLOT,
|
|
2248
|
+
key: DIAGNOSTICS_CARD_KEY,
|
|
2249
|
+
inject: () => ({
|
|
2250
|
+
t,
|
|
2251
|
+
api: diagApi,
|
|
2252
|
+
fallback: FALLBACKS.diagnostics
|
|
2253
|
+
})
|
|
2254
|
+
}, DiagnosticsCard);
|
|
1965
2255
|
});
|
|
1966
2256
|
seam.slots.inject(ROW_CONFIG_SLOT, function* () {
|
|
1967
2257
|
for (const seat of CLIENT_ROW_SEATS) for (const rowId of seat.rows) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The diagnostics card (W7 S7): the browser face of the SAME DiagnosticsView the CLI and the
|
|
3
|
+
* `/api/dsh-evolution/diagnostics` route answer.
|
|
4
|
+
*
|
|
5
|
+
* Read-only by construction: it fetches once on mount and only refetches on an explicit Refresh; a
|
|
6
|
+
* failed read renders the shared FailureBlock with the command-face fallback. Facts and runtime rows
|
|
7
|
+
* are rendered in the view's own order — the client owns no key list and no status vocabulary.
|
|
8
|
+
* @module @lmzhen/dsh-evolution-settings-ui/client/DiagnosticsCard
|
|
9
|
+
*/
|
|
10
|
+
import { type ReactNode } from 'react';
|
|
11
|
+
import type { DiagnosticsApi } from './diagnostics-state.ts';
|
|
12
|
+
import type { FallbackHint } from './contracts.generated.ts';
|
|
13
|
+
import type { MessageKey } from './messages.ts';
|
|
14
|
+
/** Injected face: the copy seat, the one host call, and this surface's command fallback. */
|
|
15
|
+
export interface DiagnosticsCardFace {
|
|
16
|
+
t: (key: MessageKey, values?: Readonly<Record<string, string | number>>) => string;
|
|
17
|
+
api: DiagnosticsApi;
|
|
18
|
+
fallback?: FallbackHint | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Render the card.
|
|
22
|
+
* @param props - the copy seat, the host call and the command fallback.
|
|
23
|
+
* @returns the card.
|
|
24
|
+
*/
|
|
25
|
+
export declare function DiagnosticsCard(props: DiagnosticsCardFace): ReactNode;
|
|
26
|
+
//# sourceMappingURL=DiagnosticsCard.d.ts.map
|
|
@@ -48,6 +48,20 @@ export type ParamCardProps = Omit<ParamCardFace, 'hooks'> & {
|
|
|
48
48
|
};
|
|
49
49
|
/** The op list a save hands to the seat — the seam's own shape, never a second vocabulary. */
|
|
50
50
|
export type SettingsParamOp = SettingsPathOpLike;
|
|
51
|
+
/**
|
|
52
|
+
* The NAMESPACE-RELATIVE key of one registry field.
|
|
53
|
+
*
|
|
54
|
+
* The E3 registry writes family ids in canonical form (`evolution-approval.pendingTtlMs`),
|
|
55
|
+
* while the platform settings seat speaks a namespace's own field keys (`pendingTtlMs`) —
|
|
56
|
+
* its snapshot user layer, its base and its op paths are all relative to the namespace. Sending
|
|
57
|
+
* the canonical id as an op path makes the Host refuse the write
|
|
58
|
+
* ("Config field evolution-approval.pendingTtlMs is not volatile", measured 2026-10-19), so the
|
|
59
|
+
* card translates once, here, and every read/write goes through it.
|
|
60
|
+
* @param namespace - the settings namespace (Loader row id) the card writes.
|
|
61
|
+
* @param field - one generated field.
|
|
62
|
+
* @returns the field's key inside that namespace.
|
|
63
|
+
*/
|
|
64
|
+
export declare function fieldKeyOf(namespace: string, field: ClientParamField): string;
|
|
51
65
|
/**
|
|
52
66
|
* U-4: whether one field's CURRENT text is a value the Host can take. The controls constrain their own
|
|
53
67
|
* domains (a number input sanitizes, a select only offers listed values), so the reachable case is a
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @module @lmzhen/dsh-evolution-settings-ui/client/PendingCard
|
|
14
14
|
*/
|
|
15
15
|
import { type ReactNode } from 'react';
|
|
16
|
+
import type { FallbackHint } from './contracts.generated.ts';
|
|
16
17
|
import type { ApprovalApi } from './api.ts';
|
|
17
18
|
import type { MessageKey } from './messages.ts';
|
|
18
19
|
/** Injected face: the copy seat and the host calls. No hook compartment — this card keeps its own state. */
|
|
@@ -24,6 +25,8 @@ export interface PendingCardFace {
|
|
|
24
25
|
* captured once would pin the gate for the life of the fiber. Absent ⇒ the shipped default `auto`.
|
|
25
26
|
*/
|
|
26
27
|
confirmMode?: () => 'auto' | 'ask' | 'timeout';
|
|
28
|
+
/** G5/W7 S4: the command-face fallback rendered with an unreachable/unauthenticated failure. */
|
|
29
|
+
fallback?: FallbackHint | undefined;
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
32
|
* U-6: the registry's gate word, read out of a section's user layer. ONE definition, exported so the
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RouteAnswer } from './route-client.generated.ts';
|
|
1
2
|
/** One staged write, as the pending route hands it over (never the staged args). */
|
|
2
3
|
export interface PendingRow {
|
|
3
4
|
readonly id: string;
|
|
@@ -33,14 +34,8 @@ export type PreviewAnswer = {
|
|
|
33
34
|
readonly available: false;
|
|
34
35
|
readonly reason: string;
|
|
35
36
|
};
|
|
36
|
-
/** What one call answered: the
|
|
37
|
-
export type ApprovalAnswer<T> =
|
|
38
|
-
readonly ok: true;
|
|
39
|
-
readonly data: T;
|
|
40
|
-
} | {
|
|
41
|
-
readonly ok: false;
|
|
42
|
-
readonly message: string;
|
|
43
|
-
};
|
|
37
|
+
/** What one call answered: the generated four-state RouteAnswer (no local failure vocabulary). */
|
|
38
|
+
export type ApprovalAnswer<T> = RouteAnswer<T>;
|
|
44
39
|
/**
|
|
45
40
|
* Which window the pending route is asked for. The host route has always taken `?status=` (the CLI
|
|
46
41
|
* asks for both); this half asked for neither, so a record stuck in `executing` — an approve that
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Literal mirror of the frozen Host diagnostics contract (W1 §2.8 / core diagnostics.ts). The browser
|
|
3
|
+
* build keeps every @deepseek-ai/* specifier external, so the client CANNOT import the host type — the
|
|
4
|
+
* shape is mirrored mechanically here instead. The key unions are rendered from core diagnostics.ts by
|
|
5
|
+
* the generator, so a key change regenerates all three copies (a stale copy fails --check).
|
|
6
|
+
*/
|
|
7
|
+
export type DiagnosticsSchemaVersion = 2;
|
|
8
|
+
export type DiagnosticsFactKey = 'platform-line' | 'family-version' | 'install-form' | 'preset-ids' | 'readonly-contract' | 'upgrade-order' | 'fallback-map';
|
|
9
|
+
export type DiagnosticsRuntimeKey = 'subagent-channel' | 'restricted-preset' | 'host-surface' | 'session-fold' | 'platform-services' | 'runs' | 'platform-pairing' | 'install-form-detected';
|
|
10
|
+
export type DiagnosticStatus = 'ok' | 'warn' | 'fail' | 'unknown';
|
|
11
|
+
export interface DocFactRow {
|
|
12
|
+
readonly key: DiagnosticsFactKey;
|
|
13
|
+
readonly label: string;
|
|
14
|
+
readonly value: string;
|
|
15
|
+
readonly home: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DiagnosticRow {
|
|
18
|
+
readonly key: DiagnosticsRuntimeKey;
|
|
19
|
+
readonly label: string;
|
|
20
|
+
readonly status: DiagnosticStatus;
|
|
21
|
+
readonly detail?: string;
|
|
22
|
+
readonly remedy?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface DiagnosticsView {
|
|
25
|
+
readonly schemaVersion: DiagnosticsSchemaVersion;
|
|
26
|
+
readonly generatedAt: string;
|
|
27
|
+
readonly facts: readonly DocFactRow[];
|
|
28
|
+
readonly runtime: readonly DiagnosticRow[];
|
|
29
|
+
}
|
|
30
|
+
/** The installation form the doctor reports (W8 install-form-detected row). */
|
|
31
|
+
export type InstallFormId = 'M1' | 'M2' | 'M3' | 'M4';
|
|
32
|
+
/** One surface's command-face path(s) — the client `fallbacks.generated.ts` table's row type. */
|
|
33
|
+
export interface FallbackHint {
|
|
34
|
+
readonly command: string;
|
|
35
|
+
readonly platformCli?: string;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=contracts.generated.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type RouteAnswer } from './route-client.generated.ts';
|
|
2
|
+
import type { DiagnosticsView } from './contracts.generated.ts';
|
|
3
|
+
/** What the card renders: the view, or the classified failure that kept it from arriving. */
|
|
4
|
+
export type DiagnosticsState = {
|
|
5
|
+
readonly phase: 'loading';
|
|
6
|
+
} | {
|
|
7
|
+
readonly phase: 'ready';
|
|
8
|
+
readonly view: DiagnosticsView;
|
|
9
|
+
} | {
|
|
10
|
+
readonly phase: 'failed';
|
|
11
|
+
readonly failure: import('./route-client.generated.ts').RouteFailure;
|
|
12
|
+
};
|
|
13
|
+
/** The one call the card makes. */
|
|
14
|
+
export interface DiagnosticsApi {
|
|
15
|
+
view(): Promise<RouteAnswer<DiagnosticsView>>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build the diagnostics face over one fetch implementation.
|
|
19
|
+
* @param doFetch - the browser fetch, or a spec stub.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createDiagnosticsApi(doFetch?: typeof fetch): DiagnosticsApi;
|
|
22
|
+
/**
|
|
23
|
+
* Read the view once and map it onto the card's four states (loading is the caller's initial value).
|
|
24
|
+
* @param doFetch - the fetch to use.
|
|
25
|
+
* @returns ready with the view, or failed with the classified failure.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadDiagnostics(doFetch?: typeof fetch): Promise<DiagnosticsState>;
|
|
28
|
+
//# sourceMappingURL=diagnostics-state.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FallbackHint } from './contracts.generated.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The command-face fallback for every Host surface, generated from evolution-commands'
|
|
4
|
+
* COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
|
|
5
|
+
* panels render this hint instead of a bare HTTP failure — same use case, command face.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FALLBACKS: Readonly<Record<'approval' | 'skill-history' | 'skill-health' | 'diagnostics', FallbackHint>>;
|
|
8
|
+
//# sourceMappingURL=fallbacks.generated.d.ts.map
|
|
@@ -28,14 +28,13 @@ export declare const zh: {
|
|
|
28
28
|
readonly discard: "放弃修改";
|
|
29
29
|
readonly saving: "保存中…";
|
|
30
30
|
readonly resetting: "恢复中…";
|
|
31
|
-
readonly
|
|
31
|
+
readonly writeTargetHost: "保存去向:宿主配置(对所有会话生效)";
|
|
32
32
|
readonly invalidNumber: "需要一个数字";
|
|
33
33
|
readonly invalidSelect: "不在允许的取值里";
|
|
34
34
|
readonly invalidAtLeast: "不能小于同组的 {n}";
|
|
35
35
|
readonly approvalConfirmNamed: "即将批准:{n}";
|
|
36
36
|
readonly approvalConfirmYes: "确认批准";
|
|
37
37
|
readonly approvalConfirmNo: "取消";
|
|
38
|
-
readonly destMemory: "保存去向:仅本次会话";
|
|
39
38
|
readonly resetTo: "复位为 {value}";
|
|
40
39
|
readonly refused: "这个值没有被接受(界面上保持原样)。想看原因:在对话里让它说明。";
|
|
41
40
|
readonly resetRefused: "恢复默认值没有生效(这一行还是你的值)。想看原因:在对话里让它说明。";
|
|
@@ -72,6 +71,26 @@ export declare const zh: {
|
|
|
72
71
|
readonly approvalDiffAdded: "{n} 行新增";
|
|
73
72
|
readonly approvalDiffRemoved: "{n} 行删除";
|
|
74
73
|
readonly approvalDiffCut: "改动区域已截断";
|
|
74
|
+
readonly diagTitle: "诊断";
|
|
75
|
+
readonly diagSubtitle: "安装形态、平台线与运行面的当前读数;只读,不改任何东西。";
|
|
76
|
+
readonly diagFactsTitle: "事实";
|
|
77
|
+
readonly diagRuntimeTitle: "运行时";
|
|
78
|
+
readonly diagSchema: "视图 schema v{n}";
|
|
79
|
+
readonly diagGeneratedAt: "生成于 {at}";
|
|
80
|
+
readonly diagRefresh: "刷新";
|
|
81
|
+
readonly diagRefreshing: "刷新中…";
|
|
82
|
+
readonly diagStatusOk: "正常";
|
|
83
|
+
readonly diagStatusWarn: "注意";
|
|
84
|
+
readonly diagStatusFail: "异常";
|
|
85
|
+
readonly diagStatusUnknown: "未知";
|
|
86
|
+
readonly diagRemedy: "下一步:";
|
|
87
|
+
readonly diagHomeCite: "出处:{home}";
|
|
88
|
+
readonly stateLoading: "正在读取…";
|
|
89
|
+
readonly failureUnauthorized: "需要登录:重新打开这个页面完成登录后再试。";
|
|
90
|
+
readonly failureForbidden: "平台拒绝了这个请求(身份不是 owner)。";
|
|
91
|
+
readonly failureUnmounted: "该功能没有挂载。";
|
|
92
|
+
readonly failureServer: "读取失败,可以手动重试。";
|
|
93
|
+
readonly fallbackHint: "可以改用命令:{command}";
|
|
75
94
|
};
|
|
76
95
|
/** English copy, key for key with {@link zh}. */
|
|
77
96
|
export declare const en: Record<keyof typeof zh, string>;
|
|
@@ -86,7 +105,7 @@ export type MessageKey = keyof typeof zh;
|
|
|
86
105
|
/**
|
|
87
106
|
* Resolve one message without the locale seat (tests and the fallback path).
|
|
88
107
|
* @param key - the message key.
|
|
89
|
-
* @param language - which dictionary to read; defaults to
|
|
108
|
+
* @param language - which dictionary to read; defaults to English (the platform FALLBACK_LOCALE).
|
|
90
109
|
* @returns the copy this bundle carries.
|
|
91
110
|
*/
|
|
92
111
|
export declare function message(key: MessageKey, language?: 'zh' | 'en'): string;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module @lmzhen/dsh-evolution-settings-ui/client/pending-state
|
|
9
9
|
*/
|
|
10
10
|
import type { PendingRow, PreviewAnswer } from './api.ts';
|
|
11
|
+
import type { RouteFailure } from './route-client.generated.ts';
|
|
11
12
|
/** What one row's preview is showing while it is open. */
|
|
12
13
|
export type PreviewView = {
|
|
13
14
|
readonly kind: 'loading';
|
|
@@ -21,7 +22,7 @@ export type PreviewView = {
|
|
|
21
22
|
readonly reason: string;
|
|
22
23
|
} | {
|
|
23
24
|
readonly kind: 'failed';
|
|
24
|
-
readonly
|
|
25
|
+
readonly failure: RouteFailure;
|
|
25
26
|
};
|
|
26
27
|
/** What the card is showing right now. */
|
|
27
28
|
export type PendingView = {
|
|
@@ -33,7 +34,7 @@ export type PendingView = {
|
|
|
33
34
|
readonly kind: 'empty';
|
|
34
35
|
} | {
|
|
35
36
|
readonly kind: 'failed';
|
|
36
|
-
readonly
|
|
37
|
+
readonly failure: RouteFailure;
|
|
37
38
|
};
|
|
38
39
|
/** The whole card state: the view, the ids with an action in flight, and the last refusal line. */
|
|
39
40
|
export interface PendingState {
|
|
@@ -62,8 +63,8 @@ export declare function loading(state: PendingState): PendingState;
|
|
|
62
63
|
* being shown the same line a slow answer shows.
|
|
63
64
|
*/
|
|
64
65
|
export declare function loaded(state: PendingState, rows: readonly PendingRow[]): PendingState;
|
|
65
|
-
/** The list could not be read: the
|
|
66
|
-
export declare function failed(state: PendingState,
|
|
66
|
+
/** The list could not be read: the classified failure stays on screen next to a retry control. */
|
|
67
|
+
export declare function failed(state: PendingState, failure: RouteFailure): PendingState;
|
|
67
68
|
/** Mark one record as having an action in flight (its buttons are disabled until it ends). */
|
|
68
69
|
export declare function beginAction(state: PendingState, id: string): PendingState;
|
|
69
70
|
/** The action ended, whatever it answered. */
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four-state transport classifier every family client shares (W7 §2). The platform owns
|
|
3
|
+
* admission (401/403/404 are its answers, not ours), so the client reads the STATUS only for those
|
|
4
|
+
* three and never tries to interpret a platform body. Everything the family route itself answers
|
|
5
|
+
* keeps its business envelope: 2xx + {ok:true,data} or 2xx + {ok:false,code,message}.
|
|
6
|
+
*/
|
|
7
|
+
export type RouteFailure = {
|
|
8
|
+
readonly kind: 'unauthenticated';
|
|
9
|
+
readonly status: 401;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'forbidden';
|
|
12
|
+
readonly status: 403;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'unmounted';
|
|
15
|
+
readonly status: 404;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: 'server';
|
|
18
|
+
readonly status: number;
|
|
19
|
+
readonly detail?: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'refused';
|
|
22
|
+
readonly code: string;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
};
|
|
25
|
+
/** One route answer: the data, or the classified failure. */
|
|
26
|
+
export type RouteAnswer<T> = {
|
|
27
|
+
readonly ok: true;
|
|
28
|
+
readonly data: T;
|
|
29
|
+
} | {
|
|
30
|
+
readonly ok: false;
|
|
31
|
+
readonly failure: RouteFailure;
|
|
32
|
+
};
|
|
33
|
+
/** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
|
|
34
|
+
export declare function classify(status: number): RouteFailure;
|
|
35
|
+
/**
|
|
36
|
+
* One request, no retries and no body parsing for the platform's admission answers.
|
|
37
|
+
* @param doFetch - the fetch in use (the browser's, or a spec's stub).
|
|
38
|
+
* @param path - the exact route path from the generated ROUTES table.
|
|
39
|
+
* @param init - standard RequestInit.
|
|
40
|
+
* @returns the data, or the classified failure (network/parse failures read as 'server').
|
|
41
|
+
*/
|
|
42
|
+
export declare function requestJson<T>(doFetch: typeof fetch, path: string, init?: RequestInit): Promise<RouteAnswer<T>>;
|
|
43
|
+
/** The one-sentence rendering legacy callers attach to a classified failure. */
|
|
44
|
+
export declare function failureMessage(failure: RouteFailure): string;
|
|
45
|
+
//# sourceMappingURL=route-client.generated.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** One host data-plane surface the browser half can call. */
|
|
2
|
+
export type SurfaceId = 'approval' | 'skill-history' | 'skill-health' | 'diagnostics';
|
|
3
|
+
/**
|
|
4
|
+
* Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
|
|
5
|
+
* A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
|
|
6
|
+
* external), so the table is mirrored here mechanically; a rename in core regenerates all three
|
|
7
|
+
* client copies, and the generator's --check makes a stale copy red.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ROUTES: {
|
|
10
|
+
readonly approval: {
|
|
11
|
+
readonly pending: "/api/dsh-evolution/approval/pending";
|
|
12
|
+
readonly preview: "/api/dsh-evolution/approval/preview";
|
|
13
|
+
readonly approve: "/api/dsh-evolution/approval/approve";
|
|
14
|
+
readonly reject: "/api/dsh-evolution/approval/reject";
|
|
15
|
+
};
|
|
16
|
+
readonly 'skill-history': {
|
|
17
|
+
readonly skills: "/api/dsh-evolution/skill-history/skills";
|
|
18
|
+
readonly versions: "/api/dsh-evolution/skill-history/versions";
|
|
19
|
+
readonly diff: "/api/dsh-evolution/skill-history/versions/diff";
|
|
20
|
+
readonly body: "/api/dsh-evolution/skill-history/versions/body";
|
|
21
|
+
readonly undo: "/api/dsh-evolution/skill-history/undo";
|
|
22
|
+
readonly health: "/api/dsh-evolution/skill-history/health";
|
|
23
|
+
};
|
|
24
|
+
readonly 'skill-health': {
|
|
25
|
+
readonly rows: "/api/dsh-evolution/skill-health/rows";
|
|
26
|
+
};
|
|
27
|
+
readonly diagnostics: {
|
|
28
|
+
readonly view: "/api/dsh-evolution/diagnostics";
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=routes.generated.d.ts.map
|
|
@@ -35,6 +35,8 @@ export declare function rowConfigKey(bundle: string, rowId: string): string;
|
|
|
35
35
|
* the section — one definition in the module both sides already share is what keeps that pair acyclic.
|
|
36
36
|
*/
|
|
37
37
|
export declare const PENDING_CARD_KEY = "approval-pending";
|
|
38
|
+
/** The diagnostics card's slot key: read-only, no settings namespace (its data comes from HOST_ROUTES.diagnostics). */
|
|
39
|
+
export declare const DIAGNOSTICS_CARD_KEY = "diagnostics";
|
|
38
40
|
/** Snapshot of one settings row, as the client seat reports it. */
|
|
39
41
|
export interface ParamSectionSnapshot {
|
|
40
42
|
status: 'loading' | 'ready' | 'unavailable';
|
|
@@ -44,8 +46,6 @@ export interface ParamSectionSnapshot {
|
|
|
44
46
|
writable: boolean;
|
|
45
47
|
/** U-5: the composition layer a cleared field reverts to (platform `base`, previously dropped). */
|
|
46
48
|
base: Record<string, unknown> | undefined;
|
|
47
|
-
/** U-5: where a save lands — `memory` means this session only (platform `mode`, previously dropped). */
|
|
48
|
-
mode: 'host' | 'memory';
|
|
49
49
|
/**
|
|
50
50
|
* Why an `unavailable` snapshot is unavailable: this bundle found no settings seat at
|
|
51
51
|
* all, or the seat is there and serves no such row. The two read differently to the
|
|
@@ -84,7 +84,6 @@ export interface ConfigFormSnapshotLike<T = Record<string, unknown>> {
|
|
|
84
84
|
user: unknown;
|
|
85
85
|
revision: number | undefined;
|
|
86
86
|
writable: boolean;
|
|
87
|
-
mode: 'host' | 'memory';
|
|
88
87
|
}
|
|
89
88
|
/** One row's client write face (0.2.x `configForms`). */
|
|
90
89
|
export interface ConfigFormLike<T = Record<string, unknown>> {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { RouteAnswer, RouteFailure } from './route-client.generated.ts';
|
|
3
|
+
import type { FallbackHint } from './contracts.generated.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The four-state base of every family card (W7 §2). Load states are read from the generated
|
|
6
|
+
* route-client's answer — a card owns no HTTP vocabulary and no second state machine.
|
|
7
|
+
*/
|
|
8
|
+
export type LoadState<T> = {
|
|
9
|
+
readonly phase: 'loading';
|
|
10
|
+
} | {
|
|
11
|
+
readonly phase: 'ready';
|
|
12
|
+
readonly data: T;
|
|
13
|
+
} | {
|
|
14
|
+
readonly phase: 'empty';
|
|
15
|
+
} | {
|
|
16
|
+
readonly phase: 'failed';
|
|
17
|
+
readonly failure: RouteFailure;
|
|
18
|
+
};
|
|
19
|
+
/** Map one classified answer onto the card's four states. */
|
|
20
|
+
export declare function loadStateOf<T>(answer: RouteAnswer<T>, isEmpty: (data: T) => boolean): LoadState<T>;
|
|
21
|
+
/** The locale lookup every block calls (the platform's own `t`). */
|
|
22
|
+
export interface Translate {
|
|
23
|
+
(key: string, values?: Readonly<Record<string, string | number>>): string;
|
|
24
|
+
}
|
|
25
|
+
export type BlockTone = 'neutral' | 'warn' | 'fail';
|
|
26
|
+
export interface BlockProps {
|
|
27
|
+
readonly tone?: BlockTone;
|
|
28
|
+
readonly title?: string;
|
|
29
|
+
readonly children?: ReactNode;
|
|
30
|
+
}
|
|
31
|
+
/** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
|
|
32
|
+
export declare function Block(props: BlockProps): ReactNode;
|
|
33
|
+
export interface FailureBlockProps {
|
|
34
|
+
readonly failure: RouteFailure;
|
|
35
|
+
readonly t: Translate;
|
|
36
|
+
/** The command-face fallback for this surface (rendered on unmounted/failed answers). */
|
|
37
|
+
readonly fallback?: FallbackHint | undefined;
|
|
38
|
+
}
|
|
39
|
+
/** One classified failure as a reader-facing sentence. */
|
|
40
|
+
export declare function failureText(failure: RouteFailure, t: Translate): string;
|
|
41
|
+
/** The failed-state block: sentence + (when the surface exists but is unreachable) the command fallback. */
|
|
42
|
+
export declare function FailureBlock(props: FailureBlockProps): ReactNode;
|
|
43
|
+
//# sourceMappingURL=states.generated.d.ts.map
|
|
@@ -5,9 +5,13 @@ export declare const CSS: string;
|
|
|
5
5
|
/** The slice of the DOM the injection touches (this package typechecks without DOM lib). */
|
|
6
6
|
interface StyleHost {
|
|
7
7
|
querySelector(selector: string): unknown;
|
|
8
|
+
/** The element shape the injection uses. `remove()` is on the real DOM element and is what the
|
|
9
|
+
* S5-08 disposer calls, so the host shape names it too - otherwise the package cannot typecheck
|
|
10
|
+
* without the DOM lib while still using the disposal the browser offers. */
|
|
8
11
|
createElement(tag: string): {
|
|
9
12
|
dataset: Record<string, string>;
|
|
10
13
|
textContent: string;
|
|
14
|
+
remove(): void;
|
|
11
15
|
};
|
|
12
16
|
head: {
|
|
13
17
|
appendChild(node: unknown): void;
|
|
@@ -15,8 +19,14 @@ interface StyleHost {
|
|
|
15
19
|
}
|
|
16
20
|
/**
|
|
17
21
|
* Inject the stylesheet once per document.
|
|
22
|
+
*
|
|
23
|
+
* S5-08: it RETURNS the disposer. The tag check alone answered only half the question - "is it
|
|
24
|
+
* there" - and left "who takes it away" to nobody, so a hot reload (unmount, remount) found the old
|
|
25
|
+
* tag, skipped the injection, and kept serving the PREVIOUS bundle CSS. skill-history hands its
|
|
26
|
+
* disposer back to the fiber; this is the same shape for the same concern.
|
|
18
27
|
* @param host - the document to inject into; defaults to the global one.
|
|
28
|
+
* @returns the disposal; a no-op when there is no document or the tag was already present.
|
|
19
29
|
*/
|
|
20
|
-
export declare function injectStyles(host?: StyleHost): void;
|
|
30
|
+
export declare function injectStyles(host?: StyleHost): () => void;
|
|
21
31
|
export {};
|
|
22
32
|
//# sourceMappingURL=styles.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-settings-ui",
|
|
3
3
|
"description": "Web settings cards for the evolution family's parameter namespaces (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.27.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -39,7 +39,10 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
42
|
+
"@deepseek-ai/cordis": "^4.0.5",
|
|
43
43
|
"react": "^18.2.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"dsh": ">=0.2.1-rc.1 <0.2.2-0"
|
|
44
47
|
}
|
|
45
48
|
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The approval paths this browser half calls.
|
|
3
|
-
*
|
|
4
|
-
* A browser half cannot import the host package's table at runtime (the family's client bundles treat
|
|
5
|
-
* every `@deepseek-ai/*` specifier as external), so the literals are declared here and a spec compares
|
|
6
|
-
* them against `APPROVAL_ROUTES` in `evolution-approval` — a rename on one side would otherwise surface
|
|
7
|
-
* as a 404 in the GUI instead of in CI.
|
|
8
|
-
* @module @lmzhen/dsh-evolution-settings-ui/client/approval-routes
|
|
9
|
-
*/
|
|
10
|
-
/** One path per call the pending card makes. */
|
|
11
|
-
export declare const APPROVAL_CLIENT_ROUTES: {
|
|
12
|
-
readonly pending: "/api/dsh-evolution/approval/pending";
|
|
13
|
-
readonly approve: "/api/dsh-evolution/approval/approve";
|
|
14
|
-
readonly reject: "/api/dsh-evolution/approval/reject";
|
|
15
|
-
readonly preview: "/api/dsh-evolution/approval/preview";
|
|
16
|
-
};
|
|
17
|
-
//# sourceMappingURL=approval-routes.d.ts.map
|