@linxin666/dsh-remote-web-ui 0.1.17 → 0.1.19

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.
Files changed (56) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +5 -2
  3. package/README.zh.md +2 -2
  4. package/lib/client.js +82 -66
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +18 -18
  7. package/lib/mobile.js +649 -45
  8. package/lib/mobile.js.map +1 -1
  9. package/lib/types/client/PluginSettingsCard.d.ts +11 -0
  10. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  11. package/lib/types/client/PluginSettingsCard.js +11 -5
  12. package/lib/types/client/RemoteSettingsCard.d.ts +5 -0
  13. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  14. package/lib/types/client/RemoteSettingsCard.js +7 -0
  15. package/lib/types/client/index.d.ts.map +1 -1
  16. package/lib/types/client/index.js +13 -7
  17. package/lib/types/client/settings-form.d.ts +8 -0
  18. package/lib/types/client/settings-form.d.ts.map +1 -1
  19. package/lib/types/client/settings-form.js +15 -1
  20. package/lib/types/gate.d.ts +4 -9
  21. package/lib/types/gate.d.ts.map +1 -1
  22. package/lib/types/gate.js +5 -32
  23. package/lib/types/loopback.d.ts +25 -0
  24. package/lib/types/loopback.d.ts.map +1 -0
  25. package/lib/types/loopback.js +68 -0
  26. package/lib/types/mobile/markdown.d.ts +26 -0
  27. package/lib/types/mobile/markdown.d.ts.map +1 -0
  28. package/lib/types/mobile/markdown.js +272 -0
  29. package/lib/types/mobile/markdown.test.d.ts +2 -0
  30. package/lib/types/mobile/markdown.test.d.ts.map +1 -0
  31. package/lib/types/mobile/markdown.test.js +67 -0
  32. package/lib/types/mobile/messages.d.ts +14 -0
  33. package/lib/types/mobile/messages.d.ts.map +1 -1
  34. package/lib/types/mobile/messages.js +54 -2
  35. package/lib/types/mobile/messages.test.js +99 -0
  36. package/lib/types/mobile/mobile-styles.d.ts +1 -1
  37. package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
  38. package/lib/types/mobile/mobile-styles.js +167 -0
  39. package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
  40. package/lib/types/mobile/views/ChatView.js +81 -5
  41. package/lib/types/mobile/views/ChatView.test.js +105 -0
  42. package/package.json +1 -1
  43. package/src/client/PluginSettingsCard.tsx +50 -47
  44. package/src/client/RemoteSettingsCard.tsx +8 -0
  45. package/src/client/index.ts +13 -7
  46. package/src/client/settings-card.module.css +9 -0
  47. package/src/client/settings-form.ts +15 -1
  48. package/src/gate.ts +5 -30
  49. package/src/loopback.ts +63 -0
  50. package/src/mobile/markdown.test.ts +77 -0
  51. package/src/mobile/markdown.ts +278 -0
  52. package/src/mobile/messages.test.ts +106 -0
  53. package/src/mobile/messages.ts +66 -2
  54. package/src/mobile/mobile-styles.ts +167 -0
  55. package/src/mobile/views/ChatView.test.tsx +121 -0
  56. package/src/mobile/views/ChatView.tsx +164 -6
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: c1e5ea0deaca90a5a188342db0d38256f5917369
6
- README.zh.md: 9be2fbeb481ea23f05aa06f732234b2310c3d16a
5
+ README.md: 3a4ac70503ef6629ac456e3e86e0e429b90fd4b2
6
+ README.zh.md: e16a9bd530de74cff366fc23ae143254a198989b
package/README.md CHANGED
@@ -140,13 +140,16 @@ mounts both halves.
140
140
  - messages render with the desktop fold discipline: reasoning hides
141
141
  behind a collapsed 深度思考 disclosure, tool calls behind a collapsed
142
142
  工具 row (tap to see each call's arguments), very long answers behind
143
- an explicit 展开全文 toggle, and each row carries its time — and
143
+ an explicit 展开全文 toggle, each row carries its time, and assistant replies render as GFM Markdown (headings / bold / italic / inline code / code blocks / lists / tables / quotes / links / images; a zero-dependency renderer escapes first and allow-lists protocols, so the mobile bundle size barely moves; KaTeX is not supported yet and will be evaluated separately), while user messages stay plain text — and
144
144
  - a composer toolbar carries the **model** picker (provider-grouped
145
145
  catalog with a 思考强度 effort section per model) and the **权限**
146
146
  picker (permission presets; 完全权限 requires an explicit confirm
147
147
  step). Both ride the host's own `session.models` /
148
148
  `session.selectModel` RPCs and the `/permission` command — the phone
149
- changes the same session settings the desktop would.
149
+ changes the same session settings the desktop would — plus a **显示**
150
+ sheet with persistent toggles for 工具调用 (tool-call disclosures) and
151
+ 系统提示词 (injected system messages), and a 上下文 usage chip that
152
+ shows the latest assistant answer's context-fill percentage.
150
153
  4. The desktop badge flips to 已连接 in real time; it falls back to
151
154
  offline/断开 when the phone leaves.
152
155
  5. 刷新二维码 invalidates the old link and issues a new one. 停止 revokes
package/README.zh.md CHANGED
@@ -60,8 +60,8 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
60
60
  - 打开会话**按需**抓取聊天内容(历史分页,"加载更早的消息"继续往回翻),
61
61
  - 实时流随消息到达展示新消息,带发送自己消息的输入框(默认 **Enter 发送、Shift+Enter 换行**;设 `mobileEnterToSend: false` 后 Enter 改为换行,发送仅走「发送」按钮),
62
62
  - **亮色优先主题**:界面默认亮色调色板;每个页头内的日/月切换翻到暗色调色板,选择跨访问持久(localStorage),
63
- - 消息按桌面折叠纪律渲染:推理隐藏在被折叠的 深度思考 揭示下面,工具调用隐藏在被折叠的 工具 行下面(点击查看每个调用的参数),超长回答藏在显式 展开全文 切换下面,每行带时间——并且
64
- - 输入栏工具条带 **模型** 选择器(provider 分组目录 + 每模型 思考强度 effort 区)与 **权限** 选择器(权限预设;完全权限 需要显式确认步骤)。两者都走 host 自己的 `session.models` / `session.selectModel` RPC 与 `/permission` 命令——手机改的与桌面改的是同一个会话设置。
63
+ - 消息按桌面折叠纪律渲染:推理隐藏在被折叠的 深度思考 揭示下面,工具调用隐藏在被折叠的 工具 行下面(点击查看每个调用的参数),超长回答藏在显式 展开全文 切换下面,每行带时间,并且 assistant 回复按 GFM Markdown 渲染(标题 / 加粗 / 斜体 / 行内码 / 代码块 / 列表 / 表格 / 引用 / 链接 / 图片;零依赖自写渲染器,先转义再白名单协议,移动端 bundle 体积几乎不变;KaTeX 公式暂不支持,后续单独评估),用户消息保持纯文本,
64
+ - 输入栏工具条带 **模型** 选择器(provider 分组目录 + 每模型 思考强度 effort 区)与 **权限** 选择器(权限预设;完全权限 需要显式确认步骤)。两者都走 host 自己的 `session.models` / `session.selectModel` RPC 与 `/permission` 命令——手机改的与桌面改的是同一个会话设置——外加 **显示** 弹层(含 工具调用 与 系统提示词 两个持久开关)和一个 上下文 用量 chip(显示最近一次助手回答的上下文占用百分比)。
65
65
  4. 桌面徽标实时翻到 已连接;手机离开时回落到离线/断开。
66
66
  5. 刷新二维码 使旧链接失效并铸一枚新的。停止 撤销移动端访问:已配对设备下一次请求 403,包括其实时流。
67
67
 
package/lib/client.js CHANGED
@@ -2219,7 +2219,7 @@ window.__ModuleLoader__.load({
2219
2219
  }
2220
2220
  //#endregion
2221
2221
  //#region \0dsh-css:packages/dsh-remote-web-ui/src/client/settings-card.module.css.mjs
2222
- const css = ".Kwoi6G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Kwoi6G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Kwoi6G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Kwoi6G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Kwoi6G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Kwoi6G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Kwoi6G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Kwoi6G_chevronOpen{transform:rotate(180deg)}.Kwoi6G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Kwoi6G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Kwoi6G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Kwoi6G_discard,.Kwoi6G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Kwoi6G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Kwoi6G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Kwoi6G_discard:disabled,.Kwoi6G_save:disabled{opacity:.4;cursor:default}.Kwoi6G_discard:focus-visible,.Kwoi6G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Kwoi6G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Kwoi6G_field+.Kwoi6G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Kwoi6G_head{align-items:center;gap:8px;display:flex}.Kwoi6G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Kwoi6G_badges{align-items:center;gap:8px;display:inline-flex}.Kwoi6G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Kwoi6G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Kwoi6G_reset:disabled{cursor:default}.Kwoi6G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Kwoi6G_input,.Kwoi6G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_input:focus-visible,.Kwoi6G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Kwoi6G_input:disabled,.Kwoi6G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kwoi6G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Kwoi6G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Kwoi6G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Kwoi6G_card,.Kwoi6G_header,.Kwoi6G_chevron,.Kwoi6G_chevronOpen,.Kwoi6G_discard,.Kwoi6G_save{transition:none}}";
2222
+ const css = ".Kwoi6G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Kwoi6G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Kwoi6G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Kwoi6G_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.Kwoi6G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Kwoi6G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Kwoi6G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Kwoi6G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Kwoi6G_chevronOpen{transform:rotate(180deg)}.Kwoi6G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Kwoi6G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Kwoi6G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Kwoi6G_discard,.Kwoi6G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Kwoi6G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Kwoi6G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Kwoi6G_discard:disabled,.Kwoi6G_save:disabled{opacity:.4;cursor:default}.Kwoi6G_discard:focus-visible,.Kwoi6G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Kwoi6G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Kwoi6G_field+.Kwoi6G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Kwoi6G_head{align-items:center;gap:8px;display:flex}.Kwoi6G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Kwoi6G_badges{align-items:center;gap:8px;display:inline-flex}.Kwoi6G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Kwoi6G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Kwoi6G_reset:disabled{cursor:default}.Kwoi6G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Kwoi6G_input,.Kwoi6G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_input:focus-visible,.Kwoi6G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Kwoi6G_input:disabled,.Kwoi6G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kwoi6G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Kwoi6G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Kwoi6G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Kwoi6G_card,.Kwoi6G_header,.Kwoi6G_chevron,.Kwoi6G_chevronOpen,.Kwoi6G_discard,.Kwoi6G_save{transition:none}}";
2223
2223
  const tagId = "@linxin666/dsh-remote-web-ui/settings-card.module.css";
2224
2224
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2225
2225
  const tag = document.createElement("style");
@@ -2244,6 +2244,7 @@ window.__ModuleLoader__.load({
2244
2244
  "head": "Kwoi6G_head",
2245
2245
  "headText": "Kwoi6G_headText",
2246
2246
  "header": "Kwoi6G_header",
2247
+ "headerStatic": "Kwoi6G_headerStatic",
2247
2248
  "hint": "Kwoi6G_hint",
2248
2249
  "input": "Kwoi6G_input",
2249
2250
  "inputInvalid": "Kwoi6G_inputInvalid",
@@ -2273,24 +2274,42 @@ window.__ModuleLoader__.load({
2273
2274
  * @returns the card, or nothing while the namespace is still loading.
2274
2275
  */
2275
2276
  function PluginSettingsCard(props) {
2276
- const [open, setOpen] = (0, react.useState)(false);
2277
- const { state } = props;
2277
+ const [open, setOpen] = (0, react.useState)(props.defaultOpen ?? true);
2278
+ const { state, alwaysOpen } = props;
2278
2279
  if (!state.available) return null;
2279
2280
  const title = props.t(props.titleKey);
2280
2281
  const description = props.t(props.descriptionKey);
2281
2282
  const blocked = !state.dirty || state.invalid || state.saving;
2282
- const cardClass = open ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
2283
- if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2284
- className: cardClass,
2285
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2286
- type: "button",
2287
- className: settings_card_module_css_default.header,
2288
- "aria-expanded": open,
2289
- "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2290
- onClick: () => {
2291
- setOpen(!open);
2292
- },
2293
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2283
+ const expanded = alwaysOpen === true || open;
2284
+ const cardClass = expanded ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
2285
+ const header = alwaysOpen === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2286
+ className: settings_card_module_css_default.headerStatic,
2287
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2288
+ className: settings_card_module_css_default.headText,
2289
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2290
+ className: settings_card_module_css_default.name,
2291
+ title,
2292
+ children: title
2293
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2294
+ className: settings_card_module_css_default.description,
2295
+ title: description,
2296
+ children: description
2297
+ })]
2298
+ }), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2299
+ className: settings_card_module_css_default.pending,
2300
+ title: props.t("settings.unsaved"),
2301
+ children: props.t("settings.unsaved")
2302
+ }) : null]
2303
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2304
+ type: "button",
2305
+ className: settings_card_module_css_default.header,
2306
+ "aria-expanded": open,
2307
+ "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2308
+ onClick: () => {
2309
+ setOpen(!open);
2310
+ },
2311
+ children: [
2312
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2294
2313
  className: settings_card_module_css_default.headText,
2295
2314
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2296
2315
  className: settings_card_module_css_default.name,
@@ -2301,7 +2320,13 @@ window.__ModuleLoader__.load({
2301
2320
  title: description,
2302
2321
  children: description
2303
2322
  })]
2304
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2323
+ }),
2324
+ state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2325
+ className: settings_card_module_css_default.pending,
2326
+ title: props.t("settings.unsaved"),
2327
+ children: props.t("settings.unsaved")
2328
+ }) : null,
2329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2305
2330
  width: "14",
2306
2331
  height: "14",
2307
2332
  viewBox: "0 0 14 14",
@@ -2312,8 +2337,12 @@ window.__ModuleLoader__.load({
2312
2337
  d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2313
2338
  fill: "currentColor"
2314
2339
  })
2315
- })]
2316
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2340
+ })
2341
+ ]
2342
+ });
2343
+ if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2344
+ className: cardClass,
2345
+ children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2317
2346
  className: settings_card_module_css_default.body,
2318
2347
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2319
2348
  className: settings_card_module_css_default.notExposed,
@@ -2324,46 +2353,7 @@ window.__ModuleLoader__.load({
2324
2353
  });
2325
2354
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2326
2355
  className: cardClass,
2327
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2328
- type: "button",
2329
- className: settings_card_module_css_default.header,
2330
- "aria-expanded": open,
2331
- "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2332
- onClick: () => {
2333
- setOpen(!open);
2334
- },
2335
- children: [
2336
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2337
- className: settings_card_module_css_default.headText,
2338
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2339
- className: settings_card_module_css_default.name,
2340
- title,
2341
- children: title
2342
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2343
- className: settings_card_module_css_default.description,
2344
- title: description,
2345
- children: description
2346
- })]
2347
- }),
2348
- state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2349
- className: settings_card_module_css_default.pending,
2350
- title: props.t("settings.unsaved"),
2351
- children: props.t("settings.unsaved")
2352
- }) : null,
2353
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2354
- width: "14",
2355
- height: "14",
2356
- viewBox: "0 0 14 14",
2357
- fill: "none",
2358
- xmlns: "http://www.w3.org/2000/svg",
2359
- className: open ? `${settings_card_module_css_default.chevron} ${settings_card_module_css_default.chevronOpen}` : settings_card_module_css_default.chevron,
2360
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2361
- d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2362
- fill: "currentColor"
2363
- })
2364
- })
2365
- ]
2366
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2356
+ children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2367
2357
  className: settings_card_module_css_default.body,
2368
2358
  children: [
2369
2359
  !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -2568,6 +2558,9 @@ window.__ModuleLoader__.load({
2568
2558
  specs;
2569
2559
  staged = /* @__PURE__ */ new Map();
2570
2560
  listeners = /* @__PURE__ */ new Set();
2561
+ /** The scope subscription installed in the constructor; released by dispose(). */
2562
+ disposeScope;
2563
+ disposed = false;
2571
2564
  saving = false;
2572
2565
  failed = false;
2573
2566
  failedReason;
@@ -2575,10 +2568,20 @@ window.__ModuleLoader__.load({
2575
2568
  constructor(scope, specs) {
2576
2569
  this.scope = scope;
2577
2570
  this.specs = new Map(specs.map((spec) => [spec.field, spec]));
2578
- scope.subscribe(() => {
2571
+ this.disposeScope = scope.subscribe(() => {
2579
2572
  this.publish();
2580
2573
  });
2581
2574
  }
2575
+ /**
2576
+ * Release the scope subscription and every bound store listener. The card
2577
+ * must call this on teardown; later calls are no-ops.
2578
+ */
2579
+ dispose() {
2580
+ if (this.disposed) return;
2581
+ this.disposed = true;
2582
+ this.disposeScope();
2583
+ this.listeners.clear();
2584
+ }
2582
2585
  /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
2583
2586
  bind(project) {
2584
2587
  const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
@@ -2821,6 +2824,13 @@ window.__ModuleLoader__.load({
2821
2824
  ...this.form.actions()
2822
2825
  };
2823
2826
  }
2827
+ /**
2828
+ * Release the card's scope subscription and bound stores; the slot
2829
+ * disposer calls this on teardown.
2830
+ */
2831
+ dispose() {
2832
+ this.form.dispose();
2833
+ }
2824
2834
  };
2825
2835
  /**
2826
2836
  * Render the remote-control card.
@@ -3359,13 +3369,19 @@ window.__ModuleLoader__.load({
3359
3369
  };
3360
3370
  });
3361
3371
  const remoteSettings = new RemoteSettingsCardController(settingsScope);
3362
- ctx.slots.inject("web-ui.plugin.item", () => ctx.slots.register({
3363
- name: "web-ui.plugin.item",
3364
- id: "remote-web-ui",
3365
- order: 90,
3366
- locale: NS,
3367
- inject: () => remoteSettings.inject()
3368
- }, RemoteSettingsCard));
3372
+ ctx.slots.inject("web-ui.plugin.item", () => {
3373
+ const unregister = ctx.slots.register({
3374
+ name: "web-ui.plugin.item",
3375
+ id: "remote-web-ui",
3376
+ order: 90,
3377
+ locale: NS,
3378
+ inject: () => remoteSettings.inject()
3379
+ }, RemoteSettingsCard);
3380
+ return () => {
3381
+ remoteSettings.dispose();
3382
+ unregister();
3383
+ };
3384
+ });
3369
3385
  let disposeRuntime;
3370
3386
  const syncRuntime = () => {
3371
3387
  if (enabled() && disposeRuntime === void 0) disposeRuntime = ctx.effect(() => {