dsh-m 0.9.73 → 0.9.74

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
@@ -8,6 +8,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.74 功能:详情截图条滚轮横滚——裸滚轮 deltaY 安全映射 scrollLeft(钢人三红线)
12
+
13
+ - **背景(主人验收 0.9.73 实拍反馈)**:截图条是横滚容器,鼠标滚轮在横滚容器上默认纵滚祖先(弹窗)——图片多时纯鼠标用户只能拖 ~700px 细滚动条(离散目标上的连续输入,精度成本极高);触控板双指横滑(deltaX)与 Shift+滚轮虽有原生路径,但属隐藏知识不可达。
14
+ - **判定(新纯函数 `shotWheelAction`,src/client/wheel-strip.js,Node tests 直测)**:钢人三红线——①可滚才拦:scrollWidth ≤ clientWidth(+1 亚像素容差,0.9.72 同款口径) 零劫持,不满行的多数条目行为与今日一致,杜绝「静态劫持表达动态适用前提」的滚轮黑洞同构病复发;②到头放行:已到 deltaY 方向边界时零动作,纵滚还给弹窗(横向有得滚才借道、借完即还);③deltaX 不碰:|deltaX| ≥ |deltaY|(触控板横滑)不劫持不重复喂。非有限输入安全短路。
15
+ - **接线**:DetailModal 手动 `addEventListener("wheel", …, {passive:false})`(React onWheel 走 document 级 passive 委托,preventDefault 无效);deps=visible.length 随截图条存在性(条件渲染/primary 异步到达/坏图剔除)重挂;布局入参逐事件现读,无缓存失效问题。灯箱、走链、键盘导航零变化。
16
+ - **验证**:1339 项测试全绿(新增 client-wheel-strip 7 项:三红线逐条 + 容差边界 + 安全短路 + 接线源锚三件;img-chain ①锚放宽容 props 前缀)、typecheck 零错误。
17
+
11
18
  ### 0.9.73 修复:详情截图条统一瓷砖——「大小不一 + 预览图盖住左右邻图」(flex 收缩外溢根治)
12
19
 
13
20
  - **根因(主人详情弹窗实拍:缩略图大小参差,图片压在左右邻图上)**:两处机制缺陷叠加——① `.dsvm-shotbox` 无固定几何(盒 min 120×84 / img max 220×130),跟着图片固有比例走,16:9、4:3、竖图、小原图各渲各的,「大的大小的小」;②盒子显式 `min-width:120px` 顶掉了 flex 的 automatic minimum size——8 张截图自然总宽(~1.7k px)远超弹窗内容宽(~688px)触发 flex 收缩时,盒子被压到 120px 地板而盒内 `<img>`(替换元素)不受盒宽约束保持固有宽度,加上盒子 `overflow:visible`,图片居中外溢每侧盖住邻图 ~44px——且发生在滚动条出现之前,两症状同屏;加载完成瞬间行高还随最高图 84→130px 跳变(CLS)。
@@ -538,6 +545,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
538
545
 
539
546
  ## English
540
547
 
548
+ ### Feature in 0.9.74 — screenshot-strip wheel hscroll: bare-wheel deltaY safely mapped to scrollLeft (steelman's three red lines)
549
+
550
+ - **Background (owner feedback while verifying 0.9.73)**: the strip is a horizontal scroll container, and a mouse wheel over it scrolls the ancestor (the modal) by default — with many screenshots, mouse-only users must drag a ~700px thin scrollbar (continuous input on a discrete target); trackpad two-finger hscroll (deltaX) and Shift+wheel exist natively but are hidden knowledge.
551
+ - **Decision (new pure predicate `shotWheelAction`, src/client/wheel-strip.js, Node-tested)**: steelman's three red lines — ① intercept only when scrollable: zero hijack while scrollWidth ≤ clientWidth (+1 sub-pixel tolerance, same rule as 0.9.72's useScrollableContain), so most entries (1-2 images) behave exactly as today — no relapse of the "static hijack expressing a dynamic precondition" wheel black-hole disease; ② release at the edge: zero action once the delta direction has no horizontal room left, handing vertical scroll back to the modal (borrow the scroll only while there is room); ③ never touch deltaX: |deltaX| ≥ |deltaY| (trackpad hscroll) passes through untouched. Non-finite inputs short-circuit safely.
552
+ - **Wiring**: DetailModal attaches a manual `addEventListener("wheel", …, {passive:false})` (React's onWheel rides the document-level passive delegation where preventDefault is a no-op); deps=visible.length re-attach as the strip's existence changes (conditional render / async primary shots / broken-shot eviction); layout inputs are read per event — nothing cached. Lightbox, image chain, and keyboard navigation unchanged.
553
+ - **Verification**: 1339 tests green (new client-wheel-strip, 7 cases: three red lines + tolerance edges + safe short-circuit + wiring anchors; the img-chain ① anchor widened to allow props prefixes), typecheck clean.
554
+
541
555
  ### Fix in 0.9.73 — detail screenshot strip unified tiles: inconsistent sizes and previews overlapping neighboring thumbnails (flex-shrink overflow fixed at the root)
542
556
 
543
557
  - **Root cause (owner screenshot: jagged thumbnails with images spilling over their left/right neighbors)**: two defects stacked — ① `.dsvm-shotbox` had no fixed geometry (box min 120×84 / img max 220×130) and followed each image's intrinsic aspect ratio, so 16:9, 4:3, portrait and small originals all rendered at their own size; ② the explicit `min-width:120px` overrode the flex automatic minimum size — with eight screenshots' natural total width (~1.7k px) far exceeding the modal content width (~688px), flex-shrink squeezed each box to its 120px floor while the inner `<img>` (a replaced element) kept its intrinsic width, and with `overflow:visible` on the box each image spilled out centered, covering ~44px of each neighbor — all before the scrollbar even appeared; the row height also jumped 84→130px when the tallest image loaded (CLS).
package/lib/client.js CHANGED
@@ -494,6 +494,28 @@ var init_lightbox = __esm({
494
494
  }
495
495
  });
496
496
 
497
+ // src/client/wheel-strip.js
498
+ var wheel_strip_exports = {};
499
+ __export(wheel_strip_exports, {
500
+ shotWheelAction: () => shotWheelAction
501
+ });
502
+ function shotWheelAction({ deltaX, deltaY, scrollWidth, clientWidth, scrollLeft }) {
503
+ const inputs = [deltaX, deltaY, scrollWidth, clientWidth, scrollLeft];
504
+ if (!inputs.every(Number.isFinite)) return { scrollBy: 0, preventDefault: false };
505
+ if (scrollWidth <= clientWidth + 1) return { scrollBy: 0, preventDefault: false };
506
+ if (deltaY === 0 || Math.abs(deltaX) >= Math.abs(deltaY)) return { scrollBy: 0, preventDefault: false };
507
+ const maxScrollLeft = scrollWidth - clientWidth;
508
+ const atEnd = scrollLeft >= maxScrollLeft - 1;
509
+ const atStart = scrollLeft <= 0;
510
+ if (deltaY > 0 && atEnd || deltaY < 0 && atStart) return { scrollBy: 0, preventDefault: false };
511
+ return { scrollBy: deltaY, preventDefault: true };
512
+ }
513
+ var init_wheel_strip = __esm({
514
+ "src/client/wheel-strip.js"() {
515
+ "use strict";
516
+ }
517
+ });
518
+
497
519
  // src/client/window-drag.js
498
520
  var window_drag_exports = {};
499
521
  __export(window_drag_exports, {
@@ -1758,6 +1780,7 @@ var { DEFAULT_PAGE_SIZE: DEFAULT_PAGE_SIZE2, MARKET_PAGE_SIZES: MARKET_PAGE_SIZE
1758
1780
  var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
1759
1781
  var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCommonJS(backdrop_exports));
1760
1782
  var { lbStep: lbStep2, lbNeighbors: lbNeighbors2, swipeDir: swipeDir2 } = (init_lightbox(), __toCommonJS(lightbox_exports));
1783
+ var { shotWheelAction: shotWheelAction2 } = (init_wheel_strip(), __toCommonJS(wheel_strip_exports));
1761
1784
  var { dragSlop: dragSlop2, grabOffset: grabOffset2, titlebarTopInset: titlebarTopInset2, clampPoint: clampPoint2, loadPanelPos: loadPanelPos2, savePanelPos: savePanelPos2, isDragTarget: isDragTarget2 } = (init_window_drag(), __toCommonJS(window_drag_exports));
1762
1785
  var { WESERV_TIMEOUT_MS: WESERV_TIMEOUT_MS2, weservUrl: weservUrl2, serviceBucketOf: serviceBucketOf2, tierOrder: tierOrder2, nextTier: nextTier2, needsTimeout: needsTimeout2, preferredTier: preferredTier2, rememberSuccess: rememberSuccess2 } = (init_img_chain(), __toCommonJS(img_chain_exports));
1763
1786
  var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
@@ -3457,6 +3480,19 @@ function DetailModal({ it, labels, busy, onClose, onInstall, onUpgrade, upgradeB
3457
3480
  useEffect(() => {
3458
3481
  if (closeRef.current) closeRef.current.focus();
3459
3482
  }, []);
3483
+ const shotRowRef = useRef(null);
3484
+ useEffect(() => {
3485
+ const row = shotRowRef.current;
3486
+ if (!row || typeof row.addEventListener !== "function") return;
3487
+ const onWheel = (e) => {
3488
+ const action = shotWheelAction2({ deltaX: e.deltaX, deltaY: e.deltaY, scrollWidth: row.scrollWidth, clientWidth: row.clientWidth, scrollLeft: row.scrollLeft });
3489
+ if (!action.preventDefault) return;
3490
+ e.preventDefault();
3491
+ row.scrollLeft += action.scrollBy;
3492
+ };
3493
+ row.addEventListener("wheel", onWheel, { passive: false });
3494
+ return () => row.removeEventListener("wheel", onWheel);
3495
+ }, [visible.length]);
3460
3496
  useEffect(() => {
3461
3497
  const onKey = (e) => {
3462
3498
  if (e.key === "Escape") {
@@ -3554,7 +3590,7 @@ function DetailModal({ it, labels, busy, onClose, onInstall, onUpgrade, upgradeB
3554
3590
  ) : null,
3555
3591
  visible.length ? h(
3556
3592
  "div",
3557
- { className: "dsvm-shotrow" },
3593
+ { ref: shotRowRef, className: "dsvm-shotrow" },
3558
3594
  ...shots.map((src, i) => h(Shot, { key: `${i}:${src}`, src, onClick: () => setLb(visible.indexOf(src)), onBroken: breakShot }))
3559
3595
  ) : null,
3560
3596
  it.community === true ? h(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.73",
3
+ "version": "0.9.74",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",