@lmzhen/dsh-evolution-skill-history 0.13.2 → 0.13.3

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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/lib/client.js +71 -28
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -33,13 +33,15 @@ The row is **inert without a web server** (`ctx.get('webServer')`, not a declare
33
33
 
34
34
  ## What the panel shows
35
35
 
36
- **The left column** lists the skills that recorded versions, one row per skill: its name, how many versions it has, its own one-line description (the listing already carried it) and the management state the marker probe reported. A **search box** filters that list by name or description — client state only, no route behind it.
36
+ **The left column** lists the skills that recorded versions, one row per skill: its name, how many versions it has, its own one-line description, clipped with an ellipsis (the listing already carried it) and the management state the marker probe reported. A **search box** filters that list by name or description — client state only, no route behind it.
37
37
 
38
- **The right pane** shows 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 reads like a sentence rather than a dump — `v3 targeted edit 2026-09-27 18:20 · 3 hours ago (+1363 chars)` — with a *current* capsule on the live content, **Restore this version** (an inline two-click confirmation, with a **Cancel** beside it once it is armed) on the others, and **Diff** expanding that version against its predecessor (lines added and removed, plus the changed region, marked truncated when it is long). 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.
38
+ **The right pane** shows 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 reads like a sentence rather than a dump — `v3 targeted edit 3 hours ago (+1363 chars)`, where the absolute clock is the row's hover text — with a *current* capsule on the live content, **Restore this version** (an inline two-click confirmation, with a **Cancel** beside it once it is armed) on the others, and **Diff** expanding that version against its predecessor (lines added and removed, plus the changed region, marked truncated when it is long). 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.
39
+
40
+ While nothing is selected the pane says so — a line pointing at the left column, or the empty listing when no skill has a history yet.
39
41
 
40
42
  **The panel owns no rule and no arithmetic.** The host reports every row with its whole verdict — `undoable` (evolution-core's artifact classification against the live bytes), `actionKind` (a closed vocabulary key), `age` (a bucket plus a count), `charsDelta`, `summary` — and this half substitutes words from its locale dictionary and renders. The time, the action words and the sentences are therefore localized; the facts are computed once, host-side, so the panel and the slash commands cannot drift.
41
43
 
42
- **Appearance follows the platform's tokens, not its modules.** The panel carries its own stylesheet (`src/client/styles.ts`, injected once behind `data-plugin-css`) and copies its metrics from the platform's own components — the command card's `<pre>` for the diff block, the sidebar row's hover and focus ring for the controls, and the theme's semantic state pair (`--dsw-alias-state-success-primary` / `--dsw-alias-state-error-primary`) for the diff's added and removed lines — while every size is a `--dsh-content-font-size*` token or a `calc()` over one, so the panel follows the deployment's content size the way the platform's panels do. Importing the platform's control package is NOT possible from here without splitting this package's compiler faces: the specifier is path-mapped to source, and `tsc -b` then refuses the foreign project (host/client face split, `react` devDependencies and a CI graph change would all be needed). The roll call of what the panel deliberately does not do is in `## Known limitations and deferred work`.
44
+ **Appearance follows the platform's tokens, not its modules.** The panel carries its own stylesheet (`src/client/styles.ts`, injected once behind `data-plugin-css`) and copies its metrics from the platform's own components — the command card's `<pre>` for the diff block, the sidebar row's hover and focus ring for the controls, and the theme's semantic state pair (`--dsw-alias-state-success-primary` / `--dsw-alias-state-error-primary`) for the diff's added and removed lines — while type follows the shell rather than the conversation: the panel declares no `font-size` of its own (the way the platform's own surfaces declare none) and steps with `calc(1em + 2px)` for the title and `calc(1em - 1px)` for a group label. Only the diff block takes the platform's code-block token — it renders content, not chrome. The setting behind `--dsh-content-font-size*` is documented as affecting conversation content only, so the panel deliberately does not follow it. Importing the platform's control package is NOT possible from here without splitting this package's compiler faces: the specifier is path-mapped to source, and `tsc -b` then refuses the foreign project (host/client face split, `react` devDependencies and a CI graph change would all be needed). The roll call of what the panel deliberately does not do is in `## Known limitations and deferred work`.
43
45
 
44
46
  ## Build
45
47
 
package/lib/client.js CHANGED
@@ -76,6 +76,7 @@ window.__ModuleLoader__.load({
76
76
  "group.support.note": "附带文件的字节与正文共用一份索引;回退不恢复它们。",
77
77
  "empty.skills": "还没有技能留下过内容版本。",
78
78
  "empty.versions": "这个技能还没有记录版本。",
79
+ "empty.pick": "从左栏选一个技能,看它记录过的版本。",
79
80
  loading: "读取中…",
80
81
  refresh: "刷新",
81
82
  undo: "回退到此版",
@@ -104,8 +105,10 @@ window.__ModuleLoader__.load({
104
105
  "action.patch": "定点修改",
105
106
  "action.update": "整篇更新",
106
107
  "action.restore": "回退",
107
- "action.delete": "删除(存的是被删的正文)",
108
- "action.archive": "归档(存的是被归档的正文)",
108
+ "action.delete": "删除",
109
+ "action.delete.note": "这一版存的是被删掉的正文",
110
+ "action.archive": "归档",
111
+ "action.archive.note": "这一版存的是被归档的正文",
109
112
  "action.consolidate": "合并",
110
113
  "action.restructure": "结构调整",
111
114
  "action.support-write": "写入附带文件",
@@ -137,6 +140,7 @@ window.__ModuleLoader__.load({
137
140
  "group.support.note": "Support-file bytes share this index with the body; restoring the body does not bring them back.",
138
141
  "empty.skills": "No skill has recorded a content version yet.",
139
142
  "empty.versions": "This skill has no recorded versions.",
143
+ "empty.pick": "Pick a skill on the left to see the versions it recorded.",
140
144
  loading: "Loading…",
141
145
  refresh: "Refresh",
142
146
  undo: "Restore this version",
@@ -165,8 +169,10 @@ window.__ModuleLoader__.load({
165
169
  "action.patch": "targeted edit",
166
170
  "action.update": "whole-body update",
167
171
  "action.restore": "restored",
168
- "action.delete": "deleted (holds the removed body)",
169
- "action.archive": "archived (holds the archived body)",
172
+ "action.delete": "deleted",
173
+ "action.delete.note": "this version holds the removed body",
174
+ "action.archive": "archived",
175
+ "action.archive.note": "this version holds the archived body",
170
176
  "action.consolidate": "merged",
171
177
  "action.restructure": "restructured",
172
178
  "action.support-write": "support file written",
@@ -226,12 +232,12 @@ window.__ModuleLoader__.load({
226
232
  const CSS_TAG_ID = "@lmzhen/dsh-evolution-skill-history/panel.css";
227
233
  /** The stylesheet the panel injects once. */
228
234
  const CSS = [
229
- ".evo-hist-root{display:flex;height:100%;min-height:0;min-width:0;font-size:var(--dsh-content-font-size-secondary,13px);color:var(--dsw-alias-label-primary)}",
235
+ ".evo-hist-root{display:flex;height:100%;min-height:0;min-width:0;color:var(--dsw-alias-label-primary)}",
230
236
  ".evo-hist-aside{display:flex;flex-direction:column;flex:0 0 264px;width:264px;min-height:0;border-right:.5px solid var(--dsw-alias-border-l2)}",
231
- ".evo-hist-search{padding:10px 12px 4px}",
237
+ ".evo-hist-search{padding:12px 12px 4px}",
232
238
  ".evo-hist-search input{width:100%;box-sizing:border-box;padding:6px 10px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit}",
233
239
  ".evo-hist-search input:focus-visible{outline:2px solid var(--dsw-alias-label-primary);outline-offset:-2px}",
234
- ".evo-hist-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:4px 6px 10px}",
240
+ ".evo-hist-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:4px 12px 10px}",
235
241
  ".evo-hist-empty{padding:8px 12px;color:var(--dsw-alias-label-tertiary)}",
236
242
  ".evo-hist-skill{display:block;width:100%;box-sizing:border-box;padding:7px 8px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;text-align:left;cursor:pointer}",
237
243
  ".evo-hist-skill:hover{background:var(--dsw-alias-interactive-bg-hover)}",
@@ -241,20 +247,22 @@ window.__ModuleLoader__.load({
241
247
  ".evo-hist-skill-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
242
248
  ".evo-hist-skill-count{flex:none;margin-left:auto;color:var(--dsw-alias-label-tertiary)}",
243
249
  ".evo-hist-skill-desc{display:block;margin-top:2px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
244
- ".evo-hist-main{flex:1 1 auto;min-width:0;min-height:0;overflow-y:auto;padding:12px 14px}",
250
+ ".evo-hist-main{flex:1 1 auto;min-width:0;min-height:0;overflow-y:auto;padding:12px 12px}",
245
251
  ".evo-hist-head{display:flex;align-items:center;gap:10px;margin:0 0 4px}",
246
- ".evo-hist-title{flex:1 1 auto;margin:0;font-size:inherit;font-weight:600}",
252
+ ".evo-hist-title{flex:1 1 auto;margin:0;font-size:calc(1em + 2px);font-weight:600}",
247
253
  ".evo-hist-hint{margin:0 0 10px;color:var(--dsw-alias-label-secondary);line-height:1.5}",
254
+ ".evo-hist-pane-empty{margin:2px 0 0;color:var(--dsw-alias-label-tertiary)}",
248
255
  ".evo-hist-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary);line-height:1.5;white-space:pre-wrap}",
249
256
  ".evo-hist-note[data-error=\"true\"]{color:var(--dsw-alias-state-error-primary)}",
250
- ".evo-hist-group{margin:10px 0 2px;font-size:inherit;font-weight:600}",
251
- ".evo-hist-row{display:flex;align-items:flex-start;gap:8px;padding:8px 0;border-top:.5px solid var(--dsw-alias-border-l3)}",
257
+ ".evo-hist-group{margin:10px 0 2px;font-size:calc(1em - 1px);font-weight:600;color:var(--dsw-alias-label-secondary)}",
258
+ ".evo-hist-row{display:flex;align-items:flex-start;gap:8px;padding:8px 0;border-top:.5px solid var(--dsw-alias-border-l2)}",
259
+ ".evo-hist-row:first-of-type{border-top:0}",
252
260
  ".evo-hist-row-body{flex:1 1 auto;min-width:0}",
253
261
  ".evo-hist-row-title{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}",
254
262
  ".evo-hist-row-meta{color:var(--dsw-alias-label-tertiary)}",
255
263
  ".evo-hist-row-summary{margin-top:2px;color:var(--dsw-alias-label-secondary);line-height:1.5}",
256
264
  ".evo-hist-actions{display:flex;flex:none;align-items:center;gap:6px}",
257
- ".evo-hist-capsule{flex:none;padding:1px 6px;border:.5px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-secondary)}",
265
+ ".evo-hist-capsule{flex:none;padding:1px 8px;border:0;border-radius:999px;background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-secondary)}",
258
266
  ".evo-hist-button{flex:none;font:inherit;padding:3px 10px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:inherit;cursor:pointer}",
259
267
  ".evo-hist-button:hover{background:var(--dsw-alias-interactive-bg-hover)}",
260
268
  ".evo-hist-button:focus-visible{outline:2px solid var(--dsw-alias-label-primary);outline-offset:-2px}",
@@ -299,6 +307,17 @@ window.__ModuleLoader__.load({
299
307
  if (skill.protectedBy !== null) return "state.protected";
300
308
  return skill.managed ? "state.managed" : "state.foreign";
301
309
  }
310
+ /**
311
+ * The one clause an action word needs: what the recorded artifact IS. It rides the action word's
312
+ * `title` instead of widening the row (a parenthetical in the sentence pushed time and size aside).
313
+ * @param face - copy plus the data callbacks.
314
+ * @param row - the version row whose action is being named.
315
+ * @returns the clause, or undefined for the actions that need none.
316
+ */
317
+ function actionNote(face, row) {
318
+ if (row.actionKind === "delete") return face.t("action.delete.note");
319
+ if (row.actionKind === "archive") return face.t("action.archive.note");
320
+ }
302
321
  /** The relative-time sentence for one row's bucket. */
303
322
  function ageText(face, age) {
304
323
  if (age.unit === "now") return face.t("time.now");
@@ -337,11 +356,15 @@ window.__ModuleLoader__.load({
337
356
  key: "v",
338
357
  className: "evo-hist-row-meta"
339
358
  }, "v" + String(row.v)),
340
- (0, react.createElement)("span", { key: "action" }, face.t("action." + row.actionKind)),
359
+ (0, react.createElement)("span", {
360
+ key: "action",
361
+ title: actionNote(face, row)
362
+ }, face.t("action." + row.actionKind)),
341
363
  (0, react.createElement)("span", {
342
364
  key: "time",
343
- className: "evo-hist-row-meta"
344
- }, localTime(row.at) + " · " + ageText(face, row.age))
365
+ className: "evo-hist-row-meta",
366
+ title: localTime(row.at)
367
+ }, ageText(face, row.age))
345
368
  ];
346
369
  const delta = deltaText(face, row);
347
370
  if (delta !== null) parts.push((0, react.createElement)("span", {
@@ -539,7 +562,7 @@ window.__ModuleLoader__.load({
539
562
  type: "button",
540
563
  className: "evo-hist-button",
541
564
  onClick: refresh
542
- }, face.t("refresh"))), (0, react.createElement)("p", { className: "evo-hist-hint" }, face.t("hint")), noteText === void 0 ? null : (0, react.createElement)("p", {
565
+ }, face.t("refresh"))), (0, react.createElement)("p", { className: "evo-hist-hint" }, face.t("hint")), selected === void 0 ? (0, react.createElement)("p", { className: "evo-hist-pane-empty" }, face.t(skills.length === 0 ? "empty.skills" : "empty.pick")) : null, noteText === void 0 ? null : (0, react.createElement)("p", {
543
566
  className: "evo-hist-note",
544
567
  "data-error": noteError ? "true" : void 0
545
568
  }, noteText), selected === void 0 ? null : loaded ? null : (0, react.createElement)("p", { className: "evo-hist-hint" }, face.t("loading")), loaded ? (0, react.createElement)("div", null, group(face, face.t("group.content"), payload.content, void 0, rowState), group(face, face.t("group.support"), payload.support, face.t("group.support.note"), rowState), payload.content.length === 0 && payload.support.length === 0 ? (0, react.createElement)("p", { className: "evo-hist-hint" }, face.t("empty.versions")) : null) : null));
@@ -564,24 +587,44 @@ window.__ModuleLoader__.load({
564
587
  /** Row order inside the global panel list (beside the platform's own rows). */
565
588
  const PANEL_ORDER = 35;
566
589
  /**
567
- * The row's icon: a platform-style line glyph, sized by the SIDEBAR's own contract.
590
+ * The row's icon: a platform-style line glyph, whose SIZE comes from the sidebar's contract and whose
591
+ * glyph BOX is the one the neighbouring rows draw at.
592
+ *
593
+ * Size: the platform hands every `sidebar.panellist` occupant `{ size, active }` (`SidebarRoot.tsx`
594
+ * `renderSlot('sidebar.panellist', { size: wide ? 16 : 18, active }, …)`), so the glyph takes its size
595
+ * from that prop; a glyph that sizes itself from CSS (`width: 100%`) resolves against an unsized flex
596
+ * box and fills the whole column.
568
597
  *
569
- * The platform hands every `sidebar.panellist` occupant `{ size, active }` (`SidebarRoot.tsx`
570
- * `renderSlot('sidebar.panellist', { size: wide ? 16 : 18, active }, …)`) and its own icons render at
571
- * that size (`IconGlobeOutline14 size={size}` in the sidebar's own spec). The neighbouring rows in a
572
- * shipped GUI take the OTHER route — they inject their entry into the sidebar's DOM with their own
573
- * `svg { width: 18px; height: 18px }` CSS — so 18px is the size a row glyph is expected to occupy.
574
- * A glyph that sizes itself from CSS (`width: 100%`) resolves against an unsized flex box and fills
575
- * the column: the size must come from the prop, with the neighbours' 18 as the fallback. The label
576
- * carries the accessible name, so the glyph stays decoration.
598
+ * Box: the three rows above (task board / SSH / skill centre) are NOT slot occupants — they inject
599
+ * plain DOM into the sidebar with their own CSS (`.entry{padding:0 10px;gap:8px}` plus a 24px icon box
600
+ * holding an 18px glyph, `shared/client/sidebar-entry-core.ts`), so their glyph ink starts 13px into
601
+ * the column and their label 42px. Inside the platform panel row (8px padding, 8px gap) those two
602
+ * lines are reached by a 26px box: `8 + (26 - 16) / 2 = 13` and `8 + 26 + 8 = 42`.
603
+ * Matching the neighbours is a CHOICE (the platform's own rows below — the workspace folders — sit at
604
+ * 8px and 32px); it keeps the four plugin rows reading as one column. The label carries the accessible
605
+ * name, so the glyph stays decoration.
577
606
  */
607
+ /** The glyph box in px: the neighbours' 24px icon box, placed to land on their two content lines. */
608
+ const GLYPH_BOX = 26;
609
+ /** The glyph's own coordinate system: every path below is drawn inside 16 units. */
610
+ const GLYPH_UNITS = 16;
611
+ /**
612
+ * Trim a computed viewBox number: three decimals stay readable and are far below one pixel.
613
+ * @param value - the raw number.
614
+ * @returns the value rounded to three decimals.
615
+ */
616
+ function round3(value) {
617
+ return Math.round(value * 1e3) / 1e3;
618
+ }
578
619
  function PanelIcon(props = {}) {
579
620
  const size = typeof props.size === "number" && props.size > 0 ? props.size : 18;
621
+ const units = round3(GLYPH_UNITS * GLYPH_BOX / size);
622
+ const inset = round3((units - GLYPH_UNITS) / 2);
580
623
  return (0, react.createElement)("svg", {
581
624
  "aria-hidden": "true",
582
- width: String(size),
583
- height: String(size),
584
- viewBox: "0 0 16 16",
625
+ width: String(GLYPH_BOX),
626
+ height: String(GLYPH_BOX),
627
+ viewBox: String(-inset) + " " + String(-inset) + " " + String(units) + " " + String(units),
585
628
  fill: "none",
586
629
  stroke: "currentColor",
587
630
  strokeWidth: "1.5",
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.13.2",
4
+ "version": "0.13.3",
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.13.2"
33
+ "@lmzhen/dsh-evolution-core": "^0.13.3"
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.13.2"
39
+ "@lmzhen/dsh-evolution-curator": "^0.13.3"
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.13.2",
45
- "@lmzhen/dsh-evolution-curator": "^0.13.2",
46
- "@lmzhen/dsh-evolution-io": "^0.13.2",
47
- "@lmzhen/dsh-evolution-io-node": "^0.13.2"
44
+ "@lmzhen/dsh-evolution-core": "^0.13.3",
45
+ "@lmzhen/dsh-evolution-curator": "^0.13.3",
46
+ "@lmzhen/dsh-evolution-io": "^0.13.3",
47
+ "@lmzhen/dsh-evolution-io-node": "^0.13.3"
48
48
  },
49
49
  "dsh": {
50
50
  "client": {