@prettier-ai/dsh-client-ui-settings-general 0.1.2-alpha.1 → 0.1.2-alpha.2

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.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
  # pnpm run verify-translation-pairing --write packages/client/ui-settings-general/README.md
5
- README.md: f0c4ab7c50798919d42ade0eac6a9a93c8a4df1c
6
- README.zh.md: 912ea0fb16c0d1d512ff846c8eda1e724664f1ea
5
+ README.md: 1d231c2c1de2d8870c6f16e0d993b2387c876e4d
6
+ README.zh.md: 13fe7b9f4a58f0f8836437485333127e76e2410e
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-client-ui-settings-general` is the settings shell of the dsh web client: the Settings panel opens from the sidebar's bottom control with the trigger chrome and modal shell, the navigation is built from the sections features contribute, and first-run users are walked through one onboarding step at a time. It also registers everything on the Settings pages that belongs to no single feature: the trigger/header/close chrome content, the local configuration-file action, the General section and its `settings.general.item` slot, and the `settings` dictionaries. Feature-owned rows (Permission, Language, Appearance), sections (Models), and conditional onboarding steps stay with their feature packages; the shell itself ships no onboarding copy of its own.
12
+ `dsh-client-ui-settings-general` is the settings shell of the dsh web client: the Settings panel opens from the sidebar's bottom control, a connection-failure indicator beside that control offers immediate recovery, the navigation is built from the sections features contribute, and first-run users are walked through one onboarding step at a time. It also registers everything on the Settings pages that belongs to no single feature: the trigger/header/close chrome content, the local configuration-file action, the General section and its `settings.general.item` slot, and the `settings` dictionaries. Feature-owned rows (Permission, Language, Appearance), sections (Models), and conditional onboarding steps stay with their feature packages; the shell itself ships no onboarding copy of its own.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -25,7 +25,7 @@ English | [中文](README.zh.md)
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
- Users reach the shell through the sidebar's bottom Settings control; feature plugins contribute their pages and onboarding steps through the slot ledgers this shell projects. The shell renders the modal panel, the navigation built from `settings.section` entries, and exactly one mounted onboarding step at a time.
28
+ Users reach the shell through the sidebar's bottom Settings control; feature plugins contribute their pages and onboarding steps through the slot ledgers this shell projects. After a Host connection failure, a pale-yellow **Disconnected** action appears to the right of Settings. Automatic recovery shows **Connecting** with one to three dots advancing every 500ms. Hover or keyboard focus changes either yellow label to **Reconnect now** without changing its background; press feedback stays within the warning palette, and selecting it starts retry 1 immediately. Recovery changes the region to pale-green **Connected** for two seconds before it disappears. The icon, left-aligned text origin, height, and width remain fixed across every visible state. Initial startup and uninterrupted healthy operation remain silent. The shell renders the modal panel, the navigation built from `settings.section` entries, and exactly one mounted onboarding step at a time.
29
29
 
30
30
  ### The General section
31
31
 
@@ -53,6 +53,10 @@ The shell owns the chrome and the projections; every piece of content and copy b
53
53
 
54
54
  The navigation is a projection of the `settings.section` ledger; nav labels may be locale-following thunks, resolved through `resolveSlotLabel` and re-rendered on the section ledger bump or the locale revision (an optional `ctx.get('locale')` read; no hard locale dependency). The onboarding ledger projects in ascending order; the active registrant receives its id, `complete()`, and an `openSection(id)` callback, and completing or skipping transfers ownership to the next entry.
55
55
 
56
+ ### Connection recovery
57
+
58
+ The shell is an explicit recovery consumer, so it injects Connection directly rather than adding lifecycle controls to `ctx.remote`. Its private hooks compartment binds `ctx.connection.state`, while the component receives only the selected state and an injected callback for `ctx.connection.reconnect()`. `ConnectionIndicator` owns the inline presentation and receives all visible and accessible copy from the `settings` locale namespace; the shell owns the two-second recovered-state timer.
59
+
56
60
  ### Document availability
57
61
 
58
62
  On a loopback page, the Client loads the provider's `hasDocument` capability through `settings/describe` and renders **Open configuration file** only when the Host confirms that a provider-owned local document can be prepared. The action calls the pathless, browser-authenticated `settings/openSettingsDocument` Remote; the Host resolves the provider path again, materializes an absent document, and hands it to a native text editor (`open -t` on macOS, bypassing a browser file association; the desktop file association on Linux and Windows; Windows association after `wslpath -w` translation on WSL). Open failures keep the action available and render a localized error. Reopening the dialog or reconnecting refreshes availability after a transient read failure or Host topology change. Non-loopback pages retain the Client policy that withholds this native action and its settings read.
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-client-ui-settings-general` 是 dsh Web 客户端的设置外壳:Settings 面板从侧边栏底部的控件打开,带触发控件与模态外壳;导航由各功能贡献的分区构建;首次运行的用户一次只走一个引导步骤。它还注册设置页面上所有不属于单一功能的内容:触发器、标题栏与关闭控件界面框架、「本地配置文件」操作、「通用」分区及其 `settings.general.item` slot,以及 `settings` 字典。归具体功能所有的行(「权限」、「语言」、「外观」)、分区(「模型」)与条件式首次使用引导步骤仍由各自的功能包提供;外壳本身不自带任何引导文案。
12
+ `dsh-client-ui-settings-general` 是 dsh Web 客户端的设置外壳:Settings 面板从侧边栏底部的控件打开,该控件旁的连接故障指示器提供即时恢复操作;导航由各功能贡献的分区构建;首次运行的用户一次只走一个引导步骤。它还注册设置页面上所有不属于单一功能的内容:触发器、标题栏与关闭控件界面框架、「本地配置文件」操作、「通用」分区及其 `settings.general.item` slot,以及 `settings` 字典。归具体功能所有的行(「权限」、「语言」、「外观」)、分区(「模型」)与条件式首次使用引导步骤仍由各自的功能包提供;外壳本身不自带任何引导文案。
13
13
 
14
14
  ## 目录
15
15
 
@@ -25,7 +25,7 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 用户通过侧边栏底部的 Settings 控件进入外壳;功能插件通过本外壳所投影的 slot 账本贡献自己的页面与引导步骤。外壳渲染模态面板、由 `settings.section` 条目构建的导航,以及每次只挂载一个的引导步骤。
28
+ 用户通过侧边栏底部的 Settings 控件进入外壳;功能插件通过本外壳所投影的 slot 账本贡献自己的页面与引导步骤。Host 连接失败后,浅黄色的**连接异常**操作会出现在 Settings 右侧;自动恢复期间显示**连接中**,其后一至三个点每 500ms 前进一次。鼠标悬浮或键盘聚焦任一黄色状态时,只有文案变为**立即重连**,背景保持不变;按压反馈留在黄色色阶内,选中后立即从 retry 1 开始。恢复后该区域变为浅绿色的**连接成功**,驻留 2 秒再消失。所有可见状态的文字都左对齐,且图标、文字起点、高度和宽度保持固定。首次启动与未曾中断的健康连接保持静默。外壳渲染模态面板、由 `settings.section` 条目构建的导航,以及每次只挂载一个的引导步骤。
29
29
 
30
30
  ### 「通用」分区
31
31
 
@@ -53,6 +53,10 @@ kind: "package-reference"
53
53
 
54
54
  导航是 `settings.section` 账本的投影;导航 label 可以是跟随语言的 thunk,经 `resolveSlotLabel` 解析,并在分区账本更新或 locale revision 变化时重新渲染(`ctx.get('locale')` 可选读取,无硬 locale 依赖)。引导账本按升序投影;当前注册方会收到该条目的 id、`complete()` 与 `openSection(id)` 回调,完成或跳过当前步骤后,所有权转交给下一项。
55
55
 
56
+ ### 连接恢复
57
+
58
+ 外壳是明确的恢复功能消费方,因此直接注入 Connection,而不把生命周期控制放进 `ctx.remote`。它的私有 hooks compartment 绑定 `ctx.connection.state`,组件只接收选出的状态与调用 `ctx.connection.reconnect()` 的注入回调。`ConnectionIndicator` 拥有内联展示并从 `settings` locale namespace 接收全部可见与无障碍文案;2 秒恢复状态计时器归外壳所有。
59
+
56
60
  ### 文档可用性
57
61
 
58
62
  在 loopback 页面上,Client 通过 `settings/describe` 加载提供方的 `hasDocument` 能力,且只有在 Host 确认可准备好一份由提供方持有的本地文档时才渲染配置文件操作。该操作调用无路径参数且经浏览器认证的 `settings/openSettingsDocument` Remote;Host 会再次解析提供方路径、在文档缺失时将其创建出来,并交给原生文本编辑器(macOS 上使用 `open -t`,绕过浏览器文件关联;Linux 和 Windows 上使用桌面文件关联;WSL 上经 `wslpath -w` 转换后使用 Windows 文件关联)。打开失败时该操作仍可使用,并渲染本地化错误。临时读取失败或 Host 拓扑变化后,重新打开对话框或重新连接会刷新可用性。非 loopback 页面保留 Client 策略,不提供该原生操作及其 settings 读取。
package/lib/client.js CHANGED
@@ -25,7 +25,7 @@ window.__ModuleLoader__.load({
25
25
  }
26
26
  //#endregion
27
27
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-settings-general/src/client/SettingsRoot.module.css.mjs
28
- const css$3 = ".eUC7PW_trigger{box-sizing:border-box;cursor:pointer;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:12px;flex:none;align-items:center;gap:8px;margin:4px -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.eUC7PW_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.eUC7PW_trigger.eUC7PW_rail{border-radius:50%;justify-content:center;gap:0;width:36px;height:36px;margin:8px 0 10px;padding:0}.eUC7PW_triggerLabel{white-space:nowrap;overflow:hidden}.eUC7PW_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.eUC7PW_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.eUC7PW_panel{z-index:1;background:var(--dsw-alias-bg-layer-2);width:800px;max-width:calc(100vw - 48px);height:min(800px,100vh - 48px);box-shadow:var(--dsw-shadow-lv3);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:24px;display:flex;position:relative;overflow:hidden}.eUC7PW_nav{box-sizing:border-box;flex-direction:column;flex:none;gap:18px;width:188px;padding:22px 12px 0;display:flex}.eUC7PW_navTitle{color:var(--dsw-alias-label-primary);padding:0 12px;font-size:16px;font-weight:500;line-height:24px}.eUC7PW_navList{flex-direction:column;gap:4px;display:flex}.eUC7PW_navCell{box-sizing:border-box;cursor:pointer;height:40px;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:9px 16px 9px 12px;font-family:inherit;font-size:14px;font-weight:400;line-height:22px;display:flex}.eUC7PW_navCell:hover{background:var(--dsw-specific-sidebar-nav-item-hover)}.eUC7PW_navCell.eUC7PW_active{background:var(--dsw-specific-sidebar-nav-item-active)}.eUC7PW_navIcon{flex:none}.eUC7PW_navLabel{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.eUC7PW_content{flex-direction:column;flex:1;min-width:0;display:flex}.eUC7PW_header{box-sizing:border-box;flex:none;justify-content:space-between;align-items:flex-start;gap:8px;height:54px;padding:20px 14px 8px 10px;display:flex}.eUC7PW_actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.eUC7PW_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.eUC7PW_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.eUC7PW_options{flex:1;min-height:0;padding:0 24px 24px;overflow-y:auto}.eUC7PW_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
28
+ const css$3 = ".eUC7PW_triggerRow{flex:none;align-items:center;gap:8px;width:calc(100% + 4px);margin:4px -2px;display:flex}.eUC7PW_triggerRow.eUC7PW_railRow{width:36px;margin:8px 0 10px}.eUC7PW_trigger{box-sizing:border-box;cursor:pointer;width:auto;min-width:0;height:42px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:12px;flex:1;align-items:center;gap:8px;margin:0;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.eUC7PW_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.eUC7PW_trigger.eUC7PW_rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.eUC7PW_triggerLabel{white-space:nowrap;overflow:hidden}.eUC7PW_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.eUC7PW_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.eUC7PW_panel{z-index:1;background:var(--dsw-alias-bg-layer-2);width:800px;max-width:calc(100vw - 48px);height:min(800px,100vh - 48px);box-shadow:var(--dsw-shadow-lv3);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:24px;display:flex;position:relative;overflow:hidden}.eUC7PW_nav{box-sizing:border-box;flex-direction:column;flex:none;gap:18px;width:188px;padding:22px 12px 0;display:flex}.eUC7PW_navTitle{color:var(--dsw-alias-label-primary);padding:0 12px;font-size:16px;font-weight:500;line-height:24px}.eUC7PW_navList{flex-direction:column;gap:4px;display:flex}.eUC7PW_navCell{box-sizing:border-box;cursor:pointer;height:40px;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:9px 16px 9px 12px;font-family:inherit;font-size:14px;font-weight:400;line-height:22px;display:flex}.eUC7PW_navCell:hover{background:var(--dsw-specific-sidebar-nav-item-hover)}.eUC7PW_navCell.eUC7PW_active{background:var(--dsw-specific-sidebar-nav-item-active)}.eUC7PW_navIcon{flex:none}.eUC7PW_navLabel{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.eUC7PW_content{flex-direction:column;flex:1;min-width:0;display:flex}.eUC7PW_header{box-sizing:border-box;flex:none;justify-content:space-between;align-items:flex-start;gap:8px;height:54px;padding:20px 14px 8px 10px;display:flex}.eUC7PW_actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.eUC7PW_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.eUC7PW_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.eUC7PW_options{flex:1;min-height:0;padding:0 24px 24px;overflow-y:auto}.eUC7PW_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
29
29
  const tagId$3 = "@prettier-ai/dsh-client-ui-settings-general/SettingsRoot.module.css";
30
30
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
31
31
  const tag = document.createElement("style");
@@ -52,8 +52,10 @@ window.__ModuleLoader__.load({
52
52
  "overlay": "eUC7PW_overlay",
53
53
  "panel": "eUC7PW_panel",
54
54
  "rail": "eUC7PW_rail",
55
+ "railRow": "eUC7PW_railRow",
55
56
  "trigger": "eUC7PW_trigger",
56
- "triggerLabel": "eUC7PW_triggerLabel"
57
+ "triggerLabel": "eUC7PW_triggerLabel",
58
+ "triggerRow": "eUC7PW_triggerRow"
57
59
  };
58
60
  //#endregion
59
61
  //#region lib/types/client/SettingsRoot.js
@@ -69,6 +71,7 @@ window.__ModuleLoader__.load({
69
71
  * sessions-derived empty-Hero fact is active. Visible dialog chrome belongs
70
72
  * to the step, so a mounted-but-deciding step paints nothing here.
71
73
  */
74
+ const RECOVERY_CONFIRMATION_MS = 2e3;
72
75
  /** Nav glyph by section id; unknown ids fall back to the settings gear. */
73
76
  function navIcon(id) {
74
77
  if (id === "models") return (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconDataOutline16, {
@@ -173,19 +176,28 @@ window.__ModuleLoader__.load({
173
176
  * @returns the settings shell element tree.
174
177
  */
175
178
  function SettingsRoot(props) {
176
- const { wide, useSections, useOnboardingSteps, useSessions, renderSlot } = props;
179
+ const { wide, reconnect, useConnectionState, useSections, useOnboardingSteps, useSessions, renderSlot, t } = props;
177
180
  const [open, setOpen] = (0, react.useState)(false);
178
181
  const [activeId, setActiveId] = (0, react.useState)(void 0);
179
182
  const [completedOnboarding, setCompletedOnboarding] = (0, react.useState)(() => /* @__PURE__ */ new Set());
183
+ const [showRecovery, setShowRecovery] = (0, react.useState)(false);
184
+ const triggerButton = (0, react.useRef)(null);
185
+ const wasOpen = (0, react.useRef)(open);
180
186
  const close = (0, react.useCallback)(() => {
181
187
  setOpen(false);
182
188
  setActiveId(void 0);
183
189
  }, []);
190
+ (0, react.useEffect)(() => {
191
+ if (wasOpen.current && !open) triggerButton.current?.focus();
192
+ wasOpen.current = open;
193
+ }, [open]);
184
194
  const openSection = (0, react.useCallback)((id) => {
185
195
  setActiveId(id);
186
196
  setOpen(true);
187
197
  }, []);
188
198
  const rows = useSections((s) => s);
199
+ const connectionState = useConnectionState((state) => state);
200
+ const previousConnectionState = (0, react.useRef)(connectionState);
189
201
  const onboardingSteps = useOnboardingSteps((s) => s);
190
202
  const onboardingActive = useSessions((state) => state.phase === "ready" && (state.current === void 0 || state.byId[state.current]?.blank === true));
191
203
  const onboardingStep = onboardingActive ? onboardingSteps.find((step) => !completedOnboarding.has(step.id)) : void 0;
@@ -193,22 +205,55 @@ window.__ModuleLoader__.load({
193
205
  if (onboardingActive) return;
194
206
  setCompletedOnboarding(/* @__PURE__ */ new Set());
195
207
  }, [onboardingActive]);
208
+ (0, react.useLayoutEffect)(() => {
209
+ const previous = previousConnectionState.current;
210
+ previousConnectionState.current = connectionState;
211
+ if (connectionState !== "connected") {
212
+ setShowRecovery(false);
213
+ return;
214
+ }
215
+ if (previous !== "disconnected" && previous !== "connecting") return;
216
+ setShowRecovery(true);
217
+ const timeout = window.setTimeout(() => {
218
+ setShowRecovery(false);
219
+ }, RECOVERY_CONFIRMATION_MS);
220
+ return () => {
221
+ window.clearTimeout(timeout);
222
+ };
223
+ }, [connectionState]);
196
224
  const completeOnboardingStep = (0, react.useCallback)((id) => {
197
225
  setCompletedOnboarding((previous) => {
198
226
  if (previous.has(id)) return previous;
199
227
  return new Set([...previous, id]);
200
228
  });
201
229
  }, []);
230
+ let connectionIndicator;
231
+ if (connectionState === "disconnected") connectionIndicator = "disconnected";
232
+ else if (connectionState === "connecting") connectionIndicator = "connecting";
233
+ else if (showRecovery) connectionIndicator = "recovered";
202
234
  return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
203
- (0, react_jsx_runtime.jsx)("button", {
204
- type: "button",
205
- className: clsx(SettingsRoot_module_css_default.trigger, !wide && SettingsRoot_module_css_default.rail),
206
- "aria-haspopup": "dialog",
207
- "aria-expanded": open,
208
- onClick: () => {
209
- setOpen(true);
210
- },
211
- children: renderSlot("settings.trigger", { wide })
235
+ (0, react_jsx_runtime.jsxs)("div", {
236
+ className: clsx(SettingsRoot_module_css_default.triggerRow, !wide && SettingsRoot_module_css_default.railRow),
237
+ children: [(0, react_jsx_runtime.jsx)("button", {
238
+ ref: triggerButton,
239
+ type: "button",
240
+ className: clsx(SettingsRoot_module_css_default.trigger, !wide && SettingsRoot_module_css_default.rail),
241
+ "aria-haspopup": "dialog",
242
+ "aria-expanded": open,
243
+ onClick: () => {
244
+ setOpen(true);
245
+ },
246
+ children: renderSlot("settings.trigger", { wide })
247
+ }), (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.ConnectionIndicator, {
248
+ state: wide ? connectionIndicator : void 0,
249
+ disconnectedLabel: t("connection.error"),
250
+ reconnectLabel: t("connection.retry"),
251
+ connectingLabel: t("connection.connecting"),
252
+ recoveredLabel: t("connection.connected"),
253
+ reconnectActionLabel: t("connection.reconnect"),
254
+ restartActionLabel: t("connection.restart"),
255
+ onReconnect: reconnect
256
+ })]
212
257
  }),
213
258
  open && (0, react_jsx_runtime.jsx)(SettingsPanel, {
214
259
  rows,
@@ -347,12 +392,9 @@ window.__ModuleLoader__.load({
347
392
  //#endregion
348
393
  //#region lib/types/client/settings-document-store.js
349
394
  /** State owner for the optional local settings-document action. */
350
- function messageOf(error) {
351
- return error instanceof Error ? error.message : String(error);
352
- }
353
395
  /** Derives local-document availability from the shared mirror and invokes the pathless Host-owned open operation. */
354
396
  var SettingsDocumentStore = class {
355
- remote;
397
+ ctx;
356
398
  describeFace;
357
399
  /** uSES-safe state source shared by the registered header action. */
358
400
  store = (0, _prettier_ai_dsh_client_store.createSnapshotStore)({
@@ -362,11 +404,12 @@ window.__ModuleLoader__.load({
362
404
  });
363
405
  following;
364
406
  /**
365
- * @param api - loopback settings wire face that opens the provider document.
407
+ * @param ctx - the plugin's context, whose loopback `remote.settings`
408
+ * namespace opens the provider document.
366
409
  * @param describeFace - the shared mirror's describe face (`hasDocument` source).
367
410
  */
368
- constructor(remote, describeFace) {
369
- this.remote = remote;
411
+ constructor(ctx, describeFace) {
412
+ this.ctx = ctx;
370
413
  this.describeFace = describeFace;
371
414
  }
372
415
  /**
@@ -397,12 +440,13 @@ window.__ModuleLoader__.load({
397
440
  state.error = null;
398
441
  });
399
442
  try {
400
- const result = await this.remote.settings.openSettingsDocument();
401
- if (!result.ok) throw new Error(result.error.message);
402
- } catch (error) {
403
- this.store.update((state) => {
404
- state.error = messageOf(error);
405
- });
443
+ const result = await this.ctx.remote.settings.openSettingsDocument();
444
+ if (!result.ok) {
445
+ const { message } = result.error;
446
+ this.store.update((state) => {
447
+ state.error = message;
448
+ });
449
+ }
406
450
  } finally {
407
451
  this.store.update((state) => {
408
452
  state.opening = false;
@@ -440,7 +484,13 @@ window.__ModuleLoader__.load({
440
484
  "close": "关闭",
441
485
  "openDocument": "打开配置文件",
442
486
  "openDocument.error": "无法打开配置文件",
443
- "general.nav": "通用设置"
487
+ "general.nav": "通用设置",
488
+ "connection.error": "连接异常",
489
+ "connection.retry": "立即重连",
490
+ "connection.connecting": "连接中",
491
+ "connection.connected": "连接成功",
492
+ "connection.reconnect": "连接异常,点击立即重连",
493
+ "connection.restart": "连接中,点击立即重连"
444
494
  };
445
495
  /** English dictionary, checked complete against the zh key set. */
446
496
  const en = {
@@ -449,7 +499,13 @@ window.__ModuleLoader__.load({
449
499
  "close": "Close",
450
500
  "openDocument": "Open configuration file",
451
501
  "openDocument.error": "Could not open configuration file",
452
- "general.nav": "General"
502
+ "general.nav": "General",
503
+ "connection.error": "Disconnected",
504
+ "connection.retry": "Reconnect now",
505
+ "connection.connecting": "Connecting",
506
+ "connection.connected": "Connected",
507
+ "connection.reconnect": "Disconnected, reconnect now",
508
+ "connection.restart": "Connecting, restart now"
453
509
  };
454
510
  //#endregion
455
511
  //#region lib/types/client/index.js
@@ -478,8 +534,9 @@ window.__ModuleLoader__.load({
478
534
  zh,
479
535
  en
480
536
  }), "ui-settings-general: dictionaries");
537
+ const connection = ctx.get("connection");
481
538
  const t = ctx.locale.bind(NS);
482
- const documentController = ctx.get("connection").isLoopback ? new SettingsDocumentStore(ctx.remote, ctx.settingsScope.describe()) : void 0;
539
+ const documentController = ctx.remote.$host.isLoopback ? new SettingsDocumentStore(ctx, ctx.settingsScope.describe()) : void 0;
483
540
  const documentInjected = documentController === void 0 ? void 0 : () => ({
484
541
  controller: documentController,
485
542
  hooks: { snapshot: documentController.store }
@@ -492,50 +549,57 @@ window.__ModuleLoader__.load({
492
549
  let rows = [];
493
550
  let onboardingVersion = -1;
494
551
  let onboardingSteps = [];
495
- const shellInjected = () => ({ hooks: {
496
- sections: {
497
- getSnapshot: () => {
498
- const version = ctx.slots.getVersion("settings.section");
499
- const revision = ctx.locale.getSnapshot().revision;
500
- if (version !== rowsVersion || revision !== rowsRevision) {
501
- rowsVersion = version;
502
- rowsRevision = revision;
503
- rows = ctx.slots.entries("settings.section").map((e) => ({
504
- /* v8 ignore next -- list-slot registration requires id (SlotCore rejects an entry without one) */
505
- id: e.options.id ?? "",
506
- order: e.options.order ?? 0,
507
- label: (0, _prettier_ai_dsh_client_ui_slots.resolveSlotLabel)(e.options.label) ?? ""
508
- })).sort((a, b) => a.order - b.order);
509
- }
510
- return rows;
511
- },
512
- subscribe: (listener) => {
513
- const offLedger = ctx.slots.subscribe("settings.section", listener);
514
- const offLocale = ctx.locale.subscribe(listener);
515
- return () => {
516
- offLedger();
517
- offLocale();
518
- };
519
- }
552
+ const shellInjected = () => ({
553
+ reconnect: () => {
554
+ connection.reconnect();
520
555
  },
521
- onboardingSteps: {
522
- getSnapshot: () => {
523
- const version = ctx.slots.getVersion("settings.onboarding");
524
- if (version !== onboardingVersion) {
525
- onboardingVersion = version;
526
- onboardingSteps = ctx.slots.entries("settings.onboarding").map((e) => ({
527
- /* v8 ignore next -- list-slot registration requires id */
528
- id: e.options.id ?? "",
529
- order: e.options.order ?? 0
530
- })).sort((a, b) => a.order - b.order);
556
+ hooks: {
557
+ connectionState: connection.state,
558
+ sections: {
559
+ getSnapshot: () => {
560
+ const version = ctx.slots.getVersion("settings.section");
561
+ const revision = ctx.locale.getSnapshot().revision;
562
+ if (version !== rowsVersion || revision !== rowsRevision) {
563
+ rowsVersion = version;
564
+ rowsRevision = revision;
565
+ rows = ctx.slots.entries("settings.section").map((e) => ({
566
+ /* v8 ignore next -- list-slot registration requires id (SlotCore rejects an entry without one) */
567
+ id: e.options.id ?? "",
568
+ order: e.options.order ?? 0,
569
+ label: (0, _prettier_ai_dsh_client_ui_slots.resolveSlotLabel)(e.options.label) ?? ""
570
+ })).sort((a, b) => a.order - b.order);
571
+ }
572
+ return rows;
573
+ },
574
+ subscribe: (listener) => {
575
+ const offLedger = ctx.slots.subscribe("settings.section", listener);
576
+ const offLocale = ctx.locale.subscribe(listener);
577
+ return () => {
578
+ offLedger();
579
+ offLocale();
580
+ };
531
581
  }
532
- return onboardingSteps;
533
582
  },
534
- subscribe: (listener) => ctx.slots.subscribe("settings.onboarding", listener)
583
+ onboardingSteps: {
584
+ getSnapshot: () => {
585
+ const version = ctx.slots.getVersion("settings.onboarding");
586
+ if (version !== onboardingVersion) {
587
+ onboardingVersion = version;
588
+ onboardingSteps = ctx.slots.entries("settings.onboarding").map((e) => ({
589
+ /* v8 ignore next -- list-slot registration requires id */
590
+ id: e.options.id ?? "",
591
+ order: e.options.order ?? 0
592
+ })).sort((a, b) => a.order - b.order);
593
+ }
594
+ return onboardingSteps;
595
+ },
596
+ subscribe: (listener) => ctx.slots.subscribe("settings.onboarding", listener)
597
+ }
535
598
  }
536
- } });
599
+ });
537
600
  ctx.slots.inject("sidebar.settings", () => ctx.slots.register({
538
601
  name: "sidebar.settings",
602
+ locale: NS,
539
603
  children: {
540
604
  "settings.trigger": {
541
605
  kind: "single",
package/lib/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import z from "@prettier-ai/schemastery";
2
- import { settingsNamespace } from "@prettier-ai/dsh-settings";
3
2
  //#region lib/types/index.js
4
3
  /** Host loader entry for the browser implementation exported from `./client`. */
5
4
  /** Durable settings namespace for product-wide GUI onboarding facts. */
@@ -8,7 +7,7 @@ const OnboardingSettingsSchema = z.object({ welcomeNoticeVersion: z.string() });
8
7
  /** Register the durable GUI-onboarding section when a settings provider exists. */
9
8
  function apply(ctx) {
10
9
  ctx.inject(["settings"], (settingsCtx) => {
11
- settingsCtx.settings.register(settingsNamespace(ONBOARDING_SETTINGS_NAMESPACE), OnboardingSettingsSchema);
10
+ settingsCtx.settings.register(ONBOARDING_SETTINGS_NAMESPACE, OnboardingSettingsSchema);
12
11
  });
13
12
  }
14
13
  //#endregion
@@ -7,6 +7,12 @@ export declare const zh: {
7
7
  openDocument: string;
8
8
  'openDocument.error': string;
9
9
  'general.nav': string;
10
+ 'connection.error': string;
11
+ 'connection.retry': string;
12
+ 'connection.connecting': string;
13
+ 'connection.connected': string;
14
+ 'connection.reconnect': string;
15
+ 'connection.restart': string;
10
16
  };
11
17
  /** The settings namespace key union. */
12
18
  export type SettingsKey = keyof typeof zh;
@@ -18,5 +24,11 @@ export declare const en: {
18
24
  openDocument: string;
19
25
  'openDocument.error': string;
20
26
  'general.nav': string;
27
+ 'connection.error': string;
28
+ 'connection.retry': string;
29
+ 'connection.connecting': string;
30
+ 'connection.connected': string;
31
+ 'connection.reconnect': string;
32
+ 'connection.restart': string;
21
33
  };
22
34
  //# sourceMappingURL=locales.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /** State owner for the optional local settings-document action. */
2
- import type { ClientRemote } from '@prettier-ai/dsh-api-remotes/client';
2
+ import type { Context as ClientContext } from '@prettier-ai/cordis';
3
3
  import { type SnapshotStore } from '@prettier-ai/dsh-client-store';
4
4
  import type { SettingsDescribeFace } from '@prettier-ai/dsh-client-ui-settings/client';
5
5
  /** Browser state of the Host-owned settings document. */
@@ -13,16 +13,17 @@ export interface SettingsDocumentState {
13
13
  }
14
14
  /** Derives local-document availability from the shared mirror and invokes the pathless Host-owned open operation. */
15
15
  export declare class SettingsDocumentStore {
16
- private readonly remote;
16
+ private readonly ctx;
17
17
  private readonly describeFace;
18
18
  /** uSES-safe state source shared by the registered header action. */
19
19
  readonly store: SnapshotStore<SettingsDocumentState>;
20
20
  private following;
21
21
  /**
22
- * @param api - loopback settings wire face that opens the provider document.
22
+ * @param ctx - the plugin's context, whose loopback `remote.settings`
23
+ * namespace opens the provider document.
23
24
  * @param describeFace - the shared mirror's describe face (`hasDocument` source).
24
25
  */
25
- constructor(remote: Pick<ClientRemote, 'settings'>, describeFace: SettingsDescribeFace);
26
+ constructor(ctx: ClientContext, describeFace: SettingsDescribeFace);
26
27
  /**
27
28
  * Begin following the mirror (idempotent) and reflect whether the current
28
29
  * provider owns a local document.
@@ -6,7 +6,8 @@
6
6
  * reference graph closes a cycle through ui-sidebar → ui-layout → ui-theme.
7
7
  * The settings SLOT types (what registrants contribute) stay in ui-settings.
8
8
  */
9
- import type { HostObservable, InjectFace, PropsRenderSlots, PropsRuntime } from '@prettier-ai/dsh-client-ui-slots';
9
+ import type { ConnectionState } from '@prettier-ai/dsh-client-connection/client';
10
+ import type { HostObservable, InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime } from '@prettier-ai/dsh-client-ui-slots';
10
11
  /** One nav row projected from a settings.section registration's options. */
11
12
  export interface SettingsSectionRow {
12
13
  id: string;
@@ -20,11 +21,15 @@ export interface SettingsOnboardingStep {
20
21
  }
21
22
  /**
22
23
  * Registrant-private injected share of the settings shell (assembled in
23
- * apply): the ledger's nav-row projection as a hooks-compartment source —
24
- * the shell reads no locale state and subscribes through the bound hook.
24
+ * apply): connection state and ledger projections arrive as hook-compartment
25
+ * sources, while the reconnect command remains a plain callback.
25
26
  */
26
27
  export type SettingsRootInjected = {
28
+ /** Request a fresh logical generation and physical WebSocket immediately. */
29
+ reconnect: () => void;
27
30
  hooks: {
31
+ /** Connection-owned state for the current Host connection. */
32
+ connectionState: HostObservable<ConnectionState | undefined>;
28
33
  /** settings.section ledger projected into ordered nav rows. */
29
34
  sections: HostObservable<readonly SettingsSectionRow[]>;
30
35
  /** settings.onboarding ledger projected into coordinator order. */
@@ -37,5 +42,5 @@ export type SettingsRootInjected = {
37
42
  * (hooks compartment bound to useSections). No store is registered — modal
38
43
  * open state and active section id are component-local viewing state.
39
44
  */
40
- export type SettingsRootComponentProps = PropsRuntime<'sidebar.settings'> & PropsRenderSlots<'settings.trigger' | 'settings.header' | 'settings.action' | 'settings.close' | 'settings.section' | 'settings.onboarding'> & InjectFace<SettingsRootInjected>;
45
+ export type SettingsRootComponentProps = PropsRuntime<'sidebar.settings'> & PropsRenderSlots<'settings.trigger' | 'settings.header' | 'settings.action' | 'settings.close' | 'settings.section' | 'settings.onboarding'> & InjectFace<SettingsRootInjected> & PropsLocale<'settings'>;
41
46
  //# sourceMappingURL=shell-contract.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prettier-ai/dsh-client-ui-settings-general",
3
3
  "description": "Settings ownerless-copy and product onboarding plugin: the General section, shell trigger/header chrome content, settings dictionaries, and the versioned welcome notice",
4
- "version": "0.1.2-alpha.1",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -44,37 +44,28 @@
44
44
  "license": "MIT",
45
45
  "dependencies": {
46
46
  "clsx": "^2.0.0",
47
- "@prettier-ai/schemastery": "^3.18.1"
47
+ "@prettier-ai/schemastery": "^3.18.2"
48
48
  },
49
49
  "peerDependencies": {
50
- "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.1",
51
- "@prettier-ai/dsh-client-ui-settings": "^0.1.2-alpha.1",
52
- "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
53
- "@prettier-ai/cordis": "^4.0.1",
54
- "@prettier-ai/dsh-client-connection": "^0.1.2-alpha.1",
55
- "@prettier-ai/dsh-client-ui-sidebar": "^0.1.2-alpha.1",
56
- "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
57
- "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
58
- "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.1",
59
- "@prettier-ai/dsh-settings": "^0.1.2-alpha.1"
50
+ "@prettier-ai/cordis": "^4.0.2"
60
51
  },
61
52
  "devDependencies": {
62
53
  "@types/react": "~18.3.1",
63
54
  "react": "^18.2.0",
64
- "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.1",
65
- "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
66
- "@prettier-ai/dsh-client-connection": "^0.1.2-alpha.1",
67
- "@prettier-ai/dsh-client-store": "^0.1.2-alpha.1",
68
- "@prettier-ai/dsh-client-test-runtime": "^0.1.2-alpha.1",
69
- "@prettier-ai/dsh-client-ui-settings": "^0.1.2-alpha.1",
70
- "@prettier-ai/dsh-client-ui-primitives": "^0.1.2-alpha.1",
71
- "@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.1",
72
- "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
73
- "@prettier-ai/cordis": "^4.0.1",
74
- "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
75
- "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.1",
76
- "@prettier-ai/dsh-client-ui-sidebar": "^0.1.2-alpha.1",
77
- "@prettier-ai/dsh-settings": "^0.1.2-alpha.1"
55
+ "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.2",
56
+ "@prettier-ai/dsh-client-connection": "^0.1.2-alpha.2",
57
+ "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.2",
58
+ "@prettier-ai/dsh-client-store": "^0.1.2-alpha.2",
59
+ "@prettier-ai/dsh-client-test-runtime": "^0.1.2-alpha.2",
60
+ "@prettier-ai/dsh-client-ui-primitives": "^0.1.2-alpha.2",
61
+ "@prettier-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
62
+ "@prettier-ai/dsh-client-ui-sidebar": "^0.1.2-alpha.2",
63
+ "@prettier-ai/dsh-invariants": "^0.1.2-alpha.2",
64
+ "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.2",
65
+ "@prettier-ai/cordis": "^4.0.2",
66
+ "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.2",
67
+ "@prettier-ai/dsh-settings": "^0.1.2-alpha.2",
68
+ "@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.2"
78
69
  },
79
70
  "files": [
80
71
  "lib/index.js",