dsh-m 0.9.63 → 0.9.65

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 +32 -0
  2. package/lib/client.js +207 -71
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.65 优化:灯箱邻图预取 + 触屏滑动切换 + 焦点圈闭
12
+
13
+ - **邻图预取**:当前图加载完成(`useImgChain` settled)后静默预热左右邻——URL 用与灯箱真实请求**逐字节同形**的 `weservUrl(w=1600)`,翻页直接命中浏览器缓存近零等待。纯 best-effort:不碰赢家记忆/链状态,预取失败对真实链零影响(导航时照常走 weserv→原图双兜底);`prefetchedRef` Set 去重防来回翻页重复发起,Set 随灯箱挂载期生命周期(重开重新预热);单图由 `lbNeighbors` 空表自然短路。
14
+ - **触屏滑动切换**:touch 指针横向滑过阈值即翻页(`swipeDir` 纯函数判向:|dx| ≥ 48px 且 |dx| ≥ 2|dy|;不设时长门槛,慢拖也算;竖滑/斜滑归零——纵向手势与 pinch 缩放可达性留给原生,`touch-action:pan-y pinch-zoom`)。只认 `pointerType==="touch"`——鼠标拖拽维持原生语义(图=原生拖拽、遮罩=按下+点击才关,0.9.27 防拖拽误关守卫不破);滑动终结后 500ms 内根上 capture 吞掉合成 click(双保险:即便滑动止于 ✕/遮罩也不触发其点击语义)。判向/邻图两枚纯函数入 `lightbox.js`(`swipeDir`/`lbNeighbors`,Node 单测覆盖)。
15
+ - **焦点圈闭**:灯箱开着时 Tab/Shift+Tab 只在灯箱内可聚焦控件(按钮/链接:✕/‹›/失败态重试+打开原图)间回绕;焦点已逃出灯箱(如点击遮罩落在 body)时拉回首/尾。portal 挂 body 后 Tab 本会走到背景面板,此处收口;DetailModal 自身 U8 债(无圈闭)维持不变,Esc 仍只关最上层弹层。
16
+ - **验证**:1289 项测试全绿、typecheck 零错误;`client-lightbox.test.mjs` 新增 6 项(`lbNeighbors`/`swipeDir` 纯逻辑单测 + 预取/滑动/圈闭/touch-action 结构锚),0.9.60/0.9.62 批次既有锚全数保持。
17
+
18
+ ### 0.9.64 优化:设置页双清单并排重排 + 操作记录整体搬迁设置 tab
19
+
20
+ - **设置页网格(mock 驱动终审)**:社区清单/精选清单两卡并排 **4:7**(社区卡只读五项 ~250px 足矣,宽度让给交互更多的精选卡);全屏态「关于」升入三卡行(`4fr 7fr 3fr`,不封顶——对齐市场「加列不加宽」哲学,设置页恰有三个卡片单元可用);视口 ≤760px 单列回落;关于卡常态通栏、操作记录恒通栏(`nth-child` 定位,与 `Section()` 签名解耦)。
21
+ - **社区卡清爽列表**:标题行回归纯标题(去 `from` 前缀);状态徽章降为绿/黄/红**小圆点**;行间发丝分隔线 + 数值右对齐等宽数字;「社区目录开关」入列表为末行(开关是目录属性,钉底反而脱离语境);关态 = 提示行 + 开关行(重开入口不丢)。中途方案「统计带」(白盒大数字)经实评否决——卡片中的卡片是视觉噪音。
22
+ - **精选卡减行**:说明行缩短(被删的「条目格式可参照下载的默认清单」与下载按钮 tooltip 重复,零信息损失);配置地址长 URL 单行省略 + `title` 悬浮全文。
23
+ - **操作记录搬迁(第二批)**:三 tab 共用的底部常驻区退役;`OperationsPanel` → `OperationsCard` 落位设置 tab 末位通宽卡——进行中组滚动区外常驻(进度不被滚走),已结束组进 220px 滚动区(展示上限 50 条防 DOM 无界,store/恢复校验不动),「清除已结束」钉底,空态 hint 卡常驻导览。
24
+ - **可见性补偿**:底部消失后失败/带警告终态无人主动发现 → 设置 tab 红点(复用 outdated 红点样式,清除/移除后随 syncOps 自灭);市场卡「进行中」徽章点击改道:切设置 tab + 滚动定位操作记录卡(文案「查看操作面板」→「查看操作记录」)。
25
+ - **验证**:构建 verify 通过、typecheck 零错误、全量 1283 项测试连续 4 轮全绿(首轮 1 例与改动无关的瞬时 flake 未复现);布局终稿经交互式 HTML mock 三宽度 × 三布局 × 多状态现场评审定稿(`docs/settings-layout-mock.html` 入库作设计记录)。
26
+
11
27
  ### 0.9.63 修复:点在加载转圈上不再无动作——转圈显式接 onClick=onClose
12
28
 
13
29
  - **背景**:0.9.62 给灯箱加了走链转圈占位,但转圈是遮罩根的**子元素**,而 `backdropCloseHandlers`(0.9.27 防拖拽误关)只在 `mousedown` 与 `click` 都命中遮罩自身(`target === currentTarget`)时才关——点在 34px 转圈正中因此无动作。转圈显式接 `onClick: onClose`,与「点图即关」同语义:走链期间的任何一次点击都是有效的关闭逃生门。
@@ -466,6 +482,22 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
466
482
 
467
483
  ## English
468
484
 
485
+ ### Added in 0.9.65 — lightbox: neighbor prefetch + touch swipe navigation + focus trap
486
+
487
+ - **Neighbor prefetch**: once the current image settles (`useImgChain` settled), the left/right neighbors are warmed silently - using a `weservUrl(w=1600)` URL **byte-identical** to the lightbox's real request, so a swipe/arrow navigation hits the browser cache with near-zero wait. Strictly best-effort: no winner-memory or chain-state mutation, a failed prefetch leaves the real chain untouched (navigation still runs the weserv→original dual fallback); a `prefetchedRef` Set dedupes repeated crossings (no re-issuing when bouncing back), the Set lives for the lightbox mount lifetime (a fresh open warms again); single-image boxes short-circuit naturally via `lbNeighbors` returning an empty list.
488
+ - **Touch swipe navigation**: a horizontal touch drag past the threshold flips images (`swipeDir` pure predicate: |dx| ≥ 48px and |dx| ≥ 2|dy|; no time gate - slow deliberate drags count; vertical/diagonal drags return 0 - vertical gestures and pinch-zoom accessibility stay native, via `touch-action:pan-y pinch-zoom`). Only `pointerType==="touch"` is recognized - mouse drags keep their native semantics (image = native drag, backdrop = press+click to close, the 0.9.27 drag-misfire guard intact); for 500ms after a recognized swipe the root's capture phase swallows the synthetic click (double belt: a swipe ending on ✕ or the backdrop triggers neither).
489
+ - **Focus trap**: while the lightbox is open, Tab/Shift+Tab cycles only among focusable controls inside it (buttons/links: ✕/‹›/failed-state retry + open-original); if focus has escaped (e.g. a backdrop click left it on body), the next Tab pulls it back to the first/last control. After the portal-to-body change, Tab used to walk into the background panel - this closes that gap; DetailModal's own U8 debt (no trap) is unchanged and Esc still closes only the topmost layer.
490
+ - **Verification**: 1289 tests green, typecheck clean; `client-lightbox.test.mjs` gains 6 items (`lbNeighbors`/`swipeDir` pure-logic units + prefetch/swipe/trap/touch-action structural anchors); all 0.9.60/0.9.62-era anchors retained.
491
+
492
+ ### Changed in 0.9.64 — settings page: dual-catalog side-by-side layout + operations history moved into the settings tab
493
+
494
+ - **Settings grid (mock-driven final review)**: the Community and Curated cards now sit side by side at **4:7** (the read-only community card needs ~250px; width goes to the more interactive registry card); in fullscreen the About card joins a three-card row (`4fr 7fr 3fr`, uncapped — the same "add columns, don't stretch units" philosophy as the market tab); below a 760px viewport it falls back to a single column; About spans full width in normal mode, the operations card is always full width.
495
+ - **Community card, clean list**: the title row is a pure title again (`from` prefix dropped); the status badge becomes a small green/amber/red dot; hairline row separators + right-aligned tabular figures; the catalog toggle joins the list as its last row (the toggle is an attribute of the catalog — pinning it to the card bottom detached it from context); off state = hint line + toggle row (the re-entry control is preserved). The intermediate "stat band" design (boxed big numbers) was rejected in live review — cards inside a card are visual noise.
496
+ - **Curated card trim**: the note line is shortened (the removed clause duplicated the download button tooltip — zero information loss); long configured URLs render as a single ellipsized line with the full value on hover.
497
+ - **Operations history migration (batch 2)**: the bottom strip shared by all three tabs is retired; `OperationsPanel` becomes `OperationsCard` at the bottom of the settings tab — active rows stay outside the scroll area (progress never scrolls away), finished records live in a 220px scroll region (display cap 50 to bound the DOM; store/restore validation untouched), "Clear finished" is pinned below, and an empty-state hint card doubles as wayfinding.
498
+ - **Visibility compensation**: with the bottom strip gone, failed/warned records would go unnoticed — the settings tab now shows a red dot (reuses the outdated-dot style; clears on remove/clear); the market card's "running" badge now switches to the settings tab and scrolls to the operations card (label 查看操作面板 → 查看操作记录).
499
+ - **Verification**: build verify passes, typecheck clean, full 1283-test suite green in 4 consecutive runs (one transient unrelated flake in the first run did not reproduce); the final layout was settled via an interactive HTML mock across three widths × three layouts × multiple states (`docs/settings-layout-mock.html` committed as a design record).
500
+
469
501
  ### Fixed in 0.9.63 — clicking dead-center on the loading spinner no longer no-ops: spinner gets an explicit onClick=onClose
470
502
 
471
503
  - **Background**: 0.9.62 added the chain-loading spinner, but the spinner is a **child** of the backdrop root, and `backdropCloseHandlers` (0.9.27 drag-misfire guard) closes only when both `mousedown` and `click` land on the backdrop itself (`target === currentTarget`) - so a click dead-center on the 34px spinner did nothing. The spinner now carries `onClick: onClose`, same semantics as click-image-to-close: during loading, any click is a valid escape hatch.
package/lib/client.js CHANGED
@@ -466,12 +466,28 @@ var init_backdrop = __esm({
466
466
  // src/client/lightbox.js
467
467
  var lightbox_exports = {};
468
468
  __export(lightbox_exports, {
469
- lbStep: () => lbStep
469
+ lbNeighbors: () => lbNeighbors,
470
+ lbStep: () => lbStep,
471
+ swipeDir: () => swipeDir
470
472
  });
471
473
  function lbStep(index, step, length) {
472
474
  if (!Number.isFinite(index) || !Number.isFinite(step) || !Number.isInteger(length) || length <= 1) return index;
473
475
  return ((index + step) % length + length) % length;
474
476
  }
477
+ function lbNeighbors(index, length) {
478
+ if (!Number.isFinite(index) || !Number.isInteger(length) || length <= 1) return [];
479
+ const out = [];
480
+ for (const n of [lbStep(index, 1, length), lbStep(index, -1, length)]) {
481
+ if (Number.isFinite(n) && !out.includes(n)) out.push(n);
482
+ }
483
+ return out;
484
+ }
485
+ function swipeDir(dx, dy, { threshold = 48, ratio = 2 } = {}) {
486
+ if (!Number.isFinite(dx) || !Number.isFinite(dy)) return 0;
487
+ const ax = Math.abs(dx);
488
+ if (ax < threshold || ax < ratio * Math.abs(dy)) return 0;
489
+ return dx > 0 ? 1 : -1;
490
+ }
475
491
  var init_lightbox = __esm({
476
492
  "src/client/lightbox.js"() {
477
493
  "use strict";
@@ -1651,7 +1667,7 @@ var API = "/dshm";
1651
1667
  var { DEFAULT_PAGE_SIZE: DEFAULT_PAGE_SIZE2, MARKET_PAGE_SIZES: MARKET_PAGE_SIZES2, pageItems: pageItems2, createZoneState: createZoneState2, normalizeMarketQuery: normalizeMarketQuery2, resetPageOnFilterChange: resetPageOnFilterChange2, normalizeMarketResponse: normalizeMarketResponse2, registryNotice: registryNotice2, zoneChips: zoneChips2, marketNotice: marketNotice2, searchSourceOf: searchSourceOf2, mergeLatestFields: mergeLatestFields2, chipRows: chipRows2, countBeyondRows: countBeyondRows2, clipTopOf: clipTopOf2, visibleRowTop: visibleRowTop2, autoExpandDecision: autoExpandDecision2 } = (init_market_state(), __toCommonJS(market_state_exports));
1652
1668
  var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
1653
1669
  var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCommonJS(backdrop_exports));
1654
- var { lbStep: lbStep2 } = (init_lightbox(), __toCommonJS(lightbox_exports));
1670
+ var { lbStep: lbStep2, lbNeighbors: lbNeighbors2, swipeDir: swipeDir2 } = (init_lightbox(), __toCommonJS(lightbox_exports));
1655
1671
  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));
1656
1672
  var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
1657
1673
  var { ExtLink, MdImg, renderMarkdown } = createMarkdown2(h);
@@ -1705,7 +1721,7 @@ var ZH = {
1705
1721
  "op.superseded.note": "{target} \u5DF2\u8DF3\u8FC7\uFF08\u524D\u63D0\u5DF2\u4E0D\u6210\u7ACB\u6216\u5DF2\u624B\u52A8\u5904\u7406\uFF09",
1706
1722
  "op.cancelled": "\u7528\u6237\u653E\u5F03\u786E\u8BA4",
1707
1723
  "op.remove": "\u79FB\u9664\u8BB0\u5F55",
1708
- "op.panel.jump": "\u67E5\u770B\u64CD\u4F5C\u9762\u677F",
1724
+ "op.panel.jump": "\u67E5\u770B\u64CD\u4F5C\u8BB0\u5F55",
1709
1725
  "op.kind.install": "\u5B89\u88C5",
1710
1726
  "op.kind.upgrade": "\u5347\u7EA7",
1711
1727
  "op.kind.uninstall": "\u5378\u8F7D",
@@ -1801,7 +1817,7 @@ var ZH = {
1801
1817
  "src.custom.unavailable": "\u81EA\u5B9A\u4E49\u6E90\uFF08\u4E0D\u53EF\u7528\uFF09",
1802
1818
  "settings.address": "Registry \u5730\u5740",
1803
1819
  "settings.address.default": "\u5185\u7F6E\u9ED8\u8BA4\u6E05\u5355",
1804
- "settings.note.custom": "\u81EA\u5B9A\u4E49\u6E90\u5C06\u6574\u4F53\u66FF\u6362\u300C\u7CBE\u9009\u6E05\u5355\u300D\uFF1B\u6761\u76EE\u683C\u5F0F\u53EF\u53C2\u7167\u4E0B\u8F7D\u7684\u9ED8\u8BA4\u6E05\u5355\u3002\u7559\u7A7A = \u4F7F\u7528\u5185\u7F6E\u9ED8\u8BA4\u6E05\u5355\u3002",
1820
+ "settings.note.custom": "\u81EA\u5B9A\u4E49\u6E90\u5C06\u6574\u4F53\u66FF\u6362\u300C\u7CBE\u9009\u6E05\u5355\u300D\uFF1B\u7559\u7A7A = \u4F7F\u7528\u5185\u7F6E\u9ED8\u8BA4\u6E05\u5355\u3002",
1805
1821
  "settings.address.ph": "https://example.com/registry.json \u6216 /path/to/registry.json",
1806
1822
  "settings.configured": "\u914D\u7F6E\u5730\u5740",
1807
1823
  "settings.effective": "\u751F\u6548\u6765\u6E90",
@@ -1863,7 +1879,7 @@ var ZH = {
1863
1879
  "guard.noforce": "\u5B88\u536B\u62E6\u622A\u65E0\u300C\u4ECD\u8981\u5B89\u88C5\u300D\u901A\u9053\uFF0C\u8BF7\u6309\u4FEE\u590D\u4F9D\u636E\u4EBA\u5DE5\u5904\u7406",
1864
1880
  "detail.screenshots": "\u622A\u56FE",
1865
1881
  "installed.check.incomplete": "\u68C0\u67E5\u672A\u5B8C\u6210",
1866
- "settings.community": "\u793E\u533A\u6E05\u5355\uFF08from awesome-dsh-plugin\uFF09",
1882
+ "settings.community": "\u793E\u533A\u6E05\u5355\uFF08awesome-dsh-plugin\uFF09",
1867
1883
  "settings.community.toggle": "\u793E\u533A\u76EE\u5F55\u5F00\u5173",
1868
1884
  "settings.community.off": "\u793E\u533A\u76EE\u5F55\u5DF2\u5173\u95ED\uFF1A\u5E02\u573A\u4EC5\u663E\u793A\u7CBE\u9009\u6E05\u5355\uFF0C\u91CD\u65B0\u5F00\u542F\u5373\u65F6\u751F\u6548\u3002",
1869
1885
  "settings.community.on.ok": "\u793E\u533A\u76EE\u5F55\u5DF2\u5F00\u542F\uFF08\u5373\u65F6\u751F\u6548\uFF09",
@@ -1877,6 +1893,11 @@ var ZH = {
1877
1893
  "settings.community.count.v": "{n}\uFF08\u4E0A\u6E38 {up}\uFF09",
1878
1894
  "settings.community.displaced": "\u4E0E\u7CBE\u9009\u91CD\u590D",
1879
1895
  "settings.community.displaced.v": "{n} \u6761",
1896
+ "settings.ops": "\u64CD\u4F5C\u8BB0\u5F55",
1897
+ "settings.ops.empty": "\u6682\u65E0\u64CD\u4F5C\u8BB0\u5F55\u3002\u5B89\u88C5\u3001\u5347\u7EA7\u3001\u5378\u8F7D\u4E0E\u5F00\u5173\u64CD\u4F5C\u7684\u6267\u884C\u5386\u53F2\u5C06\u5728\u8FD9\u91CC\u663E\u793A\u3002",
1898
+ "settings.ops.group.active": "\u8FDB\u884C\u4E2D",
1899
+ "settings.ops.group.done": "\u5DF2\u7ED3\u675F",
1900
+ "settings.ops.reddot": "\u5B58\u5728\u5931\u8D25\u6216\u5E26\u8B66\u544A\u7684\u64CD\u4F5C\u8BB0\u5F55",
1880
1901
  "registry.refreshed": "\u6536\u5F55\u6E05\u5355\u5DF2\u5F3A\u5236\u5237\u65B0",
1881
1902
  "notify.installed": "\u5DF2\u5B89\u88C5 {pkg}{version}",
1882
1903
  "notify.allowbuilds": "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF0C\u5DF2\u6309\u7B56\u7565\u653E\u884C\uFF09",
@@ -1986,7 +2007,7 @@ var EN = {
1986
2007
  "op.superseded.note": "{target} skipped (precondition gone or already handled)",
1987
2008
  "op.cancelled": "user cancelled",
1988
2009
  "op.remove": "Dismiss",
1989
- "op.panel.jump": "Open operations panel",
2010
+ "op.panel.jump": "Open operations card",
1990
2011
  "op.kind.install": "Install",
1991
2012
  "op.kind.upgrade": "Upgrade",
1992
2013
  "op.kind.uninstall": "Uninstall",
@@ -2082,7 +2103,7 @@ var EN = {
2082
2103
  "src.custom.unavailable": "Custom source (unavailable)",
2083
2104
  "settings.address": "Registry address",
2084
2105
  "settings.address.default": "Built-in default registry",
2085
- "settings.note.custom": "A custom source replaces the whole Curated registry; use the downloaded default registry as the format reference. Leave empty for the built-in default.",
2106
+ "settings.note.custom": "A custom source replaces the whole Curated registry. Leave empty for the built-in default.",
2086
2107
  "settings.address.ph": "https://example.com/registry.json or /path/to/registry.json",
2087
2108
  "settings.configured": "Configured address",
2088
2109
  "settings.effective": "Effective source",
@@ -2144,7 +2165,7 @@ var EN = {
2144
2165
  "guard.noforce": "Guard blocks have no force channel \u2014 repair manually per the basis above",
2145
2166
  "detail.screenshots": "Screenshots",
2146
2167
  "installed.check.incomplete": "Check incomplete",
2147
- "settings.community": "Community catalog (from awesome-dsh-plugin)",
2168
+ "settings.community": "Community catalog (awesome-dsh-plugin)",
2148
2169
  "settings.community.toggle": "Community catalog toggle",
2149
2170
  "settings.community.off": "Community catalog is off: the market shows only the Curated registry. Toggle back on anytime \u2014 it applies live.",
2150
2171
  "settings.community.on.ok": "Community catalog enabled (live)",
@@ -2158,6 +2179,11 @@ var EN = {
2158
2179
  "settings.community.count.v": "{n} (upstream {up})",
2159
2180
  "settings.community.displaced": "Displaced (duplicates)",
2160
2181
  "settings.community.displaced.v": "{n}",
2182
+ "settings.ops": "Operations",
2183
+ "settings.ops.empty": "No operations yet. Install, upgrade, uninstall and toggle history will appear here.",
2184
+ "settings.ops.group.active": "Active",
2185
+ "settings.ops.group.done": "Finished",
2186
+ "settings.ops.reddot": "Failed or warned operations exist",
2161
2187
  "registry.refreshed": "Registry force-refreshed",
2162
2188
  "notify.installed": "Installed {pkg}{version}",
2163
2189
  "notify.allowbuilds": " (note: this plugin ran build scripts, allowed by policy)",
@@ -2364,8 +2390,10 @@ var CSS = `
2364
2390
  \u5728\u6D45\u5E95\u4E0A\u4E0D\u53EF\u89C1\uFF08\u7528\u6237\u53CC\u6A21\u5F0F\u5B9E\u62CD\uFF09\u3002\u53CC\u4FA7\u4FEE\u590D\u2014\u2014\u76AE\u80A4\u4FA7 :not(.dsvm-lightbox) \u8C41\u514D
2365
2391
  \uFF08dsh-skins 1.5.1 \u8D77\uFF09\uFF1B\u672C\u4FA7 background !important \u81EA\u6301\uFF1Amarketplace \u793E\u533A\u76AE\u80A4\u4E0D\u53EF
2366
2392
  \u679A\u4E3E\uFF0C\u5F71\u9662\u4E0D\u53D8\u91CF\u5FC5\u987B\u7531\u7EC4\u4EF6\u81EA\u5DF1\u515C\u5E95\u3002!important \u6E05\u5355\u5B9A\u957F\uFF08\u6CE8\u91CA\u63D0\u53CA\xD72 + \u672C\u89C4\u5219
2367
- + sidebar footer \u517C\u5BB9\u89C4\u5219\uFF09\uFF0C\u65B0\u589E\u987B\u6539 client-lightbox \u65AD\u8A00\u663E\u5F0F\u767B\u8BB0\uFF0C\u6CE8\u91CA\u5373\u5B88\u536B\u3002 */
2368
- .dsvm-lightbox{position:fixed;top:var(--dsh-windows-titlebar-height,0px);left:0;right:0;bottom:0;z-index:2147483200;background:rgba(0,0,0,.88)!important;display:flex;align-items:center;justify-content:center}
2393
+ + sidebar footer \u517C\u5BB9\u89C4\u5219\uFF09\uFF0C\u65B0\u589E\u987B\u6539 client-lightbox \u65AD\u8A00\u663E\u5F0F\u767B\u8BB0\uFF0C\u6CE8\u91CA\u5373\u5B88\u536B\u3002
2394
+ 0.9.65\uFF1Atouch-action:pan-y pinch-zoom\u2014\u2014\u6A2A\u6ED1\u5224\u5411\u4EA4\u7ED9\u706F\u7BB1\u6ED1\u52A8\u5207\u6362\uFF0C\u7EB5\u5411\u539F\u751F
2395
+ \u624B\u52BF\u4E0E pinch \u7F29\u653E\uFF08\u4F4E\u89C6\u529B\u53EF\u8FBE\u6027\uFF09\u4E0D\u88AB\u52AB\u6301\u3002 */
2396
+ .dsvm-lightbox{position:fixed;top:var(--dsh-windows-titlebar-height,0px);left:0;right:0;bottom:0;z-index:2147483200;background:rgba(0,0,0,.88)!important;display:flex;align-items:center;justify-content:center;touch-action:pan-y pinch-zoom}
2369
2397
  .dsvm-lightbox img{max-width:min(92vw,1500px);max-height:74vh;border-radius:8px;cursor:zoom-out;box-shadow:0 10px 44px rgba(0,0,0,.55);display:block}
2370
2398
  .dsvm-btn:focus-visible{outline:2px solid rgba(255,255,255,.75);outline-offset:2px}
2371
2399
  .dsvm-btn.dsvm-lbclose{position:absolute;top:12px;right:12px;width:38px;height:38px;padding:0;border-radius:10px;display:flex;align-items:center;justify-content:center}
@@ -2403,7 +2431,11 @@ var CSS = `
2403
2431
  .dshm-card:hover{border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
2404
2432
  .dsvm-btn{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.14);color:#fff;border-radius:8px;padding:5px 14px;font:inherit;font-size:13px;cursor:pointer}
2405
2433
  .dsvm-btn:hover{background:rgba(255,255,255,.24)}
2406
- .dsvm-ops{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;padding:8px 10px;display:flex;flex-direction:column;gap:4px;font-size:12px}
2434
+ .dsvm-ops-caption{font-size:11px;color:var(--dsw-alias-label-caption,#9ca3af);margin:2px 0 -3px}
2435
+ .dshm-ops-scroll{max-height:220px;overflow-y:auto;overscroll-behavior:contain}
2436
+ .dshm-ops-scroll::-webkit-scrollbar{width:8px}
2437
+ .dshm-ops-scroll::-webkit-scrollbar-thumb{background:rgba(127,127,127,.28);border-radius:4px}
2438
+ .dshm-ops-scroll::-webkit-scrollbar-thumb:hover{background:rgba(127,127,127,.45)}
2407
2439
  .dsvm-opgroup{display:flex;flex-direction:column;gap:3px}
2408
2440
  .dsvm-opgroup.done{opacity:.75}
2409
2441
  .dsvm-oprow{display:flex;align-items:center;gap:8px;min-height:22px}
@@ -2447,6 +2479,23 @@ button.dshm-badge:hover{filter:brightness(.95)}
2447
2479
  .dshm-row{display:flex;align-items:center;gap:8px}
2448
2480
  .dshm-kv{display:grid;grid-template-columns:96px 1fr;gap:6px 10px;font-size:12px;align-items:baseline}
2449
2481
  .dshm-kv .k{color:var(--dsw-alias-label-caption,#6b7280);font-size:11px}
2482
+ .dshm-settings-grid{display:grid;gap:10px;grid-template-columns:4fr 7fr;min-width:0}
2483
+ .dshm-settings-grid>.dshm-section{margin:0;min-width:0}
2484
+ .dshm-settings-grid>.dshm-section:nth-child(3){grid-column:1/-1}
2485
+ .dshm-settings-grid>.dshm-ops-card{grid-column:1/-1}
2486
+ .dshm-panel.full .dshm-settings-grid{grid-template-columns:4fr 7fr 3fr}
2487
+ .dshm-panel.full .dshm-settings-grid>.dshm-section:nth-child(3){grid-column:auto}
2488
+ @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}}
2489
+ .dshm-kvlines{display:flex;flex-direction:column}
2490
+ .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}
2491
+ .dshm-kvline-row:last-child{border-bottom:0}
2492
+ .dshm-kvline-row .rl{font-size:11px;color:var(--dsw-alias-label-caption,#6b7280);flex:none}
2493
+ .dshm-kvline-row .rv{font-size:12px;text-align:right;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;min-width:0}
2494
+ .dshm-kvline-row .dshm-switch{margin-left:0;align-self:center}
2495
+ .dshm-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:5px;vertical-align:1px;background:var(--dsw-alias-state-success-primary,#22c55e)}
2496
+ .dshm-dot.warn{background:var(--dsw-alias-state-warn-primary,#b45309)}
2497
+ .dshm-dot.err{background:var(--dsw-alias-state-error-primary,#b91c1c)}
2498
+ .dshm-code-1line{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
2450
2499
  .dshm-section{background:var(--dsw-alias-bg-layer-2,rgba(38,49,72,.04));border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:9px;margin:0 0 10px}
2451
2500
  .dshm-section:last-child{margin-bottom:2px}
2452
2501
  .dshm-section-title{display:flex;align-items:center;gap:7px;font-weight:700;font-size:12px;letter-spacing:.2px;color:var(--dsw-alias-label-primary,inherit)}
@@ -3160,9 +3209,40 @@ function Lightbox({ shots, index, onNav, onClose }) {
3160
3209
  if (closeRef.current) closeRef.current.focus();
3161
3210
  }, []);
3162
3211
  const chain = useImgChain(shots[index], { w: 1600 });
3212
+ const prefetchedRef = useRef(null);
3213
+ if (prefetchedRef.current === null) prefetchedRef.current = /* @__PURE__ */ new Set();
3214
+ useEffect(() => {
3215
+ if (typeof Image === "undefined") return;
3216
+ if (!chain.settled || chain.failed) return;
3217
+ for (const n of lbNeighbors2(index, shots.length)) {
3218
+ const url = shots[n];
3219
+ if (typeof url !== "string" || url === "" || prefetchedRef.current.has(url)) continue;
3220
+ prefetchedRef.current.add(url);
3221
+ const img = new Image();
3222
+ img.decoding = "async";
3223
+ img.src = weservUrl2(url, { w: 1600 });
3224
+ }
3225
+ }, [chain.settled, chain.failed, index, shots]);
3226
+ const swipeRef = useRef(null);
3227
+ const swipedAtRef = useRef(0);
3228
+ const onPointerDown = (e) => {
3229
+ if (e.pointerType !== "touch") return;
3230
+ swipeRef.current = { x: e.clientX, y: e.clientY };
3231
+ };
3232
+ const onPointerUp = (e) => {
3233
+ if (e.pointerType !== "touch" || !swipeRef.current) return;
3234
+ const dir = swipeDir2(e.clientX - swipeRef.current.x, e.clientY - swipeRef.current.y);
3235
+ swipeRef.current = null;
3236
+ if (!dir || single) return;
3237
+ swipedAtRef.current = Date.now();
3238
+ onNav(lbStep2(index, dir, shots.length));
3239
+ };
3240
+ const onClickCapture = (e) => {
3241
+ if (Date.now() - swipedAtRef.current < 500) e.stopPropagation();
3242
+ };
3163
3243
  const node = h(
3164
3244
  "div",
3165
- { className: "dsvm-lightbox", role: "dialog", "aria-modal": "true", "aria-label": lookup("lb.title"), ...backdropCloseHandlers2(onClose) },
3245
+ { className: "dsvm-lightbox", role: "dialog", "aria-modal": "true", "aria-label": lookup("lb.title"), onPointerDown, onPointerUp, onClickCapture, ...backdropCloseHandlers2(onClose) },
3166
3246
  chain.failed ? h(
3167
3247
  "div",
3168
3248
  { className: "dsvm-lbfail", onClick: (e) => e.stopPropagation() },
@@ -3252,6 +3332,19 @@ function DetailModal({ it, labels, busy, onClose, onInstall, onUpgrade, upgradeB
3252
3332
  if (e.key === "ArrowLeft") setLb((i) => lbStep2(i, -1, visible.length));
3253
3333
  if (e.key === "ArrowRight") setLb((i) => lbStep2(i, 1, visible.length));
3254
3334
  }
3335
+ if (e.key === "Tab" && lb !== null) {
3336
+ const box = typeof document !== "undefined" ? document.querySelector(".dsvm-lightbox") : null;
3337
+ if (!box) return;
3338
+ const focusables = Array.from(box.querySelectorAll("button, a[href]")).filter((el) => el.offsetWidth > 0 && el.offsetHeight > 0);
3339
+ if (!focusables.length) return;
3340
+ const first = focusables[0];
3341
+ const last = focusables[focusables.length - 1];
3342
+ const active = document.activeElement;
3343
+ if (!box.contains(active) || (e.shiftKey ? active === first : active === last)) {
3344
+ e.preventDefault();
3345
+ (e.shiftKey ? last : first).focus();
3346
+ }
3347
+ }
3255
3348
  };
3256
3349
  document.addEventListener("keydown", onKey);
3257
3350
  return () => document.removeEventListener("keydown", onKey);
@@ -3379,7 +3472,7 @@ var ZONE_TABS = [
3379
3472
  { id: "primary", labelKey: "zone.primary" },
3380
3473
  { id: "favorites", labelKey: "zone.favorites" }
3381
3474
  ];
3382
- function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind }) {
3475
+ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind, onOpenOps }) {
3383
3476
  const [zone, setZone] = useState("community");
3384
3477
  const market = zone === "favorites" ? null : markets[zone];
3385
3478
  const { data, loading, error, reload, query, updateQuery } = market || {};
@@ -3669,10 +3762,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
3669
3762
  title: lookup("op.panel.jump"),
3670
3763
  onClick: (e) => {
3671
3764
  e.stopPropagation();
3672
- if (typeof document !== "undefined") {
3673
- const el = document.querySelector(".dsvm-ops");
3674
- if (el && el.scrollIntoView) el.scrollIntoView({ block: "center", behavior: "smooth" });
3675
- }
3765
+ if (typeof onOpenOps === "function") onOpenOps();
3676
3766
  }
3677
3767
  },
3678
3768
  lookup("op.status.running")
@@ -4045,7 +4135,7 @@ function regSourceLabel(data) {
4045
4135
  function configStatusLabel(status) {
4046
4136
  return lookup(`settings.status.${status || "loading"}` || "settings.status.loading");
4047
4137
  }
4048
- function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4138
+ function SettingsTab({ notify, onRegistryChanged, onForceMarket, opsRecords, onOpsClearFinished, onOpsRemove }) {
4049
4139
  const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
4050
4140
  const cfgState = useAsync(() => api("registry-config"), []);
4051
4141
  const [busy, setBusy] = useState(false);
@@ -4135,47 +4225,61 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4135
4225
  };
4136
4226
  const communityLive = communitySummary && (communitySummary.status === "ready" || communitySummary.status === "stale");
4137
4227
  const communityStatusBadge = !communityLive ? { cls: "err", label: lookup("settings.status.unavailable") } : communitySummary.status === "stale" ? { cls: "warn", label: lookup("settings.community.status.stale") } : { cls: "ok", label: lookup("settings.status.ready") };
4138
- return h(
4228
+ const kvlineRow = (labelKey, valueNode) => h(
4229
+ "div",
4230
+ { className: "dshm-kvline-row" },
4231
+ h("span", { className: "rl" }, lookup(labelKey)),
4232
+ h("span", { className: "rv" }, valueNode)
4233
+ );
4234
+ const communitySwitch = h(
4235
+ "label",
4236
+ {
4237
+ className: `dshm-switch${communityOn ? " on" : ""}${communityBusy || communityOn === null ? " locked" : ""}`,
4238
+ title: lookup("settings.community.toggle")
4239
+ },
4240
+ h("input", {
4241
+ type: "checkbox",
4242
+ checked: communityOn === true,
4243
+ disabled: communityBusy || communityOn === null,
4244
+ onChange: (e) => applyCommunity(e.target.checked)
4245
+ }),
4246
+ h("span", { className: "dshm-switch-slider" })
4247
+ );
4248
+ const communitySwitchRow = h(
4249
+ "div",
4250
+ { className: "dshm-kvline-row" },
4251
+ h("span", { className: "rl" }, lookup("settings.community.toggle")),
4252
+ communitySwitch
4253
+ );
4254
+ const communityBody = communityOn === false ? h(
4139
4255
  React.Fragment,
4140
4256
  null,
4141
- // ① 社区清单(from awesome-dsh-plugin):标题行右侧开关;开态紧凑 kv,关态单行说明
4257
+ h("div", { className: "dshm-hint" }, lookup("settings.community.off")),
4258
+ communitySwitchRow
4259
+ ) : h(
4260
+ "div",
4261
+ { className: "dshm-kvlines" },
4262
+ kvlineRow("settings.community.status", h(
4263
+ "span",
4264
+ null,
4265
+ h("span", { className: `dshm-dot${communityStatusBadge.cls === "ok" ? "" : ` ${communityStatusBadge.cls}`}` }),
4266
+ communityStatusBadge.label
4267
+ )),
4268
+ kvlineRow("settings.community.version", communityLive ? communitySummary.version || "\u2014" : "\u2014"),
4269
+ kvlineRow("settings.community.route", communityLive ? communitySummary.route || "\u2014" : "\u2014"),
4270
+ kvlineRow("settings.community.count", communityLive ? lookup("settings.community.count.v", { n: communitySummary.acceptedCount ?? 0, up: communitySummary.upstreamCount ?? "\u2014" }) : "\u2014"),
4271
+ kvlineRow("settings.community.displaced", communityLive ? lookup("settings.community.displaced.v", { n: communitySummary.displaced ?? 0 }) : "\u2014"),
4272
+ communitySwitchRow
4273
+ );
4274
+ return h(
4275
+ "div",
4276
+ { className: "dshm-settings-grid" },
4277
+ // ① 社区清单(awesome-dsh-plugin):左卡;清爽列表 + 发丝分隔线,开关为列表末行
4142
4278
  h(
4143
4279
  "div",
4144
4280
  { className: "dshm-section" },
4145
- h(
4146
- "div",
4147
- { className: "dshm-section-title" },
4148
- lookup("settings.community"),
4149
- h("span", { className: "dshm-spacer" }),
4150
- h(
4151
- "label",
4152
- {
4153
- className: `dshm-switch${communityOn ? " on" : ""}${communityBusy || communityOn === null ? " locked" : ""}`,
4154
- title: lookup("settings.community.toggle")
4155
- },
4156
- h("input", {
4157
- type: "checkbox",
4158
- checked: communityOn === true,
4159
- disabled: communityBusy || communityOn === null,
4160
- onChange: (e) => applyCommunity(e.target.checked)
4161
- }),
4162
- h("span", { className: "dshm-switch-slider" })
4163
- )
4164
- ),
4165
- communityOn === false ? h("div", { className: "dshm-hint" }, lookup("settings.community.off")) : h(
4166
- "div",
4167
- { className: "dshm-kv" },
4168
- h("span", { className: "k" }, lookup("settings.community.status")),
4169
- h("span", null, h("span", { className: `dshm-badge ${communityStatusBadge.cls}` }, communityStatusBadge.label)),
4170
- h("span", { className: "k" }, lookup("settings.community.version")),
4171
- h("span", null, communityLive ? communitySummary.version || "\u2014" : "\u2014"),
4172
- h("span", { className: "k" }, lookup("settings.community.route")),
4173
- h("span", null, communityLive ? communitySummary.route || "\u2014" : "\u2014"),
4174
- h("span", { className: "k" }, lookup("settings.community.count")),
4175
- h("span", null, communityLive ? lookup("settings.community.count.v", { n: communitySummary.acceptedCount ?? 0, up: communitySummary.upstreamCount ?? "\u2014" }) : "\u2014"),
4176
- h("span", { className: "k" }, lookup("settings.community.displaced")),
4177
- h("span", null, communityLive ? lookup("settings.community.displaced.v", { n: communitySummary.displaced ?? 0 }) : "\u2014")
4178
- ),
4281
+ h("div", { className: "dshm-section-title" }, lookup("settings.community")),
4282
+ communityBody,
4179
4283
  communitySummary && Array.isArray(communitySummary.errors) && communitySummary.errors.length && communityOn !== false ? h("div", { className: "dshm-err" }, communitySummary.errors.join("\uFF1B")) : null
4180
4284
  ),
4181
4285
  // ② 精选清单(registry.json):自定义源整体替换主清单,仅影响市场「精选」区
@@ -4226,7 +4330,7 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4226
4330
  "div",
4227
4331
  { className: "dshm-kv", style: { marginTop: "4px" } },
4228
4332
  h("span", { className: "k" }, lookup("settings.configured")),
4229
- h("span", null, snap.registryUrl ? h("span", { className: "dshm-code" }, snap.registryUrl) : h("span", { className: "dshm-hint" }, lookup("settings.address.default"))),
4333
+ h("span", null, snap.registryUrl ? h("span", { className: "dshm-code dshm-code-1line", title: snap.registryUrl }, snap.registryUrl) : h("span", { className: "dshm-hint" }, lookup("settings.address.default"))),
4230
4334
  h("span", { className: "k" }, lookup("settings.effective")),
4231
4335
  h("span", null, regSourceLabel(state), state && !state.isDefault ? h("span", { className: "dshm-badge info", style: { marginLeft: "6px" } }, lookup("badge.custom")) : null),
4232
4336
  h("span", { className: "k" }, lookup("settings.updated")),
@@ -4251,7 +4355,9 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4251
4355
  h("span", { className: "dshm-links-sep" }, "\xB7"),
4252
4356
  h(ExtLink, { href: "https://www.npmjs.com/package/dsh-m" }, lookup("settings.about.npm"))
4253
4357
  )
4254
- )
4358
+ ),
4359
+ // ④ 操作记录:恒通宽卡(第二批搬迁),进行中常驻 + 已结束滚动区,空态 hint 导览
4360
+ h(OperationsCard, { records: opsRecords, onClearFinished: onOpsClearFinished, onRemove: onOpsRemove })
4255
4361
  );
4256
4362
  }
4257
4363
  function Section(title, sub, ...children) {
@@ -4421,10 +4527,9 @@ var OP_STATUS_CLS = {
4421
4527
  failed: "err",
4422
4528
  superseded: "sup"
4423
4529
  };
4424
- function OperationsPanel({ records, onClearFinished, onRemove }) {
4530
+ function OperationsCard({ records, onClearFinished, onRemove }) {
4425
4531
  const active = records.filter((r) => r.status === "queued" || r.status === "running" || r.status === "input");
4426
- const finished = records.filter((r) => active.indexOf(r) < 0).slice(-6);
4427
- if (!active.length && !finished.length) return null;
4532
+ const finished = records.filter((r) => active.indexOf(r) < 0).slice(-50);
4428
4533
  const clearable = records.some((r) => TERMINAL_CLEARABLE2.has(r.status));
4429
4534
  const row = (r) => h(
4430
4535
  "div",
@@ -4440,14 +4545,30 @@ function OperationsPanel({ records, onClearFinished, onRemove }) {
4440
4545
  );
4441
4546
  return h(
4442
4547
  "div",
4443
- { className: "dsvm-ops" },
4548
+ { className: "dshm-section dshm-ops-card" },
4549
+ h("div", { className: "dshm-section-title" }, lookup("settings.ops")),
4550
+ active.length && finished.length ? h("div", { className: "dsvm-ops-caption" }, lookup("settings.ops.group.active")) : null,
4444
4551
  active.length ? h("div", { className: "dsvm-opgroup" }, ...active.map(row)) : null,
4445
4552
  finished.length ? h(
4446
4553
  "div",
4447
- { className: "dsvm-opgroup done" },
4448
- ...finished.map(row),
4554
+ { className: "dsvm-ops-caption", style: { marginTop: active.length ? "7px" : "0" } },
4555
+ lookup("settings.ops.group.done")
4556
+ ) : null,
4557
+ finished.length ? h(
4558
+ "div",
4559
+ { className: "dshm-ops-scroll" },
4560
+ h(
4561
+ "div",
4562
+ { className: "dsvm-opgroup done" },
4563
+ ...finished.map(row)
4564
+ )
4565
+ ) : null,
4566
+ finished.length ? h(
4567
+ "div",
4568
+ { className: "dshm-actions" },
4449
4569
  h("button", { className: "dshm-btn sm", disabled: !clearable, title: clearable ? void 0 : lookup("op.clear.none"), onClick: onClearFinished }, lookup("op.clear"))
4450
- ) : null
4570
+ ) : null,
4571
+ !active.length && !finished.length ? h("div", { className: "dshm-hint" }, lookup("settings.ops.empty")) : null
4451
4572
  );
4452
4573
  }
4453
4574
  var TABS = [
@@ -4567,6 +4688,16 @@ function MarketPanel({ onClose }) {
4567
4688
  const updates = useAsync(() => api("installedUpdates"), []);
4568
4689
  const [opRecords, setOpRecords] = useState(() => opsStore.list().map((r) => ({ ...r })));
4569
4690
  const syncOps = useCallback(() => setOpRecords(opsStore.list().map((r) => ({ ...r }))), []);
4691
+ const openOps = useCallback(() => {
4692
+ setTab("settings");
4693
+ if (typeof document !== "undefined") {
4694
+ setTimeout(() => {
4695
+ const el = document.querySelector(".dshm-ops-card");
4696
+ if (el && el.scrollIntoView) el.scrollIntoView({ block: "center", behavior: "smooth" });
4697
+ }, 60);
4698
+ }
4699
+ }, [setTab]);
4700
+ const opsAttention = opRecords.some((r) => r.status === "failed" || r.status === "warned");
4570
4701
  const runOp = useCallback(
4571
4702
  (kind, target, exec, meta = {}, reuseId) => (
4572
4703
  // session:true = 活会话标记(终审·新伤1):重挂载的 restore 跳过(泵仍拥有);persist 时剥离。
@@ -4727,7 +4858,9 @@ function MarketPanel({ onClose }) {
4727
4858
  },
4728
4859
  lookup(labelKey),
4729
4860
  countKey && counts[countKey] != null ? h("span", { className: "dshm-count" }, String(counts[countKey])) : null,
4730
- key === "installed" && outdatedCount > 0 ? h("span", { className: "dsvm-reddot", title: lookup("installed.upgradeAll", { n: outdatedCount }) }) : null
4861
+ key === "installed" && outdatedCount > 0 ? h("span", { className: "dsvm-reddot", title: lookup("installed.upgradeAll", { n: outdatedCount }) }) : null,
4862
+ // 底部操作区搬迁补偿(第二批):failed/warned 终态无人主动逛设置页,红点兜底可见性
4863
+ key === "settings" && opsAttention ? h("span", { className: "dsvm-reddot", title: lookup("settings.ops.reddot") }) : null
4731
4864
  )
4732
4865
  )
4733
4866
  ),
@@ -4756,20 +4889,23 @@ function MarketPanel({ onClose }) {
4756
4889
  h(
4757
4890
  "div",
4758
4891
  { className: "dshm-body" },
4759
- tab === "market" ? h(MarketTab, { notify, markets, onMutation: refreshViews, ops, favorites, profileKind: profile?.kind ?? null }) : null,
4892
+ tab === "market" ? h(MarketTab, { notify, markets, onMutation: refreshViews, ops, favorites, profileKind: profile?.kind ?? null, onOpenOps: openOps }) : null,
4760
4893
  tab === "installed" ? h(InstalledTab, { notify, installed, updates, onMutation: refreshViews, ops }) : null,
4761
- tab === "settings" ? h(SettingsTab, { notify, onRegistryChanged, onForceMarket: () => marketReloadAll(true) }) : null,
4762
- h(OperationsPanel, {
4763
- records: opRecords,
4764
- onClearFinished: () => {
4894
+ tab === "settings" ? h(SettingsTab, {
4895
+ notify,
4896
+ onRegistryChanged,
4897
+ onForceMarket: () => marketReloadAll(true),
4898
+ opsRecords: opRecords,
4899
+ onOpsClearFinished: () => {
4765
4900
  opsStore.clearFinished();
4766
4901
  syncOps();
4767
4902
  },
4768
- onRemove: (id) => {
4903
+ onOpsRemove: (id) => {
4769
4904
  opsStore.remove(id);
4770
4905
  syncOps();
4771
4906
  }
4772
- })
4907
+ }) : null
4908
+ // 操作记录已整体搬迁至设置 tab 末位通宽卡(0.9.x 布局重构·第二批):底部不再常驻
4773
4909
  ),
4774
4910
  toast ? h(
4775
4911
  "div",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.63",
3
+ "version": "0.9.65",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",