dsh-width-tiers 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aaronlei
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 ADDED
@@ -0,0 +1,90 @@
1
+ # dsh-width-tiers
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+
5
+ A client plugin for the **DeepSeek Harness Web GUI** that adds **chat content
6
+ width tiers** with a floating picker button (bottom-right, above the
7
+ composer): 标准 / 中等 / 宽 / 超宽 / 全宽 (standard / medium / wide / ultra /
8
+ full).
9
+
10
+ ## Why
11
+
12
+ The DSH Web app hard-codes a readable content width on the conversation root
13
+ (`--dsh-chat-content-width: 748px`). That caps the message column — and every
14
+ table inside it — at **748px** no matter how wide your window or panels are,
15
+ so wide tables always need horizontal scrolling.
16
+
17
+ This plugin overrides that variable per tier:
18
+
19
+ | Tier | Content width | Sidebar |
20
+ |---|---|---|
21
+ | standard 标准 | 748px (app default) | untouched |
22
+ | medium 中等 | 960px | untouched |
23
+ | wide 宽 | 1280px | untouched |
24
+ | ultra 超宽 | 1400px | untouched |
25
+ | full 全宽 | 100% (fills the window) | collapsed to the icon rail |
26
+
27
+ The choice persists in `localStorage` and is restored on the next visit; the
28
+ menu highlights the current tier, and the button's five level-bars show it at
29
+ a glance.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ dsh plugin --profile web add dsh-width-tiers
35
+ ```
36
+
37
+ Then register it as a loader entry in
38
+ `~/.dsh/profiles/web/cordis.patch.yml`:
39
+
40
+ ```yaml
41
+ - insert:
42
+ - id: dsh-width-tiers
43
+ name: dsh-width-tiers
44
+ ```
45
+
46
+ Restart `dsh web` (the plugin loads at boot) and hard-refresh the page
47
+ (Ctrl+Shift+R). A round button with five bars appears at the bottom-right.
48
+
49
+ > Installing from a local checkout: `dsh plugin --profile web add file:/path/to/dsh-width-tiers`.
50
+
51
+ ## Usage
52
+
53
+ - Click the round button → pick a tier; the current one is highlighted.
54
+ - `full` collapses the sidebar to the icon rail through the app's own toggle
55
+ — it is never force-hidden or locked: click a rail icon any time to expand
56
+ it, and it stays expanded.
57
+ - Non-standard tiers also close the details panel (re-closed automatically if
58
+ reopened); `standard` leaves everything to the app.
59
+
60
+ ## How it works
61
+
62
+ - The element that defines `--dsh-chat-content-width` is **located at
63
+ runtime** (the first element whose computed value is non-empty) and the
64
+ tier is applied as an inline override — no hashed class names, so it
65
+ survives bundle upgrades.
66
+ - Panels are driven through the **layout service** (`ctx.layout`), never by
67
+ simulating clicks.
68
+ - The sidebar is only ever collapsed by the `full` tier; returning to
69
+ `standard` restores it only if this plugin collapsed it.
70
+
71
+ ## Limitations
72
+
73
+ - Content width cannot exceed the window: on a narrow window, wider tiers are
74
+ capped by the available column. Use `full` (which also collapses the
75
+ sidebar) for the maximum.
76
+ - Requires the DSH Web client plugin system (dsh ≥ 0.1.0-rc.x with a `web`
77
+ profile).
78
+
79
+ ## Uninstall
80
+
81
+ ```bash
82
+ dsh plugin --profile web remove dsh-width-tiers
83
+ ```
84
+
85
+ Remove the `dsh-width-tiers` row from `~/.dsh/profiles/web/cordis.patch.yml`,
86
+ then restart `dsh web`.
87
+
88
+ ## License
89
+
90
+ MIT — see [LICENSE](LICENSE). Copyright (c) 2026 aaronlei.
package/README.zh.md ADDED
@@ -0,0 +1,79 @@
1
+ # dsh-width-tiers(对话区宽度档位)
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+
5
+ DeepSeek Harness Web GUI 的客户端插件:给对话区加**内容宽度档位选择器**
6
+ (右下角圆形按钮,输入框上方),共 5 档:
7
+ 标准 / 中等 / 宽 / 超宽 / 全宽。
8
+
9
+ ## 为什么需要它
10
+
11
+ DSH Web 在会话根元素上硬编码了一个阅读宽度
12
+ (`--dsh-chat-content-width: 748px`),消息列——包括里面的所有表格——最大
13
+ 只有 **748px**,不管窗口和面板多宽都不变,宽表格只能横向滚动。
14
+
15
+ 本插件按档位覆盖这个变量:
16
+
17
+ | 档位 | 内容宽度 | 侧边栏 |
18
+ |---|---|---|
19
+ | 标准 | 748px(应用默认) | 不动 |
20
+ | 中等 | 960px | 不动 |
21
+ | 宽 | 1280px | 不动 |
22
+ | 超宽 | 1400px | 不动 |
23
+ | 全宽 | 100%(占满窗口) | 收起为图标轨道 |
24
+
25
+ 选择会记住(localStorage),下次打开自动恢复;菜单里当前档位高亮,按钮上
26
+ 的五条横条图标也直观显示当前档位。
27
+
28
+ ## 安装
29
+
30
+ ```bash
31
+ dsh plugin --profile web add dsh-width-tiers
32
+ ```
33
+
34
+ 然后在 `~/.dsh/profiles/web/cordis.patch.yml` 里注册为加载项:
35
+
36
+ ```yaml
37
+ - insert:
38
+ - id: dsh-width-tiers
39
+ name: dsh-width-tiers
40
+ ```
41
+
42
+ **重启 `dsh web`**(插件在启动时加载),再硬刷新页面(Ctrl+Shift+R)。
43
+ 右下角会出现五条横条的圆形按钮。
44
+
45
+ > 本地目录安装:`dsh plugin --profile web add file:/path/to/dsh-width-tiers`。
46
+
47
+ ## 使用
48
+
49
+ - 点圆形按钮 → 选择档位,当前档位高亮。
50
+ - 「全宽」通过应用自己的收起按钮把侧边栏收成图标轨道——**不会被锁死**:
51
+ 随时点轨道图标展开,展开后保持展开。
52
+ - 非标准档会关闭详情面板(被重新打开会自动再关);标准档完全交给应用。
53
+
54
+ ## 原理
55
+
56
+ - 运行时**自动定位**定义 `--dsh-chat-content-width` 的元素(第一个计算值
57
+ 非空的元素),以行内样式覆盖——**不依赖哈希类名**,dsh 升级后依旧有效。
58
+ - 面板操作走 **layout 服务**(`ctx.layout`),从不模拟点击。
59
+ - 侧边栏只被「全宽」档收起;回到「标准」时,只有本插件自己收起的侧边栏
60
+ 才会被恢复展开(你手动收的不动)。
61
+
62
+ ## 已知限制
63
+
64
+ - 内容宽度不可能超过窗口:窗口窄时,高档位会被可用列宽截断;想要最大
65
+ 空间请用「全宽」(同时收起侧边栏)。
66
+ - 依赖 DSH Web 客户端插件体系(带 web profile 的 dsh ≥ 0.1.0-rc.x)。
67
+
68
+ ## 卸载
69
+
70
+ ```bash
71
+ dsh plugin --profile web remove dsh-width-tiers
72
+ ```
73
+
74
+ 删除 `~/.dsh/profiles/web/cordis.patch.yml` 里的 `dsh-width-tiers` 行,
75
+ 然后重启 `dsh web`。
76
+
77
+ ## 许可证
78
+
79
+ MIT —— 见 [LICENSE](LICENSE)。Copyright (c) 2026 aaronlei。
package/lib/client.js ADDED
@@ -0,0 +1,361 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-width-tiers",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ //#region lib/types/client/index.js
9
+ /**
10
+ * dsh-width-tiers — chat content width tiers for the DSH Web GUI.
11
+ *
12
+ * The app hard-codes a readable content width
13
+ * (`--dsh-chat-content-width: 748px` on the conversation root), which
14
+ * caps the message column — and every table inside it — at 748px no
15
+ * matter how wide the panels are. This plugin overrides that variable
16
+ * per tier and adds a floating picker button:
17
+ *
18
+ * 标准 standard 748px (app default, no override)
19
+ * 中等 medium 960px (sidebar untouched)
20
+ * 宽 wide 1280px (sidebar untouched)
21
+ * 超宽 ultra 1400px (sidebar untouched)
22
+ * 全宽 full 100% (sidebar collapsed to the icon rail)
23
+ *
24
+ * Design notes:
25
+ * - The element that defines `--dsh-chat-content-width` is located at
26
+ * runtime (first element whose computed value is non-empty) and the
27
+ * tier is applied as an INLINE override on it — no hashed class
28
+ * names, survives bundle upgrades. Re-detected if it remounts.
29
+ * - Panels are driven through the layout service (ctx.layout), never
30
+ * by simulating clicks.
31
+ * - The sidebar is only ever collapsed by the `full` tier, through the
32
+ * app's own toggle — never force-hidden, never locked. Going back to
33
+ * `standard` restores it only if this plugin collapsed it.
34
+ * - Non-standard tiers close the details panel (self-heal while
35
+ * active); `standard` leaves everything to the app.
36
+ * - The chosen tier persists in localStorage and is restored on the
37
+ * next visit.
38
+ */
39
+ const KEY = "dsh.widthTier";
40
+ const OLD_KEY = "dsh.wide";
41
+ const SELF_HEAL_MS = 1000;
42
+ const READY_TIMEOUT_MS = 30000;
43
+
44
+ const TIERS = [
45
+ { id: "standard", label: "标准", value: "748px", note: "默认阅读宽度" },
46
+ { id: "medium", label: "中等", value: "960px" },
47
+ { id: "wide", label: "宽", value: "1280px" },
48
+ { id: "ultra", label: "超宽", value: "1400px" },
49
+ { id: "full", label: "全宽", value: "100%", note: "占满窗口" }
50
+ ];
51
+
52
+ function tierOf(id) {
53
+ for (let i = 0; i < TIERS.length; i++) {
54
+ if (TIERS[i].id === id) return TIERS[i];
55
+ }
56
+ return TIERS[0];
57
+ }
58
+
59
+ //#region lib/types/client/styles.js
60
+ /** Button + menu styles (theme variables, light/dark aware). */
61
+ const css = [
62
+ "#dsh-wide-toggle{position:fixed;right:20px;bottom:76px;z-index:999;display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:50%;background:var(--dsw-alias-button-floating-fill);color:var(--dsw-alias-label-secondary);box-shadow:0 2px 8px rgba(0,0,0,.18);cursor:pointer;opacity:.72;transition:opacity .2s ease,transform .2s ease,background .2s ease,color .2s ease}",
63
+ "#dsh-wide-toggle:hover{opacity:1;transform:scale(1.08)}",
64
+ "#dsh-wide-toggle:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}",
65
+ "#dsh-wide-toggle.active{background:var(--dsw-alias-state-business-primary);border-color:transparent;color:var(--dsw-static-neutral-00);opacity:1}",
66
+ "#dsh-wide-toggle.pulse{animation:dsh-wide-pulse .65s ease-out}",
67
+ "@keyframes dsh-wide-pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--dsw-alias-state-business-primary) 60%,transparent)}100%{box-shadow:0 0 0 14px transparent}}",
68
+ "#dsh-width-menu{position:fixed;right:20px;bottom:124px;z-index:1000;min-width:196px;padding:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:0 8px 28px rgba(0,0,0,.24);display:flex;flex-direction:column;gap:2px}",
69
+ "#dsh-width-menu[hidden]{display:none}",
70
+ "#dsh-width-menu button{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border:none;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:18px;text-align:left;cursor:pointer}",
71
+ "#dsh-width-menu button:hover{background:var(--dsw-alias-interactive-bg-hover)}",
72
+ "#dsh-width-menu button.selected{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-state-business-primary)}",
73
+ "#dsh-width-menu .dshwm-value{color:var(--dsw-alias-label-tertiary);font-size:12px}",
74
+ "#dsh-width-menu button.selected .dshwm-value{color:var(--dsw-alias-state-business-primary)}"
75
+ ].join("");
76
+ //#endregion
77
+
78
+ /** Services required by the browser bundle. */
79
+ const inject = ["layout"];
80
+
81
+ /**
82
+ * Client plugin body: inject the styles, build the picker button and
83
+ * menu, and drive the width tiers through the layout service.
84
+ * @param ctx - client root context (layout injected).
85
+ */
86
+ function apply(ctx) {
87
+ ctx.effect(() => {
88
+ if (typeof document === "undefined") return;
89
+
90
+ const styleTag = document.createElement("style");
91
+ styleTag.dataset.plugin = "dsh-width-tiers";
92
+ styleTag.textContent = css;
93
+ document.head.appendChild(styleTag);
94
+
95
+ const layout = ctx.layout;
96
+
97
+ /* ---- helpers ---- */
98
+ const body = () => document.body;
99
+
100
+ function currentTier() {
101
+ return body().getAttribute("data-dsh-width") || "standard";
102
+ }
103
+
104
+ function getSavedTier() {
105
+ try {
106
+ const v = window.localStorage.getItem(KEY);
107
+ for (const t of TIERS) {
108
+ if (t.id === v) return v;
109
+ }
110
+ } catch (e) {
111
+ /* ignore */
112
+ }
113
+ return "standard";
114
+ }
115
+
116
+ /* The AppFrame sets data-sidebar-collapsed / data-details-collapsed
117
+ * on the frame element after each render — use them as truth. */
118
+ function isSidebarCollapsed() {
119
+ return document.querySelector("[data-sidebar-collapsed]") !== null;
120
+ }
121
+
122
+ function isDetailsOpen() {
123
+ return document.querySelector("[data-details-collapsed]") === null;
124
+ }
125
+
126
+ function collapseSidebar() {
127
+ if (!isSidebarCollapsed()) layout.toggleSidebar();
128
+ }
129
+
130
+ function expandSidebar() {
131
+ if (isSidebarCollapsed()) layout.toggleSidebar();
132
+ }
133
+
134
+ function closeDetails() {
135
+ if (isDetailsOpen()) layout.closeDetails();
136
+ }
137
+
138
+ /* Locate the element that defines --dsh-chat-content-width at
139
+ * runtime (first match wins; only one element declares it).
140
+ * Cached; re-scanned whenever the cached node is gone. */
141
+ let widthRoot = null;
142
+
143
+ function findWidthRoot() {
144
+ if (widthRoot !== null && document.contains(widthRoot)) return widthRoot;
145
+ widthRoot = null;
146
+ const all = document.querySelectorAll("[class]");
147
+ for (let i = 0; i < all.length; i++) {
148
+ const v = getComputedStyle(all[i])
149
+ .getPropertyValue("--dsh-chat-content-width")
150
+ .trim();
151
+ if (v) {
152
+ widthRoot = all[i];
153
+ return widthRoot;
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+
159
+ function tierWidth(tier) {
160
+ switch (tier) {
161
+ case "medium": return "960px";
162
+ case "wide": return "1280px";
163
+ case "ultra": return "1400px";
164
+ case "full": return "100%";
165
+ default: return null; /* standard: remove the override */
166
+ }
167
+ }
168
+
169
+ function applyContentWidth(tier) {
170
+ const root = findWidthRoot();
171
+ if (root === null) return;
172
+ const w = tierWidth(tier);
173
+ if (w === null) root.style.removeProperty("--dsh-chat-content-width");
174
+ else root.style.setProperty("--dsh-chat-content-width", w, "important");
175
+ }
176
+
177
+ /* true while the CURRENT tier is the one that collapsed the sidebar */
178
+ let collapsedByTier = false;
179
+
180
+ function applyTier(tier) {
181
+ body().setAttribute("data-dsh-width", tier);
182
+ try {
183
+ window.localStorage.setItem(KEY, tier);
184
+ } catch (e) {
185
+ /* storage unavailable — state still applies this session */
186
+ }
187
+ applyContentWidth(tier);
188
+ if (tier === "full") {
189
+ if (!isSidebarCollapsed()) {
190
+ collapseSidebar();
191
+ collapsedByTier = true;
192
+ } else {
193
+ collapsedByTier = false;
194
+ }
195
+ } else if (tier === "standard") {
196
+ if (collapsedByTier && isSidebarCollapsed()) expandSidebar();
197
+ collapsedByTier = false;
198
+ } else {
199
+ collapsedByTier = false;
200
+ }
201
+ if (tier !== "standard") closeDetails();
202
+ syncButton();
203
+ syncMenu();
204
+ pulse();
205
+ }
206
+
207
+ /* ---- floating button with level-bar icon ---- */
208
+ const btn = document.createElement("button");
209
+ btn.type = "button";
210
+ btn.id = "dsh-wide-toggle";
211
+ btn.setAttribute("aria-haspopup", "menu");
212
+ btn.setAttribute("aria-expanded", "false");
213
+
214
+ function barIcon(tier) {
215
+ let idx = 0;
216
+ for (let i = 0; i < TIERS.length; i++) {
217
+ if (TIERS[i].id === tier) idx = i;
218
+ }
219
+ const widths = [3.5, 5.5, 7.5, 10, 13];
220
+ let bars = "";
221
+ for (let b = 0; b < 5; b++) {
222
+ const y = 0.5 + b * 3;
223
+ bars += `<rect x="1.5" y="${y}" width="${widths[b]}" height="2" rx="1" fill="currentColor" opacity="${b <= idx ? 1 : 0.3}"/>`;
224
+ }
225
+ return `<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false">${bars}</svg>`;
226
+ }
227
+
228
+ function syncButton() {
229
+ const tier = currentTier();
230
+ const t = tierOf(tier);
231
+ btn.innerHTML = barIcon(tier);
232
+ btn.classList.toggle("active", tier !== "standard");
233
+ btn.setAttribute("aria-label", `对话区宽度档位:${t.label}(${t.value}),点击选择档位`);
234
+ btn.title =
235
+ `对话区宽度档位:${t.label}(${t.value})\n` +
236
+ "点击选择:标准 748px / 中等 960px / 宽 1280px / 超宽 1400px / 全宽 100%";
237
+ }
238
+
239
+ /* ---- tier menu ---- */
240
+ const menu = document.createElement("div");
241
+ menu.id = "dsh-width-menu";
242
+ menu.setAttribute("role", "menu");
243
+ menu.hidden = true;
244
+
245
+ for (const t of TIERS) {
246
+ const item = document.createElement("button");
247
+ item.type = "button";
248
+ item.setAttribute("role", "menuitemradio");
249
+ item.setAttribute("data-tier", t.id);
250
+ item.innerHTML = `<span class="dshwm-label">${t.label}</span><span class="dshwm-value">${t.value}</span>`;
251
+ item.addEventListener("click", (e) => {
252
+ if (e && e.stopPropagation) e.stopPropagation();
253
+ applyTier(t.id);
254
+ hideMenu();
255
+ });
256
+ menu.appendChild(item);
257
+ }
258
+
259
+ function syncMenu() {
260
+ const tier = currentTier();
261
+ const items = menu.querySelectorAll("button[data-tier]");
262
+ for (let i = 0; i < items.length; i++) {
263
+ const on = items[i].getAttribute("data-tier") === tier;
264
+ items[i].classList.toggle("selected", on);
265
+ items[i].setAttribute("aria-checked", String(on));
266
+ }
267
+ }
268
+
269
+ function showMenu() {
270
+ menu.hidden = false;
271
+ btn.setAttribute("aria-expanded", "true");
272
+ syncMenu();
273
+ }
274
+
275
+ function hideMenu() {
276
+ menu.hidden = true;
277
+ btn.setAttribute("aria-expanded", "false");
278
+ }
279
+
280
+ btn.addEventListener("click", (e) => {
281
+ if (e && e.stopPropagation) e.stopPropagation();
282
+ if (menu.hidden) showMenu();
283
+ else hideMenu();
284
+ });
285
+
286
+ const onDocClick = (e) => {
287
+ if (menu.hidden) return;
288
+ if (e.target !== btn && !menu.contains(e.target)) hideMenu();
289
+ };
290
+ const onDocKey = (e) => {
291
+ if (e.key === "Escape" && !menu.hidden) hideMenu();
292
+ };
293
+ document.addEventListener("click", onDocClick);
294
+ document.addEventListener("keydown", onDocKey);
295
+
296
+ /* ---- feedback ring so every selection is visibly answered ---- */
297
+ function pulse() {
298
+ btn.classList.remove("pulse");
299
+ void btn.offsetWidth;
300
+ btn.classList.add("pulse");
301
+ window.setTimeout(() => btn.classList.remove("pulse"), 700);
302
+ }
303
+
304
+ /* ---- attach once the app shell has mounted ---- */
305
+ let attached = false;
306
+
307
+ function attach() {
308
+ if (attached || !body()) return;
309
+ if (document.querySelector("[data-sidebar-collapsed], [data-details-collapsed]") === null) return;
310
+ attached = true;
311
+ try {
312
+ window.localStorage.removeItem(OLD_KEY);
313
+ } catch (e) {
314
+ /* ignore */
315
+ }
316
+ body().appendChild(btn);
317
+ body().appendChild(menu);
318
+ syncButton();
319
+ const saved = getSavedTier();
320
+ if (saved !== "standard") applyTier(saved);
321
+ }
322
+
323
+ const readyTimer = setInterval(attach, 300);
324
+ window.addEventListener("load", attach);
325
+ const timeout = window.setTimeout(() => {
326
+ clearInterval(readyTimer);
327
+ attach();
328
+ }, READY_TIMEOUT_MS);
329
+
330
+ /* ---- self-heal: keep the details panel closed for non-standard
331
+ * tiers. The sidebar is never managed here — the user may expand
332
+ * it any time. ---- */
333
+ const healTimer = setInterval(() => {
334
+ const tier = currentTier();
335
+ if (tier === "standard") return;
336
+ closeDetails();
337
+ if (widthRoot !== null && !document.contains(widthRoot)) findWidthRoot();
338
+ }, SELF_HEAL_MS);
339
+
340
+ /* ---- disposer ---- */
341
+ return () => {
342
+ clearInterval(readyTimer);
343
+ clearInterval(healTimer);
344
+ clearTimeout(timeout);
345
+ document.removeEventListener("click", onDocClick);
346
+ document.removeEventListener("keydown", onDocKey);
347
+ if (btn.parentNode) btn.parentNode.removeChild(btn);
348
+ if (menu.parentNode) menu.parentNode.removeChild(menu);
349
+ if (styleTag.parentNode) styleTag.parentNode.removeChild(styleTag);
350
+ if (widthRoot !== null) widthRoot.style.removeProperty("--dsh-chat-content-width");
351
+ body().removeAttribute("data-dsh-width");
352
+ };
353
+ }, "dsh-width-tiers: ui");
354
+ }
355
+ //#endregion
356
+
357
+ exports.apply = apply;
358
+ exports.inject = inject;
359
+ return module.exports;
360
+ }
361
+ });
package/lib/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @module dsh-width-tiers
3
+ *
4
+ * Server-side entry of the dsh-width-tiers plugin: a minimal no-op cordis
5
+ * plugin. Its only job is to exist as a loader entry so the client-modules
6
+ * scanner discovers the browser bundle declared in package.json
7
+ * (`dsh.client` + `exports["./client"]`) and serves it at
8
+ * `/plugins/dsh-width-tiers/client.js` in the Web boot manifest.
9
+ *
10
+ * All functionality lives in the browser bundle (lib/client.js).
11
+ */
12
+
13
+ /** Stable cordis plugin name. */
14
+ export const name = "dsh-width-tiers";
15
+
16
+ /** Services required before this plugin mounts (none server-side). */
17
+ export const inject = [];
18
+
19
+ /** No-op host-side apply. */
20
+ export function apply() {}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "dsh-width-tiers",
3
+ "version": "1.0.0",
4
+ "description": "DSH Web client plugin: chat content width tiers (standard / medium / wide / ultra / full) with a floating picker button. Frees wide tables from the app's hard-coded 748px content cap.",
5
+ "license": "MIT",
6
+ "author": "aaronlei",
7
+ "publishConfig": {
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "type": "module",
11
+ "main": "lib/index.js",
12
+ "exports": {
13
+ ".": "./lib/index.js",
14
+ "./client": "./lib/client.js",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "lib",
19
+ "LICENSE",
20
+ "README.md",
21
+ "README.zh.md"
22
+ ],
23
+ "dsh": {
24
+ "client": {
25
+ "platform": "web",
26
+ "inject": [
27
+ "@deepseek-ai/dsh-client-ui-layout"
28
+ ]
29
+ }
30
+ },
31
+ "keywords": [
32
+ "dsh",
33
+ "deepseek-harness",
34
+ "plugin",
35
+ "web",
36
+ "width",
37
+ "tiers",
38
+ "widescreen",
39
+ "table",
40
+ "layout"
41
+ ],
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/aaronlei/dsh-width-tiers.git"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ }
49
+ }