@deepseek-ai/dsh-client-ui-settings 0.0.1-rc.1

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/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DeepSeek
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-settings/README.md
5
+ README.md: 785f0417f00ec8eb1f8c9273b4d81f8ca5ca1810
6
+ README.zh.md: 239987e379aaabd2edbfc9e09cd2fc8cef7a685e
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @deepseek-ai/dsh-client-ui-settings
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Settings shell plugin: a pure composition face. It occupies `sidebar.settings` with the trigger chrome and modal settings panel, and declares the slots registrants fill: `settings.trigger` / `settings.header` / `settings.close` (chrome content), `settings.action` (ordered content-header actions), `settings.section` (one page per feature), and `settings.onboarding` (ordered feature-owned pages in a full-viewport stage). The shell ships no copy of its own — all text arrives from registrants (ui-settings-general owns chrome, General, and the product notice; features own their actions, sections, rows, and conditional onboarding pages). 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).
6
+
7
+ The shell projects the onboarding ledger into 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.
8
+
9
+ ## Model Experience
10
+
11
+ None, as the settings shell serves browser UI composition; nothing here reaches a model request.
12
+
13
+ #### KV Cache effect
14
+
15
+ None; this package neither assembles nor sends a provider request.
16
+
17
+ ## Known Limitations and Deferred Work
18
+
19
+ - **Panel is browser-preference scope only** — host-side settings surfaces (permission mode, tool-call mode) have no RPC backing yet; their skeletons live in ui-settings-general.
package/README.zh.md ADDED
@@ -0,0 +1,19 @@
1
+ # @deepseek-ai/dsh-client-ui-settings
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 设置外壳插件:一个纯组合表层。它以触发控件和模态设置面板占用 `sidebar.settings`,并声明由注册方填充的 slot:`settings.trigger`/`settings.header`/`settings.close`(界面框架内容)、`settings.action`(内容标题栏中的有序操作)、`settings.section`(每项功能一页)和 `settings.onboarding`(由各功能持有、显示在全视口展示层中的有序页面)。外壳不自带文案:所有文本都来自注册方(ui-settings-general 拥有界面框架、「通用」分区和产品声明;各功能拥有各自的操作、分区、行和条件式首次使用引导页面)。导航 label 可以是跟随语言的 thunk,因此导航投影经 `resolveSlotLabel` 解析,并在分区账本更新或 locale revision 变化时重新渲染(`ctx.get('locale')` 可选读取,无硬 locale 依赖)。
6
+
7
+ 外壳将首次使用引导记录按升序投影,每次只挂载一个页面;接管界面框架(body 层级的展示层、遮罩、应用根节点 `inert`)经 ui-primitives 的 `OnboardingSurface` 由步骤自身持有,因此已挂载但仍在判定私有事实的步骤渲染 null 时不绘制也不阻塞任何内容——步骤判定期间外壳不会露出空白展示层。当前注册方会收到该条目的 id、`complete()` 和 `openSection(id)` 回调;完成或跳过当前页面后,所有权转交给下一项。持久化完成状态、能力就绪状态、文案、变更操作以及展示层包装均由注册方持有,因此独立注册的流程无法堆叠,外壳也不会成为第二个配置事实来源。
8
+
9
+ ## 模型体验
10
+
11
+ 无。设置外壳为浏览器 UI 提供组合能力;这里没有任何内容进入模型请求。
12
+
13
+ #### KV Cache 影响
14
+
15
+ 无;该包既不组装也不发送提供方请求。
16
+
17
+ ## 已知限制与暂缓事项
18
+
19
+ - **面板仅涵盖浏览器偏好设置**:宿主侧设置表层(权限模式、工具调用模式)尚无 RPC 支撑;其骨架位于 ui-settings-general。
package/lib/client.js ADDED
@@ -0,0 +1,326 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@deepseek-ai/dsh-client-ui-settings",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_ui_slots = require("@deepseek-ai/dsh-client-ui-slots");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let react = require("react");
10
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
12
+ function r(e) {
13
+ var t, f, n = "";
14
+ if ("string" == typeof e || "number" == typeof e) n += e;
15
+ else if ("object" == typeof e) if (Array.isArray(e)) {
16
+ var o = e.length;
17
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
18
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
19
+ return n;
20
+ }
21
+ function clsx() {
22
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
23
+ return n;
24
+ }
25
+ //#endregion
26
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-settings/src/client/SettingsRoot.module.css.mjs
27
+ const css = ".Gph0iW_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}.Gph0iW_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.Gph0iW_trigger.Gph0iW_rail{border-radius:50%;justify-content:center;gap:0;width:36px;height:36px;margin:18px 0 10px;padding:0}.Gph0iW_triggerLabel{white-space:nowrap;overflow:hidden}.Gph0iW_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.Gph0iW_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.Gph0iW_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}.Gph0iW_nav{box-sizing:border-box;flex-direction:column;flex:none;gap:18px;width:188px;padding:22px 12px 0;display:flex}.Gph0iW_navTitle{color:var(--dsw-alias-label-primary);padding:0 12px;font-size:16px;font-weight:500;line-height:24px}.Gph0iW_navList{flex-direction:column;gap:4px;display:flex}.Gph0iW_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}.Gph0iW_navCell:hover{background:var(--dsw-specific-sidebar-nav-item-hover)}.Gph0iW_navCell.Gph0iW_active{background:var(--dsw-specific-sidebar-nav-item-active)}.Gph0iW_navIcon{flex:none}.Gph0iW_navLabel{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.Gph0iW_content{flex-direction:column;flex:1;min-width:0;display:flex}.Gph0iW_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}.Gph0iW_actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.Gph0iW_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}.Gph0iW_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.Gph0iW_options{flex:1;min-height:0;padding:0 24px 8px;overflow-y:auto}.Gph0iW_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
28
+ const tagId = "@deepseek-ai/dsh-client-ui-settings/SettingsRoot.module.css";
29
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
30
+ const tag = document.createElement("style");
31
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-settings";
32
+ tag.dataset.pluginCss = tagId;
33
+ tag.textContent = css;
34
+ document.head.appendChild(tag);
35
+ }
36
+ var SettingsRoot_module_css_default = {
37
+ "hiddenLabel": "Gph0iW_hiddenLabel",
38
+ "active": "Gph0iW_active",
39
+ "panel": "Gph0iW_panel",
40
+ "navList": "Gph0iW_navList",
41
+ "header": "Gph0iW_header",
42
+ "nav": "Gph0iW_nav",
43
+ "trigger": "Gph0iW_trigger",
44
+ "navCell": "Gph0iW_navCell",
45
+ "content": "Gph0iW_content",
46
+ "rail": "Gph0iW_rail",
47
+ "overlay": "Gph0iW_overlay",
48
+ "navTitle": "Gph0iW_navTitle",
49
+ "triggerLabel": "Gph0iW_triggerLabel",
50
+ "mask": "Gph0iW_mask",
51
+ "navIcon": "Gph0iW_navIcon",
52
+ "actions": "Gph0iW_actions",
53
+ "close": "Gph0iW_close",
54
+ "navLabel": "Gph0iW_navLabel",
55
+ "options": "Gph0iW_options"
56
+ };
57
+ //#endregion
58
+ //#region lib/types/client/SettingsRoot.js
59
+ /**
60
+ * Settings shell root: the sidebar-foot trigger row plus the centered modal
61
+ * panel (figma 501:29947, 1080x700) with the section nav rail. The shell is
62
+ * a pure composition face — every piece of text (trigger label, panel title,
63
+ * close label, sections) arrives from registrants through slots; accessible
64
+ * names resolve to that content (trigger: its own text; dialog:
65
+ * aria-labelledby the title node; close: visually-hidden slot text). Modal
66
+ * open state and the active section id are component-local viewing state;
67
+ * the onboarding coordinator mounts exactly one ordered registrant while the
68
+ * sessions-derived empty-Hero fact is active — the takeover chrome
69
+ * (OnboardingSurface) belongs to the step, so a mounted-but-deciding step
70
+ * paints nothing here.
71
+ */
72
+ /** Nav glyph by section id; unknown ids fall back to the settings gear. */
73
+ function navIcon(id) {
74
+ if (id === "models") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutline16, {
75
+ className: SettingsRoot_module_css_default.navIcon,
76
+ size: 16
77
+ });
78
+ if (id === "agent-presets") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline16, {
79
+ className: SettingsRoot_module_css_default.navIcon,
80
+ size: 16
81
+ });
82
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, {
83
+ className: SettingsRoot_module_css_default.navIcon,
84
+ size: 16
85
+ });
86
+ }
87
+ /**
88
+ * The modal layer: full-viewport mask + centered panel. Close paths: the
89
+ * header button, a mask click, and document-level Escape (mounted only while
90
+ * open, so the listener lifetime is the panel's).
91
+ */
92
+ function SettingsPanel({ rows, renderSlot, activeId, onSelect, onClose }) {
93
+ const active = rows.find((r) => r.id === activeId)?.id ?? rows[0]?.id;
94
+ const titleId = (0, react.useId)();
95
+ (0, react.useEffect)(() => {
96
+ const onKeyDown = (e) => {
97
+ if (e.key === "Escape") onClose();
98
+ };
99
+ document.addEventListener("keydown", onKeyDown);
100
+ return () => {
101
+ document.removeEventListener("keydown", onKeyDown);
102
+ };
103
+ }, [onClose]);
104
+ const closeButton = (0, react.useRef)(null);
105
+ (0, react.useEffect)(() => {
106
+ closeButton.current?.focus();
107
+ }, []);
108
+ return (0, react_jsx_runtime.jsxs)("div", {
109
+ className: SettingsRoot_module_css_default.overlay,
110
+ role: "presentation",
111
+ children: [(0, react_jsx_runtime.jsx)("div", {
112
+ className: SettingsRoot_module_css_default.mask,
113
+ "aria-hidden": "true",
114
+ onClick: onClose
115
+ }), (0, react_jsx_runtime.jsxs)("div", {
116
+ className: SettingsRoot_module_css_default.panel,
117
+ role: "dialog",
118
+ "aria-modal": "true",
119
+ "aria-labelledby": titleId,
120
+ children: [(0, react_jsx_runtime.jsxs)("nav", {
121
+ className: SettingsRoot_module_css_default.nav,
122
+ children: [(0, react_jsx_runtime.jsx)("div", {
123
+ className: SettingsRoot_module_css_default.navTitle,
124
+ id: titleId,
125
+ children: renderSlot("settings.header", {})
126
+ }), (0, react_jsx_runtime.jsx)("div", {
127
+ className: SettingsRoot_module_css_default.navList,
128
+ children: rows.map((row) => (0, react_jsx_runtime.jsxs)("button", {
129
+ type: "button",
130
+ className: clsx(SettingsRoot_module_css_default.navCell, row.id === active && SettingsRoot_module_css_default.active),
131
+ "aria-current": row.id === active ? "true" : void 0,
132
+ onClick: () => {
133
+ onSelect(row.id);
134
+ },
135
+ children: [navIcon(row.id), (0, react_jsx_runtime.jsx)("span", {
136
+ className: SettingsRoot_module_css_default.navLabel,
137
+ children: row.label
138
+ })]
139
+ }, row.id))
140
+ })]
141
+ }), (0, react_jsx_runtime.jsxs)("div", {
142
+ className: SettingsRoot_module_css_default.content,
143
+ children: [(0, react_jsx_runtime.jsxs)("div", {
144
+ className: SettingsRoot_module_css_default.header,
145
+ children: [(0, react_jsx_runtime.jsx)("div", {
146
+ className: SettingsRoot_module_css_default.actions,
147
+ children: renderSlot("settings.action", {})
148
+ }), (0, react_jsx_runtime.jsxs)("button", {
149
+ ref: closeButton,
150
+ type: "button",
151
+ className: SettingsRoot_module_css_default.close,
152
+ onClick: onClose,
153
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 }), (0, react_jsx_runtime.jsx)("span", {
154
+ className: SettingsRoot_module_css_default.hiddenLabel,
155
+ children: renderSlot("settings.close", {})
156
+ })]
157
+ })]
158
+ }), (0, react_jsx_runtime.jsx)("div", {
159
+ className: SettingsRoot_module_css_default.options,
160
+ children: active !== void 0 && renderSlot("settings.section", { close: onClose }, { only: active })
161
+ })]
162
+ })]
163
+ })]
164
+ });
165
+ }
166
+ /**
167
+ * Render the settings trigger and panel.
168
+ * @param props - composed slot props (contract/slots.ts).
169
+ * @returns the settings shell element tree.
170
+ */
171
+ function SettingsRoot(props) {
172
+ const { wide, useSections, useOnboardingSteps, useSessions, renderSlot } = props;
173
+ const [open, setOpen] = (0, react.useState)(false);
174
+ const [activeId, setActiveId] = (0, react.useState)(void 0);
175
+ const [completedOnboarding, setCompletedOnboarding] = (0, react.useState)(() => /* @__PURE__ */ new Set());
176
+ const close = (0, react.useCallback)(() => {
177
+ setOpen(false);
178
+ setActiveId(void 0);
179
+ }, []);
180
+ const openSection = (0, react.useCallback)((id) => {
181
+ setActiveId(id);
182
+ setOpen(true);
183
+ }, []);
184
+ const rows = useSections((s) => s);
185
+ const onboardingSteps = useOnboardingSteps((s) => s);
186
+ const onboardingActive = useSessions((state) => state.phase === "ready" && (state.current === void 0 || state.byId[state.current]?.blank === true));
187
+ const onboardingStep = onboardingActive ? onboardingSteps.find((step) => !completedOnboarding.has(step.id)) : void 0;
188
+ (0, react.useEffect)(() => {
189
+ if (onboardingActive) return;
190
+ setCompletedOnboarding(/* @__PURE__ */ new Set());
191
+ }, [onboardingActive]);
192
+ const completeOnboardingStep = (0, react.useCallback)((id) => {
193
+ setCompletedOnboarding((previous) => {
194
+ if (previous.has(id)) return previous;
195
+ return new Set([...previous, id]);
196
+ });
197
+ }, []);
198
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
199
+ (0, react_jsx_runtime.jsx)("button", {
200
+ type: "button",
201
+ className: clsx(SettingsRoot_module_css_default.trigger, !wide && SettingsRoot_module_css_default.rail),
202
+ "aria-haspopup": "dialog",
203
+ "aria-expanded": open,
204
+ onClick: () => {
205
+ setOpen(true);
206
+ },
207
+ children: renderSlot("settings.trigger", { wide })
208
+ }),
209
+ open && (0, react_jsx_runtime.jsx)(SettingsPanel, {
210
+ rows,
211
+ renderSlot,
212
+ activeId,
213
+ onSelect: setActiveId,
214
+ onClose: close
215
+ }),
216
+ onboardingStep !== void 0 && renderSlot("settings.onboarding", {
217
+ stepId: onboardingStep.id,
218
+ complete: () => {
219
+ completeOnboardingStep(onboardingStep.id);
220
+ },
221
+ openSection
222
+ }, { only: onboardingStep.id })
223
+ ] });
224
+ }
225
+ //#endregion
226
+ //#region lib/types/client/index.js
227
+ /**
228
+ * Required services (cordis fiber inject). The target slot is declared by
229
+ * ui-sidebar's apply, whose activation order relative to this one is NOT
230
+ * constrained (dsh.client.inject edges are informational); registration
231
+ * depends on the slot through `slots.inject()`.
232
+ */
233
+ const inject = ["slots"];
234
+ /**
235
+ * Register the settings shell into `sidebar.settings` once the declaration is
236
+ * on the ledger.
237
+ * @param ctx - client root context.
238
+ */
239
+ function apply(ctx) {
240
+ let rowsVersion = -1;
241
+ let rowsRevision = -1;
242
+ let rows = [];
243
+ let onboardingVersion = -1;
244
+ let onboardingSteps = [];
245
+ const localeRevision = () => ctx.get("locale")?.getSnapshot().revision ?? 0;
246
+ const injected = () => ({ hooks: {
247
+ sections: {
248
+ getSnapshot: () => {
249
+ const version = ctx.slots.getVersion("settings.section");
250
+ const revision = localeRevision();
251
+ if (version !== rowsVersion || revision !== rowsRevision) {
252
+ rowsVersion = version;
253
+ rowsRevision = revision;
254
+ rows = ctx.slots.entries("settings.section").map((e) => ({
255
+ /* v8 ignore next -- list-slot registration requires id (SlotCore rejects an entry without one) */
256
+ id: e.options.id ?? "",
257
+ order: e.options.order ?? 0,
258
+ label: (0, _deepseek_ai_dsh_client_ui_slots.resolveSlotLabel)(e.options.label) ?? ""
259
+ })).sort((a, b) => a.order - b.order);
260
+ }
261
+ return rows;
262
+ },
263
+ subscribe: (listener) => {
264
+ const offLedger = ctx.slots.subscribe("settings.section", listener);
265
+ const offLocale = ctx.get("locale")?.subscribe(listener);
266
+ return () => {
267
+ offLedger();
268
+ offLocale?.();
269
+ };
270
+ }
271
+ },
272
+ onboardingSteps: {
273
+ getSnapshot: () => {
274
+ const version = ctx.slots.getVersion("settings.onboarding");
275
+ if (version !== onboardingVersion) {
276
+ onboardingVersion = version;
277
+ onboardingSteps = ctx.slots.entries("settings.onboarding").map((e) => ({
278
+ /* v8 ignore next -- list-slot registration requires id */
279
+ id: e.options.id ?? "",
280
+ order: e.options.order ?? 0
281
+ })).sort((a, b) => a.order - b.order);
282
+ }
283
+ return onboardingSteps;
284
+ },
285
+ subscribe: (listener) => ctx.slots.subscribe("settings.onboarding", listener)
286
+ }
287
+ } });
288
+ ctx.slots.inject("sidebar.settings", () => ctx.slots.register({
289
+ name: "sidebar.settings",
290
+ children: {
291
+ "settings.trigger": {
292
+ kind: "single",
293
+ scope: "root"
294
+ },
295
+ "settings.header": {
296
+ kind: "single",
297
+ scope: "root"
298
+ },
299
+ "settings.action": {
300
+ kind: "list",
301
+ scope: "root"
302
+ },
303
+ "settings.close": {
304
+ kind: "single",
305
+ scope: "root"
306
+ },
307
+ "settings.section": {
308
+ kind: "list",
309
+ scope: "root"
310
+ },
311
+ "settings.onboarding": {
312
+ kind: "list",
313
+ scope: "root"
314
+ }
315
+ },
316
+ inject: injected
317
+ }, SettingsRoot));
318
+ }
319
+ //#endregion
320
+ exports.apply = apply;
321
+ exports.inject = inject;
322
+ return module.exports;
323
+ }
324
+ });
325
+
326
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ //#region lib/types/index.js
2
+ /** Host loader entry for the browser implementation exported from `./client`. */
3
+ /** Host plugin body — no host-side behavior for the settings shell plugin. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,25 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-settings`.
4
+ * @module @deepseek-ai/dsh-client-ui-settings/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-client-ui-settings";
7
+ /** Cordis companion plugin name. */
8
+ const name = "client-ui-settings-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: a presentation shell projecting the settings.section
13
+ * ledger into navigation — it emits no cordis events and owns no cross-plugin
14
+ * mutable relation; slot declaration/registration conflicts already fail loud
15
+ * in the slot core at load time.
16
+ */
17
+ const install = () => {};
18
+ /**
19
+ * Register this package's invariant companion.
20
+ * @param ctx - Cordis context carrying the invariant service.
21
+ * @returns the installed registration's disposer after setup succeeds.
22
+ */
23
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
24
+ //#endregion
25
+ export { apply, inject, name };
@@ -0,0 +1,8 @@
1
+ import type { SettingsRootComponentProps } from './contract/slots.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
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Settings shell slot contract — the canonical home of every settings slot
3
+ * type. The shell is a pure composition face with zero copy of its own: it
4
+ * occupies the sidebar-owned `sidebar.settings` hole and declares the slots
5
+ * below; ALL text (trigger label, panel title, header actions, close aria,
6
+ * section content) arrives from registrants. A feature owns its settings surface — adding a
7
+ * setting never means editing the shell; copy that belongs to no single
8
+ * feature (chrome, the General section) is owned by ui-settings-general.
9
+ */
10
+ import type { HostObservable, InjectFace, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
11
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
12
+ interface SlotMap {
13
+ /**
14
+ * The sidebar-foot trigger row content: icon + label, supplied as slot
15
+ * content (the accessible name comes from the content — rail state
16
+ * renders the label visually hidden). The shell renders the button
17
+ * chrome and owns open state. Absent contribution degrades to an
18
+ * icon-only button without an accessible name (broken-composition state;
19
+ * the shipped composition always registers the seat).
20
+ */
21
+ 'settings.trigger': {
22
+ kind: 'single';
23
+ scope: 'root';
24
+ owner: SettingsTriggerOwnerProps;
25
+ };
26
+ /**
27
+ * The panel title text seat. Content renders inside the nav heading row;
28
+ * the dialog's accessible name points at that node via aria-labelledby.
29
+ * Absent contribution leaves the heading empty.
30
+ */
31
+ 'settings.header': {
32
+ kind: 'single';
33
+ scope: 'root';
34
+ owner: SettingsHeaderOwnerProps;
35
+ };
36
+ /**
37
+ * Optional actions rendered in the content-column header before Close.
38
+ * Registrants own visibility, behavior, copy, and failure presentation;
39
+ * the shell supplies only the ordered render site.
40
+ */
41
+ 'settings.action': {
42
+ kind: 'list';
43
+ scope: 'root';
44
+ owner: SettingsHeaderOwnerProps;
45
+ };
46
+ /**
47
+ * The close button's visually-hidden label text (the button itself —
48
+ * icon, geometry, focus — is shell chrome). Absent contribution leaves
49
+ * the button without an accessible name (broken-composition state).
50
+ */
51
+ 'settings.close': {
52
+ kind: 'single';
53
+ scope: 'root';
54
+ owner: SettingsHeaderOwnerProps;
55
+ };
56
+ /**
57
+ * One settings page per list entry. Registrant options carry the nav
58
+ * identity: `id` (section key, drives `only` filtering), `order` (nav
59
+ * position), `label` (registrant-localized display text — the registrant
60
+ * re-registers with fresh text on locale change, so the shell never
61
+ * subscribes locale state; the ledger bump doubles as the shell's
62
+ * re-render trigger). Sections render inside the panel content column.
63
+ * (`settings.general.item`, declared by ui-settings-general's General
64
+ * entry, is typed in the locale package — the common dependency of every
65
+ * item registrant; the shell neither declares nor renders it.)
66
+ */
67
+ 'settings.section': {
68
+ kind: 'list';
69
+ scope: 'root';
70
+ owner: SettingsSectionOwnerProps;
71
+ };
72
+ /**
73
+ * Root-scoped onboarding steps contributed by settings features. The
74
+ * shell mounts one ordered step at a time; the active registrant either
75
+ * completes itself or keeps ownership until the user completes its sole
76
+ * path. Registrants own readiness, copy, dialog behavior, AND the
77
+ * takeover chrome: a step wraps its visible content in the
78
+ * OnboardingSurface primitive (mask, opaque stage, `#root` inert) and
79
+ * renders null while its private facts are still loading — the shell
80
+ * paints no chrome of its own, so a mounted-but-deciding step shows and
81
+ * blocks nothing (prevents a white flash on reload: a bare unwrapped step
82
+ * would render without mask or stage).
83
+ */
84
+ 'settings.onboarding': {
85
+ kind: 'list';
86
+ scope: 'root';
87
+ owner: SettingsOnboardingOwnerProps;
88
+ };
89
+ }
90
+ }
91
+ /** Owner share of the trigger content seat: the sidebar column state. */
92
+ export interface SettingsTriggerOwnerProps {
93
+ /** Whether the sidebar renders wide content (false = 56px rail, icon only). */
94
+ wide: boolean;
95
+ }
96
+ /** Owner share of the header title seat (the shell supplies nothing). */
97
+ export interface SettingsHeaderOwnerProps {
98
+ /** Marker field: header owner props are intentionally empty. */
99
+ children?: never;
100
+ }
101
+ /**
102
+ * Owner share of a settings section entry. The shell owns modal visibility
103
+ * and navigation; a section's data arrives through its own inject faces and
104
+ * stores. `close` is the one shell affordance a section receives, for flows
105
+ * that leave settings altogether (starting a session from a section) — the
106
+ * onboarding coordinator's `openSection`/`complete` precedent, inverted.
107
+ */
108
+ export interface SettingsSectionOwnerProps {
109
+ /** Close the settings panel (the shell owns the open state). */
110
+ close: () => void;
111
+ }
112
+ /** Owner share of the currently active settings-backed onboarding step. */
113
+ export interface SettingsOnboardingOwnerProps {
114
+ /** Stable id of the step currently selected by the coordinator. */
115
+ stepId: string;
116
+ /** Complete or skip this step and transfer ownership to the next entry. */
117
+ complete: () => void;
118
+ /** Open the settings panel directly on one registered section. */
119
+ openSection: (id: string) => void;
120
+ }
121
+ /** One nav row projected from a settings.section registration's options. */
122
+ export interface SettingsSectionRow {
123
+ id: string;
124
+ order: number;
125
+ label: string;
126
+ }
127
+ /** One ordered onboarding step projected from a slot registration. */
128
+ export interface SettingsOnboardingStep {
129
+ id: string;
130
+ order: number;
131
+ }
132
+ /**
133
+ * Registrant-private injected share of the settings shell (assembled in
134
+ * apply): the ledger's nav-row projection as a hooks-compartment source —
135
+ * the shell reads no locale state and subscribes through the bound hook.
136
+ */
137
+ export type SettingsRootInjected = {
138
+ hooks: {
139
+ /** settings.section ledger projected into ordered nav rows. */
140
+ sections: HostObservable<readonly SettingsSectionRow[]>;
141
+ /** settings.onboarding ledger projected into coordinator order. */
142
+ onboardingSteps: HostObservable<readonly SettingsOnboardingStep[]>;
143
+ };
144
+ };
145
+ /**
146
+ * Full component props of the settings shell root: the sidebar owner share
147
+ * (wide/rail state) plus the declared render shares and the injected face
148
+ * (hooks compartment bound to useSections). No store is registered — modal
149
+ * open state and active section id are component-local viewing state.
150
+ */
151
+ export type SettingsRootComponentProps = PropsRuntime<'sidebar.settings'> & PropsRenderSlots<'settings.trigger' | 'settings.header' | 'settings.action' | 'settings.close' | 'settings.section' | 'settings.onboarding'> & InjectFace<SettingsRootInjected>;
152
+ //# sourceMappingURL=slots.d.ts.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Settings shell plugin, browser half. A pure composition face: occupies the
3
+ * sidebar-owned `sidebar.settings` hole with the trigger chrome + modal
4
+ * panel, declares its chrome, section, and onboarding slots, and projects the
5
+ * section ledger into panel navigation. The shell ships no copy; it reads the
6
+ * optional locale revision only to resolve registrant-owned nav-label thunks.
7
+ * ui-settings-general owns the chrome and General content; features own their
8
+ * rows, sections, and onboarding pages. Export discipline: packages/client/AGENTS.md.
9
+ */
10
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
11
+ export type { SettingsHeaderOwnerProps, SettingsRootComponentProps, SettingsRootInjected, SettingsOnboardingOwnerProps, SettingsOnboardingStep, SettingsSectionOwnerProps, SettingsSectionRow, SettingsTriggerOwnerProps, } from './contract/slots.ts';
12
+ /**
13
+ * Required services (cordis fiber inject). The target slot is declared by
14
+ * ui-sidebar's apply, whose activation order relative to this one is NOT
15
+ * constrained (dsh.client.inject edges are informational); registration
16
+ * depends on the slot through `slots.inject()`.
17
+ */
18
+ export declare const inject: string[];
19
+ /**
20
+ * Register the settings shell into `sidebar.settings` once the declaration is
21
+ * on the ledger.
22
+ * @param ctx - client root context.
23
+ */
24
+ export declare function apply(ctx: ClientContext): void;
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Host loader entry for the browser implementation exported from `./client`. */
2
+ /** Host plugin body — no host-side behavior for the settings shell plugin. */
3
+ export declare function apply(): void;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-settings`.
3
+ * @module @deepseek-ai/dsh-client-ui-settings/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "client-ui-settings-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@deepseek-ai/dsh-client-ui-settings",
3
+ "description": "Settings shell plugin: sidebar trigger, modal panel, feature sections, and an ordered full-page onboarding stage",
4
+ "version": "0.0.1-rc.1",
5
+ "publishConfig": {
6
+ "access": "restricted"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/client/ui-settings"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./client": {
26
+ "types": "./lib/types/client/index.d.ts",
27
+ "default": "./lib/client.js"
28
+ },
29
+ "./src/*": "./src/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "dsh": {
33
+ "client": {
34
+ "inject": [
35
+ "@deepseek-ai/dsh-client-runtime",
36
+ "@deepseek-ai/dsh-client-ui-sidebar"
37
+ ],
38
+ "platform": "web"
39
+ }
40
+ },
41
+ "license": "BSD-3-Clause",
42
+ "dependencies": {
43
+ "clsx": "^2.0.0"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^18.2.0",
47
+ "react-dom": "^18.2.0",
48
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.1",
49
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.1",
50
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.1",
51
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
52
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
53
+ },
54
+ "devDependencies": {
55
+ "@types/react-dom": "~18.3.0",
56
+ "@types/react": "~18.3.1",
57
+ "react": "^18.2.0",
58
+ "react-dom": "^18.2.0",
59
+ "@deepseek-ai/dsh-client-locale": "^0.0.1-rc.1",
60
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.1",
61
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.1",
62
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.0.1-rc.1",
63
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.1",
64
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
65
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
66
+ },
67
+ "files": [
68
+ "lib/index.js",
69
+ "lib/invariant.js",
70
+ "lib/client.js",
71
+ "lib/types/**/*.d.ts"
72
+ ],
73
+ "scripts": {
74
+ "bundle": "tsdown",
75
+ "watch": "tsdown --watch"
76
+ }
77
+ }