dsh-m 0.9.24 → 0.9.25

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,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.25 变更:跨区搜索——浏览分区、搜索全局(社区 + 精选一并命中)
12
+
13
+ - **跨区搜索**:任一分区(社区/精选)的搜索框升级为全局——`query` 非空时底层查询切 `source=all`,两分区条目统一相关性排序;清空关键词回到本区浏览态。分区制浏览(ADR-0004)与 `dshm_search`/CLI 三端同序不动;服务端唯一增量是 `MarketResult.sourceCounts` 分桶计数。
14
+ - **搜索态呈现**:摘要行「⭐ 精选 N · 社区 M」精确计数(`!loading` 门控防旧数字闪现);精选命中页内置顶分组(段头各自仅在对应段非空时渲染,跨两列 grid);非社区卡补「精选」徽章;分类 chips 搜索态隐藏、「筛选」按钮随摘要行保留(搜索态只剩页大小组,排序被相关性优先覆盖);翻页回顶锚点随态切换。
15
+ - **修复**:分区 tab 计数改从恒定字段推导(社区 = `acceptedCount - displaced`、精选 = `registryState.count`),不再随查询变化(此前读「最近一次查询的 total」,跨区搜索会污染计数);搜索提交时清空激活分类(chips 已隐藏,残留分类会成为不可见过滤)。
16
+ - **降级**:`sourceCounts` 缺失/畸形时摘要行整体不渲染,列表行为不变;旧快照/旧宿主响应形状漂移免疫。
17
+
11
18
  ### 0.9.24 变更:排除条目代管——供应链等待期从「提前拒绝」到「治理 + 登记」(ADR-0009)
12
19
 
13
20
  - **问题(实机 2026-10-03 实证)**:0.9.19 的委派前预检把「目标版本未满 24h 等待期」预测成"官方管理器必拦"并提前拒绝——同目标 dsh-market 却能装上(copilot-auth@1.2.4 发布 8 分钟、quota-watch@0.1.21 两例)。pnpm 11.7 默认非严格策略对显式点名的新版本本来就放行并自动登记排除条目,预检的墙并不存在。
@@ -256,6 +263,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
256
263
 
257
264
  ## English
258
265
 
266
+ ### Changed in 0.9.25 — cross-zone search: browsing stays zoned, searching goes global (community + curated together)
267
+
268
+ - **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.
269
+ - **Search-mode presentation**: a summary line "⭐ Curated N · Community M" with exact per-zone counts (`!loading`-gated so stale numbers never flash); curated hits grouped on top within the page (section headers render only when their section is non-empty, spanning both grid columns); non-community cards gain a "Curated" badge; category chips hide during search while the Filter button moves onto the summary line (page-size group only — relevance outranks sort while searching); the pagination scroll anchor follows the mode.
270
+ - **Fixes**: zone tab counts now derive from stable fields (community = `acceptedCount - displaced`, curated = `registryState.count`) instead of "the last query's total" — cross-zone searches no longer pollute them; committing a search clears the active category (chips are hidden, so a leftover category would act as an invisible filter).
271
+ - **Degradation**: a missing/malformed `sourceCounts` hides the summary line entirely; list behavior unchanged; old snapshots/old host responses stay shape-drift immune.
272
+
259
273
  ### Changed in 0.9.24 — release-age exclusion governance: from pre-flight refusal to govern + register (ADR-0009)
260
274
 
261
275
  - **Problem (observed live 2026-10-03)**: the 0.9.19 pre-flight precheck predicted "the official manager will block a target inside the 24h release-age window" and refused up front — while dsh-market installed the very same targets (copilot-auth@1.2.4 published 8 minutes prior; quota-watch@0.1.21). pnpm 11.7's default non-strict policy admits explicitly named fresh releases and auto-registers their exclusion entries; the wall the precheck predicted did not exist.
package/README.en.md CHANGED
@@ -60,7 +60,7 @@ Version history is not expanded on this page — see [`CHANGELOG.md`](https://gi
60
60
 
61
61
  | View | Capabilities |
62
62
  |---|---|
63
- | **Market** | Community (4,000+ entries) / Curated / Favorites zones, each keeping its own category, search, sort, and pagination state; cards open a detail modal with install progress and results shown in place. |
63
+ | **Market** | Community (4,000+ entries) / Curated / Favorites zones; browsing state (category, sort, pagination) is kept per zone, while search is global across zones (community + curated hits together); cards open a detail modal with install progress and results shown in place. |
64
64
  | **Operations** | Installs, upgrades, uninstalls, and toggles flow through one global operation log — state lives off cards and resumes automatically after host reloads. |
65
65
  | **Installed** | What the current profile actually has: source annotation, live phase badge, one-click toggle, upgrade, and two-step confirmed uninstall. |
66
66
  | **Settings** | Curated registry configuration and status, community catalog toggle, dsh-m self-update. |
@@ -121,7 +121,7 @@ When the registry is unavailable, `registry` / `search` / `outdated` print the c
121
121
 
122
122
  - **Fetch chain**: dist-tags probe → jsDelivr pinned fetch → npmmirror → unpkg fallback; unchanged versions are not re-fetched, probes are skipped within TTL. Latest-version probes use a memory-only cache: it is dropped on every DSH restart (publish a new version, restart, and the new version shows up), and a plugin's cached entry is invalidated immediately after a successful install/upgrade/uninstall via dsh-m. On failure it falls back to the runtime cache under `<cache dir>/awesome/` and **explicitly labels it "cached snapshot"** — stale data never masquerades as fresh.
123
123
  - **Switch & pin**: `communityCatalog` toggle in Settings (default on, live), `communityCatalogPin` to lock the catalog version (exact semver); CLI opt-out with `DSHM_COMMUNITY_CATALOG=0`, pin with `DSHM_COMMUNITY_CATALOG_PIN`.
124
- - **Categories & search**: community categories are an open set (23 known categories with bilingual labels — Chinese maintained in-repo as the single source of truth, English from the upstream catalog, served per UI language); search runs a relevance-weighted pipeline (Chinese + English); the community zone offers a sort switch (downloads/stars/added × asc/desc, default downloads-desc; missing downloads ≠ zero downloads).
124
+ - **Categories & search**: community categories are an open set (23 known categories with bilingual labels — Chinese maintained in-repo as the single source of truth, English from the upstream catalog, served per UI language); search runs a relevance-weighted pipeline (Chinese + English) and is **global across zones** — either zone's search box hits community and curated listings together (browsing stays zoned; a summary line reports exact per-zone hit counts with curated hits grouped on top); the community zone offers a sort switch (downloads/stars/added × asc/desc, default downloads-desc; missing downloads ≠ zero downloads).
125
125
  - **Install semantics**: community entries go through the same install path as curated entries (npm exact version / GitHub pinned commit SHA); the browse page probes npm entries only — GitHub entries are not probed at page level (anonymous 60 req/h quota is uncontrollable).
126
126
  - **Installed-page budget (best-effort)**: update checks for GitHub-sourced plugins are capped at ≤25 wire requests per run and ≤50 per rolling hour per host process (passive checks only; active install/upgrade unaffected); capped items are honestly labeled "check incomplete" instead of "all up to date". A standalone CLI process does not share the host budget and makes no guarantee under concurrency.
127
127
  - **Capability disclosure**: capabilities/red lines appear only in the detail fold (**absent = not scanned ≠ not detected**), never as card badges; screenshots load only in the detail layer and pass a client-side allowlist.
package/README.md CHANGED
@@ -60,7 +60,7 @@ dsh-m 是 DeepSeek Harness(DSH)的插件市场:**收录 · 安装 · 卸
60
60
 
61
61
  | 视图 | 能力 |
62
62
  |---|---|
63
- | **市场** | 社区(4,000+ 条)/ 精选 / 收藏三分区,各分区独立保留分类、搜索、排序与分页状态;卡片点开详情 Modal,安装进度与结果就地显示。 |
63
+ | **市场** | 社区(4,000+ 条)/ 精选 / 收藏三分区;浏览态各分区独立保留分类、排序与分页状态,搜索为跨区全局(社区 + 精选一并命中);卡片点开详情 Modal,安装进度与结果就地显示。 |
64
64
  | **操作记录** | 安装、升级、卸载、开关统一进入全局操作日志,状态不挂卡片;宿主重载后自动恢复未完成操作。 |
65
65
  | **已装** | 当前 profile 实装列表:来源标注、运行相位徽标、一键开关、升级与两段式确认卸载。 |
66
66
  | **设置** | 精选清单配置与状态、社区目录开关、dsh-m 自更新。 |
@@ -121,7 +121,7 @@ dshm restart --yes
121
121
 
122
122
  - **数据获取**:dist-tags 探测最新版本 → jsDelivr 按精确版本直取 → npmmirror → unpkg 三线路兜底;版本未变不重拉正文,TTL 内跳过探测。「最新版本」探测结果为纯内存缓存:**重启 DSH 即失效**(发版后重启即可看到新版本),经 dsh-m 安装/升级/卸载成功后该条目缓存立即作废。失败回落 `<缓存目录>/awesome/` 运行时缓存先展示(先回缓存、后台自愈)——过期数据绝不冒充最新;缓存状态(缓存快照/目录版本/获取线路/更新时间)在**设置页**可见,浏览页不打扰。
123
123
  - **开关与锁定**:设置页 `communityCatalog` 开关(默认开,live 生效)、`communityCatalogPin` 可锁定目录版本(精确 semver);CLI 用 `DSHM_COMMUNITY_CATALOG=0` 退出、`DSHM_COMMUNITY_CATALOG_PIN` 锁版本。
124
- - **分类与搜索**:社区分类是开放集(已知 23 个分类带中英双语标签:中文由包内单一事实源维护、英文取上游目录,按界面语言下发);搜索走相关性加权管线(中英文双匹配);社区区排序可切(下载量/Star/收录日期 × 升降,默认下载量降序,无下载量 ≠ 0 下载)。
124
+ - **分类与搜索**:社区分类是开放集(已知 23 个分类带中英双语标签:中文由包内单一事实源维护、英文取上游目录,按界面语言下发);搜索走相关性加权管线(中英文双匹配),且为**跨区全局**——任一分区的搜索框同时命中社区与精选(浏览保持分区,摘要行报两分区精确命中数、精选命中置顶分组);社区区排序可切(下载量/Star/收录日期 × 升降,默认下载量降序,无下载量 ≠ 0 下载)。
125
125
  - **安装语义**:社区条目与精选条目走同一安装链路(npm 锁精确版本 / GitHub 锁 commit SHA);市场浏览页只对 npm 条目做更新探测,GitHub 条目不做页面级探测(匿名配额 60 次/小时不可控)。
126
126
  - **已装页预算(best-effort)**:已装页对 GitHub 来源插件做更新检查,单次请求 ≤25 个、宿主进程滚动 1 小时 ≤50 个(仅被动检查;主动安装/升级不受限);超限条目如实标注「检查未完成」而不冒充「全部最新」。独立 CLI 进程不共享宿主内预算,并发场景不承诺 60 次/小时绝不耗尽。
127
127
  - **能力披露**:社区条目的 capabilities/红线只出现在详情折叠区(**缺省 = 未扫描 ≠ 未检出**),卡片不打标;截图仅在详情层加载并经客户端白名单校验。
package/docs/DESIGN.md CHANGED
@@ -81,13 +81,13 @@
81
81
  > 本节只动**展示层与三端契约**;数据层(§2.5 获取链、适配层、校验、降级、缓存)与 Profile 变更事务(§3)不变。参考实现:`.dsh-research/dsh-market-clone/`(同一上游目录的原生市场应用,**结构级借鉴其浏览体验,不借鉴其安装链路**——dsh-m 的差异化 = 策展精选层 + 三端同源 + Profile 变更事务)。
82
82
 
83
83
  - **定位升级**:dsh-m 从「个人自用」转向**面向大众发布**(§1 定位句同步改写)。
84
- - **分区制(修订 §2.5 Q45)**:市场面板改为分区 tab——**「社区」为默认落地页**(全量社区条目)、**「精选」在后**(主清单条目,单页、策展顺序即排序)、**「收藏」第三**(本地收藏)。各区**独立状态实例**:分类、搜索、排序、分页互不重置。「只看主清单」chip 退役;Q40 的共享过滤桶退役(各区分类独立:精选区 5 chips、社区区全量 chips),「分类保留原生值不转译」仍成立。社区区**排除与主清单重复的条目**(displaced 数据现成)。**数据层 `mergeRegistries` 去重不变**(ADR-0003 架构不动)——「合并市场」的合并收敛为数据层语义,展示层分区。
84
+ - **分区制(修订 §2.5 Q45)**:市场面板改为分区 tab——**「社区」为默认落地页**(全量社区条目)、**「精选」在后**(主清单条目,单页、策展顺序即排序)、**「收藏」第三**(本地收藏)。各区**独立状态实例**:分类、搜索、排序、分页互不重置。**搜索例外(0.9.25 修订)**:搜索为跨区全局——任一分区的搜索框同时命中社区与精选(社区 + 精选一并命中,浏览保持分区),各区状态实例独立性不因搜索态改变。「只看主清单」chip 退役;Q40 的共享过滤桶退役(各区分类独立:精选区 5 chips、社区区全量 chips),「分类保留原生值不转译」仍成立。社区区**排除与主清单重复的条目**(displaced 数据现成)。**数据层 `mergeRegistries` 去重不变**(ADR-0003 架构不动)——「合并市场」的合并收敛为数据层语义,展示层分区。
85
85
  - **社区区浏览设施**(dsh-market 结构级借鉴):
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
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 分离。
90
+ - 搜索:相关性加权管线——NFKC 归一化 + 中西文边界插空格 + 字段权重(name/npm > owner > 描述 > 分类)+ 命中类型加分 + 按条目缓存归一化结果;输入 250ms debounce + IME composition 全程处理 + draft/已提交 query 分离。**0.9.25 起搜索跨区全局**(两分区搜索框同语义):摘要行报两分区精确命中数(`sourceCounts`),结果页内按精选段置顶分组,分类 chips 搜索态隐藏、「筛选」按钮随摘要行保留(页大小组)。
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。
@@ -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)——社区/精选/收藏三分区,各分区独立分类/搜索/排序/分页状态;搜索/分类/排序为**服务端处理**(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 缺省 24,探测仅当前页);`MarketPanel` 是市场/已装数据唯一 owner(请求 generation + AbortController 丢弃旧响应);安装/卸载/升级完成后通过统一协调器同时刷新市场与已装快照,不要求关闭并重新打开面板;页码窗口化分页(24/48/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
@@ -35,6 +35,7 @@ __export(market_state_exports, {
35
35
  pageItems: () => pageItems,
36
36
  registryNotice: () => registryNotice,
37
37
  resetPageOnFilterChange: () => resetPageOnFilterChange,
38
+ searchSourceOf: () => searchSourceOf,
38
39
  zoneChips: () => zoneChips
39
40
  });
40
41
  function toSafeInt(value, fallback, min, max) {
@@ -70,6 +71,11 @@ function normalizeMarketQuery(input, zone = "community") {
70
71
  }
71
72
  return { zone: z, source: z, query, category, sort, offset, limit };
72
73
  }
74
+ function searchSourceOf(query) {
75
+ const q = query && typeof query === "object" ? query : {};
76
+ if (typeof q.query === "string" && q.query !== "") return "all";
77
+ return q.source === "primary" ? "primary" : "community";
78
+ }
73
79
  function resetPageOnFilterChange(previous, next) {
74
80
  const prev = previous && typeof previous === "object" ? previous : {};
75
81
  const merged = { ...next };
@@ -103,6 +109,13 @@ function normalizeMarketResponse(raw) {
103
109
  if (typeof value === "number" && Number.isFinite(value)) categoryCounts[key] = value;
104
110
  }
105
111
  }
112
+ let sourceCounts;
113
+ const sc = body.sourceCounts;
114
+ if (sc && typeof sc === "object" && !Array.isArray(sc)) {
115
+ if (typeof sc.primary === "number" && Number.isFinite(sc.primary) && typeof sc.community === "number" && Number.isFinite(sc.community)) {
116
+ sourceCounts = { primary: sc.primary, community: sc.community };
117
+ }
118
+ }
106
119
  const rs = body.registryState && typeof body.registryState === "object" && !Array.isArray(body.registryState) ? body.registryState : {};
107
120
  const registryState = {
108
121
  ...FALLBACK_REGISTRY_STATE,
@@ -131,6 +144,7 @@ function normalizeMarketResponse(raw) {
131
144
  offset,
132
145
  limit,
133
146
  categoryCounts,
147
+ ...sourceCounts ? { sourceCounts } : {},
134
148
  registryState,
135
149
  community,
136
150
  installedComplete: body.installedComplete === true,
@@ -1208,7 +1222,7 @@ var h = React.createElement;
1208
1222
  var { useState, useEffect, useLayoutEffect, useCallback, useMemo, useRef } = React;
1209
1223
  var PLUGIN_ID = "dsh-m";
1210
1224
  var API = "/dshm";
1211
- 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 } = (init_market_state(), __toCommonJS(market_state_exports));
1225
+ 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));
1212
1226
  var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
1213
1227
  var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
1214
1228
  var { ExtLink, MdImg, renderMarkdown } = createMarkdown2(h);
@@ -1275,7 +1289,7 @@ var ZH = {
1275
1289
  "fav.add": "\u6536\u85CF",
1276
1290
  "fav.remove": "\u53D6\u6D88\u6536\u85CF",
1277
1291
  "common.clear": "\u6E05\u7A7A",
1278
- "search.ph": "\u641C\u7D22\u540D\u79F0 / \u63CF\u8FF0 / \u6807\u7B7E\u2026",
1292
+ "search.ph": "\u641C\u7D22\u540D\u79F0 / \u63CF\u8FF0 / \u6807\u7B7E\uFF08\u793E\u533A + \u7CBE\u9009\uFF09\u2026",
1279
1293
  "common.refresh": "\u5237\u65B0",
1280
1294
  "common.close": "\u5173\u95ED",
1281
1295
  "common.later": "\u7A0D\u540E",
@@ -1389,6 +1403,7 @@ var ZH = {
1389
1403
  "market.page.info": "\u7B2C {page} / {pages} \u9875 \xB7 \u5171 {total} \u6761",
1390
1404
  "notice.toolview.err": "\u6536\u5F55\u6E05\u5355\u6682\u4E0D\u53EF\u7528",
1391
1405
  "badge.community": "\u793E\u533A\u6536\u5F55",
1406
+ "search.summary": "\u2B50 \u7CBE\u9009 {n} \xB7 \u793E\u533A {m}",
1392
1407
  "community.fallback": "\u6536\u5F55\u6E05\u5355\u4E0D\u53EF\u7528\uFF0C\u5F53\u524D\u5C55\u793A\u793E\u533A\u6E05\u5355\u6761\u76EE",
1393
1408
  "detail.capabilities": "\u80FD\u529B\u62AB\u9732",
1394
1409
  "detail.capabilities.unscanned": "\u672A\u626B\u63CF \u2260 \u672A\u68C0\u51FA",
@@ -1537,7 +1552,7 @@ var EN = {
1537
1552
  "fav.add": "Bookmark",
1538
1553
  "fav.remove": "Remove bookmark",
1539
1554
  "common.clear": "Clear",
1540
- "search.ph": "Search name, description, tags\u2026",
1555
+ "search.ph": "Search all plugins \u2014 name, description, tags\u2026",
1541
1556
  "common.refresh": "Refresh",
1542
1557
  "common.close": "Close",
1543
1558
  "common.later": "Later",
@@ -1651,6 +1666,7 @@ var EN = {
1651
1666
  "market.page.info": "Page {page} / {pages} \xB7 {total} listings",
1652
1667
  "notice.toolview.err": "Registry temporarily unavailable",
1653
1668
  "badge.community": "Community",
1669
+ "search.summary": "\u2B50 Curated {n} \xB7 Community {m}",
1654
1670
  "community.fallback": "Registry unavailable \u2014 showing community listings",
1655
1671
  "detail.capabilities": "Capabilities",
1656
1672
  "detail.capabilities.unscanned": "Not scanned \u2260 not detected",
@@ -1886,6 +1902,8 @@ var CSS = `
1886
1902
  .dsvm-favbtn.on{color:#e0a33c}
1887
1903
  .dsvm-reddot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-error-primary,#ef4444);margin-left:5px;vertical-align:middle}
1888
1904
  .dshm-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
1905
+ .dsvm-searchmeta{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-caption,#6b7280);font-size:12px;line-height:18px;margin:2px 0}
1906
+ .dsvm-grouphead{grid-column:1/-1;color:var(--dsw-alias-label-caption,#6b7280);font-size:11px;line-height:16px;margin:2px 0 0;font-weight:600;letter-spacing:.02em}
1889
1907
  @media (max-width:680px){.dshm-cards{grid-template-columns:1fr}}
1890
1908
  .dshm-card{display:flex;gap:12px;align-items:flex-start;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;cursor:pointer;text-align:left;width:100%;box-sizing:border-box;min-width:0;font:inherit;color:var(--dsw-alias-label-primary,inherit);transition:border-color .16s,background .16s}
1891
1909
  .dshm-card:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));border-color:var(--dsw-alias-label-dimmed,#c7d2fe)}
@@ -2070,7 +2088,8 @@ function useMarketData(zone = "community") {
2070
2088
  const params = {
2071
2089
  query: nextQuery.query || void 0,
2072
2090
  category: nextQuery.category || void 0,
2073
- source: nextQuery.source || "community",
2091
+ // 0.9.25 跨区搜索:query 非空 → source=all(社区+精选一并命中),浏览态维持本区 source
2092
+ source: searchSourceOf2(nextQuery),
2074
2093
  ...nextQuery.sort ? { sort: nextQuery.sort } : {},
2075
2094
  offset: nextQuery.offset,
2076
2095
  limit: nextQuery.limit,
@@ -2578,6 +2597,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2578
2597
  const [zone, setZone] = useState("community");
2579
2598
  const market = zone === "favorites" ? null : markets[zone];
2580
2599
  const { data, loading, error, reload, query, updateQuery } = market || {};
2600
+ const searching = Boolean(query && query.query);
2581
2601
  const [detailId, setDetailId] = useState(null);
2582
2602
  const [compatConfirm, setCompatConfirm] = useState(null);
2583
2603
  useModalDepth(compatConfirm != null);
@@ -2607,7 +2627,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2607
2627
  const gotoPage = (p) => {
2608
2628
  updateQuery({ offset: Math.max(0, (p - 1) * limit) });
2609
2629
  if (typeof document !== "undefined") {
2610
- const el = document.querySelector(".dsvm-chipswrap");
2630
+ const el = document.querySelector(searching ? ".dsvm-searchmeta" : ".dsvm-chipswrap");
2611
2631
  if (el && el.scrollIntoView) el.scrollIntoView({ block: "start" });
2612
2632
  }
2613
2633
  };
@@ -2624,7 +2644,9 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2624
2644
  "div",
2625
2645
  { className: "dshm-chips" },
2626
2646
  ...ZONE_TABS.map((z) => {
2627
- const zCount = z.id === "community" ? markets.community.data && markets.community.data.total || 0 : z.id === "primary" ? markets.primary.data && markets.primary.data.total || 0 : favorites && favorites.list.length;
2647
+ const cd = markets.community.data;
2648
+ const pd = markets.primary.data;
2649
+ const zCount = z.id === "community" ? cd && cd.community.status !== "disabled" && cd.community.status !== "unavailable" ? Math.max(0, cd.community.acceptedCount - cd.community.displaced) : 0 : z.id === "primary" ? pd && pd.registryState.count || 0 : favorites && favorites.list.length;
2628
2650
  return h(
2629
2651
  "button",
2630
2652
  {
@@ -2640,7 +2662,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2640
2662
  const searchRow = market ? h(
2641
2663
  "div",
2642
2664
  { className: "dsvm-searchrow" },
2643
- h(SearchBox, { key: zone, placeholder: lookup("search.ph"), initial: query.query, onCommit: (v) => updateQuery({ query: v }) })
2665
+ h(SearchBox, { key: zone, placeholder: lookup("search.ph"), initial: query.query, onCommit: (v) => updateQuery(v ? { query: v, category: null } : { query: v }) })
2644
2666
  ) : null;
2645
2667
  const zoneBar = zoneChips3;
2646
2668
  const installResultText = (res) => lookup("notify.installed", { pkg: res.pkg, version: res.version ? ` v${res.version}` : "" }) + (res.buildApprovals && res.buildApprovals.length ? lookup("notify.builds", { names: res.buildApprovals.join(", ") }) : res.fallbackAllBuilds ? lookup("notify.builds.fallback") : "") + (res.bundleWarning === "no-patch-layer" ? lookup("notify.bundlewarning") : "");
@@ -2773,18 +2795,20 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2773
2795
  const filterPop = zone === "community" && filterOpen ? h(
2774
2796
  "div",
2775
2797
  { className: "dsvm-filterpop", onClick: (e) => e.stopPropagation() },
2776
- h(
2777
- "div",
2778
- { className: "dsvm-filtergroup" },
2779
- h("div", { className: "dsvm-filtergt" }, lookup("filter.sortfield")),
2780
- ...[["downloads", "filter.field.downloads"], ["stars", "filter.field.stars"], ["added", "filter.field.added"]].map(([f, key]) => optRow(lookup(key), curSortField === f, () => updateQuery({ sort: { field: f, dir: curSortDir } })))
2781
- ),
2782
- h(
2783
- "div",
2784
- { className: "dsvm-filtergroup" },
2785
- h("div", { className: "dsvm-filtergt" }, lookup("filter.sortdir")),
2786
- ...[["desc", "filter.dir.desc"], ["asc", "filter.dir.asc"]].map(([d, key]) => optRow(lookup(key), curSortDir === d, () => updateQuery({ sort: { field: curSortField, dir: d } })))
2787
- ),
2798
+ ...searching ? [] : [
2799
+ h(
2800
+ "div",
2801
+ { className: "dsvm-filtergroup" },
2802
+ h("div", { className: "dsvm-filtergt" }, lookup("filter.sortfield")),
2803
+ ...[["downloads", "filter.field.downloads"], ["stars", "filter.field.stars"], ["added", "filter.field.added"]].map(([f, key]) => optRow(lookup(key), curSortField === f, () => updateQuery({ sort: { field: f, dir: curSortDir } })))
2804
+ ),
2805
+ h(
2806
+ "div",
2807
+ { className: "dsvm-filtergroup" },
2808
+ h("div", { className: "dsvm-filtergt" }, lookup("filter.sortdir")),
2809
+ ...[["desc", "filter.dir.desc"], ["asc", "filter.dir.asc"]].map(([d, key]) => optRow(lookup(key), curSortDir === d, () => updateQuery({ sort: { field: curSortField, dir: d } })))
2810
+ )
2811
+ ],
2788
2812
  h(
2789
2813
  "div",
2790
2814
  { className: "dsvm-filtergroup" },
@@ -2805,6 +2829,77 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2805
2829
  ),
2806
2830
  filterPop
2807
2831
  ) : null;
2832
+ const sc = data && data.sourceCounts;
2833
+ const summaryRow = searching && sc && !loading && !error ? h(
2834
+ "div",
2835
+ { className: "dsvm-searchmeta" },
2836
+ h("span", null, lookup("search.summary", { n: sc.primary, m: sc.community })),
2837
+ filterTrigger
2838
+ ) : null;
2839
+ const cardOf = (it) => Card({
2840
+ key: it.id,
2841
+ icon: h(Icon, { entry: it }),
2842
+ name: it.name,
2843
+ badges: [
2844
+ activeInstallTarget === it.id ? h(
2845
+ "button",
2846
+ {
2847
+ className: "dshm-badge warn",
2848
+ key: "busy",
2849
+ title: lookup("op.panel.jump"),
2850
+ onClick: (e) => {
2851
+ e.stopPropagation();
2852
+ if (typeof document !== "undefined") {
2853
+ const el = document.querySelector(".dsvm-ops");
2854
+ if (el && el.scrollIntoView) el.scrollIntoView({ block: "center", behavior: "smooth" });
2855
+ }
2856
+ }
2857
+ },
2858
+ lookup("op.status.running")
2859
+ ) : null,
2860
+ it.deprecated === true ? h("span", { className: "dshm-badge warn", key: "dep" }, lookup("badge.deprecated")) : null,
2861
+ it.community !== true && Array.isArray(it.verified) && it.verified.length ? h("span", { className: "dshm-badge", key: "v", title: it.verified.join("\u3001") }, lookup("badge.verified")) : null,
2862
+ it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, lookup("badge.update")) : null,
2863
+ it.installed ? h("span", { className: "dshm-badge", key: "i" }, lookup("badge.installed")) : null,
2864
+ it.community === true ? h("span", { className: "dshm-badge info", key: "c" }, lookup("badge.community")) : null,
2865
+ searching && it.community !== true ? h("span", { className: "dshm-badge", key: "cz" }, lookup("zone.primary")) : null,
2866
+ h("span", { className: "dshm-badge info", key: "s" }, it.source === "npm" ? "npm" : "github")
2867
+ ],
2868
+ byline: it.community === true ? [
2869
+ it.owner ? { text: `by ${it.owner}` } : null,
2870
+ typeof it.downloads === "number" ? { text: `${compactCount(it.downloads)} \u2193`, title: String(it.downloads) } : null,
2871
+ typeof it.stars === "number" ? { text: `${compactCount(it.stars)} \u2605`, title: String(it.stars) } : null
2872
+ ].filter(Boolean) : (it.tags || []).map((t) => ({ text: `#${t}` })),
2873
+ desc: browserLang() === "en" && typeof it.descriptionEn === "string" && it.descriptionEn !== "" ? it.descriptionEn : it.description,
2874
+ clampLines: it.community === true ? 5 : 2,
2875
+ sub: [
2876
+ it.latestVersion ? lookup("sub.latest", { v: it.latestVersion }) : it.latestTag ? it.latestTag : it.latestSha ? lookup("sub.head", { sha: it.latestSha.slice(0, 7) }) : null,
2877
+ it.installedVersion ? lookup("sub.installed", { v: it.installedVersion }) : null,
2878
+ it.latestError ? it.version ? lookup("sub.snapshot", { v: it.version }) : lookup("version.failed") : null
2879
+ ].filter(Boolean).join(" \xB7 "),
2880
+ links: h(LinksRow, { npm: it.npm, github: it.github, homepage: it.homepage }),
2881
+ onToggle: () => setDetailId(it.id),
2882
+ topRight: favorites ? h(
2883
+ "button",
2884
+ {
2885
+ className: `dsvm-favbtn${favorites.list.some((f) => f.id === it.id) ? " on" : ""}`,
2886
+ title: favorites.list.some((f) => f.id === it.id) ? lookup("fav.remove") : lookup("fav.add"),
2887
+ onClick: (e) => {
2888
+ e.stopPropagation();
2889
+ favorites.toggle(snapshotOf(it));
2890
+ }
2891
+ },
2892
+ favorites.list.some((f) => f.id === it.id) ? "\u2605" : "\u2606"
2893
+ ) : null
2894
+ });
2895
+ const curatedHits = searching ? items.filter((it) => it.community !== true) : [];
2896
+ const communityHits = searching ? items.filter((it) => it.community === true) : [];
2897
+ const marketCards = searching && curatedHits.length > 0 ? [
2898
+ h("div", { className: "dsvm-grouphead", key: "gh-c" }, `\u2B50 ${lookup("zone.primary")}`),
2899
+ ...curatedHits.map(cardOf),
2900
+ ...communityHits.length > 0 ? [h("div", { className: "dsvm-grouphead", key: "gh-m" }, lookup("zone.community"))] : [],
2901
+ ...communityHits.map(cardOf)
2902
+ ] : items.map(cardOf);
2808
2903
  return h(
2809
2904
  React.Fragment,
2810
2905
  null,
@@ -2813,7 +2908,9 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2813
2908
  searchRow,
2814
2909
  notice && notice.notice ? h("div", { className: "dshm-err" }, lookup(notice.notice.key)) : null,
2815
2910
  notice && notice.communityFallback ? h("div", { className: "dshm-hint" }, lookup("community.fallback")) : null,
2816
- h(ZoneChips, {
2911
+ // 0.9.25 跨区搜索:搜索态隐藏分类 chips(两套分类法跨区口径失义)、「筛选」随摘要行保留;浏览态照旧
2912
+ summaryRow,
2913
+ searching ? null : h(ZoneChips, {
2817
2914
  zone,
2818
2915
  counts,
2819
2916
  labels: communityLabels(data),
@@ -2830,61 +2927,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2830
2927
  h(
2831
2928
  "div",
2832
2929
  { className: "dshm-cards" },
2833
- items.map((it) => Card({
2834
- key: it.id,
2835
- icon: h(Icon, { entry: it }),
2836
- name: it.name,
2837
- badges: [
2838
- activeInstallTarget === it.id ? h(
2839
- "button",
2840
- {
2841
- className: "dshm-badge warn",
2842
- key: "busy",
2843
- title: lookup("op.panel.jump"),
2844
- onClick: (e) => {
2845
- e.stopPropagation();
2846
- if (typeof document !== "undefined") {
2847
- const el = document.querySelector(".dsvm-ops");
2848
- if (el && el.scrollIntoView) el.scrollIntoView({ block: "center", behavior: "smooth" });
2849
- }
2850
- }
2851
- },
2852
- lookup("op.status.running")
2853
- ) : null,
2854
- it.deprecated === true ? h("span", { className: "dshm-badge warn", key: "dep" }, lookup("badge.deprecated")) : null,
2855
- it.community !== true && Array.isArray(it.verified) && it.verified.length ? h("span", { className: "dshm-badge", key: "v", title: it.verified.join("\u3001") }, lookup("badge.verified")) : null,
2856
- it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, lookup("badge.update")) : null,
2857
- it.installed ? h("span", { className: "dshm-badge", key: "i" }, lookup("badge.installed")) : null,
2858
- it.community === true ? h("span", { className: "dshm-badge info", key: "c" }, lookup("badge.community")) : null,
2859
- h("span", { className: "dshm-badge info", key: "s" }, it.source === "npm" ? "npm" : "github")
2860
- ],
2861
- byline: it.community === true ? [
2862
- it.owner ? { text: `by ${it.owner}` } : null,
2863
- typeof it.downloads === "number" ? { text: `${compactCount(it.downloads)} \u2193`, title: String(it.downloads) } : null,
2864
- typeof it.stars === "number" ? { text: `${compactCount(it.stars)} \u2605`, title: String(it.stars) } : null
2865
- ].filter(Boolean) : (it.tags || []).map((t) => ({ text: `#${t}` })),
2866
- desc: browserLang() === "en" && typeof it.descriptionEn === "string" && it.descriptionEn !== "" ? it.descriptionEn : it.description,
2867
- clampLines: it.community === true ? 5 : 2,
2868
- sub: [
2869
- it.latestVersion ? lookup("sub.latest", { v: it.latestVersion }) : it.latestTag ? it.latestTag : it.latestSha ? lookup("sub.head", { sha: it.latestSha.slice(0, 7) }) : null,
2870
- it.installedVersion ? lookup("sub.installed", { v: it.installedVersion }) : null,
2871
- it.latestError ? it.version ? lookup("sub.snapshot", { v: it.version }) : lookup("version.failed") : null
2872
- ].filter(Boolean).join(" \xB7 "),
2873
- links: h(LinksRow, { npm: it.npm, github: it.github, homepage: it.homepage }),
2874
- onToggle: () => setDetailId(it.id),
2875
- topRight: favorites ? h(
2876
- "button",
2877
- {
2878
- className: `dsvm-favbtn${favorites.list.some((f) => f.id === it.id) ? " on" : ""}`,
2879
- title: favorites.list.some((f) => f.id === it.id) ? lookup("fav.remove") : lookup("fav.add"),
2880
- onClick: (e) => {
2881
- e.stopPropagation();
2882
- favorites.toggle(snapshotOf(it));
2883
- }
2884
- },
2885
- favorites.list.some((f) => f.id === it.id) ? "\u2605" : "\u2606"
2886
- ) : null
2887
- }))
2930
+ ...marketCards
2888
2931
  ),
2889
2932
  pages > 1 ? h(
2890
2933
  "div",
@@ -455,6 +455,7 @@ export async function listMarket(cfg = {}, opts = {}, deps = {}) {
455
455
  offset: 0,
456
456
  limit: maxLimit,
457
457
  categoryCounts: zeroCounts(),
458
+ sourceCounts: { primary: 0, community: 0 },
458
459
  registryState: timeoutRegistryState(cfg),
459
460
  installedComplete: false,
460
461
  latestComplete: false,
@@ -509,6 +510,9 @@ export async function listMarket(cfg = {}, opts = {}, deps = {}) {
509
510
  .map((s) => s.entry)
510
511
  : afterSort;
511
512
  const total = ordered.length;
513
+ // sourceCounts 分桶(0.9.25 跨区搜索摘要行):filtered(分区 ∩ query ∩ category)计数,与排序/分页无关
514
+ const communityHitCount = filtered.reduce((n, entry) => n + (isCommunityEntry(entry) ? 1 : 0), 0);
515
+ const sourceCounts = { primary: filtered.length - communityHitCount, community: communityHitCount };
512
516
  const limit = clampLimit(opts.limit, maxLimit);
513
517
  let offset = normalizeOffset(opts.offset);
514
518
  if (total > 0 && offset >= total)
@@ -584,6 +588,7 @@ export async function listMarket(cfg = {}, opts = {}, deps = {}) {
584
588
  offset,
585
589
  limit,
586
590
  categoryCounts: counts,
591
+ sourceCounts,
587
592
  registryState,
588
593
  installedComplete,
589
594
  latestComplete,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.24",
3
+ "version": "0.9.25",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",