dsh-plugin-workbench 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.15] - 2026-08-29
4
+
5
+ ### Fixed
6
+
7
+ - **DSH Desktop 下文件树列不显示([issue #1](https://github.com/Pasumao/dsh-plugin-workbench/issues/1))**:
8
+ 布局补丁的锚点按 npm 构建的编译产物逐字节写死,而 DSH Desktop 安装包内置的
9
+ `dsh-client-ui-layout` 是同一版本的另一构建产物(保留注释、缩进不同、折叠侧栏宽度用
10
+ `COLLAPSED_SIDEBAR_WIDTH` 常量),`computeColumns` 锚点失配导致补丁每次启动整体放弃——
11
+ 插件加载正常但界面永远不出现。补丁脚本现按目标文件自动探测构建变体(npm / desktop-ci,
12
+ 后者锚点存于 `scripts/layout-anchors.desktop-ci.json`),两种产物都能正确打补丁。
13
+
14
+ ## [0.0.14] - 2026-08-29
15
+
16
+ - 文档:对标生态头部插件优化 README(首屏新增「装完你会看到」;布局补丁话术改为开箱即用);
17
+ - 元数据:npm description 重写(突出可编辑/右键文件操作/@引用),keywords 7 → 15;
18
+ - 新增英文版 README.en.md 与双语切换行。
3
19
  ## [0.0.13] - 2026-08-26
4
20
 
5
21
  ### Fixed
package/README.en.md ADDED
@@ -0,0 +1,185 @@
1
+ [中文](./README.md) | **English**
2
+
3
+ # dsh-plugin-workbench
4
+
5
+ ![npm version](https://img.shields.io/npm/v/dsh-plugin-workbench)
6
+ ![License](https://img.shields.io/github/license/Pasumao/dsh-plugin-workbench)
7
+ ![CI](https://img.shields.io/github/actions/workflow/status/Pasumao/dsh-plugin-workbench/ci.yml?branch=main)
8
+ ![Stars](https://img.shields.io/github/stars/Pasumao/dsh-plugin-workbench?style=social)
9
+ ![AI Assisted](https://img.shields.io/badge/AI-Assisted-8A2BE2)
10
+
11
+ **A VS Code-style workbench that edits files directly** — not a read-only preview: file tree + editable code preview
12
+ (syntax highlighting, tabs, line-number gutter) + right-click file operations (new / rename / delete / copy / cut /
13
+ paste / open in system / reveal in file explorer) + inline image preview, with state saved independently per workspace.
14
+ Once installed, the DSH web GUI becomes a lightweight code editor.
15
+
16
+ > ✅ **Works out of the box**: since 0.0.9 the layout patch is fully automated — it takes effect right after install and
17
+ > restart; if a DSH upgrade overwrites the bundle, the plugin patches it back automatically, with no manual steps
18
+ > required (manual action is only needed if the anchors break, see the "Configuration" section).
19
+ > Since 0.0.15 both build flavors — the npm build and the DSH Desktop bundled build — are detected automatically, so
20
+ > **DSH Desktop works out of the box** too.
21
+
22
+ What you'll see after installing:
23
+
24
+ - A **file tree sidebar** on the left (lazy loading + 2-second auto-refresh, expansion state kept per workspace);
25
+ - `@relative-path` mentions in messages turn into **clickable links** that open a preview in the workbench;
26
+ - **Drag files from the left panel into the chat area** and the path is inserted into the input box automatically.
27
+
28
+ ## Screenshot
29
+
30
+ ![Workbench in the DSH web GUI: file tree + editable preview](docs/workbench-webgui.png)
31
+
32
+ > Real Web GUI screenshot.
33
+
34
+ ## Features
35
+
36
+ - File tree: lazy loading, 2-second auto-refresh, expansion state kept per workspace
37
+ - File icons: colored badges for common code formats / emoji for images, audio & video, archives, etc.; expanded and
38
+ collapsed directories are visually distinguished
39
+ - Editable preview: transparent textarea layered over syntax highlighting, save with `Ctrl+S`/`Cmd+S`;
40
+ Markdown renders a preview by default (one-click switch between source and rendered view); prose formats like
41
+ .txt and very large code files automatically fall back to plain-text editing — fast to load, no UI jank
42
+ - **Disk change sync**: when an open file is modified externally (e.g. saved by an agent or another editor),
43
+ clean tabs reload automatically, while tabs with unsaved edits show a "⟳" badge (click to reload)
44
+ - **Line-number gutter**: logical line numbers along the editor's left edge, locked in alignment with text scrolling
45
+ (works in both plain-text and highlighted modes)
46
+ - **Image preview**: png/jpg/gif/webp/avif/svg and more render inline directly (same-origin byte route, 20MB limit)
47
+ - **Context menu** (VS Code style): right-click on files, folders, **any blank space in the panel, or the panel
48
+ header** — new file, new folder, rename, delete (recursive, with confirmation), copy path, copy / cut / paste,
49
+ refresh, open in system, select all, undo,
50
+ reveal in file explorer (a file is revealed selected in its folder, a folder opens directly;
51
+ Windows `explorer` / macOS `open`, automatically translated under WSL)
52
+ - **Reference files with @ in messages**: the file context-menu item "Reference with @" inserts
53
+ `@workspace-relative-path` into the chat input box; once sent, `@relative-path` mentions in the message render as
54
+ hyperlinks (click to open a preview in the workbench). The syntax is `@` + a workspace-relative path; any other
55
+ `@text` is always displayed as-is and has no special meaning
56
+ - **Drag files into the conversation**: drag a file or a multi-selection from the left panel anywhere into the chat
57
+ area and the path text is inserted at the input box cursor; dropping onto a directory row in the left panel is
58
+ still a move operation
59
+ - **Click anywhere to deselect**: clicking anywhere outside the tree (conversation / other panels / left panel
60
+ header) clears the selection
61
+ - **Copy / cut / paste**: Ctrl/Cmd+C/X/V (or the context menu), with cross-workspace paste support;
62
+ when the target already contains an item with the same name you are asked whether to overwrite, just like the
63
+ system file manager; the clipboard is cleared automatically after a successful cut (move)
64
+ - **Batch operations**: Ctrl/Cmd+click and Ctrl+A multi-select, delete selected items in bulk via the context menu or
65
+ the Delete key, drag the whole group to move it
66
+ - **Undoable operations**: the "↩" button at the top right or Ctrl+Z undoes the most recent operation (kept per
67
+ workspace, up to 30 entries): copy (removes the copy), cut/drag move (moves back to the original place), rename,
68
+ create, delete; deletion is an undoable delete (files are moved instantly into a hidden `.dsh-trash`, no bytes
69
+ copied)
70
+ - **Reveal in file explorer**: a context-menu item (equivalent to VS Code's Reveal in File Explorer) that locates the
71
+ file/folder in the system file manager (Windows Explorer, macOS Finder, etc.), executed via the `reveal` endpoint of
72
+ the loopback RPC
73
+ - Word wrap: one-click soft-wrap toggle in the tab bar (wrapping at the display layer only, file content untouched) or
74
+ horizontal scrolling for long lines; the preference persists
75
+ - Tabs: multiple files, drag to reorder, collapse/pop out; one-click light/dark theme switching
76
+ - Syntax highlighting: highlight.js (JS/TS, Python, JSON, HTML, CSS, Shell, and more;
77
+ Markdown renders a preview by default, switchable to source)
78
+ - Markdown rendering: markdown-it (raw HTML is always escaped, never executed); relative-path images are displayed
79
+ inline via the same-origin route `/dsh-plugin-files/raw/<path>`
80
+ - Disk changes: the host watches open files with `fs.watch` (watching the parent directory, surviving atomic renames);
81
+ changes are pushed over SSE at `/dsh-plugin-files/events`, and clean tabs sync automatically
82
+
83
+ ## Configuration
84
+
85
+ No environment variables or config files needed; the layout patch is fully automated (the plugin automatically detects
86
+ and re-runs `scripts/patch-layout.mjs` at startup — idempotent, non-blocking; since 0.0.15 both build flavors of the
87
+ same version are detected automatically — the npm build and the DSH Desktop bundled build; anchors are byte-coupled to
88
+ the compiled output, so a DSH upgrade that breaks them requires anchor updates):
89
+ - **Behavior preferences** (word wrap, light/dark theme, tab layout, file tree expansion state) persist per workspace
90
+ and need no manual configuration;
91
+ - **File operation channel**: via the loopback RPC `/dsh-plugin-files`; write operations are executed explicitly under
92
+ `danger-full-access`, with no external configuration options.
93
+
94
+ ## Installation
95
+
96
+ > Layout-patch anchors are byte-coupled to the DSH compiled output; a DSH upgrade that breaks them requires anchor
97
+ > updates. Since 0.0.15 both build flavors (npm / DSH Desktop) are detected automatically and the patch re-applies at
98
+ > startup (see the "Configuration" section for details).
99
+
100
+ ```powershell
101
+ # npm (recommended)
102
+ dsh plugin --profile web add dsh-plugin-workbench
103
+ # or GitHub
104
+ dsh plugin --profile web add github:Pasumao/dsh-plugin-workbench
105
+ ```
106
+
107
+ Install from source (local development / debugging):
108
+
109
+ ```bash
110
+ git clone https://github.com/Pasumao/dsh-plugin-workbench.git
111
+ cd dsh-plugin-workbench
112
+ pnpm install
113
+ pnpm run build # produces lib/index.js and lib/client.js
114
+ # mount into the profile via link:
115
+ ```
116
+
117
+ After installing, apply the layout patch and restart:
118
+
119
+ ```powershell
120
+ node node_modules/dsh-plugin-workbench/scripts/patch-layout.mjs
121
+ # restart dsh web
122
+ ```
123
+
124
+ > Since 0.0.9 the plugin automatically detects and re-applies the layout patch at startup — no need to run it manually
125
+ > after DSH upgrades; run the command above manually only if the automatic re-patching fails (broken anchors).
126
+
127
+ ## Development
128
+
129
+ ```powershell
130
+ pnpm install
131
+ pnpm run build # produces lib/index.js (host) and lib/client.js (browser)
132
+ pnpm run typecheck
133
+ ```
134
+
135
+ ## Uninstall
136
+
137
+ ```powershell
138
+ dsh plugin --profile web remove dsh-plugin-workbench
139
+ # and restore the layout bundle (patches/layout.backup/client.js.orig → dsh-client-ui-layout/lib/client.js)
140
+ ```
141
+
142
+ ## Notes
143
+
144
+ - The `/dsh-plugin-files` RPC channel is loopback-only; write operations are executed explicitly under
145
+ `danger-full-access`; the context menu's create/rename/delete go through the same channel too (loopback trust,
146
+ same as saving in the editor)
147
+ - Image preview goes through the same-origin route `/dsh-plugin-files/raw/<path>`: it only responds to image
148
+ extensions, resolves the path via `ctx.fs.resolve → stat` first (sandbox-consistent path resolution) before reading
149
+ bytes, with a 20MB limit
150
+ - DSH upgrades overwrite the layout bundle; since 0.0.9 the plugin detects and re-applies the patch automatically at
151
+ startup, so there is no need to re-run it manually; if the anchors break, `scripts/patch-layout.mjs` must be updated
152
+ (since 0.0.15 the anchor table carries two build variants auto-detected per target — npm and desktop-ci, the latter
153
+ stored in `scripts/layout-anchors.desktop-ci.json`)
154
+ - This repository does not include DSH build artifacts
155
+
156
+ ## Contributing
157
+
158
+ See [CONTRIBUTING](./CONTRIBUTING.md) and [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md); the changelog is in
159
+ [CHANGELOG](./CHANGELOG.md).
160
+
161
+ ## Related plugins
162
+
163
+ This plugin is part of **Pasumao's dsh plugin ecosystem**; the published plugins in the same series can be used
164
+ alongside it:
165
+
166
+ | Plugin (npm) | GitHub | Description |
167
+ |---|---|---|
168
+ | [dsh-notify](https://www.npmjs.com/package/dsh-notify) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-notify) | Native Windows notifications + system tray |
169
+ | [dsh-plugin-choice-refresh](https://www.npmjs.com/package/dsh-plugin-choice-refresh) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-choice-refresh) | Choice enhancements: regenerate options / more options |
170
+ | [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-dev-kb) | Plugin development knowledge base (full mirror of the official docs + skill) |
171
+ | [dsh-plugin-image-tools](https://www.npmjs.com/package/dsh-plugin-image-tools) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-image-tools) | Image choice cards + inline images in replies + image intake for text-only models |
172
+ | [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-table-zoom) | Floating window for long chat tables + one-click Markdown copy |
173
+ | [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows environment safeguards: guideline skills + mojibake detection / dangerous-write interception / encoding diagnosis & repair |
174
+
175
+ > For the remaining plugins in the series, see [Pasumao · dsh plugins](https://github.com/Pasumao); if you find them useful, a ⭐ on GitHub is always welcome.
176
+
177
+ ## AI generation statement
178
+
179
+ Part of the source code and documentation was generated with AI assistance (DeepSeek Harness) and has been
180
+ human-reviewed and verified on a live system; permission-related logic has been re-reviewed against the principle of
181
+ least privilege.
182
+
183
+ ## License
184
+
185
+ [MIT](./LICENSE)
package/README.md CHANGED
@@ -6,13 +6,28 @@
6
6
  ![Stars](https://img.shields.io/github/stars/Pasumao/dsh-plugin-workbench?style=social)
7
7
  ![AI Assisted](https://img.shields.io/badge/AI-Assisted-8A2BE2)
8
8
 
9
+ [**中文**](./README.md) | [English](./README.en.md)
10
+
9
11
  **能直接改文件的 VS Code 风格工作台**——不是只读预览:文件树 + 可编辑代码预览
10
12
  (语法高亮、标签页、行号栏)+ 右键文件操作(新建 / 重命名 / 删除 / 复制 / 剪切 /
11
13
  粘贴 / 在系统中打开 / 在资源管理器打开)+ 图片内联预览,每个工作区独立保存状态。
12
14
  装上之后,DSH 网页就是一个轻量代码编辑器。
13
15
 
14
- > ⚠️ 安装后需打一个布局补丁(见下方安装节);0.0.9 起插件启动时会自动检测并重打,
15
- > DSH 升级覆盖 bundle 后无需再手动跑。
16
+ > **开箱即用**:0.0.9 起布局补丁全自动——装完重启即生效;DSH 升级覆盖 bundle
17
+ > 后插件会自动补回,无需任何手动操作(仅锚点失效时才需手动,见「配置」节)。
18
+ > 0.0.15 起自动识别 npm 与 DSH Desktop 两种构建产物,**DSH Desktop 开箱即用**。
19
+
20
+ 装完你会看到:
21
+
22
+ - 左侧多出**文件树侧栏**(懒加载 + 2 秒自动刷新,每工作区独立展开状态);
23
+ - 消息里的 `@相对路径` 变成**可点击链接**,点开直接在工作台预览;
24
+ - 把文件从左栏**拖进聊天区**,路径自动插入输入框。
25
+
26
+ ## 效果图
27
+
28
+ ![workbench 实机截图:DSH 网页内的文件树与可编辑预览](docs/workbench-webgui.png)
29
+
30
+ > 真实 Web GUI 截图。
16
31
 
17
32
  ## 功能
18
33
 
@@ -58,19 +73,18 @@
58
73
 
59
74
  ## 配置
60
75
 
61
- 无需环境变量或配置文件,安装后打一次布局补丁即可:
62
-
63
- - **布局补丁**:插件 host 端启动时自动检测并重跑 `scripts/patch-layout.mjs`
64
- (幂等、非阻塞;针对 `dsh-client-ui-layout@0.1.0-rc.6` 编写)。DSH 升级覆盖
65
- bundle 后插件会自动补回;若自动重打失败(锚点失效),可手动运行
66
- `node node_modules/dsh-plugin-workbench/scripts/patch-layout.mjs`;
76
+ 无需环境变量或配置文件;布局补丁全自动(插件启动时自动检测并重跑
77
+ `scripts/patch-layout.mjs`,幂等、非阻塞;0.0.15 起自动识别同一版本的两种构建产物
78
+ ——npm 构建与 DSH Desktop 内置构建;锚点与编译产物字节级耦合,DSH 升级后如失配
79
+ 需更新锚点):
67
80
  - **行为偏好**(自动换行、亮暗主题、标签布局、文件树展开状态)按工作区持久化,无需手动配置;
68
81
  - **文件操作通道**:经 loopback RPC `/dsh-plugin-files`,写操作显式以 `danger-full-access` 执行,无外部配置项。
69
82
 
70
83
  ## 安装
71
84
 
72
- > 布局补丁针对 `dsh-client-ui-layout@0.1.0-rc.6` 编写,其他版本需更新锚点。
73
- > 0.0.9 起插件启动时自动检测并重打补丁(详见「配置」节)。
85
+ > 布局补丁锚点与 DSH 编译产物字节级耦合,DSH 升级后如失配需更新锚点;0.0.15
86
+ > 自动识别 npm / DSH Desktop 两种构建产物,插件启动时自动检测并重打补丁
87
+ > (详见「配置」节)。
74
88
 
75
89
  ```powershell
76
90
  # npm(推荐)
@@ -121,7 +135,9 @@ dsh plugin --profile web remove dsh-plugin-workbench
121
135
  - 图片预览走同源路由 `/dsh-plugin-files/raw/<path>`:仅响应图片扩展名,
122
136
  先经 `ctx.fs.resolve → stat`(沙箱一致的路径解析)再读取字节,20MB 上限
123
137
  - DSH 升级会覆盖布局 bundle;0.0.9 起插件启动时自动检测并重打补丁,
124
- 无需手动重跑;锚点失效时需更新 `scripts/patch-layout.mjs`
138
+ 无需手动重跑;锚点失效时需更新 `scripts/patch-layout.mjs`(0.0.15 起锚点表分
139
+ npm / desktop-ci 两个构建变体自动探测,desktop-ci 变体存于
140
+ `scripts/layout-anchors.desktop-ci.json`)
125
141
  - 本仓库不包含 DSH 编译产物
126
142
 
127
143
  ## 参与贡献
@@ -140,7 +156,7 @@ dsh plugin --profile web remove dsh-plugin-workbench
140
156
  | [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-dev-kb) | 插件开发知识库(官方文档完整镜像 + 技能) |
141
157
  | [dsh-plugin-image-tools](https://www.npmjs.com/package/dsh-plugin-image-tools) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-image-tools) | 图片选择卡 + 回复内嵌图片 + 盲模型收图 |
142
158
  | [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-table-zoom) | 聊天长表格浮窗查看 + 一键复制 Markdown |
143
- | [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows 环境防坑守则 skill(编码/转义/路径/进程/乱码预防) |
159
+ | [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows 环境防坑:守则技能 + 乱码检测 / 危险写拦截 / 编码诊断修复 |
144
160
 
145
161
  > 本系列其余插件见 [Pasumao · dsh 插件](https://github.com/Pasumao);觉得好用欢迎到 GitHub 点 ⭐。
146
162
 
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-workbench",
3
- "description": "VS Code-style workspace file explorer + editable preview for the dsh web GUI",
4
- "version": "0.0.13",
3
+ "description": "VS Code-style workspace file explorer for the DeepSeek Harness web GUI — editable preview with syntax highlighting, tabs and line numbers, full right-click file operations (create / rename / delete / copy / cut / paste / reveal in file manager), inline image & markdown preview, @-mention files into chat, drag files into the composer, per-workspace state with undo. Turns the dsh web page into a lightweight code editor.",
4
+ "version": "0.0.15",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.21.0",
7
7
  "engines": {
@@ -41,12 +41,20 @@
41
41
  },
42
42
  "keywords": [
43
43
  "dsh",
44
+ "dsh-plugin",
44
45
  "deepseek-harness",
45
46
  "cordis",
46
47
  "plugin",
47
48
  "file-explorer",
49
+ "file-manager",
50
+ "file-editor",
51
+ "code-editor",
52
+ "web-ide",
48
53
  "vscode",
49
- "workbench"
54
+ "workbench",
55
+ "syntax-highlighting",
56
+ "markdown-preview",
57
+ "image-preview"
50
58
  ],
51
59
  "peerDependencies": {
52
60
  "@deepseek-ai/cordis": "^4.0.1"
@@ -65,9 +73,11 @@
65
73
  "files": [
66
74
  "lib",
67
75
  "scripts",
76
+ "docs",
68
77
  "cordis.patch.yml",
69
78
  "src",
70
79
  "README.md",
80
+ "README.en.md",
71
81
  "CHANGELOG.md",
72
82
  "LICENSE"
73
83
  ]
@@ -0,0 +1,6 @@
1
+ {
2
+ "variant": "desktop-ci",
3
+ "note": "computeColumns anchor/replacement for the DSH Desktop packaged build of @deepseek-ai/dsh-client-ui-layout (2-tab indent, COLLAPSED_SIDEBAR_WIDTH, one stray CRLF). Generated mechanically from a real bundle; do not hand-edit.",
4
+ "anchor": "\t\tfunction computeColumns(viewport, sidebar, details) {\n\t\t\tconst s = sidebar === 0 ? COLLAPSED_SIDEBAR_WIDTH : clampWidth(sidebar, 264, 420);\r\n\t\t\tconst d0 = details === 0 ? 0 : clampWidth(details, 300, 520);\n\t\t\tif (s + d0 + 640 <= viewport) return {\n\t\t\t\tsidebar: s,\n\t\t\t\tcenter: viewport - s - d0,\n\t\t\t\tdetails: d0\n\t\t\t};\n\t\t\tconst d1 = d0 === 0 ? 0 : Math.max(300, viewport - s - 640);\n\t\t\tif (s + d1 + 640 <= viewport) return {\n\t\t\t\tsidebar: s,\n\t\t\t\tcenter: 640,\n\t\t\t\tdetails: d1\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tsidebar: s,\n\t\t\t\tcenter: Math.max(0, viewport - s),\n\t\t\t\tdetails: 0\n\t\t\t};\n\t\t}",
5
+ "replacement": "\t\tfunction computeColumns(viewport, sidebar, explorer, details) {\n\t\t\tconst s = sidebar === 0 ? COLLAPSED_SIDEBAR_WIDTH : clampWidth(sidebar, 264, 420);\r\n\t\t\tconst e0 = explorer === 0 ? 0 : clampWidth(explorer, 200, 420);\r\n\t\t\tconst d0 = details === 0 ? 0 : clampWidth(details, 300, 520);\n\t\t\tif (s + e0 + d0 + 640 <= viewport) return {\n\t\t\t\tsidebar: s,\n\t\t\t\texplorer: e0,\n\t\t\t\tcenter: viewport - s - e0 - d0,\n\t\t\t\tdetails: d0\n\t\t\t};\n\t\t\tconst d1 = d0 === 0 ? 0 : Math.max(300, viewport - s - e0 - 640);\n\t\t\tif (s + e0 + d1 + 640 <= viewport) return {\n\t\t\t\tsidebar: s,\n\t\t\t\texplorer: e0,\n\t\t\t\tcenter: 640,\n\t\t\t\tdetails: d1\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tsidebar: s,\n\t\t\t\texplorer: e0,\n\t\t\t\tcenter: Math.max(0, viewport - s - e0),\n\t\t\t\tdetails: 0\n\t\t\t};\n\t\t}"
6
+ }
@@ -8,6 +8,17 @@
8
8
  * this script performs precise, idempotent string replacements and verifies
9
9
  * every anchor landed. It backs up the original before the first write.
10
10
  *
11
+ * The same @deepseek-ai/dsh-client-ui-layout version ships as TWO different
12
+ * builds with byte-different output:
13
+ * - "npm": the npm-registry tarball (function bodies at 0 indent,
14
+ * literal `56`, LF-only line endings);
15
+ * - "desktop-ci": the build packaged inside DSH Desktop's resources/app and
16
+ * cloned into its profile (2-tab indented computeColumns,
17
+ * `COLLAPSED_SIDEBAR_WIDTH` constant, one stray CRLF).
18
+ * Every variant shares all anchors except `computeColumns`; both variants are
19
+ * dry-run against the target and the first one whose anchors ALL match is
20
+ * applied. If none matches, the bundle changed again — update the anchors.
21
+ *
11
22
  * Usage:
12
23
  * node scripts/patch-layout.mjs [--target <abs path to client.js>] [--force]
13
24
  *
@@ -19,6 +30,7 @@ import { basename, dirname, join, resolve } from 'node:path'
19
30
  import { fileURLToPath } from 'node:url'
20
31
  import { homedir } from 'node:os'
21
32
 
33
+ const SCRIPT_DIR = fileURLToPath(new URL('.', import.meta.url))
22
34
  const REPO_ROOT = fileURLToPath(new URL('..', import.meta.url))
23
35
  const BACKUP_DIR = join(REPO_ROOT, 'patches', 'layout.backup')
24
36
 
@@ -242,8 +254,41 @@ const REPLACEMENTS = [
242
254
  },
243
255
  ]
244
256
 
257
+ // desktop-ci variant: same table, with `computeColumns` swapped for the anchor
258
+ // pair extracted mechanically from a real DSH Desktop packaged bundle.
259
+ const DESKTOP_CI = JSON.parse(readFileSync(join(SCRIPT_DIR, 'layout-anchors.desktop-ci.json'), 'utf8'))
260
+ const VARIANTS = [
261
+ { id: 'npm', replacements: REPLACEMENTS },
262
+ {
263
+ id: 'desktop-ci',
264
+ replacements: REPLACEMENTS.map((item) =>
265
+ item.id === 'computeColumns'
266
+ ? { id: 'computeColumns', anchor: DESKTOP_CI.anchor, replacement: DESKTOP_CI.replacement }
267
+ : item
268
+ ),
269
+ },
270
+ ]
271
+
245
272
  const PATCHED_MARKERS = ['"explorerCol": "pI_x6G_explorerCol"', 'setExplorer: (d, px) => {', 'renderSlot("explorer"', 'renderSlot("explorer.preview"', 'conversationSeat']
246
273
 
274
+ function applyReplacements(original, replacements) {
275
+ let patched = original
276
+ const failures = []
277
+ for (const item of replacements) {
278
+ const count = countOccurrences(patched, item.anchor)
279
+ if (count === 0) {
280
+ failures.push(`${item.id}: anchor not found`)
281
+ continue
282
+ }
283
+ if (count > 1) {
284
+ failures.push(`${item.id}: anchor found ${count} times (expected exactly 1)`)
285
+ continue
286
+ }
287
+ patched = patched.replace(item.anchor, item.replacement)
288
+ }
289
+ return { patched, failures }
290
+ }
291
+
247
292
  function main() {
248
293
  const target = resolve(targetArg ?? defaultTarget)
249
294
  if (!existsSync(target)) {
@@ -264,6 +309,19 @@ function main() {
264
309
  console.log('[patch-layout] --force: re-patching from the current file. Consider restoring the .orig backup first.')
265
310
  }
266
311
 
312
+ const trials = VARIANTS.map((variant) => ({ id: variant.id, ...applyReplacements(original, variant.replacements) }))
313
+ const chosen = trials.find((trial) => trial.failures.length === 0)
314
+ if (!chosen) {
315
+ console.error('[patch-layout] ABORTED — the bundle matches no known build variant:')
316
+ for (const trial of trials) {
317
+ console.error(` variant "${trial.id}":`)
318
+ for (const failure of trial.failures) console.error(` - ${failure}`)
319
+ }
320
+ console.error('[patch-layout] the dsh version may have changed; update the variant anchors in scripts/.')
321
+ process.exit(1)
322
+ }
323
+ console.log(`[patch-layout] build variant: ${chosen.id}`)
324
+
267
325
  mkdirSync(BACKUP_DIR, { recursive: true })
268
326
  const pristine = join(BACKUP_DIR, 'client.js.orig')
269
327
  if (!existsSync(pristine)) {
@@ -274,36 +332,14 @@ function main() {
274
332
  copyFileSync(real, join(BACKUP_DIR, `client.js.${stamp}.bak`))
275
333
  }
276
334
 
277
- let patched = original
278
- const failures = []
279
- for (const item of REPLACEMENTS) {
280
- const count = countOccurrences(patched, item.anchor)
281
- if (count === 0) {
282
- failures.push(`${item.id}: anchor not found`)
283
- continue
284
- }
285
- if (count > 1) {
286
- failures.push(`${item.id}: anchor found ${count} times (expected exactly 1)`)
287
- continue
288
- }
289
- patched = patched.replace(item.anchor, item.replacement)
290
- }
291
-
292
- if (failures.length > 0) {
293
- console.error('[patch-layout] ABORTED — the bundle does not match the expected compiled output:')
294
- for (const failure of failures) console.error(` - ${failure}`)
295
- console.error('[patch-layout] the dsh version may have changed; update scripts/patch-layout.mjs anchors.')
296
- process.exit(1)
297
- }
298
-
299
- const missingMarkers = PATCHED_MARKERS.filter((marker) => !patched.includes(marker))
335
+ const missingMarkers = PATCHED_MARKERS.filter((marker) => !chosen.patched.includes(marker))
300
336
  if (missingMarkers.length > 0) {
301
337
  console.error('[patch-layout] verification failed — missing markers:')
302
338
  for (const marker of missingMarkers) console.error(` - ${marker}`)
303
339
  process.exit(1)
304
340
  }
305
341
 
306
- writeFileSync(real, patched, 'utf8')
342
+ writeFileSync(real, chosen.patched, 'utf8')
307
343
  console.log(`[patch-layout] patched: ${real}`)
308
344
  console.log('[patch-layout] verified: explorerCol class, setExplorer action, explorer slot render.')
309
345
  console.log('[patch-layout] restart dsh web to serve the new bundle rev.')