@lmzhen/dsh-evolution-skill-history 0.11.2 → 0.12.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/README.md +2 -2
- package/lib/client.js +56 -12
- package/lib/types/client/Panel.d.ts +6 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ The row is **inert without a web server** (`ctx.get('webServer')`, not a declare
|
|
|
32
32
|
|
|
33
33
|
## What the panel shows
|
|
34
34
|
|
|
35
|
-
The selected skill's versions as two groups: **Body versions** (restorable) and **File versions** (history only, because a restore rewrites `SKILL.md`). A body row offers **Restore this version** unless it already IS the live content (the host marks those), and the button turns into an inline confirmation first. After a restore the
|
|
35
|
+
The selected skill's versions as two groups: **Body versions** (restorable) and **File versions** (history only, because a restore rewrites `SKILL.md`). A body row offers **Restore this version** unless it already IS the live content (the host marks those), and the button turns into an inline confirmation first. After a restore the rows are read again — the restore itself is a new version — while the curator's result sentence stays on screen. **Refresh** re-reads the skill list and the open skill; before it, seeing what another writer recorded in the meantime meant opening the panel again for the list, or clicking the open skill again for its rows (the aside counts are read with the list, not per row).
|
|
36
36
|
|
|
37
37
|
## Build
|
|
38
38
|
|
|
@@ -44,7 +44,7 @@ This package adds **no model-visible content**: no prompt section, no tool schem
|
|
|
44
44
|
|
|
45
45
|
## Known limitations and deferred work
|
|
46
46
|
|
|
47
|
-
- **No rendered spec.** The family's specs are Node-level and this package's client half cannot be rendered there, so the panel is covered by `tsc`, the client bundle build, the pure `tests/client-api.spec.ts` and a live pass on the installed artifact. A rendered spec (jsdom plus a driven fixture runtime) is the follow-up that would catch wiring in CI.
|
|
47
|
+
- **No rendered spec.** The family's specs are Node-level and this package's client half cannot be rendered there, so the panel is covered by `tsc`, the client bundle build, the pure `tests/client-api.spec.ts` and a live pass on the installed artifact. A rendered spec (jsdom plus a driven fixture runtime) is the follow-up that would catch wiring in CI — 0.11.3 is the cost of not having one: the first installed pass restored the content correctly and re-read the rows correctly while the curator's result sentence never appeared, because the reload that follows a restore cleared the note state it had just set. No Node-level spec and no `tsc` can see the order of two state updates; only clicking the panel showed it.
|
|
48
48
|
- **The list shows skills with recorded versions only.** A skill that never wrote through the library has no history to browse, so it does not appear; the tree-wide listing stays with the skill catalog.
|
|
49
49
|
- **No diff view.** The routes return entries, not bodies; a two-version diff would need a second read route and a client-side differ.
|
|
50
50
|
- **Support-file bytes are history only.** They share the index with the body, so they are listed and labelled, but `undo` refuses them by name — the bytes stay in the blob store for a hand copy.
|
package/lib/client.js
CHANGED
|
@@ -110,12 +110,19 @@ window.__ModuleLoader__.load({
|
|
|
110
110
|
* The panel owns no rule: the host reports each body row with a verdict (`undoable`) computed from
|
|
111
111
|
* the version facts in evolution-core, and the curator owns the words a refusal uses. This file is
|
|
112
112
|
* presentation plus local state — it never subscribes to anything and it never sees the context.
|
|
113
|
+
*
|
|
114
|
+
* Two state rules, both pinned by a live pass rather than by a test (a browser half has no unit test
|
|
115
|
+
* seat in this family): a restore's result sentence stays on screen while the rows are read again, and
|
|
116
|
+
* nothing is read on a poll — the skill list is read on mount and by the refresh button, the rows when
|
|
117
|
+
* a skill opens, when the refresh button is pressed and after a restore. The width cap keeps a
|
|
118
|
+
* version's action within reach of the version it acts on.
|
|
113
119
|
* @module @lmzhen/dsh-evolution-skill-history/client
|
|
114
120
|
*/
|
|
115
121
|
const root = {
|
|
116
122
|
display: "flex",
|
|
117
123
|
height: "100%",
|
|
118
124
|
minHeight: "0",
|
|
125
|
+
maxWidth: "960px",
|
|
119
126
|
fontSize: "13px",
|
|
120
127
|
color: "var(--dsw-alias-label-primary)"
|
|
121
128
|
};
|
|
@@ -132,6 +139,12 @@ window.__ModuleLoader__.load({
|
|
|
132
139
|
overflowY: "auto",
|
|
133
140
|
padding: "12px 14px"
|
|
134
141
|
};
|
|
142
|
+
const header = {
|
|
143
|
+
display: "flex",
|
|
144
|
+
alignItems: "center",
|
|
145
|
+
gap: "10px",
|
|
146
|
+
margin: "0 0 4px"
|
|
147
|
+
};
|
|
135
148
|
const skillButton = {
|
|
136
149
|
display: "block",
|
|
137
150
|
width: "100%",
|
|
@@ -149,9 +162,10 @@ window.__ModuleLoader__.load({
|
|
|
149
162
|
fontWeight: 600
|
|
150
163
|
};
|
|
151
164
|
const heading = {
|
|
152
|
-
margin: "0
|
|
165
|
+
margin: "0",
|
|
153
166
|
fontSize: "13px",
|
|
154
|
-
fontWeight: 600
|
|
167
|
+
fontWeight: 600,
|
|
168
|
+
flex: "1 1 auto"
|
|
155
169
|
};
|
|
156
170
|
const hint = {
|
|
157
171
|
margin: "0 0 10px",
|
|
@@ -229,19 +243,42 @@ window.__ModuleLoader__.load({
|
|
|
229
243
|
const [payload, setPayload] = (0, react.useState)(void 0);
|
|
230
244
|
const [noteText, setNoteText] = (0, react.useState)(void 0);
|
|
231
245
|
const [pending, setPending] = (0, react.useState)(void 0);
|
|
246
|
+
const readTicket = (0, react.useRef)(0);
|
|
247
|
+
const failureText = (error) => face.t("error") + ": " + (error instanceof Error ? error.message : String(error));
|
|
232
248
|
const failed = (error) => {
|
|
233
|
-
setNoteText(
|
|
249
|
+
setNoteText(failureText(error));
|
|
234
250
|
};
|
|
235
|
-
|
|
236
|
-
|
|
251
|
+
/** Read the skills again, keeping the sentence: a re-read is not an answer to anything. */
|
|
252
|
+
const reloadSkills = () => {
|
|
237
253
|
face.loadSkills().then(setSkills).catch(failed);
|
|
238
254
|
};
|
|
239
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Show one skill's chains, keeping the sentence. A re-read that FAILS reports through the caller's
|
|
257
|
+
* own handler, so the failure of a read can never erase the write it was reading after.
|
|
258
|
+
* @param name - the skill to open.
|
|
259
|
+
* @param onFailure - what to do with a failed read; the panel shows it as its own sentence.
|
|
260
|
+
*/
|
|
261
|
+
const showVersions = (name, onFailure = failed) => {
|
|
262
|
+
const ticket = readTicket.current + 1;
|
|
263
|
+
readTicket.current = ticket;
|
|
240
264
|
setSelected(name);
|
|
241
265
|
setPayload(void 0);
|
|
242
266
|
setPending(void 0);
|
|
243
|
-
|
|
244
|
-
|
|
267
|
+
face.loadVersions(name).then((next) => {
|
|
268
|
+
if (readTicket.current === ticket) setPayload(next);
|
|
269
|
+
}).catch((error) => {
|
|
270
|
+
if (readTicket.current === ticket) onFailure(error);
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
/** Open a skill from the list: another skill's sentence described another skill, so it goes away. */
|
|
274
|
+
const open = (name) => {
|
|
275
|
+
if (name !== selected) setNoteText(void 0);
|
|
276
|
+
showVersions(name);
|
|
277
|
+
};
|
|
278
|
+
/** What the refresh button does: both reads again, and the sentence stays. */
|
|
279
|
+
const refresh = () => {
|
|
280
|
+
reloadSkills();
|
|
281
|
+
if (selected !== void 0) showVersions(selected);
|
|
245
282
|
};
|
|
246
283
|
const restore = (v) => {
|
|
247
284
|
if (selected === void 0) return;
|
|
@@ -250,12 +287,15 @@ window.__ModuleLoader__.load({
|
|
|
250
287
|
return;
|
|
251
288
|
}
|
|
252
289
|
setPending(void 0);
|
|
253
|
-
|
|
290
|
+
const name = selected;
|
|
291
|
+
face.undo(name, v).then((message) => {
|
|
292
|
+
showVersions(name, (error) => {
|
|
293
|
+
setNoteText(message + "\n" + failureText(error));
|
|
294
|
+
});
|
|
254
295
|
setNoteText(message);
|
|
255
|
-
open(selected);
|
|
256
296
|
}).catch(failed);
|
|
257
297
|
};
|
|
258
|
-
(0, react.useEffect)(
|
|
298
|
+
(0, react.useEffect)(reloadSkills, []);
|
|
259
299
|
const loaded = payload !== void 0;
|
|
260
300
|
return (0, react.createElement)("div", { style: root }, (0, react.createElement)("aside", { style: aside }, skills.length === 0 ? (0, react.createElement)("p", { style: hint }, face.t("empty.skills")) : skills.map((skill) => (0, react.createElement)("button", {
|
|
261
301
|
key: skill.name,
|
|
@@ -264,7 +304,11 @@ window.__ModuleLoader__.load({
|
|
|
264
304
|
onClick: () => {
|
|
265
305
|
open(skill.name);
|
|
266
306
|
}
|
|
267
|
-
}, skill.name + " (" + String(skill.versions) + ")"))), (0, react.createElement)("div", { style: main }, (0, react.createElement)("
|
|
307
|
+
}, skill.name + " (" + String(skill.versions) + ")"))), (0, react.createElement)("div", { style: main }, (0, react.createElement)("div", { style: header }, (0, react.createElement)("h2", { style: heading }, face.t("title")), (0, react.createElement)("button", {
|
|
308
|
+
type: "button",
|
|
309
|
+
style: action,
|
|
310
|
+
onClick: refresh
|
|
311
|
+
}, face.t("refresh"))), (0, react.createElement)("p", { style: hint }, face.t("hint")), noteText === void 0 ? null : (0, react.createElement)("p", { style: note }, noteText), selected === void 0 ? null : loaded ? null : (0, react.createElement)("p", { style: hint }, face.t("loading")), loaded ? (0, react.createElement)("div", null, group({
|
|
268
312
|
...face,
|
|
269
313
|
restore,
|
|
270
314
|
confirming: pending
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* The panel owns no rule: the host reports each body row with a verdict (`undoable`) computed from
|
|
6
6
|
* the version facts in evolution-core, and the curator owns the words a refusal uses. This file is
|
|
7
7
|
* presentation plus local state — it never subscribes to anything and it never sees the context.
|
|
8
|
+
*
|
|
9
|
+
* Two state rules, both pinned by a live pass rather than by a test (a browser half has no unit test
|
|
10
|
+
* seat in this family): a restore's result sentence stays on screen while the rows are read again, and
|
|
11
|
+
* nothing is read on a poll — the skill list is read on mount and by the refresh button, the rows when
|
|
12
|
+
* a skill opens, when the refresh button is pressed and after a restore. The width cap keeps a
|
|
13
|
+
* version's action within reach of the version it acts on.
|
|
8
14
|
* @module @lmzhen/dsh-evolution-skill-history/client
|
|
9
15
|
*/
|
|
10
16
|
import { type ReactNode } from 'react';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-skill-history",
|
|
3
3
|
"description": "Web surface for skill content history: loopback host routes plus the sidebar panel (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -30,21 +30,21 @@
|
|
|
30
30
|
],
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
33
|
+
"@lmzhen/dsh-evolution-core": "^0.12.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
37
37
|
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
|
|
38
38
|
"react": "^18.2.0",
|
|
39
|
-
"@lmzhen/dsh-evolution-curator": "^0.
|
|
39
|
+
"@lmzhen/dsh-evolution-curator": "^0.12.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
43
43
|
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
|
|
44
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
45
|
-
"@lmzhen/dsh-evolution-curator": "^0.
|
|
46
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
47
|
-
"@lmzhen/dsh-evolution-io-node": "^0.
|
|
44
|
+
"@lmzhen/dsh-evolution-core": "^0.12.0",
|
|
45
|
+
"@lmzhen/dsh-evolution-curator": "^0.12.0",
|
|
46
|
+
"@lmzhen/dsh-evolution-io": "^0.12.0",
|
|
47
|
+
"@lmzhen/dsh-evolution-io-node": "^0.12.0"
|
|
48
48
|
},
|
|
49
49
|
"dsh": {
|
|
50
50
|
"client": {
|