dsh-m 0.9.26 → 0.9.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.28 变更:页大小档位去上游化——32/64/96 取代 24/48/96,默认 32
12
+
13
+ - **动机**:24/48/96 是复刻 dsh-market 筛选面板(0.7.2)时带过来的上游血统数字。甄别后发现 96 早已被 dsh-m 内化为核心参数(`WITH_LATEST_MAX` 探测上限、精选区默认页、fast-open 快照判定基准),真正的上游痕迹只有 24——「砍 24、保 96」即去上游化与兼容性的交集。
14
+ - **新档位**:`MARKET_PAGE_SIZES = [32, 64, 96]`(等差 +32,读序顺)、社区区默认 `DEFAULT_PAGE_SIZE = 32`(首页更满,仍低于 0.6.x 历史默认 50 的探测负载,Q46 预算姿态不变);精选区默认 96 与 core clamp 1..96 不动。
15
+ - **联动**:host-api GUI 通道兜底默认 24 → 32(三端一致,不留暗默认);探测预算注释同步(market.ts / host-api.ts);README×2 与 DESIGN.md 措辞同步;GLOSSARY「精选区」词条按代码实态锐化(单页无分页 → 常态单页直出 + 超限降级分页);测试断言与快照 fixture 同步(偏离档位断言改用组内值 64,覆盖「档位内但非默认仍拒写/判假」)。
16
+ - **已知一次性影响**:升级后社区区旧快照(limit=24)不再命中 fast-open 判定,首次打开市场多一次正常请求,快照按新默认重建后自愈;不加 legacy 兼容分支。
17
+ - **验证**:`npm run build` 成功;全量 1030 pass / 0 fail(8 skipped)。
18
+
19
+ ### 0.9.27 变更:修复遮罩拖选误关——面板内按下、拖出释放不再关面板
20
+
21
+ - **问题(装机实测 2026-10-03)**:在市场搜索框内左键按下向左拖选(越过面板边界)释放,整个面板被关闭。非浏览器鼠标手势——面板遮罩是裸 `onClick: onClose`,而在面板内容里按下、拖到遮罩上释放时,浏览器把 `click` 派发到按下/释放目标的公共祖先(恰是遮罩),被误判为「点遮罩关面板」。详情 Modal、截图灯箱、兼容确认弹层同属该缺陷类。
22
+ - **修复**:新增纯函数 `backdropCloseHandlers`(src/client/backdrop.js)——仅当 **mousedown 与 click 都落在遮罩自身**时才关闭(其余组合一律不关,click 后按位状态复位);四处遮罩(主面板/详情/灯箱/兼容弹层)统一切换。点遮罩关闭、Esc 关闭、✕ 按钮行为不变。
23
+ - **验证**:新增 `tests/client-backdrop.test.mjs`(关闭/两类拖选不关/状态复位/畸形输入安全,5 组断言);全量 1030 pass / 0 fail。
24
+
11
25
  ### 0.9.26 变更:跨区搜索精选稳定前置——摘要行计数与首页所见一致
12
26
 
13
27
  - **问题(装机实测 2026-10-03)**:搜索「sidebar」摘要行报「⭐ 精选 3 · 社区 315」,首页精选段只见 1 条。非重复计算——`sourceCounts` 不读分类计数(`alsoCategories` 无涉),3 条为真实精选命中;错位根因是摘要行报全局命中数,而相关性排序叠加社区区默认 downloads 降序 tie-break 把弱命中精选压进后页。
@@ -269,6 +283,20 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
269
283
 
270
284
  ## English
271
285
 
286
+ ### Changed in 0.9.28 — page-size set de-upstreamed: 32/64/96 replaces 24/48/96, default 32
287
+
288
+ - **Motivation**: 24/48/96 came bundled with the dsh-market filter-panel replica (0.7.2). On inspection, 96 has long been internalized as a dsh-m core parameter (`WITH_LATEST_MAX` probe cap, primary-zone default page, fast-open snapshot baseline); the only real upstream trace is 24 — "drop 24, keep 96" is exactly the intersection of de-upstreaming and compatibility.
289
+ - **New set**: `MARKET_PAGE_SIZES = [32, 64, 96]` (arithmetic +32), community default `DEFAULT_PAGE_SIZE = 32` (fuller first page, still below the 0.6.x default of 50 in probe load; Q46 posture unchanged); primary default 96 and the core clamp 1..96 untouched.
290
+ - **Ripple**: host-api GUI fallback default 24 → 32 (three ends consistent, no hidden default); probe-budget comments updated (market.ts / host-api.ts); README×2 and DESIGN.md wording synced; GLOSSARY "Primary Zone" entry sharpened to match the code (single-page-no-pager → normally single page, degraded to the community pager beyond capacity); test assertions and snapshot fixtures synced (off-default assertions now use in-set value 64, covering "in set but not default is still rejected/false").
291
+ - **Known one-time impact**: after upgrading, a community snapshot with limit=24 no longer matches the fast-open check — the first market open makes one extra normal request, then the snapshot rebuilds on the new default; no legacy-compat branch added.
292
+ - **Verification**: `npm run build` clean; full suite 1030 pass / 0 fail (8 skipped).
293
+
294
+ ### Changed in 0.9.27 — fix accidental drag-out close: press inside, release on backdrop no longer closes the panel
295
+
296
+ - **Problem (observed on install, 2026-10-03)**: pressing inside the market search box and dragging left past the panel edge closed the whole panel. Not a browser mouse gesture — the panel backdrop used a bare `onClick: onClose`; when a press inside the panel content drags onto the backdrop and releases, the browser dispatches `click` to the common ancestor of the press/release targets (the backdrop itself), misread as "clicked the backdrop". The detail modal, screenshot lightbox, and compat dialog shared the same defect class.
297
+ - **Fix**: new pure helper `backdropCloseHandlers` (src/client/backdrop.js) — close only when **both mousedown and click land on the backdrop itself** (all other combinations are ignored; the pressed state resets after each click); all four overlays (main panel/detail/lightbox/compat dialog) switched over. Backdrop-click close, Esc, and the ✕ button behave exactly as before.
298
+ - **Verification**: new `tests/client-backdrop.test.mjs` (close / two drag-out scenarios ignored / state reset / malformed input safe — 5 assertions); full suite 1030 pass / 0 fail.
299
+
272
300
  ### Changed in 0.9.26 — curated hits first in cross-zone search: summary counts now match page one
273
301
 
274
302
  - **Problem (observed on install, 2026-10-03)**: searching "sidebar" showed "⭐ Curated 3 · Community 315" but only one curated card on the first page. Not double counting — `sourceCounts` never reads category counts (`alsoCategories` is unrelated) and all 3 were genuine curated hits. The mismatch: the summary line reports global hit counts while the relevance ranking plus the community zone's default downloads-desc tie-break pushed weakly-matching curated entries onto later pages.
package/README.en.md CHANGED
@@ -172,7 +172,7 @@ Desktop (the `desktop` profile) supports browsing the market, **installing new p
172
172
  No. Only the package reference in the profile is removed (live UI disabled first), and suspected leftover paths are reported to you.
173
173
 
174
174
  **3. Will a custom registry slow the market down?**
175
- The market list is server-paginated (24/48/96 per page); even a 1,000-entry registry queries latest versions for the current page only, so browsing stays fast.
175
+ The market list is server-paginated (32/64/96 per page); even a 1,000-entry registry queries latest versions for the current page only, so browsing stays fast.
176
176
 
177
177
  **4. What if my custom source goes down?**
178
178
  dsh-m serves its last successful cache for that source (the Settings tab's effective-source row labels it honestly); with no cache at all the market reports the listing unavailable while installed plugins stay manageable. Fix the address or restore the default anytime.
package/README.md CHANGED
@@ -172,7 +172,7 @@ Desktop(desktop profile)支持浏览市场、**安装新包**和插件开关
172
172
  不会。只移除 profile 中的包引用(卸载前先下线运行中的界面),并把疑似残留路径报告给你。
173
173
 
174
174
  **3. 自定义清单会让市场变慢吗?**
175
- 市场列表是服务端分页(每页 24/48/96 可选),1,000 条清单第一页也只查询当前页的最新版本,浏览仍然流畅。
175
+ 市场列表是服务端分页(每页 32/64/96 可选),1,000 条清单第一页也只查询当前页的最新版本,浏览仍然流畅。
176
176
 
177
177
  **4. 自定义源挂了怎么办?**
178
178
  优先使用该源最近一次成功的缓存(设置页「生效来源」会如实标注);完全没有缓存时市场会提示清单不可用,已安装插件仍可正常管理。修正地址或恢复默认即可。
package/docs/DESIGN.md CHANGED
@@ -86,7 +86,7 @@
86
86
  - 分类 chips:两行折叠 + 实测裁剪(渲染后逐 chip 测量可容纳数)+ 收起时激活分类置前 + 滚动吸顶自动收缩(不改写用户开合选择);分类标签**单一事实源**(core 提供标签表,杀掉客户端内嵌副本)。
87
87
  - 排序:`downloads | stars | added × asc/desc`,默认 `downloads-desc`;**无下载量(github-only)≠ 0 下载**(永远排在有真实计数条目之后,彼此按 stars 排);时间窗过滤明确不做(`added` 排序覆盖「找最新」诉求)。
88
88
  - 卡片 byline:`owner / downloads / ★stars`(旁路数据已透传,Tooltip 给精确数);`deprecated` 徽章 + 详情内 replacement 替代链接(适配层补透传原生字段)。
89
- - 分页:页码窗口化(`1 … n-1 n n+1 … total`,≤7 页全显)+ 页大小 24/48/96 + 筛选变化重置页 1 + 翻页回顶;仅 prev/next 的旧分页退役。
89
+ - 分页:页码窗口化(`1 … n-1 n n+1 … total`,≤7 页全显)+ 页大小 32/64/96 + 筛选变化重置页 1 + 翻页回顶;仅 prev/next 的旧分页退役。
90
90
  - 搜索:相关性加权管线——NFKC 归一化 + 中西文边界插空格 + 字段权重(name/npm > owner > 描述 > 分类)+ 命中类型加分 + 按条目缓存归一化结果;输入 250ms debounce + IME composition 全程处理 + draft/已提交 query 分离。**0.9.25 起搜索跨区全局**(两分区搜索框同语义):摘要行报两分区精确命中数(`sourceCounts`),结果页内按精选段置顶分组,分类 chips 搜索态隐藏、「筛选」按钮随摘要行保留(页大小组)。**0.9.26 补充**:GUI 跨区搜索通道精选命中稳定前置(`curatedFirst`,分区内相关序不变;tools/CLI 不传,三端同序不变)——摘要行全局计数与首页所见一致,弱命中精选不被 downloads tie-break 埋进后页。
91
91
  - 描述 5 行钳制(真实溢出才显示展开钮);**数据层双语**——描述按 UI 语言取 zh/en(数据现成),UI 完整双语字典列 backlog。
92
92
  - **详情 = Modal 且为卡片超集**(「detail 显示少于摘要就是倒退」):byline / 分类 / 收录日期 / 下载量窗口三要素(计数 + 窗口 + 核对时间)/ 描述全文 / 截图灯箱(←→/Esc、**禁自动轮播**)/ 能力披露 + 红线(**默认收起**)/ 安装命令折叠行(0.9.14 起按上下文显隐:desktop 上下文与已安装条目整行隐藏——命令两来源恒为 `--profile web` 语义,desktop 装机走弹窗内安装按钮)/ deprecated 替代链接;精选条目超集另加 verified 与 tags。**安装确认走 Modal**。安装信息就地显示(0.9.15):进行中进度行与终态结果行挂 Modal 内、底层同源进度行让位——展示派生自全局操作记录,§2.6「状态不挂卡片」所有权模型不变。卡片瘦身:能力披露、截图、安装命令全部迁出卡片(Q44 精神「只在详情层、默认收起」不变,载体升级)。
@@ -163,7 +163,7 @@ npm 安装 / GitHub 安装 / 升级 / 自升级 / 卸载是**同一个事务模
163
163
 
164
164
  3 个视图,**中文优先**,跟随 DSH Web 深色主题(0.7.0:卡片展开式详情已由 Modal 取代):
165
165
 
166
- 1. **市场页**(默认):**分区制**(0.7.0 §2.6)——社区/精选/收藏三分区,各分区独立分类/排序/分页状态(**搜索自 0.9.25 起为跨区全局**,社区 + 精选一并命中);搜索/分类/排序为**服务端处理**(Host 强制 `withLatest=true`、limit clamp 1..96 缺省 24,探测仅当前页);`MarketPanel` 是市场/已装数据唯一 owner(请求 generation + AbortController 丢弃旧响应);安装/卸载/升级完成后通过统一协调器同时刷新市场与已装快照,不要求关闭并重新打开面板;页码窗口化分页(24/48/96)+ 默认/自定义/缓存/不可用短提示(不含本地路径;0.5.0 的「超过 200 条性能提示」随分区退役);详情 Modal 保留 README markdown 预览与 npm/GitHub 官方外链。
166
+ 1. **市场页**(默认):**分区制**(0.7.0 §2.6)——社区/精选/收藏三分区,各分区独立分类/排序/分页状态(**搜索自 0.9.25 起为跨区全局**,社区 + 精选一并命中);搜索/分类/排序为**服务端处理**(Host 强制 `withLatest=true`、limit clamp 1..96 缺省 32,探测仅当前页);`MarketPanel` 是市场/已装数据唯一 owner(请求 generation + AbortController 丢弃旧响应);安装/卸载/升级完成后通过统一协调器同时刷新市场与已装快照,不要求关闭并重新打开面板;页码窗口化分页(32/64/96)+ 默认/自定义/缓存/不可用短提示(不含本地路径;0.5.0 的「超过 200 条性能提示」随分区退役);详情 Modal 保留 README markdown 预览与 npm/GitHub 官方外链。
167
167
  2. **已装页**:profile 实际安装列表,标注来源(市场/非市场/未知);registry 不可用时仍列出已装并标记;「可升级 → x.y.z」徽标 +「升级」;「卸载」。两段加载(ADR-0008):列表不等待探测(`probe:false`),更新提示由 `installedUpdates`(TTL=0)就地补上。
168
168
  3. **设置页**:registry 地址草稿 +「校验并应用」(先校验候选再写 settings,失败不落盘)/「恢复默认」/「下载默认 registry.json」(不改当前配置)/「检查条目可达性」(probe 统计 + 最多 100 条 issue,只读不改配置);同时展示配置地址、当前生效配置、生效来源与 configStatus(含 rejected/回滚原因与维护性 warnings);完整本地路径仅在此页显示。registry 配置为 **live 生效,不出现安装类重启横幅**;安装/卸载/升级仍保留重启横幅。
169
169
 
package/lib/client.js CHANGED
@@ -213,8 +213,8 @@ var MARKET_PAGE_SIZES, DEFAULT_PAGE_SIZE, CURATED_ORDER, CURATED_LABELS, CURATED
213
213
  var init_market_state = __esm({
214
214
  "src/client/market-state.js"() {
215
215
  "use strict";
216
- MARKET_PAGE_SIZES = [24, 48, 96];
217
- DEFAULT_PAGE_SIZE = 24;
216
+ MARKET_PAGE_SIZES = [32, 64, 96];
217
+ DEFAULT_PAGE_SIZE = 32;
218
218
  CURATED_ORDER = ["essentials", "cui-picks", "self-dev", "tencent-lighthouse", "watchlist"];
219
219
  CURATED_LABELS = {
220
220
  essentials: "\u88C5\u673A\u5FC5\u5907",
@@ -348,6 +348,31 @@ var init_market_snapshot = __esm({
348
348
  }
349
349
  });
350
350
 
351
+ // src/client/backdrop.js
352
+ var backdrop_exports = {};
353
+ __export(backdrop_exports, {
354
+ backdropCloseHandlers: () => backdropCloseHandlers
355
+ });
356
+ function backdropCloseHandlers(onClose) {
357
+ let pressedOnBackdrop = false;
358
+ return {
359
+ onMouseDown: (e) => {
360
+ pressedOnBackdrop = Boolean(e) && e.target === e.currentTarget;
361
+ },
362
+ onClick: (e) => {
363
+ const onBackdrop = Boolean(e) && e.target === e.currentTarget;
364
+ const shouldClose = pressedOnBackdrop && onBackdrop;
365
+ pressedOnBackdrop = false;
366
+ if (shouldClose && typeof onClose === "function") onClose();
367
+ }
368
+ };
369
+ }
370
+ var init_backdrop = __esm({
371
+ "src/client/backdrop.js"() {
372
+ "use strict";
373
+ }
374
+ });
375
+
351
376
  // src/client/markdown.js
352
377
  var markdown_exports = {};
353
378
  __export(markdown_exports, {
@@ -1224,6 +1249,7 @@ var PLUGIN_ID = "dsh-m";
1224
1249
  var API = "/dshm";
1225
1250
  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 } = (init_market_state(), __toCommonJS(market_state_exports));
1226
1251
  var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
1252
+ var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCommonJS(backdrop_exports));
1227
1253
  var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
1228
1254
  var { ExtLink, MdImg, renderMarkdown } = createMarkdown2(h);
1229
1255
  var { installedViewModel: installedViewModel2, registrySourceKey: registrySourceKey2 } = (init_installed_view(), __toCommonJS(installed_view_exports));
@@ -2444,7 +2470,7 @@ function Shot({ src, onClick }) {
2444
2470
  function Lightbox({ shots, index, onNav, onClose }) {
2445
2471
  return h(
2446
2472
  "div",
2447
- { className: "dsvm-lightbox", onClick: onClose },
2473
+ { className: "dsvm-lightbox", ...backdropCloseHandlers2(onClose) },
2448
2474
  h("img", { src: shots[index], alt: "", onClick: (e) => e.stopPropagation(), referrerPolicy: "no-referrer" }),
2449
2475
  h(
2450
2476
  "div",
@@ -2504,7 +2530,7 @@ function DetailModal({ it, labels, busy, onClose, onInstall, profileKind, instal
2504
2530
  const kv = (k, v) => h(React.Fragment, { key: k }, h("dt", null, k), h("dd", null, v));
2505
2531
  return h(
2506
2532
  "div",
2507
- { className: "dsvm-modal", onClick: onClose },
2533
+ { className: "dsvm-modal", ...backdropCloseHandlers2(onClose) },
2508
2534
  h(
2509
2535
  "div",
2510
2536
  { className: "dsvm-modalbox", onClick: (e) => e.stopPropagation() },
@@ -2709,7 +2735,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2709
2735
  };
2710
2736
  const CompatDialog = compatConfirm ? h(
2711
2737
  "div",
2712
- { className: "dshm-compat-overlay", onClick: () => setCompatConfirm(null) },
2738
+ { className: "dshm-compat-overlay", ...backdropCloseHandlers2(() => setCompatConfirm(null)) },
2713
2739
  h(
2714
2740
  "div",
2715
2741
  { className: "dshm-compat-dialog", onClick: (e) => e.stopPropagation() },
@@ -3857,7 +3883,8 @@ function MarketPanel({ onClose }) {
3857
3883
  }, [profile]);
3858
3884
  return h(
3859
3885
  "div",
3860
- { className: full ? "dshm-overlay full" : "dshm-overlay", onClick: onClose },
3886
+ // 0.9.27 防拖拽误关:面板内按下拖选到遮罩释放时,click 落在公共祖先(遮罩)上——旧 onClick: onClose 会误关面板
3887
+ { className: full ? "dshm-overlay full" : "dshm-overlay", ...backdropCloseHandlers2(onClose) },
3861
3888
  h(
3862
3889
  "div",
3863
3890
  { className: full ? "dshm-panel full" : "dshm-panel", onClick: (e) => e.stopPropagation() },
@@ -323,11 +323,11 @@ export function createApiDispatcher(ctx) {
323
323
  }
324
324
  case 'market': {
325
325
  await ctx.controller.ensureReady();
326
- // GUI policy:忽略客户端 withLatest,固定 true;limit clamp 1..96、缺省 24
327
- // (0.7.0 Task 7 探测预算决策:96 为 opt-in 页大小,默认 24 低于 0.6.x 默认 50 的探测负载;
326
+ // GUI policy:忽略客户端 withLatest,固定 true;limit clamp 1..96、缺省 32
327
+ // (0.7.0 Task 7 探测预算决策:96 为 opt-in 页大小,0.9.28 起默认 32 仍低于 0.6.x 默认 50 的探测负载;
328
328
  // 最坏情况被 core 60s deadline 框死为 latestError,不阻塞列表,Q46 不动)
329
329
  const limitRaw = Number(body.limit);
330
- const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(96, Math.max(1, Math.floor(limitRaw))) : 24;
330
+ const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(96, Math.max(1, Math.floor(limitRaw))) : 32;
331
331
  const offsetRaw = Number(body.offset);
332
332
  const offset = Number.isFinite(offsetRaw) && offsetRaw > 0 ? Math.floor(offsetRaw) : 0;
333
333
  // category:策展五桶 + 社区开放 slug(非法 slug → 400,不静默吞)
@@ -62,7 +62,7 @@ function loaderEntriesOrNull() {
62
62
  }
63
63
  // ---------- 通用工具 ----------
64
64
  const DEFAULT_DEADLINE_MS = 60_000;
65
- /** withLatest 上限 = 96(0.7.0 Task 7:50→96,opt-in 页大小;默认页 24 的探测负载低于 0.6.x 默认 50——
65
+ /** withLatest 上限 = 96(0.7.0 Task 7:50→96,opt-in 页大小;默认页 24、0.9.28 起 32,探测负载仍低于 0.6.x 默认 50——
66
66
  * 96/页冷缓存最坏情况被 60s deadline 框死为 latestError 不阻塞列表,Q46「探测对象=页面条目」不动;
67
67
  * 人工验收阈值:96/页冷缓存 latestError > 20% 即回退默认页大小并重议)。 */
68
68
  const WITH_LATEST_MAX = 96;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.26",
3
+ "version": "0.9.28",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",