dsh-m 0.9.25 → 0.9.27
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 +24 -0
- package/docs/DESIGN.md +1 -1
- package/lib/client.js +31 -4
- package/lib/core/host-api.js +3 -0
- package/lib/core/market.js +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,18 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
8
8
|
|
|
9
9
|
## 中文
|
|
10
10
|
|
|
11
|
+
### 0.9.27 变更:修复遮罩拖选误关——面板内按下、拖出释放不再关面板
|
|
12
|
+
|
|
13
|
+
- **问题(装机实测 2026-10-03)**:在市场搜索框内左键按下向左拖选(越过面板边界)释放,整个面板被关闭。非浏览器鼠标手势——面板遮罩是裸 `onClick: onClose`,而在面板内容里按下、拖到遮罩上释放时,浏览器把 `click` 派发到按下/释放目标的公共祖先(恰是遮罩),被误判为「点遮罩关面板」。详情 Modal、截图灯箱、兼容确认弹层同属该缺陷类。
|
|
14
|
+
- **修复**:新增纯函数 `backdropCloseHandlers`(src/client/backdrop.js)——仅当 **mousedown 与 click 都落在遮罩自身**时才关闭(其余组合一律不关,click 后按位状态复位);四处遮罩(主面板/详情/灯箱/兼容弹层)统一切换。点遮罩关闭、Esc 关闭、✕ 按钮行为不变。
|
|
15
|
+
- **验证**:新增 `tests/client-backdrop.test.mjs`(关闭/两类拖选不关/状态复位/畸形输入安全,5 组断言);全量 1030 pass / 0 fail。
|
|
16
|
+
|
|
17
|
+
### 0.9.26 变更:跨区搜索精选稳定前置——摘要行计数与首页所见一致
|
|
18
|
+
|
|
19
|
+
- **问题(装机实测 2026-10-03)**:搜索「sidebar」摘要行报「⭐ 精选 3 · 社区 315」,首页精选段只见 1 条。非重复计算——`sourceCounts` 不读分类计数(`alsoCategories` 无涉),3 条为真实精选命中;错位根因是摘要行报全局命中数,而相关性排序叠加社区区默认 downloads 降序 tie-break 把弱命中精选压进后页。
|
|
20
|
+
- **修复**:`listMarket` 新增 `curatedFirst`——`source='all'` 且 query 非空时精选命中**稳定前置**(稳定分区,分区内相关序不变,社区命中随后);仅 host-api GUI 通道携带,tools/CLI 不传,搜索排序三端同序不变;单分区/浏览态天然无效。跨页精选段头悬空与计数错位随之消失(精选命中 ≤ 页大小时全数落在首页段内)。
|
|
21
|
+
- **验证**:新增测试 ⑮(无 flag 保持交织相关序 / 带 flag 精选前置 / 空 query 与单分区无效);全量 1025 pass / 0 fail。
|
|
22
|
+
|
|
11
23
|
### 0.9.25 变更:跨区搜索——浏览分区、搜索全局(社区 + 精选一并命中)
|
|
12
24
|
|
|
13
25
|
- **跨区搜索**:任一分区(社区/精选)的搜索框升级为全局——`query` 非空时底层查询切 `source=all`,两分区条目统一相关性排序;清空关键词回到本区浏览态。分区制浏览(ADR-0004)与 `dshm_search`/CLI 三端同序不动;服务端唯一增量是 `MarketResult.sourceCounts` 分桶计数。
|
|
@@ -263,6 +275,18 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
263
275
|
|
|
264
276
|
## English
|
|
265
277
|
|
|
278
|
+
### Changed in 0.9.27 — fix accidental drag-out close: press inside, release on backdrop no longer closes the panel
|
|
279
|
+
|
|
280
|
+
- **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.
|
|
281
|
+
- **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.
|
|
282
|
+
- **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.
|
|
283
|
+
|
|
284
|
+
### Changed in 0.9.26 — curated hits first in cross-zone search: summary counts now match page one
|
|
285
|
+
|
|
286
|
+
- **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.
|
|
287
|
+
- **Fix**: `listMarket` gains `curatedFirst` — with `source='all'` and a non-empty query, curated hits are stably moved to the front (stable partition; relevance order preserved within each segment, community hits follow). Carried only by the host-api GUI channel — tools/CLI don't pass it, so the shared search ordering across the three surfaces is unchanged; single-zone and browse states are naturally unaffected. Dangling cross-page section headers and the count mismatch disappear (with curated hits ≤ page size, all of them land in the first-page section).
|
|
288
|
+
- **Verification**: new test ⑮ (no flag keeps the interleaved relevance order / flag moves curated first / empty query and single-zone are no-ops); full suite 1025 pass / 0 fail.
|
|
289
|
+
|
|
266
290
|
### Changed in 0.9.25 — cross-zone search: browsing stays zoned, searching goes global (community + curated together)
|
|
267
291
|
|
|
268
292
|
- **Cross-zone search**: either zone's (Community/Curated) search box now searches globally — with a non-empty `query` the underlying query switches to `source=all` and both zones' entries are ranked by the shared relevance pipeline; clearing the keyword returns to the zone's own browse state. The zoned browsing model (ADR-0004) and the `dshm_search`/CLI shared ordering are untouched; the only server-side addition is the `MarketResult.sourceCounts` bucket count.
|
package/docs/DESIGN.md
CHANGED
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
- 排序:`downloads | stars | added × asc/desc`,默认 `downloads-desc`;**无下载量(github-only)≠ 0 下载**(永远排在有真实计数条目之后,彼此按 stars 排);时间窗过滤明确不做(`added` 排序覆盖「找最新」诉求)。
|
|
88
88
|
- 卡片 byline:`owner / downloads / ★stars`(旁路数据已透传,Tooltip 给精确数);`deprecated` 徽章 + 详情内 replacement 替代链接(适配层补透传原生字段)。
|
|
89
89
|
- 分页:页码窗口化(`1 … n-1 n n+1 … total`,≤7 页全显)+ 页大小 24/48/96 + 筛选变化重置页 1 + 翻页回顶;仅 prev/next 的旧分页退役。
|
|
90
|
-
- 搜索:相关性加权管线——NFKC 归一化 + 中西文边界插空格 + 字段权重(name/npm > owner > 描述 > 分类)+ 命中类型加分 + 按条目缓存归一化结果;输入 250ms debounce + IME composition 全程处理 + draft/已提交 query 分离。**0.9.25 起搜索跨区全局**(两分区搜索框同语义):摘要行报两分区精确命中数(`sourceCounts`),结果页内按精选段置顶分组,分类 chips
|
|
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 精神「只在详情层、默认收起」不变,载体升级)。
|
|
93
93
|
- **全局操作记录**:每个变更操作一条 record(`queued / running / input=冲突待决 / done / warned / failed`),**状态不挂卡片**——翻页、搜索、切 tab 不丢;**localStorage 持久化队列**,宿主重载恢复时逐条校验「此刻仍成立才执行,否则报告」;覆盖安装、升级、卸载、开关。已装侧跟随一致化:「全部更新 (N)」批量入口 + tab 更新红点 + 卡片视觉与发现侧同体系;组管理、个人备注列 backlog。
|
package/lib/client.js
CHANGED
|
@@ -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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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() },
|
package/lib/core/host-api.js
CHANGED
|
@@ -366,6 +366,9 @@ export function createApiDispatcher(ctx) {
|
|
|
366
366
|
category,
|
|
367
367
|
source,
|
|
368
368
|
sort,
|
|
369
|
+
// 0.9.26:GUI 跨区搜索精选稳定前置(listMarket 内部仅在 source='all' 且 query 非空时生效)。
|
|
370
|
+
// 本参数仅 GUI 通道携带——tools/CLI 走各自调用面不传,搜索排序三端同序不变。
|
|
371
|
+
curatedFirst: true,
|
|
369
372
|
offset,
|
|
370
373
|
limit,
|
|
371
374
|
force: boolArg(body.force),
|
package/lib/core/market.js
CHANGED
|
@@ -503,12 +503,16 @@ export async function listMarket(cfg = {}, opts = {}, deps = {}) {
|
|
|
503
503
|
});
|
|
504
504
|
// 排序:显式 sort 先行;query 命中时相关性优先(稳定 tie-break 回到既有序——merged 现序或用户排序)
|
|
505
505
|
const afterSort = opts.sort ? sortEntries(filtered, opts.sort) : filtered;
|
|
506
|
-
const
|
|
506
|
+
const ranked = terms.length > 0
|
|
507
507
|
? afterSort
|
|
508
508
|
.map((entry, idx) => ({ entry, score: relevanceScore(entry, terms), idx }))
|
|
509
509
|
.sort((a, b) => b.score - a.score || a.idx - b.idx)
|
|
510
510
|
.map((s) => s.entry)
|
|
511
511
|
: afterSort;
|
|
512
|
+
// curatedFirst 稳定前置(0.9.26):见 MarketQuery 注释——稳定分区不破坏分区内相关序
|
|
513
|
+
const ordered = opts.curatedFirst === true && source === 'all' && terms.length > 0
|
|
514
|
+
? [...ranked.filter((entry) => !isCommunityEntry(entry)), ...ranked.filter(isCommunityEntry)]
|
|
515
|
+
: ranked;
|
|
512
516
|
const total = ordered.length;
|
|
513
517
|
// sourceCounts 分桶(0.9.25 跨区搜索摘要行):filtered(分区 ∩ query ∩ category)计数,与排序/分页无关
|
|
514
518
|
const communityHitCount = filtered.reduce((n, entry) => n + (isCommunityEntry(entry) ? 1 : 0), 0);
|