dsh-plugin-message-edit 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  English | [简体中文](README.md)
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-message-edit?color=cb3837&logo=npm)](https://www.npmjs.com/package/dsh-plugin-message-edit)
6
+ [![CI](https://github.com/SpookySandwich/dsh-plugin-message-edit/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/SpookySandwich/dsh-plugin-message-edit/actions/workflows/ci.yml)
5
7
  [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
- [![dsh](https://img.shields.io/badge/dsh-0.1.0--rc.7-4b8dff)](https://github.com/deepseek-ai/deepseek-harness)
8
+ [![dsh](https://img.shields.io/badge/dsh-0.1.2--rc.1-4b8dff)](https://github.com/deepseek-ai/deepseek-harness)
7
9
  [![stars](https://img.shields.io/github/stars/SpookySandwich/dsh-plugin-message-edit?style=flat&label=stars)](https://github.com/SpookySandwich/dsh-plugin-message-edit/stargazers)
8
10
 
9
11
  Edit a message you already sent and the conversation **rewinds and branches** from that point, the way ChatGPT, Claude and DeepSeek all do it. The old version is not overwritten — a `‹ 2/4 ›` counter appears under the bubble, and a Versions tab draws the whole tree.
@@ -70,9 +72,16 @@ For technical details and developer guides, see:
70
72
 
71
73
  ## Compatibility
72
74
 
75
+ Version `1.0.1` has been verified in an isolated DSH `0.1.2-rc.1` Web environment:
76
+ plugin loading, images, edit/retry, nested branches, and restored sessions. Model
77
+ responses use a local test adapter; remote model services were not exercised.
78
+
79
+ The compatibility layer retains the older `events` / `seedLength` interfaces,
80
+ covered by automated tests. Later DSH releases need separate verification.
81
+ Restart DSH after updating the plugin.
82
+
73
83
  Coexists with [dsh-plugin-smooth-stream](https://github.com/SpookySandwich/dsh-plugin-smooth-stream) and [dsh-plugin-rollout-scout](https://github.com/SpookySandwich/dsh-plugin-rollout-scout).
74
84
 
75
85
  ## License
76
86
 
77
87
  MIT © SpookySandwich. Portions of the host half derive from dsh-message-edit (MIT © Moeblack).
78
-
package/README.md CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  [English](README.en.md) | 简体中文
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-message-edit?color=cb3837&logo=npm)](https://www.npmjs.com/package/dsh-plugin-message-edit)
6
+ [![CI](https://github.com/SpookySandwich/dsh-plugin-message-edit/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/SpookySandwich/dsh-plugin-message-edit/actions/workflows/ci.yml)
5
7
  [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
- [![dsh](https://img.shields.io/badge/dsh-0.1.0--rc.7-4b8dff)](https://github.com/deepseek-ai/deepseek-harness)
8
+ [![dsh](https://img.shields.io/badge/dsh-0.1.2--rc.1-4b8dff)](https://github.com/deepseek-ai/deepseek-harness)
7
9
  [![stars](https://img.shields.io/github/stars/SpookySandwich/dsh-plugin-message-edit?style=flat&label=stars)](https://github.com/SpookySandwich/dsh-plugin-message-edit/stargazers)
8
10
 
9
11
  编辑一条已经发出的消息,对话会从那一刻 **真正回溯并分叉**——和 ChatGPT、Claude、DeepSeek 的做法一致。旧版本不会被覆盖:气泡下方出现 `‹ 2/4 ›` 计数,「版本」标签页则画出整棵树。
@@ -70,9 +72,12 @@ DSH 的会话是仅追加的事件日志,本身不支持会话内分支,因
70
72
 
71
73
  ## 兼容性
72
74
 
75
+ `1.0.1` 已在 DSH `0.1.2-rc.1` 的隔离 Web 环境验证:插件加载、图片显示、编辑与重试、深层分支,以及持久会话的恢复读取。模型回复使用本地测试实现,未调用远程模型服务。
76
+
77
+ 兼容层保留了旧版 `events` / `seedLength` 接口支持,并由自动测试覆盖。DSH 仍在快速迭代,尚未验证的后续版本不在此保证范围内。更新插件后请重启 DSH。
78
+
73
79
  可与 [dsh-plugin-smooth-stream](https://github.com/SpookySandwich/dsh-plugin-smooth-stream)、[dsh-plugin-rollout-scout](https://github.com/SpookySandwich/dsh-plugin-rollout-scout) 共存。
74
80
 
75
81
  ## 许可
76
82
 
77
83
  MIT © SpookySandwich。宿主端部分逻辑源自 dsh-message-edit(MIT © Moeblack)。
78
-
@@ -1,156 +1,164 @@
1
- # Architecture Overview
2
-
3
- `dsh-plugin-message-edit` provides ChatGPT/Claude-style conversation branching and message editing for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness).
4
-
5
- Because DSH session event logs are append-only without native in-session branching, this plugin splits responsibilities across a **Node.js Host Service** and a **Browser/Web Client**.
6
-
7
- ---
8
-
9
- ## 1. System Components
10
-
11
- ```
12
- ┌─────────────────────────────────────────────────────────────┐
13
- │ DSH Desktop / Web │
14
- │ │
15
- │ ┌──────────────────────┐ ┌──────────────────────┐ │
16
- │ │ Client Half │ HTTP │ Host Half │ │
17
- │ │ (plugin.client.js) │<───────>│ (lib/index.js) │ │
18
- │ └──────────┬───────────┘ └──────────┬───────────┘ │
19
- │ │ │ │
20
- │ Shadow User Message Cordis Services: │
21
- │ Versions Tab (Graph) - sessions │
22
- │ Settings UI - agents │
23
- │ - webServer │
24
- │ - sessionPersistence │
25
- └─────────────────────────────────────────────────────────────┘
26
- ```
27
-
28
- ### 1.1 Host Half (`lib/index.js`)
29
- - Runs in the Node.js backend process via Cordis lifecycle injection.
30
- - Registers the `/message-tree` HTTP route on `ctx.webServer`.
31
- - Owns branch creation transactions (`POST /message-tree`):
32
- 1. Truncates parent events up to the target turn.
33
- 2. Seeds a new DSH session with the prefix events.
34
- 3. Appends a durable `message-tree/version` marker with `ignorable: true`.
35
- 4. Submits the edited prompt into the new session.
36
- - Owns graph queries (`GET /message-tree?sessionId=...`):
37
- - Traverses the session family DAG.
38
- - Recovers deleted/ghost ancestors from surviving descendants' event logs.
39
- - Extracts turn event boundaries for turn-level rendering.
40
-
41
- ### 1.2 Client Half (`plugin.client.js`)
42
- - Runs in the browser / renderer process.
43
- - Injects a shadowed `user` message renderer at priority `-1` to add the edit/copy/retry toolbar and `‹ n/m ›` version ring without modifying agent responses, tool calls, or reasoning blocks.
44
- - Adds the **Versions** tab (`VIEW_ORDER: 16`) providing an interactive pan/zoom graph with spring physics.
45
- - Adds settings options in **Settings Message Edit** with live layout switching (ChatGPT, DeepSeek, Claude styles).
46
-
47
- ---
48
-
49
- ## 2. Durable Storage Model
50
-
51
- DSH sessions are immutable append-only logs. When branching:
52
-
53
- 1. **Seed Inheritance**: A new session is initialized whose log begins with an exact clone of the parent's event log up to the start of the edited turn (`seedLength`).
54
- 2. **Durable Marker**: The host appends a custom event:
55
- ```json
56
- {
57
- "type": "message-tree/version",
58
- "data": {
59
- "schemaVersion": 1,
60
- "effect": {
61
- "operation": "edit",
62
- "targetTurn": 1,
63
- "targetEventSeq": 5,
64
- "before": "Original message text",
65
- "after": "Edited message text"
66
- },
67
- "inverse": {
68
- "kind": "restore-version",
69
- "sessionId": "parent-session-id"
70
- }
71
- }
72
- }
73
- ```
74
- 3. **`ignorable` Flag**: Custom plugin event types fall outside DSH's core schema. The event envelope must set `ignorable: true`; otherwise, DSH's built-in event reader will reject the entire session log.
75
-
76
- ---
77
-
78
- ## 3. HTTP API
79
-
80
- ### `GET /message-tree?sessionId={id}`
81
- Returns the entire conversation family surrounding the requested session.
82
-
83
- **Response Schema:**
84
- ```json
85
- {
86
- "sessionId": "current-session-id",
87
- "versions": [
88
- {
89
- "sessionId": "session-a",
90
- "createdAt": 1724334000000,
91
- "depth": 0,
92
- "current": false,
93
- "onCurrentPath": true,
94
- "turns": [
95
- { "turn": 1, "text": "Hello", "time": 1724334001000 },
96
- { "turn": 2, "text": "Tell me more", "time": 1724334005000 }
97
- ]
98
- },
99
- {
100
- "sessionId": "session-b",
101
- "parentSessionId": "session-a",
102
- "createdAt": 1724334020000,
103
- "depth": 1,
104
- "current": true,
105
- "onCurrentPath": true,
106
- "operation": "edit",
107
- "targetTurn": 1,
108
- "before": "Hello",
109
- "after": "Hello world",
110
- "turns": [
111
- { "turn": 1, "text": "Hello world", "time": 1724334021000 },
112
- { "turn": 2, "text": "What is next?", "time": 1724334025000 }
113
- ]
114
- }
115
- ]
116
- }
117
- ```
118
-
119
- ### `POST /message-tree`
120
- Performs branch creation or reactivation.
121
-
122
- - **`edit`**: Rewinds to before the specified user turn, creates a new branched session, appends a durable `message-tree/version` marker, and submits the replacement prompt.
123
- - **`retry`**: Rewinds to before the target turn, creates a child session, and replays the original user prompt.
124
- - **`activate`**: Unarchives an archived version session via the host registry queue so the client can navigate to it.
125
-
126
- ---
127
-
128
- ## 4. Performance & In-Memory Caching
129
-
130
- 1. **Host-Side Parsed Session Cache (`sessionParsedCache`)**:
131
- - Parses turn boundaries (`extractTurns`) and version headers once per immutable event sequence.
132
- - Bounded to 500 session entries with key invalidation on live event count / disk mtime changes.
133
- - Subsequent `tree()` queries across siblings in the family hit in-memory cache in sub-millisecond time.
134
-
135
- 2. **Client-Side Family SWR Store (`treeStore`)**:
136
- - Maps every non-deleted branch in a tree to the shared family structure upon fetch.
137
- - Switching between sibling branches (`‹ n/m ›` or Versions view) is 100% synchronous (0ms lag, zero indicator flicker).
138
- - Uses monotonic request timestamps to prevent race-condition overwrites from out-of-order responses.
139
- - Optimistically seeds newly created edit/retry branches before navigation.
140
-
141
- ---
142
-
143
- ## 5. Security and Error Resilience
144
-
145
- - **Ignorable Event Envelope**: `ignorable: true` ensures foreign event markers do not crash the core DSH log parser.
146
- - **Fail-Safe Mutation Recovery**: Transaction reversals (`child.dispose()`) on failures prevent dangling session artifacts.
147
- - **Memory Bounded Stores**: LRU bounds (500 sessions) prevent unbounded memory growth in long-running processes.
148
-
149
- ## 6. Naming & Namespaces
150
-
151
- - **NPM Package**: `dsh-plugin-message-edit`
152
- - **Cordis Service Name**: `message-tree`
153
- - **HTTP Path**: `/message-tree`
154
- - **Durable Event Type**: `message-tree/version`
155
-
156
- > The package uses `dsh-plugin-message-edit` for discovery, but retains `message-tree` in routes, cordis IDs, and event types to prevent collisions with prior third-party plugins (such as `dsh-message-edit`) and ensure seamless side-by-side operation.
1
+ # Architecture Overview
2
+
3
+ `dsh-plugin-message-edit` provides ChatGPT/Claude-style conversation branching and message editing for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness).
4
+
5
+ Because DSH session event logs are append-only without native in-session branching, this plugin splits responsibilities across a **Node.js Host Service** and a **Browser/Web Client**.
6
+
7
+ ---
8
+
9
+ ## 1. System Components
10
+
11
+ ```
12
+ ┌─────────────────────────────────────────────────────────────┐
13
+ │ DSH Desktop / Web │
14
+ │ │
15
+ │ ┌──────────────────────┐ ┌──────────────────────┐ │
16
+ │ │ Client Half │ HTTP │ Host Half │ │
17
+ │ │ (plugin.client.js) │<───────>│ (lib/index.js) │ │
18
+ │ └──────────┬───────────┘ └──────────┬───────────┘ │
19
+ │ │ │ │
20
+ │ Shadow User Message Cordis Services: │
21
+ │ Versions Tab (Graph) - sessions │
22
+ │ Settings UI - agents │
23
+ │ - webServer │
24
+ │ - sessionPersistence │
25
+ └─────────────────────────────────────────────────────────────┘
26
+ ```
27
+
28
+ ### 1.1 Host Half (`lib/index.js`)
29
+ - Runs in the Node.js backend process via Cordis lifecycle injection.
30
+ - Normalizes DSH sessions through `lib/session-record.js`: current live sessions
31
+ expose `snapshotEvents()`, query snapshots carry their header in `session`,
32
+ and older records expose `events` / `header`. Invalid logs fail explicitly.
33
+ - Current DSH branches use `meta.isSeeded` plus `inheritedEventCount`; older
34
+ hosts use `meta.seedLength`. The normalized inherited cut prevents a nested
35
+ branch from mistaking an inherited version marker for its own marker.
36
+ - Registers the `/message-tree` HTTP route on `ctx.webServer`.
37
+ - Owns branch creation transactions (`POST /message-tree`):
38
+ 1. Truncates parent events up to the target turn.
39
+ 2. Seeds a new DSH session with the prefix events.
40
+ 3. Appends a durable `message-tree/version` marker with `ignorable: true`.
41
+ 4. Submits the edited prompt into the new session.
42
+ - Owns graph queries (`GET /message-tree?sessionId=...`):
43
+ - Traverses the session family DAG.
44
+ - Recovers deleted/ghost ancestors from surviving descendants' event logs.
45
+ - Extracts turn event boundaries for turn-level rendering.
46
+
47
+ ### 1.2 Client Half (`plugin.client.js`)
48
+ - Runs in the browser / renderer process.
49
+ - Injects a shadowed `user` message renderer at priority `-1` to add the edit/copy/retry toolbar and `‹ n/m ›` version ring without modifying agent responses, tool calls, or reasoning blocks.
50
+ - Adds the **Versions** tab (`VIEW_ORDER: 16`) providing an interactive pan/zoom graph with spring physics.
51
+ - Adds settings options in **Settings Message Edit** with live layout switching (ChatGPT, DeepSeek, Claude styles).
52
+
53
+ ---
54
+
55
+ ## 2. Durable Storage Model
56
+
57
+ DSH sessions are immutable append-only logs. When branching:
58
+
59
+ 1. **Seed Inheritance**: A new session is initialized whose log begins with an exact clone of the parent's event log up to the start of the edited turn (`seedLength`).
60
+ 2. **Durable Marker**: The host appends a custom event:
61
+ ```json
62
+ {
63
+ "type": "message-tree/version",
64
+ "data": {
65
+ "schemaVersion": 1,
66
+ "effect": {
67
+ "operation": "edit",
68
+ "targetTurn": 1,
69
+ "targetEventSeq": 5,
70
+ "before": "Original message text",
71
+ "after": "Edited message text"
72
+ },
73
+ "inverse": {
74
+ "kind": "restore-version",
75
+ "sessionId": "parent-session-id"
76
+ }
77
+ }
78
+ }
79
+ ```
80
+ 3. **`ignorable` Flag**: Custom plugin event types fall outside DSH's core schema. The event envelope must set `ignorable: true`; otherwise, DSH's built-in event reader will reject the entire session log.
81
+
82
+ ---
83
+
84
+ ## 3. HTTP API
85
+
86
+ ### `GET /message-tree?sessionId={id}`
87
+ Returns the entire conversation family surrounding the requested session.
88
+
89
+ **Response Schema:**
90
+ ```json
91
+ {
92
+ "sessionId": "current-session-id",
93
+ "versions": [
94
+ {
95
+ "sessionId": "session-a",
96
+ "createdAt": 1724334000000,
97
+ "depth": 0,
98
+ "current": false,
99
+ "onCurrentPath": true,
100
+ "turns": [
101
+ { "turn": 1, "text": "Hello", "time": 1724334001000 },
102
+ { "turn": 2, "text": "Tell me more", "time": 1724334005000 }
103
+ ]
104
+ },
105
+ {
106
+ "sessionId": "session-b",
107
+ "parentSessionId": "session-a",
108
+ "createdAt": 1724334020000,
109
+ "depth": 1,
110
+ "current": true,
111
+ "onCurrentPath": true,
112
+ "operation": "edit",
113
+ "targetTurn": 1,
114
+ "before": "Hello",
115
+ "after": "Hello world",
116
+ "turns": [
117
+ { "turn": 1, "text": "Hello world", "time": 1724334021000 },
118
+ { "turn": 2, "text": "What is next?", "time": 1724334025000 }
119
+ ]
120
+ }
121
+ ]
122
+ }
123
+ ```
124
+
125
+ ### `POST /message-tree`
126
+ Performs branch creation or reactivation.
127
+
128
+ - **`edit`**: Rewinds to before the specified user turn, creates a new branched session, appends a durable `message-tree/version` marker, and submits the replacement prompt.
129
+ - **`retry`**: Rewinds to before the target turn, creates a child session, and replays the original user prompt.
130
+ - **`activate`**: Unarchives an archived version session via the host registry queue so the client can navigate to it.
131
+
132
+ ---
133
+
134
+ ## 4. Performance & In-Memory Caching
135
+
136
+ 1. **Host-Side Parsed Session Cache (`sessionParsedCache`)**:
137
+ - Parses turn boundaries (`extractTurns`) and version headers once per immutable event sequence.
138
+ - Bounded to 500 entries per plugin context. Live keys use session identity
139
+ and event count; unchanged modern logs do not need a new snapshot.
140
+ - Cold logs are read before comparing their event count. Creation timestamps
141
+ cannot invalidate append-only history and must not be used as revisions.
142
+
143
+ 2. **Client-Side Family SWR Store (`treeStore`)**:
144
+ - Maps every non-deleted branch in a tree to the shared family structure upon fetch.
145
+ - Switching between sibling branches (`‹ n/m ›` or Versions view) is 100% synchronous (0ms lag, zero indicator flicker).
146
+ - Uses monotonic request timestamps to prevent race-condition overwrites from out-of-order responses.
147
+ - Optimistically seeds newly created edit/retry branches before navigation.
148
+
149
+ ---
150
+
151
+ ## 5. Security and Error Resilience
152
+
153
+ - **Ignorable Event Envelope**: `ignorable: true` ensures foreign event markers do not crash the core DSH log parser.
154
+ - **Fail-Safe Mutation Recovery**: Transaction reversals (`child.dispose()`) on failures prevent dangling session artifacts.
155
+ - **Memory Bounded Stores**: LRU bounds (500 sessions) prevent unbounded memory growth in long-running processes.
156
+
157
+ ## 6. Naming & Namespaces
158
+
159
+ - **NPM Package**: `dsh-plugin-message-edit`
160
+ - **Cordis Service Name**: `message-tree`
161
+ - **HTTP Path**: `/message-tree`
162
+ - **Durable Event Type**: `message-tree/version`
163
+
164
+ > The package uses `dsh-plugin-message-edit` for discovery, but retains `message-tree` in routes, cordis IDs, and event types to prevent collisions with prior third-party plugins (such as `dsh-message-edit`) and ensure seamless side-by-side operation.
@@ -1,75 +1,135 @@
1
- # Development & Testing Guide
2
-
3
- This guide covers building, testing, packaging, and installing `dsh-plugin-message-edit`.
4
-
5
- ---
6
-
7
- ## 1. Repository Structure
8
-
9
- ```
10
- dsh-plugin-message-edit/
11
- ├── lib/
12
- │ ├── index.js # Host-side Cordis plugin (routes, session log processing)
13
- │ ├── tree-logic.js # Pure tree algorithms (shared with client and tests)
14
- │ └── client.js # Generated client bundle (wrapped from plugin.client.js)
15
- ├── plugin.client.js # Source client-side UI and React components
16
- ├── scripts/
17
- │ └── build-client.mjs # Build script wrapping plugin.client.js into lib/client.js
18
- ├── test/
19
- │ └── tree.test.mjs # Automated test suite (36+ unit tests)
20
- ├── cordis.patch.yml # Service dependencies and injection metadata
21
- ├── docs/ # Technical architecture and data model documentation
22
- └── package.json
23
- ```
24
-
25
- ---
26
-
27
- ## 2. Build Pipeline
28
-
29
- The client component [`plugin.client.js`](file:///D:/dsh-plugin-message-edit/plugin.client.js) is written in browser-compatible JavaScript. Before distribution or testing, it is wrapped with a Cordis module preamble into [`lib/client.js`](file:///D:/dsh-plugin-message-edit/lib/client.js).
30
-
31
- ### Build Client
32
- ```bash
33
- npm run build
34
- ```
35
- Executes `node scripts/build-client.mjs` to regenerate `lib/client.js`.
36
-
37
- ### Check Build Integrity
38
- ```bash
39
- node scripts/build-client.mjs --check
40
- ```
41
- Exits with code 1 if `lib/client.js` is out of date relative to `plugin.client.js`.
42
-
43
- ---
44
-
45
- ## 3. Testing
46
-
47
- The project includes an automated test suite verifying tree construction, sibling fan-out, ghost recovery, active path calculation, and ring index calculation.
48
-
49
- ```bash
50
- npm test
51
- ```
52
- Runs the build check and executes `test/tree.test.mjs`.
53
-
54
- To add new tests, edit [`test/tree.test.mjs`](file:///D:/dsh-plugin-message-edit/test/tree.test.mjs).
55
-
56
- ---
57
-
58
- ## 4. Local Installation into DSH Desktop
59
-
60
- ### Step 1: Build and Package
61
- ```bash
62
- npm run build
63
- npm pack
64
- ```
65
- This produces a tarball: `dsh-plugin-message-edit-0.1.0.tgz`.
66
-
67
- ### Step 2: Install into DSH Profile
68
- To install into the DSH Desktop profile:
69
- ```bash
70
- dsh plugin --profile desktop add file:/path/to/dsh-plugin-message-edit-0.1.0.tgz
71
- ```
72
- Or sync files directly into `~/.dsh/profiles/desktop/node_modules/dsh-plugin-message-edit/`.
73
-
74
- ### Step 3: Restart DSH Desktop
75
- Restart DSH Desktop to reload the host-side plugin in the server process and mount the updated client interface.
1
+ # Development & Testing Guide
2
+
3
+ This guide covers building, testing, packaging, and installing `dsh-plugin-message-edit`.
4
+
5
+ ---
6
+
7
+ ## 1. Repository Structure
8
+
9
+ ```
10
+ dsh-plugin-message-edit/
11
+ ├── lib/
12
+ │ ├── index.js # Host-side Cordis plugin (routes, session log processing)
13
+ │ ├── tree-logic.js # Pure tree algorithms (shared with client and tests)
14
+ │ └── client.js # Generated client bundle (wrapped from plugin.client.js)
15
+ ├── plugin.client.js # Source client-side UI and React components
16
+ ├── scripts/
17
+ │ └── build-client.mjs # Build script wrapping plugin.client.js into lib/client.js
18
+ ├── test/
19
+ │ └── tree.test.mjs # Automated test suite (36+ unit tests)
20
+ ├── cordis.patch.yml # Service dependencies and injection metadata
21
+ ├── docs/ # Technical architecture and data model documentation
22
+ └── package.json
23
+ ```
24
+
25
+ ---
26
+
27
+ ## 2. Build Pipeline
28
+
29
+ The client component [`plugin.client.js`](file:///D:/dsh-plugin-message-edit/plugin.client.js) is written in browser-compatible JavaScript. Before distribution or testing, it is wrapped with a Cordis module preamble into [`lib/client.js`](file:///D:/dsh-plugin-message-edit/lib/client.js).
30
+
31
+ ### Build Client
32
+ ```bash
33
+ npm run build
34
+ ```
35
+ Executes `node scripts/build-client.mjs` to regenerate `lib/client.js`.
36
+
37
+ ### Check Build Integrity
38
+ ```bash
39
+ node scripts/build-client.mjs --check
40
+ ```
41
+ Exits with code 1 if `lib/client.js` is out of date relative to `plugin.client.js`.
42
+
43
+ ---
44
+
45
+ ## 3. Testing
46
+
47
+ The project includes an automated test suite verifying tree construction, sibling fan-out, ghost recovery, active path calculation, and ring index calculation.
48
+
49
+ ```bash
50
+ npm test
51
+ ```
52
+ Automatically builds the client first, checks that it matches the source, then
53
+ runs the Node test runner. Install development dependencies with `npm ci` on a
54
+ fresh checkout before running tests (Node 22.19+ or Node 24).
55
+
56
+ - `test/tree.test.mjs`: 36 assertions covering branch and version-tree behavior.
57
+ - `test/client-images.test.mjs`: loads the generated client through its module
58
+ loader, mounts its registered user-message component using React and jsdom,
59
+ and checks image delegation, multiple/image-only messages, old-host fallback,
60
+ text-only messages, and entering/cancelling an edit. Host services and the image
61
+ gallery are test doubles; these are not full DSH integration tests.
62
+ - `test/client-registration.test.mjs`: checks module dependencies and visible
63
+ failures when services or registrations are unavailable.
64
+ - `test/session-record.test.mjs` and `test/host-compatibility.test.mjs`: cover
65
+ legacy/current session shapes, live/resumed edit requests, retained images,
66
+ retry ancestry, nested version markers, and cache invalidation.
67
+
68
+ GitHub Actions runs these checks for pull requests and branch pushes, on
69
+ Linux (Node 22 and 24) and Windows (Node 22). It also runs:
70
+
71
+ ```bash
72
+ npm run check:package
73
+ ```
74
+
75
+ This creates a real npm archive in a temporary directory, verifies that runtime
76
+ entry points are present and development-only directories are absent, checks
77
+ the host entry's syntax, and removes the temporary archive. `npm pack` and
78
+ `npm publish` now build the client automatically through `prepack`, preventing
79
+ an old or missing generated client from being shipped.
80
+
81
+ The workflow needs to be pushed to GitHub to run there. Making its checks
82
+ mandatory before merging is a separate repository ruleset/branch-protection
83
+ setting; adding the workflow alone does not block the Merge button.
84
+
85
+ Before accepting an image-rendering change, also check it in a running DSH:
86
+ send text with one/multiple images and an image-only message, open an image in
87
+ the native viewer, enter/cancel an edit, then verify the original attachments
88
+ survive an edit submission. CI's gallery double cannot verify native image
89
+ loading, lightbox behavior, or compatibility with DSH's module injection.
90
+
91
+ To add new tests, edit [`test/tree.test.mjs`](file:///D:/dsh-plugin-message-edit/test/tree.test.mjs).
92
+
93
+ ### Optional real DSH acceptance
94
+
95
+ `test/fixtures/dsh-acceptance.mjs` is an offline model adapter and live/cold
96
+ session fixture for an installed official DSH `0.1.2-rc.1` runtime. Mount it
97
+ only in a new temporary home whose name contains `message-edit-dsh-qa-`.
98
+ Set `DSH_HOME` to that home and `DSH_QA_MODULES` to the official runtime's
99
+ `node_modules` directory. Use a separate Web profile with the base/Web bundles,
100
+ a built copy of this plugin, and a loader entry for the fixture. Never point
101
+ this fixture at an existing user's DSH home.
102
+
103
+ Once the isolated server prints its URL, run:
104
+
105
+ ```bash
106
+ node scripts/verify-dsh-acceptance.mjs http://127.0.0.1:61587
107
+ ```
108
+
109
+ The verifier exercises real HTTP edit/retry operations, local model execution,
110
+ image retention, unchanged source messages, and nested branch markers. Restart
111
+ the same isolated server and repeat to exercise persisted branches. The fixture
112
+ adds `/qa/state` and `/qa/followup` endpoints solely for this disposable test.
113
+ Browser acceptance additionally checks the settings entry, version switcher,
114
+ thumbnails, and native original-image viewer. No remote API key is needed.
115
+
116
+ ---
117
+
118
+ ## 4. Local Installation into DSH Desktop
119
+
120
+ ### Step 1: Build and Package
121
+ ```bash
122
+ npm run build
123
+ npm pack
124
+ ```
125
+ This produces a tarball: `dsh-plugin-message-edit-0.1.0.tgz`.
126
+
127
+ ### Step 2: Install into DSH Profile
128
+ To install into the DSH Desktop profile:
129
+ ```bash
130
+ dsh plugin --profile desktop add file:/path/to/dsh-plugin-message-edit-0.1.0.tgz
131
+ ```
132
+ Or sync files directly into `~/.dsh/profiles/desktop/node_modules/dsh-plugin-message-edit/`.
133
+
134
+ ### Step 3: Restart DSH Desktop
135
+ Restart DSH Desktop to reload the host-side plugin in the server process and mount the updated client interface.