dsh-width-tiers 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,29 +4,38 @@
4
4
 
5
5
  A client plugin for the **DeepSeek Harness Web GUI** that adds **chat content
6
6
  width tiers** with a floating picker button (bottom-right, above the
7
- composer): 标准 / 中等 / 宽 / 超宽 / 全宽 (standard / medium / wide / ultra /
8
- full).
7
+ composer): 标准 / 中等 / 宽 / 超宽 / 全宽 / 自定义 (standard / medium / wide /
8
+ ultra / full / custom).
9
9
 
10
10
  ## Why
11
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.
12
+ The DSH Web app caps the message column — and every table inside it — at
13
+ `--dsh-chat-content-width` (0.1.7 default `clamp(680px, column × 0.64,
14
+ 920px)`), no matter how wide your window is, so wide tables always need
15
+ horizontal scrolling.
16
16
 
17
- This plugin overrides that variable per tier:
17
+ This plugin sets the width per tier through 0.1.7's **native width axis**
18
+ (`--dsh-chat-user-width` + the app's `dsh.conversation.contentWidth`
19
+ preference), letting the app clamp every value to "column − 176px handle
20
+ budget" — so the built-in drag handles keep working:
18
21
 
19
22
  | Tier | Content width | Sidebar |
20
23
  |---|---|---|
21
- | standard 标准 | 748px (app default) | untouched |
24
+ | standard 标准 | app default | untouched |
22
25
  | medium 中等 | 960px | untouched |
23
26
  | wide 宽 | 1280px | untouched |
24
27
  | ultra 超宽 | 1400px | untouched |
25
- | full 全宽 | 100% (fills the window) | collapsed to the icon rail |
28
+ | full 全宽 | column minus the handle budget (fills the available width) | collapsed to the icon rail |
29
+ | custom 自定义 | your dragged width, re-selectable from the menu | untouched |
30
+
31
+ On narrow windows wider tiers are clamped to the available column, so nothing
32
+ overflows under the sidebar.
26
33
 
27
34
  The choice persists in `localStorage` and is restored on the next visit; the
28
35
  menu highlights the current tier, and the button's five level-bars show it at
29
- a glance.
36
+ a glance (all lit for custom). Dragging one of 0.1.7's native edge handles
37
+ switches the picker to the custom tier — the plugin stands aside for native
38
+ drags.
30
39
 
31
40
  ## Install
32
41
 
package/README.zh.md CHANGED
@@ -3,27 +3,33 @@
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
4
 
5
5
  DeepSeek Harness Web GUI 的客户端插件:给对话区加**内容宽度档位选择器**
6
- (右下角圆形按钮,输入框上方),共 5 档:
7
- 标准 / 中等 / 宽 / 超宽 / 全宽。
6
+ (右下角圆形按钮,输入框上方),共 6 档:
7
+ 标准 / 中等 / 宽 / 超宽 / 全宽 / 自定义。
8
8
 
9
9
  ## 为什么需要它
10
10
 
11
- DSH Web 在会话根元素上硬编码了一个阅读宽度
12
- (`--dsh-chat-content-width: 748px`),消息列——包括里面的所有表格——最大
13
- 只有 **748px**,不管窗口和面板多宽都不变,宽表格只能横向滚动。
11
+ DSH Web 把消息列——包括里面的所有表格——的宽度限制在
12
+ `--dsh-chat-content-width`(0.1.7 默认 `clamp(680px, 列宽 × 0.64, 920px)`),
13
+ 不管窗口多宽都不变,宽表格只能横向滚动。
14
14
 
15
- 本插件按档位覆盖这个变量:
15
+ 本插件通过 0.1.7 的**原生宽度通道**(`--dsh-chat-user-width` +
16
+ `dsh.conversation.contentWidth` 偏好)按档位设定宽度,由应用自身钳制到
17
+ 「列宽 − 176px 手柄预留」,因此自带的原生拖拽手柄始终可用:
16
18
 
17
19
  | 档位 | 内容宽度 | 侧边栏 |
18
20
  |---|---|---|
19
- | 标准 | 748px(应用默认) | 不动 |
21
+ | 标准 | 应用默认 | 不动 |
20
22
  | 中等 | 960px | 不动 |
21
23
  | 宽 | 1280px | 不动 |
22
24
  | 超宽 | 1400px | 不动 |
23
- | 全宽 | 100%(占满窗口) | 收起为图标轨道 |
25
+ | 全宽 | 列宽减手柄预留(占满可用宽度) | 收起为图标轨道 |
26
+ | 自定义 | 拖拽原生手柄得出,可在菜单回切 | 不动 |
27
+
28
+ 窄窗口下超宽档位会自动被钳到可用宽度,不会溢出被侧栏覆盖。
24
29
 
25
30
  选择会记住(localStorage),下次打开自动恢复;菜单里当前档位高亮,按钮上
26
- 的五条横条图标也直观显示当前档位。
31
+ 的五条横条图标也直观显示当前档位(自定义档全亮)。拖动 0.1.7 自带的
32
+ 对话区边缘手柄会自动切到「自定义」档,插件让位于原生拖拽。
27
33
 
28
34
  ## 安装
29
35
 
package/lib/client.js CHANGED
@@ -9,23 +9,28 @@ window.__ModuleLoader__.load({
9
9
  /**
10
10
  * dsh-width-tiers — chat content width tiers for the DSH Web GUI.
11
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:
12
+ * The app derives the transcript width from
13
+ * `--dsh-chat-content-width`, which 0.1.7-alpha.1 resolves as
14
+ * `var(--dsh-chat-user-width, clamp(680px, column * .64, 920px))` on
15
+ * the conversation body. 0.1.7 also added native drag handles
16
+ * (ConversationWidthControls) that publish
17
+ * `--dsh-conversation-column-width` / `--dsh-chat-user-width` on the
18
+ * body's PARENT and clamp every value to the column minus a 176px
19
+ * handle budget. Forcing `--dsh-chat-content-width` with `!important`
20
+ * (the 1.0.5 approach) muted those handles and let over-wide
21
+ * transcripts overflow under the sidebar/rightbar columns, so this
22
+ * version drives the app's own width axis instead — see
23
+ * `applyContentWidth`. Other invariants:
17
24
  *
18
- * 标准 standard 748px (app default, no override)
25
+ * 标准 standard app default (no override)
19
26
  * 中等 medium 960px (sidebar untouched)
20
27
  * 宽 wide 1280px (sidebar untouched)
21
28
  * 超宽 ultra 1400px (sidebar untouched)
22
- * 全宽 full 100% (sidebar collapsed to the icon rail)
29
+ * 全宽 full column max (sidebar collapsed to the icon rail)
30
+ * 自定义 custom user-dragged width (plugin hands width over to
31
+ * the app's native handles; the dragged value stays
32
+ * re-selectable from the menu)
23
33
  *
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
34
  * - Panels are driven through the layout service (ctx.layout), never
30
35
  * by simulating clicks.
31
36
  * - The sidebar is only ever collapsed by the `full` tier, through the
@@ -41,6 +46,14 @@ window.__ModuleLoader__.load({
41
46
  */
42
47
  const KEY = "dsh.widthTier";
43
48
  const OLD_KEY = "dsh.wide";
49
+ /** Last user-dragged width (px), backing the `custom` tier. */
50
+ const CUSTOM_KEY = "dsh.widthTierCustom";
51
+ /** The app's own transcript width preference (ConversationWidthControls). */
52
+ const APP_PREF_KEY = "dsh.conversation.contentWidth";
53
+ /* Mirrors ConversationWidthControls in 0.1.7-alpha.1: clamp floor and
54
+ * the horizontal budget both width handles + safe edges reserve. */
55
+ const CONTENT_MIN_PX = 640;
56
+ const CONTENT_EDGE_BUDGET_PX = 176;
44
57
  const READY_TIMEOUT_MS = 30000;
45
58
  /* Self-heal cadence: DOM-driven (MutationObserver scheduling on the
46
59
  * next animation frame — before paint, so the width never flashes
@@ -62,6 +75,7 @@ window.__ModuleLoader__.load({
62
75
  "tier.wide": "宽",
63
76
  "tier.ultra": "超宽",
64
77
  "tier.full": "全宽",
78
+ "tier.custom": "自定义",
65
79
  "button.aria": "对话区宽度档位:{label}({value}),点击选择档位",
66
80
  "button.title.summary": "对话区宽度档位:{label}({value})",
67
81
  "button.title.hint": "点击选择:{list}"
@@ -73,6 +87,7 @@ window.__ModuleLoader__.load({
73
87
  "tier.wide": "Wide",
74
88
  "tier.ultra": "Ultra",
75
89
  "tier.full": "Full",
90
+ "tier.custom": "Custom",
76
91
  "button.aria": "Chat width tier: {label} ({value}). Click to choose a tier.",
77
92
  "button.title.summary": "Chat width tier: {label} ({value})",
78
93
  "button.title.hint": "Click to choose: {list}"
@@ -84,7 +99,10 @@ window.__ModuleLoader__.load({
84
99
  { id: "medium", key: "tier.medium", value: "960px" },
85
100
  { id: "wide", key: "tier.wide", value: "1280px" },
86
101
  { id: "ultra", key: "tier.ultra", value: "1400px" },
87
- { id: "full", key: "tier.full", value: "100%" }
102
+ { id: "full", key: "tier.full", value: "100%" },
103
+ /* Reached only by dragging a native width handle; the displayed
104
+ * value is the dragged width, resolved at render time. */
105
+ { id: "custom", key: "tier.custom", value: null }
88
106
  ];
89
107
 
90
108
  function tierOf(id) {
@@ -110,6 +128,7 @@ window.__ModuleLoader__.load({
110
128
  "#dsh-width-menu[hidden]{display:none}",
111
129
  "#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}",
112
130
  "#dsh-width-menu button:hover{background:var(--dsw-alias-interactive-bg-hover)}",
131
+ "#dsh-width-menu button.disabled{opacity:.45;cursor:default;pointer-events:none}",
113
132
  "#dsh-width-menu button.selected{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-state-business-primary)}",
114
133
  "#dsh-width-menu .dshwm-value{color:var(--dsw-alias-label-tertiary);font-size:12px}",
115
134
  "#dsh-width-menu button.selected .dshwm-value{color:var(--dsw-alias-state-business-primary)}"
@@ -165,12 +184,15 @@ window.__ModuleLoader__.load({
165
184
  return document.querySelector("[data-sidebar-collapsed]") !== null;
166
185
  }
167
186
 
168
- /* 0.1.5-rc.2 replaced the details panel with the rightbar and
169
- * dropped `data-details-collapsed` entirely; closeRightbar is
170
- * idempotent, so it is called unconditionally. */
171
- function closeRightbar() {
172
- layout.closeRightbar?.();
173
- }
187
+ /* NOTE: the 1.0.x "close the right panel on non-standard tiers"
188
+ * self-heal is gone on purpose. On 0.1.7 the rightbar panel
189
+ * lives in its own seat: closeRightbar() only retracts the
190
+ * grid TRACK (rightbarTrack=false) while the seat stays
191
+ * expanded, so the panel keeps rendering as an OVERLAY pinned
192
+ * to the frame's right edge — hanging over and covering the
193
+ * conversation (对话窗口被边侧栏覆盖). The plugin must never
194
+ * touch the rightbar; the app's own width axis already yields
195
+ * space to the panel. */
174
196
 
175
197
  function collapseSidebar() {
176
198
  if (!isSidebarCollapsed()) layout.toggleSidebar();
@@ -180,10 +202,6 @@ window.__ModuleLoader__.load({
180
202
  if (isSidebarCollapsed()) layout.toggleSidebar();
181
203
  }
182
204
 
183
- function closeDetails() {
184
- closeRightbar();
185
- }
186
-
187
205
  /* Locate the element that defines --dsh-chat-content-width at
188
206
  * runtime (first match wins; only one element declares it).
189
207
  * Cached; the app remounts the chat tree on conversation switch,
@@ -242,22 +260,77 @@ window.__ModuleLoader__.load({
242
260
  return scanWidthRoot();
243
261
  }
244
262
 
245
- function tierWidth(tier) {
263
+ /* ---- 0.1.7 native width axis ----
264
+ * The conversation body's parent is the element the app's
265
+ * ConversationWidthControls publishes
266
+ * `--dsh-conversation-column-width` / `--dsh-chat-user-width`
267
+ * on. Writing there (never with `!important`) keeps native
268
+ * drag handles in charge: during a drag the app rewrites the
269
+ * same inline property every frame and wins the cascade. */
270
+ function widthTarget(root) {
271
+ return (root && root.parentElement) || root;
272
+ }
273
+
274
+ function readStoredNumber(key) {
275
+ try {
276
+ const v = Number(window.localStorage.getItem(key));
277
+ return Number.isFinite(v) && v > 0 ? v : null;
278
+ } catch (e) {
279
+ return null;
280
+ }
281
+ }
282
+
283
+ /* Resolve a tier to the px the app would clamp it to
284
+ * (resolveContentWidth: max = column - handle budget), or
285
+ * null when the tier leaves the width to the app. */
286
+ function tierPx(tier, root) {
287
+ const cap = Math.max(CONTENT_MIN_PX, root.offsetWidth - CONTENT_EDGE_BUDGET_PX);
246
288
  switch (tier) {
247
- case "medium": return "960px";
248
- case "wide": return "1280px";
249
- case "ultra": return "1400px";
250
- case "full": return "100%";
251
- default: return null; /* standard: remove the override */
289
+ case "medium": return Math.min(960, cap);
290
+ case "wide": return Math.min(1280, cap);
291
+ case "ultra": return Math.min(1400, cap);
292
+ case "full": return cap;
293
+ case "custom": {
294
+ const dragged = readStoredNumber(CUSTOM_KEY);
295
+ return dragged === null ? null : Math.min(dragged, cap);
296
+ }
297
+ default: return null; /* standard: app default */
252
298
  }
253
299
  }
254
300
 
255
301
  function applyContentWidth(tier) {
256
302
  const root = findWidthRoot();
257
303
  if (root === null) return;
258
- const w = tierWidth(tier);
259
- if (w === null) root.style.removeProperty("--dsh-chat-content-width");
260
- else root.style.setProperty("--dsh-chat-content-width", w, "important");
304
+ const target = widthTarget(root);
305
+ /* custom before the first drag has no width of its own —
306
+ * leave whatever the app last published in place. */
307
+ if (tier === "custom" && readStoredNumber(CUSTOM_KEY) === null) return;
308
+ const px = tierPx(tier, root);
309
+ if (px === null) {
310
+ if (target.style.getPropertyValue("--dsh-chat-user-width") !== "") {
311
+ target.style.removeProperty("--dsh-chat-user-width");
312
+ try {
313
+ window.localStorage.removeItem(APP_PREF_KEY);
314
+ } catch (e) {
315
+ /* ignore */
316
+ }
317
+ }
318
+ return;
319
+ }
320
+ /* Mirror into the app's preference so window resizes and
321
+ * conversation remounts re-clamp through the app's own
322
+ * publish cycle instead of relying on plugin healing. */
323
+ const next = String(Math.round(px));
324
+ if (target.style.getPropertyValue("--dsh-chat-user-width") !== next + "px") {
325
+ target.style.setProperty("--dsh-chat-user-width", next + "px");
326
+ }
327
+ try {
328
+ if (window.localStorage.getItem(APP_PREF_KEY) !== next) {
329
+ window.localStorage.setItem(APP_PREF_KEY, next);
330
+ }
331
+ } catch (e) {
332
+ /* ignore */
333
+ }
261
334
  }
262
335
 
263
336
  /* true while the CURRENT tier is the one that collapsed the sidebar */
@@ -284,7 +357,6 @@ window.__ModuleLoader__.load({
284
357
  } else {
285
358
  collapsedByTier = false;
286
359
  }
287
- if (tier !== "standard") closeDetails();
288
360
  syncButton();
289
361
  syncMenu();
290
362
  pulse();
@@ -311,17 +383,26 @@ window.__ModuleLoader__.load({
311
383
  return `<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false">${bars}</svg>`;
312
384
  }
313
385
 
386
+ /* Display value for a tier; `custom` shows the dragged width. */
387
+ function tierValueText(tierId) {
388
+ const def = tierOf(tierId);
389
+ if (def.value !== null) return def.value;
390
+ const px = readStoredNumber(CUSTOM_KEY);
391
+ return px === null ? "—" : `${Math.round(px)}px`;
392
+ }
393
+
314
394
  function syncButton() {
315
395
  const tier = currentTier();
316
396
  const def = tierOf(tier);
317
397
  const label = t(def.key);
318
- const summary = t("button.title.summary", { label, value: def.value });
398
+ const value = tierValueText(tier);
399
+ const summary = t("button.title.summary", { label, value });
319
400
  const hint = t("button.title.hint", {
320
- list: TIERS.map((tierDef) => `${t(tierDef.key)} ${tierDef.value}`).join(" / ")
401
+ list: TIERS.map((tierDef) => `${t(tierDef.key)} ${tierValueText(tierDef.id)}`).join(" / ")
321
402
  });
322
403
  btn.innerHTML = barIcon(tier);
323
404
  btn.classList.toggle("active", tier !== "standard");
324
- btn.setAttribute("aria-label", t("button.aria", { label, value: def.value }));
405
+ btn.setAttribute("aria-label", t("button.aria", { label, value }));
325
406
  btn.title = `${summary}\n${hint}`;
326
407
  }
327
408
 
@@ -336,9 +417,10 @@ window.__ModuleLoader__.load({
336
417
  item.type = "button";
337
418
  item.setAttribute("role", "menuitemradio");
338
419
  item.setAttribute("data-tier", def.id);
339
- item.innerHTML = `<span class="dshwm-label">${t(def.key)}</span><span class="dshwm-value">${def.value}</span>`;
420
+ item.innerHTML = `<span class="dshwm-label">${t(def.key)}</span><span class="dshwm-value">${tierValueText(def.id)}</span>`;
340
421
  item.addEventListener("click", (e) => {
341
422
  if (e && e.stopPropagation) e.stopPropagation();
423
+ if (item.classList.contains("disabled")) return;
342
424
  applyTier(def.id);
343
425
  hideMenu();
344
426
  });
@@ -349,11 +431,18 @@ window.__ModuleLoader__.load({
349
431
  const tier = currentTier();
350
432
  const items = menu.querySelectorAll("button[data-tier]");
351
433
  for (let i = 0; i < items.length; i++) {
352
- const on = items[i].getAttribute("data-tier") === tier;
434
+ const id = items[i].getAttribute("data-tier");
435
+ const on = id === tier;
353
436
  items[i].classList.toggle("selected", on);
354
437
  items[i].setAttribute("aria-checked", String(on));
355
438
  const labelEl = items[i].querySelector(".dshwm-label");
356
- if (labelEl) labelEl.textContent = t(tierOf(items[i].getAttribute("data-tier")).key);
439
+ if (labelEl) labelEl.textContent = t(tierOf(id).key);
440
+ const valueEl = items[i].querySelector(".dshwm-value");
441
+ if (valueEl) valueEl.textContent = tierValueText(id);
442
+ /* custom is inert until the user has dragged once */
443
+ const disabled = id === "custom" && readStoredNumber(CUSTOM_KEY) === null;
444
+ items[i].classList.toggle("disabled", disabled);
445
+ items[i].setAttribute("aria-disabled", String(disabled));
357
446
  }
358
447
  }
359
448
 
@@ -384,6 +473,48 @@ window.__ModuleLoader__.load({
384
473
  document.addEventListener("click", onDocClick);
385
474
  document.addEventListener("keydown", onDocKey);
386
475
 
476
+ /* ---- hand the width over to the app's native drag handles ----
477
+ * Pressing a `[data-width-handle]` means the user is taking the
478
+ * width over: switch to the `custom` tier so healing stops
479
+ * enforcing a preset, and let the app's drag win the cascade
480
+ * (it rewrites the same inline property every frame). The
481
+ * settled width is recorded on release so it stays
482
+ * re-selectable from the menu. */
483
+ const isHandleEvent = (e) =>
484
+ e.target instanceof Element && e.target.closest("[data-width-handle]") !== null;
485
+
486
+ const onHandlePointerDown = (e) => {
487
+ if (!isHandleEvent(e) || currentTier() === "custom") return;
488
+ body().setAttribute("data-dsh-width", "custom");
489
+ try {
490
+ window.localStorage.setItem(KEY, "custom");
491
+ } catch (err) {
492
+ /* ignore */
493
+ }
494
+ collapsedByTier = false;
495
+ syncButton();
496
+ syncMenu();
497
+ };
498
+
499
+ const onHandlePointerUp = (e) => {
500
+ if (!isHandleEvent(e)) return;
501
+ const root = findWidthRoot();
502
+ if (root === null) return;
503
+ const px = Number.parseFloat(
504
+ widthTarget(root).style.getPropertyValue("--dsh-chat-user-width")
505
+ );
506
+ if (!Number.isFinite(px) || px <= 0) return;
507
+ try {
508
+ window.localStorage.setItem(CUSTOM_KEY, String(Math.round(px)));
509
+ } catch (err) {
510
+ /* ignore */
511
+ }
512
+ syncButton();
513
+ syncMenu();
514
+ };
515
+ document.addEventListener("pointerdown", onHandlePointerDown, true);
516
+ document.addEventListener("pointerup", onHandlePointerUp, true);
517
+
387
518
  /* ---- feedback ring so every selection is visibly answered ---- */
388
519
  function pulse() {
389
520
  btn.classList.remove("pulse");
@@ -433,16 +564,19 @@ window.__ModuleLoader__.load({
433
564
  attach();
434
565
  }, READY_TIMEOUT_MS);
435
566
 
436
- /* ---- self-heal: keep the details panel closed for non-standard
437
- * tiers. The sidebar is never managed here — the user may expand
438
- * it any time. Driven by DOM mutations, scheduled on the next
439
- * animation frame (before the browser paints) instead of a fixed
440
- * hot loop: conversation switches remount the chat tree, and the
441
- * width override must land on the fresh root before its first
442
- * paint — otherwise the default width flashes for a frame.
443
- * While the new root has not appeared yet (mid-remount), full
444
- * scans are owned by a backoff retry; mutation-driven heals only
445
- * try the cheap path walk so DOM churn cannot storm. ---- */
567
+ /* ---- self-heal: re-apply the width when the chat tree
568
+ * remounts (conversation switch). Panels are never managed
569
+ * here — the user may expand/collapse either side any time;
570
+ * forcing the rightbar closed only retracts the grid track
571
+ * while the seat keeps rendering as an overlay that covers
572
+ * the conversation. Driven by DOM mutations, scheduled on the
573
+ * next animation frame (before the browser paints) instead of
574
+ * a fixed hot loop: the width must land on the fresh root
575
+ * before its first paint — otherwise the default width
576
+ * flashes for a frame. While the new root has not appeared
577
+ * yet (mid-remount), full scans are owned by a backoff retry;
578
+ * mutation-driven heals only try the cheap path walk so DOM
579
+ * churn cannot storm. ---- */
446
580
  let rootMissing = false;
447
581
  let rootRetryDelay = ROOT_RETRY_MIN_MS;
448
582
  let rootRetryTimer = null;
@@ -479,7 +613,6 @@ window.__ModuleLoader__.load({
479
613
  function heal(fromObserver) {
480
614
  const tier = currentTier();
481
615
  if (tier === "standard") return;
482
- closeDetails();
483
616
  const prev = widthRoot;
484
617
  let root;
485
618
  if (rootMissing) {
@@ -503,6 +636,11 @@ window.__ModuleLoader__.load({
503
636
  rootRetryDelay = ROOT_RETRY_MIN_MS;
504
637
  }
505
638
  if (root !== prev) applyContentWidth(tier);
639
+ /* full tracks the live column: after the sidebar collapse
640
+ * animation settles the column widens, and the width must
641
+ * follow (applyContentWidth is diff-checked, so steady
642
+ * state writes nothing). */
643
+ if (tier === "full") applyContentWidth("full");
506
644
  }
507
645
 
508
646
  function startHealing() {
@@ -518,7 +656,7 @@ window.__ModuleLoader__.load({
518
656
  subtree: true,
519
657
  childList: true,
520
658
  attributes: true,
521
- attributeFilter: ["data-sidebar-collapsed", "data-rightbar-collapsed"]
659
+ attributeFilter: ["data-sidebar-collapsed"]
522
660
  });
523
661
  }
524
662
  /* Low-frequency fallback so self-heal still runs if the
@@ -538,10 +676,18 @@ window.__ModuleLoader__.load({
538
676
  if (observer !== null) observer.disconnect();
539
677
  document.removeEventListener("click", onDocClick);
540
678
  document.removeEventListener("keydown", onDocKey);
679
+ document.removeEventListener("pointerdown", onHandlePointerDown, true);
680
+ document.removeEventListener("pointerup", onHandlePointerUp, true);
541
681
  if (btn.parentNode) btn.parentNode.removeChild(btn);
542
682
  if (menu.parentNode) menu.parentNode.removeChild(menu);
543
683
  if (styleTag.parentNode) styleTag.parentNode.removeChild(styleTag);
544
- if (widthRoot !== null) widthRoot.style.removeProperty("--dsh-chat-content-width");
684
+ /* Hand the width back to the app default; the app's own
685
+ * preference (which this plugin mirrors) stays so the last
686
+ * width survives a plugin reload — matching native-drag
687
+ * semantics. */
688
+ if (widthRoot !== null) {
689
+ widthTarget(widthRoot).style.removeProperty("--dsh-chat-user-width");
690
+ }
545
691
  body().removeAttribute("data-dsh-width");
546
692
  };
547
693
  }, "dsh-width-tiers: ui");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-width-tiers",
3
- "version": "1.0.5",
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. UI copy follows the app language (zh / en).",
3
+ "version": "1.0.6",
4
+ "description": "DSH Web client plugin: chat content width tiers (standard / medium / wide / ultra / full / custom) with a floating picker button. Drives the app's native width axis on DSH 0.1.7+, so the built-in drag handles keep working. UI copy follows the app language (zh / en).",
5
5
  "license": "MIT",
6
6
  "author": "aaronlei",
7
7
  "publishConfig": {