dsh-m 0.9.87 → 0.9.89

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/lib/client.js +324 -89
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,21 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.89 优化:两行态头部视觉恒定——完整标题保留 + winctl 尺寸/行高不随断点跳变
12
+
13
+ - **需求(2026-10-15,主人真机反馈)**:0.9.88 的 720 断点两行重排带来两处可感知跳变——标题在「插件市场 ↔ DeepSeek Harness 插件市场」间换字;右上角 winctl 按钮忽大忽小(28↔36px),且标题/按钮垂直位置跨断点漂移 1–2px,来回拖拽「一跳一跳」。
14
+ - **方案**:① 两行态保留完整标题——seg 让行后首行空间充裕(640 宽实测 612px 内容区只需 ~300px),短标题切换退役(DOM 双 span 与基础隐藏规则保留,极端窄由 nowrap+ellipsis 兜底);② winctl 触控 36px 从容器断点迁 `pointer:coarse`(按钮大小跟随输入方式而非宽度:鼠标恒 28px、触屏恒 36px);③ winctl 盒钉 34px = seg 行高(按钮 28 + 边框 2 同源公式),单行/两行第一行高度归一。
15
+ - **验证**:全量 1496 测试全绿、typecheck 零错误;CDP 实测跨断点标题/按钮垂直中心 delta 0.0px(修复前 -1~-2px);主人真机验收通过。
16
+ - **杂项**:package-lock 根版本字段自 0.9.68 起历届发版漏改,本次补齐至当前版本并纳入发版流程。
17
+
18
+ ### 0.9.88 特性:面板三区 resize——尺寸记忆 + 就地生长(ADR-0021)+ 验收三修
19
+
20
+ - **需求(2026-10-15)**:市场面板「920px 浏览卡片太挤、全屏又太大」的真实档位空洞 + 桌面窗口本能手势(下意识移向边缘想 resize)。`/grill-with-docs` 两轮锁定设计:三区手柄(右/下/右下角)、就地生长锚定、尺寸记忆全套;实施计划经独立评审 Agent 三轮对抗审核(计划 11 条 + 执行 2 条全闭环,含两条方法论沉淀:`treeShaking:false` 下「bundle 标识符缺席」类预言必假、esbuild 默认 charset=ascii 下中文 bundle 锚必假)。
21
+ - **方案(ADR-0021)**:① 纯逻辑 `window-resize.js`——`clampSize`(下限 640×460、视口−8px 边距、`.abs` 形态 origin 第二钳保右/下缘不破边距、视口小于下限退化)+ 尺寸记忆存取(与位置记忆逐字对称);② 手势状态机逐条镜像 ADR-0017(rAF 帧内写/手势帧零 setState/setPointerCapture/主键门卫/多指守卫/卸载清理);③ 热区 6/8px 边 + 14×14 角,cursor 即全部供性,全屏/窄视口/触摸四重豁免;④ reclamp 升维:先夹尺寸、后夹位置。
22
+ - **主人实测三修(同版)**:头部两行重排断点 600→720(resize min 640 落入两行区,标题/seg 零折行);详情弹窗中段包进 `modalbody`(README 任意展开后 ✕ 与「安装」双端常驻);设置网格三列门控由 `.full` 改容器宽 ≥1100(还原态拉宽同样升三列,顺带修正小视口全屏强塞三列旧账)。
23
+ - **实验记录**:tab 切换淡入当日实现当日被主人否决,整体回滚(字节级干净,评审复核在案)。
24
+ - **验证**:全量 1496 测试(+24:纯逻辑 18 + 冒烟 3 + 三修源锚 3)、typecheck 零错误;CDP 量化矩阵 14 格全过(列数阈值 640→1/920→2/1140→3/1500→4、钳位下限 640×460 与上限、视口缩小双夹紧、720 断点、modalbody、coarse 豁免);Performance CDP 实测与 Windows 壳冒烟按计划延后(前者有代码级保证:手势帧零 setState)。验收记录(诚实口径):dsh-workspace `01_docs/research/2026-10-15-dshm-panel-resize-sweep-record.md`。
25
+
11
26
  ### 0.9.87 性能:热路径四连——已装枚举并行化 + patchRows 按需采集 + 收藏 stale 批量化 + 设置页社区 summary 接入 L2
12
27
 
13
28
  - **问题(2026-10-15 架构走查 + 独立复核)**:市场查询热路径上仍有三处重复支付——① `listInstalledPlugins` 逐依赖**串行**双文件读(package.json + cordis.patch.yml),每次查询/翻页/每次防抖搜索提交都全量重跑(14 依赖实测 ~11ms,占暖缓存查询近半预算);② 收藏分区 stale 检测对**每个收藏发一整条 market 查询**(≤8 并发扇出):回答一个布尔值却付全管线(枚举+社区+4445 条评分+排序+分页),20 收藏 ≈ 3 批 × 全管线;且旧路径存在 curatedFirst 挤出边界误判——primary 域同词命中 ≥8 条时 id-exact 社区条目被前置挤出前 8,**误判 stale**;③ 设置页 registry 方法背后的 `getCommunitySummary` 走无 memo 的全量 adapt+merge(~4400 条,每次打开 ~10-50ms CPU),而同身份的 L2 物化代(ADR-0016)近在咫尺。
@@ -615,6 +630,21 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
615
630
 
616
631
  ## English
617
632
 
633
+ ### Polish in 0.9.89 — constant two-row head: full title kept, winctl size/row height independent of the breakpoint
634
+
635
+ - **Need (2026-10-15, live feedback)**: the 720px two-row reflow shipped in 0.9.88 introduced two perceptible jumps — the title swapped between "插件市场" and "DeepSeek Harness 插件市场", and the winctl buttons changed size (28↔36px) while title/button vertical positions drifted 1–2px across the breakpoint.
636
+ - **Approach**: ① the two-row state keeps the full title — with seg moved to its own row the first row has ample room (~300px used of 612px at 640 width), so the short-title swap retires (DOM spans and base hiding kept; nowrap+ellipsis covers extreme narrow); ② the winctl touch bump (36px) moved from the container breakpoint to `pointer:coarse` (button size follows input modality, not width: mouse always 28px, touch always 36px); ③ the winctl box pinned to 34px = seg row height (same 28+2-border formula), normalizing first-row height across both modes.
637
+ - **Verification**: full suite 1496 green, typecheck clean; CDP measurement shows 0.0px vertical-center delta for title and buttons across the breakpoint (was -1~-2px); accepted on-device by the owner.
638
+ - **Misc**: package-lock root version fields, unsynced since 0.9.68, now track the release version and join the release checklist.
639
+
640
+ ### 0.9.88 — three-zone panel resize: size memory + grow-in-place (ADR-0021) + acceptance fixes
641
+
642
+ - **Need (2026-10-15)**: a real gap between "cramped at 920px" and "overkill fullscreen", plus the instinctive desktop gesture of grabbing window edges. Design locked via two grill rounds: three handles (right/bottom/bottom-right), grow-in-place anchoring, full size memory; the implementation plan survived three independent adversarial review rounds (11 plan + 2 execution issues all closed, including two methodology findings: bundle-absence predictions are structurally false under `treeShaking:false`, and CJK bundle anchors are false under esbuild's default ascii charset).
643
+ - **Approach (ADR-0021)**: ① DOM-free `window-resize.js` — `clampSize` (min 640×460, viewport−8px margins, an origin second clamp keeping right/bottom edges inside margins for the freely-positioned form, degraded bounds on tiny viewports) + size-memory storage symmetric to position memory; ② gesture state machine mirrors ADR-0017 (rAF-batched writes, zero setState in gesture frames, setPointerCapture, primary-button guard, multi-pointer guard, unmount cleanup); ③ hot zones 6/8px edges + 14×14 corner, cursor-only affordance, four exemptions (fullscreen / narrow viewport / touch / scrollbar trade-off); ④ reclamp upgraded to size-first-then-position.
644
+ - **Acceptance fixes from live testing (same release)**: head two-row breakpoint 600→720 (min-width 640 no longer crushes the title); detail modal middle wrapped in `modalbody` (✕ and Install stay pinned however far the README expands); settings 3-column gate switched from `.full` to container ≥1100px (a widened restored panel now gets three columns too).
645
+ - **Experiment record**: a tab-switch fade was implemented and rejected by the owner the same day, fully reverted (byte-identical, reviewer-verified).
646
+ - **Verification**: 1496 tests green (+24), typecheck clean; CDP quantified matrix 14/14 cells passed (column thresholds 640→1/920→2/1140→3/1500→4, lower/upper clamping, dual reclamp on viewport shrink, 720 breakpoint, modalbody, coarse-pointer exemption); Performance CDP measurement and Windows shell smoke deferred per plan (the former backed by a code-level guarantee: zero setState in gesture frames). Honest acceptance record: dsh-workspace `01_docs/research/2026-10-15-dshm-panel-resize-sweep-record.md`.
647
+
618
648
  ### Performance in 0.9.87 — hot-path quartet: parallel installed enumeration, on-demand patchRows, batched favorites stale check, settings community summary via L2
619
649
 
620
650
  - **Problem (2026-10-15 architecture walk + independent audit)**: three recurring costs remained on the market-query hot path - (1) `listInstalledPlugins` read each dependency's two files (package.json + cordis.patch.yml) **strictly serially**, re-running in full on every query / page turn / debounced search commit (~11ms for 14 deps, nearly half the warm-cache query budget); (2) the favorites stale check fired **one full market query per favorite** (fan-out of 8): a single boolean answer paid the entire pipeline (enumeration + community + scoring 4,445 entries + sort + paging), 20 favorites ≈ 3 batches × full pipeline; the old path also had a curatedFirst squeeze-out false-stale edge - with ≥8 primary same-token hits the id-exact community entry got pushed past the page-8 window; (3) the settings-page registry method's `getCommunitySummary` ran an unmemoized full adapt+merge (~4,400 entries, ~10-50ms CPU per open) while the same-identity L2 materialized generation (ADR-0016) sat one function away.
package/lib/client.js CHANGED
@@ -624,6 +624,66 @@ var init_window_drag = __esm({
624
624
  }
625
625
  });
626
626
 
627
+ // src/client/window-resize.js
628
+ var window_resize_exports = {};
629
+ __export(window_resize_exports, {
630
+ MIN_H: () => MIN_H,
631
+ MIN_W: () => MIN_W,
632
+ SIZE_KEY: () => SIZE_KEY,
633
+ clampSize: () => clampSize,
634
+ loadPanelSize: () => loadPanelSize,
635
+ savePanelSize: () => savePanelSize
636
+ });
637
+ function finiteInset2(v) {
638
+ return Number.isFinite(v) ? v : 0;
639
+ }
640
+ function clampSize(size, viewport, insets = ZERO_INSETS2, origin = null) {
641
+ const maxW = Math.max(0, viewport.width - finiteInset2(insets?.left) - finiteInset2(insets?.right) - PANEL_MARGIN * 2);
642
+ const maxH = Math.max(0, viewport.height - finiteInset2(insets?.top) - finiteInset2(insets?.bottom) - PANEL_MARGIN * 2);
643
+ const effMaxW = origin ? Math.min(maxW, Math.max(0, viewport.width - finiteInset2(insets?.right) - PANEL_MARGIN - finiteInset2(origin.left))) : maxW;
644
+ const effMaxH = origin ? Math.min(maxH, Math.max(0, viewport.height - finiteInset2(insets?.bottom) - PANEL_MARGIN - finiteInset2(origin.top))) : maxH;
645
+ const minW = Math.min(MIN_W, effMaxW);
646
+ const minH = Math.min(MIN_H, effMaxH);
647
+ return {
648
+ w: Math.min(Math.max(size.w, minW), effMaxW),
649
+ h: Math.min(Math.max(size.h, minH), effMaxH)
650
+ };
651
+ }
652
+ function loadPanelSize(storage) {
653
+ if (!storage || typeof storage.getItem !== "function") return null;
654
+ try {
655
+ const raw = storage.getItem(SIZE_KEY);
656
+ if (typeof raw !== "string" || raw === "") return null;
657
+ const parsed = JSON.parse(raw);
658
+ if (typeof parsed !== "object" || parsed === null || !Number.isFinite(parsed.w) || !Number.isFinite(parsed.h)) {
659
+ return null;
660
+ }
661
+ return { w: parsed.w, h: parsed.h };
662
+ } catch {
663
+ return null;
664
+ }
665
+ }
666
+ function savePanelSize(storage, size) {
667
+ if (typeof size !== "object" || size === null || !Number.isFinite(size.w) || !Number.isFinite(size.h)) {
668
+ throw new TypeError(`invalid panel size: ${JSON.stringify(size)}`);
669
+ }
670
+ try {
671
+ storage.setItem(SIZE_KEY, JSON.stringify({ w: size.w, h: size.h }));
672
+ } catch {
673
+ }
674
+ }
675
+ var SIZE_KEY, MIN_W, MIN_H, ZERO_INSETS2;
676
+ var init_window_resize = __esm({
677
+ "src/client/window-resize.js"() {
678
+ "use strict";
679
+ init_window_drag();
680
+ SIZE_KEY = "dshm-panel-size";
681
+ MIN_W = 640;
682
+ MIN_H = 460;
683
+ ZERO_INSETS2 = { left: 0, right: 0, top: 0, bottom: 0 };
684
+ }
685
+ });
686
+
627
687
  // src/client/img-chain.js
628
688
  var img_chain_exports = {};
629
689
  __export(img_chain_exports, {
@@ -1958,6 +2018,7 @@ var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCo
1958
2018
  var { lbStep: lbStep2, lbNeighbors: lbNeighbors2, swipeDir: swipeDir2 } = (init_lightbox(), __toCommonJS(lightbox_exports));
1959
2019
  var { shotWheelAction: shotWheelAction2 } = (init_wheel_strip(), __toCommonJS(wheel_strip_exports));
1960
2020
  var { dragSlop: dragSlop2, grabOffset: grabOffset2, titlebarTopInset: titlebarTopInset2, clampPoint: clampPoint2, loadPanelPos: loadPanelPos2, savePanelPos: savePanelPos2, isDragTarget: isDragTarget2 } = (init_window_drag(), __toCommonJS(window_drag_exports));
2021
+ var { clampSize: clampSize2, loadPanelSize: loadPanelSize2, savePanelSize: savePanelSize2 } = (init_window_resize(), __toCommonJS(window_resize_exports));
1961
2022
  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));
1962
2023
  var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
1963
2024
  var { ExtLink, MdImg, renderMarkdown } = createMarkdown2(h);
@@ -2150,7 +2211,7 @@ var ZH = {
2150
2211
  "pager.jump.ph": "\u9875\u53F7",
2151
2212
  "panel.fullscreen": "\u5168\u5C4F",
2152
2213
  "panel.restore": "\u8FD8\u539F",
2153
- "panel.dragHint": "\u62D6\u62FD\u79FB\u52A8 \xB7 \u53CC\u51FB\u5207\u6362\u5168\u5C4F",
2214
+ "panel.dragHint": "\u62D6\u62FD\u79FB\u52A8 \xB7 \u53CC\u51FB\u5168\u5C4F \xB7 \u62D6\u8FB9\u89D2\u8C03\u5927\u5C0F",
2154
2215
  "lb.title": "\u63D2\u4EF6\u622A\u56FE\u9884\u89C8",
2155
2216
  "lb.prev": "\u4E0A\u4E00\u5F20\uFF08\u2190\uFF09",
2156
2217
  "lb.next": "\u4E0B\u4E00\u5F20\uFF08\u2192\uFF09",
@@ -2450,7 +2511,7 @@ var EN = {
2450
2511
  "pager.jump.ph": "Page",
2451
2512
  "panel.fullscreen": "Fullscreen",
2452
2513
  "panel.restore": "Restore",
2453
- "panel.dragHint": "Drag to move \xB7 Double-click to toggle fullscreen",
2514
+ "panel.dragHint": "Drag to move \xB7 Double-click to fullscreen \xB7 Drag edges to resize",
2454
2515
  "lb.title": "Plugin screenshot preview",
2455
2516
  "lb.prev": "Previous (\u2190)",
2456
2517
  "lb.next": "Next (\u2192)",
@@ -2619,19 +2680,30 @@ var CSS = `
2619
2680
  DSH Web / \u6D4F\u89C8\u5668\u65E0\u6B64\u53D8\u91CF \u2192 \u56DE\u843D 0px\uFF0C\u884C\u4E3A\u4E0E\u65E7\u7248\u9010\u5B57\u8282\u4E00\u81F4\u3002 */
2620
2681
  .dshm-overlay.full{padding:0;top:var(--dsh-windows-titlebar-height,0px)}
2621
2682
  .dshm-panel.full{width:100%;height:100%;border-radius:0}
2683
+ /* resize \u70ED\u533A\uFF08panel resize\uFF09\uFF1A\u900F\u660E\u8D34\u5185\u7F18\uFF08\u9762\u677F overflow:hidden \u4E0D\u53EF\u5916\u7F6E\uFF09\u3002
2684
+ \u8FB9 6/8px\u3001\u89D2 14\xD714\uFF08Q8 \u5B9A\u7A3F 12\u201316px \u5E26\u5185\uFF1B\u626B\u63CF\u671F\u53EF\u6821\u51C6\uFF09\u3002\u53F3\u70ED\u533A\u76D6\u4F4F .dshm-body
2685
+ \u7EB5\u5411\u6EDA\u52A8\u6761\u5916\u7F18 ~6px\uFF08\u4F59 ~9px \u53EF\u62D6\uFF09\u3001\u89D2\u70ED\u533A\u4E0E\u6EDA\u52A8\u6761 corner \u6B7B\u533A\u57FA\u672C\u91CD\u5408\uFF08Q4 \u63A5\u53D7\uFF09\u3002
2686
+ cursor \u5373\u5168\u90E8\u4F9B\u6027\u2014\u2014\u65E0\u89C6\u89C9\u63A7\u4EF6\uFF08Q8\uFF09\u3002z-index \u538B\u8FC7 body \u5185\u5BB9\u4F46\u4E0D\u8FDB\u5934\u90E8\u4EA4\u4E92\u533A
2687
+ \uFF08head padding 14px > 6px \u70ED\u533A\uFF0Cwinctl \u6309\u94AE\u65E0\u78B0\u649E\uFF09\u3002 */
2688
+ .dshm-rz{position:absolute;z-index:5;touch-action:none}
2689
+ .rz-e{top:0;right:0;width:6px;height:100%;cursor:ew-resize}
2690
+ .rz-s{bottom:0;left:0;width:100%;height:8px;cursor:ns-resize}
2691
+ .rz-se{right:0;bottom:0;width:14px;height:14px;cursor:nwse-resize}
2692
+ .dshm-panel.full .dshm-rz{display:none} /* belt \u2460\uFF1A\u5168\u5C4F\u6001\u624B\u67C4\u4E0D\u6E32\u67D3\uFF0CCSS \u515C\u5E95 */
2693
+ @media (pointer:coarse){.dshm-rz{display:none}} /* belt \u2461\uFF1Acoarse \u6307\u9488\u6574\u7EC4\u9690\u85CF\uFF08Q8\uFF09 */
2622
2694
  /* \u7A97\u53E3\u63A7\u5236\u7EC4\uFF080.7.7\uFF09\uFF1A\u6700\u5927\u5316/\u5173\u95ED\u8FDE\u4F53\u6309\u94AE\u7EC4\u2014\u2014\u7B49\u5BBD\u4E24\u683C + \u53D1\u4E1D\u5206\u9694\u7EBF\uFF0C\u7EDF\u4E00\u7EBF\u6761\u56FE\u6807 */
2623
2695
  /* \u7A97\u53E3\u63A7\u5236\u7EC4\uFF080.7.7/0.7.8\uFF09\uFF1A\u6700\u5927\u5316/\u5173\u95ED\u8FDE\u4F53\u6309\u94AE\u7EC4\u2014\u2014\u900F\u660E\u5E95 + \u53D1\u4E1D\u5916\u6846\uFF0C\u4E0E\u641C\u7D22\u6E05\u9664\u94AE\u540C\u4E00\u914D\u8272\u8BED\u8A00\uFF1B\u5BBD\u683C\u9632\u8BEF\u89E6 */
2624
- .dshm-winctl{display:inline-flex;align-items:stretch;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:9px;background:transparent;overflow:hidden;flex:none}
2696
+ .dshm-winctl{display:inline-flex;align-items:center;height:34px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:9px;background:transparent;overflow:hidden;flex:none}
2625
2697
  .dshm-winctl button{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-secondary,#4b5563);width:44px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,color .15s}
2626
2698
  .dshm-winctl button + button{border-left:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}
2627
2699
  .dshm-winctl button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,inherit)}
2628
2700
  .dshm-winctl button:active{background:var(--dsw-alias-fill-secondary,rgba(127,127,127,.2))}
2629
2701
  .dshm-winctl button.danger:hover{background:#e5484d;color:#fff}
2630
2702
  .dshm-head{display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb)}
2631
- .dshm-title{font-weight:600;font-size:15px;margin-right:2px}
2703
+ .dshm-title{font-weight:600;font-size:15px;margin-right:2px;white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
2632
2704
  .dshm-head-divider{width:1px;height:18px;background:var(--dsw-alias-border-l2,#e5e7eb);flex:none}
2633
2705
  .dshm-seg{display:inline-flex;align-items:center;gap:2px;padding:2px;border:1px solid var(--dsw-alias-border-l2,#e2e4e8);border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f5f6f8)}
2634
- .dshm-seg button{appearance:none;border:0;background:transparent;height:28px;padding:0 14px;border-radius:7px;font:inherit;font-size:12px;color:var(--dsw-alias-label-tertiary,#7b8088);cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:background .15s,color .15s,box-shadow .15s}
2706
+ .dshm-seg button{appearance:none;border:0;background:transparent;height:28px;padding:0 14px;border-radius:7px;font:inherit;font-size:12px;color:var(--dsw-alias-label-tertiary,#7b8088);cursor:pointer;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;transition:background .15s,color .15s,box-shadow .15s}
2635
2707
  .dshm-seg button:hover{color:var(--dsw-alias-label-secondary,#4b5058)}
2636
2708
  .dshm-seg button.on{background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-primary,#17191c);font-weight:600;box-shadow:var(--dsw-shadow-lv1,0 2px 8px rgb(20 24 32 / 8%))}
2637
2709
  .dsvm-filterbtn{appearance:none;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-elevated,transparent);color:var(--dsw-alias-label-secondary,#4b5563);border-radius:8px;padding:4px 12px;font:inherit;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;transition:color .15s,border-color .15s,background .15s}
@@ -2707,7 +2779,8 @@ var CSS = `
2707
2779
  .dsvm-pagebtn.on{border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5);color:var(--dsw-alias-interactive-bg-selected,#4f46e5);font-weight:600}
2708
2780
  .dsvm-pagebtn:disabled{opacity:.4;cursor:default}
2709
2781
  .dsvm-modal{position:fixed;inset:0;z-index:2147483100;background:var(--dsw-alias-bg-mask-3,rgba(15,23,42,.55));display:flex;align-items:center;justify-content:center;padding:24px 16px}
2710
- .dsvm-modalbox{width:min(720px,100%);max-height:86vh;overflow:auto;background:var(--dsw-alias-bg-elevated,#fff);background:color-mix(in srgb,var(--dsw-alias-bg-base,#fff) 94%,transparent);backdrop-filter:blur(14px);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;box-shadow:0 18px 48px rgba(2,6,23,.25);padding:16px;display:flex;flex-direction:column;gap:10px}
2782
+ .dsvm-modalbox{width:min(720px,100%);max-height:86vh;overflow:hidden;background:var(--dsw-alias-bg-elevated,#fff);background:color-mix(in srgb,var(--dsw-alias-bg-base,#fff) 94%,transparent);backdrop-filter:blur(14px);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;box-shadow:0 18px 48px rgba(2,6,23,.25);padding:16px;display:flex;flex-direction:column;gap:10px}
2783
+ .dsvm-modalbody{overflow:auto;min-height:0;overscroll-behavior:contain;display:flex;flex-direction:column;gap:10px}
2711
2784
  .dsvm-modalhead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
2712
2785
  .dsvm-kv{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;font-size:12px;margin:0}
2713
2786
  .dsvm-kv dt{color:var(--dsw-alias-label-caption,#6b7280);white-space:nowrap}
@@ -2860,8 +2933,13 @@ button.dshm-badge:hover{filter:brightness(.95)}
2860
2933
  .dshm-settings-grid>.dshm-section{margin:0;min-width:0}
2861
2934
  .dshm-settings-grid>.dshm-section:nth-child(3){grid-column:1/-1}
2862
2935
  .dshm-settings-grid>.dshm-ops-card{grid-column:1/-1}
2863
- .dshm-panel.full .dshm-settings-grid{grid-template-columns:4fr 7fr 3fr}
2864
- .dshm-panel.full .dshm-settings-grid>.dshm-section:nth-child(3){grid-column:auto}
2936
+ /* \u4E09\u5217\u5236\u95E8\u63A7 = \u5BB9\u5668\u5BBD\u5EA6\u800C\u975E\u5168\u5C4F\u6001\uFF080.9.88\uFF0Cpanel resize \u4F34\u751F\u4FEE\u590D\uFF09\uFF1A\u8FD8\u539F\u6001\u53EF\u62C9\u5230
2937
+ \u4EFB\u610F\u5BBD\uFF0C\u22651100px \u65F6\u300C\u5173\u4E8E\u300D\u5347\u7B2C\u4E09\u5217\uFF08\u7EA6 230px \u5BBD\uFF0Ckv \u884C\u653E\u5F97\u4E0B\uFF09\uFF1B\u987A\u5E26\u4FEE\u6B63\u65E7\u8D26\u2014\u2014
2938
+ \u5C0F\u89C6\u53E3\u5168\u5C4F\uFF08<1100\uFF09\u4E0D\u518D\u88AB\u5F3A\u585E\u4E09\u5217\uFF0C\u56DE\u843D\u4E24\u5217\u3002\u2264760px \u89C6\u53E3\u5355\u5217\u5A92\u4F53\u67E5\u8BE2\u539F\u6837\u4FDD\u7559\u3002 */
2939
+ @container (min-width:1100px){
2940
+ .dshm-settings-grid{grid-template-columns:4fr 7fr 3fr}
2941
+ .dshm-settings-grid>.dshm-section:nth-child(3){grid-column:auto}
2942
+ }
2865
2943
  @media (max-width:760px){.dshm-settings-grid,.dshm-panel.full .dshm-settings-grid{grid-template-columns:minmax(0,1fr)}.dshm-settings-grid>.dshm-section:nth-child(3),.dshm-settings-grid>.dshm-ops-card{grid-column:1/-1}}
2866
2944
  .dshm-kvlines{display:flex;flex-direction:column}
2867
2945
  .dshm-kvline-row{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:5px 0;border-bottom:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.14));min-width:0}
@@ -2913,7 +2991,7 @@ button.dshm-badge:hover{filter:brightness(.95)}
2913
2991
  .dshm-links{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:7px;padding-top:6px;border-top:1px solid var(--dsw-alias-border-l2,#e5e7eb);font-size:11px;color:var(--dsw-alias-label-caption,#6b7280)}
2914
2992
  .dshm-links-k,.dshm-links-sep{color:var(--dsw-alias-label-caption,#9ca3af)}
2915
2993
  .dshm-links a{color:var(--dsw-alias-state-business-primary,#4d6bfe);text-decoration:none;font-weight:500}
2916
- .dshm-links-ver{font-weight:500;color:var(--dsw-alias-label-secondary,#4b5563);font-variant-numeric:tabular-nums}
2994
+ .dshm-links-ver{font-weight:500;color:var(--dsw-alias-label-caption,#6b7280);font-variant-numeric:tabular-nums}
2917
2995
  .dshm-links a:hover{text-decoration:underline}
2918
2996
  .dshm-prog{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;font-size:12px;color:var(--dsw-alias-label-secondary,#4b5563)}
2919
2997
  .dshm-prog .bar{flex:1;height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-2,rgba(38,49,72,.08));overflow:hidden;min-width:80px}
@@ -2973,8 +3051,10 @@ button.dshm-dshchip:disabled{cursor:default;opacity:.75}
2973
3051
  \u89C6\u53E3\u5BBD \u2260 \u9762\u677F\u5BBD\uFF08600px \u89C6\u53E3\u2192568px \u9762\u677F\u4ECD\u6EA2\u51FA\uFF09\uFF1B\u5BB9\u5668\u67E5\u8BE2\u8DDF\u968F\u9762\u677F\u5B9E\u9645\u5BBD\u5EA6\u3002\u9762\u677F\u5BBD\u9AD8
2974
3052
  \u5747\u4E3A min(920px,100%)/min(680px,86vh) \u516C\u5F0F\u6D3E\u751F\u3001\u4E0D\u4F9D\u8D56\u5185\u5BB9\uFF0Cinline-size \u5305\u542B\u5B89\u5168\uFF1B
2975
3053
  \u706F\u7BB1 portal \u6302 body\uFF080.9.73\uFF09\u3001\u5206\u7C7B\u884C sticky \u5728 .dshm-body \u6EDA\u52A8\u5BB9\u5668\u5185\uFF0C\u5747\u4E0D\u53D7\u7956\u5148
2976
- inline-size \u5305\u542B\u5F71\u54CD\u3002\u65AD\u70B9\uFF1A\u5BB9\u5668 600px\uFF08\u4E24\u884C\u91CD\u6392\uFF09\u4E0E\u89C6\u53E3 600px\uFF08\u9ED8\u8BA4\u5168\u5C4F\u5224\u5B9A +
2977
- overlay 8px\uFF09\u540C\u503C\u3002
3054
+ inline-size \u5305\u542B\u5F71\u54CD\u3002\u65AD\u70B9\uFF1A\u5BB9\u5668 720px\uFF08\u4E24\u884C\u91CD\u6392\uFF09\u4E0E\u89C6\u53E3 600px\uFF08\u9ED8\u8BA4\u5168\u5C4F\u5224\u5B9A +
3055
+ overlay 8px\uFF09\u2014\u2014\u4E24\u65AD\u70B9 0.9.88 \u8D77\u89E3\u8026\uFF1Apanel resize \u7684 min 640 \u843D\u5728 601\u2013720 \u5E26\u65F6\u65E7
3056
+ 600px \u65AD\u70B9\u4E0D\u89E6\u53D1\uFF0C\u957F\u6807\u9898\u6298\u884C/seg \u6324\u538B\uFF08\u4E3B\u4EBA\u5B9E\u6D4B 2026-10-15\uFF09\uFF0C720 \u65AD\u70B9\u8BA9 min \u6052\u5165
3057
+ \u4E24\u884C\u533A\uFF1B\u526F\u4F5C\u7528 = 600\u2013720px \u89C6\u53E3\u7684\u624B\u673A\u5168\u5C4F\u4E5F\u4E24\u884C\uFF0C\u5C5E\u4FEE\u590D\u800C\u975E\u56DE\u5F52\u3002
2978
3058
  \u515C\u5E95\u8BED\u4E49\u8BF4\u660E\uFF08\u5171\u8BC6 Q3 + \u8BC4\u5BA1 R7\uFF09\uFF1A\u300C\u4E0D\u652F\u6301\u5BB9\u5668\u67E5\u8BE2\u7684\u6D4F\u89C8\u5668\u65B0\u89C4\u5219\u96F6\u751F\u6548\u300D\u6309 CSS
2979
3059
  \u6761\u4EF6\u89C4\u5219\u7EC4\u673A\u5236\u7B49\u6548\u843D\u5730\u2014\u2014.dshm-title-short \u57FA\u7840\u9690\u85CF\u4E0D\u4F9D\u8D56\u5BB9\u5668\u67E5\u8BE2\uFF08\u8001\u6D4F\u89C8\u5668\u4E5F\u8981
2980
3060
  \u751F\u6548\uFF09\uFF1B\u5176\u4F59\u65B0\u89C4\u5219\u90FD\u662F\u6574\u5757 at-rule\uFF08@supports/@container/@media \u9876\u5C42\u5144\u5F1F\uFF09\uFF0C\u4E0D\u652F\u6301
@@ -2988,15 +3068,19 @@ button.dshm-dshchip:disabled{cursor:default;opacity:.75}
2988
3068
  /* \u4E24\u884C\u91CD\u6392\uFF1A\u884C1 [\u77ED\u6807\u9898 \u22EFspacer\u22EF chip winctl]\uFF0C\u884C2 [seg \u62C9\u901A]\u3002DOM \u987A\u5E8F\u4E0D\u52A8
2989
3069
  \uFF08title\u2192divider\u2192seg\u2192spacer\u2192chip\u2192winctl\uFF09\uFF0C\u5168\u9760 order+wrap\u2014\u2014\u6E90\u951A/tab \u5E8F/\u8BFB\u5C4F\u5E8F
2990
3070
  \u96F6\u6270\u52A8\uFF1B\u89C6\u89C9\u5E8F\u4E0E DOM \u5E8F\u4E0D\u4E00\u81F4\uFF08seg \u5728 DOM \u524D\u3001\u89C6\u89C9\u5728\u540E\uFF09\uFF0C\u9762\u677F\u5185\u5143\u7D20\u5C11\u53EF\u63A5\u53D7 */
2991
- @container (max-width:600px){
3071
+ @container (max-width:720px){
2992
3072
  .dshm-head{flex-wrap:wrap;row-gap:6px}
2993
3073
  .dshm-head-divider{display:none}
2994
- .dshm-title-full{display:none}
2995
- .dshm-title-short{display:inline}
3074
+ /* 0.9.89\uFF1A\u4E24\u884C\u6001\u4FDD\u7559\u5B8C\u6574\u6807\u9898\uFF08\u4E3B\u4EBA\u63D0\u8BAE\u5B9E\u6D4B\u91C7\u7EB3\uFF09\u2014\u2014seg \u8BA9\u884C\u540E\u9996\u884C\u7A7A\u95F4\u5145\u88D5
3075
+ \uFF08640 \u5BBD\u5B9E\u6D4B 612px \u5185\u5BB9\u533A\u53EA\u9700 ~300px\uFF09\uFF0C\u77ED\u6807\u9898\u5207\u6362\u53CD\u800C\u5236\u9020\u65AD\u70B9\u6362\u5B57\u95EA\u70C1\uFF1B
3076
+ \u6781\u7AEF\u7A84\u7531 .dshm-title \u7684 nowrap+ellipsis \u515C\u5E95\u3002DOM \u53CC span \u4E0E\u57FA\u7840\u9690\u85CF\u89C4\u5219
3077
+ \u4FDD\u7559\uFF08\u77ED\u6807\u9898\u6C38\u4E0D\u663E\u793A\uFF0C\u56DE\u9000\u96F6\u6210\u672C\uFF09\u3002 */
2996
3078
  .dshm-spacer{order:1}
2997
3079
  .dshm-head>.dshm-dshchip{order:2}
2998
3080
  .dshm-winctl{order:3}
2999
- .dshm-winctl button{height:36px}
3081
+ /* 0.9.89\uFF1Awinctl \u89E6\u63A7\u52A0\u5927\uFF0836px\uFF09\u4ECE\u5BB9\u5668\u65AD\u70B9\u8FC1\u81F3 pointer:coarse\u2014\u2014\u6309\u94AE\u5927\u5C0F\u4E0D\u8BE5\u968F
3082
+ \u4E24\u884C/\u5355\u884C\u6A21\u5F0F\u8DF3\u53D8\uFF08\u4E3B\u4EBA\u5B9E\u6D4B 720 \u8FB9\u754C\u5FFD\u5927\u5FFD\u5C0F\uFF09\uFF0C\u771F\u6B63\u53D8\u91CF\u662F\u8F93\u5165\u65B9\u5F0F\uFF1B\u89E6\u5C4F\u8BBE\u5907
3083
+ \u5168\u6A21\u5F0F\u6052 36px\uFF0C\u9F20\u6807\u8BBE\u5907\u5168\u6A21\u5F0F\u6052 28px */
3000
3084
  .dshm-seg{order:4;flex:1 1 100%}
3001
3085
  .dshm-seg button{flex:1;justify-content:center;white-space:nowrap;padding:0 10px;height:36px}
3002
3086
  /* \u7248\u672C chip\uFF08\u5171\u8BC6 Q7 + \u8BC4\u5BA1 R5 \u6536\u7A84\uFF09\uFF1A\u5E38\u6001\u9690\u85CF\uFF08\u8BBE\u7F6E\u9875\u968F\u65F6\u53EF\u89C1\uFF09\uFF1Bwarn \u6001\u7F29\u4E3A \u2B06
@@ -3009,6 +3093,8 @@ button.dshm-dshchip:disabled{cursor:default;opacity:.75}
3009
3093
  /* overlay \u7A84\u89C6\u53E3\u515C\u5E95\uFF1A\u8FD8\u539F\u6001 8px\uFF08.full \u7684 padding:0 \u7279\u5F02\u6027 (0,2,0)>(0,1,0) \u81EA\u7136\u4FDD\u6301\uFF09\uFF1B
3010
3094
  \u7A84\u89C6\u53E3\u9ED8\u8BA4\u5168\u5C4F\u7684 JS \u5224\u5B9A\u5728 MarketPanel full \u521D\u59CB\u5316 */
3011
3095
  @media (max-width:600px){.dshm-overlay{padding:8px}}
3096
+ /* \u89E6\u63A7\u76EE\u6807\u52A0\u5927\u968F\u8F93\u5165\u65B9\u5F0F\u800C\u975E\u5BBD\u5EA6\uFF080.9.89 \u8FC1\u79FB\uFF0C\u89C1 720 \u5757\u5185\u6CE8\u91CA\uFF09 */
3097
+ @media (pointer:coarse){.dshm-winctl{height:38px}.dshm-winctl button{height:36px}}
3012
3098
  `;
3013
3099
  function cssVersionOf(s) {
3014
3100
  let h2 = 5381;
@@ -3822,75 +3908,82 @@ function DetailModal({ it, labels, busy, onClose, onInstall, onUpgrade, upgradeB
3822
3908
  h("span", { className: "dshm-spacer" }),
3823
3909
  h("button", { ref: closeRef, className: "dshm-xbtn", "aria-label": lookup("common.close"), title: lookup("common.close"), onClick: onClose }, h(XIcon))
3824
3910
  ),
3825
- it.community === true ? h(
3826
- "div",
3827
- { className: "dsvm-byline" },
3828
- it.owner ? h("span", null, `by ${it.owner}`) : null,
3829
- typeof it.stars === "number" ? h("span", { title: String(it.stars) }, `${compactCount(it.stars)} \u2605`) : null,
3830
- typeof it.downloads === "number" ? h("span", { title: String(it.downloads) }, `${compactCount(it.downloads)} \u2193`) : null
3831
- ) : null,
3832
- h(LinksRow, { npm: it.npm, github: it.github, homepage: it.homepage }),
3911
+ // 0.9.88(T7 验收·主人反馈二):中段内容包进 modalbody 滚动层——✕(head)与
3912
+ // 「安装」(actions)双端常驻,README/能力披露展开多长都关得上、装得了;
3913
+ // box 攂 overflow:hidden,滚动收进 body(overscroll-behavior 防链式透传底页)。
3833
3914
  h(
3834
- "dl",
3835
- { className: "dsvm-kv" },
3836
- kv(lookup("modal.category"), catLabel),
3837
- it.added ? kv(lookup("modal.added"), fmtDate(it.added)) : null,
3838
- it.community === true ? kv(
3839
- lookup("modal.dlwindow"),
3840
- typeof it.downloads === "number" ? h("span", { title: String(it.downloads) }, `${compactCount(it.downloads)}\uFF08${it.downloadsStart || "?"} ~ ${it.downloadsEnd || "?"}${it.downloadsCheckedAt ? `\uFF0C${lookup("modal.checkedat")} ${it.downloadsCheckedAt}` : ""}\uFF09`) : lookup("modal.dlnone")
3841
- ) : null,
3842
- kv(lookup("detail.latest"), it.latestVersion ? `v${it.latestVersion}` : it.latestTag ? it.latestTag : it.latestSha ? it.latestSha : it.latestError ? it.version ? lookup("sub.snapshot", { v: it.version }) : it.latestError : "\u2014"),
3843
- kv(lookup("detail.installed"), it.installedPkg ? `${it.installedPkg} v${it.installedVersion || "?"}` : lookup("installed.none")),
3844
- it.community !== true && Array.isArray(it.verified) && it.verified.length ? kv(lookup("modal.verified"), it.verified.join("\u3001")) : null,
3845
- it.audience === "internal" ? kv(lookup("modal.audience.label"), lookup("modal.audience.value")) : null,
3846
- it.decoupled === true ? kv(lookup("modal.decoupled.label"), lookup("modal.decoupled.value")) : null,
3847
- (it.tags || []).length ? kv(lookup("modal.tags"), it.tags.join(", ")) : null,
3848
- it.deprecated === true && it.replacement ? kv(lookup("modal.replacement"), it.replacement) : null
3849
- ),
3850
- h("div", { className: "dshm-desc", style: { WebkitLineClamp: "unset" } }, descFull),
3851
- it.npm ? h(
3852
- "details",
3853
- { className: "dsvm-fold", onToggle: (e) => {
3854
- if (e.target.open) setRmOpen(true);
3855
- } },
3856
- h("summary", null, lookup("readme.show")),
3857
- rmOpen ? h(ReadmeBlock, { pkg: it.npm, repo: it.github }) : h("div", { className: "dshm-hint" }, lookup("readme.loading"))
3858
- ) : null,
3859
- visible.length ? h(
3860
3915
  "div",
3861
- { ref: shotRowRef, className: "dsvm-shotrow" },
3862
- ...shots.map((src, i) => h(Shot, { key: `${i}:${src}`, src, onClick: () => setLb(visible.indexOf(src)), onBroken: breakShot }))
3863
- ) : null,
3864
- it.community === true ? h(
3865
- "details",
3866
- { className: "dsvm-fold" },
3867
- h("summary", null, lookup("detail.capabilities")),
3868
- Array.isArray(it.capabilities) && it.capabilities.length ? h(
3916
+ { className: "dsvm-modalbody" },
3917
+ it.community === true ? h(
3869
3918
  "div",
3870
- null,
3871
- h("div", null, it.capabilities.join(", ")),
3872
- Array.isArray(it.capabilityRedLines) && it.capabilityRedLines.length ? h("div", { className: "dshm-err" }, `${lookup("detail.redlines")}: ${it.capabilityRedLines.join("; ")}`) : null
3873
- ) : h("div", { className: "dshm-hint" }, lookup("detail.capabilities.unscanned"))
3874
- ) : null,
3875
- showInstallCmd ? h(
3876
- "details",
3877
- { className: "dsvm-fold" },
3878
- h("summary", null, lookup("modal.installcmd")),
3919
+ { className: "dsvm-byline" },
3920
+ it.owner ? h("span", null, `by ${it.owner}`) : null,
3921
+ typeof it.stars === "number" ? h("span", { title: String(it.stars) }, `${compactCount(it.stars)} \u2605`) : null,
3922
+ typeof it.downloads === "number" ? h("span", { title: String(it.downloads) }, `${compactCount(it.downloads)} \u2193`) : null
3923
+ ) : null,
3924
+ h(LinksRow, { npm: it.npm, github: it.github, homepage: it.homepage }),
3879
3925
  h(
3926
+ "dl",
3927
+ { className: "dsvm-kv" },
3928
+ kv(lookup("modal.category"), catLabel),
3929
+ it.added ? kv(lookup("modal.added"), fmtDate(it.added)) : null,
3930
+ it.community === true ? kv(
3931
+ lookup("modal.dlwindow"),
3932
+ typeof it.downloads === "number" ? h("span", { title: String(it.downloads) }, `${compactCount(it.downloads)}\uFF08${it.downloadsStart || "?"} ~ ${it.downloadsEnd || "?"}${it.downloadsCheckedAt ? `\uFF0C${lookup("modal.checkedat")} ${it.downloadsCheckedAt}` : ""}\uFF09`) : lookup("modal.dlnone")
3933
+ ) : null,
3934
+ kv(lookup("detail.latest"), it.latestVersion ? `v${it.latestVersion}` : it.latestTag ? it.latestTag : it.latestSha ? it.latestSha : it.latestError ? it.version ? lookup("sub.snapshot", { v: it.version }) : it.latestError : "\u2014"),
3935
+ kv(lookup("detail.installed"), it.installedPkg ? `${it.installedPkg} v${it.installedVersion || "?"}` : lookup("installed.none")),
3936
+ it.community !== true && Array.isArray(it.verified) && it.verified.length ? kv(lookup("modal.verified"), it.verified.join("\u3001")) : null,
3937
+ it.audience === "internal" ? kv(lookup("modal.audience.label"), lookup("modal.audience.value")) : null,
3938
+ it.decoupled === true ? kv(lookup("modal.decoupled.label"), lookup("modal.decoupled.value")) : null,
3939
+ (it.tags || []).length ? kv(lookup("modal.tags"), it.tags.join(", ")) : null,
3940
+ it.deprecated === true && it.replacement ? kv(lookup("modal.replacement"), it.replacement) : null
3941
+ ),
3942
+ h("div", { className: "dshm-desc", style: { WebkitLineClamp: "unset" } }, descFull),
3943
+ it.npm ? h(
3944
+ "details",
3945
+ { className: "dsvm-fold", onToggle: (e) => {
3946
+ if (e.target.open) setRmOpen(true);
3947
+ } },
3948
+ h("summary", null, lookup("readme.show")),
3949
+ rmOpen ? h(ReadmeBlock, { pkg: it.npm, repo: it.github }) : h("div", { className: "dshm-hint" }, lookup("readme.loading"))
3950
+ ) : null,
3951
+ visible.length ? h(
3880
3952
  "div",
3881
- { className: "dsvm-cmdrow" },
3882
- h("code", { className: "dsvm-code" }, installCmd),
3883
- h("button", { className: "dshm-btn sm", onClick: copyCmd }, copied ? lookup("modal.copied") : lookup("modal.copy"))
3884
- )
3885
- ) : null,
3886
- // 0.9.15:安装信息就地进 Modal(主人反馈 2026-10-02:进度/终态原先只隔遮罩在底层透出)。
3887
- // 进度行复用 ProgressLine(自轮询 host status);底层同源行由 MarketTab 在本 Modal 打开时让位。
3888
- // installRec 派生自全局操作记录(DESIGN §2.6「状态不挂卡片」的所有权模型不变,此处只是展示):
3889
- // 仅 queued/running 挂行,input(兼容待决)走上层 CompatDialog;installNote 为本次终态摘要。
3890
- installRec && (installRec.status === "running" || installRec.status === "queued") ? h(ProgressLine, { key: "opprog" }) : null,
3891
- upgradeRec && (upgradeRec.status === "running" || upgradeRec.status === "queued") ? h(ProgressLine, { key: "upprog" }) : null,
3892
- installNote ? h("div", { key: "opnote", className: installNote.kind === "err" ? "dshm-err" : installNote.kind === "hint" ? "dshm-hint" : "dshm-ok" }, installNote.text) : null,
3893
- installNote && installNote.kind === "ok" ? h("div", { key: "opnote-rh", className: "dshm-hint" }, lookup(profileKind === "desktop" ? "profile.restartHint" : "banner.done")) : null,
3953
+ { ref: shotRowRef, className: "dsvm-shotrow" },
3954
+ ...shots.map((src, i) => h(Shot, { key: `${i}:${src}`, src, onClick: () => setLb(visible.indexOf(src)), onBroken: breakShot }))
3955
+ ) : null,
3956
+ it.community === true ? h(
3957
+ "details",
3958
+ { className: "dsvm-fold" },
3959
+ h("summary", null, lookup("detail.capabilities")),
3960
+ Array.isArray(it.capabilities) && it.capabilities.length ? h(
3961
+ "div",
3962
+ null,
3963
+ h("div", null, it.capabilities.join(", ")),
3964
+ Array.isArray(it.capabilityRedLines) && it.capabilityRedLines.length ? h("div", { className: "dshm-err" }, `${lookup("detail.redlines")}: ${it.capabilityRedLines.join("; ")}`) : null
3965
+ ) : h("div", { className: "dshm-hint" }, lookup("detail.capabilities.unscanned"))
3966
+ ) : null,
3967
+ showInstallCmd ? h(
3968
+ "details",
3969
+ { className: "dsvm-fold" },
3970
+ h("summary", null, lookup("modal.installcmd")),
3971
+ h(
3972
+ "div",
3973
+ { className: "dsvm-cmdrow" },
3974
+ h("code", { className: "dsvm-code" }, installCmd),
3975
+ h("button", { className: "dshm-btn sm", onClick: copyCmd }, copied ? lookup("modal.copied") : lookup("modal.copy"))
3976
+ )
3977
+ ) : null,
3978
+ // 0.9.15:安装信息就地进 Modal(主人反馈 2026-10-02:进度/终态原先只隔遮罩在底层透出)。
3979
+ // 进度行复用 ProgressLine(自轮询 host status);底层同源行由 MarketTab 在本 Modal 打开时让位。
3980
+ // installRec 派生自全局操作记录(DESIGN §2.6「状态不挂卡片」的所有权模型不变,此处只是展示):
3981
+ // 仅 queued/running 挂行,input(兼容待决)走上层 CompatDialog;installNote 为本次终态摘要。
3982
+ installRec && (installRec.status === "running" || installRec.status === "queued") ? h(ProgressLine, { key: "opprog" }) : null,
3983
+ upgradeRec && (upgradeRec.status === "running" || upgradeRec.status === "queued") ? h(ProgressLine, { key: "upprog" }) : null,
3984
+ installNote ? h("div", { key: "opnote", className: installNote.kind === "err" ? "dshm-err" : installNote.kind === "hint" ? "dshm-hint" : "dshm-ok" }, installNote.text) : null,
3985
+ installNote && installNote.kind === "ok" ? h("div", { key: "opnote-rh", className: "dshm-hint" }, lookup(profileKind === "desktop" ? "profile.restartHint" : "banner.done")) : null
3986
+ ),
3894
3987
  h(
3895
3988
  "div",
3896
3989
  { className: "dsvm-modalactions" },
@@ -5225,20 +5318,35 @@ function MarketPanel({ onClose }) {
5225
5318
  return null;
5226
5319
  }
5227
5320
  });
5321
+ const [size, setSize] = useState(() => {
5322
+ try {
5323
+ return loadPanelSize2(typeof window !== "undefined" && window.localStorage ? window.localStorage : null);
5324
+ } catch {
5325
+ return null;
5326
+ }
5327
+ });
5228
5328
  const fullRef = useRef(full);
5229
5329
  fullRef.current = full;
5230
5330
  const posRef = useRef(pos);
5231
5331
  posRef.current = pos;
5332
+ const sizeRef = useRef(size);
5333
+ sizeRef.current = size;
5334
+ const resizingRef = useRef(false);
5232
5335
  const reclamp = useCallback(() => {
5233
- if (fullRef.current || draggingRef.current || !panelRef.current || typeof window === "undefined") return;
5336
+ if (fullRef.current || draggingRef.current || resizingRef.current || !panelRef.current || typeof window === "undefined") return;
5337
+ const vp = { width: window.innerWidth, height: window.innerHeight };
5338
+ const insets = readTitlebarInsets();
5339
+ const prevSize = sizeRef.current;
5340
+ const eff = prevSize ? clampSize2(prevSize, vp, insets) : null;
5341
+ if (eff && prevSize && (eff.w !== prevSize.w || eff.h !== prevSize.h)) setSize(eff);
5234
5342
  const prev = posRef.current;
5235
5343
  if (!prev) return;
5236
5344
  const rect = panelRef.current.getBoundingClientRect();
5237
5345
  const next = clampPoint2(
5238
5346
  { x: prev.x, y: prev.y },
5239
- { width: window.innerWidth, height: window.innerHeight },
5240
- { width: rect.width, height: rect.height },
5241
- readTitlebarInsets()
5347
+ vp,
5348
+ eff ? { width: eff.w, height: eff.h } : { width: rect.width, height: rect.height },
5349
+ insets
5242
5350
  );
5243
5351
  if (next.x !== prev.x || next.y !== prev.y) setPos(next);
5244
5352
  }, []);
@@ -5259,7 +5367,7 @@ function MarketPanel({ onClose }) {
5259
5367
  let originY = 0;
5260
5368
  let grab = { dx: 0, dy: 0 };
5261
5369
  let rest = { x: 0, y: 0 };
5262
- let size = { width: 0, height: 0 };
5370
+ let size2 = { width: 0, height: 0 };
5263
5371
  let lastPoint = null;
5264
5372
  const bindGesture = () => {
5265
5373
  document.addEventListener("pointermove", onPointerMove);
@@ -5276,7 +5384,7 @@ function MarketPanel({ onClose }) {
5276
5384
  const topLeft = clampPoint2(
5277
5385
  { x: lastPoint.x - grab.dx, y: lastPoint.y - grab.dy },
5278
5386
  { width: window.innerWidth, height: window.innerHeight },
5279
- size,
5387
+ size2,
5280
5388
  readTitlebarInsets()
5281
5389
  );
5282
5390
  panelRef.current.style.transform = `translate3d(${Math.round(topLeft.x - rest.x)}px, ${Math.round(topLeft.y - rest.y)}px, 0)`;
@@ -5299,7 +5407,7 @@ function MarketPanel({ onClose }) {
5299
5407
  const final = clampPoint2(
5300
5408
  { x: event.clientX - grab.dx, y: event.clientY - grab.dy },
5301
5409
  { width: window.innerWidth, height: window.innerHeight },
5302
- size,
5410
+ size2,
5303
5411
  readTitlebarInsets()
5304
5412
  );
5305
5413
  panel.style.left = `${final.x}px`;
@@ -5349,7 +5457,7 @@ function MarketPanel({ onClose }) {
5349
5457
  const rect = panel.getBoundingClientRect();
5350
5458
  grab = grabOffset2({ x: event.clientX, y: event.clientY }, rect);
5351
5459
  rest = { x: rect.left, y: rect.top };
5352
- size = { width: rect.width, height: rect.height };
5460
+ size2 = { width: rect.width, height: rect.height };
5353
5461
  if (headEl.setPointerCapture) {
5354
5462
  try {
5355
5463
  headEl.setPointerCapture(event.pointerId);
@@ -5365,6 +5473,122 @@ function MarketPanel({ onClose }) {
5365
5473
  document.body.style.userSelect = "";
5366
5474
  };
5367
5475
  }, []);
5476
+ useEffect(() => {
5477
+ const panelEl = panelRef.current;
5478
+ if (!panelEl) return void 0;
5479
+ let zone = null;
5480
+ let resizing = false;
5481
+ let moved = false;
5482
+ let slop = 6;
5483
+ let originX = 0, originY = 0;
5484
+ let startW = 0, startH = 0;
5485
+ let originRect = null;
5486
+ let lastPoint = null;
5487
+ let raf = 0;
5488
+ const ZONE_CURSOR = { e: "ew-resize", s: "ns-resize", se: "nwse-resize" };
5489
+ const bindGesture = () => {
5490
+ document.addEventListener("pointermove", onPointerMove);
5491
+ document.addEventListener("pointerup", onPointerUp);
5492
+ document.addEventListener("pointercancel", onPointerCancel);
5493
+ };
5494
+ const unbindGesture = () => {
5495
+ document.removeEventListener("pointermove", onPointerMove);
5496
+ document.removeEventListener("pointerup", onPointerUp);
5497
+ document.removeEventListener("pointercancel", onPointerCancel);
5498
+ };
5499
+ const frameStep = () => {
5500
+ raf = 0;
5501
+ if (!resizing || !moved || !lastPoint || !panelRef.current) return;
5502
+ const raw = {
5503
+ w: zone === "s" ? startW : startW + (lastPoint.x - originX),
5504
+ h: zone === "e" ? startH : startH + (lastPoint.y - originY)
5505
+ };
5506
+ const next = clampSize2(raw, { width: window.innerWidth, height: window.innerHeight }, readTitlebarInsets(), originRect);
5507
+ panelRef.current.style.width = `${Math.round(next.w)}px`;
5508
+ panelRef.current.style.height = `${Math.round(next.h)}px`;
5509
+ };
5510
+ const onPointerMove = (event) => {
5511
+ if (!resizing) return;
5512
+ if (!moved) {
5513
+ if (Math.hypot(event.clientX - originX, event.clientY - originY) < slop) return;
5514
+ moved = true;
5515
+ document.body.style.userSelect = "none";
5516
+ document.body.style.cursor = ZONE_CURSOR[zone];
5517
+ }
5518
+ lastPoint = { x: event.clientX, y: event.clientY };
5519
+ if (!raf) raf = requestAnimationFrame(frameStep);
5520
+ };
5521
+ const onPointerUp = () => {
5522
+ if (!resizing) return;
5523
+ const baked = panelRef.current ? clampSize2(
5524
+ moved && lastPoint ? { w: zone === "s" ? startW : startW + (lastPoint.x - originX), h: zone === "e" ? startH : startH + (lastPoint.y - originY) } : { w: startW, h: startH },
5525
+ { width: window.innerWidth, height: window.innerHeight },
5526
+ readTitlebarInsets(),
5527
+ originRect
5528
+ ) : null;
5529
+ unbindGesture();
5530
+ if (raf) cancelAnimationFrame(raf);
5531
+ raf = 0;
5532
+ resizing = false;
5533
+ resizingRef.current = false;
5534
+ document.body.style.userSelect = "";
5535
+ document.body.style.cursor = "";
5536
+ if (!moved) return;
5537
+ try {
5538
+ savePanelSize2(typeof window !== "undefined" && window.localStorage ? window.localStorage : null, baked);
5539
+ } catch {
5540
+ }
5541
+ setSize(baked);
5542
+ };
5543
+ const onPointerCancel = () => {
5544
+ if (!resizing) return;
5545
+ unbindGesture();
5546
+ if (raf) cancelAnimationFrame(raf);
5547
+ raf = 0;
5548
+ resizing = false;
5549
+ resizingRef.current = false;
5550
+ document.body.style.userSelect = "";
5551
+ document.body.style.cursor = "";
5552
+ if (panelRef.current) {
5553
+ panelRef.current.style.width = sizeRef.current ? `${sizeRef.current.w}px` : "";
5554
+ panelRef.current.style.height = sizeRef.current ? `${sizeRef.current.h}px` : "";
5555
+ }
5556
+ };
5557
+ const onPointerDown = (event) => {
5558
+ if (resizing) return;
5559
+ if (event.button !== 0) return;
5560
+ if (event.pointerType !== "mouse") return;
5561
+ const handle = event.target?.closest?.("[data-dshm-resize]");
5562
+ if (!handle) return;
5563
+ zone = handle.getAttribute("data-dshm-resize");
5564
+ const rect = panelRef.current.getBoundingClientRect();
5565
+ startW = rect.width;
5566
+ startH = rect.height;
5567
+ originRect = posRef.current ? { left: rect.left, top: rect.top } : null;
5568
+ originX = event.clientX;
5569
+ originY = event.clientY;
5570
+ slop = dragSlop2(event.pointerType);
5571
+ resizing = true;
5572
+ resizingRef.current = true;
5573
+ moved = false;
5574
+ lastPoint = null;
5575
+ if (handle.setPointerCapture) {
5576
+ try {
5577
+ handle.setPointerCapture(event.pointerId);
5578
+ } catch {
5579
+ }
5580
+ }
5581
+ bindGesture();
5582
+ };
5583
+ panelEl.addEventListener("pointerdown", onPointerDown);
5584
+ return () => {
5585
+ panelEl.removeEventListener("pointerdown", onPointerDown);
5586
+ unbindGesture();
5587
+ if (raf) cancelAnimationFrame(raf);
5588
+ document.body.style.userSelect = "";
5589
+ document.body.style.cursor = "";
5590
+ };
5591
+ }, []);
5368
5592
  const marketCommunity = useMarketData("community");
5369
5593
  const marketPrimary = useMarketData("primary");
5370
5594
  const markets = { community: marketCommunity, primary: marketPrimary };
@@ -5543,7 +5767,12 @@ function MarketPanel({ onClose }) {
5543
5767
  "div",
5544
5768
  {
5545
5769
  className: ["dshm-panel", full && "full", !full && pos && "abs"].filter(Boolean).join(" "),
5546
- style: !full && pos ? { left: `${pos.x}px`, top: `${pos.y}px` } : void 0,
5770
+ // panel resize:尺寸记忆以 inline 宽高覆盖类默认 min(920px,100%)·min(680px,86vh);
5771
+ // 与位置记忆互不诱发(就地生长:居中形态只带 width/height 仍由 flex 居中)
5772
+ style: !full ? {
5773
+ ...pos ? { left: `${pos.x}px`, top: `${pos.y}px` } : {},
5774
+ ...size ? { width: `${size.w}px`, height: `${size.h}px` } : {}
5775
+ } : void 0,
5547
5776
  ref: panelRef,
5548
5777
  onClick: (e) => e.stopPropagation()
5549
5778
  },
@@ -5661,7 +5890,13 @@ function MarketPanel({ onClose }) {
5661
5890
  h("span", { className: `dshm-banner-text${toastScrollCls}`, ref: toastTextRef }, toast.text)
5662
5891
  ) : null,
5663
5892
  banner ? h(RestartBanner, { note: banner.text, onDone: () => setBanner(null), desktop: banner.desktop === true, onRestarted: reloadPing }) : null
5664
- ) : null
5893
+ ) : null,
5894
+ // resize 热区(panel resize):透明无视觉;pointer-only 供性(aria-hidden)。
5895
+ // 全屏态不渲染(Task 5 CSS @media pointer:coarse 与 .full belt 另有双保险);
5896
+ // data-dshm-resize 供 T3 手势机 closest 门卫。
5897
+ !full && h("div", { className: "dshm-rz rz-e", "data-dshm-resize": "e", "aria-hidden": "true" }),
5898
+ !full && h("div", { className: "dshm-rz rz-s", "data-dshm-resize": "s", "aria-hidden": "true" }),
5899
+ !full && h("div", { className: "dshm-rz rz-se", "data-dshm-resize": "se", "aria-hidden": "true" })
5665
5900
  )
5666
5901
  );
5667
5902
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.87",
3
+ "version": "0.9.89",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",