@deepseek-ai/dsh-client-ui-settings-general 0.0.1-rc.1 → 0.0.1-rc.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: ab27e073dc76335efc619f56365d1705007f7ef2
6
- README.zh.md: 16ff5604bee5425569b783e27a29699344f630e3
5
+ README.md: 0cdffdfaad20784535a7ed010ad4b71d63a0a2c1
6
+ README.zh.md: 0b72b3db96b335f0c288758b3f7a8b56441ee88c
package/README.md CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- Settings ownerless-copy and product-onboarding plugin: registers everything on the Settings surface that belongs to no single feature — the shell's trigger/header/close chrome content, the local configuration-file action, the General section and its `settings.general.item` slot, the `settings` dictionaries, and the first ordered welcome step. Feature-owned rows (Permission, Language, Appearance), sections (Models), and conditional onboarding steps stay with their feature packages.
5
+ Settings shell, ownerless-copy, and product-onboarding plugin. It occupies `sidebar.settings` with the trigger chrome and modal settings panel, projects the `settings.section` ledger into the navigation and the `settings.onboarding` ledger into one mounted page at a time, and 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, the `settings` dictionaries, and the first ordered welcome step. The slot types it renders into belong to ui-settings, the settings domain base; only the shell's own contract types live here, because they reference ui-sidebar's slot type and the base layer must depend on no `ui-*` package. Feature-owned rows (Permission, Language, Appearance), sections (Models), and conditional onboarding steps stay with their feature packages.
6
+
7
+ The shell ships no copy of its own — all text arrives from registrants. Nav labels may be locale-following thunks, so the nav projection resolves them through `resolveSlotLabel` and re-renders 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 and mounts exactly one page at a time; the takeover chrome (body-level stage, mask, app-root `inert`) belongs to the step itself through ui-primitives' `OnboardingSurface`, so a mounted step still resolving its private facts renders null and neither paints nor blocks anything — the shell shows no empty stage while a step decides. The active registrant receives its id, `complete()`, and an `openSection(id)` callback; completing or skipping transfers ownership to the next entry. Registrants own durable completion, capability readiness, copy, mutations, and the surface wrap, so independently registered flows cannot stack and the shell does not become a second configuration fact source.
6
8
 
7
9
  A loopback browser 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 sends the pathless, loopback-only `settings.openDocument` request; 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. Remote browsers never register the action and never issue the privileged settings read.
8
10
 
9
- `src/onboarding-copy.ts` is the single editable owner of the complete notice plus `WELCOME_NOTICE_VERSION`; both supported GUI locales intentionally render the same Chinese copy. The Host half registers `ui-onboarding` in the user-settings seam. A loopback browser compares `welcomeNoticeVersion` for exact equality and writes the current value only after Continue succeeds. The path mutation is idempotent across tabs and preserves sibling settings, while `host/settings-changed` makes an externally acknowledged notice advance without a reload. A non-loopback browser cannot access the privileged settings API: it still presents the notice, but Continue advances only the current browser process and a reload presents the notice again. A different version deliberately presents the notice again. The welcome page preserves every authored paragraph, gives the requested clause in the final paragraph the sole emphasis, initially focuses the title, and has no close, Escape, mask-click, or secondary path. None of its copy or acknowledgement enters a Session log or model request. The notice identifies `DSH_TELEMETRY_DISABLED=1` as the telemetry opt-out.
11
+ `src/onboarding-copy.ts` is the single editable owner of the complete notice plus `WELCOME_NOTICE_VERSION`; both supported GUI locales intentionally render the same Chinese copy. The Host half registers `ui-onboarding` in the user-settings seam. A loopback browser compares `welcomeNoticeVersion` for exact equality and writes the current value only after Continue succeeds. The path mutation is idempotent across tabs and preserves sibling settings, while the forwarded `settings/document-updated` event makes an externally acknowledged notice advance without a reload. A non-loopback browser cannot access the privileged settings API: it still presents the notice, but Continue advances only the current browser process and a reload presents the notice again. A different version deliberately presents the notice again. The welcome page preserves every authored paragraph, gives the requested clause in the final paragraph the sole emphasis, initially focuses the title, and has no close, Escape, mask-click, or secondary path. None of its copy or acknowledgement enters a Session log or model request. The notice identifies `DSH_TELEMETRY_DISABLED=1` as the telemetry opt-out.
10
12
 
11
13
  ## Model Experience
12
14
 
package/README.zh.md CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 设置界面无特定功能归属的文案与产品引导插件:在设置界面注册所有不属于单一功能的内容,包括外壳的触发器、标题栏与关闭控件内容、本地配置文件操作,「通用」分区及其 `settings.general.item` slot、`settings` 字典,以及第一个有序欢迎步骤。归具体功能所有的行(「权限」、「语言」、「外观」)、分区(「模型」)和条件式首次使用引导步骤仍由各自的功能包提供。
5
+ 设置外壳、无特定功能归属文案与产品引导插件。它以触发控件和模态设置面板占用 `sidebar.settings`,把 `settings.section` 账本投影成导航、把 `settings.onboarding` 账本投影成每次只挂载一页的引导流程,并在设置页面上注册所有不属于单一功能的内容:触发器、标题栏与关闭控件内容、本地配置文件操作,「通用」分区及其 `settings.general.item` slot、`settings` 字典,以及第一个有序欢迎步骤。它渲染进的那些 slot 类型归 ui-settings——设置领域底座——所有;只有外壳自身的契约类型放在这里,因为它们引用 ui-sidebar 的 slot 类型,而底座不得依赖任何 `ui-*` 包。归具体功能所有的行(「权限」、「语言」、「外观」)、分区(「模型」)和条件式首次使用引导步骤仍由各自的功能包提供。
6
+
7
+ 外壳不自带文案:所有文本都来自注册方。导航 label 可以是跟随语言的 thunk,因此导航投影经 `resolveSlotLabel` 解析,并在分区账本更新或 locale revision 变化时重新渲染(`ctx.get('locale')` 可选读取,无硬 locale 依赖)。首次使用引导记录按升序投影,每次只挂载一个页面;接管界面框架(body 层级的展示层、遮罩、应用根节点 `inert`)经 ui-primitives 的 `OnboardingSurface` 由步骤自身持有,因此已挂载但仍在判定私有事实的步骤渲染 null 时不绘制也不阻塞任何内容——步骤判定期间外壳不会露出空白展示层。当前注册方会收到该条目的 id、`complete()` 和 `openSection(id)` 回调;完成或跳过当前页面后,所有权转交给下一项。持久化完成状态、能力就绪状态、文案、变更操作以及展示层包装均由注册方持有,因此独立注册的流程无法堆叠,外壳也不会成为第二个配置事实来源。
6
8
 
7
9
  回环浏览器通过 `settings.describe` 加载提供方的 `hasDocument` 能力,且只有在 Host 确认可准备好一份由提供方持有的本地文档时才渲染**打开配置文件**。该操作发送无路径参数且仅限回环访问的 `settings.openDocument` 请求;Host 会再次解析提供方路径、在文档缺失时将其创建出来,并交给原生文本编辑器(macOS 上使用 `open -t`,绕过浏览器文件关联;Linux 和 Windows 上使用桌面文件关联;WSL 上经 `wslpath -w` 转换后使用 Windows 文件关联)。打开失败时该操作仍可使用,并渲染本地化错误。临时读取失败或 Host 拓扑变化后,重新打开对话框或重新连接会刷新可用性。远程浏览器从不注册该操作,也从不发起这项特权设置读取。
8
10
 
9
- `src/onboarding-copy.ts` 是完整通知文案和 `WELCOME_NOTICE_VERSION` 的唯一可编辑来源;GUI 支持的两种 locale 都有意渲染同一份中文文案。宿主端在用户设置 seam 中注册 `ui-onboarding`。回环浏览器会比较 `welcomeNoticeVersion` 是否精确相等,仅在「继续」操作成功后写入当前值。该路径变更在不同标签页间幂等,并会保留同级设置;`host/settings-changed` 则让页面在通知被外部确认后,无需重新加载即可推进。非回环浏览器不能访问受保护的设置 API:它仍会显示通知,但「继续」只推进当前浏览器进程,重新加载后会再次显示通知。版本不同时,系统也会有意重新显示通知。欢迎页保留原文的每个段落,仅强调最后一段中指定的句段,初始焦点落在标题上,并且没有关闭操作、Escape、点击遮罩或次要操作路径。其文案和确认状态均不会进入会话日志或模型请求。通知明确以 `DSH_TELEMETRY_DISABLED=1` 作为遥测关闭方式。
11
+ `src/onboarding-copy.ts` 是完整通知文案和 `WELCOME_NOTICE_VERSION` 的唯一可编辑来源;GUI 支持的两种 locale 都有意渲染同一份中文文案。宿主端在用户设置 seam 中注册 `ui-onboarding`。回环浏览器会比较 `welcomeNoticeVersion` 是否精确相等,仅在「继续」操作成功后写入当前值。该路径变更在不同标签页间幂等,并会保留同级设置;转发的 `settings/document-updated` 事件则让页面在通知被外部确认后,无需重新加载即可推进。非回环浏览器不能访问受保护的设置 API:它仍会显示通知,但「继续」只推进当前浏览器进程,重新加载后会再次显示通知。版本不同时,系统也会有意重新显示通知。欢迎页保留原文的每个段落,仅强调最后一段中指定的句段,初始焦点落在标题上,并且没有关闭操作、Escape、点击遮罩或次要操作路径。其文案和确认状态均不会进入会话日志或模型请求。通知明确以 `DSH_TELEMETRY_DISABLED=1` 作为遥测关闭方式。
10
12
 
11
13
  ## 模型体验
12
14
 
package/lib/client.js CHANGED
@@ -4,11 +4,231 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_ui_slots = require("@deepseek-ai/dsh-client-ui-slots");
7
8
  let _deepseek_ai_dsh_client_web_react = require("@deepseek-ai/dsh-client-web-react");
8
9
  let react_jsx_runtime = require("react/jsx-runtime");
9
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
10
  let react = require("react");
11
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
12
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
13
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
14
+ function r(e) {
15
+ var t, f, n = "";
16
+ if ("string" == typeof e || "number" == typeof e) n += e;
17
+ else if ("object" == typeof e) if (Array.isArray(e)) {
18
+ var o = e.length;
19
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
20
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
21
+ return n;
22
+ }
23
+ function clsx() {
24
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
25
+ return n;
26
+ }
27
+ //#endregion
28
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-settings-general/src/client/SettingsRoot.module.css.mjs
29
+ const css$4 = ".VOzbGW_trigger{cursor:pointer;width:100%;height:49px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:12px;flex:none;align-items:center;gap:8px;margin:8px 0 0;padding:0 2px 0 6px;font-family:inherit;font-size:14px;display:flex;overflow:hidden}.VOzbGW_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.VOzbGW_trigger.VOzbGW_rail{border-radius:50%;justify-content:center;gap:0;width:36px;height:36px;margin:18px 0 10px;padding:0}.VOzbGW_triggerLabel{white-space:nowrap;overflow:hidden}.VOzbGW_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.VOzbGW_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.VOzbGW_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}.VOzbGW_nav{box-sizing:border-box;flex-direction:column;flex:none;gap:18px;width:188px;padding:22px 12px 0;display:flex}.VOzbGW_navTitle{color:var(--dsw-alias-label-primary);padding:0 12px;font-size:16px;font-weight:500;line-height:24px}.VOzbGW_navList{flex-direction:column;gap:4px;display:flex}.VOzbGW_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}.VOzbGW_navCell:hover{background:var(--dsw-specific-sidebar-nav-item-hover)}.VOzbGW_navCell.VOzbGW_active{background:var(--dsw-specific-sidebar-nav-item-active)}.VOzbGW_navIcon{flex:none}.VOzbGW_navLabel{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.VOzbGW_content{flex-direction:column;flex:1;min-width:0;display:flex}.VOzbGW_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}.VOzbGW_actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.VOzbGW_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}.VOzbGW_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.VOzbGW_options{flex:1;min-height:0;padding:0 24px 24px;overflow-y:auto}.VOzbGW_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
30
+ const tagId$4 = "@deepseek-ai/dsh-client-ui-settings-general/SettingsRoot.module.css";
31
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
32
+ const tag = document.createElement("style");
33
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-settings-general";
34
+ tag.dataset.pluginCss = tagId$4;
35
+ tag.textContent = css$4;
36
+ document.head.appendChild(tag);
37
+ }
38
+ var SettingsRoot_module_css_default = {
39
+ "triggerLabel": "VOzbGW_triggerLabel",
40
+ "panel": "VOzbGW_panel",
41
+ "trigger": "VOzbGW_trigger",
42
+ "overlay": "VOzbGW_overlay",
43
+ "navTitle": "VOzbGW_navTitle",
44
+ "close": "VOzbGW_close",
45
+ "hiddenLabel": "VOzbGW_hiddenLabel",
46
+ "nav": "VOzbGW_nav",
47
+ "rail": "VOzbGW_rail",
48
+ "mask": "VOzbGW_mask",
49
+ "actions": "VOzbGW_actions",
50
+ "active": "VOzbGW_active",
51
+ "navList": "VOzbGW_navList",
52
+ "options": "VOzbGW_options",
53
+ "navIcon": "VOzbGW_navIcon",
54
+ "header": "VOzbGW_header",
55
+ "content": "VOzbGW_content",
56
+ "navCell": "VOzbGW_navCell",
57
+ "navLabel": "VOzbGW_navLabel"
58
+ };
59
+ //#endregion
60
+ //#region lib/types/client/SettingsRoot.js
61
+ /**
62
+ * Settings shell root: the sidebar-foot trigger row plus the centered modal
63
+ * panel (figma 501:29947, 1080x700) with the section nav rail. The shell is
64
+ * a pure composition face — every piece of text (trigger label, panel title,
65
+ * close label, sections) arrives from registrants through slots; accessible
66
+ * names resolve to that content (trigger: its own text; dialog:
67
+ * aria-labelledby the title node; close: visually-hidden slot text). Modal
68
+ * open state and the active section id are component-local viewing state;
69
+ * the onboarding coordinator mounts exactly one ordered registrant while the
70
+ * sessions-derived empty-Hero fact is active — the takeover chrome
71
+ * (OnboardingSurface) belongs to the step, so a mounted-but-deciding step
72
+ * paints nothing here.
73
+ */
74
+ /** Nav glyph by section id; unknown ids fall back to the settings gear. */
75
+ function navIcon(id) {
76
+ if (id === "models") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutline16, {
77
+ className: SettingsRoot_module_css_default.navIcon,
78
+ size: 16
79
+ });
80
+ if (id === "agent-presets") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconAgentPresetOutline16, {
81
+ className: SettingsRoot_module_css_default.navIcon,
82
+ size: 16
83
+ });
84
+ if (id === "plugins") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, {
85
+ className: SettingsRoot_module_css_default.navIcon,
86
+ size: 16
87
+ });
88
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, {
89
+ className: SettingsRoot_module_css_default.navIcon,
90
+ size: 16
91
+ });
92
+ }
93
+ /**
94
+ * The modal layer: full-viewport mask + centered panel. Close paths: the
95
+ * header button, a mask click, and document-level Escape (mounted only while
96
+ * open, so the listener lifetime is the panel's).
97
+ */
98
+ function SettingsPanel({ rows, renderSlot, activeId, onSelect, onClose }) {
99
+ const active = rows.find((r) => r.id === activeId)?.id ?? rows[0]?.id;
100
+ const titleId = (0, react.useId)();
101
+ (0, react.useEffect)(() => {
102
+ const onKeyDown = (e) => {
103
+ if (e.key === "Escape") onClose();
104
+ };
105
+ document.addEventListener("keydown", onKeyDown);
106
+ return () => {
107
+ document.removeEventListener("keydown", onKeyDown);
108
+ };
109
+ }, [onClose]);
110
+ const closeButton = (0, react.useRef)(null);
111
+ (0, react.useEffect)(() => {
112
+ closeButton.current?.focus();
113
+ }, []);
114
+ return (0, react_jsx_runtime.jsxs)("div", {
115
+ className: SettingsRoot_module_css_default.overlay,
116
+ role: "presentation",
117
+ children: [(0, react_jsx_runtime.jsx)("div", {
118
+ className: SettingsRoot_module_css_default.mask,
119
+ "aria-hidden": "true",
120
+ onClick: onClose
121
+ }), (0, react_jsx_runtime.jsxs)("div", {
122
+ className: SettingsRoot_module_css_default.panel,
123
+ role: "dialog",
124
+ "aria-modal": "true",
125
+ "aria-labelledby": titleId,
126
+ children: [(0, react_jsx_runtime.jsxs)("nav", {
127
+ className: SettingsRoot_module_css_default.nav,
128
+ children: [(0, react_jsx_runtime.jsx)("div", {
129
+ className: SettingsRoot_module_css_default.navTitle,
130
+ id: titleId,
131
+ children: renderSlot("settings.header", {})
132
+ }), (0, react_jsx_runtime.jsx)("div", {
133
+ className: SettingsRoot_module_css_default.navList,
134
+ children: rows.map((row) => (0, react_jsx_runtime.jsxs)("button", {
135
+ type: "button",
136
+ className: clsx(SettingsRoot_module_css_default.navCell, row.id === active && SettingsRoot_module_css_default.active),
137
+ "aria-current": row.id === active ? "true" : void 0,
138
+ onClick: () => {
139
+ onSelect(row.id);
140
+ },
141
+ children: [navIcon(row.id), (0, react_jsx_runtime.jsx)("span", {
142
+ className: SettingsRoot_module_css_default.navLabel,
143
+ children: row.label
144
+ })]
145
+ }, row.id))
146
+ })]
147
+ }), (0, react_jsx_runtime.jsxs)("div", {
148
+ className: SettingsRoot_module_css_default.content,
149
+ children: [(0, react_jsx_runtime.jsxs)("div", {
150
+ className: SettingsRoot_module_css_default.header,
151
+ children: [(0, react_jsx_runtime.jsx)("div", {
152
+ className: SettingsRoot_module_css_default.actions,
153
+ children: renderSlot("settings.action", {})
154
+ }), (0, react_jsx_runtime.jsxs)("button", {
155
+ ref: closeButton,
156
+ type: "button",
157
+ className: SettingsRoot_module_css_default.close,
158
+ onClick: onClose,
159
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 }), (0, react_jsx_runtime.jsx)("span", {
160
+ className: SettingsRoot_module_css_default.hiddenLabel,
161
+ children: renderSlot("settings.close", {})
162
+ })]
163
+ })]
164
+ }), (0, react_jsx_runtime.jsx)("div", {
165
+ className: SettingsRoot_module_css_default.options,
166
+ children: active !== void 0 && renderSlot("settings.section", { close: onClose }, { only: active })
167
+ })]
168
+ })]
169
+ })]
170
+ });
171
+ }
172
+ /**
173
+ * Render the settings trigger and panel.
174
+ * @param props - composed slot props (contract/slots.ts).
175
+ * @returns the settings shell element tree.
176
+ */
177
+ function SettingsRoot(props) {
178
+ const { wide, useSections, useOnboardingSteps, useSessions, renderSlot } = props;
179
+ const [open, setOpen] = (0, react.useState)(false);
180
+ const [activeId, setActiveId] = (0, react.useState)(void 0);
181
+ const [completedOnboarding, setCompletedOnboarding] = (0, react.useState)(() => /* @__PURE__ */ new Set());
182
+ const close = (0, react.useCallback)(() => {
183
+ setOpen(false);
184
+ setActiveId(void 0);
185
+ }, []);
186
+ const openSection = (0, react.useCallback)((id) => {
187
+ setActiveId(id);
188
+ setOpen(true);
189
+ }, []);
190
+ const rows = useSections((s) => s);
191
+ const onboardingSteps = useOnboardingSteps((s) => s);
192
+ const onboardingActive = useSessions((state) => state.phase === "ready" && (state.current === void 0 || state.byId[state.current]?.blank === true));
193
+ const onboardingStep = onboardingActive ? onboardingSteps.find((step) => !completedOnboarding.has(step.id)) : void 0;
194
+ (0, react.useEffect)(() => {
195
+ if (onboardingActive) return;
196
+ setCompletedOnboarding(/* @__PURE__ */ new Set());
197
+ }, [onboardingActive]);
198
+ const completeOnboardingStep = (0, react.useCallback)((id) => {
199
+ setCompletedOnboarding((previous) => {
200
+ if (previous.has(id)) return previous;
201
+ return new Set([...previous, id]);
202
+ });
203
+ }, []);
204
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
205
+ (0, react_jsx_runtime.jsx)("button", {
206
+ type: "button",
207
+ className: clsx(SettingsRoot_module_css_default.trigger, !wide && SettingsRoot_module_css_default.rail),
208
+ "aria-haspopup": "dialog",
209
+ "aria-expanded": open,
210
+ onClick: () => {
211
+ setOpen(true);
212
+ },
213
+ children: renderSlot("settings.trigger", { wide })
214
+ }),
215
+ open && (0, react_jsx_runtime.jsx)(SettingsPanel, {
216
+ rows,
217
+ renderSlot,
218
+ activeId,
219
+ onSelect: setActiveId,
220
+ onClose: close
221
+ }),
222
+ onboardingStep !== void 0 && renderSlot("settings.onboarding", {
223
+ stepId: onboardingStep.id,
224
+ complete: () => {
225
+ completeOnboardingStep(onboardingStep.id);
226
+ },
227
+ openSection
228
+ }, { only: onboardingStep.id })
229
+ ] });
230
+ }
231
+ //#endregion
12
232
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-settings-general/src/client/chrome.module.css.mjs
13
233
  const css$3 = ".UQsH_q_triggerLabel{white-space:nowrap;overflow:hidden}";
14
234
  const tagId$3 = "@deepseek-ai/dsh-client-ui-settings-general/chrome.module.css";
@@ -225,16 +445,16 @@ window.__ModuleLoader__.load({
225
445
  document.head.appendChild(tag);
226
446
  }
227
447
  var WelcomeNotice_module_css_default = {
228
- "page": "k2cPoG_page",
229
448
  "error": "k2cPoG_error",
230
449
  "title": "k2cPoG_title",
231
- "footer": "k2cPoG_footer",
232
- "brand": "k2cPoG_brand",
233
- "welcome-enter": "k2cPoG_welcome-enter",
234
450
  "feedback": "k2cPoG_feedback",
235
- "primary": "k2cPoG_primary",
236
451
  "reflection": "k2cPoG_reflection",
237
- "opening": "k2cPoG_opening"
452
+ "page": "k2cPoG_page",
453
+ "footer": "k2cPoG_footer",
454
+ "primary": "k2cPoG_primary",
455
+ "welcome-enter": "k2cPoG_welcome-enter",
456
+ "opening": "k2cPoG_opening",
457
+ "brand": "k2cPoG_brand"
238
458
  };
239
459
  //#endregion
240
460
  //#region lib/types/client/WelcomeNotice.js
@@ -522,7 +742,8 @@ window.__ModuleLoader__.load({
522
742
  const inject = [
523
743
  "slots",
524
744
  "locale",
525
- "connection"
745
+ "connection",
746
+ "remote"
526
747
  ];
527
748
  /**
528
749
  * Register the `settings` dictionaries, the chrome content, and the General
@@ -551,11 +772,13 @@ window.__ModuleLoader__.load({
551
772
  useSnapshot: useWelcomeSnapshot
552
773
  });
553
774
  ctx.effect(() => {
554
- const refresh = (ns) => {
555
- if (ns !== void 0 && ns !== "ui-onboarding") return;
775
+ const refresh = () => {
556
776
  refreshWelcomeIfLoaded(welcomeController);
557
777
  };
558
- const disposers = [ctx.on("settings/changed", refresh), ctx.on("connection/reset", () => {
778
+ const disposers = [ctx.remote.$on("settings/document-updated", (ns) => {
779
+ if (ns !== "ui-onboarding") return;
780
+ refresh();
781
+ }), ctx.on("connection/reset", () => {
559
782
  refresh();
560
783
  refreshDocumentIfLoaded(documentController);
561
784
  })];
@@ -563,6 +786,83 @@ window.__ModuleLoader__.load({
563
786
  for (const dispose of disposers) dispose();
564
787
  };
565
788
  }, "ui-settings-general: metadata invalidations");
789
+ let rowsVersion = -1;
790
+ let rowsRevision = -1;
791
+ let rows = [];
792
+ let onboardingVersion = -1;
793
+ let onboardingSteps = [];
794
+ const shellInjected = () => ({ hooks: {
795
+ sections: {
796
+ getSnapshot: () => {
797
+ const version = ctx.slots.getVersion("settings.section");
798
+ const revision = ctx.locale.getSnapshot().revision;
799
+ if (version !== rowsVersion || revision !== rowsRevision) {
800
+ rowsVersion = version;
801
+ rowsRevision = revision;
802
+ rows = ctx.slots.entries("settings.section").map((e) => ({
803
+ /* v8 ignore next -- list-slot registration requires id (SlotCore rejects an entry without one) */
804
+ id: e.options.id ?? "",
805
+ order: e.options.order ?? 0,
806
+ label: (0, _deepseek_ai_dsh_client_ui_slots.resolveSlotLabel)(e.options.label) ?? ""
807
+ })).sort((a, b) => a.order - b.order);
808
+ }
809
+ return rows;
810
+ },
811
+ subscribe: (listener) => {
812
+ const offLedger = ctx.slots.subscribe("settings.section", listener);
813
+ const offLocale = ctx.locale.subscribe(listener);
814
+ return () => {
815
+ offLedger();
816
+ offLocale();
817
+ };
818
+ }
819
+ },
820
+ onboardingSteps: {
821
+ getSnapshot: () => {
822
+ const version = ctx.slots.getVersion("settings.onboarding");
823
+ if (version !== onboardingVersion) {
824
+ onboardingVersion = version;
825
+ onboardingSteps = ctx.slots.entries("settings.onboarding").map((e) => ({
826
+ /* v8 ignore next -- list-slot registration requires id */
827
+ id: e.options.id ?? "",
828
+ order: e.options.order ?? 0
829
+ })).sort((a, b) => a.order - b.order);
830
+ }
831
+ return onboardingSteps;
832
+ },
833
+ subscribe: (listener) => ctx.slots.subscribe("settings.onboarding", listener)
834
+ }
835
+ } });
836
+ ctx.slots.inject("sidebar.settings", () => ctx.slots.register({
837
+ name: "sidebar.settings",
838
+ children: {
839
+ "settings.trigger": {
840
+ kind: "single",
841
+ scope: "root"
842
+ },
843
+ "settings.header": {
844
+ kind: "single",
845
+ scope: "root"
846
+ },
847
+ "settings.action": {
848
+ kind: "list",
849
+ scope: "root"
850
+ },
851
+ "settings.close": {
852
+ kind: "single",
853
+ scope: "root"
854
+ },
855
+ "settings.section": {
856
+ kind: "list",
857
+ scope: "root"
858
+ },
859
+ "settings.onboarding": {
860
+ kind: "list",
861
+ scope: "root"
862
+ }
863
+ },
864
+ inject: shellInjected
865
+ }, SettingsRoot));
566
866
  ctx.slots.inject("settings.trigger", () => ctx.slots.register({
567
867
  name: "settings.trigger",
568
868
  locale: NS
@@ -0,0 +1,8 @@
1
+ import type { SettingsRootComponentProps } from './shell-contract.ts';
2
+ /**
3
+ * Render the settings trigger and panel.
4
+ * @param props - composed slot props (contract/slots.ts).
5
+ * @returns the settings shell element tree.
6
+ */
7
+ export declare function SettingsRoot(props: SettingsRootComponentProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=SettingsRoot.d.ts.map
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Settings ownerless-copy plugin, browser half: registers everything on the
3
- * Settings surface that belongs to no single feature — the trigger/header
4
- * chrome content, local-document action, General section, and `settings`
5
- * dictionaries. Feature-owned rows and sections stay with their features.
2
+ * Settings shell and ownerless-copy plugin, browser half: renders the
3
+ * `sidebar.settings` occupant — panel chrome, section navigation, and the
4
+ * onboarding stage — and registers everything on the Settings pages that
5
+ * belongs to no single feature: the trigger/header chrome content,
6
+ * local-document action, General section, and `settings` dictionaries.
7
+ * Feature-owned rows and sections stay with their features.
6
8
  * Export discipline: packages/client/AGENTS.md.
7
9
  */
8
10
  import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Settings shell contract — the types of the `sidebar.settings` occupant this
3
+ * package renders. They live here rather than in ui-settings because they
4
+ * reference the sidebar's own slot type: ui-settings is the settings domain's
5
+ * base layer and must not depend on any `ui-*` presentation package, or the
6
+ * reference graph closes a cycle through ui-sidebar → ui-layout → ui-theme.
7
+ * The settings SLOT types (what registrants contribute) stay in ui-settings.
8
+ */
9
+ import type { HostObservable, InjectFace, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
10
+ /** One nav row projected from a settings.section registration's options. */
11
+ export interface SettingsSectionRow {
12
+ id: string;
13
+ order: number;
14
+ label: string;
15
+ }
16
+ /** One ordered onboarding step projected from a slot registration. */
17
+ export interface SettingsOnboardingStep {
18
+ id: string;
19
+ order: number;
20
+ }
21
+ /**
22
+ * 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.
25
+ */
26
+ export type SettingsRootInjected = {
27
+ hooks: {
28
+ /** settings.section ledger projected into ordered nav rows. */
29
+ sections: HostObservable<readonly SettingsSectionRow[]>;
30
+ /** settings.onboarding ledger projected into coordinator order. */
31
+ onboardingSteps: HostObservable<readonly SettingsOnboardingStep[]>;
32
+ };
33
+ };
34
+ /**
35
+ * Full component props of the settings shell root: the sidebar owner share
36
+ * (wide/rail state) plus the declared render shares and the injected face
37
+ * (hooks compartment bound to useSections). No store is registered — modal
38
+ * open state and active section id are component-local viewing state.
39
+ */
40
+ export type SettingsRootComponentProps = PropsRuntime<'sidebar.settings'> & PropsRenderSlots<'settings.trigger' | 'settings.header' | 'settings.action' | 'settings.close' | 'settings.section' | 'settings.onboarding'> & InjectFace<SettingsRootInjected>;
41
+ //# sourceMappingURL=shell-contract.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-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.0.1-rc.1",
4
+ "version": "0.0.1-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },
@@ -35,41 +35,48 @@
35
35
  "@deepseek-ai/dsh-client-runtime",
36
36
  "@deepseek-ai/dsh-client-ui-settings",
37
37
  "@deepseek-ai/dsh-client-locale",
38
- "@deepseek-ai/dsh-client-connection"
38
+ "@deepseek-ai/dsh-client-connection",
39
+ "@deepseek-ai/dsh-api-remotes",
40
+ "@deepseek-ai/dsh-client-ui-sidebar"
39
41
  ],
40
42
  "platform": "web"
41
43
  }
42
44
  },
43
45
  "license": "BSD-3-Clause",
44
46
  "dependencies": {
45
- "@deepseek-ai/dsh-settings": "^0.0.1-rc.1",
47
+ "clsx": "^2.0.0",
48
+ "@deepseek-ai/dsh-settings": "^0.0.1-rc.2",
46
49
  "@deepseek-ai/schemastery": "^3.18.1-rc.1"
47
50
  },
48
51
  "peerDependencies": {
49
52
  "react": "^18.2.0",
50
- "@deepseek-ai/dsh-client-connection": "^0.0.1-rc.1",
51
- "@deepseek-ai/dsh-client-locale": "^0.0.1-rc.1",
52
- "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.1",
53
- "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.1",
54
- "@deepseek-ai/dsh-client-ui-settings": "^0.0.1-rc.1",
55
- "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.1",
56
- "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.1",
57
- "@deepseek-ai/cordis": "^4.0.1-rc.1",
58
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1"
53
+ "@deepseek-ai/dsh-api-remotes": "^0.0.1-rc.2",
54
+ "@deepseek-ai/dsh-client-connection": "^0.0.1-rc.2",
55
+ "@deepseek-ai/dsh-client-locale": "^0.0.1-rc.2",
56
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.2",
57
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.2",
58
+ "@deepseek-ai/dsh-client-ui-settings": "^0.0.1-rc.2",
59
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.0.1-rc.2",
60
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.2",
61
+ "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.2",
62
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.2",
63
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
59
64
  },
60
65
  "devDependencies": {
61
66
  "@types/react": "~18.3.1",
62
67
  "react": "^18.2.0",
63
- "@deepseek-ai/dsh-client-connection": "^0.0.1-rc.1",
64
- "@deepseek-ai/dsh-client-locale": "^0.0.1-rc.1",
65
- "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.1",
66
- "@deepseek-ai/dsh-client-test-runtime": "^0.0.1-rc.1",
67
- "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.1",
68
- "@deepseek-ai/dsh-client-ui-settings": "^0.0.1-rc.1",
69
- "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.1",
70
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
71
- "@deepseek-ai/cordis": "^4.0.1-rc.1",
72
- "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.1"
68
+ "@deepseek-ai/dsh-api-remotes": "^0.0.1-rc.2",
69
+ "@deepseek-ai/dsh-client-connection": "^0.0.1-rc.2",
70
+ "@deepseek-ai/dsh-client-locale": "^0.0.1-rc.2",
71
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.2",
72
+ "@deepseek-ai/dsh-client-test-runtime": "^0.0.1-rc.2",
73
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.2",
74
+ "@deepseek-ai/dsh-client-ui-settings": "^0.0.1-rc.2",
75
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.0.1-rc.2",
76
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.2",
77
+ "@deepseek-ai/dsh-client-web-react": "^0.0.1-rc.2",
78
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.2",
79
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
73
80
  },
74
81
  "files": [
75
82
  "lib/index.js",