dsh-plugin-workbench 0.0.12 → 0.0.13

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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.13] - 2026-08-26
4
+
5
+ ### Fixed
6
+
7
+ - **预览分栏宽度拖拽:一拖就骤然缩小到最窄,之后无法拖动、无法复原**。根因:
8
+ 拖拽计算"预览最大宽度"时用 `handle.parentElement` 测量中间列宽度,但 slot 系统把
9
+ slot 内容包在一层 `display: contents` 的包装元素里——该包装层不参与布局,
10
+ `getBoundingClientRect().width` 恒为 **0**,于是
11
+ `max = max(240, 0 - 240) = 240`,第一次 `pointermove` 就把宽度钳到最小宽度 240,
12
+ 之后每次移动都被钳在 240,表现为骤然缩小且拖不回来(100% 复现,与窗口/鼠标操作
13
+ 无关)。修复:`laidOutParent()` 跳过所有 `display: contents` 层,取真正参与布局的
14
+ flex 容器(中间列)再测量;持久化宽度恢复时同样用它校准。
15
+ - 拖拽健壮性(防同类"卡死"):`setPointerCapture` 保证 `pointerup` 在指针移出窗口后
16
+ 也能派发并清理监听器;监听 `pointercancel`;每次 `pointerdown` 防御性移除上一次
17
+ 残留的监听器;组件卸载时清理窗口监听器;拖拽中每步实时重测中间列宽度。
18
+
19
+ ### Added
20
+
21
+ - **预览宽度持久化**:拖拽后的分栏宽度写入 localStorage,刷新/重开会话后恢复
22
+ (自动按当前窗宽 clamp 在有效区间内)。
23
+
24
+
3
25
  ## [0.0.12] - 2026-08-25
4
26
 
5
27
  - 相关插件段新增 dsh-plugin-windows-guard(Windows 环境防坑守则 skill 插件,互相引流)。
package/lib/client.js CHANGED
@@ -15324,6 +15324,20 @@ window.__ModuleLoader__.load({
15324
15324
  const PREVIEW_MIN = 240;
15325
15325
  const CHAT_MIN = 240;
15326
15326
  const PREVIEW_TOO_LARGE_LABEL = "512KB";
15327
+ /** Persisted split width (px) — survives reloads so a drag is never lost. */
15328
+ const PREVIEW_WIDTH_KEY = "dsh-plugin-workbench:preview-width";
15329
+ /** Read the persisted preview width; `null` means "use the default 55%". */
15330
+ function storedPreviewWidth() {
15331
+ try {
15332
+ if (typeof window === "undefined") return null;
15333
+ const raw = window.localStorage.getItem(PREVIEW_WIDTH_KEY);
15334
+ if (raw === null) return null;
15335
+ const px = Number(raw);
15336
+ return Number.isFinite(px) && px > 0 ? px : null;
15337
+ } catch {
15338
+ return null;
15339
+ }
15340
+ }
15327
15341
  /** Same-origin raw-bytes route registered by the host half (see src/index.ts). */
15328
15342
  const RAW_PREFIX = "/dsh-plugin-files/raw";
15329
15343
  /** Same-origin SSE endpoint pushed by the host half (see src/index.ts). */
@@ -15385,6 +15399,22 @@ window.__ModuleLoader__.load({
15385
15399
  function clamp(value, min, max) {
15386
15400
  return Math.min(max, Math.max(min, value));
15387
15401
  }
15402
+ /**
15403
+ * Nearest ancestor that actually takes part in layout. The slot system wraps
15404
+ * each slot's content in a `display: contents` element: children still join
15405
+ * the OUTER flex row, but the wrapper itself reports a 0×0 bounding rect.
15406
+ * Measuring that (the old `handle.parentElement`) made `max` collapse to
15407
+ * `PREVIEW_MIN` on the first move — the pane jumped to its minimum width and
15408
+ * could never be dragged back out. Skip every `display: contents` layer.
15409
+ */
15410
+ function laidOutParent(el) {
15411
+ let node = el?.parentElement ?? null;
15412
+ while (node !== null) {
15413
+ if (getComputedStyle(node).display !== "contents") return node;
15414
+ node = node.parentElement;
15415
+ }
15416
+ return null;
15417
+ }
15388
15418
  /** Parent directory of a path ('C:/a/b.md' → 'C:/a'; '' when there is none). */
15389
15419
  function dirnameOf(path) {
15390
15420
  const idx = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
@@ -15436,6 +15466,8 @@ window.__ModuleLoader__.load({
15436
15466
  const highlightRef = (0, react.useRef)(null);
15437
15467
  const textareaRef = (0, react.useRef)(null);
15438
15468
  const gutterRef = (0, react.useRef)(null);
15469
+ const dragMoveRef = (0, react.useRef)(() => void 0);
15470
+ const dragUpRef = (0, react.useRef)(() => void 0);
15439
15471
  const refresh = (0, react.useCallback)(() => bump((v) => v + 1), []);
15440
15472
  const tabsRef = (0, react.useRef)(tabs);
15441
15473
  (0, react.useEffect)(() => {
@@ -15625,26 +15657,72 @@ window.__ModuleLoader__.load({
15625
15657
  document.body.style.removeProperty("--dsh-preview-width");
15626
15658
  };
15627
15659
  }, [isOpen]);
15660
+ (0, react.useEffect)(() => {
15661
+ const saved = storedPreviewWidth();
15662
+ if (saved === null) return;
15663
+ const preview = previewRef.current;
15664
+ const center = laidOutParent(preview);
15665
+ if (preview === null || preview === void 0 || center === null || center === void 0) return;
15666
+ const centerWidth = center.getBoundingClientRect().width;
15667
+ const max = Math.max(PREVIEW_MIN, centerWidth - CHAT_MIN);
15668
+ setPreviewWidth(clamp(saved, PREVIEW_MIN, max));
15669
+ }, [isOpen]);
15670
+ (0, react.useEffect)(() => () => {
15671
+ window.removeEventListener("pointermove", dragMoveRef.current);
15672
+ window.removeEventListener("pointerup", dragUpRef.current);
15673
+ window.removeEventListener("pointercancel", dragUpRef.current);
15674
+ }, []);
15675
+ /**
15676
+ * Start a split-width drag. Robustness notes (the "pane suddenly shrinks
15677
+ * and freezes" bug class):
15678
+ *
15679
+ * - Pointer capture reroutes every later pointer event to the handle, so
15680
+ * `pointerup` fires EVEN when the mouse is released outside the window.
15681
+ * Without it the up event is lost, `onUp` never runs, and the leftover
15682
+ * `onMove` keeps rewriting the width from its stale baseline on every
15683
+ * mouse move anywhere on the page — that is the "cannot drag / cannot
15684
+ * restore" state.
15685
+ * - `pointercancel` is cleaned up too (browser steals the pointer, e.g. a
15686
+ * tablet palm or an OS gesture).
15687
+ * - Pointerdown defensively removes any previous listeners first, so even a
15688
+ * capture-less leftover cannot survive into a second drag.
15689
+ * - The max is re-measured every move: the chat minimum is relative to the
15690
+ * CURRENT center column, which can change while the drag is in flight.
15691
+ */
15628
15692
  const onHandleDown = (0, react.useCallback)((e) => {
15629
15693
  e.preventDefault();
15630
15694
  const handle = handleRef.current;
15631
15695
  const preview = previewRef.current;
15632
15696
  if (handle === null || preview === null) return;
15633
- const center = handle.parentElement;
15697
+ const center = laidOutParent(handle);
15634
15698
  if (center === null) return;
15635
15699
  const startX = e.clientX;
15636
15700
  const startWidth = preview.getBoundingClientRect().width;
15637
- const centerWidth = center.getBoundingClientRect().width;
15638
- const max = Math.max(PREVIEW_MIN, centerWidth - CHAT_MIN);
15639
15701
  const onMove = (ev) => {
15640
- setPreviewWidth(clamp(startWidth + ev.clientX - startX, PREVIEW_MIN, max));
15702
+ const centerWidth = center.getBoundingClientRect().width;
15703
+ const max = Math.max(PREVIEW_MIN, centerWidth - CHAT_MIN);
15704
+ const width = clamp(startWidth + ev.clientX - startX, PREVIEW_MIN, max);
15705
+ setPreviewWidth(width);
15706
+ try {
15707
+ window.localStorage.setItem(PREVIEW_WIDTH_KEY, String(width));
15708
+ } catch {}
15641
15709
  };
15642
15710
  const onUp = () => {
15643
- window.removeEventListener("pointermove", onMove);
15644
- window.removeEventListener("pointerup", onUp);
15711
+ window.removeEventListener("pointermove", dragMoveRef.current);
15712
+ window.removeEventListener("pointerup", dragUpRef.current);
15713
+ window.removeEventListener("pointercancel", dragUpRef.current);
15645
15714
  };
15715
+ window.removeEventListener("pointermove", dragMoveRef.current);
15716
+ window.removeEventListener("pointerup", dragUpRef.current);
15717
+ window.removeEventListener("pointercancel", dragUpRef.current);
15718
+ dragMoveRef.current = onMove;
15719
+ dragUpRef.current = onUp;
15646
15720
  window.addEventListener("pointermove", onMove);
15647
15721
  window.addEventListener("pointerup", onUp);
15722
+ window.addEventListener("pointercancel", onUp);
15723
+ try {
15724
+ handle.setPointerCapture(e.pointerId);
15725
+ } catch {}
15648
15726
  }, []);
15649
15727
  const onSave = (0, react.useCallback)(async () => {
15650
15728
  if (active === void 0) return;