@grapecity-software/dsh-spreadjs-editor 0.2.5 → 0.2.7

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,69 @@
1
1
  # Release Notes / 版本记录
2
2
 
3
+ ## 0.2.7
4
+
5
+ ### 中文
6
+
7
+ **隔离授权及初始化失败,保留 DSH 侧栏可用性**
8
+
9
+ - 隔离 SpreadJS / Designer 初始化与文档渲染错误,失败时显示当前页面来源和授权排查提示,避免异常冒泡并破坏 DSH sidebar。
10
+ - 初始化失败后清理局部 DOM 和已创建实例;销毁时先撤销工作簿与 Driver 租约,分别处理恢复、事件解绑及 SDK 销毁异常。
11
+ - 主题切换触发的工作簿刷新异常交由文档错误页处理,避免中断 DSH 的主题事件。
12
+ - 避免正常关闭时重复销毁 Workbook;Designer 已负责销毁它持有的工作簿。
13
+ - 补充桌面版 `dsh-app://app/` 授权说明、配置位置和测试步骤,修正「留空即能试用」的描述。
14
+
15
+ **范围说明:** 本版保留 SDK 的授权校验,失败时显示当前文件的提示。用户用主机名 `app` 申请的试用授权已让运行时启动,但 Designer 显示仍异常;本版不宣称解决桌面版完整授权和 Designer 兼容问题。无须配套升级 Driver,继续使用 `0.1.4`。
16
+
17
+ **验证:** 类型检查、260 项测试(含真实 React 调度下的初始化失败与侧栏隔离)、构建、客户端 / 宿主 smoke 和独立离线包安装校验通过。真实桌面许可证及 Designer 显示仍需后续验证。
18
+
19
+ ### English
20
+
21
+ **Contain licensing and initialization failures within the editor**
22
+
23
+ - Contain SpreadJS / Designer initialization and document rendering failures within the editor, with page-origin and licensing guidance.
24
+ - Clean up partially initialized instances and DOM; revoke Workbook access and Driver leases before independently guarded recovery and disposal steps.
25
+ - Contain workbook refresh failures during theme changes without interrupting DSH's theme event.
26
+ - Avoid destroying a Workbook twice on normal close: Designer owns its workbook's disposal.
27
+ - Document desktop licensing for `dsh-app://app/`, profile configuration, regression steps and the limits of keyless evaluation.
28
+
29
+ **Scope:** SDK license validation remains in place; failures display a document-local message. A trial key issued for hostname `app` let the runtime start in a user test, but Designer still displayed incorrectly. This release does not establish complete desktop licensing or Designer compatibility. Companion Driver `0.1.4` remains suitable.
30
+
31
+ **Validation:** Typecheck, 260 tests (including real React initialization-failure and sidebar-isolation tests), build, client / host smokes and independent offline package installation passed. Real desktop licensing and Designer rendering still need further validation.
32
+
33
+ ## 0.2.6
34
+
35
+ ### 中文
36
+
37
+ **放宽 DSH 兼容范围,修复保存与恢复中的数据丢失风险**
38
+
39
+ - DSH 兼容声明和 UI peer 范围调整为 `>=0.2.1-rc.3 <0.3.0-0`,开发依赖继续固定在 `0.2.1-rc.4`。同一范围内的 patch 和 RC 不再要求逐个发布插件;扩展到新的 DSH 次版本前先验证 API 和运行行为。
40
+ - 保存期间继续编辑时,新改动仍标为未保存;关闭或切换标签页后,稍后完成的旧保存不会清除新留下的恢复快照。
41
+ - 另存为只写入了较早内容时,保留当前文件和后续改动,并提示继续保存;文档已关闭或切走时,不再因旧任务完成而自动跳转。
42
+ - 串行载入同一工作簿,避免旧加载最后完成并覆盖新内容。载入失败或导出期间工作簿发生切换、重新加载时,拒绝写入过时内容。
43
+ - 在同一宿主进程内,串行执行同一文件的冲突检查与写回,拒绝基于旧文件版本的后续保存;读取文件状态失败时停止写入。
44
+ - 保留延迟加载或临时重载的桥选择,避免其他桥先注册时清空用户设置;设置页会提示所选桥暂不可用,并允许改选或选择「不使用」。
45
+ - 打开工作簿支持取消信号:导航前拒绝已取消的请求,取消后停止加载等待并释放打开锁。
46
+
47
+ **升级说明:** 建议与 Driver `0.1.4` 配套升级,完成打开任务的取消处理。恢复快照仍仅在当前页面生命周期内有效;取消不会撤销已发生的导航或已发送的写入。
48
+
49
+ **验证:** 类型检查、240 项单测、构建、两个 smoke 和独立离线包安装校验通过;用户在 DSH `0.2.1-rc.4` dev 环境做过基本回归,未发现问题。rc.3、rc.4、rc.5 相关 UI API 包实现比对无差异,桌面运行验证基线为 rc.4。
50
+
51
+ ### English
52
+
53
+ **Broaden DSH compatibility and fix save and recovery data-loss risks**
54
+
55
+ - 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.
56
+ - 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.
57
+ - 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.
58
+ - Serialize imports into one Workbook so an older load cannot overwrite newer contents. Refuse stale exports after a load failure, workbook switch or reload.
59
+ - 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.
60
+ - Preserve bridge selections during delayed registration and temporary reloads. Settings explains unavailable selections and lets users choose another bridge or None.
61
+ - Accept cancellation signals when opening workbooks: refuse cancelled requests before navigation, stop readiness waits and release the opening lock.
62
+
63
+ **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.
64
+
65
+ **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.
66
+
3
67
  ## 0.2.5
4
68
 
5
69
  - 将 DSH 开发依赖、宿主 peer 与兼容声明更新至 `0.2.1-rc.4`,补齐安装器读取的顶层 `engines.dsh`,解决在该 RC 中安装时的版本不兼容拦截。
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@0.2.5
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.4`**:桥选择页使用 `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,20 +79,23 @@ driver 的状态和执行按调用聊天隔离,并绑定当前文件的加载
77
79
 
78
80
  ### 安装
79
81
 
80
- Editor `0.2.5` 支持 DSH `0.2.1-rc.4`,需要 Node.js ≥22.19;用户已确认该 RC 的 dev 环境运行正常。插件使用 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.4` | 支持与验证基线 | 类型检查、215 项单测、构建、两个 smoke 与包安装校验通过;用户已确认 dev 环境运行正常 |
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` | 桌面环境验收基线 | 类型检查、260 项单测、构建、两个 smoke 与包安装校验通过;用户已确认 dev 环境运行正常 |
88
+
89
+ **版本策略:** `engines.dsh` 与 DSH API peers 使用同一有界范围;范围起点取插件所需 API 的最低版本,上界停在下一条 DSH 次版本线。范围内的 patch 和 RC 不要求每次发布插件;开发依赖固定在最近通过验证的 RC。扩展到新的 DSH 次版本前,先比较 API 并跑安装/运行 smoke,再发布更新后的兼容声明。
85
90
 
86
- 请同步使用 Editor `0.2.5` 与 Driver `0.1.3`;旧版插件对 DSH 的精确版本声明可能导致安装被拒绝。
91
+ 请同步使用 Editor `0.2.6` 与 Driver `0.1.4`;旧版插件的精确版本声明可能导致安装被拒绝。
87
92
 
88
- 该 RC 提供工作簿所需的二进制后缀声明、内置只读预览、文档头部动作槽 `sidebar.right.tab.document.actions` 和设置分区 `settings.section`。本版不再声明兼容旧版 DSH。
93
+ 当前验收的 rc.4 提供工作簿所需的二进制后缀声明、内置只读预览、文档头部动作槽 `sidebar.right.tab.document.actions` 和设置分区 `settings.section`。兼容声明限定在 DSH 0.2.x 主线。
89
94
 
90
95
  插件内置 SpreadJS 和 Designer,无需编译。使用已安装的 DSH CLI 安装并启动:
91
96
 
92
97
  ```sh
93
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.5
98
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
94
99
  dsh --profile web
95
100
  ```
96
101
 
@@ -99,7 +104,7 @@ dsh --profile web
99
104
  也可以通过 npx 安装并启动:
100
105
 
101
106
  ```sh
102
- npx --yes @deepseek-ai/dsh@0.2.1-rc.4 plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.5
107
+ npx --yes @deepseek-ai/dsh@0.2.1-rc.4 plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
103
108
  npx --yes @deepseek-ai/dsh@0.2.1-rc.4 --profile web
104
109
  ```
105
110
 
@@ -132,7 +137,7 @@ Designer 自带的「文件」标签页已经**从 Ribbon 上隐藏**:插件
132
137
  头部工具栏上的「另存为…」把当前工作簿写到工作区的**另一个**路径——包括在 Designer 里重新置空的空白工作簿。它不碰原来那个文件。
133
138
 
134
139
  - **默认目标**:同目录、同后缀,文件名后面加「副本」(`报表.xlsx` → `报表-副本.xlsx`)。
135
- - **成功后就地切换**:这个标签页会直接开到刚写下的文件上继续编辑,不会另开一个标签页。
140
+ - **成功后就地切换**:当前内容完整写入且文档仍在时,这个标签页直接打开新文件继续编辑;若期间产生新改动,则保留当前文件并提示继续保存。已关闭或切走的文档不会因旧任务完成而跳转。
136
141
  - **同名不覆盖**:目标已存在时宿主会拒绝写入(写回要求带版本基线,另存为没有),对话框直接说「目标文件已存在,请换一个名字」,而不是默默覆盖。
137
142
  - **只接受 5 种后缀**:`.xlsx`、`.xlsm`、`.csv`、`.sjs`、`.ssjson`。写出格式由后缀决定,其它后缀会写出打不开的文件,所以在提交前就拦下。
138
143
  - **越界同样被拒**:路径解析与围栏由宿主按写回那一套规则判定,工作区之外一律拒绝。
@@ -145,18 +150,18 @@ DSH 在标签页被切走时会卸载文档主体,平台也没有可供拦下
145
150
  - **切走再切回不丢。** 有未保存改动的工作簿会在主体卸载前留成一份快照,切回同一个标签页时原样恢复,不需要重新读盘。
146
151
  - **关掉标签页也不丢。** 快照同时按文件路径保留;重新打开同一个文件时,只要磁盘上的文件仍是快照写下的那一版,就自动恢复,并在面板右下角的胶囊里说明来源。
147
152
  - **文件被外部改过时不默默套用。** 若磁盘上的文件已经变了(Agent 改过、你在别处存过),面板右下角会浮出一个胶囊提示「存在未保存的改动缓存,但文件已在磁盘上被修改」,由你选择**恢复**(用缓存继续,之后保存即写入磁盘)或**丢弃**(重新读盘)。
148
- - **看得见,而且有两处。** 头部工具栏的「保存」在有未保存改动时带一个标记点(像 Excel 标题栏的 `*`);面板右下角另有一个浮动胶囊,显示「未保存的改动」与「丢弃」操作。胶囊悬浮在表格上方,不占布局高度,保存成功后消失。
153
+ - **看得见,而且有两处。** 头部工具栏的「保存」在有未保存改动时带一个标记点(像 Excel 标题栏的 `*`);面板右下角另有一个浮动胶囊,显示「未保存的改动」与「丢弃」操作。胶囊悬浮在表格上方,不占布局高度,当前内容全部保存后消失。
149
154
  - **整页关闭会提示。** 刷新或关闭页面时,只要还有未保存的改动(含已留存的缓存),浏览器会弹出一次自己的离开确认。
150
- - **保存即清空。** 保存成功后,该文件的快照全部清除。
155
+ - **保存当前内容后清空。** 当前内容完整写入后,该文件的快照清除;保存期间的新改动仍保留未保存标记,切走或关闭后留下的较新快照不会被旧保存清掉。
151
156
 
152
157
  ### 配置
153
158
 
154
- 如已获得许可证,在 web profile 的 `cordis.patch.yml` 中添加以下配置:
159
+ 如已获得许可证,在当前 profile 的 `cordis.patch.yml` 中添加以下配置(浏览器为 `web`,桌面版为 `desktop`):
155
160
 
156
161
  | Key | 默认值 | 说明 |
157
162
  | --- | --- | --- |
158
- | `licenseKey` | `''` | SpreadJS 部署许可证。留空以带水印的试用模式运行。 |
159
- | `designerLicenseKey` | `''` | 独立的 SpreadJS Designer 部署许可证。留空保持 Designer 试用状态。 |
163
+ | `licenseKey` | `''` | SpreadJS 试用或部署许可证。无 key 的本地试用限官方支持的 HTTP(S) 回环地址。 |
164
+ | `designerLicenseKey` | `''` | 独立的 SpreadJS Designer 试用或部署许可证。 |
160
165
  | `maxSaveBytes` | `67108864` | 单次保存允许的最大字节数;超出直接拒绝,不做截断。 |
161
166
  | `trustedHosts` | `[]` | 除本机回环地址外,额外允许调用保存接口的地址(局域网部署时使用)。 |
162
167
 
@@ -167,7 +172,9 @@ DSH 在标签页被切走时会卸载文档主体,平台也没有可供拦下
167
172
  designerLicenseKey: 'YOUR_DESIGNER_KEY'
168
173
  ```
169
174
 
170
- 修改后重启 DSH 并刷新网页。
175
+ 修改后重启 DSH 并重新打开文件。
176
+
177
+ 桌面版 DSH `0.2.1-rc.5` 的页面地址是 `dsh-app://app/`,后端在本机不代表页面是 localhost,因此不能依赖无 key 的本地试用。请先向葡萄城确认适用于该协议的 SpreadJS / Designer 授权。[桌面授权与故障排查](docs/desktop-licensing.md) 记录了地址来源、配置位置和回归步骤。初始化失败时,本插件显示局部错误页,关闭或切换文档仍可继续使用 DSH。
171
178
 
172
179
  还有一项**不写在这里**:`bridge` 是「电子表格编辑器」设置页在 Web UI 里写的,代表用户选了哪一个桥。它同样属于本插件的配置,但**不该手写进 patch**——设置页会把写入结果如实报出来(写入被拒或失败时页面会说明),而手写的值不会经过这条检查。既没选过、又在 patch 里留空,含义相同:`bridge: ''` 等于「不使用」。本插件自身的设置页取代了 DSH 从这个 schema 自动生成的那一页,所以不会出现两个入口。
173
180
 
@@ -175,7 +182,7 @@ DSH 在标签页被切走时会卸载文档主体,平台也没有可供拦下
175
182
 
176
183
  | 命令 | 验证内容 |
177
184
  | --- | --- |
178
- | `npm run typecheck`、`npm test` | 类型契约,以及 215 个单元测试(保存路由的每条守卫、文件地址解析与地址构造、另存为的目标校验、默认名与失败文案、保存命令重定向的每条分支、编辑器主题桥的每条分支、查看器名称与二进制后缀声明的边界、未保存改动的缓存规则与脏状态跟踪的边界、打开时「用文件还是用缓存」的每个分支、面板状态胶囊的状态与操作矩阵、文档头部动作的发布/订阅/命令路由、按钮的尺寸字号颜色规格、「只在表格文件上出现」的判定与注册顺序,隐藏 Designer「文件」Tab 那条规则的两处身份与作用域,以及桥选择的方向与「写入被拒也要报出来」、桥字段是否真的声明为 volatile、名册自身的契约(重复 id 拒绝、disposer 语义、只在真的变化时通知)、名册与设置页在有无 settings 服务时的接线,以及设置页每一行确实是照 DSH 自己设置页的行规格画的) |
185
+ | `npm run typecheck`、`npm test` | 类型契约,以及 260 个单元测试(保存路由的每条守卫、文件地址解析与地址构造、另存为的目标校验、默认名与失败文案、保存命令重定向的每条分支、编辑器主题桥的每条分支、查看器名称与二进制后缀声明的边界、未保存改动的缓存规则与脏状态跟踪的边界、打开时「用文件还是用缓存」的每个分支、面板状态胶囊的状态与操作矩阵、文档头部动作的发布/订阅/命令路由、按钮的尺寸字号颜色规格、「只在表格文件上出现」的判定与注册顺序,隐藏 Designer「文件」Tab 那条规则的两处身份与作用域,以及桥选择的方向与「写入被拒也要报出来」、桥字段是否真的声明为 volatile、名册自身的契约(重复 id 拒绝、disposer 语义、只在真的变化时通知)、名册与设置页在有无 settings 服务时的接线,以及设置页每一行确实是照 DSH 自己设置页的行规格画的) |
179
186
  | `node scripts/smoke-client.mjs` | 构建后的浏览器半结构:loader 包装、内联的 SpreadJS 与样式、外部依赖只有 react |
180
187
  | `node scripts/smoke-node.mjs` | 构建后的宿主半:路由注册、许可证配置,以及保存路径的真实写盘、冲突与越界拒绝 |
181
188
  | `npm run probe:live` | **对着正在运行的 DSH** 跑一遍真实保存:真实 `ctx.fs` 的解析、落盘、冲突守卫、工作区越界;并核对进程里装的是不是本构建(插件版本与内置 SpreadJS 版本) |
@@ -184,7 +191,7 @@ DSH 在标签页被切走时会卸载文档主体,平台也没有可供拦下
184
191
 
185
192
  ### 许可
186
193
 
187
- 插件代码免费提供,采用 [MIT 许可证](LICENSE)。内置的 SpreadJS 和 Designer 是葡萄城商业产品,未配置许可证时可按其试用条款体验;插件免费不包含这两个产品的商业授权。
194
+ 插件代码免费提供,采用 [MIT 许可证](LICENSE)。内置的 SpreadJS 和 Designer 是葡萄城商业产品,无 key 试用限其官方支持的本地环境;桌面版 `dsh-app://app/` 需确认适用授权。插件免费不包含这两个产品的商业授权。
188
195
 
189
196
  如用于正式部署或 SaaS 服务,请根据使用场景获取适用的 SpreadJS 和 Designer 授权。
190
197
 
@@ -214,8 +221,8 @@ This package depends on no driver. SpreadJS, Designer and extensions are build/d
214
221
  Both plugins go into the **same web profile** — the driver's live-workbook capability exists only in the Web UI:
215
222
 
216
223
  ```sh
217
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.5
218
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
224
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
225
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver@0.1.4
219
226
  dsh --profile web
220
227
  ```
221
228
 
@@ -234,12 +241,14 @@ The editor **owns** the workbook you open; each driver **registers** as a candid
234
241
 
235
242
  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.
236
243
 
244
+ 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.
245
+
237
246
  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.
238
247
 
239
248
  #### Requirements
240
249
 
241
250
  - **Update the editor and driver together** for the complete instance identity, lease and recovery protocol.
242
- - **Both plugins support DSH `0.2.1-rc.4`**: 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.
251
+ - **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.
243
252
  - **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.
244
253
 
245
254
  ### Bundled Versions and File Compatibility
@@ -253,20 +262,23 @@ The **About** button in the Designer's settings tab shows the bundled versions,
253
262
 
254
263
  ### Installation
255
264
 
256
- Editor `0.2.5` supports DSH `0.2.1-rc.4` and requires Node.js ≥22.19; the user confirmed this RC works in the development environment. 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.
265
+ 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.
257
266
 
258
267
  | DSH version | Status | Notes |
259
268
  | --- | --- | --- |
260
- | `0.2.1-rc.4` | Supported and verified baseline | Typecheck, 215 unit tests, build, both smokes and package installation checks pass; the user confirmed the development environment works |
269
+ | `>=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 |
270
+ | `0.2.1-rc.4` | Desktop validation baseline | Typecheck, 260 unit tests, build, both smokes and package installation checks pass; the user confirmed the development environment works |
271
+
272
+ **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.
261
273
 
262
- Use Editor `0.2.5` together with Driver `0.1.3`; older plugins may be rejected because they declare an exact DSH version.
274
+ Use Editor `0.2.6` together with Driver `0.1.4`; older plugins may be rejected because they declare an exact DSH version.
263
275
 
264
- 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.
276
+ 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.
265
277
 
266
278
  The plugin includes SpreadJS and Designer, with no build step required. With the DSH CLI installed:
267
279
 
268
280
  ```sh
269
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.5
281
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
270
282
  dsh --profile web
271
283
  ```
272
284
 
@@ -275,7 +287,7 @@ After startup, open `.xlsx`, `.xlsm`, `.csv`, `.sjs`, or `.ssjson` from the righ
275
287
  Alternatively, install and start through npx:
276
288
 
277
289
  ```sh
278
- npx --yes @deepseek-ai/dsh@0.2.1-rc.4 plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.5
290
+ npx --yes @deepseek-ai/dsh@0.2.1-rc.4 plugin --profile web add @grapecity-software/dsh-spreadjs-editor@0.2.6
279
291
  npx --yes @deepseek-ai/dsh@0.2.1-rc.4 --profile web
280
292
  ```
281
293
 
@@ -308,7 +320,7 @@ The price is that the File tab's other categories (new, open, import, export, pr
308
320
  **另存为…** 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.
309
321
 
310
322
  - **Suggested target**: same directory, same suffix, with the localized word for a copy appended (`report.xlsx` → `report-copy.xlsx`).
311
- - **It switches this tab.** On success the tab navigates to the file just written and keeps editing there; no second tab is opened.
323
+ - **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.
312
324
  - **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.
313
325
  - **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.
314
326
  - **Containment is judged by the host**, on the same rule as a write-back: a path outside the session workspace is refused.
@@ -321,18 +333,18 @@ The harness unmounts a document body when its tab is hidden, and the platform of
321
333
  - **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.
322
334
  - **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.
323
335
  - **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.
324
- - **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.
336
+ - **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.
325
337
  - **Closing the page warns.** A reload or close raises the browser's own prompt once, as long as anything is unsaved or still cached.
326
- - **Saving clears it.** A successful save drops every snapshot for that file, so reopening starts from disk.
338
+ - **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.
327
339
 
328
340
  ### Configuration
329
341
 
330
- If you have licenses, add the following to the web profile's `cordis.patch.yml`:
342
+ If you have licenses, add the following to the active profile's `cordis.patch.yml` (`web` for the browser, `desktop` for the desktop app):
331
343
 
332
344
  | Key | Default | Description |
333
345
  | --- | --- | --- |
334
- | `licenseKey` | `''` | SpreadJS deployment key. Empty runs in evaluation mode with a watermark. |
335
- | `designerLicenseKey` | `''` | Separate SpreadJS Designer deployment key. Empty keeps Designer in evaluation mode. |
346
+ | `licenseKey` | `''` | SpreadJS trial or deployment key. Keyless local evaluation requires a supported HTTP(S) loopback address. |
347
+ | `designerLicenseKey` | `''` | Separate SpreadJS Designer trial or deployment key. |
336
348
  | `maxSaveBytes` | `67108864` | Largest payload one save may carry; a bigger one is refused, never truncated. |
337
349
  | `trustedHosts` | `[]` | Extra authorities allowed to call the save endpoint, beyond loopback (for LAN deployments). |
338
350
 
@@ -343,7 +355,7 @@ If you have licenses, add the following to the web profile's `cordis.patch.yml`:
343
355
  designerLicenseKey: 'YOUR_DESIGNER_KEY'
344
356
  ```
345
357
 
346
- Restart DSH and refresh the browser after changing the configuration.
358
+ Restart DSH and reopen the workbook after changing the configuration. DSH Desktop uses `dsh-app://app/`, so keyless localhost evaluation must not be assumed to work there. Confirm appropriate licensing with GrapeCity; see [desktop licensing and troubleshooting](docs/desktop-licensing.md).
347
359
 
348
360
  One more field is deliberately **not** in that table: `bridge` is written by the **Spreadsheet Editor** page in the Web UI and records which bridge the user chose. It is this plugin's configuration all the same, but it should not be written into the patch by hand — the page reports honestly whether a write was accepted, and a hand-written value never goes through that check. Not having chosen yet and leaving it empty in the patch mean the same thing: `bridge: ''` is "None". This plugin's own page replaces the one DSH would otherwise synthesise from the schema, so there is only ever one entry point.
349
361
 
@@ -351,7 +363,7 @@ One more field is deliberately **not** in that table: `bridge` is written by the
351
363
 
352
364
  | Command | What it proves |
353
365
  | --- | --- |
354
- | `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 |
366
+ | `npm run typecheck`, `npm test` | The type contract, and 260 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 |
355
367
  | `node scripts/smoke-client.mjs` | The built browser half's structure: loader wrapper, inlined SpreadJS and styles, react as the only runtime external |
356
368
  | `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 |
357
369
  | `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) |
@@ -360,6 +372,6 @@ One more field is deliberately **not** in that table: `bridge` is written by the
360
372
 
361
373
  ### License
362
374
 
363
- The plugin code is free under the [MIT license](LICENSE). Bundled SpreadJS and Designer are GrapeCity commercial products available for evaluation under their trial terms without license keys. The free plugin does not include commercial licenses for these products.
375
+ The plugin code is free under the [MIT license](LICENSE). Bundled SpreadJS and Designer are GrapeCity commercial products. Keyless local evaluation is limited to the environments supported by their trial terms; DSH Desktop's `dsh-app://app/` page requires confirming suitable licensing with GrapeCity. See [desktop licensing and troubleshooting](docs/desktop-licensing.md). The free plugin does not include commercial licenses for these products.
364
376
 
365
377
  For production or SaaS deployments, obtain SpreadJS and Designer licenses appropriate to your use.
package/cordis.patch.yml CHANGED
@@ -17,8 +17,8 @@
17
17
  name: '@grapecity-software/dsh-spreadjs-editor'
18
18
  inject: [webServer]
19
19
  config:
20
- # Optional: your SpreadJS license key. Empty (default) runs the
21
- # evaluation watermark build. Keep it out of the patch if you prefer
22
- # to configure it per-deployment.
20
+ # Your SpreadJS and Designer trial/deployment keys. Keyless evaluation
21
+ # requires a supported HTTP(S) loopback origin; DSH Desktop uses
22
+ # dsh-app://app/ instead. See docs/desktop-licensing.md.
23
23
  licenseKey: ''
24
24
  designerLicenseKey: ''
@@ -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
 
@@ -0,0 +1,75 @@
1
+ # 桌面版授权与故障排查
2
+
3
+ ## 为什么 localhost 能打开,桌面版却提示没有授权
4
+
5
+ DSH Desktop `0.2.1-rc.5` 将界面加载到 `dsh-app://app/`。SpreadJS 在这个页面中运行,看到的协议是 `dsh-app:`、主机名是 `app`。DSH 的文件和保存请求虽然被转发到本地 HTTP 后端,页面来源并不会因此变为 localhost。
6
+
7
+ [SpreadJS 官方试用与许可说明](https://demo.grapecity.com.cn/spreadjs/help/docs/started-guide/trial-and-license) 明确指出:没有试用或生产许可证时,只支持本地计算机环境,并以 `http(s)://localhost` 或 `http(s)://127.0.0.1` 为例。Designer 还列举了 `file:` 等本地地址,但文档并未承诺支持 `dsh-app:`。SpreadJS 和 Designer 需要各自的许可证,并须在创建工作簿和 Designer 之前赋值。
8
+
9
+ 因此,不能将「在本机运行」等同于「符合无 key 的本地试用条件」。本插件免费,不包含 SpreadJS 或 Designer 的商业授权。
10
+
11
+ ## 可行的部署方式
12
+
13
+ 向葡萄城提供以下信息,确认适用于桌面环境的试用或部署许可证:
14
+
15
+ - SpreadJS 与 Designer 版本:均为 `19.2.1`。
16
+ - 运行环境:Electron 中的 DSH Desktop。
17
+ - 页面地址:`dsh-app://app/`,自定义协议 `dsh-app:`,主机名 `app`。
18
+ - 使用方式:公开分发的插件,在用户自己的 DSH 桌面客户端创建和编辑工作簿。
19
+
20
+ [官方 Licensing FAQ](https://developer.mescius.com/spreadjs/licensing/) 提供 Named Mobile/Desktop Application License,并明确将 Electron 列为适用的分发场景。需要进一步确认许可证是否支持 `dsh-app:`、如何签发,以及插件分发所需的授权条款。仅凭主机名 `app` 不能断定普通域名许可证可用。配置了非空 key 也不代表授权校验已经通过。
21
+
22
+ 插件已经支持 `licenseKey` 和 `designerLicenseKey`,拿到适用许可证后无需更改 SDK 的授权校验。当前无 key 的本地开发可继续使用 HTTP(S) localhost 的浏览器 dev 环境。
23
+
24
+ 2026-11-05,用户用主机名 `app` 申请试用授权后反馈:运行时能够启动,但 Designer 没有正常显示。这说明主机名授权值得继续验证,还不足以确认桌面端完整可用。Editor `0.2.7` 先修复初始化失败的错误隔离,Designer 的授权和显示问题继续单独调查。
25
+
26
+ ## 配置桌面 profile
27
+
28
+ 默认配置文件为 `%USERPROFILE%\.dsh\profiles\desktop\cordis.patch.yml`。自定义 `DSH_HOME` 时,以实际目录为准。在已有配置中更新 `spreadjs-editor`,不要覆盖其他插件的配置:
29
+
30
+ ```yaml
31
+ - id: spreadjs-editor
32
+ config:
33
+ licenseKey: 'YOUR_SPREADJS_KEY'
34
+ designerLicenseKey: 'YOUR_DESIGNER_KEY'
35
+ ```
36
+
37
+ 浏览器 profile 使用 `profiles\web\cordis.patch.yml`;两个 profile 的配置互不替代。修改后重启相应 DSH 实例,再打开工作簿。不要将真实 key 提交到公共仓库。
38
+
39
+ ## 初始化失败时的行为
40
+
41
+ 插件会将创建、授权赋值或 React 文档生命周期中的异常隔离在当前编辑器,显示错误页和页面来源。可以关闭当前文件或切换到其他文档。SDK 清理失败也不会阻止后续清理和 Driver 租约撤销。
42
+
43
+ 文件导入失败仍显示该文件的加载错误,不会写回一个尚未加载成功的工作簿。初始化失败页没有自动重试;确认配置后重新打开文件。错误页不代表所有错误均由授权引起,应结合错误详情判断。
44
+
45
+ 这些保护不承诺能够拦截 SDK 在全局计时器、独立 DOM 事件或模块加载 / 求值时抛出的任意异常,也无法终止第三方 SDK 的同步死循环。
46
+
47
+ ## 回归步骤
48
+
49
+ 1. 在没有许可证的桌面 profile 打开一个 `.xlsx` 或 `.ssjson`。若 SDK 拒绝初始化,应显示局部错误页,页面来源为 `dsh-app://app`。
50
+ 2. 关闭该文件,打开文本或图片,再切换聊天。Sidebar 应继续响应;失败的工作簿不得被 Driver 获取或保存。
51
+ 3. 切换 DSH 明暗主题,确认失败页仍可显示,其他界面正常。
52
+ 4. 在 HTTP(S) localhost dev 环境打开同一工作簿,检查加载、编辑、保存和切换标签后的恢复没有退化。
53
+ 5. 获得供应商确认的两份许可证后,在 desktop profile 配置并重启,检查编辑器启动、导入、编辑、保存、Driver 操作及主题切换。
54
+
55
+ 自动测试使用可控 SDK 故障覆盖授权赋值、构造器、实例初始化及清理异常;它们不能替代第 5 步的真实许可证验证。
56
+
57
+ ## 调查依据
58
+
59
+ 2026-11-05 只读检查本机安装的 DSH Desktop `0.2.1-rc.5`:`resources/app.asar` 中的 `lib/main.js` 定义 `SCHEME = 'dsh-app'`,构造并导航到 `applicationUrl = 'dsh-app://app/'`,并注册该协议的静态资源及后端转发处理。后续 DSH 版本应以错误页展示的实际页面来源为准。
60
+
61
+ - [SpreadJS 官方试用与许可说明](https://demo.grapecity.com.cn/spreadjs/help/docs/started-guide/trial-and-license)
62
+ - [SpreadJS 官方技术支持 FAQ](https://demo.grapecity.com.cn/spreadjs/help/docs/started-guide/techsupport)
63
+ - [SpreadJS v19 licensing](https://developer.mescius.com/spreadjs/docs/getstarted/trialSpreadJS)
64
+ - [Designer Component licensing](https://developer.mescius.com/spreadjs/docs/spreadjs-designer-component/getting-started-designer)
65
+ - [Desktop application licensing FAQ](https://developer.mescius.com/spreadjs/licensing/)
66
+
67
+ ## English
68
+
69
+ DSH Desktop `0.2.1-rc.5` loads its UI at `dsh-app://app/`. Forwarding API requests to a local HTTP backend does not change the page origin. GrapeCity documents keyless local evaluation for HTTP(S) localhost / loopback addresses, so it must not be assumed to cover this custom protocol.
70
+
71
+ Ask GrapeCity to confirm licensing for SpreadJS and Designer `19.2.1` in Electron, including the `dsh-app:` protocol, hostname `app`, and distribution through a public plugin. Configure the separate `licenseKey` and `designerLicenseKey` values in the active desktop profile as shown above, then restart DSH. Nonempty keys only mean configured, not validated.
72
+
73
+ On 2026-11-05, the user reported that a trial key issued for hostname `app` let the runtime start, but Designer did not display correctly. Editor `0.2.7` contains initialization failures; it does not resolve or establish full desktop licensing and Designer compatibility.
74
+
75
+ Initialization and React lifecycle errors are contained within this document. The failure page shows the page origin; users can close or switch documents. Cleanup attempts recovery, cancels Driver leases and disposes SDK resources independently. Automated fault tests do not establish successful desktop licensing: that still requires a real key and desktop regression checks. Browser development over HTTP(S) localhost remains an alternative for keyless local evaluation under the vendor's terms.
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: c684c55380caf0511b86beebce03bf54d66b694a745e8ec3ba27c59708eb57a0
3
+ size: 52058214 bytes
4
+ sha256: 29b3bee17aaa80859288a6a26dfbd53871bd06fd554649e0af67560d0bb85389
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,122 +1,125 @@
1
- {
2
- "name": "@grapecity-software/dsh-spreadjs-editor",
3
- "version": "0.2.5",
4
- "description": "DSH sidebar SpreadJS editor with binary save, recovery and a selectable bridge for compatible drivers.",
5
- "keywords": [
6
- "deepseek-harness",
7
- "dsh",
8
- "webui",
9
- "plugin",
10
- "spreadjs",
11
- "excel"
12
- ],
13
- "type": "module",
14
- "main": "lib/index.js",
15
- "exports": {
16
- ".": {
17
- "default": "./lib/index.js"
18
- },
19
- "./client": {
20
- "default": "./lib/client.js"
21
- },
22
- "./package.json": "./package.json"
23
- },
24
- "files": [
25
- "lib/*.js",
26
- "lib/licenses",
27
- "cordis.patch.yml",
28
- "README.md",
29
- "docs/bridge-contract.md",
30
- "CHANGELOG.md",
31
- "LICENSE",
32
- "THIRD_PARTY_NOTICES.md"
33
- ],
34
- "scripts": {
35
- "build": "node scripts/build.mjs && node scripts/copy-notices.mjs",
36
- "grapecity:update": "node scripts/update-grapecity.mjs",
37
- "prepack": "npm run build",
38
- "prepublishOnly": "npm run typecheck && npm test && npm run build && node scripts/smoke-client.mjs && node scripts/smoke-node.mjs && npm run verify:package",
39
- "rebuild": "node scripts/build.mjs",
40
- "probe:live": "node scripts/probe-live-save.mjs",
41
- "release": "node scripts/release.mjs",
42
- "test": "vitest run",
43
- "typecheck": "tsc --noEmit",
44
- "verify:package": "node scripts/verify-package.mjs"
45
- },
46
- "engines": {
47
- "node": ">=22.19.0",
48
- "dsh": "0.2.1-rc.4"
49
- },
50
- "publishConfig": {
51
- "access": "public"
52
- },
53
- "repository": {
54
- "type": "git",
55
- "url": "https://github.com/GrapeCityXA/dsh-plugin-spreadjs-editor.git"
56
- },
57
- "dsh": {
58
- "engines": {
59
- "dsh": "0.2.1-rc.4"
60
- },
61
- "bundle": {
62
- "patch": "./cordis.patch.yml"
63
- },
64
- "client": {
65
- "platform": "web",
66
- "inject": [
67
- "@deepseek-ai/dsh-client-ui-theme",
68
- "@deepseek-ai/dsh-client-ui-slots",
69
- "@deepseek-ai/dsh-client-ui-sidebar-right",
70
- "@deepseek-ai/dsh-client-ui-sidebar-documentpreview"
71
- ]
72
- }
73
- },
74
- "peerDependencies": {
75
- "@deepseek-ai/cordis": "~4.0.5",
76
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.4",
77
- "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.2.1-rc.4",
78
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.4",
79
- "@deepseek-ai/dsh-client-ui-theme": "0.2.1-rc.4",
80
- "@deepseek-ai/schemastery": "~3.18.5",
81
- "react": "^18.0.0"
82
- },
83
- "devDependencies": {
84
- "@deepseek-ai/cordis": "~4.0.5",
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",
92
- "@deepseek-ai/schemastery": "~3.18.5",
93
- "@grapecity-software/spread-excelio": "19.2.1",
94
- "@grapecity-software/spread-sheets": "19.2.1",
95
- "@grapecity-software/spread-sheets-barcode": "19.2.1",
96
- "@grapecity-software/spread-sheets-charts": "19.2.1",
97
- "@grapecity-software/spread-sheets-datacharts-addon": "19.2.1",
98
- "@grapecity-software/spread-sheets-designer": "19.2.1",
99
- "@grapecity-software/spread-sheets-designer-resources-cn": "19.2.1",
100
- "@grapecity-software/spread-sheets-formula-panel": "19.2.1",
101
- "@grapecity-software/spread-sheets-ganttsheet": "19.2.1",
102
- "@grapecity-software/spread-sheets-io": "19.2.1",
103
- "@grapecity-software/spread-sheets-languagepackages": "19.2.1",
104
- "@grapecity-software/spread-sheets-pdf": "19.2.1",
105
- "@grapecity-software/spread-sheets-pivot-addon": "19.2.1",
106
- "@grapecity-software/spread-sheets-print": "19.2.1",
107
- "@grapecity-software/spread-sheets-reportsheet-addon": "19.2.1",
108
- "@grapecity-software/spread-sheets-resources-zh": "19.2.1",
109
- "@grapecity-software/spread-sheets-shapes": "19.2.1",
110
- "@grapecity-software/spread-sheets-slicers": "19.2.1",
111
- "@grapecity-software/spread-sheets-sparklines": "19.2.1",
112
- "@grapecity-software/spread-sheets-tablesheet": "19.2.1",
113
- "@types/node": "^22.0.0",
114
- "@types/react": "^18.3.0",
115
- "react": "^18.2.0",
116
- "tsdown": "^0.22.0",
117
- "typescript": "^5.9.0",
118
- "unrun": "^0.3.1",
119
- "vitest": "^3.0.0"
120
- },
121
- "license": "MIT"
122
- }
1
+ {
2
+ "name": "@grapecity-software/dsh-spreadjs-editor",
3
+ "version": "0.2.7",
4
+ "description": "DSH sidebar SpreadJS editor with binary save, recovery and a selectable bridge for compatible drivers.",
5
+ "keywords": [
6
+ "deepseek-harness",
7
+ "dsh",
8
+ "webui",
9
+ "plugin",
10
+ "spreadjs",
11
+ "excel"
12
+ ],
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "exports": {
16
+ ".": {
17
+ "default": "./lib/index.js"
18
+ },
19
+ "./client": {
20
+ "default": "./lib/client.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "lib/*.js",
26
+ "lib/licenses",
27
+ "cordis.patch.yml",
28
+ "README.md",
29
+ "docs/bridge-contract.md",
30
+ "docs/desktop-licensing.md",
31
+ "CHANGELOG.md",
32
+ "LICENSE",
33
+ "THIRD_PARTY_NOTICES.md"
34
+ ],
35
+ "scripts": {
36
+ "build": "node scripts/build.mjs && node scripts/copy-notices.mjs",
37
+ "grapecity:update": "node scripts/update-grapecity.mjs",
38
+ "prepack": "npm run build",
39
+ "prepublishOnly": "npm run typecheck && npm test && npm run build && node scripts/smoke-client.mjs && node scripts/smoke-node.mjs && npm run verify:package",
40
+ "rebuild": "node scripts/build.mjs",
41
+ "probe:live": "node scripts/probe-live-save.mjs",
42
+ "release": "node scripts/release.mjs",
43
+ "test": "vitest run",
44
+ "typecheck": "tsc --noEmit",
45
+ "verify:package": "node scripts/verify-package.mjs"
46
+ },
47
+ "engines": {
48
+ "node": ">=22.19.0",
49
+ "dsh": ">=0.2.1-rc.3 <0.3.0-0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/GrapeCityXA/dsh-plugin-spreadjs-editor.git"
57
+ },
58
+ "dsh": {
59
+ "engines": {
60
+ "dsh": ">=0.2.1-rc.3 <0.3.0-0"
61
+ },
62
+ "bundle": {
63
+ "patch": "./cordis.patch.yml"
64
+ },
65
+ "client": {
66
+ "platform": "web",
67
+ "inject": [
68
+ "@deepseek-ai/dsh-client-ui-theme",
69
+ "@deepseek-ai/dsh-client-ui-slots",
70
+ "@deepseek-ai/dsh-client-ui-sidebar-right",
71
+ "@deepseek-ai/dsh-client-ui-sidebar-documentpreview"
72
+ ]
73
+ }
74
+ },
75
+ "peerDependencies": {
76
+ "@deepseek-ai/cordis": "~4.0.5",
77
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.2.1-rc.3 <0.3.0-0",
78
+ "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": ">=0.2.1-rc.3 <0.3.0-0",
79
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.2.1-rc.3 <0.3.0-0",
80
+ "@deepseek-ai/dsh-client-ui-theme": ">=0.2.1-rc.3 <0.3.0-0",
81
+ "@deepseek-ai/schemastery": "~3.18.5",
82
+ "react": "^18.0.0"
83
+ },
84
+ "devDependencies": {
85
+ "@deepseek-ai/cordis": "~4.0.5",
86
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.4",
87
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.4",
88
+ "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.2.1-rc.4",
89
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "0.2.1-rc.4",
90
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.4",
91
+ "@deepseek-ai/dsh-client-ui-theme": "0.2.1-rc.4",
92
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-rc.4",
93
+ "@deepseek-ai/schemastery": "~3.18.5",
94
+ "@grapecity-software/spread-excelio": "19.2.1",
95
+ "@grapecity-software/spread-sheets": "19.2.1",
96
+ "@grapecity-software/spread-sheets-barcode": "19.2.1",
97
+ "@grapecity-software/spread-sheets-charts": "19.2.1",
98
+ "@grapecity-software/spread-sheets-datacharts-addon": "19.2.1",
99
+ "@grapecity-software/spread-sheets-designer": "19.2.1",
100
+ "@grapecity-software/spread-sheets-designer-resources-cn": "19.2.1",
101
+ "@grapecity-software/spread-sheets-formula-panel": "19.2.1",
102
+ "@grapecity-software/spread-sheets-ganttsheet": "19.2.1",
103
+ "@grapecity-software/spread-sheets-io": "19.2.1",
104
+ "@grapecity-software/spread-sheets-languagepackages": "19.2.1",
105
+ "@grapecity-software/spread-sheets-pdf": "19.2.1",
106
+ "@grapecity-software/spread-sheets-pivot-addon": "19.2.1",
107
+ "@grapecity-software/spread-sheets-print": "19.2.1",
108
+ "@grapecity-software/spread-sheets-reportsheet-addon": "19.2.1",
109
+ "@grapecity-software/spread-sheets-resources-zh": "19.2.1",
110
+ "@grapecity-software/spread-sheets-shapes": "19.2.1",
111
+ "@grapecity-software/spread-sheets-slicers": "19.2.1",
112
+ "@grapecity-software/spread-sheets-sparklines": "19.2.1",
113
+ "@grapecity-software/spread-sheets-tablesheet": "19.2.1",
114
+ "@types/node": "^22.0.0",
115
+ "@types/react": "^18.3.0",
116
+ "@types/react-test-renderer": "18.3.1",
117
+ "react": "^18.2.0",
118
+ "react-test-renderer": "18.3.1",
119
+ "tsdown": "^0.22.0",
120
+ "typescript": "^5.9.0",
121
+ "unrun": "^0.3.1",
122
+ "vitest": "^3.0.0"
123
+ },
124
+ "license": "MIT"
125
+ }