dsh-m 0.9.62 → 0.9.64

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 -2
  2. package/lib/client.js +144 -68
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,12 +8,26 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.64 优化:设置页双清单并排重排 + 操作记录整体搬迁设置 tab
12
+
13
+ - **设置页网格(mock 驱动终审)**:社区清单/精选清单两卡并排 **4:7**(社区卡只读五项 ~250px 足矣,宽度让给交互更多的精选卡);全屏态「关于」升入三卡行(`4fr 7fr 3fr`,不封顶——对齐市场「加列不加宽」哲学,设置页恰有三个卡片单元可用);视口 ≤760px 单列回落;关于卡常态通栏、操作记录恒通栏(`nth-child` 定位,与 `Section()` 签名解耦)。
14
+ - **社区卡清爽列表**:标题行回归纯标题(去 `from` 前缀);状态徽章降为绿/黄/红**小圆点**;行间发丝分隔线 + 数值右对齐等宽数字;「社区目录开关」入列表为末行(开关是目录属性,钉底反而脱离语境);关态 = 提示行 + 开关行(重开入口不丢)。中途方案「统计带」(白盒大数字)经实评否决——卡片中的卡片是视觉噪音。
15
+ - **精选卡减行**:说明行缩短(被删的「条目格式可参照下载的默认清单」与下载按钮 tooltip 重复,零信息损失);配置地址长 URL 单行省略 + `title` 悬浮全文。
16
+ - **操作记录搬迁(第二批)**:三 tab 共用的底部常驻区退役;`OperationsPanel` → `OperationsCard` 落位设置 tab 末位通宽卡——进行中组滚动区外常驻(进度不被滚走),已结束组进 220px 滚动区(展示上限 50 条防 DOM 无界,store/恢复校验不动),「清除已结束」钉底,空态 hint 卡常驻导览。
17
+ - **可见性补偿**:底部消失后失败/带警告终态无人主动发现 → 设置 tab 红点(复用 outdated 红点样式,清除/移除后随 syncOps 自灭);市场卡「进行中」徽章点击改道:切设置 tab + 滚动定位操作记录卡(文案「查看操作面板」→「查看操作记录」)。
18
+ - **验证**:构建 verify 通过、typecheck 零错误、全量 1283 项测试连续 4 轮全绿(首轮 1 例与改动无关的瞬时 flake 未复现);布局终稿经交互式 HTML mock 三宽度 × 三布局 × 多状态现场评审定稿(`docs/settings-layout-mock.html` 入库作设计记录)。
19
+
20
+ ### 0.9.63 修复:点在加载转圈上不再无动作——转圈显式接 onClick=onClose
21
+
22
+ - **背景**:0.9.62 给灯箱加了走链转圈占位,但转圈是遮罩根的**子元素**,而 `backdropCloseHandlers`(0.9.27 防拖拽误关)只在 `mousedown` 与 `click` 都命中遮罩自身(`target === currentTarget`)时才关——点在 34px 转圈正中因此无动作。转圈显式接 `onClick: onClose`,与「点图即关」同语义:走链期间的任何一次点击都是有效的关闭逃生门。
23
+ - **验证**:`client-lightbox.test.mjs` 转圈锚随签名更新(门控 + onClick 语义);全量测试/typecheck 照常。
24
+
11
25
  ### 0.9.62 修复:灯箱控件浅色模式下不可见——影院遮罩自持 + 控件深色玻璃芯片 + 加载转圈/淡入
12
26
 
13
27
  - **根因(2026-10-09 用户浅/深双模式实拍)**:dsh-skins ADR-0007 浮层毛玻璃并集按 `[role="dialog"][aria-modal="true"]` 命中灯箱根(灯箱根带 dialog 语义),浅色模式下 86% 暖白 tint + blur14 把影院式黑遮罩改成了浅色毛玻璃;而 ‹›/✕ 沿用基础 `.dsvm-btn` 白色半透配方(`rgba(255,255,255,.14)` 底 + 白字白边),浅底上整体隐形。深色模式走皮肤 `rgb(18,18,26)` 分支尚可读,故「浅色看不清、深色还行」。无皮肤的原生形态(黑遮罩恒在)无症状——这正是它漏网到 0.9.61 的原因。
14
28
  - **双侧修复**:① dsh-m 侧(本版):灯箱 `background` 加 `!important` 自持——「深色遮罩 + 浅色控件」是影院面的组件自有可读性不变量,不随颜色模式/皮肤翻转;marketplace 社区皮肤不可枚举,不变量必须组件自己兜底(`!important` 清单定长 4,回归门钉住,新增须显式登记)。② dsh-skins 侧:三皮肤 `[role="dialog"][aria-modal="true"]` 追加 `:not(.dsvm-lightbox)` 豁免(1.5.1,ADR-0007 amendment)——语义上「弹窗毛玻璃」本就不该染指图片影院。
15
29
  - **控件深色玻璃芯片**:‹›/✕ 从裸透明白改为与底部 pill 同族的深色芯片(slate-900 55% + 白描边 + blur8 + 投影)——深遮罩上靠描边/blur/投影出层次,浅遮罩上芯片本身就是对比面,任何颜色模式与皮肤下都可读;显式 `:hover` 提亮(特异性 0,3,0 压基础 `.dsvm-btn:hover` 0,2,0,免疫源顺序——评审 R1 同族教训)。
16
- - **加载指示与淡入(UX 补强)**:灯箱走链期间(weserv 慢速/8s 守卫换层/直连大图)此前是空黑黑洞,现转圈占位(`useImgChain` 暴露 `settled`;点转圈视为点遮罩可关——急躁关闭是合理逃生门);图片 onLoad 后 160ms 淡入换场;`prefers-reduced-motion` 下停用淡入、转圈降速。
30
+ - **加载指示与淡入(UX 补强)**:灯箱走链期间(weserv 慢速/8s 守卫换层/直连大图)此前是空黑黑洞,现转圈占位(`useImgChain` 暴露 `settled`);图片 onLoad 后 160ms 淡入换场;`prefers-reduced-motion` 下停用淡入、转圈降速。已知小瑕疵:转圈是遮罩的子元素,`backdropCloseHandlers` 防拖拽误关只认根元素——点在 34px 转圈正中无动作(点周边遮罩可关),0.9.63 补 `onClick` 收口。
17
31
  - **验证**:1283 项测试全绿、typecheck 零错误;`client-lightbox.test.mjs` 新增 6 项结构锚(遮罩 `!important` 定长清单/芯片规则与显式 hover/转圈门控/淡入类名/settled 暴露/reduced-motion),0.9.60 批次既有锚(portal/复合选择器/拖拽带/焦点还原/序号 key)全数保持。
18
32
 
19
33
  ### 0.9.61 修复:大陆浏览器拉不到插件截图/图标——图片加载链 weserv 优先双兜底
@@ -461,12 +475,26 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
461
475
 
462
476
  ## English
463
477
 
478
+ ### Changed in 0.9.64 — settings page: dual-catalog side-by-side layout + operations history moved into the settings tab
479
+
480
+ - **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.
481
+ - **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.
482
+ - **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.
483
+ - **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.
484
+ - **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 查看操作面板 → 查看操作记录).
485
+ - **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).
486
+
487
+ ### Fixed in 0.9.63 — clicking dead-center on the loading spinner no longer no-ops: spinner gets an explicit onClick=onClose
488
+
489
+ - **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.
490
+ - **Verification**: the spinner anchor in `client-lightbox.test.mjs` follows the new signature (gating + onClick semantics); full suite/typecheck as usual.
491
+
464
492
  ### Fixed in 0.9.62 — lightbox controls invisible in light color schemes: self-owned cinema backdrop + dark glass control chips + loading spinner/fade
465
493
 
466
494
  - **Root cause (user photos, both color schemes, 2026-10-09)**: the dsh-skins ADR-0007 overlay-frost union targets `[role="dialog"][aria-modal="true"]` - which matches the lightbox root (it carries dialog semantics). In light schemes the 86% warm-white tint + blur14 turned the cinema-black backdrop into light frosted glass, while ‹›/✕ reuse the base `.dsvm-btn` translucent-white recipe (`rgba(255,255,255,.14)` fill, white glyph/border) - invisible on a light surface. Dark schemes hit the skin's `rgb(18,18,26)` branch and stayed readable, hence "light mode unreadable, dark mode okay". Vanilla installs (backdrop always near-black) never showed it - which is how it slipped past 0.9.61.
467
495
  - **Two-sided fix**: ① dsh-m side (this release): the lightbox `background` is now `!important`-owned - "dark backdrop + light controls" is the component's own readability invariant for a cinema surface and must not flip with color schemes or skins; marketplace community skins are un-enumerable, so the component must defend itself (the `!important` inventory is pinned at a fixed length of 4 by the regression gate; additions must register explicitly). ② dsh-skins side: all three skins exempt `.dsvm-lightbox` via `:not(...)` (1.5.1, ADR-0007 amendment) - semantically a "dialog frost" never should have tinted an image theater.
468
496
  - **Dark glass control chips**: ‹›/✕ move from bare translucent white to a chip in the same family as the bottom pill (slate-900 55% + white border + blur8 + shadow) - on dark backdrops the border/blur/shadow provide edge definition, on light backdrops the chip itself is the contrast surface; readable under any scheme or skin. Explicit `:hover` brightening (specificity 0,3,0 beats base `.dsvm-btn:hover` 0,2,0 regardless of source order - the R1 family lesson).
469
- - **Loading indicator & fade (UX)**: while the image chain runs (slow weserv / 8s guard tier-swap / large direct original) the lightbox used to be a silent black void; it now shows a spinner (`useImgChain` exposes `settled`; clicking the spinner counts as a backdrop click - an impatient close is a legitimate escape hatch). The image fades in over 160ms on onLoad; under `prefers-reduced-motion` the fade is disabled and the spinner slows down.
497
+ - **Loading indicator & fade (UX)**: while the image chain runs (slow weserv / 8s guard tier-swap / large direct original) the lightbox used to be a silent black void; it now shows a spinner (`useImgChain` exposes `settled`). The image fades in over 160ms on onLoad; under `prefers-reduced-motion` the fade is disabled and the spinner slows down. Known nit: the spinner is a child of the backdrop and `backdropCloseHandlers` only honors the root element (drag-misfire guard), so a click dead-center on the 34px spinner does nothing (the surrounding backdrop still closes) - `onClick` added in 0.9.63.
470
498
  - **Verification**: 1283 tests green, typecheck clean; `client-lightbox.test.mjs` gains 6 structural anchors (backdrop `!important` fixed-length inventory / chip rules with explicit hover / spinner gating / fade class / settled exposure / reduced-motion); all 0.9.60-era anchors (portal / compound selectors / titlebar offset / focus restore / index keys) retained.
471
499
 
472
500
  ### Fixed in 0.9.61 — screenshots/icons unreachable from mainland browsers: weserv-first dual-fallback image chain
package/lib/client.js CHANGED
@@ -1705,7 +1705,7 @@ var ZH = {
1705
1705
  "op.superseded.note": "{target} \u5DF2\u8DF3\u8FC7\uFF08\u524D\u63D0\u5DF2\u4E0D\u6210\u7ACB\u6216\u5DF2\u624B\u52A8\u5904\u7406\uFF09",
1706
1706
  "op.cancelled": "\u7528\u6237\u653E\u5F03\u786E\u8BA4",
1707
1707
  "op.remove": "\u79FB\u9664\u8BB0\u5F55",
1708
- "op.panel.jump": "\u67E5\u770B\u64CD\u4F5C\u9762\u677F",
1708
+ "op.panel.jump": "\u67E5\u770B\u64CD\u4F5C\u8BB0\u5F55",
1709
1709
  "op.kind.install": "\u5B89\u88C5",
1710
1710
  "op.kind.upgrade": "\u5347\u7EA7",
1711
1711
  "op.kind.uninstall": "\u5378\u8F7D",
@@ -1801,7 +1801,7 @@ var ZH = {
1801
1801
  "src.custom.unavailable": "\u81EA\u5B9A\u4E49\u6E90\uFF08\u4E0D\u53EF\u7528\uFF09",
1802
1802
  "settings.address": "Registry \u5730\u5740",
1803
1803
  "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",
1804
+ "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
1805
  "settings.address.ph": "https://example.com/registry.json \u6216 /path/to/registry.json",
1806
1806
  "settings.configured": "\u914D\u7F6E\u5730\u5740",
1807
1807
  "settings.effective": "\u751F\u6548\u6765\u6E90",
@@ -1863,7 +1863,7 @@ var ZH = {
1863
1863
  "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
1864
  "detail.screenshots": "\u622A\u56FE",
1865
1865
  "installed.check.incomplete": "\u68C0\u67E5\u672A\u5B8C\u6210",
1866
- "settings.community": "\u793E\u533A\u6E05\u5355\uFF08from awesome-dsh-plugin\uFF09",
1866
+ "settings.community": "\u793E\u533A\u6E05\u5355\uFF08awesome-dsh-plugin\uFF09",
1867
1867
  "settings.community.toggle": "\u793E\u533A\u76EE\u5F55\u5F00\u5173",
1868
1868
  "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
1869
  "settings.community.on.ok": "\u793E\u533A\u76EE\u5F55\u5DF2\u5F00\u542F\uFF08\u5373\u65F6\u751F\u6548\uFF09",
@@ -1877,6 +1877,11 @@ var ZH = {
1877
1877
  "settings.community.count.v": "{n}\uFF08\u4E0A\u6E38 {up}\uFF09",
1878
1878
  "settings.community.displaced": "\u4E0E\u7CBE\u9009\u91CD\u590D",
1879
1879
  "settings.community.displaced.v": "{n} \u6761",
1880
+ "settings.ops": "\u64CD\u4F5C\u8BB0\u5F55",
1881
+ "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",
1882
+ "settings.ops.group.active": "\u8FDB\u884C\u4E2D",
1883
+ "settings.ops.group.done": "\u5DF2\u7ED3\u675F",
1884
+ "settings.ops.reddot": "\u5B58\u5728\u5931\u8D25\u6216\u5E26\u8B66\u544A\u7684\u64CD\u4F5C\u8BB0\u5F55",
1880
1885
  "registry.refreshed": "\u6536\u5F55\u6E05\u5355\u5DF2\u5F3A\u5236\u5237\u65B0",
1881
1886
  "notify.installed": "\u5DF2\u5B89\u88C5 {pkg}{version}",
1882
1887
  "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 +1991,7 @@ var EN = {
1986
1991
  "op.superseded.note": "{target} skipped (precondition gone or already handled)",
1987
1992
  "op.cancelled": "user cancelled",
1988
1993
  "op.remove": "Dismiss",
1989
- "op.panel.jump": "Open operations panel",
1994
+ "op.panel.jump": "Open operations card",
1990
1995
  "op.kind.install": "Install",
1991
1996
  "op.kind.upgrade": "Upgrade",
1992
1997
  "op.kind.uninstall": "Uninstall",
@@ -2082,7 +2087,7 @@ var EN = {
2082
2087
  "src.custom.unavailable": "Custom source (unavailable)",
2083
2088
  "settings.address": "Registry address",
2084
2089
  "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.",
2090
+ "settings.note.custom": "A custom source replaces the whole Curated registry. Leave empty for the built-in default.",
2086
2091
  "settings.address.ph": "https://example.com/registry.json or /path/to/registry.json",
2087
2092
  "settings.configured": "Configured address",
2088
2093
  "settings.effective": "Effective source",
@@ -2144,7 +2149,7 @@ var EN = {
2144
2149
  "guard.noforce": "Guard blocks have no force channel \u2014 repair manually per the basis above",
2145
2150
  "detail.screenshots": "Screenshots",
2146
2151
  "installed.check.incomplete": "Check incomplete",
2147
- "settings.community": "Community catalog (from awesome-dsh-plugin)",
2152
+ "settings.community": "Community catalog (awesome-dsh-plugin)",
2148
2153
  "settings.community.toggle": "Community catalog toggle",
2149
2154
  "settings.community.off": "Community catalog is off: the market shows only the Curated registry. Toggle back on anytime \u2014 it applies live.",
2150
2155
  "settings.community.on.ok": "Community catalog enabled (live)",
@@ -2158,6 +2163,11 @@ var EN = {
2158
2163
  "settings.community.count.v": "{n} (upstream {up})",
2159
2164
  "settings.community.displaced": "Displaced (duplicates)",
2160
2165
  "settings.community.displaced.v": "{n}",
2166
+ "settings.ops": "Operations",
2167
+ "settings.ops.empty": "No operations yet. Install, upgrade, uninstall and toggle history will appear here.",
2168
+ "settings.ops.group.active": "Active",
2169
+ "settings.ops.group.done": "Finished",
2170
+ "settings.ops.reddot": "Failed or warned operations exist",
2161
2171
  "registry.refreshed": "Registry force-refreshed",
2162
2172
  "notify.installed": "Installed {pkg}{version}",
2163
2173
  "notify.allowbuilds": " (note: this plugin ran build scripts, allowed by policy)",
@@ -2403,7 +2413,11 @@ var CSS = `
2403
2413
  .dshm-card:hover{border-color:var(--dsw-alias-interactive-bg-selected,#4f46e5)}
2404
2414
  .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
2415
  .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}
2416
+ .dsvm-ops-caption{font-size:11px;color:var(--dsw-alias-label-caption,#9ca3af);margin:2px 0 -3px}
2417
+ .dshm-ops-scroll{max-height:220px;overflow-y:auto;overscroll-behavior:contain}
2418
+ .dshm-ops-scroll::-webkit-scrollbar{width:8px}
2419
+ .dshm-ops-scroll::-webkit-scrollbar-thumb{background:rgba(127,127,127,.28);border-radius:4px}
2420
+ .dshm-ops-scroll::-webkit-scrollbar-thumb:hover{background:rgba(127,127,127,.45)}
2407
2421
  .dsvm-opgroup{display:flex;flex-direction:column;gap:3px}
2408
2422
  .dsvm-opgroup.done{opacity:.75}
2409
2423
  .dsvm-oprow{display:flex;align-items:center;gap:8px;min-height:22px}
@@ -2447,6 +2461,23 @@ button.dshm-badge:hover{filter:brightness(.95)}
2447
2461
  .dshm-row{display:flex;align-items:center;gap:8px}
2448
2462
  .dshm-kv{display:grid;grid-template-columns:96px 1fr;gap:6px 10px;font-size:12px;align-items:baseline}
2449
2463
  .dshm-kv .k{color:var(--dsw-alias-label-caption,#6b7280);font-size:11px}
2464
+ .dshm-settings-grid{display:grid;gap:10px;grid-template-columns:4fr 7fr;min-width:0}
2465
+ .dshm-settings-grid>.dshm-section{margin:0;min-width:0}
2466
+ .dshm-settings-grid>.dshm-section:nth-child(3){grid-column:1/-1}
2467
+ .dshm-settings-grid>.dshm-ops-card{grid-column:1/-1}
2468
+ .dshm-panel.full .dshm-settings-grid{grid-template-columns:4fr 7fr 3fr}
2469
+ .dshm-panel.full .dshm-settings-grid>.dshm-section:nth-child(3){grid-column:auto}
2470
+ @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}}
2471
+ .dshm-kvlines{display:flex;flex-direction:column}
2472
+ .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}
2473
+ .dshm-kvline-row:last-child{border-bottom:0}
2474
+ .dshm-kvline-row .rl{font-size:11px;color:var(--dsw-alias-label-caption,#6b7280);flex:none}
2475
+ .dshm-kvline-row .rv{font-size:12px;text-align:right;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;min-width:0}
2476
+ .dshm-kvline-row .dshm-switch{margin-left:0;align-self:center}
2477
+ .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)}
2478
+ .dshm-dot.warn{background:var(--dsw-alias-state-warn-primary,#b45309)}
2479
+ .dshm-dot.err{background:var(--dsw-alias-state-error-primary,#b91c1c)}
2480
+ .dshm-code-1line{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
2450
2481
  .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
2482
  .dshm-section:last-child{margin-bottom:2px}
2452
2483
  .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)}
@@ -3170,8 +3201,10 @@ function Lightbox({ shots, index, onNav, onClose }) {
3170
3201
  h("button", { className: "dsvm-btn", onClick: chain.retry }, lookup("lb.retry")),
3171
3202
  h(ExtLink, { href: shots[index] }, lookup("lb.open"))
3172
3203
  ) : [
3173
- // 0.9.62 走链期间转圈占位(settled 即卸载;点它视为点遮罩可关——急躁关闭是合理逃生门)
3174
- !chain.settled && chain.src ? h("div", { className: "dsvm-lbspin", "aria-hidden": "true" }) : null,
3204
+ // 0.9.62 走链期间转圈占位(settled 即卸载);0.9.63 onClick=onClose——
3205
+ // backdropCloseHandlers 仅 target===根 时关(防拖拽误关),转圈是子元素,
3206
+ // 不补这条则点在 34px 转圈上无动作;急躁关闭是合理逃生门,与点图即关同语义。
3207
+ !chain.settled && chain.src ? h("div", { className: "dsvm-lbspin", "aria-hidden": "true", onClick: onClose }) : null,
3175
3208
  h("img", { className: `dsvm-lbimg${chain.settled ? " dsvm-lbimg-on" : ""}`, src: chain.src, alt: "", onClick: onClose, referrerPolicy: "no-referrer", decoding: "async", onError: chain.onError, onLoad: chain.onLoad })
3176
3209
  ],
3177
3210
  h("button", { ref: closeRef, className: "dsvm-btn dsvm-lbclose", "aria-label": lookup("common.close"), title: lookup("common.close"), onClick: onClose }, h(XIcon)),
@@ -3377,7 +3410,7 @@ var ZONE_TABS = [
3377
3410
  { id: "primary", labelKey: "zone.primary" },
3378
3411
  { id: "favorites", labelKey: "zone.favorites" }
3379
3412
  ];
3380
- function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind }) {
3413
+ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind, onOpenOps }) {
3381
3414
  const [zone, setZone] = useState("community");
3382
3415
  const market = zone === "favorites" ? null : markets[zone];
3383
3416
  const { data, loading, error, reload, query, updateQuery } = market || {};
@@ -3667,10 +3700,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
3667
3700
  title: lookup("op.panel.jump"),
3668
3701
  onClick: (e) => {
3669
3702
  e.stopPropagation();
3670
- if (typeof document !== "undefined") {
3671
- const el = document.querySelector(".dsvm-ops");
3672
- if (el && el.scrollIntoView) el.scrollIntoView({ block: "center", behavior: "smooth" });
3673
- }
3703
+ if (typeof onOpenOps === "function") onOpenOps();
3674
3704
  }
3675
3705
  },
3676
3706
  lookup("op.status.running")
@@ -4043,7 +4073,7 @@ function regSourceLabel(data) {
4043
4073
  function configStatusLabel(status) {
4044
4074
  return lookup(`settings.status.${status || "loading"}` || "settings.status.loading");
4045
4075
  }
4046
- function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4076
+ function SettingsTab({ notify, onRegistryChanged, onForceMarket, opsRecords, onOpsClearFinished, onOpsRemove }) {
4047
4077
  const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
4048
4078
  const cfgState = useAsync(() => api("registry-config"), []);
4049
4079
  const [busy, setBusy] = useState(false);
@@ -4133,47 +4163,61 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4133
4163
  };
4134
4164
  const communityLive = communitySummary && (communitySummary.status === "ready" || communitySummary.status === "stale");
4135
4165
  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") };
4136
- return h(
4166
+ const kvlineRow = (labelKey, valueNode) => h(
4167
+ "div",
4168
+ { className: "dshm-kvline-row" },
4169
+ h("span", { className: "rl" }, lookup(labelKey)),
4170
+ h("span", { className: "rv" }, valueNode)
4171
+ );
4172
+ const communitySwitch = h(
4173
+ "label",
4174
+ {
4175
+ className: `dshm-switch${communityOn ? " on" : ""}${communityBusy || communityOn === null ? " locked" : ""}`,
4176
+ title: lookup("settings.community.toggle")
4177
+ },
4178
+ h("input", {
4179
+ type: "checkbox",
4180
+ checked: communityOn === true,
4181
+ disabled: communityBusy || communityOn === null,
4182
+ onChange: (e) => applyCommunity(e.target.checked)
4183
+ }),
4184
+ h("span", { className: "dshm-switch-slider" })
4185
+ );
4186
+ const communitySwitchRow = h(
4187
+ "div",
4188
+ { className: "dshm-kvline-row" },
4189
+ h("span", { className: "rl" }, lookup("settings.community.toggle")),
4190
+ communitySwitch
4191
+ );
4192
+ const communityBody = communityOn === false ? h(
4137
4193
  React.Fragment,
4138
4194
  null,
4139
- // ① 社区清单(from awesome-dsh-plugin):标题行右侧开关;开态紧凑 kv,关态单行说明
4195
+ h("div", { className: "dshm-hint" }, lookup("settings.community.off")),
4196
+ communitySwitchRow
4197
+ ) : h(
4198
+ "div",
4199
+ { className: "dshm-kvlines" },
4200
+ kvlineRow("settings.community.status", h(
4201
+ "span",
4202
+ null,
4203
+ h("span", { className: `dshm-dot${communityStatusBadge.cls === "ok" ? "" : ` ${communityStatusBadge.cls}`}` }),
4204
+ communityStatusBadge.label
4205
+ )),
4206
+ kvlineRow("settings.community.version", communityLive ? communitySummary.version || "\u2014" : "\u2014"),
4207
+ kvlineRow("settings.community.route", communityLive ? communitySummary.route || "\u2014" : "\u2014"),
4208
+ kvlineRow("settings.community.count", communityLive ? lookup("settings.community.count.v", { n: communitySummary.acceptedCount ?? 0, up: communitySummary.upstreamCount ?? "\u2014" }) : "\u2014"),
4209
+ kvlineRow("settings.community.displaced", communityLive ? lookup("settings.community.displaced.v", { n: communitySummary.displaced ?? 0 }) : "\u2014"),
4210
+ communitySwitchRow
4211
+ );
4212
+ return h(
4213
+ "div",
4214
+ { className: "dshm-settings-grid" },
4215
+ // ① 社区清单(awesome-dsh-plugin):左卡;清爽列表 + 发丝分隔线,开关为列表末行
4140
4216
  h(
4141
4217
  "div",
4142
4218
  { className: "dshm-section" },
4143
- h(
4144
- "div",
4145
- { className: "dshm-section-title" },
4146
- lookup("settings.community"),
4147
- h("span", { className: "dshm-spacer" }),
4148
- h(
4149
- "label",
4150
- {
4151
- className: `dshm-switch${communityOn ? " on" : ""}${communityBusy || communityOn === null ? " locked" : ""}`,
4152
- title: lookup("settings.community.toggle")
4153
- },
4154
- h("input", {
4155
- type: "checkbox",
4156
- checked: communityOn === true,
4157
- disabled: communityBusy || communityOn === null,
4158
- onChange: (e) => applyCommunity(e.target.checked)
4159
- }),
4160
- h("span", { className: "dshm-switch-slider" })
4161
- )
4162
- ),
4163
- communityOn === false ? h("div", { className: "dshm-hint" }, lookup("settings.community.off")) : h(
4164
- "div",
4165
- { className: "dshm-kv" },
4166
- h("span", { className: "k" }, lookup("settings.community.status")),
4167
- h("span", null, h("span", { className: `dshm-badge ${communityStatusBadge.cls}` }, communityStatusBadge.label)),
4168
- h("span", { className: "k" }, lookup("settings.community.version")),
4169
- h("span", null, communityLive ? communitySummary.version || "\u2014" : "\u2014"),
4170
- h("span", { className: "k" }, lookup("settings.community.route")),
4171
- h("span", null, communityLive ? communitySummary.route || "\u2014" : "\u2014"),
4172
- h("span", { className: "k" }, lookup("settings.community.count")),
4173
- h("span", null, communityLive ? lookup("settings.community.count.v", { n: communitySummary.acceptedCount ?? 0, up: communitySummary.upstreamCount ?? "\u2014" }) : "\u2014"),
4174
- h("span", { className: "k" }, lookup("settings.community.displaced")),
4175
- h("span", null, communityLive ? lookup("settings.community.displaced.v", { n: communitySummary.displaced ?? 0 }) : "\u2014")
4176
- ),
4219
+ h("div", { className: "dshm-section-title" }, lookup("settings.community")),
4220
+ communityBody,
4177
4221
  communitySummary && Array.isArray(communitySummary.errors) && communitySummary.errors.length && communityOn !== false ? h("div", { className: "dshm-err" }, communitySummary.errors.join("\uFF1B")) : null
4178
4222
  ),
4179
4223
  // ② 精选清单(registry.json):自定义源整体替换主清单,仅影响市场「精选」区
@@ -4224,7 +4268,7 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4224
4268
  "div",
4225
4269
  { className: "dshm-kv", style: { marginTop: "4px" } },
4226
4270
  h("span", { className: "k" }, lookup("settings.configured")),
4227
- h("span", null, snap.registryUrl ? h("span", { className: "dshm-code" }, snap.registryUrl) : h("span", { className: "dshm-hint" }, lookup("settings.address.default"))),
4271
+ 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"))),
4228
4272
  h("span", { className: "k" }, lookup("settings.effective")),
4229
4273
  h("span", null, regSourceLabel(state), state && !state.isDefault ? h("span", { className: "dshm-badge info", style: { marginLeft: "6px" } }, lookup("badge.custom")) : null),
4230
4274
  h("span", { className: "k" }, lookup("settings.updated")),
@@ -4249,7 +4293,9 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4249
4293
  h("span", { className: "dshm-links-sep" }, "\xB7"),
4250
4294
  h(ExtLink, { href: "https://www.npmjs.com/package/dsh-m" }, lookup("settings.about.npm"))
4251
4295
  )
4252
- )
4296
+ ),
4297
+ // ④ 操作记录:恒通宽卡(第二批搬迁),进行中常驻 + 已结束滚动区,空态 hint 导览
4298
+ h(OperationsCard, { records: opsRecords, onClearFinished: onOpsClearFinished, onRemove: onOpsRemove })
4253
4299
  );
4254
4300
  }
4255
4301
  function Section(title, sub, ...children) {
@@ -4419,10 +4465,9 @@ var OP_STATUS_CLS = {
4419
4465
  failed: "err",
4420
4466
  superseded: "sup"
4421
4467
  };
4422
- function OperationsPanel({ records, onClearFinished, onRemove }) {
4468
+ function OperationsCard({ records, onClearFinished, onRemove }) {
4423
4469
  const active = records.filter((r) => r.status === "queued" || r.status === "running" || r.status === "input");
4424
- const finished = records.filter((r) => active.indexOf(r) < 0).slice(-6);
4425
- if (!active.length && !finished.length) return null;
4470
+ const finished = records.filter((r) => active.indexOf(r) < 0).slice(-50);
4426
4471
  const clearable = records.some((r) => TERMINAL_CLEARABLE2.has(r.status));
4427
4472
  const row = (r) => h(
4428
4473
  "div",
@@ -4438,14 +4483,30 @@ function OperationsPanel({ records, onClearFinished, onRemove }) {
4438
4483
  );
4439
4484
  return h(
4440
4485
  "div",
4441
- { className: "dsvm-ops" },
4486
+ { className: "dshm-section dshm-ops-card" },
4487
+ h("div", { className: "dshm-section-title" }, lookup("settings.ops")),
4488
+ active.length && finished.length ? h("div", { className: "dsvm-ops-caption" }, lookup("settings.ops.group.active")) : null,
4442
4489
  active.length ? h("div", { className: "dsvm-opgroup" }, ...active.map(row)) : null,
4443
4490
  finished.length ? h(
4444
4491
  "div",
4445
- { className: "dsvm-opgroup done" },
4446
- ...finished.map(row),
4492
+ { className: "dsvm-ops-caption", style: { marginTop: active.length ? "7px" : "0" } },
4493
+ lookup("settings.ops.group.done")
4494
+ ) : null,
4495
+ finished.length ? h(
4496
+ "div",
4497
+ { className: "dshm-ops-scroll" },
4498
+ h(
4499
+ "div",
4500
+ { className: "dsvm-opgroup done" },
4501
+ ...finished.map(row)
4502
+ )
4503
+ ) : null,
4504
+ finished.length ? h(
4505
+ "div",
4506
+ { className: "dshm-actions" },
4447
4507
  h("button", { className: "dshm-btn sm", disabled: !clearable, title: clearable ? void 0 : lookup("op.clear.none"), onClick: onClearFinished }, lookup("op.clear"))
4448
- ) : null
4508
+ ) : null,
4509
+ !active.length && !finished.length ? h("div", { className: "dshm-hint" }, lookup("settings.ops.empty")) : null
4449
4510
  );
4450
4511
  }
4451
4512
  var TABS = [
@@ -4565,6 +4626,16 @@ function MarketPanel({ onClose }) {
4565
4626
  const updates = useAsync(() => api("installedUpdates"), []);
4566
4627
  const [opRecords, setOpRecords] = useState(() => opsStore.list().map((r) => ({ ...r })));
4567
4628
  const syncOps = useCallback(() => setOpRecords(opsStore.list().map((r) => ({ ...r }))), []);
4629
+ const openOps = useCallback(() => {
4630
+ setTab("settings");
4631
+ if (typeof document !== "undefined") {
4632
+ setTimeout(() => {
4633
+ const el = document.querySelector(".dshm-ops-card");
4634
+ if (el && el.scrollIntoView) el.scrollIntoView({ block: "center", behavior: "smooth" });
4635
+ }, 60);
4636
+ }
4637
+ }, [setTab]);
4638
+ const opsAttention = opRecords.some((r) => r.status === "failed" || r.status === "warned");
4568
4639
  const runOp = useCallback(
4569
4640
  (kind, target, exec, meta = {}, reuseId) => (
4570
4641
  // session:true = 活会话标记(终审·新伤1):重挂载的 restore 跳过(泵仍拥有);persist 时剥离。
@@ -4725,7 +4796,9 @@ function MarketPanel({ onClose }) {
4725
4796
  },
4726
4797
  lookup(labelKey),
4727
4798
  countKey && counts[countKey] != null ? h("span", { className: "dshm-count" }, String(counts[countKey])) : null,
4728
- key === "installed" && outdatedCount > 0 ? h("span", { className: "dsvm-reddot", title: lookup("installed.upgradeAll", { n: outdatedCount }) }) : null
4799
+ key === "installed" && outdatedCount > 0 ? h("span", { className: "dsvm-reddot", title: lookup("installed.upgradeAll", { n: outdatedCount }) }) : null,
4800
+ // 底部操作区搬迁补偿(第二批):failed/warned 终态无人主动逛设置页,红点兜底可见性
4801
+ key === "settings" && opsAttention ? h("span", { className: "dsvm-reddot", title: lookup("settings.ops.reddot") }) : null
4729
4802
  )
4730
4803
  )
4731
4804
  ),
@@ -4754,20 +4827,23 @@ function MarketPanel({ onClose }) {
4754
4827
  h(
4755
4828
  "div",
4756
4829
  { className: "dshm-body" },
4757
- tab === "market" ? h(MarketTab, { notify, markets, onMutation: refreshViews, ops, favorites, profileKind: profile?.kind ?? null }) : null,
4830
+ tab === "market" ? h(MarketTab, { notify, markets, onMutation: refreshViews, ops, favorites, profileKind: profile?.kind ?? null, onOpenOps: openOps }) : null,
4758
4831
  tab === "installed" ? h(InstalledTab, { notify, installed, updates, onMutation: refreshViews, ops }) : null,
4759
- tab === "settings" ? h(SettingsTab, { notify, onRegistryChanged, onForceMarket: () => marketReloadAll(true) }) : null,
4760
- h(OperationsPanel, {
4761
- records: opRecords,
4762
- onClearFinished: () => {
4832
+ tab === "settings" ? h(SettingsTab, {
4833
+ notify,
4834
+ onRegistryChanged,
4835
+ onForceMarket: () => marketReloadAll(true),
4836
+ opsRecords: opRecords,
4837
+ onOpsClearFinished: () => {
4763
4838
  opsStore.clearFinished();
4764
4839
  syncOps();
4765
4840
  },
4766
- onRemove: (id) => {
4841
+ onOpsRemove: (id) => {
4767
4842
  opsStore.remove(id);
4768
4843
  syncOps();
4769
4844
  }
4770
- })
4845
+ }) : null
4846
+ // 操作记录已整体搬迁至设置 tab 末位通宽卡(0.9.x 布局重构·第二批):底部不再常驻
4771
4847
  ),
4772
4848
  toast ? h(
4773
4849
  "div",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.62",
3
+ "version": "0.9.64",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",