dshmarket 1.2.2 → 1.2.4

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/README.md CHANGED
@@ -51,6 +51,14 @@ Installs prefer npm tarballs over full-repo GitHub downloads whenever a plugin p
51
51
 
52
52
  Live from [awesome-dsh-plugin.com/plugins.json](https://awesome-dsh-plugin.com/plugins.json) — curated entries, npm mapping, and star counts refreshed daily by CI — with a bundled snapshot as offline fallback.
53
53
 
54
+ ## Related projects
55
+
56
+ ### DeepSeek Harness Desktop
57
+
58
+ [DeepSeek Harness Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) is a modern desktop client for the DeepSeek Harness ecosystem — start and manage a local Harness service without installing Node.js or touching the command line. Plugin marketplace, mobile remote control, and IM Channels are on its roadmap.
59
+
60
+ [Website](https://www.dshdesktop.cn) · [GitHub](https://github.com/anywhere-labs/deepseek-harness-desktop)
61
+
54
62
  ## License
55
63
 
56
64
  MIT · [dshmarket.com](https://dshmarket.com)
package/README.zh.md CHANGED
@@ -51,6 +51,14 @@ dsh plugin --profile web add dshmarket
51
51
 
52
52
  实时来自 [awesome-dsh-plugin.com/plugins.json](https://awesome-dsh-plugin.com/plugins.json)——精选条目、npm 映射、star 数由 CI 每日刷新——内置快照做离线兜底。
53
53
 
54
+ ## 友情链接
55
+
56
+ ### DeepSeek Harness Desktop
57
+
58
+ [DeepSeek Harness Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) 是一款为 DeepSeek Harness 生态打造的现代化桌面端,让用户无需配置 Node.js 或执行命令,即可启动和管理本地 Harness 服务。项目后续还将支持插件市场、移动端远程控制和 IM Channels。
59
+
60
+ [访问官网](https://www.dshdesktop.cn) · [GitHub](https://github.com/anywhere-labs/deepseek-harness-desktop)
61
+
54
62
  ## 许可
55
63
 
56
64
  MIT · [dshmarket.com](https://dshmarket.com)
package/client/client.js CHANGED
@@ -38,6 +38,12 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
38
38
  update: "更新",
39
39
  updating: "更新中…",
40
40
  updated: "✓ 已更新,重启后生效",
41
+ cancelOp: "取消",
42
+ cancelled: "已取消",
43
+ busyWait: "已有操作正在进行,请等它结束(同一时间只执行一个安装/更新/卸载)",
44
+ approveBuilds: "放行构建脚本并重试",
45
+ buildsSkipped: "该插件需要运行构建脚本才能工作,出于安全默认被拦下。点击下方按钮为它放行并重装:",
46
+ updateNow: "立即更新",
41
47
  updateFail: "更新失败",
42
48
  upToDate: "已是最新",
43
49
  linkedDev: "本地开发链接",
@@ -96,6 +102,12 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
96
102
  update: "Update",
97
103
  updating: "Updating…",
98
104
  updated: "✓ Updated — restart to apply",
105
+ cancelOp: "Cancel",
106
+ cancelled: "Cancelled",
107
+ busyWait: "Another operation is already running — please wait for it to finish (one install/update/uninstall at a time)",
108
+ approveBuilds: "Allow build scripts and retry",
109
+ buildsSkipped: "This plugin needs its build scripts to run; they are blocked by default for safety. Click below to allow them and reinstall:",
110
+ updateNow: "Update now",
99
111
  updateFail: "Update failed",
100
112
  upToDate: "Up to date",
101
113
  linkedDev: "linked (dev)",
@@ -132,10 +144,6 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
132
144
  for (let i = 0; i < name.length; i++) hash = hash * 31 + name.charCodeAt(i) | 0;
133
145
  return "hsl(" + (hash % 360 + 360) % 360 + " 55% 52%)";
134
146
  }
135
- function repoOf(url) {
136
- const m = /^https:\/\/github\.com\/([^/]+\/[^/]+?)(?:\/tree\/.+)?\/?$/.exec(url);
137
- return m ? m[1] : null;
138
- }
139
147
  function readSession(key) {
140
148
  try {
141
149
  return JSON.parse(sessionStorage.getItem(key) || "null");
@@ -149,6 +157,34 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
149
157
  return /\b(tui|cli|tty|terminal)\b|终端|命令行/i.test(plugin.name + " " + desc);
150
158
  }
151
159
  /**
160
+ * The discover list: category filter, then search across name / owner /
161
+ * localized description, then the selected sort. Pure — the section renders
162
+ * exactly this.
163
+ */
164
+ function visiblePlugins(plugins, options) {
165
+ const query = options.query.trim().toLowerCase();
166
+ const list = plugins.filter((p) => {
167
+ if (options.category !== "all" && p.category !== options.category) return false;
168
+ if (query === "") return true;
169
+ const desc = p.description && (p.description[options.lang] || p.description.en) || "";
170
+ return p.name.toLowerCase().includes(query) || p.owner.toLowerCase().includes(query) || desc.toLowerCase().includes(query);
171
+ });
172
+ if (options.sort === "hot") return [...list].sort((a, b) => (b.stars ?? -1) - (a.stars ?? -1));
173
+ if (options.sort === "new") return [...list].sort((a, b) => String(b.added).localeCompare(String(a.added)));
174
+ return list;
175
+ }
176
+ /** The themes tab listing: theme category only, most-starred first. */
177
+ function themePlugins(plugins) {
178
+ return plugins.filter((p) => p.category === "theme").sort((a, b) => (b.stars || 0) - (a.stars || 0));
179
+ }
180
+ /**
181
+ * Category chip order: collapsed with an active non-'all' chip, the active
182
+ * one moves to the front so it stays visible inside the two-row clip.
183
+ */
184
+ function orderedCategories(categories, active, open) {
185
+ return open || active === "all" ? categories : [active, ...categories.filter((id) => id !== active)];
186
+ }
187
+ /**
152
188
  * Unified installed-state matching (#15): both sides collapse to lowercase
153
189
  * identity sets — the registry entry contributes its bare name, npm name and
154
190
  * owner/repo; the dependency contributes its key and the repo inside its
@@ -158,16 +194,19 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
158
194
  function entryIdentities(plugin) {
159
195
  const ids = /* @__PURE__ */ new Set([plugin.name.toLowerCase()]);
160
196
  if (plugin.npm) ids.add(plugin.npm.toLowerCase());
161
- const repo = repoOf(plugin.url);
162
- if (repo !== null) ids.add(repo.toLowerCase());
197
+ const m = /^https:\/\/github\.com\/([^/]+\/[^/]+?)(?:\/tree\/[^/]+\/(.+?))?\/?$/.exec(plugin.url);
198
+ if (m !== null) ids.add(m[2] !== void 0 ? `${m[1].toLowerCase()}#path:/${m[2].toLowerCase()}` : m[1].toLowerCase());
163
199
  return ids;
164
200
  }
165
201
  function depIdentities(name, spec) {
166
202
  const ids = /* @__PURE__ */ new Set([name.toLowerCase()]);
167
203
  const scoped = /^@([^/]+)\/(.+)$/.exec(name);
168
204
  if (scoped !== null) ids.add(`${scoped[1].toLowerCase()}/${scoped[2].toLowerCase()}`);
169
- const match = /github:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)/i.exec(spec);
170
- if (match !== null) ids.add(match[1].toLowerCase());
205
+ const match = /github:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)(?:#path:\/([A-Za-z0-9_./-]+))?/i.exec(spec);
206
+ if (match !== null) {
207
+ ids.add(match[1].toLowerCase());
208
+ if (match[2] !== void 0) ids.add(`${match[1].toLowerCase()}#path:/${match[2].toLowerCase()}`);
209
+ }
171
210
  return ids;
172
211
  }
173
212
  /** The installed dependency name a registry entry corresponds to, or null. */
@@ -235,7 +274,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
235
274
  }
236
275
  //#endregion
237
276
  //#region \0dsh-css:/home/runner/work/dsh-market/dsh-market/src/client/Market.module.css.mjs
238
- const css = ".eGUBIq_root{min-width:0;height:100%;color:var(--dsw-alias-label-primary,#1f2328);flex-direction:column;display:flex;position:relative}.eGUBIq_head{flex-direction:column;gap:12px;padding:4px 4px 12px;display:flex}.eGUBIq_title{margin:0;font-size:16px;font-weight:500;line-height:24px}.eGUBIq_sub{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:0;font-size:14px;line-height:22px}.eGUBIq_searchInline{flex-shrink:0;width:200px;margin-bottom:6px}.eGUBIq_tabs{border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);align-items:flex-end;gap:2px;display:flex}.eGUBIq_tab{font:inherit;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;padding:7px 12px;font-size:13px}.eGUBIq_tab.eGUBIq_on{color:var(--dsw-alias-brand-primary,#4f6ef7);border-bottom-color:var(--dsw-alias-brand-primary,#4f6ef7);font-weight:600}.eGUBIq_restart{background:var(--dsw-alias-bg-layer-2,#fdf3e3);border:1px solid var(--dsw-alias-border-l2,#f3e3c3);border-radius:8px;align-items:center;gap:8px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_body{flex:1;padding:12px 4px 24px;overflow-x:hidden;overflow-y:auto}.eGUBIq_cats{z-index:5;background:var(--dsw-alias-bg-layer-2,#f7f8fa);margin:-12px -4px 2px;padding:12px 4px 4px;position:sticky;top:-13px}.eGUBIq_catsRow{align-items:flex-start;gap:8px;display:flex;position:relative}.eGUBIq_sort{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#e5e7eb);border-radius:8px;flex-shrink:0;gap:2px;padding:2px;display:flex}.eGUBIq_sort button{font:inherit;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:3px 10px;font-size:12px}.eGUBIq_sort button.eGUBIq_on{background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);font-weight:600;box-shadow:0 1px 3px #00000014}.eGUBIq_star{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_top{z-index:20;border:1px solid var(--dsw-alias-border-l1,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);width:38px;height:38px;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;border-radius:99px;font-size:16px;position:absolute;bottom:18px;right:18px;box-shadow:0 4px 14px #0000001f}.eGUBIq_top:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_tag{border:1px solid var(--dsw-alias-border-l3,#d9dde3);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:4px;flex-shrink:0;padding:1px 6px;font-size:11px;line-height:16px}.eGUBIq_okState{color:var(--dsw-alias-state-success-primary,#16a34a);white-space:nowrap;font-size:12px;font-weight:600}.eGUBIq_dangerBtn.eGUBIq_dangerBtn{border-color:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-state-error-primary,#dc2626)}.eGUBIq_dangerArmed.eGUBIq_dangerArmed{background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff}.eGUBIq_warnBtn.eGUBIq_warnBtn{background:var(--dsw-alias-state-warn-primary,#ea580c);color:#fff}.eGUBIq_catsWrap{flex-wrap:wrap;flex:1;align-items:center;gap:6px;min-width:0;display:flex}.eGUBIq_catsCollapsed{max-height:62px;overflow:hidden}.eGUBIq_catsToggle.eGUBIq_catsToggle{height:26px;min-height:26px;color:var(--dsw-alias-label-secondary,#6b7280);padding:0 6px}.eGUBIq_cmdDetails{margin:0}.eGUBIq_cmdSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary,#6b7280);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}.eGUBIq_cmdSummary::-webkit-details-marker{display:none}.eGUBIq_cmdSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.eGUBIq_cmdDetails[open]>.eGUBIq_cmdSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.eGUBIq_cmdSummary:hover{color:var(--dsw-alias-label-primary,#1f2328)}.eGUBIq_cmd{background:var(--dsw-alias-bg-layer-2,#f3f4f6);word-break:break-all;border-radius:6px;margin:8px 0 0;padding:8px 10px;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:18px}.eGUBIq_warnLine{color:var(--dsw-alias-state-warn-primary,#b45309);margin:0;font-size:12px;font-weight:600;line-height:18px}.eGUBIq_modalNote{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:12px 0 0;font-size:12px;line-height:18px}.eGUBIq_grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;display:grid}.eGUBIq_sect{color:var(--dsw-alias-label-secondary,#6b7280);margin:14px 2px 8px;font-size:12px;font-weight:600}.eGUBIq_sect:first-child{margin-top:2px}.eGUBIq_swatches{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;gap:0;height:34px;display:flex;overflow:hidden}.eGUBIq_themesGrid{margin-bottom:12px}.eGUBIq_swatches i{flex:1}.eGUBIq_card{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.eGUBIq_row1{align-items:center;gap:10px;min-width:0;display:flex}.eGUBIq_av{color:#fff;object-fit:cover;background:var(--dsw-alias-bg-layer-2,#f3f4f6);border-radius:8px;flex-shrink:0;place-items:center;width:32px;height:32px;font-size:14px;font-weight:700;display:grid}.eGUBIq_nm{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:22px;overflow:hidden}.eGUBIq_owner{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_desc{color:var(--dsw-alias-label-tertiary,#8b93a1);min-height:36px;margin:0;font-size:12px;line-height:18px}.eGUBIq_foot{align-items:center;gap:8px;margin-top:auto;display:flex}.eGUBIq_grow{flex:1}.eGUBIq_titleRow{align-items:center;gap:10px;display:flex}.eGUBIq_descTight{min-height:0}.eGUBIq_src{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px;text-decoration:none}.eGUBIq_src:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_dot{vertical-align:2px;margin-left:5px}.eGUBIq_loading{color:var(--dsw-alias-label-secondary,#9ca3af);flex-direction:column;align-items:center;gap:12px;padding:48px;font-size:13px;display:flex}.eGUBIq_spin{border:3px solid var(--dsw-alias-border-l1,#e5e7eb);border-top-color:var(--dsw-alias-brand-primary,#4f6ef7);border-radius:99px;width:22px;height:22px;animation:.8s linear infinite eGUBIq_sp}@keyframes eGUBIq_sp{to{transform:rotate(360deg)}}.eGUBIq_progress{background:var(--dsw-alias-bg-layer-2,#f3f4f6);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:8px;align-items:center;gap:9px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_irow .eGUBIq_progress{margin-top:8px}.eGUBIq_progress .eGUBIq_spin{border-width:2px;flex-shrink:0;width:14px;height:14px}.eGUBIq_progress code{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,monospace;font-size:11px;overflow:hidden}.eGUBIq_empty{color:var(--dsw-alias-label-secondary,#9ca3af);text-align:center;padding:32px;font-size:13px}.eGUBIq_err{color:var(--dsw-alias-state-error-primary,#dc2626);white-space:pre-wrap;word-break:break-all;margin:8px 0;font-size:12px}.eGUBIq_irow{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;align-items:center;gap:10px;margin-bottom:8px;padding:12px 14px;display:flex}.eGUBIq_irow>.eGUBIq_src,.eGUBIq_irow>.eGUBIq_owner,.eGUBIq_irow>.eGUBIq_btn{white-space:nowrap;flex-shrink:0}.eGUBIq_spec{color:var(--dsw-alias-label-secondary,#9ca3af);font-family:ui-monospace,Menlo,monospace;font-size:11px}";
277
+ const css = ".eGUBIq_root{min-width:0;height:100%;color:var(--dsw-alias-label-primary,#1f2328);flex-direction:column;display:flex;position:relative}.eGUBIq_head{flex-direction:column;gap:12px;padding:4px 4px 12px;display:flex}.eGUBIq_title{margin:0;font-size:16px;font-weight:500;line-height:24px}.eGUBIq_sub{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:0;font-size:14px;line-height:22px}.eGUBIq_searchInline{flex-shrink:0;width:200px;margin-bottom:6px}.eGUBIq_tabs{border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);align-items:flex-end;gap:2px;display:flex}.eGUBIq_tab{font:inherit;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;padding:7px 12px;font-size:13px}.eGUBIq_tab.eGUBIq_on{color:var(--dsw-alias-brand-primary,#4f6ef7);border-bottom-color:var(--dsw-alias-brand-primary,#4f6ef7);font-weight:600}.eGUBIq_restart{background:var(--dsw-alias-bg-layer-2,#fdf3e3);border:1px solid var(--dsw-alias-border-l2,#f3e3c3);border-radius:8px;align-items:center;gap:8px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_body{flex:1;padding:12px 4px 24px;overflow-x:hidden;overflow-y:auto}.eGUBIq_cats{z-index:5;background:var(--dsw-alias-bg-layer-2,#f7f8fa);margin:-12px -4px 2px;padding:12px 4px 4px;position:sticky;top:-13px}.eGUBIq_catsRow{align-items:flex-start;gap:8px;display:flex;position:relative}.eGUBIq_sort{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#e5e7eb);border-radius:8px;flex-shrink:0;gap:2px;padding:2px;display:flex}.eGUBIq_sort button{font:inherit;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:3px 10px;font-size:12px}.eGUBIq_sort button.eGUBIq_on{background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);font-weight:600;box-shadow:0 1px 3px #00000014}.eGUBIq_star{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_top{z-index:20;border:1px solid var(--dsw-alias-border-l1,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);width:38px;height:38px;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;border-radius:99px;font-size:16px;position:absolute;bottom:18px;right:18px;box-shadow:0 4px 14px #0000001f}.eGUBIq_top:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_tag{border:1px solid var(--dsw-alias-border-l3,#d9dde3);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:4px;flex-shrink:0;padding:1px 6px;font-size:11px;line-height:16px}.eGUBIq_okState{color:var(--dsw-alias-state-success-primary,#16a34a);white-space:nowrap;font-size:12px;font-weight:600}.eGUBIq_dangerBtn.eGUBIq_dangerBtn{border-color:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-state-error-primary,#dc2626)}.eGUBIq_dangerArmed.eGUBIq_dangerArmed{background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff}.eGUBIq_warnBtn.eGUBIq_warnBtn{background:var(--dsw-alias-state-warn-primary,#ea580c);color:#fff}.eGUBIq_catsWrap{flex-wrap:wrap;flex:1;align-items:center;gap:6px;min-width:0;display:flex}.eGUBIq_catsCollapsed{max-height:62px;overflow:hidden}.eGUBIq_catsToggle.eGUBIq_catsToggle{height:26px;min-height:26px;color:var(--dsw-alias-label-secondary,#6b7280);padding:0 6px}.eGUBIq_cmdDetails{margin:0}.eGUBIq_cmdSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary,#6b7280);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}.eGUBIq_cmdSummary::-webkit-details-marker{display:none}.eGUBIq_cmdSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.eGUBIq_cmdDetails[open]>.eGUBIq_cmdSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.eGUBIq_cmdSummary:hover{color:var(--dsw-alias-label-primary,#1f2328)}.eGUBIq_cmd{background:var(--dsw-alias-bg-layer-2,#f3f4f6);word-break:break-all;border-radius:6px;margin:8px 0 0;padding:8px 10px;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:18px}.eGUBIq_warnLine{color:var(--dsw-alias-state-warn-primary,#b45309);margin:0;font-size:12px;font-weight:600;line-height:18px}.eGUBIq_modalNote{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:12px 0 0;font-size:12px;line-height:18px}.eGUBIq_grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;display:grid}.eGUBIq_sect{color:var(--dsw-alias-label-secondary,#6b7280);margin:14px 2px 8px;font-size:12px;font-weight:600}.eGUBIq_sect:first-child{margin-top:2px}.eGUBIq_swatches{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;gap:0;height:34px;display:flex;overflow:hidden}.eGUBIq_themesGrid{margin-bottom:12px}.eGUBIq_swatches i{flex:1}.eGUBIq_card{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.eGUBIq_row1{align-items:center;gap:10px;min-width:0;display:flex}.eGUBIq_av{color:#fff;object-fit:cover;background:var(--dsw-alias-bg-layer-2,#f3f4f6);border-radius:8px;flex-shrink:0;place-items:center;width:32px;height:32px;font-size:14px;font-weight:700;display:grid}.eGUBIq_nm{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:22px;overflow:hidden}.eGUBIq_owner{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_desc{color:var(--dsw-alias-label-tertiary,#8b93a1);min-height:36px;margin:0;font-size:12px;line-height:18px}.eGUBIq_foot{align-items:center;gap:8px;margin-top:auto;display:flex}.eGUBIq_grow{flex:1}.eGUBIq_titleRow{align-items:center;gap:10px;display:flex}.eGUBIq_descTight{min-height:0}.eGUBIq_src{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px;text-decoration:none}.eGUBIq_src:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_dot{vertical-align:2px;margin-left:5px}.eGUBIq_loading{color:var(--dsw-alias-label-secondary,#9ca3af);flex-direction:column;align-items:center;gap:12px;padding:48px;font-size:13px;display:flex}.eGUBIq_spin{border:3px solid var(--dsw-alias-border-l1,#e5e7eb);border-top-color:var(--dsw-alias-brand-primary,#4f6ef7);border-radius:99px;width:22px;height:22px;animation:.8s linear infinite eGUBIq_sp}@keyframes eGUBIq_sp{to{transform:rotate(360deg)}}.eGUBIq_progress{background:var(--dsw-alias-bg-layer-2,#f3f4f6);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:8px;align-items:center;gap:9px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_irow .eGUBIq_progress{margin-top:8px}.eGUBIq_progress .eGUBIq_spin{border-width:2px;flex-shrink:0;width:14px;height:14px}.eGUBIq_progress code{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,monospace;font-size:11px;overflow:hidden}.eGUBIq_empty{color:var(--dsw-alias-label-secondary,#9ca3af);text-align:center;padding:32px;font-size:13px}.eGUBIq_err{color:var(--dsw-alias-state-error-primary,#dc2626);white-space:pre-wrap;word-break:break-all;margin:8px 0;font-size:12px}.eGUBIq_irow{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;align-items:center;gap:10px;margin-bottom:8px;padding:12px 14px;display:flex}.eGUBIq_irow>.eGUBIq_src,.eGUBIq_irow>.eGUBIq_owner,.eGUBIq_dangerBtn.eGUBIq_dangerBtn,.eGUBIq_warnBtn.eGUBIq_warnBtn,.eGUBIq_dangerArmed.eGUBIq_dangerArmed{white-space:nowrap;flex-shrink:0}.eGUBIq_spec{color:var(--dsw-alias-label-secondary,#9ca3af);font-family:ui-monospace,Menlo,monospace;font-size:11px}.eGUBIq_staleAction{margin-top:8px}.eGUBIq_pct{color:var(--dsw-alias-label-secondary,#6b7280);flex-shrink:0;font-size:11px;font-weight:600}.eGUBIq_cancelBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#6b7280);font:inherit;cursor:pointer;white-space:nowrap;border-radius:6px;flex-shrink:0;padding:2px 10px;font-size:11px;line-height:16px}.eGUBIq_cancelBtn:hover{color:var(--dsw-alias-state-error-primary,#dc2626);border-color:var(--dsw-alias-state-error-primary,#dc2626)}.eGUBIq_sentinel{height:1px}";
239
278
  const tagId = "dshmarket/Market.module.css";
240
279
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
241
280
  const tag = document.createElement("style");
@@ -245,59 +284,62 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
245
284
  document.head.appendChild(tag);
246
285
  }
247
286
  var Market_module_css_default = {
248
- "tabs": "eGUBIq_tabs",
249
- "swatches": "eGUBIq_swatches",
250
- "star": "eGUBIq_star",
251
- "okState": "eGUBIq_okState",
252
- "themesGrid": "eGUBIq_themesGrid",
253
- "dangerBtn": "eGUBIq_dangerBtn",
254
- "modalNote": "eGUBIq_modalNote",
255
- "nm": "eGUBIq_nm",
256
- "btn": "eGUBIq_btn",
257
- "descTight": "eGUBIq_descTight",
258
- "empty": "eGUBIq_empty",
259
- "cats": "eGUBIq_cats",
260
- "title": "eGUBIq_title",
261
- "on": "eGUBIq_on",
262
- "row1": "eGUBIq_row1",
263
- "body": "eGUBIq_body",
264
- "top": "eGUBIq_top",
265
- "searchInline": "eGUBIq_searchInline",
266
- "sub": "eGUBIq_sub",
267
- "catsWrap": "eGUBIq_catsWrap",
268
- "head": "eGUBIq_head",
269
- "av": "eGUBIq_av",
270
287
  "warnBtn": "eGUBIq_warnBtn",
271
- "owner": "eGUBIq_owner",
272
- "cmdDetails": "eGUBIq_cmdDetails",
273
- "restart": "eGUBIq_restart",
288
+ "grow": "eGUBIq_grow",
289
+ "sentinel": "eGUBIq_sentinel",
290
+ "body": "eGUBIq_body",
291
+ "titleRow": "eGUBIq_titleRow",
292
+ "tag": "eGUBIq_tag",
293
+ "sect": "eGUBIq_sect",
274
294
  "catsRow": "eGUBIq_catsRow",
275
- "foot": "eGUBIq_foot",
276
- "src": "eGUBIq_src",
295
+ "dangerArmed": "eGUBIq_dangerArmed",
296
+ "catsCollapsed": "eGUBIq_catsCollapsed",
297
+ "head": "eGUBIq_head",
298
+ "star": "eGUBIq_star",
299
+ "swatches": "eGUBIq_swatches",
300
+ "card": "eGUBIq_card",
301
+ "grid": "eGUBIq_grid",
277
302
  "root": "eGUBIq_root",
278
303
  "tab": "eGUBIq_tab",
304
+ "av": "eGUBIq_av",
305
+ "searchInline": "eGUBIq_searchInline",
279
306
  "desc": "eGUBIq_desc",
280
- "sect": "eGUBIq_sect",
281
- "catsToggle": "eGUBIq_catsToggle",
282
- "card": "eGUBIq_card",
283
- "titleRow": "eGUBIq_titleRow",
307
+ "foot": "eGUBIq_foot",
308
+ "themesGrid": "eGUBIq_themesGrid",
309
+ "src": "eGUBIq_src",
284
310
  "dot": "eGUBIq_dot",
311
+ "spin": "eGUBIq_spin",
312
+ "descTight": "eGUBIq_descTight",
313
+ "row1": "eGUBIq_row1",
285
314
  "progress": "eGUBIq_progress",
286
- "tag": "eGUBIq_tag",
315
+ "err": "eGUBIq_err",
316
+ "title": "eGUBIq_title",
317
+ "restart": "eGUBIq_restart",
318
+ "cmdSummary": "eGUBIq_cmdSummary",
319
+ "loading": "eGUBIq_loading",
287
320
  "sp": "eGUBIq_sp",
321
+ "spec": "eGUBIq_spec",
322
+ "staleAction": "eGUBIq_staleAction",
323
+ "cancelBtn": "eGUBIq_cancelBtn",
324
+ "tabs": "eGUBIq_tabs",
325
+ "on": "eGUBIq_on",
326
+ "top": "eGUBIq_top",
327
+ "sub": "eGUBIq_sub",
328
+ "modalNote": "eGUBIq_modalNote",
329
+ "cmdDetails": "eGUBIq_cmdDetails",
330
+ "owner": "eGUBIq_owner",
331
+ "okState": "eGUBIq_okState",
332
+ "empty": "eGUBIq_empty",
288
333
  "cmd": "eGUBIq_cmd",
289
- "cmdSummary": "eGUBIq_cmdSummary",
290
- "grid": "eGUBIq_grid",
291
- "catsCollapsed": "eGUBIq_catsCollapsed",
334
+ "pct": "eGUBIq_pct",
335
+ "nm": "eGUBIq_nm",
292
336
  "sort": "eGUBIq_sort",
293
- "grow": "eGUBIq_grow",
294
337
  "warnLine": "eGUBIq_warnLine",
295
- "dangerArmed": "eGUBIq_dangerArmed",
296
- "loading": "eGUBIq_loading",
297
- "spin": "eGUBIq_spin",
298
338
  "irow": "eGUBIq_irow",
299
- "err": "eGUBIq_err",
300
- "spec": "eGUBIq_spec"
339
+ "cats": "eGUBIq_cats",
340
+ "catsToggle": "eGUBIq_catsToggle",
341
+ "dangerBtn": "eGUBIq_dangerBtn",
342
+ "catsWrap": "eGUBIq_catsWrap"
301
343
  };
302
344
  //#endregion
303
345
  //#region src/client/MarketSection.tsx
@@ -325,14 +367,25 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
325
367
  onError: () => setFailed(true)
326
368
  });
327
369
  }
370
+ /**
371
+ * Module-scope caches so re-entering the section renders instantly instead
372
+ * of refetching and rebuilding from a spinner (#30 by @StarsTom). Module
373
+ * state survives section switches; a background refetch keeps it current.
374
+ */
375
+ let cachedRegistry = null;
376
+ let cachedInstalled = null;
328
377
  function MarketSection(props) {
329
378
  const t = props.t;
330
379
  const localeSnap = (0, react.useSyncExternalStore)((cb) => props.locale.subscribe(cb), () => props.locale.getSnapshot());
331
380
  const lang = String(localeSnap.active).toLowerCase().startsWith("zh") ? "zh" : "en";
332
381
  const themeSnap = (0, react.useSyncExternalStore)(props.themeStore.subscribe, props.themeStore.getSnapshot);
333
- const [data, setData] = (0, react.useState)(null);
382
+ const [data, setData] = (0, react.useState)(cachedRegistry);
334
383
  const [loadError, setLoadError] = (0, react.useState)(false);
335
- const [installed, setInstalled] = (0, react.useState)({});
384
+ const [installed, setInstalledState] = (0, react.useState)(cachedInstalled ?? {});
385
+ const setInstalled = (0, react.useCallback)((value) => {
386
+ cachedInstalled = value;
387
+ setInstalledState(value);
388
+ }, []);
336
389
  const [skins, setSkins] = (0, react.useState)([]);
337
390
  const [tab, setTab] = (0, react.useState)(() => {
338
391
  const saved = sessionStorage.getItem("dshm-tab");
@@ -347,6 +400,23 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
347
400
  const [installError, setInstallError] = (0, react.useState)(null);
348
401
  const [updates, setUpdates] = (0, react.useState)({});
349
402
  const [updatingName, setUpdatingName] = (0, react.useState)(null);
403
+ const [staleName, setStaleName] = (0, react.useState)(null);
404
+ /**
405
+ * Cards rendered so far — the discover grid renders incrementally (60,
406
+ * then +120 as the sentinel scrolls into view) instead of all 400+ at
407
+ * once, which built a ~70k px DOM and made section switches lag (#30).
408
+ */
409
+ const [visibleCount, setVisibleCount] = (0, react.useState)(60);
410
+ const sentinelRef = (0, react.useCallback)((node) => {
411
+ if (node === null) return;
412
+ new IntersectionObserver((entries) => {
413
+ if (entries.some((entry) => entry.isIntersecting)) setVisibleCount((count) => count + 120);
414
+ }, { rootMargin: "600px" }).observe(node);
415
+ }, []);
416
+ /** Determinate percent parsed from pnpm's Progress line, when available. */
417
+ const [progressPct, setProgressPct] = (0, react.useState)(null);
418
+ /** Blocked build scripts from the last install: enables approve-and-retry (#6). */
419
+ const [buildsSkipped, setBuildsSkipped] = (0, react.useState)(null);
350
420
  const [updatingAll, setUpdatingAll] = (0, react.useState)(false);
351
421
  const [updatedNames, setUpdatedNames] = (0, react.useState)([]);
352
422
  const [hotUrls, setHotUrls] = (0, react.useState)([]);
@@ -376,7 +446,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
376
446
  fetch("/dsh-market/registry", { cache: "no-store" }).then((res) => {
377
447
  if (!res.ok) throw new Error("HTTP " + res.status);
378
448
  return res.json();
379
- }).then((body) => setData(body.registry)).catch(() => setLoadError(true));
449
+ }).then((body) => {
450
+ cachedRegistry = body.registry;
451
+ setData(body.registry);
452
+ }).catch(() => setLoadError(true));
380
453
  fetch("/dsh-market/status", { cache: "no-store" }).then((res) => res.json()).then((status) => {
381
454
  setEnvReady(status.pnpm !== false);
382
455
  if (typeof status.boot === "string") setBootId(status.boot);
@@ -433,9 +506,16 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
433
506
  }
434
507
  const timer = setInterval(() => {
435
508
  fetch("/dsh-market/status", { cache: "no-store" }).then((res) => res.json()).then((status) => {
436
- if (status.active) setProgressLine((status.lastLine || "…") + " (" + status.seconds + "s)");
437
- else {
509
+ if (status.active) {
510
+ setProgressLine((status.lastLine || "…") + " (" + status.seconds + "s)");
511
+ const m = /resolved (\d+), reused (\d+), downloaded (\d+), added (\d+)/.exec(status.lastLine || "");
512
+ if (m !== null && Number(m[1]) > 0) {
513
+ const done = Number(m[2]) + Number(m[3]) + Number(m[4]);
514
+ setProgressPct(Math.max(4, Math.min(96, Math.round(done / Number(m[1]) * 100))));
515
+ }
516
+ } else {
438
517
  setProgressLine(null);
518
+ setProgressPct(null);
439
519
  setInstalled(status.installed || {});
440
520
  if (readSession("dshm-pending") !== null && busyUrl !== null) {
441
521
  if (data !== null && data.plugins.some((p) => p.url === busyUrl && isInstalled(p, status.installed || {}))) {
@@ -453,26 +533,27 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
453
533
  updatingName,
454
534
  data
455
535
  ]);
456
- const plugins = (0, react.useMemo)(() => {
457
- if (data === null) return [];
458
- const query = q.trim().toLowerCase();
459
- const list = data.plugins.filter((p) => {
460
- if (cat !== "all" && p.category !== cat) return false;
461
- if (query === "") return true;
462
- const desc = p.description && (p.description[lang] || p.description.en) || "";
463
- return p.name.toLowerCase().includes(query) || p.owner.toLowerCase().includes(query) || desc.toLowerCase().includes(query);
464
- });
465
- if (sort === "hot") return [...list].sort((a, b) => (b.stars ?? -1) - (a.stars ?? -1));
466
- if (sort === "new") return [...list].sort((a, b) => String(b.added).localeCompare(String(a.added)));
467
- return list;
468
- }, [
536
+ const plugins = (0, react.useMemo)(() => data === null ? [] : visiblePlugins(data.plugins, {
537
+ category: cat,
538
+ query: q,
539
+ lang,
540
+ sort
541
+ }), [
469
542
  data,
470
543
  q,
471
544
  cat,
472
545
  lang,
473
546
  sort
474
547
  ]);
548
+ (0, react.useEffect)(() => {
549
+ setVisibleCount(60);
550
+ }, [
551
+ q,
552
+ cat,
553
+ sort
554
+ ]);
475
555
  const doInstall = (0, react.useCallback)((plugin) => {
556
+ setBuildsSkipped(null);
476
557
  setConfirming(null);
477
558
  setInstallError(null);
478
559
  setBusyUrl(plugin.url);
@@ -492,6 +573,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
492
573
  location.reload();
493
574
  return;
494
575
  }
576
+ if (body.cancelled === true) {
577
+ refreshInstalled();
578
+ return;
579
+ }
495
580
  if (status === 200 && body.ok) {
496
581
  sessionStorage.setItem("dshm-tab", "installed");
497
582
  if (body.hot) {
@@ -500,6 +585,14 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
500
585
  } else setDoneUrls((urls) => urls.includes(plugin.url) ? urls : urls.concat(plugin.url));
501
586
  refreshInstalled();
502
587
  } else {
588
+ if (status === 409) {
589
+ setInstallError(t("busyWait"));
590
+ return;
591
+ }
592
+ if (Array.isArray(body.ignoredBuilds) && body.ignoredBuilds.length > 0) setBuildsSkipped({
593
+ plugin,
594
+ names: body.ignoredBuilds.map(String)
595
+ });
503
596
  const text = (v) => typeof v === "string" ? v : v && typeof v.text === "string" ? v.text : v == null ? "" : JSON.stringify(v);
504
597
  const detail = text(body.error) || [text(body.stderr), text(body.stdout)].filter(Boolean).join("\n").trim() || "exit " + body.exitCode;
505
598
  setInstallError(t("installFail") + ": " + plugin.name + " — " + detail.trim().slice(-600));
@@ -509,21 +602,42 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
509
602
  setInstallError(t("installFail") + ": " + String(error));
510
603
  }).finally(() => setBusyUrl(null));
511
604
  }, [refreshInstalled, t]);
512
- const doUpdate = (0, react.useCallback)((name) => {
605
+ /** Cancel the running plugin command (#6 by @qichuang321). */
606
+ const doCancel = (0, react.useCallback)(() => {
607
+ fetch("/dsh-market/cancel", {
608
+ method: "POST",
609
+ headers: { "content-type": "application/json" },
610
+ body: "{}"
611
+ }).catch(() => {});
612
+ }, []);
613
+ const doUpdate = (0, react.useCallback)((name, force = false) => {
513
614
  setInstallError(null);
615
+ setStaleName(null);
514
616
  setUpdatingName(name);
515
617
  return fetch("/dsh-market/update", {
516
618
  method: "POST",
517
619
  headers: { "content-type": "application/json" },
518
- body: JSON.stringify({ name })
620
+ body: JSON.stringify(force ? {
621
+ name,
622
+ force: true
623
+ } : { name })
519
624
  }).then((res) => res.json().then((body) => ({
520
625
  status: res.status,
521
626
  body
522
627
  }))).then(({ status, body }) => {
628
+ if (body.cancelled === true) {
629
+ refreshInstalled();
630
+ return;
631
+ }
523
632
  if (status === 200 && body.ok) {
524
633
  setUpdatedNames((names) => names.concat(name));
525
634
  refreshInstalled();
526
635
  } else {
636
+ if (status === 409) {
637
+ setInstallError(t("busyWait"));
638
+ return;
639
+ }
640
+ if (body.stale === true) setStaleName(name);
527
641
  const text = (v) => typeof v === "string" ? v : v && typeof v.text === "string" ? v.text : v == null ? "" : JSON.stringify(v);
528
642
  const detail = text(body.error) || [text(body.stderr), text(body.stdout)].filter(Boolean).join("\n").trim() || "exit " + body.exitCode;
529
643
  setInstallError(t("updateFail") + ": " + name + " — " + detail.trim().slice(-600));
@@ -586,7 +700,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
586
700
  }, [updatableNames, doUpdate]);
587
701
  const pendingRestart = doneUrls.length + updatedNames.length + removedCount;
588
702
  const hasUpdates = Object.keys(installed).some((name) => !updatedNames.includes(name) && updates[name] && updates[name].updateAvailable);
589
- const themePlugins = data === null ? [] : data.plugins.filter((p) => p.category === "theme").sort((a, b) => (b.stars || 0) - (a.stars || 0));
703
+ const themePlugins$1 = data === null ? [] : themePlugins(data.plugins);
590
704
  const pluginCard = (p) => {
591
705
  const desc = p.description && (p.description[lang] || p.description.en) || "";
592
706
  const done = doneUrls.includes(p.url) || hotUrls.includes(p.url);
@@ -663,10 +777,23 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
663
777
  }),
664
778
  busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
665
779
  className: Market_module_css_default.progress,
666
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
667
- className: Market_module_css_default.grow,
668
- children: progressLine || t("progressHint")
669
- })]
780
+ children: [
781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }),
782
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
783
+ className: Market_module_css_default.grow,
784
+ children: progressLine || t("progressHint")
785
+ }),
786
+ progressPct !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
787
+ className: Market_module_css_default.pct,
788
+ children: [progressPct, "%"]
789
+ }),
790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
791
+ type: "button",
792
+ className: Market_module_css_default.cancelBtn,
793
+ onClick: doCancel,
794
+ children: t("cancelOp")
795
+ })
796
+ ]
670
797
  })
671
798
  ]
672
799
  }, p.url);
@@ -970,9 +1097,43 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
970
1097
  })
971
1098
  ]
972
1099
  }),
973
- installError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1100
+ buildsSkipped !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1101
+ className: Market_module_css_default.restart,
1102
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1103
+ className: Market_module_css_default.grow,
1104
+ children: [
1105
+ t("buildsSkipped"),
1106
+ " ",
1107
+ buildsSkipped.names.join(", ")
1108
+ ]
1109
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1110
+ size: "sm",
1111
+ disabled: busyUrl !== null,
1112
+ onClick: () => {
1113
+ const { plugin, names } = buildsSkipped;
1114
+ setBuildsSkipped(null);
1115
+ fetch("/dsh-market/approve-builds", {
1116
+ method: "POST",
1117
+ headers: { "content-type": "application/json" },
1118
+ body: JSON.stringify({ packages: names })
1119
+ }).then((res) => res.json()).then((body) => {
1120
+ if (body.ok) doInstall(plugin);
1121
+ else setInstallError(String(body.error || "approve failed"));
1122
+ }).catch((error) => setInstallError(String(error)));
1123
+ },
1124
+ children: t("approveBuilds")
1125
+ })]
1126
+ }),
1127
+ installError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
974
1128
  className: Market_module_css_default.err,
975
- children: installError
1129
+ children: [installError, staleName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1130
+ className: Market_module_css_default.staleAction,
1131
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1132
+ size: "sm",
1133
+ onClick: () => doUpdate(staleName, true),
1134
+ children: t("updateNow")
1135
+ })
1136
+ })]
976
1137
  }),
977
1138
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
978
1139
  className: Market_module_css_default.body,
@@ -992,7 +1153,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
992
1153
  ref: catsWrapRef,
993
1154
  className: catsOpen || visibleCats === null ? `${Market_module_css_default.catsWrap} ${Market_module_css_default.catsCollapsed}` : Market_module_css_default.catsWrap,
994
1155
  children: (() => {
995
- const ordered = catsOpen || cat === "all" ? categories : [cat, ...categories.filter((id) => id !== cat)];
1156
+ const ordered = orderedCategories(categories, cat, catsOpen);
996
1157
  const shown = catsOpen || visibleCats === null ? ordered : ordered.slice(0, Math.max(0, visibleCats - 1));
997
1158
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
998
1159
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
@@ -1029,10 +1190,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1029
1190
  }), plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1030
1191
  className: Market_module_css_default.empty,
1031
1192
  children: t("empty")
1032
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1193
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1033
1194
  className: Market_module_css_default.grid,
1034
- children: plugins.map(pluginCard)
1035
- })] }) : tab === "themes" && themeSnap !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(() => {
1195
+ children: plugins.slice(0, visibleCount).map(pluginCard)
1196
+ }), plugins.length > visibleCount && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1197
+ ref: sentinelRef,
1198
+ className: Market_module_css_default.sentinel
1199
+ })] })] }) : tab === "themes" && themeSnap !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(() => {
1036
1200
  const extra = themeSnap.themes.filter((def) => def.id !== "light" && def.id !== "dark");
1037
1201
  return extra.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1038
1202
  className: `${Market_module_css_default.grid} ${Market_module_css_default.themesGrid}`,
@@ -1041,12 +1205,12 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1041
1205
  })(), data === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1042
1206
  className: Market_module_css_default.loading,
1043
1207
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }), t("loading")]
1044
- }) : themePlugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1208
+ }) : themePlugins$1.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1045
1209
  className: Market_module_css_default.empty,
1046
1210
  children: t("themeEmpty")
1047
1211
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1048
1212
  className: Market_module_css_default.grid,
1049
- children: themePlugins.map(themePluginCard)
1213
+ children: themePlugins$1.map(themePluginCard)
1050
1214
  })] }) : Object.keys(installed).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1051
1215
  className: Market_module_css_default.empty,
1052
1216
  children: t("installedEmpty")
@@ -1087,10 +1251,23 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1087
1251
  }),
1088
1252
  updatingName === name && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1089
1253
  className: Market_module_css_default.progress,
1090
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1091
- className: Market_module_css_default.grow,
1092
- children: progressLine || t("progressHint")
1093
- })]
1254
+ children: [
1255
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }),
1256
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1257
+ className: Market_module_css_default.grow,
1258
+ children: progressLine || t("progressHint")
1259
+ }),
1260
+ progressPct !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1261
+ className: Market_module_css_default.pct,
1262
+ children: [progressPct, "%"]
1263
+ }),
1264
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1265
+ type: "button",
1266
+ className: Market_module_css_default.cancelBtn,
1267
+ onClick: doCancel,
1268
+ children: t("cancelOp")
1269
+ })
1270
+ ]
1094
1271
  })
1095
1272
  ]
1096
1273
  }),