@grapecity-software/dsh-spreadjs-editor 0.2.0 → 0.2.2

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,59 @@
1
1
  # Release Notes / 版本记录
2
2
 
3
+ ## 0.2.2(未发布)
4
+
5
+ ### 中文
6
+
7
+ - **切走标签页不再丢掉未保存的改动**:DSH 在标签页被切走时会卸载文档主体,此前这等于销毁工作簿——未保存的编辑就此消失,而且没有任何提示。平台也没有能拦下的关闭钩子(关闭是同步的,文档定义里没有任何生命周期或否决点),所以这里不做「确认离开」的拦截,改为一律不丢:脏工作簿在主体卸载前留成快照,切回来即恢复;标签页真正关闭后,快照按文件路径继续保留,重新打开同一文件时可以恢复;若文件在磁盘上已被改动,则不会默默套用快照,而是给出「恢复 / 丢弃」两个选择。整页刷新或关闭时浏览器会提示一次。保存成功即清空该文件的全部快照。
8
+ - **未保存状态是右下角一个浮动胶囊,不占布局**:悬浮在表格右下角,只在有话说时出现——有未保存改动时显示「未保存的改动」并带一颗「丢弃」,保存成功后自己消失。(这一版中途曾做成面板顶部常驻的一行,实装后发现它虽然不高,却始终占着表格的纵向空间,于是改了回来;`0.1.5/0.1.6` 上文档头部还没有动作槽,这个胶囊就是唯一的状态提示,所以它必须保留。)「丢弃」同时覆盖两种未保存内容:本会话的缓存,以及只存在于编辑器里的改动(尚无缓存可清),所以它每次都重新从磁盘载入当前版本。
9
+ - **工作簿不再被当作文本提供查看**:为 `.xlsm`、`.sjs`、`.ssjson` 声明「二进制后缀」(`.xlsx` 一并声明),DSH 因此不会再把它们列进纯文本查看方式——此前打开这类文件,可能选到把工作簿当文本渲染的方式,看到的是一屏乱码。该声明需要 DSH `0.1.7` 及以上才生效;`0.1.5-rc.3` 会忽略这个字段,行为与之前一致,不影响编辑与保存。
10
+ - **查看器名称跟随 DSH 的语言,并与内置的只读预览区分开**:DSH `0.1.7` 起自带了只读的「表格」预览,会与本插件并列出现在同一份查看方式列表里,所以这里的名称由固定的 `SpreadJS` 改为随语言变化的「SpreadJS 编辑器 / SpreadJS Editor」,一眼能看出哪个能编辑。DSH 未提供语言服务时,则用内置的「SpreadJS 编辑器」。
11
+ - **健康接口回报版本**:`/spreadjs/api/health` 现在返回插件版本与内置 SpreadJS 版本,`npm run probe:live` 因此能判断「正在运行的 DSH 里装的是不是这一版」——此前只能靠 404 猜测,进程里跑着旧版宿主半时其余检查都是一样的结果。
12
+ - **「保存」搬进 DSH 自己的文档工具栏,不再依赖 Designer 私有模板**:DSH `0.1.7` 起,文档预览头部有一个官方动作槽(`sidebar.right.tab.document.actions`),「保存」现在注册在那里——就排在文件名和其它查看器控件之后,有未保存改动时它前面会多一个标记点,像 Excel 标题栏的 `*`。此前只有改写 Designer「文件」菜单的私有模板才点得到保存,模板一变就失效;那处改写随后**整个删掉**了——既然保存已经另有正式入口,就没有理由再依赖 Designer 的内部模板结构,插件也因此少一处私有结构依赖。必须贴着文件解释的「恢复 / 丢弃」由面板右下角的浮动胶囊承载。
13
+ - **Designer 自带的「文件」标签页已从 Ribbon 上隐藏**:它里面的保存是**下载副本**,与头部那颗**写回**的保存只隔一次点击,两颗都在就是误存副本的来源——现在这个编辑器里只剩一处保存(文档头部「保存 / 另存为…」与 `Ctrl+S`)。隐藏用的是一条按产品**公开命令名**(`CommandNames.FileMenuButton`)加模板元素类名的 CSS,作用域限定在本插件面板内,有单测钉住,创建编辑器时控制台会回报命中元素数与它算出的 `display`,所以将来产品改名、规则失效会被看见。代价是「文件」页里的新建 / 打开 / 导入 / 导出 / 打印 / 信息也看不到了,需要哪一项可以单独接回 Ribbon。
14
+ - **新增「另存为」,把工作簿(包括重置成空白的新工作簿)写进工作区**:工具栏「另存为…」询问一个相对会话工作区的路径,默认是「同目录 + 同后缀 + 文件名加『副本』」;写成功后就地切换到那个文件继续编辑,不会另开标签页。同名文件不会被覆盖——宿主对没有版本基线的写入一律拒绝,对话框直接说「目标文件已存在,请换一个名字」。只接受本插件能打开的 5 种后缀(`.xlsx / .xlsm / .csv / .sjs / .ssjson`):写出格式由后缀决定,别的后缀会写出打不开的文件,因此在提交前就拦下。
15
+ - **面板内残留的固定英文提示改走语言表**:新工作簿提示、载入中、缺少完整字节的提示原先固定为英文,现在随 DSH 语言切换。
16
+ - **头部两颗按钮改用 DSH 自己控件的规格**:28px 高、12px 字号、常态用次要色、悬停转主要色并给悬停底色、禁用转三级色——与文档头部自己的「查看器名称」和图标按钮同一套值(照抄数值而不是引用内部 UI 包:第三方客户端插件不能假定内部的 `@deepseek-ai/*` 在运行时一定可解析,解析失败会连编辑器一起挂掉)。有一条单测钉住这些数值。
17
+ - **头部两颗按钮只在表格文件上出现,并排在「用本地应用打开」之前**:动作槽对所有预览文件都渲染,因此按钮现在只在插件自己的编辑器占据屏幕时才画出来(此前打开任意 Markdown、图片也会看到两颗按不动的按钮);顺序改用槽位公开的 `order`(`-10`,DSH 自己给「头部动作排第一」用的值),不再由插件加载顺序决定,未保存标记点也因此落在这一组的第一项。
18
+ - **新增「SpreadJS 桥」设置页:决定由谁来使用屏幕上打开的那份工作簿**:编辑器把它手里的**活工作簿**交给用户在设置里选中的那一个「桥」(bridge)——装上 `@grapecity-software/dsh-spreadjs-driver` 之后,AI 就能直接改你正在看的这张表,而不是另开一个文件去写。名册是**登记制**:任何插件都可以报名,但**只有一个**会拿到工作簿,就是用户选中的那个;工作簿是**递出去的引用,不是副本**,所以通过它改的样式会立刻出现在表格里。没装任何桥时名册为空,编辑器行为与没有这项功能时完全一样。
19
+ - **这个选择一定留得住**:存在 DSH 自己的设置文档里(本插件那条 Loader 记录,命名空间 `spreadjs-editor`,字段 `bridge`)。让写入被接受要靠 schema 里声明 `.volatile()`——DSH 只接受对 volatile 字段的设置写入,否则每次点选都被宿主拒绝;而**拒绝是以 resolve `false` 的形式回来的,不是抛错**,所以只挂 `.catch` 会让它看起来一切正常、重启后却变回「不使用」。设置页现在两种失败都会说明(`refused` / `failed`),因为后果要到下次启动才看得见。字段**故意不给默认值**:「从没选过」与「明确选了不使用」是两个不同状态,将来若要加「默认选中唯一的桥」只能靠这个区分。指向已被卸载的桥的旧选择,会在名册确实拿到条目且其中没有它时清掉;**名册为空时一律保留**——插件还没加载完和桥被卸载两种情况长得一样,那时清掉就等于把重装本该恢复的选择扔掉。
20
+ - **保存失败不再被报告成保存成功**:从页面外驱动编辑器的那条路(桥,以及任何等在 promise 上的调用方)现在拿到的是真话。此前四种情况都会被吞掉——工作簿还没载入、已有另一次保存在进行、写盘失败、以及「没有文件可写、只能下载一份」——前两种静默返回,后两种只写进面板右下角的状态栏,而 promise 照样 resolve,于是调用方会以为文件已经写好。现在这四种都抛错;状态栏照旧先说给人看,两处并不重复:一处说给看着面板的人,一处说给等在 promise 上的人。面板头部与 Designer 自己的保存命令没有 promise 可传,仍然只报状态栏,但那一处吞掉失败是有名有姓、写了原因的。
21
+ - **设置页按 DSH 自己设置页的行规格重画,不再是裸按钮**:这一页最初**只有类名、一条样式都没有**,所以浏览器按默认 `<button>` 画——灰底、居中、内缩;标题与「使用中」是两个相邻的行内元素、中间没有间距,于是挤成了「SpreadJS Driver使用中」。现在整页照 DSH 自己设置页的行规格(`.5px` 分隔线、每行 16px 纵向留白、标签与右侧之间 24px、标题 14px/20px、说明 12px/18px),颜色全部走 `--dsw-*` 令牌,因此跟随浅色/深色。**选中改成右侧一个对勾,不再写「使用中」**:这一页知道的是「选了谁」,不是「谁正握着工作簿」——桥随面板开关而变,而设置文档只记一个选择,写「使用中」超出了它实际知道的事;对勾是形状,顺带省掉一处翻译。
22
+ - **这一页现在是一组单选,不是三个开关**:容器 `role="radiogroup"`、每行 `role="radio"` + `aria-checked`,整组只有一个 Tab 停靠点,方向键在组内移动并选中。此前用的是 `aria-pressed`,那等于说「这个按钮被按下了」,对「选一个即排除其余」是错的语义;这也正是 DSH 自己画同类列表的方式。空名册时的说明文字也改走了语言表(此前写死英文)。
23
+ - 构建与验收基线升到 DSH `0.1.7-rc.2`(`npm run typecheck`、201 项单测、两个 smoke、`verify:package`、`probe:live` 全部通过)。插件在 `0.1.5-rc.3` 上仍可运行(该版本没有文档头部动作槽,保存入口只有 `Ctrl+S`,未保存状态靠面板右下角的浮动胶囊)。
24
+
25
+ ### English
26
+
27
+ - **Switching tabs no longer throws unsaved edits away**: the harness unmounts a document body as soon as its tab is hidden, which used to destroy the workbook and silently take every unsaved edit with it — and the platform offers no close hook to intercept (a close is synchronous, and a document definition has no lifecycle or veto point). So leaving is still never blocked; nothing is dropped either. A dirty workbook is kept as a snapshot before its body goes away and put back when the tab returns, the snapshot also survives a real tab close under the file's path so reopening that file can restore it, and a file that changed on disk since the snapshot is *offered* rather than silently applied (restore or discard). Closing or reloading the page raises the browser's own prompt once, and a successful save clears every snapshot for that file.
28
+ - **The unsaved state is a floating pill in the bottom-right corner, costing no layout**: it hovers over the sheet and appears only while it has something to say — `Unsaved changes` with a Discard action while edits are pending, and gone once everything is saved. (This release first turned it into a permanent row at the top of the panel; in use that row kept vertical space away from the spreadsheet for the whole session, so it became a pill again. On `0.1.5`/`0.1.6`, where the document header has no actions slot, the pill is the only state indicator there is — which is why it stays.) Discard covers both shapes of pending work, a buffer from this session and edits that only exist inside the Designer, so it always reloads the file's current bytes.
29
+ - **Workbooks are no longer offered as text**: `.xlsm`, `.sjs` and `.ssjson` (and `.xlsx`) are now declared as binary suffixes, so the harness stops listing a plain-text view for them — until now that view could render a workbook as a screenful of mojibake. The declaration takes effect on DSH `0.1.7` and later; `0.1.5-rc.3` ignores the field and behaves exactly as before, editing and saving unaffected.
30
+ - **The viewer name follows the harness language and no longer collides with the built-in preview**: from DSH `0.1.7` the product ships its own read-only spreadsheet preview, listed beside this plugin, so the name changes from a fixed `SpreadJS` to a localized "SpreadJS 编辑器 / SpreadJS Editor" that says which one edits. Without a locale service the shipped Chinese name is used.
31
+ - **The health endpoint reports versions**: `/spreadjs/api/health` now returns the plugin and bundled SpreadJS versions, so `npm run probe:live` can tell whether the running DSH carries this build — previously only a 404 distinguished an old host half, and every other check looked the same.
32
+ - **Save moved into the harness's own document header, off the Designer's private template**: from DSH `0.1.7` a document header publishes an official actions slot (`sidebar.right.tab.document.actions`), and Save is now registered there — after the file name and the other viewers' controls, carrying a mark while anything is unsaved, like Excel's title-bar `*`. Until now Save was only reachable through a rewrite of the Designer's private File-menu template, which stops working the moment that template changes; that rewrite has since been **deleted outright** — with Save having a home of its own there was no reason left to depend on the Designer's internal template structure, and the plugin carries one private-structure dependency fewer. The Restore/Discard decision that has to be explained next to the file lives in the panel's floating pill.
33
+ - **The Designer's own File tab is hidden from the ribbon**: its Save *downloads a copy*, one click away from the header's Save that writes the file back, and having both is how the wrong one gets pressed — this editor now has exactly one place to save (the header's Save / Save As…, plus `Ctrl+S`). The hiding is one CSS rule keyed on the product's **public** command name (`CommandNames.FileMenuButton`) and the class its template draws, scoped to this plugin's panel; a unit test pins it and creating the editor logs how many elements matched and what `display` they compute to, so a rename in a future build shows up as a dead rule. The cost is that the File tab's new / open / import / export / print / info entries are gone from the UI too — ask and we will wire back the one you need.
34
+ - **New Save As writes the workbook — including one reset to blank — into the workspace**: the header's **另存为…** asks for a path relative to the session workspace (suggesting the same directory and suffix with a localized "copy" in the name) and, on success, moves this tab onto the file just written instead of opening a second tab. An existing name is never overwritten — the host refuses any write that carries no version baseline — and the dialog says so plainly. Only the five suffixes this plugin opens (`.xlsx / .xlsm / .csv / .sjs / .ssjson`) are accepted, because the written format follows the suffix and anything else would produce a file that cannot be reopened here.
35
+ - **The remaining hard-coded English panel strings now go through the locale table**: the new-workbook notice, the loading notice and the "complete file contents" notice.
36
+ - **The two header buttons now follow the harness's own control spec**: 28px tall, 12px text, secondary colour at rest, turning primary with the hover background on hover, and tertiary when disabled — the same values the document header's own viewer-name button and icon buttons use. The values are copied rather than imported, because a third-party client plugin cannot assume an internal `@deepseek-ai/*` package resolves at runtime and a failure there would take the whole editor down; one unit test pins them.
37
+ - **The two header buttons appear on spreadsheets only, and before open-in-app**: the actions slot renders for every previewed file, so the buttons are now drawn only while this plugin's own editor is the body on screen — previously every markdown file and image in the workspace carried two buttons that could not do anything. Their position now uses the slot's documented `order` (`-10`, the value DSH itself uses for "first in a header actions list") instead of plugin load order, which also puts the unsaved mark on the first item of the group.
38
+ - **New Settings page: a "SpreadJS bridge" chooses who may drive the open workbook**: the editor hands the **live workbook** to whichever bridge the user selects in Settings — with `@grapecity-software/dsh-spreadjs-driver` installed, that lets the AI edit the very sheet on screen instead of writing a separate file. The roster is a **register**, not a broadcast: any plugin may enter, but exactly one holds the workbook, the one the user picked. What is handed over is a **live reference, never a copy**, which is why a style written through it appears in the sheet immediately. With no bridge installed the roster is empty and the editor behaves exactly as it does without the feature.
39
+ - **The choice is kept.** It lives in DSH's own settings document (this plugin's Loader entry: namespace `spreadjs-editor`, field `bridge`). Making the write *accepted* needs the field declared `.volatile()` in the schema — DSH accepts a settings write only for a volatile field — and a refusal comes back as a **resolved `false` rather than a rejection**, so handling only the rejection made every click look fine while the choice evaporated on the next start. The page now reports both kinds of failure (`refused` / `failed`), because the consequence is not visible until the next launch. The field deliberately carries **no default**: "never chosen" and "chosen None" are different states, and only that difference lets a future default-if-unset rule exist. A stored id that no longer names anything is cleared once the roster has entries and none of them is it — while the roster is **empty** it is kept, because "no provider has loaded yet" and "the provider is gone" look identical, and clearing on that would throw away a choice a reinstall should restore.
40
+ - **A failed save is no longer reported as a save**: the path that drives the editor from outside the page — the bridge, and any caller waiting on the promise — now hears the truth. Four cases used to be swallowed: a workbook that is not ready, another save already running, a failed write, and "there is no file to write, so the bytes went to a download". The first two returned silently; the last two only reached the status line while the promise still resolved, so the caller went on to describe a file that was never written. All four throw now. The status line still reports first, for the person looking at the panel: one voice for them, one for whoever is waiting on the promise. The header button and the Designer's own Save command have no promise to carry a rejection, so they still report only to the status line — but that is now one named, explained place rather than a habit.
41
+ - **The Settings page was redrawn to the harness's own settings-row spec, off bare buttons**: the page shipped with class names and **no rule for any of them**, so the browser drew its default `<button>` — grey, centred, inset — and the title and the "In use" tag were two adjacent inline spans with no gap between them, collapsing into `SpreadJS Driver使用中`. It now follows the product's own settings rows (a `.5px` separator, 16px of vertical padding, 24px between the label and the right-hand side, a 14px/20px title and 12px/18px prose) and every colour is a `--dsw-*` token, so it follows light and dark. **The selection is now a tick on the right rather than the words "In use"**: what this page knows is *which bridge was chosen*, not *which one is holding a workbook right now* — a bridge comes and goes with the panel, while the settings document records only a choice — so the words claimed more than the page can know. A tick is also a shape, which saves a translation.
42
+ - **The page is now one radio group rather than three toggles**: `role="radiogroup"` over `role="radio"` rows with `aria-checked`, a single tab stop for the group, and the arrow keys moving the selection within it. It used `aria-pressed` before, which says "this button is currently pressed" — the wrong claim about a choice that excludes the others. It is also how the harness draws its own lists of this kind. The no-bridge prose also goes through the locale table now (it was hard-coded English).
43
+ - Built and verified against DSH `0.1.7-rc.2` (`npm run typecheck`, 201 unit tests, both smokes, `verify:package`, `probe:live` all green). The plugin still runs on `0.1.5-rc.3`, which has no document-header actions slot — there, `Ctrl+S` is the only Save entry and unsaved work shows in the panel's floating pill.
44
+
45
+ ## 0.2.1
46
+
47
+ ### 中文
48
+
49
+ - **编辑器跟随 DSH 的主题,而不是操作系统**:此前深色与否取决于系统的 `prefers-color-scheme`,所以在 DSH 里选「浅色 / 深色」时编辑器毫无反应——两者不一致时最明显。现在改为订阅 DSH 的主题服务;DSH 偏好设为「跟随系统」时,仍然随系统一起切换。
50
+ - **面板自身配色改用 DSH 真实存在的主题 token**:原先用的几个 `--dsw-*` 变量 DSH 并未定义,那些规则实际一直落在硬编码的浅色回退值上,深色下会出现白色底块。
51
+
52
+ ### English
53
+
54
+ - **The editor follows the harness theme, not the operating system**: toolbars, sheet area and panel used to darken according to the system's `prefers-color-scheme`, so choosing light or dark inside the harness changed nothing here — most visible when the two disagreed. The plugin now subscribes to the harness theme service, and a harness preference of `system` still tracks the OS.
55
+ - **The panel's own chrome reads real harness theme tokens**: several `--dsw-*` variables it used are not defined by DSH, so those rules were silently falling back to hard-coded light colours and left white patches in dark mode.
56
+
3
57
  ## 0.2.0
4
58
 
5
59
  > 破坏性变更:本版要求 DSH `>=0.1.5-rc.3`,并改用 DSH 自带的右侧栏,不再需要任何第三方侧边栏插件;升级前请先升级 DSH。仓库里准备过的 `0.1.5` 从未发布,其内容已并入本版。
@@ -10,7 +64,6 @@
10
64
  - **浅色/深色完整跟随系统**:此前只有工具栏和面板变深、工作表区域仍是白底,现在一起切换。
11
65
  - **界面更干净**:去掉面板顶部的文件名标题栏(DSH 标签页已有)和底部状态栏,保存与冲突提示改为右下角浮层。
12
66
  - **新增「关于」**:Designer「设置」标签里可查看插件版本、内置 SpreadJS 与 Designer 版本、授权状态和版权信息。
13
- - **可以让 AI 直接改你正在编辑的表**:另装配套的 `@grapecity-software/dsh-spreadjs-driver` 后,可通过对话让 Agent 修改当前工作簿;未安装时插件行为不变。
14
67
  - 内置 SpreadJS 升级到 19.2.0;新增配置项 `maxSaveBytes`(默认 64 MB)与 `trustedHosts`。
15
68
 
16
69
  ### English
@@ -19,7 +72,6 @@
19
72
  - **Light and dark follow the system all the way**: previously only the toolbars and panels darkened while the sheet stayed white.
20
73
  - **A cleaner panel**: the file-name title bar (the harness tab already shows it) and the status bar are gone, and save or conflict messages appear as a floating notice in the corner.
21
74
  - **A new About dialog**: the Designer's settings tab shows the plugin and bundled versions, the licence state and the copyright.
22
- - **Let an agent edit the sheet you are looking at**: with the companion `@grapecity-software/dsh-spreadjs-driver` installed, you can ask an agent to change the open workbook in conversation; without it the plugin behaves as before.
23
75
  - Bundled SpreadJS moved to 19.2.0; new configuration `maxSaveBytes` (default 64 MB) and `trustedHosts`.
24
76
 
25
77
  ## 0.1.4
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 dsh plugin contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh plugin contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -18,10 +18,50 @@ A DeepSeek Harness Web UI plugin that opens, views, and edits Excel / SpreadJS f
18
18
 
19
19
  - 支持 `.xlsx`、`.xlsm`、`.csv`、`.sjs`、`.ssjson` 文件。
20
20
  - 从文件树打开工作簿,使用 SpreadJS Designer 编辑表格。
21
- - 编辑后可直接保存回原文件:原子写入、限制在会话工作区内、带冲突检测。
22
- - 编辑器跟随系统切换浅色和深色主题,工具栏与工作表区域一起变。
21
+ - 编辑后可直接保存回原文件:原子写入、限制在会话工作区内、带冲突检测。保存按钮在 DSH 文档头部自己的工具栏上,未保存时带一个标记点;它与「另存为…」的尺寸、字号、颜色都按 DSH 头部自身控件的规格绘制,而且**只在表格文件上出现**。
22
+ - 「另存为」可以把当前工作簿(含新建的空白工作簿)写进工作区的另一个路径,成功后就地切换到那个文件;同名文件不会被覆盖。
23
+ - 编辑器跟随 DSH 的浅色/深色主题,工具栏与工作表区域一起变;DSH 偏好设为「跟随系统」时自然跟着系统走。
23
24
  - 「设置」标签里有「关于」按钮:内置版本、授权状态与版权信息在弹窗里。
24
25
 
26
+ ### 配合 SpreadJS Driver 使用
27
+
28
+ 本插件**单独使用就是完整的**:打开、编辑、保存工作簿都不需要别的插件。但配上 driver,它会多出一件单独做不到的事——**让 AI 直接改你屏幕上这份表**。
29
+
30
+ [`@grapecity-software/dsh-spreadjs-driver`](https://www.npmjs.com/package/@grapecity-software/dsh-spreadjs-driver) 是配套的 driver。它**自己就能用**,带着一整套文件级工具(新建 `/` 导入 `/` 状态 `/` 执行 `/` 导出 `/` 截图 `/` 草稿)和一个 `spreadjs` 编排 skill——但那些工具作用在**工作区里的文件**上,你只能看到结果,看不到过程。把这个编辑器装上并在设置里选中 driver,它就额外拿到一份**活引用**:改的是你正打开的那张表,改完当场就在你眼前。
31
+
32
+ #### 安装
33
+
34
+ 两个插件都装在**同一个 web profile** 下——driver 的活工作簿能力只存在于 Web UI 里:
35
+
36
+ ```sh
37
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
38
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
39
+ dsh --profile web
40
+ ```
41
+
42
+ 装完重启 DSH,打开任意一个表格文件,然后在 **设置 → SpreadJS 桥** 里选中 **SpreadJS Driver**。(没装任何 driver 时这一页只有「不使用」一项,编辑器行为与本功能不存在时完全一样。)
43
+
44
+ > 「装了」不等于「激活」:两个包都必须出现在 profile 的 `dsh.profile.bundles` 里。光 `pnpm add` 或 `dsh plugin add` 不一定替你写进去,装完请开一个真实会话确认工具确实在场。
45
+
46
+ #### 两边是怎么配合的
47
+
48
+ 编辑器**持有**你打开的那份工作簿,每个 driver **登记**为候选人;名册里可以有多个(比如我们的测试夹具),但**只有一个**会拿到工作簿——就是你在设置里选中的那一个。
49
+
50
+ | | |
51
+ | --- | --- |
52
+ | **递出去的是活引用,不是副本** | 工作簿从不离开页面、也从不被序列化。所以通过它写的样式、公式、格式,当场就出现在表格里。 |
53
+ | **默认不落盘** | AI 的改动先活在工作簿里,你看着满意了它才会要求保存。保存走的还是文档头部那颗「保存」,因此冲突检测、工作区围栏、原子写入一条都不少。 |
54
+ | **名册只认被选中的那个** | 设置里改选别人、或选「不使用」,driver 的**全部**工具与 skill 都会让位。这个设置命名的是「谁来管这里的电子表格」,而不只是「谁暂时拿着这份活文档」。 |
55
+ | **编辑器始终是守门人** | 名册是候选人列表,不是广播。没有被递到工作簿的插件,没有任何办法去够到它。 |
56
+
57
+ 装上之后,AI 会多出一件工具 `sjs_live_execute`(改你正打开的表)和一个 `sjs_live_status`(看看现在有没有活的设计器)。driver 其余的工具照旧作用于文件。
58
+
59
+ #### 要求
60
+
61
+ - **本插件的「桥」名册随本版(0.2.2)一同发布。** 更早的编辑器上装着 driver 不会报错,只是 `sjs_live_execute` 永远找不到设计器——所以两个插件都建议用最新版。
62
+ - **两侧都需要 DSH `0.1.7-alpha.1` 或更新**(设置命名空间从 Loader 记录 id 派生,是 0.1.7 才有的)。本插件自身在 `0.1.5-rc.3` 上仍可运行,见下面的版本表——但**这一对**不行。
63
+ - **driver 自己还有前提**:本机装了 Chrome 或 Edge(引擎跑在真实浏览器进程里),Node ≥ 22.19,导出含中日韩文字的 PDF 需要系统里有可嵌入的 `.ttf` 字体。这些是 driver 的要求,细节见[它的 README](https://github.com/GrapeCityXA/dsh-plugin-spreadjs-driver#requirements)。
64
+
25
65
  ### 内置版本与文件兼容性
26
66
 
27
67
  | 组件 | 内置版本 |
@@ -35,6 +75,14 @@ A DeepSeek Harness Web UI plugin that opens, views, and edits Excel / SpreadJS f
35
75
 
36
76
  需要 DSH `>=0.1.5-rc.3`。插件使用 DSH 自带的右侧栏,**不需要安装任何第三方侧边栏插件**。
37
77
 
78
+ | DSH 版本 | 状态 | 说明 |
79
+ | --- | --- | --- |
80
+ | `0.1.7-rc.2` | ✅ 构建与验收基线 | `typecheck`、201 项单测、两个 smoke、`verify:package`、`probe:live` 全部通过(`probe:live` 的版本核对需要先重启 DSH,见下文) |
81
+ | `0.1.6.x` | ⚪ 未验证 | 没有核对过这个区间的平台:既未逐项跑验收,也不确定它是否已提供下表中的能力 |
82
+ | `0.1.5-rc.3` | ✅ 上一版验收基线 | 插件最初就是对着它构建并验收的;它会忽略「二进制后缀」声明,查看方式列表里仍可能出现纯文本一项;文档头部没有动作槽,因此保存入口只有 `Ctrl+S`,未保存状态靠面板右下角的浮动胶囊 |
83
+
84
+ `0.1.7` 起平台提供了三样本插件会用到的能力:文档定义里的**二进制后缀**声明(决定是否还提供纯文本查看方式)、内置的只读 Excel 预览(会与本插件并列出现在查看方式列表里),以及文档头部**官方动作槽** `sidebar.right.tab.document.actions`(「保存 / 另存为」就注册在这里)。插件在 `0.1.5-rc.3` 上照常工作,只是拿不到这三点中的前两点,工具栏上的这两个按钮也不出现。
85
+
38
86
  插件内置 SpreadJS 和 Designer,无需编译。使用已安装的 DSH CLI 安装并启动:
39
87
 
40
88
  ```sh
@@ -55,29 +103,47 @@ npx --yes @deepseek-ai/dsh@latest --profile web
55
103
 
56
104
  ### 保存
57
105
 
58
- 保存只有一处入口:Designer「文件」菜单里的「保存到工作区」,就在原本内置"另存为 .sjs"那颗按钮的位置上。改动写回你打开的那个文件,不会再问你"要不要保存",因为按下保存本身就是你的意图。
106
+ 保存的正式入口是 **DSH 文档头部自己的工具栏**(打开工作簿后,文件名旁边那颗「保存」)。它注册在 DSH `0.1.7` 起的官方动作槽 `sidebar.right.tab.document.actions` 里,与其它查看器的控件排在一起;有未保存改动时,「保存」前会多一个标记点,像 Excel 标题栏的 `*`。改动写回你打开的那个文件,不会再问你"要不要保存",因为按下保存本身就是你的意图。
107
+
108
+ 同一份写回也挂在 **`Ctrl+S` 和 Designer 工具栏上的保存按钮**上:插件把 Designer 的 Save 命令重定向到写回,所以这两处按下都不会再弹出下载框。(Designer 自带的「文件」标签页已从 Ribbon 上隐藏,见本节末尾。)
109
+
110
+ 这两颗头部按钮并不引用 DSH 的按钮组件——第三方客户端插件不能假定内部的 `@deepseek-ai/*` 包在运行时一定可解析,解析失败会连编辑器一起挂掉——而是照抄了邻居的规格:28px 高、`padding: 0 6px`、12px 字号、常态 `--dsw-alias-label-secondary`,悬停转 `--dsw-alias-label-primary` 并给 `--dsw-alias-interactive-bg-hover` 底色,禁用转 `--dsw-alias-label-tertiary` 且不再响应。也就是头部「查看器名称」与图标按钮的那一套;有一条单测钉住这些数值,避免以后悄悄漂移。
111
+
112
+ 它们**只在本插件负责的文件上画出来**。这个动作槽对每个预览文件都会渲染,所以按钮要在插件自己的编辑器占据屏幕时才出现——否则工作区里每个 Markdown、图片、日志旁边都会多两颗按不动的按钮。产品自己的「用本地应用打开」也是这个形状:没有可打开的应用时就什么都不画。
113
+
114
+ 顺序上它们排在「用本地应用打开」**之前**。槽位是 list,条目按公开的 `order` 排序(先 `priority`,再 `order`,最后才轮到注册顺序),插件注册在 `-10` —— 而这正是 DSH 自己给「头部动作里排第一」用的值(`dsh-client-ui-agent-preset` 注册会话头部标签时就是 `order: -10`)。这样「先保存、再考虑外部打开」是确定的阅读顺序,也不再受插件加载顺序影响;未保存的标记点也因此落在这一组的第一项上,一眼就能看到。
115
+
116
+ Designer 自带的「文件」标签页已经**从 Ribbon 上隐藏**:插件不再改写它的私有菜单模板(那段改写已删掉),也不让它留在那里——因为「文件」页里的保存是**下载副本**,与上面那颗**写回**的保存只隔一次点击,两颗都在就是误存副本的来源。现在这个编辑器里只有一处保存:文档头部的「保存 / 另存为…」,加上 `Ctrl+S`。
117
+
118
+ 代价是「文件」页里的其它分类(新建、打开、导入、导出、打印、信息)也一并看不到了——这是隐藏整页的必然结果,如果你需要其中某一项,告诉我,我们把它单独接回 Ribbon。隐藏方式是按产品公开的命令名(`CommandNames.FileMenuButton`)加它模板画出的元素类名做一条 CSS,作用域限定在本插件的面板内;这条规则有单测钉住,创建编辑器时控制台也会回报命中的元素数与它实际算出的 `display`,所以将来产品改名、规则失效会被看见,而不是悄悄多出一颗下载按钮。
59
119
 
60
- - **「文件」菜单只剩一个保存动作。** Designer 原本的保存是「弹框下载到本地」;本插件把它换成写回你打开的那个文件,菜单里只保留这一个动作,新建、打开、导入、导出、打印、信息等分类一并移除。需要一份副本时,用 DSH 自己的文件下载即可。
61
120
  - **工作簿还没载入完时不让保存。** 载入中的工作簿是空的,此时保存会用空表覆盖原文件;载入完成前保存命令会拒绝并说明。
62
121
  - **不会冲掉别人的修改。** 打开文件时插件记下其内容哈希;保存前重新比对,若磁盘上的文件在此期间被改过(Agent 改过、你在别处存过),保存会被拒绝并说明原因,而不是覆盖掉那些改动。
63
122
  - **不会写出半个文件。** 同目录临时文件写完后 rename 覆盖,其他程序任何时候读到的都是完整文件。
64
123
  - **写不出工作区。** 目标必须在当前会话的工作区内,越界(含 `..` 逃逸)直接拒绝。
65
124
  - **只有循环回环地址可用。** 保存接口与 DSH 其它接口一样只服务本机来源;通过局域网地址访问时,需要把该地址加进 `trustedHosts`。
66
125
 
67
- ### 配合 AI 使用:让 Agent 改你正在看的表
126
+ ### 另存为
68
127
 
69
- 再装上 [`@grapecity-software/dsh-spreadjs-driver`](https://www.npmjs.com/package/@grapecity-software/dsh-spreadjs-driver),就可以直接对 Agent 说"把金额列改成红色"——改动**立刻出现在你正看着的这张表上**。
128
+ 头部工具栏上的「另存为…」把当前工作簿写到工作区的**另一个**路径——包括在 Designer 里重新置空的空白工作簿。它不碰原来那个文件。
70
129
 
71
- ```sh
72
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
73
- ```
130
+ - **默认目标**:同目录、同后缀,文件名后面加「副本」(`报表.xlsx` → `报表-副本.xlsx`)。
131
+ - **成功后就地切换**:这个标签页会直接开到刚写下的文件上继续编辑,不会另开一个标签页。
132
+ - **同名不覆盖**:目标已存在时宿主会拒绝写入(写回要求带版本基线,另存为没有),对话框直接说「目标文件已存在,请换一个名字」,而不是默默覆盖。
133
+ - **只接受 5 种后缀**:`.xlsx`、`.xlsm`、`.csv`、`.sjs`、`.ssjson`。写出格式由后缀决定,其它后缀会写出打不开的文件,所以在提交前就拦下。
134
+ - **越界同样被拒**:路径解析与围栏由宿主按写回那一套规则判定,工作区之外一律拒绝。
135
+ - **写不进去的另一种可能**:正在载入或另一次保存尚未结束时,对话框会说「编辑器还没准备好」,不会假装成功。
74
136
 
75
- - **改的是同一个对象,不是文件副本。** 编辑器把当前工作簿交给该插件的 `spreadjsHostBridge` 服务;两个插件的浏览器半跑在同一个页面里,所以改动直接落在 Designer 正在渲染的那份文档上——**不会覆盖你尚未保存的编辑**(这一点是按文件走的路子给不了的)。
76
- - **磁盘上的文件不动。** 改动只活在编辑器里,除非你明确要求保存——所以你有机会先看清楚,再决定要不要落盘。
77
- - **只有它拿得到。** 工作簿是编辑器**主动交出去**的,不存在"查找别人的工作簿"这类入口,同页面上的其他插件够不到。
78
- - 该插件本身还带一整套表格工具(新建、导入、导出 `.xlsx` / `.csv` / `.pdf`、截图),**不装编辑器也能单独使用**。
137
+ ### 未保存的改动
79
138
 
80
- > 需要本插件 **0.2.0 或更高版本**——更早的版本里没有这个桥,装上 driver 也不会有联动。
139
+ DSH 在标签页被切走时会卸载文档主体,平台也没有可供拦下的关闭钩子(关闭是同步的,文档定义里既无生命周期也没有否决点),所以这里**不做「确认离开」的拦截**,而是保证不丢:
140
+
141
+ - **切走再切回不丢。** 有未保存改动的工作簿会在主体卸载前留成一份快照,切回同一个标签页时原样恢复,不需要重新读盘。
142
+ - **关掉标签页也不丢。** 快照同时按文件路径保留;重新打开同一个文件时,只要磁盘上的文件仍是快照写下的那一版,就自动恢复,并在面板右下角的胶囊里说明来源。
143
+ - **文件被外部改过时不默默套用。** 若磁盘上的文件已经变了(Agent 改过、你在别处存过),面板右下角会浮出一个胶囊提示「存在未保存的改动缓存,但文件已在磁盘上被修改」,由你选择**恢复**(用缓存继续,之后保存即写入磁盘)或**丢弃**(重新读盘)。
144
+ - **看得见,而且有两处。** 头部工具栏的「保存」在有未保存改动时带一个标记点(像 Excel 标题栏的 `*`),这是 DSH `0.1.7` 及以上才有的位置;面板右下角另有一个浮动胶囊:有未保存改动时显示「未保存的改动」并带一颗「丢弃」,保存成功后自己消失。它悬浮在表格上方、不占任何布局高度——这是它从「面板顶部常驻一行」改回来的原因——而 `0.1.5/0.1.6` 上文档头部还没有动作槽,这个胶囊就是唯一的状态提示,所以它必须留着。改动只会替换胶囊里的文字与按钮,且它只在有话说时出现。
145
+ - **整页关闭会提示。** 刷新或关闭页面时,只要还有未保存的改动(含已留存的缓存),浏览器会弹出一次自己的离开确认。
146
+ - **保存即清空。** 保存成功后,该文件的快照全部清除。
81
147
 
82
148
  ### 配置
83
149
 
@@ -99,16 +165,18 @@ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
99
165
 
100
166
  修改后重启 DSH 并刷新网页。
101
167
 
168
+ 还有一项**不写在这里**:`bridge` 是「SpreadJS 桥」设置页在 Web UI 里写的,代表用户选了哪一个桥。它同样属于本插件的配置,但**不该手写进 patch**——设置页会把写入结果如实报出来(写入被拒或失败时页面会说明),而手写的值不会经过这条检查。既没选过、又在 patch 里留空,含义相同:`bridge: ''` 等于「不使用」。本插件自身的设置页取代了 DSH 从这个 schema 自动生成的那一页,所以不会出现两个入口。
169
+
102
170
  ### 验证
103
171
 
104
172
  | 命令 | 验证内容 |
105
173
  | --- | --- |
106
- | `npm run typecheck`、`npm test` | 类型契约,以及 53 个单元测试(保存路由的每条守卫、文件地址解析,以及保存命令重定向的每条分支) |
174
+ | `npm run typecheck`、`npm test` | 类型契约,以及 201 个单元测试(保存路由的每条守卫、文件地址解析与地址构造、另存为的目标校验、默认名与失败文案、保存命令重定向的每条分支、编辑器主题桥的每条分支、查看器名称与二进制后缀声明的边界、未保存改动的缓存规则与脏状态跟踪的边界、打开时「用文件还是用缓存」的每个分支、面板状态胶囊的状态与操作矩阵、文档头部动作的发布/订阅/命令路由、按钮的尺寸字号颜色规格、「只在表格文件上出现」的判定与注册顺序,隐藏 Designer「文件」Tab 那条规则的两处身份与作用域,以及桥选择的方向与「写入被拒也要报出来」、桥字段是否真的声明为 volatile、名册自身的契约(重复 id 拒绝、disposer 语义、只在真的变化时通知)、名册与设置页在有无 settings 服务时的接线,以及设置页每一行确实是照 DSH 自己设置页的行规格画的) |
107
175
  | `node scripts/smoke-client.mjs` | 构建后的浏览器半结构:loader 包装、内联的 SpreadJS 与样式、外部依赖只有 react |
108
176
  | `node scripts/smoke-node.mjs` | 构建后的宿主半:路由注册、许可证配置,以及保存路径的真实写盘、冲突与越界拒绝 |
109
- | `npm run probe:live` | **对着正在运行的 DSH** 跑一遍真实保存:真实 `ctx.fs` 的解析、落盘、冲突守卫、工作区越界 |
177
+ | `npm run probe:live` | **对着正在运行的 DSH** 跑一遍真实保存:真实 `ctx.fs` 的解析、落盘、冲突守卫、工作区越界;并核对进程里装的是不是本构建(插件版本与内置 SpreadJS 版本) |
110
178
 
111
- `npm run probe:live` 要求 DSH 已重启到本版本;如果它报 404,说明进程里还是旧的宿主半。可选参数:`--url`(默认 `http://127.0.0.1:3080`)、`--session <id>`、`--home <DSH_HOME>`。探针会在会话工作区内建一个临时文件,结束时删除。
179
+ `npm run probe:live` 要求 DSH 已重启到本版本。宿主半会在 `/spreadjs/api/health` 回报插件版本与内置 SpreadJS 版本,探针据此判断「正在跑的进程是不是这一版」——回报的版本与本构建不一致会直接判失败;如果进程里是很早的宿主半(连版本字段都没有),探针会说明需要重启,如果连保存路由都报 404,则说明那更是旧版。可选参数:`--url`(默认 `http://127.0.0.1:3080`)、`--session <id>`、`--home <DSH_HOME>`。探针会在会话工作区内建一个临时文件,结束时删除。
112
180
 
113
181
  ### 许可
114
182
 
@@ -124,10 +192,50 @@ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
124
192
 
125
193
  - Opens `.xlsx`, `.xlsm`, `.csv`, `.sjs`, and `.ssjson` files.
126
194
  - Open workbooks from the file tree and edit them with SpreadJS Designer.
127
- - Save edits straight back to the file: atomic, confined to the session workspace, with conflict detection.
128
- - Follows the system's light or dark theme, toolbars and worksheet alike.
195
+ - Save edits straight back to the file: atomic, confined to the session workspace, with conflict detection. Save lives in the harness's own document header, with a mark while anything is unsaved.
196
+ - **Save As** writes the workbook — including a new blank one — to another path in the workspace and moves the tab onto it; an existing name is never overwritten. Both header buttons follow the spec of the header's own controls (see Saving below) and appear on this plugin's own files only.
197
+ - Follows the harness's light or dark theme, toolbars and worksheet alike; a harness preference of `system` still tracks the OS.
129
198
  - An **About** button in the settings tab reports the bundled versions, the licence state and the copyright.
130
199
 
200
+ ### Using it with the SpreadJS Driver
201
+
202
+ This plugin is **complete on its own**: opening, editing and saving workbooks needs nothing else. Paired with the driver, though, it gains the one thing it cannot do alone — **letting the AI edit the very sheet you have on screen**.
203
+
204
+ [`@grapecity-software/dsh-spreadjs-driver`](https://www.npmjs.com/package/@grapecity-software/dsh-spreadjs-driver) is that companion driver. It **works by itself**, carrying a full set of file-level tools (create / import / status / execute / export / screenshot / drafts) and a `spreadjs` orchestration skill — but those act on **files in the workspace**, so you see the result and not the work. Add this editor and select the driver in Settings, and it is additionally handed a **live reference**: it edits the workbook you have open, and the change lands in front of you as it happens.
205
+
206
+ #### Install
207
+
208
+ Both plugins go into the **same web profile** — the driver's live-workbook capability exists only in the Web UI:
209
+
210
+ ```sh
211
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
212
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
213
+ dsh --profile web
214
+ ```
215
+
216
+ Restart DSH, open any spreadsheet file, then pick **SpreadJS Driver** under **Settings → SpreadJS bridge**. (With no driver installed that page offers only "None", and the editor behaves exactly as it does without the feature.)
217
+
218
+ > Installed is not the same as active: both packages have to appear in the profile's `dsh.profile.bundles`. `pnpm add` or `dsh plugin add` does not always write that for you, so after installing, open a real session and confirm the tools are actually there.
219
+
220
+ #### How the two fit together
221
+
222
+ The editor **owns** the workbook you open; each driver **registers** as a candidate. The roster may hold several — our test fixture, for one — but exactly **one** is handed the workbook: the one you selected in Settings.
223
+
224
+ | | |
225
+ | --- | --- |
226
+ | **What is handed over is a live reference, not a copy** | The workbook never leaves the page and is never serialized, so a style, formula or format written through it appears in the sheet at once. |
227
+ | **Nothing reaches disk by default** | An AI edit lives in the workbook until you are happy with it and the agent asks for a save. That save is the document header's own Save, so conflict detection, the workspace fence and the atomic write are all still in force. |
228
+ | **The roster answers to the selection alone** | Choose another bridge — or "None" — and **every** tool and skill the driver registers steps aside. The setting names the plugin that *owns spreadsheets* here, not merely the one holding the live document for the moment. |
229
+ | **The editor stays the gatekeeper** | The roster is a list of candidates, not a broadcast. A plugin that was not handed the workbook has no way to reach it. |
230
+
231
+ Installed, the AI gains `sjs_live_execute` (edit the sheet you have open) and `sjs_live_status` (is a designer live right now?). The driver's other tools go on acting on files.
232
+
233
+ #### Requirements
234
+
235
+ - **This plugin's bridge roster ships with this version (0.2.2).** On an earlier editor the driver installs without complaint and `sjs_live_execute` simply never finds a designer — so use the latest of both.
236
+ - **Both sides need DSH `0.1.7-alpha.1` or later** (a settings namespace derived from the Loader entry is a 0.1.7 idea). This plugin still runs on `0.1.5-rc.3` by itself — see the version table below — but **the pair** does not.
237
+ - **The driver has requirements of its own**: Chrome or Microsoft Edge on the machine (the engine runs in a real browser process), Node ≥ 22.19, and an embeddable `.ttf` system font for PDF export containing CJK text. Those are the driver's terms; see [its README](https://github.com/GrapeCityXA/dsh-plugin-spreadjs-driver#requirements).
238
+
131
239
  ### Bundled Versions and File Compatibility
132
240
 
133
241
  | Component | Bundled version |
@@ -141,6 +249,14 @@ The **About** button in the Designer's settings tab shows the bundled versions,
141
249
 
142
250
  Requires DSH `>=0.1.5-rc.3`. The plugin uses the harness's own right Sidebar and needs **no third-party sidebar plugin**.
143
251
 
252
+ | DSH version | Status | Notes |
253
+ | --- | --- | --- |
254
+ | `0.1.7-rc.2` | ✅ build and verification baseline | `typecheck`, 201 unit tests, both smokes, `verify:package` and `probe:live` all green (the probe's version check needs DSH restarted first, see below) |
255
+ | `0.1.6.x` | ⚪ not verified | This range was never checked: neither point-by-point acceptance nor whether it already offers the capabilities below |
256
+ | `0.1.5-rc.3` | ✅ previous verification baseline | The release this plugin was originally built and accepted against; it ignores the binary-suffix declaration, so a plain-text entry may still appear among the viewer choices, and its document header has no actions slot — so `Ctrl+S` is the only Save entry and unsaved work shows in the panel's floating pill |
257
+
258
+ From `0.1.7` the platform offers three things this plugin uses: the **binary-suffix** declaration on a document definition (which is what removes the plain-text viewer choice), its own read-only Excel preview (which is listed beside this plugin), and the document header's **actions slot** `sidebar.right.tab.document.actions`, where Save and Save As are registered. The plugin still works on `0.1.5-rc.3`; it simply does not get the first two, and the two header buttons do not appear.
259
+
144
260
  The plugin includes SpreadJS and Designer, with no build step required. With the DSH CLI installed:
145
261
 
146
262
  ```sh
@@ -161,29 +277,47 @@ For upgrades, see the [release notes](CHANGELOG.md).
161
277
 
162
278
  ### Saving
163
279
 
164
- Save has a single entry point: the **保存到工作区** button in the Designer's File tab, in the place the built-in "save as .sjs" button used to occupy. It writes your changes back to the file you opened, and there is no second prompt, because pressing Save *is* the intent.
280
+ Save's official entry point is **the harness's own document header** — the Save button beside the file name once a workbook is open. It is registered in the actions slot `sidebar.right.tab.document.actions` that DSH publishes from `0.1.7` on, next to the other viewers' own controls, and it carries a mark while anything is unsaved, like the `*` in Excel's title bar. It writes your changes back to the file you opened, and there is no second prompt, because pressing Save *is* the intent.
281
+
282
+ The same write-back is also on **`Ctrl+S` and the Save button on the Designer's own toolbar**: the plugin redirects the Designer's Save command, so neither of those reaches a download dialog any more. (The Designer's own File tab is hidden from the ribbon — see the end of this section.)
283
+
284
+ These two header buttons do not import the harness's button component — a third-party client plugin cannot assume an internal `@deepseek-ai/*` package resolves at runtime, and a failure there would take the whole editor down — so they copy the values from the controls beside them instead: 28px tall, `padding: 0 6px`, 12px text, `--dsw-alias-label-secondary` at rest, `--dsw-alias-label-primary` with the `--dsw-alias-interactive-bg-hover` background on hover, and `--dsw-alias-label-tertiary` with no response when disabled. That is exactly what the header's own viewer-name button and icon buttons do, and a unit test pins those values so they cannot drift unnoticed.
285
+
286
+ They are drawn **only on the files this plugin handles**. The slot renders for every previewed file, so the buttons appear only while this plugin's own editor is the body on screen — otherwise every markdown file, image and log in the workspace would carry two buttons that cannot do anything. The product's own open-in-app control works the same way: it draws nothing when there is no application to offer.
287
+
288
+ They are ordered **before** open-in-app. The slot is a list, and its entries sort by the documented `order` (priority first, then order, then registration sequence), where this plugin registers at `-10` — the very value the harness itself uses for "first in a header actions list" (`dsh-client-ui-agent-preset` registers its session-header label at `order: -10`). That makes "save first, think about opening elsewhere second" a decided reading order rather than a side effect of plugin load order, and it puts the unsaved mark on the first item of the group, where the eye lands.
289
+
290
+ The Designer's own File tab is **hidden from the ribbon**. The plugin no longer rewrites its private menu template (that rewrite is gone), and it does not leave the tab in place either: the File tab's Save *downloads a copy*, one click away from the Save that writes the file back, and having both is how the wrong one gets pressed. This editor now has exactly one place to save — the document header's **Save / Save As…**, plus `Ctrl+S`.
291
+
292
+ The price is that the File tab's other categories (new, open, import, export, print, info) are not reachable from the UI any more; that is what hiding the page means. Say the word if you want one of them back on the ribbon and we will wire that one in. The hiding is one CSS rule keyed on the product's public command name (`CommandNames.FileMenuButton`) and the class its template draws, scoped to this plugin's panel; a unit test pins the rule, and creating the editor logs how many elements it matched and what `display` they compute to, so a rename in a future build shows up as a dead rule rather than as a quietly reappearing download button.
165
293
 
166
- - **The File tab is reduced to one Save action.** The Designer's own Save is a dialog that downloads a file; this plugin replaces it with a write-back to the file you opened, and the File tab keeps only that one action — new, open, import, export, print and info are removed outright. A copy is what the harness's own file download is for.
167
294
  - **A workbook that is still loading cannot be saved.** It has no content yet, so saving would overwrite the file with an empty book; the save command refuses, with an explanation, until the load finishes.
168
295
  - **It will not clobber someone else's edit.** The plugin records the file's content hash when it opens it and re-checks before writing; if the file changed in the meantime (an agent edited it, you saved it elsewhere), the save is refused with an explanation instead of overwriting those changes.
169
296
  - **It never leaves half a file.** A same-directory temporary file is renamed over the target, so any other reader always sees a complete workbook.
170
297
  - **It cannot write outside the workspace.** The target must sit inside the current session's workspace; anything else — including `..` escapes — is refused.
171
298
  - **Loopback only.** The save endpoint serves the local machine like the rest of DSH. If you reach DSH through a LAN address, add that address to `trustedHosts`.
172
299
 
173
- ### Let the agent edit the sheet you are looking at
300
+ ### Save As
174
301
 
175
- Install [`@grapecity-software/dsh-spreadjs-driver`](https://www.npmjs.com/package/@grapecity-software/dsh-spreadjs-driver) alongside this plugin, and you can simply ask the agent — "make the amount column red" — and **watch the change land on the sheet you have open**.
302
+ **另存为…** 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.
176
303
 
177
- ```sh
178
- dsh plugin --profile web add @grapecity-software/dsh-spreadjs-driver
179
- ```
304
+ - **Suggested target**: same directory, same suffix, with the localized word for a copy appended (`report.xlsx` → `report-copy.xlsx`).
305
+ - **It switches this tab.** On success the tab navigates to the file just written and keeps editing there; no second tab is opened.
306
+ - **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.
307
+ - **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.
308
+ - **Containment is judged by the host**, on the same rule as a write-back: a path outside the session workspace is refused.
309
+ - **A busy editor says so.** While a load or another save is running the dialog reports that the editor is not ready rather than pretending to have written.
180
310
 
181
- - **It edits the same object, not a copy of the file.** The editor hands its current workbook to that plugin's `spreadjsHostBridge` service. Both plugins' browser halves run in the same page, so the change lands on the very document the Designer is rendering — and it **does not clobber edits you have not saved**, which a file-based route cannot promise.
182
- - **The file on disk is not touched.** The change lives in the editor until you explicitly ask for a save, so you get to look before deciding.
183
- - **Only that plugin can reach it.** The workbook is handed over *by the editor*; there is no way to go looking for somebody else's workbook, so other plugins on the page cannot touch it.
184
- - That plugin also brings a full set of spreadsheet tools of its own (create, import, export `.xlsx` / `.csv` / `.pdf`, screenshot) and **works on its own without the editor**.
311
+ ### Unsaved work
185
312
 
186
- > Requires this plugin **0.2.0 or later** — earlier versions have no bridge, so installing the driver alongside them changes nothing.
313
+ The harness unmounts a document body when its tab is hidden, and the platform offers no close hook to intercept (a close is synchronous, and a document definition has neither a lifecycle nor a veto point). So leaving is never blocked — and nothing is dropped either:
314
+
315
+ - **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.
316
+ - **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.
317
+ - **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.
318
+ - **It is visible, in two places.** The header's Save carries a mark while edits are pending (like Excel's title-bar `*`) — a position DSH `0.1.7` and later offer. The panel's own floating pill at the bottom-right shows `Unsaved changes` with a Discard action while edits are pending, and disappears once everything is saved. It hovers over the sheet and costs no layout height — which is why it went back from a permanent row at the top of the panel to a pill — and on `0.1.5`/`0.1.6`, where the document header has no actions slot, it is the only state indicator there is. Editing only swaps the text and the buttons inside it, and it appears only when it has something to say.
319
+ - **Closing the page warns.** A reload or close raises the browser's own prompt once, as long as anything is unsaved or still cached.
320
+ - **Saving clears it.** A successful save drops every snapshot for that file, so reopening starts from disk.
187
321
 
188
322
  ### Configuration
189
323
 
@@ -205,16 +339,18 @@ If you have licenses, add the following to the web profile's `cordis.patch.yml`:
205
339
 
206
340
  Restart DSH and refresh the browser after changing the configuration.
207
341
 
342
+ One more field is deliberately **not** in that table: `bridge` is written by the **SpreadJS bridge** 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.
343
+
208
344
  ### Verification
209
345
 
210
346
  | Command | What it proves |
211
347
  | --- | --- |
212
- | `npm run typecheck`, `npm test` | The type contract, and 53 unit tests: every save guard, file-address parsing, and every branch of the Save-command redirect |
348
+ | `npm run typecheck`, `npm test` | The type contract, and 201 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 |
213
349
  | `node scripts/smoke-client.mjs` | The built browser half's structure: loader wrapper, inlined SpreadJS and styles, react as the only runtime external |
214
350
  | `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 |
215
- | `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 |
351
+ | `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) |
216
352
 
217
- `npm run probe:live` needs DSH restarted onto this version; a 404 from it means the running process still carries the old host half. Options: `--url` (default `http://127.0.0.1:3080`), `--session <id>`, `--home <DSH_HOME>`. The probe creates one temporary file inside the session workspace and deletes it.
353
+ `npm run probe:live` needs DSH restarted onto this version. The host half reports the plugin and bundled SpreadJS versions on `/spreadjs/api/health`, so the probe can tell whether the running process is this build: a version that disagrees with this build fails outright, a host half old enough to omit the field is reported as needing a restart, and a 404 means the host half predates the save route. Options: `--url` (default `http://127.0.0.1:3080`), `--session <id>`, `--home <DSH_HOME>`. The probe creates one temporary file inside the session workspace and deletes it.
218
354
 
219
355
  ### License
220
356
 
package/cordis.patch.yml CHANGED
@@ -1,22 +1,24 @@
1
- # dsh-spreadjs-editor bundle patch
2
- #
3
- # One row activates the whole plugin: the Loader imports the package's node
4
- # half (lib/index.js, the /spreadjs license/config endpoint) and the
5
- # client-modules scan picks up the `dsh.client` declaration for the browser
6
- # half (lib/client.js, which registers the SpreadJS viewer into
7
- # dsh-better-sidebar).
8
- #
9
- # The row must run only under the Web composition (it injects the webServer
10
- # service); a headless profile should not mount this bundle. The browser half
11
- # has no hard client dependency: it waits for `dsh-better-sidebar`, which is
12
- # bundled by @linxin666/dsh-web-all (ui-all).
13
- - insert:
14
- - id: spreadjs-editor
15
- name: '@grapecity-software/dsh-spreadjs-editor'
16
- inject: [webServer]
17
- config:
18
- # Optional: your SpreadJS license key. Empty (default) runs the
19
- # evaluation watermark build. Keep it out of the patch if you prefer
20
- # to configure it per-deployment.
21
- licenseKey: ''
22
- designerLicenseKey: ''
1
+ # dsh-spreadjs-editor bundle patch
2
+ #
3
+ # One row activates the whole plugin: the Loader imports the package's node
4
+ # half (lib/index.js, the /spreadjs license handshake, health report, and the
5
+ # binary save endpoint) and the client-modules scan picks up the `dsh.client`
6
+ # declaration for the browser half (lib/client.js, which registers the SpreadJS
7
+ # editor with the harness's OWN right Sidebar through `ctx.documentPreviews`
8
+ # and the `sidebar.right.tab.document` slot).
9
+ #
10
+ # The row must run only under the Web composition (it injects the webServer
11
+ # service); a headless profile should not mount this bundle. No third-party
12
+ # sidebar plugin is required or consulted — the browser half's optional
13
+ # dependencies (`dsh-spreadjs-driver`'s bridge, the locale service) are attached
14
+ # only when present, and their absence costs nothing but the extra behaviour.
15
+ - insert:
16
+ - id: spreadjs-editor
17
+ name: '@grapecity-software/dsh-spreadjs-editor'
18
+ inject: [webServer]
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.
23
+ licenseKey: ''
24
+ designerLicenseKey: ''
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: 51960527 bytes
4
- sha256: 755ed8c0ded6f8614655c3fa528f535c2d80092201ce8b62eb0ea0c7c44d7655
3
+ size: 52028494 bytes
4
+ sha256: 527c9ec52231b1a8836adb84ecb91fdd8b675b134fa386477ffb22260ad321c3
package/lib/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
2
3
  import { mkdir, rename, rm, writeFile } from "node:fs/promises";
3
4
  import { dirname, join } from "node:path";
5
+ import z from "@deepseek-ai/schemastery";
6
+ //#region src/shared/settings.ts
7
+ /** The field holding the id of the chosen bridge. */
8
+ const BRIDGE_FIELD = "bridge";
9
+ //#endregion
4
10
  //#region src/index.ts
5
11
  /**
6
12
  * dsh-spreadjs-editor — node half.
@@ -36,7 +42,66 @@ import { dirname, join } from "node:path";
36
42
  const name = "dsh-spreadjs-editor";
37
43
  /** Required service: the web server whose route table this plugin extends. */
38
44
  const inject = ["webServer"];
45
+ /**
46
+ * The largest workbook accepted on the wire, used when the patch sets none.
47
+ *
48
+ * Declared above the schema so the schema's own `default` can name it: a
49
+ * schema is built while this module evaluates, so a constant declared below
50
+ * would be in its temporal dead zone at that moment.
51
+ */
39
52
  const DEFAULT_MAX_SAVE_BYTES = 67108864;
53
+ /**
54
+ * Cordis configuration schema.
55
+ *
56
+ * The `[BRIDGE_FIELD]` key is spread rather than spelled so the two halves of
57
+ * the choice cannot drift: the browser half writes through the same constant,
58
+ * and a rename would otherwise surface as "the setting does not stick".
59
+ *
60
+ * **`volatile()` is what makes the choice persist at all.** DSH accepts a
61
+ * settings write only for a field declared volatile — `SettingsForms.write`
62
+ * builds a volatile-only projection of the schema and throws
63
+ * `Config field "bridge" is not volatile` for anything else. Without it every
64
+ * click was refused, the refusal arrived as a *resolved `false`* rather than a
65
+ * rejection, and the choice lived only in the running page: the next start
66
+ * showed None again. The license keys and the two save limits are deployment
67
+ * configuration an operator puts in the patch, so they stay non-volatile and
68
+ * unwritable from the UI.
69
+ *
70
+ * No `default` on `bridge`: an ABSENT field and an explicit `''` are different
71
+ * states — "never chosen" versus "chosen None" — and only the absence is
72
+ * visible to a later default-if-unset rule.
73
+ *
74
+ * Deliberately NOT annotated `z<Config>`. That annotation asserts the schema's
75
+ * input and output are the same type, and for a volatile field they are not: a
76
+ * patch carries a plain string, while the resolved value is a `Volatile<…>`
77
+ * handle — a stable reference, because DSH may rewrite the value while this
78
+ * plugin runs. The shipped plugins with a volatile field leave the annotation
79
+ * off for the same reason.
80
+ */
81
+ const Config = z.object({
82
+ licenseKey: z.string().default(""),
83
+ designerLicenseKey: z.string().default(""),
84
+ maxSaveBytes: z.natural().default(DEFAULT_MAX_SAVE_BYTES),
85
+ trustedHosts: z.array(z.string()).default([]),
86
+ [BRIDGE_FIELD]: z.string().volatile()
87
+ });
88
+ /** The SpreadJS version this build bundled, or `unknown` outside a build. */
89
+ const SPREADJS_VERSION = "19.2.0";
90
+ /**
91
+ * This package's own version, read from the manifest the module ships beside.
92
+ * The published layout keeps `lib/index.js` next to `package.json`, so this
93
+ * relative URL resolves in the repository, in a tarball install, and inside a
94
+ * profile — no import of a bare `package.json` specifier, which a consumer's
95
+ * resolver may not be allowed to reach.
96
+ */
97
+ const PLUGIN_VERSION = (() => {
98
+ try {
99
+ const manifest = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
100
+ return typeof manifest.version === "string" && manifest.version !== "" ? manifest.version : "unknown";
101
+ } catch {
102
+ return "unknown";
103
+ }
104
+ })();
40
105
  /** A failure with an HTTP status, so handlers stay flat. */
41
106
  var HttpError = class extends Error {
42
107
  status;
@@ -220,7 +285,14 @@ function apply(ctx, config = {}) {
220
285
  }
221
286
  try {
222
287
  if (pathname === "/spreadjs/api/health") {
223
- sendJson(res, 200, { ok: true });
288
+ sendJson(res, 200, {
289
+ ok: true,
290
+ plugin: {
291
+ name,
292
+ version: PLUGIN_VERSION
293
+ },
294
+ spreadjs: SPREADJS_VERSION
295
+ });
224
296
  return;
225
297
  }
226
298
  if (pathname === "/spreadjs/api/config") {
@@ -237,6 +309,11 @@ function apply(ctx, config = {}) {
237
309
  }
238
310
  });
239
311
  ctx.effect(() => disposer, "dsh-spreadjs-editor: /spreadjs routes");
312
+ ctx.inject(["settings"], (child) => {
313
+ const settings = child.settings;
314
+ if (settings === void 0) return () => {};
315
+ return settings.configure({ auto: false }, ctx.fiber);
316
+ });
240
317
  }
241
318
  //#endregion
242
- export { apply, inject, name };
319
+ export { Config, PLUGIN_VERSION, SPREADJS_VERSION, apply, inject, name };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grapecity-software/dsh-spreadjs-editor",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "DeepSeek Harness Web UI plugin: SpreadJS workbook editor for the harness's own right Sidebar, with binary save.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -37,6 +37,7 @@
37
37
  "prepublishOnly": "npm run typecheck && npm test && npm run build && node scripts/smoke-client.mjs && node scripts/smoke-node.mjs && npm run verify:package",
38
38
  "rebuild": "node scripts/build.mjs",
39
39
  "probe:live": "node scripts/probe-live-save.mjs",
40
+ "release": "node scripts/release.mjs",
40
41
  "test": "vitest run",
41
42
  "typecheck": "tsc --noEmit",
42
43
  "verify:package": "node scripts/verify-package.mjs"
@@ -61,6 +62,7 @@
61
62
  "client": {
62
63
  "platform": "web",
63
64
  "inject": [
65
+ "@deepseek-ai/dsh-client-ui-theme",
64
66
  "@deepseek-ai/dsh-client-ui-slots",
65
67
  "@deepseek-ai/dsh-client-ui-sidebar-right",
66
68
  "@deepseek-ai/dsh-client-ui-sidebar-documentpreview"
@@ -69,9 +71,11 @@
69
71
  },
70
72
  "peerDependencies": {
71
73
  "@deepseek-ai/cordis": ">=4.0.0",
74
+ "@deepseek-ai/schemastery": "^3.18.4",
72
75
  "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.5-rc.3",
73
76
  "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": ">=0.1.5-rc.3",
74
77
  "@deepseek-ai/dsh-client-ui-slots": ">=0.1.5-rc.3",
78
+ "@deepseek-ai/dsh-client-ui-theme": ">=0.1.5-rc.3",
75
79
  "react": "^18.0.0"
76
80
  },
77
81
  "devDependencies": {
@@ -98,12 +102,14 @@
98
102
  "tsdown": "^0.22.0",
99
103
  "typescript": "^5.9.0",
100
104
  "unrun": "^0.3.1",
101
- "@deepseek-ai/cordis": "^4.0.1",
102
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.3",
103
- "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.3",
104
- "@deepseek-ai/dsh-client-ui-sidebar-right": "0.1.5-rc.3",
105
- "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.1.5-rc.3",
106
- "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.3",
105
+ "@deepseek-ai/cordis": "^4.0.4",
106
+ "@deepseek-ai/schemastery": "^3.18.4",
107
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
108
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
109
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "0.1.7-rc.2",
110
+ "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.1.7-rc.2",
111
+ "@deepseek-ai/dsh-client-ui-theme": "0.1.7-rc.2",
112
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
107
113
  "@types/node": "^22.0.0",
108
114
  "@types/react": "^18.3.0",
109
115
  "react": "^18.2.0",