@lmzhen/dsh-evolution-skill-history 0.25.8 → 0.26.15

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 CHANGED
@@ -76,3 +76,9 @@ This package adds **no model-visible content**: no prompt section, no tool schem
76
76
  - **The six routes are loopback-only by design.** The fence is the family's shared loopback check, authoritative on the socket (`X-Forwarded-For` is never trusted), so a LAN-facing host that answers the platform's `/api` normally returns **403** here for any non-loopback client; reach the host over loopback (a local browser, or an SSH tunnel). Following the platform's `trustedHosts` instead is an open decision (v46 `决策 3`; the mechanism lives in `evolution-core`'s `http-routes.ts`).
77
77
  - **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.
78
78
  - **No occupancy row.** `.history` growth is documented in the evolution-core README; showing the size in the panel would promise a cleanup path the family has not designed (a whole-library reference count belongs to the curator).
79
+
80
+ ## 单元与撤销(本包负责的面)
81
+
82
+ - 面板行的 undoable 是**单元属性**:同 unitId 的记录同值;路标(有 skill、无 moves)返回 undefined ⇒ 路由侧**省略该键**,绝不为 false;聚合时跳过路标,避免它的 hash 污染兄弟片(src/unit-undoable.ts)。
83
+ - 面板不拥有规则:哪条记录持正文、哪条已是当前,由 core 的 partitionVersions 与该行的哈希决定;本包只把结论送上 wire。
84
+ - 面向读者的完整说明见仓库 docs/units-and-undo.md;撤销的决策层在 evolution-curator(src/unit-undo-plan.ts)。
package/lib/index.js CHANGED
@@ -1,4 +1,39 @@
1
- import { contentHash, displayBodyOf, elapsedSince, errorText, isLoopbackRequest, latestVersionAt, partitionVersions, readJsonObject, skillRowCells, textDiffFacts, versionActionKind, versionRowCells, writeJson } from "@lmzhen/dsh-evolution-core";
1
+ import { contentHash, displayBodyOf, elapsedSince, errorText, isLandmark, isLoopbackRequest, latestVersionAt, partitionVersions, readJsonObject, skillRowCells, textDiffFacts, versionActionKind, versionRowCells, writeJson } from "@lmzhen/dsh-evolution-core";
2
+ //#region lib/types/unit-undoable.js
3
+ /**
4
+ * G3b: undoability is a property of the UNIT, not of one entry.
5
+ *
6
+ * Every entry of a unit shares the same live body, so "is this undoable" must answer the same for all
7
+ * of them; computing it per entry made the body row say `false` (already current) while a support row
8
+ * of the SAME transaction said `true` — two entrances to one unit in one panel.
9
+ *
10
+ * Entries without a unit id keep the per-entry answer (they are their own singleton unit).
11
+ */
12
+ /** @param members - every entry that shares the unit (content and support chains together).
13
+ * @param liveHash - hash of the live body, or null when the skill is not readable.
14
+ * @returns one flag per unit id; unknown ids fall back to the per-entry rule. */
15
+ function unitUndoableFlags(members, liveHash) {
16
+ const flags = /* @__PURE__ */ new Map();
17
+ for (const entry of members) {
18
+ if (entry.unitId === void 0) continue;
19
+ if (isLandmark(entry)) continue;
20
+ const differs = entry.hash !== liveHash;
21
+ flags.set(entry.unitId, (flags.get(entry.unitId) ?? false) || differs);
22
+ }
23
+ return flags;
24
+ }
25
+ /** @param entry - the entry being rendered. @param flags - the unit flags from {@link unitUndoableFlags}.
26
+ * @param liveHash - hash of the live body. @returns the row's `undoable` value. */
27
+ function undoableFor(entry, flags, liveHash) {
28
+ if (isLandmark(entry)) return void 0;
29
+ const unit = entry.unitId;
30
+ if (unit !== void 0) {
31
+ const flagged = flags.get(unit);
32
+ if (flagged !== void 0) return flagged;
33
+ }
34
+ return entry.hash !== liveHash;
35
+ }
36
+ //#endregion
2
37
  //#region lib/types/routes.js
3
38
  /**
4
39
  * The skill-history HTTP surface: six loopback routes over the curator's read/write seam (five reads plus the one write).
@@ -125,13 +160,15 @@ function makeSkillHistoryRoutes(services) {
125
160
  ...!withDelta || previous === void 0 ? {} : { charsDelta: entry.chars - previous.chars }
126
161
  };
127
162
  };
163
+ const unitFlags = unitUndoableFlags([...groups.content, ...groups.support], liveHash);
128
164
  writeJson(res, 200, {
129
165
  ok: true,
130
166
  data: {
131
167
  content: groups.content.map((entry, index) => {
168
+ const undoable = undoableFor(entry, unitFlags, liveHash);
132
169
  const row = {
133
170
  ...decorate(entry, index, groups.content, true),
134
- undoable: entry.hash !== liveHash
171
+ ...undoable === void 0 ? {} : { undoable }
135
172
  };
136
173
  return {
137
174
  ...row,
@@ -0,0 +1,18 @@
1
+ /**
2
+ * G3b: undoability is a property of the UNIT, not of one entry.
3
+ *
4
+ * Every entry of a unit shares the same live body, so "is this undoable" must answer the same for all
5
+ * of them; computing it per entry made the body row say `false` (already current) while a support row
6
+ * of the SAME transaction said `true` — two entrances to one unit in one panel.
7
+ *
8
+ * Entries without a unit id keep the per-entry answer (they are their own singleton unit).
9
+ */
10
+ import { type SkillVersion } from '@lmzhen/dsh-evolution-core';
11
+ /** @param members - every entry that shares the unit (content and support chains together).
12
+ * @param liveHash - hash of the live body, or null when the skill is not readable.
13
+ * @returns one flag per unit id; unknown ids fall back to the per-entry rule. */
14
+ export declare function unitUndoableFlags(members: readonly SkillVersion[], liveHash: string | null): Map<string, boolean>;
15
+ /** @param entry - the entry being rendered. @param flags - the unit flags from {@link unitUndoableFlags}.
16
+ * @param liveHash - hash of the live body. @returns the row's `undoable` value. */
17
+ export declare function undoableFor(entry: SkillVersion, flags: Map<string, boolean>, liveHash: string | null): boolean | undefined;
18
+ //# sourceMappingURL=unit-undoable.d.ts.map
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.25.8",
4
+ "version": "0.26.15",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -30,23 +30,23 @@
30
30
  ],
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@lmzhen/dsh-evolution-core": "^0.25.8"
33
+ "@lmzhen/dsh-evolution-core": "^0.26.15"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@deepseek-ai/cordis": "^4.0.1",
37
37
  "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
38
38
  "react": "^18.2.0",
39
- "@lmzhen/dsh-evolution-curator": "^0.25.8"
39
+ "@lmzhen/dsh-evolution-curator": "^0.26.15"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@deepseek-ai/cordis": "^4.0.1",
43
43
  "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
44
44
  "@types/react": "~18.3.1",
45
45
  "react": "^18.2.0",
46
- "@lmzhen/dsh-evolution-core": "^0.25.8",
47
- "@lmzhen/dsh-evolution-curator": "^0.25.8",
48
- "@lmzhen/dsh-evolution-io": "^0.25.8",
49
- "@lmzhen/dsh-evolution-io-node": "^0.25.8"
46
+ "@lmzhen/dsh-evolution-core": "^0.26.15",
47
+ "@lmzhen/dsh-evolution-curator": "^0.26.15",
48
+ "@lmzhen/dsh-evolution-io": "^0.26.15",
49
+ "@lmzhen/dsh-evolution-io-node": "^0.26.15"
50
50
  },
51
51
  "dsh": {
52
52
  "client": {