dshmarket 1.2.3 → 1.3.0

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
@@ -32,6 +32,7 @@ Restart `dsh web`, then open **Settings → Plugin Market**.
32
32
  - **One-click install** — confirm the source, watch live progress; most plugins go live after a page refresh, no restart
33
33
  - **Updates** — per-plugin update checks (npm version or pinned commit vs HEAD), one-click update, or update everything at once; the market updates itself the same way
34
34
  - **Uninstall** — two-step confirm; plugins installed this session are removed live
35
+ - **Restart when needed** — changes that cannot hot-load show a one-click restart beside the pending-change banner; the action is restricted to same-origin loopback requests
35
36
  - **Zero jargon** — if a component is missing (pnpm), the market detects it and offers a one-click automatic setup
36
37
  - **Log export** — one click produces a sanitized plain-text log for bug reports (home paths and credential shapes are masked; nothing is ever sent anywhere)
37
38
 
@@ -45,6 +46,9 @@ Installs prefer npm tarballs over full-repo GitHub downloads whenever a plugin p
45
46
  - Build scripts stay blocked by default (pnpm ≥10); allowing one is your explicit per-package choice
46
47
  - Terminal/CLI-surface plugins are flagged before you install them into the web profile
47
48
  - The install endpoint accepts same-origin POST only; the market never phones home
49
+ - The restart endpoint additionally requires a direct loopback client (forwarded requests are rejected) and relaunches the exact DSH entry, arguments, environment, and working directory
50
+ - One-click restart launches a detached replacement. If DSH is managed by systemd, launchd, pm2, or another supervisor, set the plugin option `allowRestart: false` and let the supervisor own restarts instead; the pending-change notice remains visible but the button is hidden
51
+ - For terminal-attached launches, the detached replacement keeps running after the original terminal closes
48
52
  - Listing ≠ endorsement: plugins are third-party code, install sources you trust
49
53
 
50
54
  ## Data source
package/README.zh.md CHANGED
@@ -32,6 +32,7 @@ dsh plugin --profile web add dshmarket
32
32
  - **一键安装**——确认来源,实时进度;多数插件刷新页面即可用,无需重启
33
33
  - **更新**——逐插件检测(npm 版本或锁定 commit 对比 HEAD),一键更新或全部更新;市场自己也走同一通道升级
34
34
  - **卸载**——两步确认防误触;本次会话装的插件即点即卸
35
+ - **按需重启**——无法热加载的变更会在待重启提示旁显示一键重启;操作仅接受本机同源请求
35
36
  - **零术语**——缺组件(pnpm)时市场自己发现、一键自动装好,全程不见命令行
36
37
  - **导出日志**——一键生成脱敏纯文本日志方便反馈(home 路径与密钥形状已打码;任何数据都不会被上传)
37
38
 
@@ -45,6 +46,9 @@ dsh plugin --profile web add dshmarket
45
46
  - 构建脚本默认禁止执行(pnpm ≥10),放行与否由你按包显式决定
46
47
  - 终端/命令行类插件装进网页版前会被明确提醒
47
48
  - 安装接口只接受同源 POST;市场不会向任何地方上报数据
49
+ - 重启接口还要求客户端直接来自环回地址(拒绝代理转发请求),并使用原入口、参数、环境和工作目录重新启动 DSH
50
+ - 一键重启会启动脱离终端的替代进程。若 DSH 由 systemd、launchd、pm2 等进程管理器托管,请设置插件选项 `allowRestart: false`,交由管理器负责重启;待重启提示仍会显示,但按钮会隐藏
51
+ - 从终端启动时,替代进程脱离原终端,关闭原终端后仍会继续运行
48
52
  - 收录 ≠ 背书:插件是第三方代码,请只安装你信任的来源
49
53
 
50
54
  ## 数据源
package/client/client.js CHANGED
@@ -38,6 +38,15 @@ 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
+ restartNow: "立即重启",
47
+ restarting: "正在重启…",
48
+ restartFail: "重启失败",
49
+ restartTimeout: "等待 DeepSeek Harness 启动超时",
41
50
  updateNow: "立即更新",
42
51
  updateFail: "更新失败",
43
52
  upToDate: "已是最新",
@@ -97,6 +106,15 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
97
106
  update: "Update",
98
107
  updating: "Updating…",
99
108
  updated: "✓ Updated — restart to apply",
109
+ cancelOp: "Cancel",
110
+ cancelled: "Cancelled",
111
+ busyWait: "Another operation is already running — please wait for it to finish (one install/update/uninstall at a time)",
112
+ approveBuilds: "Allow build scripts and retry",
113
+ buildsSkipped: "This plugin needs its build scripts to run; they are blocked by default for safety. Click below to allow them and reinstall:",
114
+ restartNow: "Restart now",
115
+ restarting: "Restarting…",
116
+ restartFail: "Restart failed",
117
+ restartTimeout: "Timed out waiting for DeepSeek Harness to start",
100
118
  updateNow: "Update now",
101
119
  updateFail: "Update failed",
102
120
  upToDate: "Up to date",
@@ -264,7 +282,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
264
282
  }
265
283
  //#endregion
266
284
  //#region \0dsh-css:/home/runner/work/dsh-market/dsh-market/src/client/Market.module.css.mjs
267
- 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}";
285
+ 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}";
268
286
  const tagId = "dshmarket/Market.module.css";
269
287
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
270
288
  const tag = document.createElement("style");
@@ -274,59 +292,62 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
274
292
  document.head.appendChild(tag);
275
293
  }
276
294
  var Market_module_css_default = {
295
+ "dangerBtn": "eGUBIq_dangerBtn",
296
+ "top": "eGUBIq_top",
297
+ "catsWrap": "eGUBIq_catsWrap",
298
+ "catsToggle": "eGUBIq_catsToggle",
277
299
  "grid": "eGUBIq_grid",
278
- "cmdDetails": "eGUBIq_cmdDetails",
279
- "title": "eGUBIq_title",
280
- "body": "eGUBIq_body",
281
- "empty": "eGUBIq_empty",
282
- "nm": "eGUBIq_nm",
283
- "on": "eGUBIq_on",
284
- "loading": "eGUBIq_loading",
285
- "card": "eGUBIq_card",
300
+ "grow": "eGUBIq_grow",
301
+ "titleRow": "eGUBIq_titleRow",
302
+ "staleAction": "eGUBIq_staleAction",
286
303
  "modalNote": "eGUBIq_modalNote",
304
+ "star": "eGUBIq_star",
305
+ "sect": "eGUBIq_sect",
306
+ "row1": "eGUBIq_row1",
307
+ "av": "eGUBIq_av",
308
+ "warnBtn": "eGUBIq_warnBtn",
309
+ "pct": "eGUBIq_pct",
310
+ "loading": "eGUBIq_loading",
311
+ "cancelBtn": "eGUBIq_cancelBtn",
312
+ "spec": "eGUBIq_spec",
287
313
  "catsRow": "eGUBIq_catsRow",
288
314
  "cats": "eGUBIq_cats",
289
- "foot": "eGUBIq_foot",
290
- "tabs": "eGUBIq_tabs",
291
- "catsWrap": "eGUBIq_catsWrap",
292
- "titleRow": "eGUBIq_titleRow",
315
+ "themesGrid": "eGUBIq_themesGrid",
316
+ "progress": "eGUBIq_progress",
317
+ "descTight": "eGUBIq_descTight",
318
+ "irow": "eGUBIq_irow",
319
+ "card": "eGUBIq_card",
320
+ "owner": "eGUBIq_owner",
293
321
  "warnLine": "eGUBIq_warnLine",
294
- "src": "eGUBIq_src",
295
- "cmdSummary": "eGUBIq_cmdSummary",
296
- "restart": "eGUBIq_restart",
297
- "root": "eGUBIq_root",
322
+ "nm": "eGUBIq_nm",
298
323
  "dangerArmed": "eGUBIq_dangerArmed",
299
324
  "desc": "eGUBIq_desc",
325
+ "empty": "eGUBIq_empty",
326
+ "src": "eGUBIq_src",
327
+ "body": "eGUBIq_body",
300
328
  "err": "eGUBIq_err",
301
- "top": "eGUBIq_top",
302
- "catsToggle": "eGUBIq_catsToggle",
329
+ "catsCollapsed": "eGUBIq_catsCollapsed",
303
330
  "tab": "eGUBIq_tab",
304
- "av": "eGUBIq_av",
305
- "dangerBtn": "eGUBIq_dangerBtn",
306
- "dot": "eGUBIq_dot",
307
- "irow": "eGUBIq_irow",
308
- "cmd": "eGUBIq_cmd",
309
- "swatches": "eGUBIq_swatches",
310
- "spin": "eGUBIq_spin",
331
+ "on": "eGUBIq_on",
311
332
  "sort": "eGUBIq_sort",
312
- "spec": "eGUBIq_spec",
313
- "grow": "eGUBIq_grow",
314
- "star": "eGUBIq_star",
315
- "themesGrid": "eGUBIq_themesGrid",
333
+ "cmd": "eGUBIq_cmd",
334
+ "tag": "eGUBIq_tag",
335
+ "head": "eGUBIq_head",
316
336
  "okState": "eGUBIq_okState",
317
- "sect": "eGUBIq_sect",
318
- "staleAction": "eGUBIq_staleAction",
319
- "sub": "eGUBIq_sub",
320
- "row1": "eGUBIq_row1",
321
- "owner": "eGUBIq_owner",
337
+ "root": "eGUBIq_root",
338
+ "tabs": "eGUBIq_tabs",
339
+ "dot": "eGUBIq_dot",
340
+ "title": "eGUBIq_title",
322
341
  "sp": "eGUBIq_sp",
323
- "head": "eGUBIq_head",
324
- "warnBtn": "eGUBIq_warnBtn",
325
- "catsCollapsed": "eGUBIq_catsCollapsed",
326
- "progress": "eGUBIq_progress",
327
- "searchInline": "eGUBIq_searchInline",
328
- "tag": "eGUBIq_tag",
329
- "descTight": "eGUBIq_descTight"
342
+ "sub": "eGUBIq_sub",
343
+ "restart": "eGUBIq_restart",
344
+ "swatches": "eGUBIq_swatches",
345
+ "cmdDetails": "eGUBIq_cmdDetails",
346
+ "foot": "eGUBIq_foot",
347
+ "spin": "eGUBIq_spin",
348
+ "sentinel": "eGUBIq_sentinel",
349
+ "cmdSummary": "eGUBIq_cmdSummary",
350
+ "searchInline": "eGUBIq_searchInline"
330
351
  };
331
352
  //#endregion
332
353
  //#region src/client/MarketSection.tsx
@@ -354,14 +375,25 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
354
375
  onError: () => setFailed(true)
355
376
  });
356
377
  }
378
+ /**
379
+ * Module-scope caches so re-entering the section renders instantly instead
380
+ * of refetching and rebuilding from a spinner (#30 by @StarsTom). Module
381
+ * state survives section switches; a background refetch keeps it current.
382
+ */
383
+ let cachedRegistry = null;
384
+ let cachedInstalled = null;
357
385
  function MarketSection(props) {
358
386
  const t = props.t;
359
387
  const localeSnap = (0, react.useSyncExternalStore)((cb) => props.locale.subscribe(cb), () => props.locale.getSnapshot());
360
388
  const lang = String(localeSnap.active).toLowerCase().startsWith("zh") ? "zh" : "en";
361
389
  const themeSnap = (0, react.useSyncExternalStore)(props.themeStore.subscribe, props.themeStore.getSnapshot);
362
- const [data, setData] = (0, react.useState)(null);
390
+ const [data, setData] = (0, react.useState)(cachedRegistry);
363
391
  const [loadError, setLoadError] = (0, react.useState)(false);
364
- const [installed, setInstalled] = (0, react.useState)({});
392
+ const [installed, setInstalledState] = (0, react.useState)(cachedInstalled ?? {});
393
+ const setInstalled = (0, react.useCallback)((value) => {
394
+ cachedInstalled = value;
395
+ setInstalledState(value);
396
+ }, []);
365
397
  const [skins, setSkins] = (0, react.useState)([]);
366
398
  const [tab, setTab] = (0, react.useState)(() => {
367
399
  const saved = sessionStorage.getItem("dshm-tab");
@@ -377,6 +409,22 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
377
409
  const [updates, setUpdates] = (0, react.useState)({});
378
410
  const [updatingName, setUpdatingName] = (0, react.useState)(null);
379
411
  const [staleName, setStaleName] = (0, react.useState)(null);
412
+ /**
413
+ * Cards rendered so far — the discover grid renders incrementally (60,
414
+ * then +120 as the sentinel scrolls into view) instead of all 400+ at
415
+ * once, which built a ~70k px DOM and made section switches lag (#30).
416
+ */
417
+ const [visibleCount, setVisibleCount] = (0, react.useState)(60);
418
+ const sentinelRef = (0, react.useCallback)((node) => {
419
+ if (node === null) return;
420
+ new IntersectionObserver((entries) => {
421
+ if (entries.some((entry) => entry.isIntersecting)) setVisibleCount((count) => count + 120);
422
+ }, { rootMargin: "600px" }).observe(node);
423
+ }, []);
424
+ /** Determinate percent parsed from pnpm's Progress line, when available. */
425
+ const [progressPct, setProgressPct] = (0, react.useState)(null);
426
+ /** Blocked build scripts from the last install: enables approve-and-retry (#6). */
427
+ const [buildsSkipped, setBuildsSkipped] = (0, react.useState)(null);
380
428
  const [updatingAll, setUpdatingAll] = (0, react.useState)(false);
381
429
  const [updatedNames, setUpdatedNames] = (0, react.useState)([]);
382
430
  const [hotUrls, setHotUrls] = (0, react.useState)([]);
@@ -389,6 +437,9 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
389
437
  const [envFixing, setEnvFixing] = (0, react.useState)(false);
390
438
  const [envFailed, setEnvFailed] = (0, react.useState)(false);
391
439
  const [bootId, setBootId] = (0, react.useState)(null);
440
+ /** One-click restart (#14 by @ysyyhhh): server capability + in-flight state. */
441
+ const [restartEnabled, setRestartEnabled] = (0, react.useState)(false);
442
+ const [restarting, setRestarting] = (0, react.useState)(false);
392
443
  const [showTop, setShowTop] = (0, react.useState)(false);
393
444
  const bodyRef = (0, react.useRef)(null);
394
445
  const [sort, setSort] = (0, react.useState)("hot");
@@ -406,10 +457,14 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
406
457
  fetch("/dsh-market/registry", { cache: "no-store" }).then((res) => {
407
458
  if (!res.ok) throw new Error("HTTP " + res.status);
408
459
  return res.json();
409
- }).then((body) => setData(body.registry)).catch(() => setLoadError(true));
460
+ }).then((body) => {
461
+ cachedRegistry = body.registry;
462
+ setData(body.registry);
463
+ }).catch(() => setLoadError(true));
410
464
  fetch("/dsh-market/status", { cache: "no-store" }).then((res) => res.json()).then((status) => {
411
465
  setEnvReady(status.pnpm !== false);
412
466
  if (typeof status.boot === "string") setBootId(status.boot);
467
+ setRestartEnabled(status.restart === true);
413
468
  }).catch(() => {});
414
469
  refreshInstalled();
415
470
  }, [refreshInstalled]);
@@ -463,9 +518,16 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
463
518
  }
464
519
  const timer = setInterval(() => {
465
520
  fetch("/dsh-market/status", { cache: "no-store" }).then((res) => res.json()).then((status) => {
466
- if (status.active) setProgressLine((status.lastLine || "…") + " (" + status.seconds + "s)");
467
- else {
521
+ if (status.active) {
522
+ setProgressLine((status.lastLine || "…") + " (" + status.seconds + "s)");
523
+ const m = /resolved (\d+), reused (\d+), downloaded (\d+), added (\d+)/.exec(status.lastLine || "");
524
+ if (m !== null && Number(m[1]) > 0) {
525
+ const done = Number(m[2]) + Number(m[3]) + Number(m[4]);
526
+ setProgressPct(Math.max(4, Math.min(96, Math.round(done / Number(m[1]) * 100))));
527
+ }
528
+ } else {
468
529
  setProgressLine(null);
530
+ setProgressPct(null);
469
531
  setInstalled(status.installed || {});
470
532
  if (readSession("dshm-pending") !== null && busyUrl !== null) {
471
533
  if (data !== null && data.plugins.some((p) => p.url === busyUrl && isInstalled(p, status.installed || {}))) {
@@ -495,7 +557,15 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
495
557
  lang,
496
558
  sort
497
559
  ]);
560
+ (0, react.useEffect)(() => {
561
+ setVisibleCount(60);
562
+ }, [
563
+ q,
564
+ cat,
565
+ sort
566
+ ]);
498
567
  const doInstall = (0, react.useCallback)((plugin) => {
568
+ setBuildsSkipped(null);
499
569
  setConfirming(null);
500
570
  setInstallError(null);
501
571
  setBusyUrl(plugin.url);
@@ -515,6 +585,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
515
585
  location.reload();
516
586
  return;
517
587
  }
588
+ if (body.cancelled === true) {
589
+ refreshInstalled();
590
+ return;
591
+ }
518
592
  if (status === 200 && body.ok) {
519
593
  sessionStorage.setItem("dshm-tab", "installed");
520
594
  if (body.hot) {
@@ -523,6 +597,14 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
523
597
  } else setDoneUrls((urls) => urls.includes(plugin.url) ? urls : urls.concat(plugin.url));
524
598
  refreshInstalled();
525
599
  } else {
600
+ if (status === 409) {
601
+ setInstallError(t("busyWait"));
602
+ return;
603
+ }
604
+ if (Array.isArray(body.ignoredBuilds) && body.ignoredBuilds.length > 0) setBuildsSkipped({
605
+ plugin,
606
+ names: body.ignoredBuilds.map(String)
607
+ });
526
608
  const text = (v) => typeof v === "string" ? v : v && typeof v.text === "string" ? v.text : v == null ? "" : JSON.stringify(v);
527
609
  const detail = text(body.error) || [text(body.stderr), text(body.stdout)].filter(Boolean).join("\n").trim() || "exit " + body.exitCode;
528
610
  setInstallError(t("installFail") + ": " + plugin.name + " — " + detail.trim().slice(-600));
@@ -532,6 +614,65 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
532
614
  setInstallError(t("installFail") + ": " + String(error));
533
615
  }).finally(() => setBusyUrl(null));
534
616
  }, [refreshInstalled, t]);
617
+ /**
618
+ * Restart the host and reload once the boot id changes (#14 by @ysyyhhh).
619
+ * The 202 races the process's SIGTERM, so network errors on the initial
620
+ * request are expected and treated as "restart under way".
621
+ */
622
+ const doRestart = (0, react.useCallback)(() => {
623
+ if (bootId === null || restarting) return;
624
+ const previousBoot = bootId;
625
+ setRestarting(true);
626
+ setInstallError(null);
627
+ const awaitNewBoot = () => {
628
+ const deadline = Date.now() + 6e4;
629
+ const poll = () => {
630
+ fetch("/dsh-market/status", { cache: "no-store" }).then((res) => res.json()).then((next) => {
631
+ if (typeof next.boot === "string" && next.boot !== previousBoot) {
632
+ location.reload();
633
+ return;
634
+ }
635
+ retry();
636
+ }).catch(retry);
637
+ };
638
+ const retry = () => {
639
+ if (Date.now() > deadline) {
640
+ setRestarting(false);
641
+ setInstallError(t("restartTimeout"));
642
+ return;
643
+ }
644
+ setTimeout(poll, 1500);
645
+ };
646
+ poll();
647
+ };
648
+ fetch("/dsh-market/restart", {
649
+ method: "POST",
650
+ headers: { "content-type": "application/json" },
651
+ body: "{}"
652
+ }).then((res) => res.json().then((body) => ({
653
+ status: res.status,
654
+ body
655
+ }))).then(({ status, body }) => {
656
+ if (status !== 202 || body.ok !== true) {
657
+ setRestarting(false);
658
+ setInstallError(t("restartFail") + ": " + String(body.error || "HTTP " + String(status)));
659
+ return;
660
+ }
661
+ awaitNewBoot();
662
+ }).catch(awaitNewBoot);
663
+ }, [
664
+ bootId,
665
+ restarting,
666
+ t
667
+ ]);
668
+ /** Cancel the running plugin command (#6 by @qichuang321). */
669
+ const doCancel = (0, react.useCallback)(() => {
670
+ fetch("/dsh-market/cancel", {
671
+ method: "POST",
672
+ headers: { "content-type": "application/json" },
673
+ body: "{}"
674
+ }).catch(() => {});
675
+ }, []);
535
676
  const doUpdate = (0, react.useCallback)((name, force = false) => {
536
677
  setInstallError(null);
537
678
  setStaleName(null);
@@ -547,10 +688,18 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
547
688
  status: res.status,
548
689
  body
549
690
  }))).then(({ status, body }) => {
691
+ if (body.cancelled === true) {
692
+ refreshInstalled();
693
+ return;
694
+ }
550
695
  if (status === 200 && body.ok) {
551
696
  setUpdatedNames((names) => names.concat(name));
552
697
  refreshInstalled();
553
698
  } else {
699
+ if (status === 409) {
700
+ setInstallError(t("busyWait"));
701
+ return;
702
+ }
554
703
  if (body.stale === true) setStaleName(name);
555
704
  const text = (v) => typeof v === "string" ? v : v && typeof v.text === "string" ? v.text : v == null ? "" : JSON.stringify(v);
556
705
  const detail = text(body.error) || [text(body.stderr), text(body.stdout)].filter(Boolean).join("\n").trim() || "exit " + body.exitCode;
@@ -691,10 +840,23 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
691
840
  }),
692
841
  busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
693
842
  className: Market_module_css_default.progress,
694
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
695
- className: Market_module_css_default.grow,
696
- children: progressLine || t("progressHint")
697
- })]
843
+ children: [
844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }),
845
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
846
+ className: Market_module_css_default.grow,
847
+ children: progressLine || t("progressHint")
848
+ }),
849
+ progressPct !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
850
+ className: Market_module_css_default.pct,
851
+ children: [progressPct, "%"]
852
+ }),
853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
854
+ type: "button",
855
+ className: Market_module_css_default.cancelBtn,
856
+ onClick: doCancel,
857
+ children: t("cancelOp")
858
+ })
859
+ ]
698
860
  })
699
861
  ]
700
862
  }, p.url);
@@ -993,11 +1155,45 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
993
1155
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
994
1156
  title: t("restartHint"),
995
1157
  children: "ℹ️"
1158
+ }),
1159
+ restartEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1160
+ variant: "primary",
1161
+ size: "sm",
1162
+ disabled: restarting || busyUrl !== null || updatingName !== null || removingName !== null,
1163
+ onClick: doRestart,
1164
+ children: restarting ? t("restarting") : t("restartNow")
996
1165
  })
997
1166
  ]
998
1167
  })
999
1168
  ]
1000
1169
  }),
1170
+ buildsSkipped !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1171
+ className: Market_module_css_default.restart,
1172
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1173
+ className: Market_module_css_default.grow,
1174
+ children: [
1175
+ t("buildsSkipped"),
1176
+ " ",
1177
+ buildsSkipped.names.join(", ")
1178
+ ]
1179
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1180
+ size: "sm",
1181
+ disabled: busyUrl !== null,
1182
+ onClick: () => {
1183
+ const { plugin, names } = buildsSkipped;
1184
+ setBuildsSkipped(null);
1185
+ fetch("/dsh-market/approve-builds", {
1186
+ method: "POST",
1187
+ headers: { "content-type": "application/json" },
1188
+ body: JSON.stringify({ packages: names })
1189
+ }).then((res) => res.json()).then((body) => {
1190
+ if (body.ok) doInstall(plugin);
1191
+ else setInstallError(String(body.error || "approve failed"));
1192
+ }).catch((error) => setInstallError(String(error)));
1193
+ },
1194
+ children: t("approveBuilds")
1195
+ })]
1196
+ }),
1001
1197
  installError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1002
1198
  className: Market_module_css_default.err,
1003
1199
  children: [installError, staleName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1064,10 +1260,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1064
1260
  }), plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1065
1261
  className: Market_module_css_default.empty,
1066
1262
  children: t("empty")
1067
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1263
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1068
1264
  className: Market_module_css_default.grid,
1069
- children: plugins.map(pluginCard)
1070
- })] }) : tab === "themes" && themeSnap !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(() => {
1265
+ children: plugins.slice(0, visibleCount).map(pluginCard)
1266
+ }), plugins.length > visibleCount && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1267
+ ref: sentinelRef,
1268
+ className: Market_module_css_default.sentinel
1269
+ })] })] }) : tab === "themes" && themeSnap !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(() => {
1071
1270
  const extra = themeSnap.themes.filter((def) => def.id !== "light" && def.id !== "dark");
1072
1271
  return extra.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1073
1272
  className: `${Market_module_css_default.grid} ${Market_module_css_default.themesGrid}`,
@@ -1122,10 +1321,23 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1122
1321
  }),
1123
1322
  updatingName === name && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1124
1323
  className: Market_module_css_default.progress,
1125
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1126
- className: Market_module_css_default.grow,
1127
- children: progressLine || t("progressHint")
1128
- })]
1324
+ children: [
1325
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.spin }),
1326
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1327
+ className: Market_module_css_default.grow,
1328
+ children: progressLine || t("progressHint")
1329
+ }),
1330
+ progressPct !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1331
+ className: Market_module_css_default.pct,
1332
+ children: [progressPct, "%"]
1333
+ }),
1334
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1335
+ type: "button",
1336
+ className: Market_module_css_default.cancelBtn,
1337
+ onClick: doCancel,
1338
+ children: t("cancelOp")
1339
+ })
1340
+ ]
1129
1341
  })
1130
1342
  ]
1131
1343
  }),