@deepseek-ai/dsh-client-ui-layout 0.1.1-rc.2 → 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-layout/README.md
5
- README.md: 7c9d64b9ca7a5fada492c37200a283904466c36d
6
- README.zh.md: a2604d865977c495d2a3d2362b25f05e34142c97
5
+ README.md: 5eba9ac8ce793092053d9b8c84daa8f737597c26
6
+ README.zh.md: 1af81cea580d97cfa89365bdd79659933c0b2ae1
package/README.md CHANGED
@@ -1,13 +1,63 @@
1
+ ---
2
+ description: "Shell layout for the Web GUI: the three-column AppFrame with drag handles, concession behavior, the panel-geometry service, and theme presentation; for users and maintainers of the window chrome."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-client-ui-layout
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- Shell plugin: three-column AppFrame (drag handles and concession chain) plus the `ctx.layout` panel-geometry service; it registers into the runtime-owned `root` slot and declares `sidebar`, `conversation`, `details`, and `conversation.empty`. The sidebar resize boundary is an invisible hit strip, while the details boundary retains its floating pill; only details shrinks during concession and then auto-closes. A closed sidebar retains a 56px control rail while details closes to zero width. The package also seats the theme presenter: it consumes resolved `ctx.theme` snapshots and projects them onto the document (`html { color-scheme }` for native UA chrome, `body[data-ds-dark-theme]` from the active color scheme, the theme's alias tokens as inline variables on body, and one owned `<meta name="theme-color">` whose content follows the computed body background). Measuring after palette and token application keeps the rendered background as the single color authority; disposing the presenter removes its metadata node with its other global writes.
10
+ ## Summary
11
+
12
+ This package provides the shell layout of the Web GUI: a three-column AppFrame with resizable sidebar and details panels, a concession chain that shrinks the details column and then auto-closes it when space runs out, and the `ctx.layout` panel-geometry service other plugins call to open or close the details column. It also seats the theme presenter, which projects the resolved color scheme, alias tokens, content font size, and `theme-color` metadata onto the document. Choose it for the standard window chrome; panel geometry is transient and resets on reload.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount this plugin at the root slot; it then renders the app frame around whatever occupies the sidebar, conversation, and details columns. Users resize the sidebar by dragging its invisible hit strip and the details panel by dragging its floating pill; when the window narrows, only details shrinks, then auto-closes. A closed sidebar retains a 56px control rail; details closes to zero width.
29
+
30
+ ### Theme presentation
31
+
32
+ The presenter consumes resolved theme snapshots and projects them onto the document: `html { color-scheme }` for native UA chrome, `body[data-ds-dark-theme]` from the active color scheme, the theme's alias tokens and `--dsh-content-font-size` as inline variables on body, and one owned `<meta name="theme-color">` whose content follows the computed body background. Disposing the presenter removes its metadata node with its other global writes.
33
+
34
+ -----
35
+
36
+ <a id="understand-the-implementation"></a>
37
+ ## Understand the implementation
38
+
39
+ <details>
40
+ <summary>Implementation internals — click to expand</summary>
6
41
 
7
- AppFrame always mounts the conversation and details columns; a connected Session renders through `SessionProvider`. The transient layout store starts the sidebar at its default width and details closed, and it never reads or writes `localStorage`. Hero and other unselected states also derive a zero rendered details width without changing that stored preference. AppFrame retains the last non-blank Session id across those states: the first Session remains closed, an explicit details action opens the contract default width, returning to the same Session restores its unchanged width, and selecting a different Session closes details before paint. The conversation owner share is empty, while the sidebar owner share contains only `collapsed` and `width`; registrants obtain business data from standard hooks and actions from their own inject faces.
42
+ One `register()` call contributes `AppFrame` into the runtime's built-in `'root'` slot and, in the same breath, declares the four child slots (`sidebar`, `conversation`, `details`, `shell.overlay`), seats the layout store (panel geometry), and wires the `ctx.layout` panel-action service. The transient layout store starts the sidebar at its default width and details closed, and never reads or writes `localStorage`. AppFrame always mounts the conversation and details columns; a connected Session renders through `SessionProvider`. It projects the selected Session title over the build-configured product title or the localized `common.brand.localBuild` fallback, so locale revisions update document metadata with the root entry. The theme presenter is a second effect: pure DOM writes from resolved snapshots initial state through the getter once, then event-driven only, with no React path. It applies palette, font-size, and token variables before measuring the rendered background as the single color authority.
8
43
 
9
- The `/client` exports are the plugin body (`apply`/`inject`), `LayoutController`, and the four owner-share interfaces. AppFrame, the panel store, and the concession solver remain package-internal.
44
+ </details>
10
45
 
46
+ -----
47
+
48
+ <a id="further-exploration"></a>
49
+ ## Further Exploration
50
+
51
+ Read these pages when the layout surface is not enough. They move from the frame to the columns it renders and the theme it presents.
52
+
53
+ - [ui-sidebar](../ui-sidebar/README.md) — occupies the `sidebar` column and its seats.
54
+ - [ui-conversation](../ui-conversation/README.md) — occupies the `conversation` and `details` columns.
55
+ - [ui-theme](../ui-theme/README.md) — the theme seam whose resolved snapshots the presenter consumes.
56
+ - [Web client architecture](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) — how browser plugin rows load and register slots.
57
+
58
+ -----
59
+
60
+ <a id="model-experience"></a>
11
61
  ## Model Experience
12
62
 
13
63
  None, as the layout shell manages browser viewing state; nothing here reaches a model request.
@@ -18,6 +68,21 @@ None; this package neither assembles nor sends a provider request.
18
68
 
19
69
  ## Known Limitations and Deferred Work
20
70
 
71
+ <a id="known-limitations-and-deferred-work"></a>
72
+
73
+
74
+ These limits define the current layout behavior. They are current package constraints, not a general window-manager comparison or a task backlog.
75
+
21
76
  - **Panel geometry is transient** — reload restores the sidebar default and details closed; switching between distinct Session ids also closes details and forgets its dragged width, while unselected surfaces render details at zero width without modifying geometry.
22
77
  - **Concession-chain auto-close derives a zero width without touching the preferred width** — the panel restores itself when the window widens; consumers must not read the stored details width as the rendered truth.
23
78
  - **No scroll anchoring during squeeze reflow** — layout changes may move the reader's viewport.
79
+
80
+ <a id="dev-note"></a>
81
+ ### Dev Note
82
+
83
+ <details>
84
+ <summary>Working context for maintainers — click to expand</summary>
85
+
86
+ None.
87
+
88
+ </details>
package/README.zh.md CHANGED
@@ -1,13 +1,63 @@
1
+ ---
2
+ description: "Web GUI 的外壳布局:三栏 AppFrame、拖动手柄与让步行为、面板几何服务与主题呈现;供窗口外观的用户与维护者阅读。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-client-ui-layout
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- 外壳插件:三栏 AppFrame(拖动手柄与让步链)加 `ctx.layout` 面板几何服务;它注册到运行时拥有的 `root` slot,并声明 `sidebar`、`conversation`、`details` 和 `conversation.empty`。侧边栏的缩放边界是不可见命中条带,详情栏边界则保留其浮动胶囊;让步期间只有详情栏会收缩并随后自动关闭。关闭的侧边栏仍保留 56px 控制栏,详情栏则关闭到零宽度。该包还提供主题呈现器:它消费解析后的 `ctx.theme` 快照,并将其投影到 document(用 `html { color-scheme }` 驱动原生 UA 控件,依据当前配色方案设置 `body[data-ds-dark-theme]`,并将主题的别名 token 设为 body 上的内联变量,同时拥有一个 `<meta name="theme-color">`,其内容随计算后的 body 背景色更新)。在应用调色板和 token 后进行测量,可确保渲染后的背景成为唯一的颜色依据;呈现器在 dispose(资源释放)时会移除其自有的元数据节点,并一并清除其写入的其他全局状态。
10
+ ## 概述
11
+
12
+ 本包提供 Web GUI 的外壳布局:一个三栏 AppFrame,带可缩放的侧栏与详情面板;一条让步链,在空间不足时先收缩详情栏、随后自动关闭它;以及 `ctx.layout` 面板几何服务,供其他插件调用以打开或关闭详情栏。它还承载主题呈现器,把解析后的配色方案、别名 token、正文字号与 `theme-color` 元数据投影到 document。需要标准窗口外观时选择它;面板几何是瞬时的,重新加载即重置。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 在 root 槽位挂载本插件;它随即围绕占据侧栏、会话与详情栏的内容渲染应用框架。用户拖动不可见命中条带缩放侧栏、拖动浮动胶囊缩放详情面板;窗口变窄时只有详情栏收缩,随后自动关闭。关闭的侧栏保留 56px 控制栏;详情栏关闭到零宽度。
29
+
30
+ ### 主题呈现
31
+
32
+ 呈现器消费解析后的主题快照,并投影到 document:`html { color-scheme }` 驱动原生 UA 控件,依据当前配色方案设置 `body[data-ds-dark-theme]`,把主题的别名 token 与 `--dsh-content-font-size` 设为 body 上的内联变量,并持有一个 `<meta name="theme-color">`,其内容随计算后的 body 背景色更新。释放呈现器时,它会连同其他全局写入一起移除自己的元数据节点。
33
+
34
+ -----
35
+
36
+ <a id="understand-the-implementation"></a>
37
+ ## 理解实现
38
+
39
+ <details>
40
+ <summary>实现细节——点击展开</summary>
6
41
 
7
- AppFrame 始终挂载会话栏和详情栏;已连接 Session 通过 `SessionProvider` 渲染。布局 store 是瞬时状态,侧边栏以默认宽度启动,详情栏则保持关闭,且该 store 从不读写 `localStorage`。hero 和其他未选中状态也会将详情栏的渲染宽度派生为零,但不会改变存储的宽度偏好。AppFrame 会跨越这些状态保留最后一个非 blank 会话 id:首个会话保持关闭;显式打开详情栏的操作会使用约定默认宽度;返回同一会话时恢复其未改变的宽度;选择不同会话时,详情栏会在绘制前关闭。会话 owner share 为空,侧边栏 owner share 只包含 `collapsed` `width`;注册方通过标准钩子获取业务数据,并从各自的 inject 接口获取操作。
42
+ 一次 `register()` 调用把 `AppFrame` 贡献进运行时的内建 `'root'` 槽位,并在同一刻声明四个子槽位(`sidebar`、`conversation`、`details`、`shell.overlay`)、安放布局 store(面板几何)并接好 `ctx.layout` 面板动作服务。瞬时布局 store 以默认宽度启动侧栏、保持详情栏关闭,从不读写 `localStorage`。AppFrame 始终挂载会话与详情两栏;已连接 Session `SessionProvider` 渲染。它把所选 Session 标题投影到构建配置的产品标题或本地化 `common.brand.localBuild` 回退值之上,因此 locale revision 会随根 entry 一起更新文档元数据。主题呈现器是第二个 effect:从解析后的快照做纯 DOM 写入——初始状态经 getter 读取一次,此后仅事件驱动,不经过 React。它先应用调色板、字号与 token 变量,再把渲染出的背景测量为唯一的颜色依据。
8
43
 
9
- `/client` 导出表层包含插件主体(`apply`/`inject`)、`LayoutController` 和四个 owner-share 接口。AppFrame、面板 store 与让步求解器仍属于包内部。
44
+ </details>
10
45
 
46
+ -----
47
+
48
+ <a id="further-exploration"></a>
49
+ ## 进一步探索
50
+
51
+ 当布局面不够用时阅读以下页面。它们从框架进入它所渲染的栏与它所呈现的主题。
52
+
53
+ - [ui-sidebar](../ui-sidebar/README.zh.md)——占据 `sidebar` 栏及其座位。
54
+ - [ui-conversation](../ui-conversation/README.zh.md)——占据 `conversation` 与 `details` 栏。
55
+ - [ui-theme](../ui-theme/README.zh.md)——呈现器消费其解析快照的主题 seam。
56
+ - [Web 客户端架构](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md)——浏览器插件行如何加载并注册槽位。
57
+
58
+ -----
59
+
60
+ <a id="model-experience"></a>
11
61
  ## 模型体验
12
62
 
13
63
  无。布局外壳管理浏览器查看状态;这里没有任何内容进入模型请求。
@@ -16,8 +66,23 @@ AppFrame 始终挂载会话栏和详情栏;已连接 Session 通过 `SessionPr
16
66
 
17
67
  无;该包既不组装也不发送提供方请求。
18
68
 
19
- ## 已知限制与暂缓事项
69
+ ## 已知限制与延期工作
70
+
71
+ <a id="known-limitations-and-deferred-work"></a>
72
+
73
+
74
+ 这些限制界定了当前布局行为。它们是当前包约束,不是通用窗口管理器对比或任务积压。
75
+
76
+ - **面板几何是瞬时状态**——重新加载会恢复侧栏默认值并保持详情栏关闭;在不同会话 id 之间切换同样会关闭详情栏并忘记拖动后的宽度,而未选中表面以零宽度渲染详情栏却不修改几何。
77
+ - **让步链自动关闭通过推导零宽度实现,不触碰偏好宽度**——窗口变宽时面板自行恢复;消费方不得把 store 中的详情宽度当作渲染真值。
78
+ - **挤压重排期间无滚动锚定**——布局变化可能移动读者的视口。
79
+
80
+ <a id="dev-note"></a>
81
+ ### 开发备注
82
+
83
+ <details>
84
+ <summary>维护者的工作上下文——点击展开</summary>
85
+
86
+ 无。
20
87
 
21
- - **面板几何信息是瞬时状态**:重新加载会恢复侧边栏默认值,并使详情栏保持关闭;在不同会话 id 之间切换同样会关闭详情栏,并忘记拖动后的宽度,而未选中表面会以零宽度渲染详情栏,但不会修改几何信息。
22
- - **让步链自动关闭通过推导零宽度实现,不会改动宽度偏好**:窗口变宽时面板会自行恢复;消费方禁止把 store 中的详情宽度当作实际渲染状态。
23
- - **挤压重排期间不提供滚动锚定**:布局变化可能移动读者的 viewport。
88
+ </details>
package/lib/client.js CHANGED
@@ -6,7 +6,7 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
8
  let react = require("react");
9
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
9
+ let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
10
10
  /** Viewport width below which the sidebar auto-collapses to the rail (deepsuite
11
11
  * LG breakpoint); a manual toggle below it re-expands over the squeezed center
12
12
  * (stores.ts narrowExpanded). */
@@ -52,6 +52,23 @@ window.__ModuleLoader__.load({
52
52
  };
53
53
  }
54
54
  //#endregion
55
+ //#region lib/types/client/DocumentTitle.js
56
+ /**
57
+ * Project the selected durable session title into the browser title and
58
+ * restore the build-selected product title when unmounted.
59
+ * @param props - Selected session title projection.
60
+ * @returns No rendered content.
61
+ */
62
+ function DocumentTitle({ title, productTitle }) {
63
+ (0, react.useEffect)(() => {
64
+ document.title = title === void 0 ? productTitle : `${title} — ${productTitle}`;
65
+ return () => {
66
+ document.title = productTitle;
67
+ };
68
+ }, [productTitle, title]);
69
+ return null;
70
+ }
71
+ //#endregion
55
72
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-layout/src/client/AppFrame.module.css.mjs
56
73
  const css = ".pI_x6G_frame{background:var(--dsw-alias-bg-base);height:100%;transition:grid-template-columns var(--ds-transition-duration-slow) var(--ds-ease-in-out);grid-template-rows:100%;display:grid;position:relative;overflow:hidden}.pI_x6G_frame[data-dragging]{transition:none}@media (prefers-reduced-motion:reduce){.pI_x6G_frame{transition:none}}.pI_x6G_sidebarCol{background:var(--dsw-specific-sidebar-fill);border-right:1px solid var(--dsw-alias-border-l1);min-width:0;overflow:hidden}.pI_x6G_centerCol{flex-direction:column;min-width:0;display:flex;overflow:hidden}.pI_x6G_detailsCol{border-left:1px solid var(--dsw-alias-border-l2);min-width:0;overflow:hidden}.pI_x6G_frame[data-details-collapsed] .pI_x6G_detailsCol{border-left:none}.pI_x6G_handle{cursor:col-resize;z-index:2;touch-action:none;width:8px;transition:left var(--ds-transition-duration-slow) var(--ds-ease-in-out);margin-left:-4px;position:absolute;top:0;bottom:0}.pI_x6G_frame[data-dragging] .pI_x6G_handle{transition:none}@media (prefers-reduced-motion:reduce){.pI_x6G_handle{transition:none}}.pI_x6G_handle[data-side=details]:after{content:\"\";box-sizing:border-box;background:var(--dsw-alias-button-floating-fill);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);opacity:0;width:12px;height:32px;transition:opacity var(--ds-transition-duration-slow) var(--ds-ease-in-out), background var(--ds-transition-duration-slow) var(--ds-ease-in-out);border-radius:10px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.pI_x6G_detailsCol:hover~.pI_x6G_handle[data-side=details]:after,.pI_x6G_handle[data-side=details]:hover:after,.pI_x6G_handle[data-side=details][data-dragging=true]:after{opacity:1}.pI_x6G_handle[data-side=details]:hover:after,.pI_x6G_handle[data-side=details][data-dragging=true]:after{background:var(--dsw-alias-button-floating-hover);border-color:var(--dsw-alias-border-l3)}.pI_x6G_overlayLayer{z-index:20;pointer-events:none;position:absolute;inset:0}.pI_x6G_overlayLayer>*{pointer-events:auto}";
57
74
  const tagId = "@deepseek-ai/dsh-client-ui-layout/AppFrame.module.css";
@@ -155,12 +172,16 @@ window.__ModuleLoader__.load({
155
172
  });
156
173
  }
157
174
  /** The three-column frame (see module doc). */
158
- function AppFrame({ useStore, useSessions, actions, renderSlot }) {
175
+ function AppFrame({ useStore, useSessions, actions, renderSlot, SessionProvider, t }) {
159
176
  const panels = useStore((s) => s);
160
177
  const detailsSession = useSessions((s) => {
161
178
  const current = s.current;
162
179
  return current !== void 0 && s.byId[current]?.blank === false ? current : void 0;
163
180
  });
181
+ const documentTitle = useSessions((s) => {
182
+ const current = s.current;
183
+ return current === void 0 ? void 0 : s.byId[current]?.title;
184
+ });
164
185
  const frameRef = (0, react.useRef)(null);
165
186
  const [viewport, setViewport] = (0, react.useState)(() => window.innerWidth);
166
187
  const lastSession = (0, react.useRef)(detailsSession);
@@ -223,6 +244,10 @@ window.__ModuleLoader__.load({
223
244
  "data-details-collapsed": cols.details === 0 || void 0,
224
245
  "data-dragging": dragging || void 0,
225
246
  children: [
247
+ (0, react_jsx_runtime.jsx)(DocumentTitle, {
248
+ productTitle: "DeepSeek Harness",
249
+ ...documentTitle === void 0 ? {} : { title: documentTitle }
250
+ }),
226
251
  (0, react_jsx_runtime.jsx)("div", {
227
252
  className: AppFrame_module_css_default.sidebarCol,
228
253
  children: renderSlot("sidebar", {
@@ -230,7 +255,7 @@ window.__ModuleLoader__.load({
230
255
  width: cols.sidebar
231
256
  })
232
257
  }),
233
- (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(CenterColumn, { children: renderSlot("conversation", {}) }), (0, react_jsx_runtime.jsx)(DetailsColumn, { children: renderSlot("details", {}) })] }),
258
+ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(CenterColumn, { children: renderSlot("conversation", {}) }), (0, react_jsx_runtime.jsx)(DetailsColumn, { children: (0, react_jsx_runtime.jsx)(SessionProvider, { children: renderSlot("details", {}) }) })] }),
234
259
  (0, react_jsx_runtime.jsx)("div", {
235
260
  className: AppFrame_module_css_default.overlayLayer,
236
261
  "data-shell-overlay": true,
@@ -275,7 +300,7 @@ window.__ModuleLoader__.load({
275
300
  * @returns the store handle (spec + type + identity + factory in one).
276
301
  */
277
302
  function createLayoutStore() {
278
- return (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
303
+ return (0, _deepseek_ai_dsh_client_store.defineStore)({
279
304
  init: () => ({
280
305
  sidebar: 280,
281
306
  details: 0,
@@ -343,6 +368,8 @@ window.__ModuleLoader__.load({
343
368
  //#region lib/types/client/theme-presenter.js
344
369
  /** Body attribute selecting the dark base palette in the token stylesheets. */
345
370
  const DARK_ATTRIBUTE = "data-ds-dark-theme";
371
+ /** Body variable carrying the user's content font size in px. */
372
+ const CONTENT_FONT_SIZE_VARIABLE = "--dsh-content-font-size";
346
373
  /** Applies theme snapshots to the document; one instance per plugin fiber. */
347
374
  var ThemePresenter = class {
348
375
  /** Token names this presenter wrote in the last apply (its retraction set). */
@@ -357,10 +384,10 @@ window.__ModuleLoader__.load({
357
384
  /**
358
385
  * Project a snapshot onto the document: set root `color-scheme` and the body
359
386
  * palette attribute from `active.colorScheme` (never the id — `system` is
360
- * resolved upstream), then replace the previously applied token variables
361
- * with `active.tokens`. Browser theme-color metadata follows the computed
362
- * body background after those writes, so the rendered palette remains the
363
- * color authority.
387
+ * resolved upstream), publish the content font-size axis, then replace the
388
+ * previously applied token variables with `active.tokens`. Browser
389
+ * theme-color metadata follows the computed body background after those
390
+ * writes, so the rendered palette remains the color authority.
364
391
  * @param snapshot - resolved theme snapshot from ctx.theme.
365
392
  */
366
393
  apply(snapshot) {
@@ -369,6 +396,7 @@ window.__ModuleLoader__.load({
369
396
  const body = document.body;
370
397
  if (scheme === "dark") body.setAttribute(DARK_ATTRIBUTE, "");
371
398
  else body.removeAttribute(DARK_ATTRIBUTE);
399
+ body.style.setProperty(CONTENT_FONT_SIZE_VARIABLE, `${snapshot.fontSize}px`);
372
400
  for (const name of this.appliedTokens) body.style.removeProperty(name);
373
401
  this.appliedTokens = [];
374
402
  for (const [name, value] of Object.entries(snapshot.active.tokens)) {
@@ -378,11 +406,12 @@ window.__ModuleLoader__.load({
378
406
  this.themeColorMeta.content = getComputedStyle(body).backgroundColor;
379
407
  if (!this.themeColorMeta.isConnected) document.head.append(this.themeColorMeta);
380
408
  }
381
- /** Retract root color-scheme, the palette attribute, token variables, and the owned metadata node. */
409
+ /** Retract root color-scheme, the palette attribute, token variables, the font-size axis, and the owned metadata node. */
382
410
  dispose() {
383
411
  document.documentElement.style.removeProperty("color-scheme");
384
412
  const body = document.body;
385
413
  body.removeAttribute(DARK_ATTRIBUTE);
414
+ body.style.removeProperty(CONTENT_FONT_SIZE_VARIABLE);
386
415
  for (const name of this.appliedTokens) body.style.removeProperty(name);
387
416
  this.appliedTokens = [];
388
417
  this.themeColorMeta.remove();
@@ -391,7 +420,11 @@ window.__ModuleLoader__.load({
391
420
  //#endregion
392
421
  //#region lib/types/client/index.js
393
422
  /** Required services (cordis fiber inject — the loader passes all module exports as an object plugin). */
394
- const inject = ["slots", "theme"];
423
+ const inject = [
424
+ "slots",
425
+ "theme",
426
+ "locale"
427
+ ];
395
428
  /**
396
429
  * Client plugin body: provide ctx.layout, then one register() call — AppFrame
397
430
  * into 'root' with the four child-slot declarations, the layout store seat,
@@ -404,6 +437,7 @@ window.__ModuleLoader__.load({
404
437
  const disposeService = ctx.reflect.provide("layout", layout);
405
438
  const disposeRegistration = ctx.slots.register({
406
439
  name: "root",
440
+ locale: "common",
407
441
  children: {
408
442
  "sidebar": {
409
443
  kind: "single",
@@ -1,7 +1,7 @@
1
- import type { PropsRenderSlots, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
1
+ import type { PropsLocale, PropsRenderSlots, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
2
2
  import type { createLayoutStore } from './stores.ts';
3
3
  /** Full composed props: runtime share + child-slot render share + store share. */
4
- export type AppFrameProps = PropsRuntime<'root'> & PropsRenderSlots<'sidebar' | 'conversation' | 'details' | 'shell.overlay'> & PropsStore<ReturnType<typeof createLayoutStore>>;
4
+ export type AppFrameProps = PropsRuntime<'root'> & PropsRenderSlots<'sidebar' | 'conversation' | 'details' | 'shell.overlay'> & PropsStore<ReturnType<typeof createLayoutStore>> & PropsLocale<'common'>;
5
5
  /** The three-column frame (see module doc). */
6
- export declare function AppFrame({ useStore, useSessions, actions, renderSlot, }: AppFrameProps): import("react").JSX.Element;
6
+ export declare function AppFrame({ useStore, useSessions, actions, renderSlot, SessionProvider, t, }: AppFrameProps): import("react").JSX.Element;
7
7
  //# sourceMappingURL=AppFrame.d.ts.map
@@ -0,0 +1,15 @@
1
+ /** Props for the browser title projection. */
2
+ export interface DocumentTitleProps {
3
+ /** Durable title of the selected session, or undefined for the product title. */
4
+ title?: string;
5
+ /** Build-configured or localized product title. */
6
+ productTitle: string;
7
+ }
8
+ /**
9
+ * Project the selected durable session title into the browser title and
10
+ * restore the build-selected product title when unmounted.
11
+ * @param props - Selected session title projection.
12
+ * @returns No rendered content.
13
+ */
14
+ export declare function DocumentTitle({ title, productTitle }: DocumentTitleProps): null;
15
+ //# sourceMappingURL=DocumentTitle.d.ts.map
@@ -7,7 +7,7 @@
7
7
  * with the runtime sessions service. A second effect seats the theme
8
8
  * presenter, which projects ctx.theme snapshots onto document.body.
9
9
  */
10
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
10
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
11
11
  export { LayoutController } from './service.ts';
12
12
  export type { ILayout } from './service.ts';
13
13
  declare module '@deepseek-ai/cordis' {
@@ -7,7 +7,7 @@
7
7
  * derives its PropsStore share from the return type, and the service face
8
8
  * receives the bound actions through the registration's inject hook.
9
9
  */
10
- import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-runtime/client';
10
+ import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-store';
11
11
  /**
12
12
  * Layout store state: panel width preferences in px (0 = closed), plus the
13
13
  * narrow-viewport pair — `narrow` mirrors AppFrame's breakpoint reading
@@ -2,14 +2,17 @@
2
2
  * Global theme DOM applier: projects the resolved ThemeSnapshot onto the
3
3
  * document — `html { color-scheme }` for native UA chrome (scrollbars, form
4
4
  * controls), `body[data-ds-dark-theme]` for the token palette, the active
5
- * theme's alias-token overrides as inline CSS variables on body, and one
6
- * presenter-owned `meta[name="theme-color"]` for surrounding browser UI. Pure
7
- * DOM writes, no React involvement; the presenter only ever retracts what it
8
- * wrote itself, so foreign attributes, metadata, and inline styles survive.
5
+ * theme's alias-token overrides as inline CSS variables on body, the content
6
+ * font-size axis (`--dsh-content-font-size`), and one presenter-owned
7
+ * `meta[name="theme-color"]` for surrounding browser UI. Pure DOM writes, no
8
+ * React involvement; the presenter only ever retracts what it wrote itself,
9
+ * so foreign attributes, metadata, and inline styles survive.
9
10
  */
10
11
  import type { ThemeSnapshot } from '@deepseek-ai/dsh-client-ui-theme/client';
11
12
  /** Body attribute selecting the dark base palette in the token stylesheets. */
12
13
  export declare const DARK_ATTRIBUTE = "data-ds-dark-theme";
14
+ /** Body variable carrying the user's content font size in px. */
15
+ export declare const CONTENT_FONT_SIZE_VARIABLE = "--dsh-content-font-size";
13
16
  /** Applies theme snapshots to the document; one instance per plugin fiber. */
14
17
  export declare class ThemePresenter {
15
18
  /** Token names this presenter wrote in the last apply (its retraction set). */
@@ -21,14 +24,14 @@ export declare class ThemePresenter {
21
24
  /**
22
25
  * Project a snapshot onto the document: set root `color-scheme` and the body
23
26
  * palette attribute from `active.colorScheme` (never the id — `system` is
24
- * resolved upstream), then replace the previously applied token variables
25
- * with `active.tokens`. Browser theme-color metadata follows the computed
26
- * body background after those writes, so the rendered palette remains the
27
- * color authority.
27
+ * resolved upstream), publish the content font-size axis, then replace the
28
+ * previously applied token variables with `active.tokens`. Browser
29
+ * theme-color metadata follows the computed body background after those
30
+ * writes, so the rendered palette remains the color authority.
28
31
  * @param snapshot - resolved theme snapshot from ctx.theme.
29
32
  */
30
33
  apply(snapshot: ThemeSnapshot): void;
31
- /** Retract root color-scheme, the palette attribute, token variables, and the owned metadata node. */
34
+ /** Retract root color-scheme, the palette attribute, token variables, the font-size axis, and the owned metadata node. */
32
35
  dispose(): void;
33
36
  }
34
37
  //# sourceMappingURL=theme-presenter.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-layout",
3
3
  "description": "Shell plugin: three-column AppFrame with drag handles, ctx.layout viewing-state service (navigation + panels)",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,7 +32,9 @@
32
32
  "dsh": {
33
33
  "client": {
34
34
  "inject": [
35
- "@deepseek-ai/dsh-client-runtime",
35
+ "@deepseek-ai/dsh-client-locale",
36
+ "@deepseek-ai/dsh-client-ui-renderer",
37
+ "@deepseek-ai/dsh-client-ui-session",
36
38
  "@deepseek-ai/dsh-client-ui-theme"
37
39
  ],
38
40
  "platform": "web"
@@ -40,20 +42,19 @@
40
42
  },
41
43
  "license": "MIT",
42
44
  "peerDependencies": {
43
- "@deepseek-ai/dsh-client-ui-theme": "^0.1.1-rc.2",
44
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
45
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
46
- "@deepseek-ai/cordis": "^4.0.1"
45
+ "@deepseek-ai/cordis": "^4.0.2"
47
46
  },
48
47
  "devDependencies": {
49
48
  "@types/react": "~18.3.1",
50
49
  "react": "^18.2.0",
51
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
52
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
53
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
54
- "@deepseek-ai/dsh-client-ui-theme": "^0.1.1-rc.2",
55
- "@deepseek-ai/cordis": "^4.0.1",
56
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
50
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
51
+ "@deepseek-ai/dsh-client-store": "^0.1.2-alpha.2",
52
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.2",
53
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.2-alpha.2",
54
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
55
+ "@deepseek-ai/dsh-client-ui-theme": "^0.1.2-alpha.2",
56
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
57
+ "@deepseek-ai/cordis": "^4.0.2"
57
58
  },
58
59
  "files": [
59
60
  "lib/index.js",