@grapecity-software/dsh-spreadjs-editor 0.2.4 → 0.2.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Release Notes / 版本记录
2
2
 
3
+ ## 0.2.6
4
+
5
+ ### 中文
6
+
7
+ **放宽 DSH 兼容范围,修复保存与恢复中的数据丢失风险**
8
+
9
+ - DSH 兼容声明和 UI peer 范围调整为 `>=0.2.1-rc.3 <0.3.0-0`,开发依赖继续固定在 `0.2.1-rc.4`。同一范围内的 patch 和 RC 不再要求逐个发布插件;扩展到新的 DSH 次版本前先验证 API 和运行行为。
10
+ - 保存期间继续编辑时,新改动仍标为未保存;关闭或切换标签页后,稍后完成的旧保存不会清除新留下的恢复快照。
11
+ - 另存为只写入了较早内容时,保留当前文件和后续改动,并提示继续保存;文档已关闭或切走时,不再因旧任务完成而自动跳转。
12
+ - 串行载入同一工作簿,避免旧加载最后完成并覆盖新内容。载入失败或导出期间工作簿发生切换、重新加载时,拒绝写入过时内容。
13
+ - 在同一宿主进程内,串行执行同一文件的冲突检查与写回,拒绝基于旧文件版本的后续保存;读取文件状态失败时停止写入。
14
+ - 保留延迟加载或临时重载的桥选择,避免其他桥先注册时清空用户设置;设置页会提示所选桥暂不可用,并允许改选或选择「不使用」。
15
+ - 打开工作簿支持取消信号:导航前拒绝已取消的请求,取消后停止加载等待并释放打开锁。
16
+
17
+ **升级说明:** 建议与 Driver `0.1.4` 配套升级,完成打开任务的取消处理。恢复快照仍仅在当前页面生命周期内有效;取消不会撤销已发生的导航或已发送的写入。
18
+
19
+ **验证:** 类型检查、240 项单测、构建、两个 smoke 和独立离线包安装校验通过;用户在 DSH `0.2.1-rc.4` dev 环境做过基本回归,未发现问题。rc.3、rc.4、rc.5 相关 UI API 包实现比对无差异,桌面运行验证基线为 rc.4。
20
+
21
+ ### English
22
+
23
+ **Broaden DSH compatibility and fix save and recovery data-loss risks**
24
+
25
+ - Declare DSH and UI peer compatibility as `>=0.2.1-rc.3 <0.3.0-0`, keeping development dependencies pinned to `0.2.1-rc.4`. Patch and RC updates within this range no longer require individual plugin releases; verify APIs and runtime behavior before supporting a new DSH minor.
26
+ - Keep edits made during a save marked as unsaved. An earlier upload finishing after a tab closes or switches no longer clears its newer recovery snapshot.
27
+ - Keep the current file and later edits when Save As wrote an older revision, and explain that another save is needed. A completed Save As no longer navigates after its document has closed or switched away.
28
+ - Serialize imports into one Workbook so an older load cannot overwrite newer contents. Refuse stale exports after a load failure, workbook switch or reload.
29
+ - Serialize conflict checks and writes to each file within the same host process, rejecting subsequent saves based on an outdated version. Stop writes when file metadata cannot be read.
30
+ - Preserve bridge selections during delayed registration and temporary reloads. Settings explains unavailable selections and lets users choose another bridge or None.
31
+ - Accept cancellation signals when opening workbooks: refuse cancelled requests before navigation, stop readiness waits and release the opening lock.
32
+
33
+ **Upgrade notes:** Update together with Driver `0.1.4` for complete opening cancellation support. Recovery snapshots remain limited to the current page lifetime. Cancellation does not undo navigation or writes already sent.
34
+
35
+ **Validation:** Typecheck, 240 tests, build, both smokes and an independent offline package installation passed. The user completed basic development desktop checks on DSH `0.2.1-rc.4` without finding issues. Relevant UI API implementations compare without differences across rc.3, rc.4 and rc.5; desktop runtime validation is on rc.4.
36
+
37
+ ## 0.2.5
38
+
39
+ - 将 DSH 开发依赖、宿主 peer 与兼容声明更新至 `0.2.1-rc.4`,补齐安装器读取的顶层 `engines.dsh`,解决在该 RC 中安装时的版本不兼容拦截。
40
+ - 用户已确认 DSH `0.2.1-rc.4` 的 dev 环境运行正常。请配套使用 Driver `0.1.3`。
41
+ - Target DSH `0.2.1-rc.4` in development dependencies, host peers and compatibility declarations, including the top-level `engines.dsh` read by the installer, so this RC accepts the plugin.
42
+ - The user confirmed the DSH `0.2.1-rc.4` development environment works. Use companion Driver `0.1.3`.
43
+
3
44
  ## 0.2.4
4
45
 
5
46
  - 更新 editor 与配套 driver 的 DSH 支持基线至 `0.2.1-rc.2`,并升级对应的 Cordis、Schemastery 与 UI/host 开发依赖。
package/README.md CHANGED
@@ -36,8 +36,8 @@ A DeepSeek Harness Web UI plugin that opens, views, and edits Excel / SpreadJS f
36
36
  两个插件都装在**同一个 web profile** 下——driver 的活工作簿能力只存在于 Web UI 里:
37
37
 
38
38
  ```sh
39
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
40
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
39
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
40
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver@0.1.4
41
41
  dsh --profile web
42
42
  ```
43
43
 
@@ -56,6 +56,8 @@ dsh --profile web
56
56
 
57
57
  选中 driver 后,AI 获得 `sjs_live_open`、`sjs_live_execute` 和 `sjs_live_status`。没有打开工作簿时,driver 可以请求 editor 打开文件并等待初始化;新文件可由 driver 从准备好的空白模板复制,Workbook 仍由 editor 创建和持有。
58
58
 
59
+ 已保存的桥选择会在插件延迟加载、重载或卸载后保留。所选桥暂未注册时,设置页会显示「当前不可用」及它的 id;它重新加载后会自动连接,也可以手动选择其他桥或「不使用」。其他桥先加载不会清空这项设置。
60
+
59
61
  driver 执行期间 editor 会显示操作中提示,禁用 Designer 的鼠标/键盘编辑及冲突的保存、另存为、新建、导出入口。若用户关闭或切换 editor,销毁 Workbook 前会保存当前快照到现有恢复缓存,driver 返回中断;重新打开文件后检查已恢复的部分,再继续任务。该缓存只在当前页面生命周期内有效,不跨页面刷新,也不恢复 JavaScript 的执行位置。快照失败会明确写入控制台日志。
60
62
 
61
63
  driver 的状态和执行按调用聊天隔离,并绑定当前文件的加载实例;重新加载即更新实例身份。恢复缓存使用完整聊天资源地址,不同聊天的同名文件不会互相恢复或清理。取消或超时后阻止后续自动保存;异步导出结束、写回之前再次检查任务权限。忽略取消信号的脚本仍持有编辑锁,直到结束或实例被销毁。已发送的写入可能完成,因此取消不能保证回滚。两侧插件需同时更新以支持实例身份协议。
@@ -63,7 +65,7 @@ driver 的状态和执行按调用聊天隔离,并绑定当前文件的加载
63
65
  #### 要求
64
66
 
65
67
  - **请同步升级 editor 与 driver**,以使用完整的实例身份、独占锁和恢复协议。
66
- - **两侧统一支持 DSH `0.2.1-rc.2`**:桥选择页使用 `settings.section`,选择结果保存在 editor 的设置文档中。开发依赖与宿主 peer 声明均以该 RC 为基线;旧版本和 alpha 不在支持范围内。
68
+ - **两侧声明支持 DSH `>=0.2.1-rc.3 <0.3.0-0`**:桥选择页使用 `settings.section`,选择结果保存在 editor 的设置文档中。开发依赖固定在已验证的 rc.4;rc.3 到 rc.5 的相关 UI API 实现无差异,当前桌面环境验收基线为 rc.4。
67
69
  - **driver 前提**:Node ≥22.19、同一 web profile 中的 editor 和可用的前端连接。服务端无需安装 Chrome/Edge。
68
70
 
69
71
  ### 内置版本与文件兼容性
@@ -77,18 +79,23 @@ driver 的状态和执行按调用聊天隔离,并绑定当前文件的加载
77
79
 
78
80
  ### 安装
79
81
 
80
- 支持 DSH `0.2.1-rc.2`,需要 Node.js ≥22.19。插件使用 DSH 自带的右侧栏,**不需要安装任何第三方侧边栏插件**。升级 DSH 时请同步升级插件;新的 RC 需重新验证后再调整支持声明。
82
+ Editor `0.2.6` 声明支持 DSH `>=0.2.1-rc.3 <0.3.0-0`,需要 Node.js ≥22.19;用户已确认 rc.4 的 dev 环境运行正常。插件使用 DSH 自带的右侧栏,**不需要安装任何第三方侧边栏插件**。新 DSH 次版本需验证 API 后再扩展支持范围。
81
83
 
82
84
  | DSH 版本 | 状态 | 说明 |
83
85
  | --- | --- | --- |
84
- | `0.2.1-rc.2` | 支持与验证基线 | 类型检查、215 项单测、构建、两个 smoke 与 `verify:package` 通过;运行中的 DSH 仍需重启后执行 `probe:live` 和界面验收 |
86
+ | `>=0.2.1-rc.3 <0.3.0-0` | 声明兼容范围 | 覆盖 DSH 0.2.x 从 rc.3 起的版本;rc.3/rc.4/rc.5 UI API 包实现对比无差异 |
87
+ | `0.2.1-rc.4` | 桌面环境验收基线 | 类型检查、240 项单测、构建、两个 smoke 与包安装校验通过;用户已确认 dev 环境运行正常 |
88
+
89
+ **版本策略:** `engines.dsh` 与 DSH API peers 使用同一有界范围;范围起点取插件所需 API 的最低版本,上界停在下一条 DSH 次版本线。范围内的 patch 和 RC 不要求每次发布插件;开发依赖固定在最近通过验证的 RC。扩展到新的 DSH 次版本前,先比较 API 并跑安装/运行 smoke,再发布更新后的兼容声明。
90
+
91
+ 请同步使用 Editor `0.2.6` 与 Driver `0.1.4`;旧版插件的精确版本声明可能导致安装被拒绝。
85
92
 
86
- 该 RC 提供工作簿所需的二进制后缀声明、内置只读预览、文档头部动作槽 `sidebar.right.tab.document.actions` 和设置分区 `settings.section`。本版不再声明兼容旧版 DSH。
93
+ 当前验收的 rc.4 提供工作簿所需的二进制后缀声明、内置只读预览、文档头部动作槽 `sidebar.right.tab.document.actions` 和设置分区 `settings.section`。兼容声明限定在 DSH 0.2.x 主线。
87
94
 
88
95
  插件内置 SpreadJS 和 Designer,无需编译。使用已安装的 DSH CLI 安装并启动:
89
96
 
90
97
  ```sh
91
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
98
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
92
99
  dsh --profile web
93
100
  ```
94
101
 
@@ -97,8 +104,8 @@ dsh --profile web
97
104
  也可以通过 npx 安装并启动:
98
105
 
99
106
  ```sh
100
- npx --yes @deepseek-ai/dsh@0.2.1-rc.2 plugin --profile web add @grapecity-software/dsh-spreadjs-editor
101
- npx --yes @deepseek-ai/dsh@0.2.1-rc.2 --profile web
107
+ npx --yes @deepseek-ai/dsh@0.2.1-rc.4 plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
108
+ npx --yes @deepseek-ai/dsh@0.2.1-rc.4 --profile web
102
109
  ```
103
110
 
104
111
  旧版本用户请参阅[升级说明](CHANGELOG.md)。
@@ -130,7 +137,7 @@ Designer 自带的「文件」标签页已经**从 Ribbon 上隐藏**:插件
130
137
  头部工具栏上的「另存为…」把当前工作簿写到工作区的**另一个**路径——包括在 Designer 里重新置空的空白工作簿。它不碰原来那个文件。
131
138
 
132
139
  - **默认目标**:同目录、同后缀,文件名后面加「副本」(`报表.xlsx` → `报表-副本.xlsx`)。
133
- - **成功后就地切换**:这个标签页会直接开到刚写下的文件上继续编辑,不会另开一个标签页。
140
+ - **成功后就地切换**:当前内容完整写入且文档仍在时,这个标签页直接打开新文件继续编辑;若期间产生新改动,则保留当前文件并提示继续保存。已关闭或切走的文档不会因旧任务完成而跳转。
134
141
  - **同名不覆盖**:目标已存在时宿主会拒绝写入(写回要求带版本基线,另存为没有),对话框直接说「目标文件已存在,请换一个名字」,而不是默默覆盖。
135
142
  - **只接受 5 种后缀**:`.xlsx`、`.xlsm`、`.csv`、`.sjs`、`.ssjson`。写出格式由后缀决定,其它后缀会写出打不开的文件,所以在提交前就拦下。
136
143
  - **越界同样被拒**:路径解析与围栏由宿主按写回那一套规则判定,工作区之外一律拒绝。
@@ -143,9 +150,9 @@ DSH 在标签页被切走时会卸载文档主体,平台也没有可供拦下
143
150
  - **切走再切回不丢。** 有未保存改动的工作簿会在主体卸载前留成一份快照,切回同一个标签页时原样恢复,不需要重新读盘。
144
151
  - **关掉标签页也不丢。** 快照同时按文件路径保留;重新打开同一个文件时,只要磁盘上的文件仍是快照写下的那一版,就自动恢复,并在面板右下角的胶囊里说明来源。
145
152
  - **文件被外部改过时不默默套用。** 若磁盘上的文件已经变了(Agent 改过、你在别处存过),面板右下角会浮出一个胶囊提示「存在未保存的改动缓存,但文件已在磁盘上被修改」,由你选择**恢复**(用缓存继续,之后保存即写入磁盘)或**丢弃**(重新读盘)。
146
- - **看得见,而且有两处。** 头部工具栏的「保存」在有未保存改动时带一个标记点(像 Excel 标题栏的 `*`);面板右下角另有一个浮动胶囊,显示「未保存的改动」与「丢弃」操作。胶囊悬浮在表格上方,不占布局高度,保存成功后消失。
153
+ - **看得见,而且有两处。** 头部工具栏的「保存」在有未保存改动时带一个标记点(像 Excel 标题栏的 `*`);面板右下角另有一个浮动胶囊,显示「未保存的改动」与「丢弃」操作。胶囊悬浮在表格上方,不占布局高度,当前内容全部保存后消失。
147
154
  - **整页关闭会提示。** 刷新或关闭页面时,只要还有未保存的改动(含已留存的缓存),浏览器会弹出一次自己的离开确认。
148
- - **保存即清空。** 保存成功后,该文件的快照全部清除。
155
+ - **保存当前内容后清空。** 当前内容完整写入后,该文件的快照清除;保存期间的新改动仍保留未保存标记,切走或关闭后留下的较新快照不会被旧保存清掉。
149
156
 
150
157
  ### 配置
151
158
 
@@ -173,7 +180,7 @@ DSH 在标签页被切走时会卸载文档主体,平台也没有可供拦下
173
180
 
174
181
  | 命令 | 验证内容 |
175
182
  | --- | --- |
176
- | `npm run typecheck`、`npm test` | 类型契约,以及 215 个单元测试(保存路由的每条守卫、文件地址解析与地址构造、另存为的目标校验、默认名与失败文案、保存命令重定向的每条分支、编辑器主题桥的每条分支、查看器名称与二进制后缀声明的边界、未保存改动的缓存规则与脏状态跟踪的边界、打开时「用文件还是用缓存」的每个分支、面板状态胶囊的状态与操作矩阵、文档头部动作的发布/订阅/命令路由、按钮的尺寸字号颜色规格、「只在表格文件上出现」的判定与注册顺序,隐藏 Designer「文件」Tab 那条规则的两处身份与作用域,以及桥选择的方向与「写入被拒也要报出来」、桥字段是否真的声明为 volatile、名册自身的契约(重复 id 拒绝、disposer 语义、只在真的变化时通知)、名册与设置页在有无 settings 服务时的接线,以及设置页每一行确实是照 DSH 自己设置页的行规格画的) |
183
+ | `npm run typecheck`、`npm test` | 类型契约,以及 240 个单元测试(保存路由的每条守卫、文件地址解析与地址构造、另存为的目标校验、默认名与失败文案、保存命令重定向的每条分支、编辑器主题桥的每条分支、查看器名称与二进制后缀声明的边界、未保存改动的缓存规则与脏状态跟踪的边界、打开时「用文件还是用缓存」的每个分支、面板状态胶囊的状态与操作矩阵、文档头部动作的发布/订阅/命令路由、按钮的尺寸字号颜色规格、「只在表格文件上出现」的判定与注册顺序,隐藏 Designer「文件」Tab 那条规则的两处身份与作用域,以及桥选择的方向与「写入被拒也要报出来」、桥字段是否真的声明为 volatile、名册自身的契约(重复 id 拒绝、disposer 语义、只在真的变化时通知)、名册与设置页在有无 settings 服务时的接线,以及设置页每一行确实是照 DSH 自己设置页的行规格画的) |
177
184
  | `node scripts/smoke-client.mjs` | 构建后的浏览器半结构:loader 包装、内联的 SpreadJS 与样式、外部依赖只有 react |
178
185
  | `node scripts/smoke-node.mjs` | 构建后的宿主半:路由注册、许可证配置,以及保存路径的真实写盘、冲突与越界拒绝 |
179
186
  | `npm run probe:live` | **对着正在运行的 DSH** 跑一遍真实保存:真实 `ctx.fs` 的解析、落盘、冲突守卫、工作区越界;并核对进程里装的是不是本构建(插件版本与内置 SpreadJS 版本) |
@@ -212,8 +219,8 @@ This package depends on no driver. SpreadJS, Designer and extensions are build/d
212
219
  Both plugins go into the **same web profile** — the driver's live-workbook capability exists only in the Web UI:
213
220
 
214
221
  ```sh
215
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
216
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
222
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
223
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver@0.1.4
217
224
  dsh --profile web
218
225
  ```
219
226
 
@@ -232,12 +239,14 @@ The editor **owns** the workbook you open; each driver **registers** as a candid
232
239
 
233
240
  Selecting the driver enables `sjs_live_open`, `sjs_live_execute` and `sjs_live_status`. The driver can copy a template and ask this editor to open and initialize it. Import/export and Workbook ownership remain editor features.
234
241
 
242
+ The saved bridge choice survives delayed loading, reloads and removal. If that bridge is absent, Settings shows it as unavailable with its id; it reconnects when loaded, or you can choose another bridge or None. Another bridge loading first never clears the saved choice.
243
+
235
244
  Driver status and execution are confined to the calling chat and bound to the current loaded-document identity, renewed on every reload. Recovery snapshots use the full session resource address, so same-named files in different chats cannot restore or clear each other's buffers. Cancellation/timeout prevent subsequent automatic saves; the editor rechecks permission after export and before write-back. Scripts ignoring cancellation retain their edit lock until they settle or the instance is destroyed. A write already sent may complete; cancellation cannot guarantee rollback. Update both plugins for the identity protocol.
236
245
 
237
246
  #### Requirements
238
247
 
239
248
  - **Update the editor and driver together** for the complete instance identity, lease and recovery protocol.
240
- - **Both plugins support DSH `0.2.1-rc.2`**: the bridge picker uses `settings.section` and stores the choice in the editor settings document. Development dependencies and host peers target this RC; older releases and alpha builds are outside the supported baseline.
249
+ - **Both plugins declare support for DSH `>=0.2.1-rc.3 <0.3.0-0`**: the bridge picker uses `settings.section` and stores the choice in the editor settings document. Development dependencies remain pinned to validated rc.4; the relevant UI API package implementations compare without differences from rc.3 through rc.5.
241
250
  - **Driver requirements**: Node ≥22.19, the editor in the same web profile, and a working browser connection. No Chrome/Edge installation on the server is required.
242
251
 
243
252
  ### Bundled Versions and File Compatibility
@@ -251,18 +260,23 @@ The **About** button in the Designer's settings tab shows the bundled versions,
251
260
 
252
261
  ### Installation
253
262
 
254
- Supports DSH `0.2.1-rc.2` and requires Node.js ≥22.19. The plugin uses the harness's own right Sidebar and needs **no third-party sidebar plugin**. Update the plugins alongside DSH; a newer RC needs verification before changing the supported baseline.
263
+ Editor `0.2.6` declares support for DSH `>=0.2.1-rc.3 <0.3.0-0` and requires Node.js ≥22.19; the user confirmed rc.4 works in the development environment. The plugin uses the harness's own right Sidebar and needs **no third-party sidebar plugin**. Verify a new DSH minor's APIs before extending the supported range.
255
264
 
256
265
  | DSH version | Status | Notes |
257
266
  | --- | --- | --- |
258
- | `0.2.1-rc.2` | Supported and verified baseline | Typecheck, 215 unit tests, build, both smokes and `verify:package` pass; restart the running DSH before a separate `probe:live` and UI acceptance check |
267
+ | `>=0.2.1-rc.3 <0.3.0-0` | Declared compatibility range | DSH 0.2.x from rc.3; the rc.3/rc.4/rc.5 UI API package implementations compare without differences |
268
+ | `0.2.1-rc.4` | Desktop validation baseline | Typecheck, 240 unit tests, build, both smokes and package installation checks pass; the user confirmed the development environment works |
269
+
270
+ **Version policy:** Keep `engines.dsh` and DSH API peers on the same bounded range, starting at the oldest API version the plugin needs and ending before the next DSH minor line. Patch and RC releases inside that range do not require a plugin release each time; development dependencies stay pinned to the latest validated RC. Before extending support to a new DSH minor, compare the APIs and run install/runtime smokes, then publish the updated compatibility declaration.
271
+
272
+ Use Editor `0.2.6` together with Driver `0.1.4`; older plugins may be rejected because they declare an exact DSH version.
259
273
 
260
- This RC supplies the binary-suffix declaration, built-in read-only preview, document-header actions slot `sidebar.right.tab.document.actions`, and settings section `settings.section`. This version no longer declares support for older DSH releases.
274
+ The validated rc.4 supplies the binary-suffix declaration, built-in read-only preview, document-header actions slot `sidebar.right.tab.document.actions`, and settings section `settings.section`. Compatibility is declared for the DSH 0.2.x line.
261
275
 
262
276
  The plugin includes SpreadJS and Designer, with no build step required. With the DSH CLI installed:
263
277
 
264
278
  ```sh
265
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
279
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
266
280
  dsh --profile web
267
281
  ```
268
282
 
@@ -271,8 +285,8 @@ After startup, open `.xlsx`, `.xlsm`, `.csv`, `.sjs`, or `.ssjson` from the righ
271
285
  Alternatively, install and start through npx:
272
286
 
273
287
  ```sh
274
- npx --yes @deepseek-ai/dsh@0.2.1-rc.2 plugin --profile web add @grapecity-software/dsh-spreadjs-editor
275
- npx --yes @deepseek-ai/dsh@0.2.1-rc.2 --profile web
288
+ npx --yes @deepseek-ai/dsh@0.2.1-rc.4 plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
289
+ npx --yes @deepseek-ai/dsh@0.2.1-rc.4 --profile web
276
290
  ```
277
291
 
278
292
  For upgrades, see the [release notes](CHANGELOG.md).
@@ -304,7 +318,7 @@ The price is that the File tab's other categories (new, open, import, export, pr
304
318
  **另存为…** in the same document header writes the workbook to a *different* path in the workspace — including a workbook that was reset to blank inside the Designer. The file it came from is left untouched.
305
319
 
306
320
  - **Suggested target**: same directory, same suffix, with the localized word for a copy appended (`report.xlsx` → `report-copy.xlsx`).
307
- - **It switches this tab.** On success the tab navigates to the file just written and keeps editing there; no second tab is opened.
321
+ - **It switches this tab.** When the current contents are fully written and the document is still present, the tab opens the new file. If later edits remain, it keeps the current file and asks for another save. A completed task does not navigate after its document has closed or switched away.
308
322
  - **An existing name is never overwritten.** The host refuses the write (a write-back needs a version baseline, and a Save As has none) and the dialog says so — "that file already exists; choose another name" — instead of replacing what is there.
309
323
  - **Only five suffixes are accepted**: `.xlsx`, `.xlsm`, `.csv`, `.sjs`, `.ssjson`. The written format follows the suffix, so anything else would produce a file this editor cannot reopen; it is refused before the write.
310
324
  - **Containment is judged by the host**, on the same rule as a write-back: a path outside the session workspace is refused.
@@ -317,9 +331,9 @@ The harness unmounts a document body when its tab is hidden, and the platform of
317
331
  - **Switching away and back keeps the edits.** A dirty workbook is kept as a snapshot just before its body unmounts, and restored as-is when the same tab returns, without re-reading the file.
318
332
  - **Closing the tab keeps them too.** The snapshot is also kept under the file's path, so reopening that file restores it — and says so in the pill at the panel's bottom-right — as long as the file on disk is still the version the snapshot was written from.
319
333
  - **A file changed elsewhere is never silently replaced.** If the file on disk changed since the snapshot (an agent, another editor), the panel offers it instead in that same pill: **Restore** carries on from the buffer (a later save writes it to disk), **Discard** re-reads the file.
320
- - **It is visible, in two places.** Save in the document header carries a mark while edits are pending, like Excel's title-bar `*`. A floating pill at the bottom-right shows `Unsaved changes` and a Discard action. It costs no layout height and disappears after saving.
334
+ - **It is visible, in two places.** Save in the document header carries a mark while edits are pending, like Excel's title-bar `*`. A floating pill at the bottom-right shows `Unsaved changes` and a Discard action. It costs no layout height and disappears once the current contents are fully saved.
321
335
  - **Closing the page warns.** A reload or close raises the browser's own prompt once, as long as anything is unsaved or still cached.
322
- - **Saving clears it.** A successful save drops every snapshot for that file, so reopening starts from disk.
336
+ - **Saving the current contents clears recovery.** Later edits remain marked as unsaved. An earlier save cannot clear a newer snapshot left when closing or switching the document.
323
337
 
324
338
  ### Configuration
325
339
 
@@ -347,7 +361,7 @@ One more field is deliberately **not** in that table: `bridge` is written by the
347
361
 
348
362
  | Command | What it proves |
349
363
  | --- | --- |
350
- | `npm run typecheck`, `npm test` | The type contract, and 215 unit tests: every save guard, file-address parsing and address building, the Save As target rules, suggested name and failure wording, every branch of the Save-command redirect, every branch of the editor theme bridge, the viewer-name and binary-suffix edge cases, the unsaved-buffer and dirty-tracking rules, every branch of the file-versus-buffer decision at open, the panel state pill's state/action matrix, the document header actions' publication, subscription, command routing, button spec, "spreadsheets only" rule and registration order, both identities plus the scope of the rule that hides the Designer's File tab, the direction of the bridge choice and that a refused write is reported, that the bridge field really is declared volatile, and the registry's own contract (a duplicate id refused, disposer semantics, notifying only on a real change), the wiring of the roster and the settings page with and without a settings service, and that every row of that page is drawn to the harness's own settings-row spec |
364
+ | `npm run typecheck`, `npm test` | The type contract, and 240 unit tests: every save guard, file-address parsing and address building, the Save As target rules, suggested name and failure wording, every branch of the Save-command redirect, every branch of the editor theme bridge, the viewer-name and binary-suffix edge cases, the unsaved-buffer and dirty-tracking rules, every branch of the file-versus-buffer decision at open, the panel state pill's state/action matrix, the document header actions' publication, subscription, command routing, button spec, "spreadsheets only" rule and registration order, both identities plus the scope of the rule that hides the Designer's File tab, the direction of the bridge choice and that a refused write is reported, that the bridge field really is declared volatile, and the registry's own contract (a duplicate id refused, disposer semantics, notifying only on a real change), the wiring of the roster and the settings page with and without a settings service, and that every row of that page is drawn to the harness's own settings-row spec |
351
365
  | `node scripts/smoke-client.mjs` | The built browser half's structure: loader wrapper, inlined SpreadJS and styles, react as the only runtime external |
352
366
  | `node scripts/smoke-node.mjs` | The built host half: route registration, license config, and the save path writing real bytes plus refusing conflicts and escapes |
353
367
  | `npm run probe:live` | A real save against a **running DSH**: the actual `ctx.fs` resolving, writing, refusing a stale base hash, and refusing a workspace escape — plus a check that the process carries *this* build (plugin and bundled SpreadJS versions) |
@@ -25,6 +25,8 @@ interface SpreadjsBridgeRegistry {
25
25
 
26
26
  Registration rejects duplicate ids. Registration and subscription return idempotent disposers; selection changes notify subscribers. `selected()` preserves the user's saved id even before its entry registers; `current()` returns that entry only when registered. An empty selection hands out no Workbook. Drivers must drop references when detached, stop advertising them, and revalidate before using them.
27
27
 
28
+ The saved selection remains intact when its entry is absent, including delayed registration and temporary unloads. Registration order never clears that preference: Settings displays an unavailable selection and lets the user replace it or explicitly select None.
29
+
28
30
  The editor persists its selected driver id in its DSH settings document's `bridge` field; `''` or an absent value means no driver. Browser registry selection mirrors that document. The bundled Driver's host reads only the explicitly configured `editorSettingsNamespace`, default `spreadjs-editor`, to register its tools and skill. A third-party editor uses its own namespace, declares a writable/volatile `bridge` field, and emits the normal DSH `settings/document-updated` event when changed. Set the Driver config to that namespace and restart DSH; do not impersonate another editor's settings document.
29
31
 
30
32
  ```yaml
@@ -51,7 +53,9 @@ interface SpreadjsWorkbookProvider {
51
53
  instanceId: string
52
54
  } | undefined
53
55
  getSessionId?(): string | undefined
54
- openWorkbook?(address: string, timeoutMs: number): Promise<void>
56
+ openWorkbook?(address: string, timeoutMs: number, options?: {
57
+ signal?: AbortSignal
58
+ }): Promise<void>
55
59
  acquireDriverLease?(): string
56
60
  releaseDriverLease?(lease: string): void
57
61
  isDriverBusy?(): boolean
@@ -73,7 +77,7 @@ interface SpreadjsWorkbookProvider {
73
77
  | Exclusive lease | Refuse acquisition while loading/saving/busy; finish pending cell input. Block concurrent user edits and conflicting owner operations until release. | Required for execution, including reads. Lease release must be idempotent and bound to its original owner. |
74
78
  | Owner teardown signal | Provide a lease AbortSignal; preserve any recoverable partial state, then abort it before destroying the instance. | Required for prompt interruption when the owner closes during an awaiting script. |
75
79
  | Save | Validate lease ownership; export, await `beforeWrite`, check `signal` before write-back and pass it to cancellable I/O. Reject failures and downloads that did not write the original file. | Required only for `save:true`; omitting it must not report a successful write. |
76
- | Navigation | Advertise the visible chat via `getSessionId()` even without a Workbook. `openWorkbook` opens through the owner's navigation and resolves only when the requested resource is ready; reject session changes, timeout and load failure. | Required only for `sjs_live_open`; no browser-side Workbook creation by the driver. |
80
+ | Navigation | Advertise the visible chat via `getSessionId()` even without a Workbook. `openWorkbook` opens through the owner's navigation and resolves only when the requested resource is ready; reject cancellation, session changes, timeout and load failure. Check `signal` before navigating and stop readiness waits when it aborts. | Required only for `sjs_live_open`; no browser-side Workbook creation by the driver. |
77
81
 
78
82
  Navigation must not destroy a leased instance without preserving partial state and aborting its owner signal. Recovery data belongs to the editor and is keyed by the full chat resource address; a snapshot is not a resumable JavaScript execution. The bundled Editor retains snapshots only for the current page lifetime. Other editors may provide durable recovery, but drivers must not assume it.
79
83
 
package/lib/client.js CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/lib/client.js
3
- size: 52049119 bytes
4
- sha256: 81f011276cf73ec805a4b94f34ca7638b39db283e2ecd9d013b699671605fd67
3
+ size: 52052364 bytes
4
+ sha256: ec38a36efd2c2ec37f7e448f66c9cd27801581189bd76bd0614c2b9af467f351
package/lib/index.js CHANGED
@@ -226,6 +226,24 @@ async function replaceFile(targetPath, bytes) {
226
226
  throw error;
227
227
  }
228
228
  }
229
+ /** Keep the freshness check and replacement together for saves to one file. */
230
+ const pendingSaves = /* @__PURE__ */ new Map();
231
+ async function withSaveLock(targetPath, save) {
232
+ const key = process.platform === "win32" ? targetPath.toLowerCase() : targetPath;
233
+ const previous = pendingSaves.get(key);
234
+ let release;
235
+ const pending = new Promise((resolve) => {
236
+ release = resolve;
237
+ });
238
+ pendingSaves.set(key, pending);
239
+ await previous;
240
+ try {
241
+ await save();
242
+ } finally {
243
+ release();
244
+ if (pendingSaves.get(key) === pending) pendingSaves.delete(key);
245
+ }
246
+ }
229
247
  /**
230
248
  * Save one workbook posted by the document panel.
231
249
  *
@@ -258,13 +276,16 @@ async function handleSave(req, res, ctx, config) {
258
276
  const bytes = await readBody(req, limit);
259
277
  const hash = sha256Hex(bytes);
260
278
  if (hash !== claimedHash.toLowerCase()) throw new HttpError(400, "hash_mismatch", "The uploaded bytes do not match the declared body hash");
261
- const existing = await fs.stat(target).catch(() => void 0);
262
- if (existing !== void 0) {
263
- if (existing.type !== "file") throw new HttpError(400, "not_a_file", `${target.displayPath} is not a regular file`);
264
- if (baseHash === null || baseHash === "") throw new HttpError(409, "conflict", "The file already exists but the editor did not state what it opened");
265
- if (sha256Hex(await fs.readBytes(target, void 0, limit)) !== baseHash.toLowerCase()) throw new HttpError(409, "conflict", "The file changed on disk since it was opened; nothing was written");
266
- } else if (baseHash !== null && baseHash !== "") throw new HttpError(409, "conflict", "The editor expected an existing file but none is there");
267
- await replaceFile(fs.processPath(target), bytes);
279
+ const targetPath = fs.processPath(target);
280
+ await withSaveLock(targetPath, async () => {
281
+ const existing = await fs.stat(target);
282
+ if (existing !== void 0) {
283
+ if (existing.type !== "file") throw new HttpError(400, "not_a_file", `${target.displayPath} is not a regular file`);
284
+ if (baseHash === null || baseHash === "") throw new HttpError(409, "conflict", "The file already exists but the editor did not state what it opened");
285
+ if (sha256Hex(await fs.readBytes(target, void 0, limit)) !== baseHash.toLowerCase()) throw new HttpError(409, "conflict", "The file changed on disk since it was opened; nothing was written");
286
+ } else if (baseHash !== null && baseHash !== "") throw new HttpError(409, "conflict", "The editor expected an existing file but none is there");
287
+ await replaceFile(targetPath, bytes);
288
+ });
268
289
  sendJson(res, 200, {
269
290
  ok: true,
270
291
  hash,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grapecity-software/dsh-spreadjs-editor",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "DSH sidebar SpreadJS editor with binary save, recovery and a selectable bridge for compatible drivers.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -44,7 +44,8 @@
44
44
  "verify:package": "node scripts/verify-package.mjs"
45
45
  },
46
46
  "engines": {
47
- "node": ">=22.19.0"
47
+ "node": ">=22.19.0",
48
+ "dsh": ">=0.2.1-rc.3 <0.3.0-0"
48
49
  },
49
50
  "publishConfig": {
50
51
  "access": "public"
@@ -55,7 +56,7 @@
55
56
  },
56
57
  "dsh": {
57
58
  "engines": {
58
- "dsh": "0.2.1-rc.2"
59
+ "dsh": ">=0.2.1-rc.3 <0.3.0-0"
59
60
  },
60
61
  "bundle": {
61
62
  "patch": "./cordis.patch.yml"
@@ -72,22 +73,22 @@
72
73
  },
73
74
  "peerDependencies": {
74
75
  "@deepseek-ai/cordis": "~4.0.5",
75
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
76
- "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.2.1-rc.2",
77
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
78
- "@deepseek-ai/dsh-client-ui-theme": "0.2.1-rc.2",
76
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.2.1-rc.3 <0.3.0-0",
77
+ "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": ">=0.2.1-rc.3 <0.3.0-0",
78
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.2.1-rc.3 <0.3.0-0",
79
+ "@deepseek-ai/dsh-client-ui-theme": ">=0.2.1-rc.3 <0.3.0-0",
79
80
  "@deepseek-ai/schemastery": "~3.18.5",
80
81
  "react": "^18.0.0"
81
82
  },
82
83
  "devDependencies": {
83
84
  "@deepseek-ai/cordis": "~4.0.5",
84
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
85
- "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.2",
86
- "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.2.1-rc.2",
87
- "@deepseek-ai/dsh-client-ui-sidebar-right": "0.2.1-rc.2",
88
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
89
- "@deepseek-ai/dsh-client-ui-theme": "0.2.1-rc.2",
90
- "@deepseek-ai/dsh-host-webserver": "0.2.1-rc.2",
85
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.4",
86
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.4",
87
+ "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.2.1-rc.4",
88
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "0.2.1-rc.4",
89
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.4",
90
+ "@deepseek-ai/dsh-client-ui-theme": "0.2.1-rc.4",
91
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-rc.4",
91
92
  "@deepseek-ai/schemastery": "~3.18.5",
92
93
  "@grapecity-software/spread-excelio": "19.2.1",
93
94
  "@grapecity-software/spread-sheets": "19.2.1",