dsh-m 0.9.63 → 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 +18 -0
  2. package/lib/client.js +140 -66
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,15 @@ 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
+
11
20
  ### 0.9.63 修复:点在加载转圈上不再无动作——转圈显式接 onClick=onClose
12
21
 
13
22
  - **背景**:0.9.62 给灯箱加了走链转圈占位,但转圈是遮罩根的**子元素**,而 `backdropCloseHandlers`(0.9.27 防拖拽误关)只在 `mousedown` 与 `click` 都命中遮罩自身(`target === currentTarget`)时才关——点在 34px 转圈正中因此无动作。转圈显式接 `onClick: onClose`,与「点图即关」同语义:走链期间的任何一次点击都是有效的关闭逃生门。
@@ -466,6 +475,15 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
466
475
 
467
476
  ## English
468
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
+
469
487
  ### Fixed in 0.9.63 — clicking dead-center on the loading spinner no longer no-ops: spinner gets an explicit onClick=onClose
470
488
 
471
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.
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)}
@@ -3379,7 +3410,7 @@ var ZONE_TABS = [
3379
3410
  { id: "primary", labelKey: "zone.primary" },
3380
3411
  { id: "favorites", labelKey: "zone.favorites" }
3381
3412
  ];
3382
- function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind }) {
3413
+ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind, onOpenOps }) {
3383
3414
  const [zone, setZone] = useState("community");
3384
3415
  const market = zone === "favorites" ? null : markets[zone];
3385
3416
  const { data, loading, error, reload, query, updateQuery } = market || {};
@@ -3669,10 +3700,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
3669
3700
  title: lookup("op.panel.jump"),
3670
3701
  onClick: (e) => {
3671
3702
  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
- }
3703
+ if (typeof onOpenOps === "function") onOpenOps();
3676
3704
  }
3677
3705
  },
3678
3706
  lookup("op.status.running")
@@ -4045,7 +4073,7 @@ function regSourceLabel(data) {
4045
4073
  function configStatusLabel(status) {
4046
4074
  return lookup(`settings.status.${status || "loading"}` || "settings.status.loading");
4047
4075
  }
4048
- function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4076
+ function SettingsTab({ notify, onRegistryChanged, onForceMarket, opsRecords, onOpsClearFinished, onOpsRemove }) {
4049
4077
  const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
4050
4078
  const cfgState = useAsync(() => api("registry-config"), []);
4051
4079
  const [busy, setBusy] = useState(false);
@@ -4135,47 +4163,61 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4135
4163
  };
4136
4164
  const communityLive = communitySummary && (communitySummary.status === "ready" || communitySummary.status === "stale");
4137
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") };
4138
- 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(
4139
4193
  React.Fragment,
4140
4194
  null,
4141
- // ① 社区清单(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):左卡;清爽列表 + 发丝分隔线,开关为列表末行
4142
4216
  h(
4143
4217
  "div",
4144
4218
  { 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
- ),
4219
+ h("div", { className: "dshm-section-title" }, lookup("settings.community")),
4220
+ communityBody,
4179
4221
  communitySummary && Array.isArray(communitySummary.errors) && communitySummary.errors.length && communityOn !== false ? h("div", { className: "dshm-err" }, communitySummary.errors.join("\uFF1B")) : null
4180
4222
  ),
4181
4223
  // ② 精选清单(registry.json):自定义源整体替换主清单,仅影响市场「精选」区
@@ -4226,7 +4268,7 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4226
4268
  "div",
4227
4269
  { className: "dshm-kv", style: { marginTop: "4px" } },
4228
4270
  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"))),
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"))),
4230
4272
  h("span", { className: "k" }, lookup("settings.effective")),
4231
4273
  h("span", null, regSourceLabel(state), state && !state.isDefault ? h("span", { className: "dshm-badge info", style: { marginLeft: "6px" } }, lookup("badge.custom")) : null),
4232
4274
  h("span", { className: "k" }, lookup("settings.updated")),
@@ -4251,7 +4293,9 @@ function SettingsTab({ notify, onRegistryChanged, onForceMarket }) {
4251
4293
  h("span", { className: "dshm-links-sep" }, "\xB7"),
4252
4294
  h(ExtLink, { href: "https://www.npmjs.com/package/dsh-m" }, lookup("settings.about.npm"))
4253
4295
  )
4254
- )
4296
+ ),
4297
+ // ④ 操作记录:恒通宽卡(第二批搬迁),进行中常驻 + 已结束滚动区,空态 hint 导览
4298
+ h(OperationsCard, { records: opsRecords, onClearFinished: onOpsClearFinished, onRemove: onOpsRemove })
4255
4299
  );
4256
4300
  }
4257
4301
  function Section(title, sub, ...children) {
@@ -4421,10 +4465,9 @@ var OP_STATUS_CLS = {
4421
4465
  failed: "err",
4422
4466
  superseded: "sup"
4423
4467
  };
4424
- function OperationsPanel({ records, onClearFinished, onRemove }) {
4468
+ function OperationsCard({ records, onClearFinished, onRemove }) {
4425
4469
  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;
4470
+ const finished = records.filter((r) => active.indexOf(r) < 0).slice(-50);
4428
4471
  const clearable = records.some((r) => TERMINAL_CLEARABLE2.has(r.status));
4429
4472
  const row = (r) => h(
4430
4473
  "div",
@@ -4440,14 +4483,30 @@ function OperationsPanel({ records, onClearFinished, onRemove }) {
4440
4483
  );
4441
4484
  return h(
4442
4485
  "div",
4443
- { 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,
4444
4489
  active.length ? h("div", { className: "dsvm-opgroup" }, ...active.map(row)) : null,
4445
4490
  finished.length ? h(
4446
4491
  "div",
4447
- { className: "dsvm-opgroup done" },
4448
- ...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" },
4449
4507
  h("button", { className: "dshm-btn sm", disabled: !clearable, title: clearable ? void 0 : lookup("op.clear.none"), onClick: onClearFinished }, lookup("op.clear"))
4450
- ) : null
4508
+ ) : null,
4509
+ !active.length && !finished.length ? h("div", { className: "dshm-hint" }, lookup("settings.ops.empty")) : null
4451
4510
  );
4452
4511
  }
4453
4512
  var TABS = [
@@ -4567,6 +4626,16 @@ function MarketPanel({ onClose }) {
4567
4626
  const updates = useAsync(() => api("installedUpdates"), []);
4568
4627
  const [opRecords, setOpRecords] = useState(() => opsStore.list().map((r) => ({ ...r })));
4569
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");
4570
4639
  const runOp = useCallback(
4571
4640
  (kind, target, exec, meta = {}, reuseId) => (
4572
4641
  // session:true = 活会话标记(终审·新伤1):重挂载的 restore 跳过(泵仍拥有);persist 时剥离。
@@ -4727,7 +4796,9 @@ function MarketPanel({ onClose }) {
4727
4796
  },
4728
4797
  lookup(labelKey),
4729
4798
  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
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
4731
4802
  )
4732
4803
  )
4733
4804
  ),
@@ -4756,20 +4827,23 @@ function MarketPanel({ onClose }) {
4756
4827
  h(
4757
4828
  "div",
4758
4829
  { className: "dshm-body" },
4759
- 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,
4760
4831
  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: () => {
4832
+ tab === "settings" ? h(SettingsTab, {
4833
+ notify,
4834
+ onRegistryChanged,
4835
+ onForceMarket: () => marketReloadAll(true),
4836
+ opsRecords: opRecords,
4837
+ onOpsClearFinished: () => {
4765
4838
  opsStore.clearFinished();
4766
4839
  syncOps();
4767
4840
  },
4768
- onRemove: (id) => {
4841
+ onOpsRemove: (id) => {
4769
4842
  opsStore.remove(id);
4770
4843
  syncOps();
4771
4844
  }
4772
- })
4845
+ }) : null
4846
+ // 操作记录已整体搬迁至设置 tab 末位通宽卡(0.9.x 布局重构·第二批):底部不再常驻
4773
4847
  ),
4774
4848
  toast ? h(
4775
4849
  "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.64",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",