@qilin-agent/client-ui-sidebar 0.0.0-stage → 3.1.3

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-sidebar/README.md
5
+ /:
6
+ en: c2607c6d03c52187
7
+ zh: bf0140a9f62c5139
8
+ /qilin-client-ui-sidebar:
9
+ en: d3f0fb7798e2e72f
10
+ zh: a78f3e5a93bfa46a
11
+ /qilin-client-ui-sidebar/summary:
12
+ en: b31ae78ccf8acf60
13
+ zh: 095026d07097f088
14
+ /qilin-client-ui-sidebar/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /qilin-client-ui-sidebar/use-this-package:
18
+ en: 62e21a143fc404ae
19
+ zh: 55a7228b24370025
20
+ /qilin-client-ui-sidebar/use-this-package/brand-and-new-session:
21
+ en: b375e1edb5171239
22
+ zh: 7f8659395ab519cd
23
+ /qilin-client-ui-sidebar/use-this-package/global-panel-entries:
24
+ en: 5d3300d3506254c5
25
+ zh: bb2220103e91d9de
26
+ /qilin-client-ui-sidebar/use-this-package/collapse-behavior:
27
+ en: c847d47379a58e5d
28
+ zh: 6af5e11c2bf5d86c
29
+ /qilin-client-ui-sidebar/use-this-package/macos-desktop:
30
+ en: 2394638d0a211e25
31
+ zh: c28fffe34fbc47cd
32
+ /qilin-client-ui-sidebar/use-this-package/scrollbars:
33
+ en: d053b1ed0f4fd293
34
+ zh: ff25a56541f57530
35
+ /qilin-client-ui-sidebar/understand-the-implementation:
36
+ en: 676cd4c309b4110d
37
+ zh: 13be655486791b8e
38
+ /qilin-client-ui-sidebar/understand-the-implementation/slot-discipline:
39
+ en: cc1d53236154e97e
40
+ zh: 77a601b7c9cf7dec
41
+ /qilin-client-ui-sidebar/further-exploration:
42
+ en: 9605d06cd352d035
43
+ zh: 05e02f3bd35bf929
44
+ /qilin-client-ui-sidebar/model-experience:
45
+ en: 3120df665299c634
46
+ zh: 1d9f365d091d0d2a
47
+ /qilin-client-ui-sidebar/model-experience/kv-cache-effect:
48
+ en: ca75c51c89c2c9b0
49
+ zh: de54f3467ef3b9be
50
+ /qilin-client-ui-sidebar/known-limitations-and-deferred-work:
51
+ en: 262db5b10dfb7d98
52
+ zh: c1b5cf6aa58b054e
53
+ /qilin-client-ui-sidebar/known-limitations-and-deferred-work/dev-note:
54
+ en: e67f633dc6fe7773
55
+ zh: 3bb0c20c93f16f98
package/README.md CHANGED
@@ -1,3 +1,116 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "Sidebar shell plugin for the qilin web client: brand row, New Session action, collapse control, scroll-aware region seat, and bottom-pinned footer seats."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/client-ui-sidebar
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ The qilin web client sidebar lets users recognize the active build, start a new session, collapse navigation to a 56px rail, browse Workspaces and Sessions, and reach Settings. It preserves a bottom-pinned footer and hides idle scrollbars without moving browser rows. New Session uses an explicitly selected Workspace, then the current Session's Workspace, then the most recently active Workspace; if none exists, it opens a blank New Session page. Deployments can replace the brand mark or name while retaining the navigation controls and rail geometry.
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
+ The sidebar is the navigation shell: users see the brand, the workbench switch, start new sessions, collapse the rail, and reach Settings. Feature plugins fill its seats — ui-workspace fills `sidebar.workbench` (the switch between the brand row and New Session) and `sidebar.workspaces`, ui-account fills `sidebar.footer.action`, and ui-settings fills `sidebar.settings` with the settings panel.
29
+
30
+ ### Brand and New Session
31
+
32
+ The expanded brand row renders `sidebar.brand.mark` and `sidebar.brand.name` as independent single slots; the collapsed rail renders the same mark slot. Without occupants, the shell uses the QiLin seal and a localized local-build label. A complete build renders a version chip anchored to the wordmark's top-right corner as `version`, tooltipped with `version[-commit][-dirty]` and built from `QILIN_CLIENT_VERSION`, the optional 7-character `QILIN_CLIENT_COMMIT_HASH`, and `QILIN_CLIENT_GIT_DIRTY=true`; missing version metadata omits the chip. New Session targets the explicit Workspace used by a scoped action, otherwise the current Session's Workspace, otherwise the most recently active Workspace; when none exists it clears into the blank New Session page.
33
+
34
+ ### Global panel entries
35
+
36
+ Plugins add an icon component to the root-scoped `sidebar.panellist` list with an `id`, optional `order`, and a string or locale-aware `label`. The same id addresses the component registered in the layout's root-scoped `main` keyed slot; selecting a missing main entry throws without changing the current selection. The label supplies plain visible text, the accessible name, and the collapsed tooltip. Each row reads its own selected state through `usePanelInfo`; moving DOM focus to search or a directory picker does not change the displayed panel or its selected row. With no registrations, neither the list nor spacing for it is rendered. The shipped composition registers no example panel.
37
+
38
+ ### Collapse behavior
39
+
40
+ During a live collapse, the expanded content fades out at its current width, the upper controls share one fade and leftward translation into the 56px rail, and the layout's column slide ends the motion. A page that starts collapsed renders the rail statically, and reduced-motion mode disables both transitions. The bottom-pinned footer seats share the fade timing but have no horizontal translation.
41
+
42
+ The top expand button hosts the optional, non-interactive `sidebar.toggle.badge` slot while collapsed. Its occupant supplies status and tooltip content without adding another action or changing the button's navigation behavior.
43
+
44
+ During a live collapse, the expanded content fades out at its current width, the upper controls share one fade and leftward translation into the 56px rail, and the layout's column slide ends the motion. A page that starts collapsed renders the rail statically, and reduced-motion mode disables both transitions. The bottom-pinned `sidebar.settings` control shares the fade timing but has no horizontal translation.
45
+
46
+ On Windows Electron, `html[data-windows-titlebar]` fixes the sidebar toggle in the caption's top-left corner in both states, aligned with New Session's left edge only when expanded. The expanded brand sits below the caption and above New Session, with 8px of extra space above that button. Collapsing hides the brand and sidebar content and places New Session between the sidebar toggle and the Desktop-owned menus. The sidebar sets the root `--qilin-windows-menu-start` to 84px when collapsed; the Desktop preload uses it to position its menu after New Session and defaults to 48px when expanded. Caption icon buttons use centered 16px glyphs in 28px circular controls and exclude themselves from the window drag region.
47
+
48
+ ### macOS desktop
49
+
50
+ Under `html[data-platform='darwin']` (set only by a desktop carrier's preload) the expanded column opens with a 52px top strip that clears the hiddenInset traffic lights and carries the collapse toggle; the strip marks itself `data-window-drag`, so its own box is the window drag region (ui-web base.css declares the one darwin drag rule, and the toggle subtracts through its interactive rule) while the brand button below keeps its New Session shortcut; collapsing hides the column entirely instead of leaving the rail. The package registers `HeaderLeadingControls` into the conversation header's `conversation.session.header.leading` seat — the open-sidebar and New Session controls shown, purely via CSS against the AppFrame-published `data-sidebar-collapsed` attribute, only while the column is hidden. Historical rationale: the [macOS hidden-titlebar Agent Note](../../../.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md).
51
+
52
+ ### Scrollbars
53
+
54
+ Scrollbars in the column are a pointer affordance: the shell rebinds the scrollbar indirection to `transparent` whenever the pointer is outside the column and keeps the thumb drawn for 2s after the pointer leaves, so a list nobody is pointing at carries no bar. The reservation that keeps rows from moving belongs to the scrolling region (ui-workspace), so revealing a thumb never reflows.
55
+
56
+ -----
57
+
58
+ <a id="understand-the-implementation"></a>
59
+ ## Understand the implementation
60
+
61
+ <details>
62
+ <summary>Implementation internals — click to expand</summary>
63
+
64
+ The shell is pure composition: `SidebarRootComponentProps` composes the layout owner share, the global `useSessions` and `useWorkspaces` hooks, the declared brand, the `sidebar.workspaces` and `sidebar.settings` child slots, and injected navigation callbacks. Panel entries and their optional titles use the same composition path. Panel metadata is derived from list registrations and locale changes; selection belongs to the layout store.
65
+
66
+ ### Slot discipline
67
+
68
+ Declaration-aware `slots.inject()` lets a replacing package activate before or after the sidebar. The foot stacks the `sidebar.footer.action` list above the `sidebar.settings` occupant, and the sidebar shares its column state (`wide`) with both. The `/client` exports are the plugin body (`apply`/`inject`) plus the contract types only; SidebarRoot, the row components, and the tree derivation remain package-internal behind the slot registration.
69
+
70
+ </details>
71
+
72
+ -----
73
+
74
+ <a id="further-exploration"></a>
75
+ ## Further Exploration
76
+
77
+ These pages cover the surfaces that fill the shell's seats and the composition model.
78
+
79
+ - [ui-workspace](../ui-workspace/README.md) — the Workspace and Session browser rendered into `sidebar.workspaces`.
80
+ - [ui-account](../ui-account/README.md) — the avatar menu rendered into `sidebar.footer.action`, including the Settings entry.
81
+ - [ui-settings](../ui-settings/README.md) — the settings domain base behind the `sidebar.settings` panel occupant.
82
+ - [ui-layout](../ui-layout/README.md) — the layout owner whose rail and column state the collapse uses.
83
+ - [ui-theme](../ui-theme/README.md) — the scrollbar token indirection the shell rebinds.
84
+ - [Slot system standard](../../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md) — the composition model behind the seats.
85
+
86
+ -----
87
+
88
+ <a id="model-experience"></a>
89
+ ## Model Experience
90
+
91
+ None, as the package is a browser-side UI plugin layer that registers nothing model-facing.
92
+
93
+ #### KV Cache effect
94
+
95
+ None; this package neither assembles nor sends a provider request.
96
+
97
+ ## Known Limitations and Deferred Work
98
+
99
+ <a id="known-limitations-and-deferred-work"></a>
100
+
101
+
102
+ These limits define what the shell owns versus what its occupants own; they are current package constraints.
103
+
104
+ - **Session state-dot rendering is owned by ui-workspace** — no done/error notification sources are available to this shell.
105
+ - **Workspace browser behavior is composition-owned** — grouping, ordering, search, and row state belong to ui-workspace, not this shell.
106
+ - **"New task completed" unread marking is local viewing state** — completion-time > last-seen never reaches the host.
107
+
108
+ <a id="dev-note"></a>
109
+ ### Dev Note
110
+
111
+ <details>
112
+ <summary>Working context for maintainers — click to expand</summary>
113
+
114
+ None.
115
+
116
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,116 @@
1
+ ---
2
+ description: "qilin Web 客户端的侧边栏外壳插件:品牌行、New Session 操作、折叠控件、可感知滚动的区域席位与底部固定的页脚席位。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/client-ui-sidebar
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ qilin Web 客户端的侧边栏让用户识别当前构建、启动新会话、将导航折叠为 56px 轨道、浏览 Workspace 与 Session,以及打开 Settings。它把页脚固定在底部,并在隐藏空闲滚动条时避免浏览器行发生位移。New Session 优先使用显式选择的 Workspace,其次使用当前 Session 所属的 Workspace,再其次使用最近活跃的 Workspace;如果都不存在,则打开空白的 New Session 页面。部署可以替换品牌标记或名称,同时保留导航控件和轨道几何。
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
+ 侧边栏是导航外壳:用户看到品牌、工作台切换、启动新会话、折叠轨道并到达 Settings。功能插件填充它的席位——ui-workspace 填充 `sidebar.workbench`(品牌行与新建会话之间的切换控件)与 `sidebar.workspaces`,ui-account 填充 `sidebar.footer.action`,ui-settings 以设置面板填充 `sidebar.settings`。
29
+
30
+ ### 品牌与 New Session
31
+
32
+ 展开的品牌行把 `sidebar.brand.mark` 与 `sidebar.brand.name` 渲染为两个独立的 single slot;收起轨道则渲染同一个 mark slot。没有占位者时,外壳使用麒麟印章和本地化的本地构建标签。完整构建把版本胶囊锚定在文字标的右上角;胶囊显示 `version`,并以 `version[-commit][-dirty]` 作为提示文字,该串由 `QILIN_CLIENT_VERSION`、可选的 7 位 `QILIN_CLIENT_COMMIT_HASH` 与 `QILIN_CLIENT_GIT_DIRTY=true` 组装;缺少版本元数据时不显示胶囊。New Session 优先使用作用域操作明确指定的 Workspace,否则使用当前 Session 所属 Workspace,再否则使用最近活跃 Workspace;一个 Workspace 都没有时则清空选择,进入空白 New Session 页面。
33
+
34
+ ### 全局面板入口
35
+
36
+ 插件在 root 作用域的 `sidebar.panellist` list 中注册图标组件,提供 `id`、可选 `order`,以及字符串或随语言变化的 `label`。同一个 id 寻址布局中 root 作用域 `main` keyed slot 的组件;选择不存在的主面板条目会抛错,并保留当前选中态。标签提供普通可见文字、无障碍名称和折叠提示。每一行通过 `usePanelInfo` 读取自己的选中态;DOM 焦点移到搜索框或目录选择器时,显示的面板及其列表项选中态不变。没有注册项时,列表及其间距均不渲染。默认组合不注册示例面板。
37
+
38
+ ### 折叠行为
39
+
40
+ 实时收起时,展开内容在当前宽度淡出,上方控件共用一次淡入并左移进入 56px 轨道,由布局的栏滑动结束整段动画。页面初始即为收起状态时会静态渲染轨道;减少动态效果模式会禁用两段过渡。固定在底部的页脚席位只共用淡入时序,不发生横向位移。
41
+
42
+ 侧栏收起时,顶部展开按钮承载可选、不可交互的 `sidebar.toggle.badge` slot。占用方提供状态和提示内容,不增加操作,也不改变按钮的导航行为。
43
+
44
+ 实时收起时,展开内容在当前宽度淡出,上方控件共用同一段透明度渐变,并向左平移进入 56px 轨道,由布局的栏滑动结束整段动画。页面初始即为收起状态时会静态渲染轨道;减少动态效果模式会禁用两段过渡。固定在底部的 `sidebar.settings` 控件共用相同的透明度渐变时序,但不发生横向位移。
45
+
46
+ 在 Windows Electron 中,`html[data-windows-titlebar]` 将两种状态下的侧栏开关固定在顶栏左上角,仅在展开态与新建会话按钮左边缘对齐。展开态品牌位于顶栏下方、新建会话按钮上方,按钮上方额外留出 8px。收起后,品牌和侧栏内容隐藏,新建会话按钮排在侧栏开关与 Desktop 菜单之间。侧栏在收起态将根元素的 `--qilin-windows-menu-start` 设为 84px;Desktop preload 使用它将菜单放在新建会话之后,展开态默认为 48px。顶栏图标按钮采用 28px 圆形控件中的居中 16px 图标,并从窗口拖拽区域中排除。
47
+
48
+ ### macOS 桌面
49
+
50
+ 在 `html[data-platform='darwin']`(仅由桌面载体的 preload 设置)下,展开的侧边栏列顶部有一条 52px 的顶部条:避开 hiddenInset 红绿灯并承载收起按钮;顶部条给自身打上 `data-window-drag`,于是它自己的盒子就是窗口拖拽区(唯一的 darwin drag 规则由 ui-web base.css 声明,收起按钮经其交互规则排除在外),其下方的品牌按钮保留 New Session 快捷入口;收起时整列隐藏而非保留轨道。本包向会话头部的 `conversation.session.header.leading` 座注册 `HeaderLeadingControls`——打开侧边栏与 New Session 两个控件,纯由 CSS 依据 AppFrame 发布的 `data-sidebar-collapsed` 属性仅在列隐藏时显示。设计依据(历史记录)见 [macOS 隐藏标题栏 Agent Note](../../../.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md)。
51
+
52
+ ### 滚动条
53
+
54
+ 栏内的滚动条是一种指针可供性:只要指针不在栏内,外壳就把滚动条间接层重新绑定为 `transparent`;指针离开后滑块再保留 2 秒,因此没人指向的列表不会带着滚动条。避免行位移的空间预留属于滚动区域本身(ui-workspace),所以显示滑块不会引起重排。
55
+
56
+ -----
57
+
58
+ <a id="understand-the-implementation"></a>
59
+ ## 理解实现
60
+
61
+ <details>
62
+ <summary>实现细节——点击展开</summary>
63
+
64
+ 外壳是纯组合:`SidebarRootComponentProps` 组合布局 owner share、全局 `useSessions` 与 `useWorkspaces` 钩子、已声明的品牌、`sidebar.workspaces` 与 `sidebar.settings` 子 slot,以及注入的导航回调。面板入口及其可选标题使用相同的组合方式。面板元数据由列表注册和语言变化派生;选中态属于布局存储。
65
+
66
+ ### Slot 纪律
67
+
68
+ 声明感知的 `slots.inject()` 让替换包无论先于还是后于侧边栏激活都能生效。页脚把 `sidebar.footer.action` 列表堆叠在 `sidebar.settings` 占位者之上,侧边栏向两者共享其栏状态(`wide`)。`/client` 导出接口只包含插件主体(`apply`/`inject`)及约定类型;SidebarRoot、行组件与树派生仍由 slot 注册封装在包内。
69
+
70
+ </details>
71
+
72
+ -----
73
+
74
+ <a id="further-exploration"></a>
75
+ ## 进一步探索
76
+
77
+ 以下页面覆盖填充外壳席位的各个界面与组合模型。
78
+
79
+ - [ui-workspace](../ui-workspace/README.zh.md)——渲染到 `sidebar.workspaces` 的 Workspace 与 Session 浏览器。
80
+ - [ui-account](../ui-account/README.zh.md)——渲染到 `sidebar.footer.action` 的头像菜单,其中包含 Settings 入口。
81
+ - [ui-settings](../ui-settings/README.zh.md)——`sidebar.settings` 面板占位者背后的设置领域底座。
82
+ - [ui-layout](../ui-layout/README.zh.md)——折叠所使用轨道与栏状态的布局 owner。
83
+ - [ui-theme](../ui-theme/README.zh.md)——外壳所重新绑定的滚动条 token 间接层。
84
+ - [slot 系统标准](../../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.zh.md)——席位背后的组合模型。
85
+
86
+ -----
87
+
88
+ <a id="model-experience"></a>
89
+ ## 模型体验
90
+
91
+ 无。该包是浏览器端 UI 插件层,不注册任何面向模型的内容。
92
+
93
+ #### KV Cache 影响
94
+
95
+ 无;该包既不组装也不发送提供方请求。
96
+
97
+ ## 已知限制与延期工作
98
+
99
+ <a id="known-limitations-and-deferred-work"></a>
100
+
101
+
102
+ 这些限制定义外壳拥有什么、其占位方拥有什么;它们是当前包约束。
103
+
104
+ - **Session 状态点渲染由 ui-workspace 持有**:本外壳没有可用的 done/error 通知数据源。
105
+ - **Workspace 浏览行为由组合持有**:分组、排序、搜索与行状态都属于 ui-workspace,不属于此外壳。
106
+ - **「New task completed」未读标记是本地查看状态**:完成时间 > 上次查看时间这一事实永远不会到达宿主。
107
+
108
+ <a id="dev-note"></a>
109
+ ### 开发备注
110
+
111
+ <details>
112
+ <summary>维护者的工作上下文——点击展开</summary>
113
+
114
+ 无。
115
+
116
+ </details>
package/lib/client.js ADDED
@@ -0,0 +1,569 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@qilin-agent/client-ui-sidebar",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _qilin_agent_client_store = require("@qilin-agent/client-store");
8
+ let _qilin_agent_client_ui_slots = require("@qilin-agent/client-ui-slots");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
11
+ let react = require("react");
12
+ //#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-sidebar/src/client/HeaderLeadingControls.module.css.mjs
13
+ const css$1 = ".bVkeZq_controls{display:none}[data-sidebar-collapsed] .bVkeZq_controls{align-items:center;gap:8px;margin-right:8px;padding-left:68px;display:flex}.bVkeZq_iconButton{corner-shape:round;width:28px;height:28px;color:var(--qilin-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.bVkeZq_iconButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.bVkeZq_iconButton svg{width:15px;height:15px}";
14
+ const tagId$1 = "@qilin-agent/client-ui-sidebar/HeaderLeadingControls.module.css";
15
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
16
+ const tag = document.createElement("style");
17
+ tag.dataset.plugin = "@qilin-agent/client-ui-sidebar";
18
+ tag.dataset.pluginCss = tagId$1;
19
+ tag.textContent = css$1;
20
+ document.head.appendChild(tag);
21
+ }
22
+ var HeaderLeadingControls_module_css_default = {
23
+ "controls": "bVkeZq_controls",
24
+ "iconButton": "bVkeZq_iconButton"
25
+ };
26
+ //#endregion
27
+ //#region lib/types/client/HeaderLeadingControls.js
28
+ /** macOS-desktop conversation-header controls for the fully hidden sidebar. */
29
+ /**
30
+ * Sidebar-open and New Session controls in the conversation header's
31
+ * root-scoped leading seat, which the resident header renders with or without
32
+ * a selected Session. On macOS desktop a collapsed sidebar hides entirely (no
33
+ * rail), taking both controls off screen; this occupant puts them back beside
34
+ * the traffic lights. Mounted whenever the platform matches; visibility rides the
35
+ * AppFrame-published `data-sidebar-collapsed` attribute in CSS, so no
36
+ * collapse-state pipe is added here.
37
+ * @param props - Injected sidebar actions plus the sidebar locale seat.
38
+ * @returns the two header controls, or null off macOS desktop.
39
+ */
40
+ function HeaderLeadingControls({ toggleSidebar, startSession, useShortcuts, t }) {
41
+ const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "sidebar.left.toggle"));
42
+ const newShortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.new"));
43
+ const newHint = newShortcut?.keys.length ? t("shortcut.hint", {
44
+ label: t("session.new.label"),
45
+ keys: newShortcut.keys.join(" ")
46
+ }) : t("session.new.label");
47
+ if (!(0, _qilin_agent_client_ui_primitives.isDarwinDesktop)()) return null;
48
+ return (0, react_jsx_runtime.jsxs)("div", {
49
+ className: HeaderLeadingControls_module_css_default.controls,
50
+ children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
51
+ label: shortcut?.keys.length ? t("shortcut.hint", {
52
+ label: t("toggle.open"),
53
+ keys: shortcut.keys.join(" ")
54
+ }) : t("toggle.open"),
55
+ delayMs: 500,
56
+ children: (0, react_jsx_runtime.jsx)("button", {
57
+ type: "button",
58
+ className: HeaderLeadingControls_module_css_default.iconButton,
59
+ "aria-label": t("toggle.open"),
60
+ "aria-keyshortcuts": shortcut?.aria,
61
+ onClick: () => {
62
+ toggleSidebar();
63
+ },
64
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPanelLeftOutline16, { size: 16 })
65
+ })
66
+ }), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
67
+ label: newHint,
68
+ delayMs: 500,
69
+ children: (0, react_jsx_runtime.jsx)("button", {
70
+ type: "button",
71
+ className: HeaderLeadingControls_module_css_default.iconButton,
72
+ "aria-label": t("session.new.label"),
73
+ "aria-keyshortcuts": newShortcut?.aria,
74
+ onClick: () => {
75
+ startSession();
76
+ },
77
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconNewChatOutline16, { size: 16 })
78
+ })
79
+ })]
80
+ });
81
+ }
82
+ //#endregion
83
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
84
+ function r(e) {
85
+ var t, f, n = "";
86
+ if ("string" == typeof e || "number" == typeof e) n += e;
87
+ else if ("object" == typeof e) if (Array.isArray(e)) {
88
+ var o = e.length;
89
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
90
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
91
+ return n;
92
+ }
93
+ function clsx() {
94
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
95
+ return n;
96
+ }
97
+ //#endregion
98
+ //#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-sidebar/src/client/SidebarRoot.module.css.mjs
99
+ const css = "._1HRR8W_root{--qilin-sidebar-inline-padding:12px;height:100%;padding:6px var(--qilin-sidebar-inline-padding);box-sizing:border-box;background:var(--qilin-specific-sidebar-fill);color:var(--qilin-alias-label-primary);--qilin-scrollbar-thumb:var(--qilin-alias-scrollbar-bg-l2);--qilin-scrollbar-thumb-hover:var(--qilin-alias-scrollbar-hover-l2);flex-direction:column;font-size:14px;display:flex}[data-platform=darwin] ._1HRR8W_root{background:0 0}._1HRR8W_root._1HRR8W_collapsed{padding:18px 10px 6px}[data-windows-titlebar] ._1HRR8W_root ._1HRR8W_logoRow{height:40px;margin:0;padding:0}[data-windows-titlebar] ._1HRR8W_toggle{top:calc((var(--qilin-windows-titlebar-height) - 28px) / 2);z-index:30;-webkit-app-region:no-drag;position:fixed;left:12px}[data-windows-titlebar] ._1HRR8W_root._1HRR8W_collapsed ._1HRR8W_logoRow{height:0}[data-windows-titlebar] ._1HRR8W_root:not(._1HRR8W_collapsed) ._1HRR8W_brand{padding-left:4px}[data-windows-titlebar] ._1HRR8W_brandIdentity{transform:translateY(1px)}[data-windows-titlebar] ._1HRR8W_brandMark{transform:translate(1px)}[data-windows-titlebar] ._1HRR8W_brandName{font-weight:400}[data-windows-titlebar] ._1HRR8W_root._1HRR8W_collapsed{padding:0}[data-windows-titlebar] ._1HRR8W_root:not(._1HRR8W_collapsed) ._1HRR8W_newSession{margin-top:8px;margin-left:0}[data-windows-titlebar] ._1HRR8W_collapsed ._1HRR8W_panelList,[data-windows-titlebar] ._1HRR8W_collapsed ._1HRR8W_regionArea,[data-windows-titlebar] ._1HRR8W_collapsed ._1HRR8W_footArea{display:none}html[data-windows-titlebar]:has([data-sidebar-collapsed=true]){--qilin-windows-menu-start:84px}[data-windows-titlebar] ._1HRR8W_collapsed ._1HRR8W_newSession{top:calc((var(--qilin-windows-titlebar-height) - 28px) / 2);z-index:30;-webkit-app-region:no-drag;border:none;margin:0;padding:0;position:fixed;left:48px}[data-windows-titlebar] ._1HRR8W_railIn ._1HRR8W_iconButton,[data-windows-titlebar] ._1HRR8W_railIn ._1HRR8W_newSession{animation:none}[data-windows-titlebar] ._1HRR8W_collapsed ._1HRR8W_toggle,[data-windows-titlebar] ._1HRR8W_collapsed ._1HRR8W_newSession{corner-shape:round;width:28px;height:28px;color:var(--qilin-alias-label-secondary);border-radius:50%}[data-windows-titlebar] ._1HRR8W_collapsed ._1HRR8W_toggle ._1HRR8W_panelIcon{display:inline}._1HRR8W_root._1HRR8W_quietBars{--qilin-scrollbar-thumb:transparent;--qilin-scrollbar-thumb-hover:transparent}._1HRR8W_fading>*{opacity:0;transition:opacity .15s var(--ds-ease-in-out)}._1HRR8W_wide{animation:_1HRR8W_wide-in .2s var(--ds-ease-in-out)}@keyframes _1HRR8W_wide-in{0%{opacity:0}}._1HRR8W_railIn ._1HRR8W_iconButton,._1HRR8W_railIn ._1HRR8W_newSession,._1HRR8W_railIn ._1HRR8W_panelList,._1HRR8W_railIn ._1HRR8W_regionArea{animation:_1HRR8W_rail-in .15s var(--ds-ease-in-out) backwards}._1HRR8W_railIn ._1HRR8W_footArea{animation:_1HRR8W_rail-fade-in .15s var(--ds-ease-in-out) backwards}@keyframes _1HRR8W_rail-in{0%{opacity:0;transform:translate(49px)}}@keyframes _1HRR8W_rail-fade-in{0%{opacity:0}}._1HRR8W_topStrip{box-sizing:border-box;height:52px;margin:-6px calc(-1 * var(--qilin-sidebar-inline-padding)) 0;flex:none;justify-content:flex-end;align-items:center;padding:0 12px;display:flex}._1HRR8W_topStrip+._1HRR8W_logoRow{margin-top:-12px}._1HRR8W_logoRow{box-sizing:border-box;flex:none;justify-content:flex-end;align-items:center;gap:8px;height:60px;margin-bottom:8px;padding:8px 0 8px 4px;display:flex;overflow:hidden}._1HRR8W_collapsed ._1HRR8W_logoRow{justify-content:flex-start;height:36px;margin-bottom:12px;padding:0}._1HRR8W_brand{min-width:0;color:inherit;cursor:pointer;background:0 0;border:none;flex:1;align-items:center;padding:0;display:inline-flex;overflow:hidden}._1HRR8W_brandIdentity{align-items:center;gap:8px;min-width:0;height:24px;display:inline-flex}._1HRR8W_brandMark{flex:none;justify-content:center;align-items:center;display:inline-flex;overflow:visible}._1HRR8W_brandName{letter-spacing:.04em;align-items:center;gap:6px;min-width:0;height:24px;font-size:18px;font-weight:600;line-height:24px;display:inline-flex}._1HRR8W_fallbackBrandName{letter-spacing:0;white-space:nowrap;font-size:17px}._1HRR8W_iconButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:var(--qilin-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex;position:relative}._1HRR8W_iconButton:hover{background:var(--qilin-alias-interactive-bg-hover)}._1HRR8W_collapsed ._1HRR8W_iconButton{border-radius:12px;width:36px;height:36px}._1HRR8W_collapsed ._1HRR8W_toggle ._1HRR8W_panelIcon{display:none}._1HRR8W_collapsed ._1HRR8W_toggle:hover ._1HRR8W_panelIcon{display:inline}._1HRR8W_collapsed ._1HRR8W_toggle:hover ._1HRR8W_railMark{display:none}._1HRR8W_railMark{justify-content:center;align-items:center;display:inline-flex}._1HRR8W_collapsed ._1HRR8W_iconButton{color:var(--qilin-alias-label-primary)}._1HRR8W_buildBadge{border:.5px solid var(--qilin-alias-border-l2);height:16px;color:var(--qilin-alias-label-tertiary);background:var(--qilin-alias-bg-layer-2);font-family:var(--ds-font-family-code);white-space:nowrap;pointer-events:none;border-radius:5px;flex:none;align-self:flex-start;align-items:center;margin-top:1px;padding:0 5px;font-size:10px;font-weight:500;line-height:16px;display:inline-flex}._1HRR8W_newSession{box-sizing:border-box;border:.5px solid var(--qilin-alias-border-l3);background:var(--qilin-alias-button-elevated-fill);height:38px;color:var(--qilin-alias-label-primary);cursor:pointer;border-radius:12px;flex:none;justify-content:center;align-items:center;gap:6px;margin:0 2px 12px;padding:8px 16px;font-size:14px;font-weight:500;line-height:22px;display:flex;position:relative;overflow:hidden}._1HRR8W_newSession:hover{background:var(--qilin-alias-button-floating-hover)}[data-platform=darwin] ._1HRR8W_newSession{background:color-mix(in srgb, var(--qilin-alias-button-elevated-fill) 75%, transparent)}[data-platform=darwin] ._1HRR8W_newSession:hover{background:color-mix(in srgb, var(--qilin-alias-button-floating-hover) 75%, transparent)}._1HRR8W_collapsed ._1HRR8W_newSession{background:0 0;border-color:#0000;justify-content:center;align-self:flex-start;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}._1HRR8W_collapsed ._1HRR8W_newSession:hover{background:var(--qilin-alias-interactive-bg-hover)}._1HRR8W_newSessionLabel{white-space:nowrap;max-width:200px;overflow:hidden}._1HRR8W_collapsed ._1HRR8W_newSessionLabel{max-width:0}._1HRR8W_newSessionShortcut{pointer-events:none;opacity:0;flex:none;font-weight:400;display:inline-flex;position:absolute;right:12px}._1HRR8W_newSession:is(:hover,:focus-visible) ._1HRR8W_newSessionShortcut{opacity:1}._1HRR8W_newSession:hover ._1HRR8W_newSessionLabel:has(+._1HRR8W_newSessionShortcut){mask-image:linear-gradient(90deg,#000 calc(100% - 16px),#0000)}._1HRR8W_panelList{flex-direction:column;flex:none;gap:4px;margin-bottom:8px;display:flex}._1HRR8W_panelSection{letter-spacing:.08em;min-height:0;color:var(--qilin-alias-label-tertiary,#7a8b9f);user-select:none;align-items:center;padding:12px 10px 2px;font-size:11px;display:flex}._1HRR8W_panelSectionRail{background:var(--qilin-alias-border-l1,#e8eff712);width:100%;height:.5px;display:block}._1HRR8W_panelList>._1HRR8W_panelSection:first-child{padding-top:4px}._1HRR8W_collapsed ._1HRR8W_panelList ._1HRR8W_panelSection{margin:-3px 0;padding:0}._1HRR8W_panelRow{box-sizing:border-box;min-height:36px;color:var(--qilin-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;margin:0 2px;padding:7px 8px;line-height:22px;display:flex}._1HRR8W_panelRow:hover{background:var(--qilin-alias-interactive-bg-hover)}._1HRR8W_panelRow._1HRR8W_panelActive{background:var(--qilin-alias-interactive-bg-hover);color:var(--qilin-alias-label-primary)}._1HRR8W_panelRow:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:-2px}._1HRR8W_panelGlyph{flex:none;justify-content:center;align-items:center;display:inline-flex}._1HRR8W_panelTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}._1HRR8W_collapsed ._1HRR8W_panelList{gap:12px;margin-bottom:12px}._1HRR8W_collapsed ._1HRR8W_panelRow{width:36px;height:36px;color:var(--qilin-alias-label-primary);justify-content:center;margin:0;padding:0}._1HRR8W_regionArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--qilin-sidebar-inline-padding));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:hidden}._1HRR8W_collapsed ._1HRR8W_regionArea{margin-left:0;margin-right:0;padding-left:0}._1HRR8W_footArea{flex-direction:column;flex:none;display:flex}._1HRR8W_settingsArea,._1HRR8W_accountArea,._1HRR8W_footerActions{flex:none;width:100%;min-width:0}._1HRR8W_footerActions{display:flex}._1HRR8W_collapsed ._1HRR8W_footArea{align-items:center}._1HRR8W_collapsed ._1HRR8W_settingsArea,._1HRR8W_collapsed ._1HRR8W_accountArea,._1HRR8W_collapsed ._1HRR8W_footerActions{justify-content:center;width:auto;display:flex}@media (prefers-reduced-motion:reduce){._1HRR8W_wide,._1HRR8W_fading>*,._1HRR8W_railIn ._1HRR8W_iconButton,._1HRR8W_railIn ._1HRR8W_newSession,._1HRR8W_railIn ._1HRR8W_panelList,._1HRR8W_railIn ._1HRR8W_footArea,._1HRR8W_railIn ._1HRR8W_regionArea{transition:none;animation:none}}";
100
+ const tagId = "@qilin-agent/client-ui-sidebar/SidebarRoot.module.css";
101
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
102
+ const tag = document.createElement("style");
103
+ tag.dataset.plugin = "@qilin-agent/client-ui-sidebar";
104
+ tag.dataset.pluginCss = tagId;
105
+ tag.textContent = css;
106
+ document.head.appendChild(tag);
107
+ }
108
+ var SidebarRoot_module_css_default = {
109
+ "accountArea": "_1HRR8W_accountArea",
110
+ "brand": "_1HRR8W_brand",
111
+ "brandIdentity": "_1HRR8W_brandIdentity",
112
+ "brandMark": "_1HRR8W_brandMark",
113
+ "brandName": "_1HRR8W_brandName",
114
+ "buildBadge": "_1HRR8W_buildBadge",
115
+ "collapsed": "_1HRR8W_collapsed",
116
+ "fading": "_1HRR8W_fading",
117
+ "fallbackBrandName": "_1HRR8W_fallbackBrandName",
118
+ "footArea": "_1HRR8W_footArea",
119
+ "footerActions": "_1HRR8W_footerActions",
120
+ "iconButton": "_1HRR8W_iconButton",
121
+ "logoRow": "_1HRR8W_logoRow",
122
+ "newSession": "_1HRR8W_newSession",
123
+ "newSessionLabel": "_1HRR8W_newSessionLabel",
124
+ "newSessionShortcut": "_1HRR8W_newSessionShortcut",
125
+ "panelActive": "_1HRR8W_panelActive",
126
+ "panelGlyph": "_1HRR8W_panelGlyph",
127
+ "panelIcon": "_1HRR8W_panelIcon",
128
+ "panelList": "_1HRR8W_panelList",
129
+ "panelRow": "_1HRR8W_panelRow",
130
+ "panelSection": "_1HRR8W_panelSection",
131
+ "panelSectionRail": "_1HRR8W_panelSectionRail",
132
+ "panelTitle": "_1HRR8W_panelTitle",
133
+ "quietBars": "_1HRR8W_quietBars",
134
+ "rail-fade-in": "_1HRR8W_rail-fade-in",
135
+ "rail-in": "_1HRR8W_rail-in",
136
+ "railIn": "_1HRR8W_railIn",
137
+ "railMark": "_1HRR8W_railMark",
138
+ "regionArea": "_1HRR8W_regionArea",
139
+ "root": "_1HRR8W_root",
140
+ "settingsArea": "_1HRR8W_settingsArea",
141
+ "toggle": "_1HRR8W_toggle",
142
+ "topStrip": "_1HRR8W_topStrip",
143
+ "wide": "_1HRR8W_wide",
144
+ "wide-in": "_1HRR8W_wide-in"
145
+ };
146
+ //#endregion
147
+ //#region lib/types/client/SidebarRoot.js
148
+ /**
149
+ * Sidebar shell: column geometry and global panel navigation.
150
+ * Collapse is a slide plus crossfade:
151
+ * content freezes at its expanded width (inline style) and fades out in place
152
+ * while the sliding column (AppFrame grid tracks) clips it — nothing reflows
153
+ * mid-slide. At settle the wide-only content unmounts and the upper
154
+ * controls enter the 56px rail from the same horizontal offset (one icon each,
155
+ * same top-down order) on one fade that ends with the slide. The bottom-pinned
156
+ * footer seats only fade. The workspace/session browsing region between
157
+ * global panel rows and the foot is the `sidebar.workspaces` registrant's,
158
+ * and the foot stacks `sidebar.footer.action`, the `sidebar.account` row, and
159
+ * `sidebar.settings`; the shell hands them the wide flag (plus an expand
160
+ * request callback for the browser).
161
+ *
162
+ * The column also owns whether the scroll regions nested in it draw a
163
+ * scrollbar at all: the shell tracks the pointer and rebinds ui-theme's
164
+ * scrollbar indirection away while it is elsewhere, so a list the user is not
165
+ * pointing at carries no bar.
166
+ */
167
+ /** Wide-content unmount delay; matches the 150ms wide-content fade-out. */
168
+ const COLLAPSE_SETTLE_MS = 150;
169
+ /**
170
+ * How long the column's scrollbars stay drawn after the pointer leaves it.
171
+ * The bar is a pointer affordance here, and hiding it on the leave event
172
+ * itself makes it blink out while the pointer is only crossing the column's
173
+ * edge — on the way to the conversation, or around a portalled menu.
174
+ */
175
+ const SCROLLBAR_LINGER_MS = 2e3;
176
+ /**
177
+ * Read the build identity the client artifact was compiled with.
178
+ * @returns the badge facts, or undefined when the artifact carries no version.
179
+ */
180
+ function localBuild() {
181
+ const engine = "3.1.3";
182
+ const version = {}.QILIN_CLIENT_PRODUCT_VERSION ?? engine;
183
+ if (version === void 0) return void 0;
184
+ return {
185
+ version,
186
+ detail: version + `-d61554c` + ({}.QILIN_CLIENT_GIT_DIRTY === "true" ? "-dirty" : "") + (engine === version ? "" : `(引擎 ${engine})`)
187
+ };
188
+ }
189
+ /** Each panel row subscribes only to its own selection state. */
190
+ function PanelRow({ id, label, wide, usePanelInfo, selectPanel, renderSlot }) {
191
+ const active = usePanelInfo((info) => info.activePanelId === id);
192
+ return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
193
+ label,
194
+ delayMs: 500,
195
+ disabled: wide,
196
+ children: (0, react_jsx_runtime.jsxs)("button", {
197
+ type: "button",
198
+ className: clsx(SidebarRoot_module_css_default.panelRow, active && SidebarRoot_module_css_default.panelActive),
199
+ "aria-label": label,
200
+ "aria-current": active ? "page" : void 0,
201
+ onClick: () => {
202
+ selectPanel(id);
203
+ },
204
+ children: [(0, react_jsx_runtime.jsx)("span", {
205
+ className: SidebarRoot_module_css_default.panelGlyph,
206
+ "aria-hidden": "true",
207
+ children: renderSlot("sidebar.panellist", {
208
+ size: wide ? 16 : 18,
209
+ active
210
+ }, { only: id })
211
+ }), wide && (0, react_jsx_runtime.jsx)("span", {
212
+ className: clsx(SidebarRoot_module_css_default.panelTitle, SidebarRoot_module_css_default.wide),
213
+ children: label
214
+ })]
215
+ })
216
+ });
217
+ }
218
+ /**
219
+ * Render the sidebar column shell.
220
+ * @param props - composed slot props (runtime share + injected callbacks, contract/slots.ts).
221
+ * @returns the sidebar element tree.
222
+ */
223
+ function SidebarRoot({ collapsed, width, startSession, toggleSidebar, selectPanel, usePanels, useShortcuts, usePanelInfo, t, renderSlot }) {
224
+ const panels = usePanels((snapshot) => snapshot);
225
+ const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "sidebar.left.toggle"));
226
+ const newShortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.new"));
227
+ const [settled, setSettled] = (0, react.useState)(collapsed);
228
+ (0, react.useEffect)(() => {
229
+ if (!collapsed) {
230
+ setSettled(false);
231
+ return;
232
+ }
233
+ const timer = window.setTimeout(() => {
234
+ setSettled(true);
235
+ }, COLLAPSE_SETTLE_MS);
236
+ return () => {
237
+ window.clearTimeout(timer);
238
+ };
239
+ }, [collapsed]);
240
+ const windowsTitlebar = document.documentElement.hasAttribute("data-windows-titlebar");
241
+ const wide = windowsTitlebar ? !collapsed : !collapsed || !settled;
242
+ const lastWideWidth = (0, react.useRef)(width);
243
+ if (!collapsed) lastWideWidth.current = width;
244
+ const everWide = (0, react.useRef)(!collapsed);
245
+ if (!collapsed) everWide.current = true;
246
+ const column = (0, react.useRef)(null);
247
+ const [pointerInside, setPointerInside] = (0, react.useState)(false);
248
+ const lingerTimer = (0, react.useRef)(void 0);
249
+ const armLinger = () => {
250
+ if (lingerTimer.current !== void 0) return;
251
+ lingerTimer.current = window.setTimeout(() => {
252
+ lingerTimer.current = void 0;
253
+ setPointerInside(false);
254
+ }, SCROLLBAR_LINGER_MS);
255
+ };
256
+ const cancelLinger = () => {
257
+ window.clearTimeout(lingerTimer.current);
258
+ lingerTimer.current = void 0;
259
+ };
260
+ (0, react.useEffect)(() => {
261
+ if (!pointerInside) return;
262
+ const onMove = (event) => {
263
+ const rect = column.current?.getBoundingClientRect();
264
+ /* v8 ignore next -- the listener only exists while the column is mounted and revealed. */
265
+ if (rect === void 0) return;
266
+ if (event.clientX >= rect.left && event.clientX < rect.right && event.clientY >= rect.top && event.clientY < rect.bottom) cancelLinger();
267
+ else armLinger();
268
+ };
269
+ document.addEventListener("pointermove", onMove);
270
+ return () => {
271
+ document.removeEventListener("pointermove", onMove);
272
+ cancelLinger();
273
+ };
274
+ }, [pointerInside]);
275
+ const build = localBuild();
276
+ const darwinDesktop = (0, _qilin_agent_client_ui_primitives.isDarwinDesktop)();
277
+ const toggle = (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
278
+ label: collapsed ? t("toggle.open") : t("toggle.collapse"),
279
+ shortcutKeys: shortcut?.keys,
280
+ delayMs: 500,
281
+ children: (0, react_jsx_runtime.jsxs)("button", {
282
+ type: "button",
283
+ className: clsx(SidebarRoot_module_css_default.iconButton, SidebarRoot_module_css_default.toggle),
284
+ "aria-label": collapsed ? t("toggle.open") : t("toggle.collapse"),
285
+ "aria-keyshortcuts": shortcut?.aria,
286
+ onClick: () => {
287
+ toggleSidebar();
288
+ },
289
+ children: [
290
+ !wide && !windowsTitlebar && (0, react_jsx_runtime.jsx)("span", {
291
+ className: SidebarRoot_module_css_default.railMark,
292
+ "aria-hidden": "true",
293
+ children: renderSlot("sidebar.brand.mark", { size: 24 }, { fallback: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.QilinSeal, { size: 24 }) })
294
+ }),
295
+ (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPanelLeftOutline16, {
296
+ className: SidebarRoot_module_css_default.panelIcon,
297
+ size: wide || windowsTitlebar ? 16 : 18
298
+ }),
299
+ !wide && renderSlot("sidebar.toggle.badge", {})
300
+ ]
301
+ })
302
+ });
303
+ return (0, react_jsx_runtime.jsxs)("div", {
304
+ ref: column,
305
+ className: clsx(SidebarRoot_module_css_default.root, !wide && SidebarRoot_module_css_default.collapsed, !wide && everWide.current && SidebarRoot_module_css_default.railIn, collapsed && wide && SidebarRoot_module_css_default.fading, !pointerInside && SidebarRoot_module_css_default.quietBars),
306
+ style: wide ? { width: collapsed ? lastWideWidth.current : width } : void 0,
307
+ onPointerEnter: () => {
308
+ cancelLinger();
309
+ setPointerInside(true);
310
+ },
311
+ onPointerLeave: () => {
312
+ armLinger();
313
+ },
314
+ children: [
315
+ darwinDesktop && (0, react_jsx_runtime.jsx)("div", {
316
+ className: SidebarRoot_module_css_default.topStrip,
317
+ "data-window-drag": true,
318
+ children: toggle
319
+ }),
320
+ (0, react_jsx_runtime.jsxs)("div", {
321
+ className: SidebarRoot_module_css_default.logoRow,
322
+ children: [wide && (0, react_jsx_runtime.jsx)("button", {
323
+ type: "button",
324
+ className: clsx(SidebarRoot_module_css_default.brand, SidebarRoot_module_css_default.wide),
325
+ "aria-label": t("session.new.label"),
326
+ "aria-keyshortcuts": newShortcut?.aria,
327
+ onClick: () => {
328
+ startSession();
329
+ },
330
+ children: (0, react_jsx_runtime.jsxs)("span", {
331
+ className: SidebarRoot_module_css_default.brandIdentity,
332
+ "aria-hidden": "true",
333
+ children: [(0, react_jsx_runtime.jsx)("span", {
334
+ className: SidebarRoot_module_css_default.brandMark,
335
+ children: renderSlot("sidebar.brand.mark", { size: 24 }, { fallback: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.QilinSeal, { size: 24 }) })
336
+ }), (0, react_jsx_runtime.jsxs)("span", {
337
+ className: SidebarRoot_module_css_default.brandName,
338
+ children: [renderSlot("sidebar.brand.name", {}, { fallback: (0, react_jsx_runtime.jsx)("span", {
339
+ className: SidebarRoot_module_css_default.fallbackBrandName,
340
+ children: t("brand.localBuild")
341
+ }) }), build !== void 0 && (0, react_jsx_runtime.jsx)("span", {
342
+ className: SidebarRoot_module_css_default.buildBadge,
343
+ title: build.detail,
344
+ children: build.version
345
+ })]
346
+ })]
347
+ })
348
+ }), !darwinDesktop && toggle]
349
+ }),
350
+ wide && renderSlot("sidebar.workbench", { wide }),
351
+ (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
352
+ label: t("session.new.label"),
353
+ shortcutKeys: newShortcut?.keys,
354
+ delayMs: 500,
355
+ disabled: wide,
356
+ children: (0, react_jsx_runtime.jsxs)("button", {
357
+ type: "button",
358
+ className: SidebarRoot_module_css_default.newSession,
359
+ "aria-label": t("session.new.label"),
360
+ "aria-keyshortcuts": newShortcut?.aria,
361
+ onClick: () => {
362
+ startSession();
363
+ },
364
+ children: [
365
+ (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconNewChatOutline16, { size: wide ? 14 : windowsTitlebar ? 16 : 18 }),
366
+ wide && (0, react_jsx_runtime.jsx)("span", {
367
+ className: clsx(SidebarRoot_module_css_default.newSessionLabel, SidebarRoot_module_css_default.wide),
368
+ children: t("session.new")
369
+ }),
370
+ wide && newShortcut !== void 0 && newShortcut.keys.length > 0 && (0, react_jsx_runtime.jsx)("span", {
371
+ className: SidebarRoot_module_css_default.newSessionShortcut,
372
+ "aria-hidden": "true",
373
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.ShortcutKeys, { keys: newShortcut.keys })
374
+ })
375
+ ]
376
+ })
377
+ }),
378
+ panels.length > 0 && (0, react_jsx_runtime.jsx)("nav", {
379
+ className: SidebarRoot_module_css_default.panelList,
380
+ "aria-label": t("panels.label"),
381
+ children: panels.map(({ id, label, section }, index) => (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [section !== void 0 && section !== panels[index - 1]?.section ? (0, react_jsx_runtime.jsx)("div", {
382
+ className: clsx(SidebarRoot_module_css_default.panelSection, wide && SidebarRoot_module_css_default.wide),
383
+ "aria-hidden": !wide,
384
+ children: wide ? section : (0, react_jsx_runtime.jsx)("span", { className: SidebarRoot_module_css_default.panelSectionRail })
385
+ }) : null, (0, react_jsx_runtime.jsx)(PanelRow, {
386
+ id,
387
+ label,
388
+ wide,
389
+ usePanelInfo,
390
+ selectPanel,
391
+ renderSlot
392
+ })] }, id))
393
+ }),
394
+ (0, react_jsx_runtime.jsx)("div", {
395
+ className: SidebarRoot_module_css_default.regionArea,
396
+ children: renderSlot("sidebar.workspaces", {
397
+ wide,
398
+ expandSidebar: () => {
399
+ if (collapsed) toggleSidebar();
400
+ }
401
+ })
402
+ }),
403
+ (0, react_jsx_runtime.jsxs)("div", {
404
+ className: SidebarRoot_module_css_default.footArea,
405
+ children: [
406
+ (0, react_jsx_runtime.jsx)("div", {
407
+ className: SidebarRoot_module_css_default.footerActions,
408
+ children: renderSlot("sidebar.footer.action", { wide })
409
+ }),
410
+ (0, react_jsx_runtime.jsx)("div", {
411
+ className: SidebarRoot_module_css_default.accountArea,
412
+ children: renderSlot("sidebar.account", { wide })
413
+ }),
414
+ (0, react_jsx_runtime.jsx)("div", {
415
+ className: SidebarRoot_module_css_default.settingsArea,
416
+ children: renderSlot("sidebar.settings", { wide })
417
+ })
418
+ ]
419
+ })
420
+ ]
421
+ });
422
+ }
423
+ //#endregion
424
+ //#region lib/types/client/locales.js
425
+ /** `sidebar` namespace dictionaries for shell controls and global panels. */
426
+ /** Simplified Chinese dictionary (the key-set source of truth). */
427
+ const zh = {
428
+ "shortcut.hint": "{label} {keys}",
429
+ "session.new": "新会话",
430
+ "session.new.label": "新建会话",
431
+ "toggle.open": "打开侧边栏",
432
+ "toggle.collapse": "收起侧边栏",
433
+ "panels.label": "全局面板"
434
+ };
435
+ /** English dictionary, checked complete against the zh key set. */
436
+ const en = {
437
+ "shortcut.hint": "{label} {keys}",
438
+ "session.new": "New Session",
439
+ "session.new.label": "New session",
440
+ "toggle.open": "Open sidebar",
441
+ "toggle.collapse": "Collapse sidebar",
442
+ "panels.label": "Global panels"
443
+ };
444
+ //#endregion
445
+ //#region lib/types/client/index.js
446
+ /** Dictionary namespace owned by this plugin. */
447
+ const NS = "sidebar";
448
+ /** Services required by the sidebar plugin. */
449
+ const inject = [
450
+ "slots",
451
+ "layout",
452
+ "uiWorkspace",
453
+ "locale",
454
+ "shortcuts"
455
+ ];
456
+ /** Registers the sidebar shell and its service callbacks.
457
+ * @param ctx - Client root context.
458
+ */
459
+ function apply(ctx) {
460
+ const workspaceNavigation = ctx.get("uiWorkspace");
461
+ ctx.effect(() => ctx.locale.register(NS, {
462
+ zh,
463
+ en
464
+ }), "ui-sidebar: dictionaries");
465
+ const panels = (0, _qilin_agent_client_store.createSnapshotStore)([]);
466
+ const syncPanels = () => {
467
+ const assigned = {};
468
+ for (const entry of ctx.slots.entriesOfSlot("sidebar.section.assignments")) {
469
+ const face = entry.inject?.(void 0);
470
+ if (face?.assignments !== void 0) Object.assign(assigned, face.assignments);
471
+ }
472
+ const next = ctx.slots.entriesOfSlot("sidebar.panellist").map(({ options }) => {
473
+ const id = options.id;
474
+ const section = assigned[id] ?? (0, _qilin_agent_client_ui_slots.resolveSlotLabel)(options.section);
475
+ return {
476
+ id,
477
+ order: options.order ?? 0,
478
+ label: (0, _qilin_agent_client_ui_slots.resolveSlotLabel)(options.label) ?? id,
479
+ section
480
+ };
481
+ }).sort((a, b) => a.order - b.order);
482
+ const previous = panels.getSnapshot();
483
+ if (previous.length === next.length && previous.every((panel, index) => {
484
+ const candidate = next[index];
485
+ return panel.id === candidate.id && panel.order === candidate.order && panel.label === candidate.label && panel.section === candidate.section;
486
+ })) return;
487
+ panels.set(next);
488
+ };
489
+ ctx.effect(() => ctx.slots.subscribe("sidebar.panellist", syncPanels), "ui-sidebar: panel entries");
490
+ ctx.effect(() => ctx.slots.subscribe("sidebar.section.assignments", syncPanels), "ui-sidebar: section assignments");
491
+ ctx.effect(() => ctx.locale.subscribe(syncPanels), "ui-sidebar: panel labels");
492
+ ctx.effect(() => ctx.slots.admission().subscribe(syncPanels), "ui-sidebar: admission revisions");
493
+ const injectProps = () => ({
494
+ startSession: (workspaceId) => {
495
+ workspaceNavigation.startSession(workspaceId);
496
+ },
497
+ toggleSidebar: () => {
498
+ ctx.layout.toggleSidebar();
499
+ },
500
+ selectPanel: (id) => {
501
+ ctx.layout.selectPanel(id);
502
+ },
503
+ hooks: {
504
+ panels,
505
+ shortcuts: ctx.shortcuts.catalog
506
+ }
507
+ });
508
+ ctx.slots.inject("sidebar", () => ctx.slots.register({
509
+ name: "sidebar",
510
+ locale: NS,
511
+ children: {
512
+ "sidebar.brand.mark": {
513
+ kind: "single",
514
+ scope: "root"
515
+ },
516
+ "sidebar.brand.name": {
517
+ kind: "single",
518
+ scope: "root"
519
+ },
520
+ "sidebar.toggle.badge": {
521
+ kind: "single",
522
+ scope: "root"
523
+ },
524
+ "sidebar.workbench": {
525
+ kind: "single",
526
+ scope: "root"
527
+ },
528
+ "sidebar.panellist": {
529
+ kind: "list",
530
+ scope: "root"
531
+ },
532
+ "sidebar.section.assignments": {
533
+ kind: "list",
534
+ scope: "root"
535
+ },
536
+ "sidebar.workspaces": {
537
+ kind: "single",
538
+ scope: "root"
539
+ },
540
+ "sidebar.settings": {
541
+ kind: "single",
542
+ scope: "root"
543
+ },
544
+ "sidebar.account": {
545
+ kind: "single",
546
+ scope: "root"
547
+ },
548
+ "sidebar.footer.action": {
549
+ kind: "list",
550
+ scope: "root"
551
+ }
552
+ },
553
+ inject: injectProps
554
+ }, SidebarRoot));
555
+ ctx.slots.inject("conversation.header.leading", () => ctx.slots.register({
556
+ name: "conversation.header.leading",
557
+ locale: NS,
558
+ inject: injectProps
559
+ }, HeaderLeadingControls));
560
+ syncPanels();
561
+ }
562
+ //#endregion
563
+ exports.apply = apply;
564
+ exports.inject = inject;
565
+ return module.exports;
566
+ }
567
+ });
568
+
569
+ //# 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-only sidebar plugin. */
3
+ /** Provides no host-side behavior. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,17 @@
1
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@qilin-agent/client-ui-slots';
2
+ import type { SidebarRootInjected } from './contract/slots.ts';
3
+ /** Full props of the conversation-header leading occupant. */
4
+ export type HeaderLeadingControlsProps = PropsRuntime<'conversation.header.leading'> & InjectFace<SidebarRootInjected> & PropsLocale<'sidebar'>;
5
+ /**
6
+ * Sidebar-open and New Session controls in the conversation header's
7
+ * root-scoped leading seat, which the resident header renders with or without
8
+ * a selected Session. On macOS desktop a collapsed sidebar hides entirely (no
9
+ * rail), taking both controls off screen; this occupant puts them back beside
10
+ * the traffic lights. Mounted whenever the platform matches; visibility rides the
11
+ * AppFrame-published `data-sidebar-collapsed` attribute in CSS, so no
12
+ * collapse-state pipe is added here.
13
+ * @param props - Injected sidebar actions plus the sidebar locale seat.
14
+ * @returns the two header controls, or null off macOS desktop.
15
+ */
16
+ export declare function HeaderLeadingControls({ toggleSidebar, startSession, useShortcuts, t }: HeaderLeadingControlsProps): import("react").JSX.Element | null;
17
+ //# sourceMappingURL=HeaderLeadingControls.d.ts.map
@@ -0,0 +1,8 @@
1
+ import type { SidebarRootComponentProps } from './contract/slots.ts';
2
+ /**
3
+ * Render the sidebar column shell.
4
+ * @param props - composed slot props (runtime share + injected callbacks, contract/slots.ts).
5
+ * @returns the sidebar element tree.
6
+ */
7
+ export declare function SidebarRoot({ collapsed, width, startSession, toggleSidebar, selectPanel, usePanels, useShortcuts, usePanelInfo, t, renderSlot, }: SidebarRootComponentProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=SidebarRoot.d.ts.map
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Sidebar slot contract: the registrant-side props composition for the
3
+ * layout-owned `sidebar` slot, plus the holes this shell declares. The shell
4
+ * owns column geometry, the brand row, the workbench-switch seat, New Session,
5
+ * and global panel rows; everything between the workspace section header and
6
+ * the list bottom is the `sidebar.workspaces` registrant's (ui-workspace), and
7
+ * the foot is the `sidebar.settings` registrant's (ui-settings), followed by
8
+ * optional footer actions in `sidebar.footer.action`.
9
+ */
10
+ import type { InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime } from '@qilin-agent/client-ui-slots';
11
+ import type { ObservableSnapshot } from '@qilin-agent/client-store';
12
+ import type { WorkspaceId } from '@qilin-agent/api-workspace-controller/client';
13
+ import type { MainPanelId } from '@qilin-agent/client-ui-layout/client';
14
+ import type { ShortcutCatalogEntry } from '@qilin-agent/client-shortcuts/client';
15
+ declare module '@qilin-agent/client-ui-slots' {
16
+ interface SlotMap {
17
+ /** Non-interactive notification inside the collapsed sidebar expand button. */
18
+ 'sidebar.toggle.badge': {
19
+ kind: 'single';
20
+ scope: 'root';
21
+ owner: Record<never, never>;
22
+ };
23
+ /**
24
+ * Brand mark rendered in the expanded brand row and collapsed rail.
25
+ * Declared by this package's `sidebar` entry; deployments may replace
26
+ * the shell's fish fallback without replacing the surrounding controls.
27
+ */
28
+ 'sidebar.brand.mark': {
29
+ kind: 'single';
30
+ scope: 'root';
31
+ owner: SidebarBrandMarkOwnerProps;
32
+ };
33
+ /**
34
+ * Brand name rendered beside the expanded mark. Declared by this
35
+ * package's `sidebar` entry; the shell supplies a generic text fallback.
36
+ */
37
+ 'sidebar.brand.name': {
38
+ kind: 'single';
39
+ scope: 'root';
40
+ owner: SidebarBrandNameOwnerProps;
41
+ };
42
+ /**
43
+ * Global panel icons. Each list id addresses the matching main panel;
44
+ * the sidebar owns the button and resolves its label from list metadata.
45
+ */
46
+ 'sidebar.panellist': {
47
+ kind: 'list';
48
+ scope: 'root';
49
+ owner: SidebarPanelIconOwnerProps;
50
+ };
51
+ /**
52
+ * The dual-workbench switch between the brand row and New Session.
53
+ * Declared by this package's `sidebar` entry; ui-workspace registers the
54
+ * switch. The shell supplies only the column state.
55
+ */
56
+ 'sidebar.workbench': {
57
+ kind: 'single';
58
+ scope: 'root';
59
+ owner: SidebarWorkbenchOwnerProps;
60
+ };
61
+ /**
62
+ * Deployment-side section assignment for panel rows. Each occupant's
63
+ * inject face contributes a row-id → section-label map applied over the
64
+ * panel list before grouping, so a deployment can join rows it does not
65
+ * register itself (engine panels) into sections of its naming. An
66
+ * assignment wins over the row's own `section`. Declared by this
67
+ * package's `sidebar` entry.
68
+ */
69
+ 'sidebar.section.assignments': {
70
+ kind: 'list';
71
+ scope: 'root';
72
+ owner: SidebarSectionAssignmentsOwnerProps;
73
+ };
74
+ /**
75
+ * The workspace/session browsing region: section header, search, the
76
+ * grouped/flat session list, and every workspace dialog. Declared by this
77
+ * package's 'sidebar' entry (declaring is claiming); ui-workspace
78
+ * registers the browser.
79
+ */
80
+ 'sidebar.workspaces': {
81
+ kind: 'single';
82
+ scope: 'root';
83
+ owner: SidebarSectionOwnerProps;
84
+ };
85
+ /**
86
+ * The settings seat at the sidebar foot. Declared by this package's
87
+ * 'sidebar' entry; ui-settings registers its panel and the connection
88
+ * recovery row it reports while closed. The sidebar passes only its
89
+ * column state — it holds no settings state, and the account menu owns
90
+ * the visible Settings entry point.
91
+ */
92
+ 'sidebar.settings': {
93
+ kind: 'single';
94
+ scope: 'root';
95
+ owner: SidebarSettingsOwnerProps;
96
+ };
97
+ /**
98
+ * The sidebar foot's account row, declared by this package's 'sidebar'
99
+ * entry and occupied by the account menu. It stacks below the footer
100
+ * actions and receives only the column state.
101
+ */
102
+ 'sidebar.account': {
103
+ kind: 'single';
104
+ scope: 'root';
105
+ owner: SidebarFooterActionOwnerProps;
106
+ };
107
+ /**
108
+ * Optional additive actions at the sidebar foot, stacked above the account
109
+ * row. Declared by this package's 'sidebar' entry; each action receives
110
+ * only the column state.
111
+ */
112
+ 'sidebar.footer.action': {
113
+ kind: 'list';
114
+ scope: 'root';
115
+ owner: SidebarFooterActionOwnerProps;
116
+ };
117
+ }
118
+ }
119
+ /** Geometry supplied to the sidebar brand-mark occupant. */
120
+ export interface SidebarBrandMarkOwnerProps {
121
+ /** Requested square edge in pixels. */
122
+ size: number;
123
+ }
124
+ /** Empty owner share for the sidebar brand-name occupant. */
125
+ export interface SidebarBrandNameOwnerProps {
126
+ /** Marker field: the occupant owns its own content and width. */
127
+ children?: never;
128
+ }
129
+ /** Icon presentation supplied by the global panel row. */
130
+ export interface SidebarPanelIconOwnerProps {
131
+ /** Requested square edge in pixels. */
132
+ size: number;
133
+ /** Whether this panel is selected in the main column. */
134
+ active: boolean;
135
+ }
136
+ /**
137
+ * Owner share of the workbench-switch seat: the column display state the
138
+ * occupant renders against (wide row vs 56px rail).
139
+ */
140
+ export interface SidebarWorkbenchOwnerProps {
141
+ /** Whether the sidebar renders wide content (false = 56px rail). */
142
+ wide: boolean;
143
+ }
144
+ /** Owner share of a section-assignments seat: row-id → section label. */
145
+ export interface SidebarSectionAssignmentsOwnerProps {
146
+ /** Applied over every panel row; an assignment wins over the row's own section. */
147
+ assignments: Readonly<Record<string, string>>;
148
+ }
149
+ /** Serializable metadata for one active global panel list registration. */
150
+ export interface SidebarPanelMetadata {
151
+ /** List id and matching main panel key. */
152
+ id: MainPanelId;
153
+ /** Ascending row order; ties retain registration order. */
154
+ order: number;
155
+ /** Row title and accessible name: resolved label, or the id when omitted. */
156
+ label: string;
157
+ /**
158
+ * Optional grouping label. Consecutive rows sharing a section render under
159
+ * one header; `undefined` rows render header-less. The sidebar owns the
160
+ * header because it owns the list.
161
+ *
162
+ * Spelled `| undefined` on purpose: this value is copied out of the
163
+ * type-erased registration options (which admit undefined under
164
+ * exactOptionalPropertyTypes), so the projection must accept it.
165
+ */
166
+ section?: string | undefined;
167
+ }
168
+ /**
169
+ * Owner share of the browser hole — the only facts crossing the shell/region
170
+ * boundary. Business data and actions arrive through the region's own inject.
171
+ */
172
+ export interface SidebarSectionOwnerProps {
173
+ /** Shell fold-state output: wide renders the full browser, rail the icon column. */
174
+ wide: boolean;
175
+ /** Rail icons request expansion; the browser rides the wide flip for focus. */
176
+ expandSidebar: () => void;
177
+ }
178
+ /**
179
+ * Owner share of the sidebar settings seat: the column display state the
180
+ * occupant renders against (wide row vs 56px rail).
181
+ */
182
+ export interface SidebarSettingsOwnerProps {
183
+ /** Whether the sidebar renders wide content (false = 56px rail). */
184
+ wide: boolean;
185
+ }
186
+ /** Owner share of an action rendered at the sidebar foot. */
187
+ export interface SidebarFooterActionOwnerProps {
188
+ /** Whether the sidebar renders wide content (false = 56px rail). */
189
+ wide: boolean;
190
+ }
191
+ /**
192
+ * Registrant-private injected share (arrives via the register inject
193
+ * factory). The renderer binds the panel metadata source to usePanels.
194
+ */
195
+ export type SidebarRootInjected = {
196
+ /**
197
+ * Start a New Session: with a workspace, reuse-or-create its blank session
198
+ * and open it; without one, inherit the current Session Workspace, then the
199
+ * recent Workspace, or clear into the New Session pure view when none exist.
200
+ */
201
+ startSession: (workspaceId?: WorkspaceId) => void;
202
+ /** Toggle the sidebar column through the layout service. */
203
+ toggleSidebar: () => void;
204
+ /** Select the global panel addressed by a sidebar row. */
205
+ selectPanel: (id: MainPanelId) => void;
206
+ /** Private reactive sources bound to framework selector hooks. */
207
+ hooks: {
208
+ panels: ObservableSnapshot<readonly SidebarPanelMetadata[]>;
209
+ shortcuts: ObservableSnapshot<readonly ShortcutCatalogEntry[]>;
210
+ };
211
+ };
212
+ /**
213
+ * Full component props: layout owner state/actions plus the declared holes'
214
+ * render shares, this package's injected callbacks, and the standard locale
215
+ * seat. Panel metadata arrives through an injected observable.
216
+ */
217
+ export type SidebarRootComponentProps = PropsRuntime<'sidebar'> & PropsRenderSlots<'sidebar.brand.mark' | 'sidebar.brand.name' | 'sidebar.toggle.badge' | 'sidebar.workbench' | 'sidebar.panellist' | 'sidebar.workspaces' | 'sidebar.settings' | 'sidebar.account' | 'sidebar.footer.action'> & InjectFace<SidebarRootInjected> & PropsLocale<'sidebar'>;
218
+ //# sourceMappingURL=slots.d.ts.map
@@ -0,0 +1,18 @@
1
+ /** Registers the sidebar shell and global panel navigation. */
2
+ import type { Context as ClientContext } from '@qilin-agent/kylin';
3
+ import { type SidebarKey } from './locales.ts';
4
+ export type { SidebarBrandMarkOwnerProps, SidebarBrandNameOwnerProps, SidebarFooterActionOwnerProps, SidebarPanelIconOwnerProps, SidebarPanelMetadata, SidebarRootComponentProps, SidebarRootInjected, SidebarSectionAssignmentsOwnerProps, SidebarSectionOwnerProps, SidebarSettingsOwnerProps, } from './contract/slots.ts';
5
+ export type { SidebarKey } from './locales.ts';
6
+ declare module '@qilin-agent/client-ui-slots' {
7
+ interface LocaleNamespaceMap {
8
+ /** Sidebar controls and global panel copy. */
9
+ sidebar: SidebarKey;
10
+ }
11
+ }
12
+ /** Services required by the sidebar plugin. */
13
+ export declare const inject: string[];
14
+ /** Registers the sidebar shell and its service callbacks.
15
+ * @param ctx - Client root context.
16
+ */
17
+ export declare function apply(ctx: ClientContext): void;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,22 @@
1
+ /** `sidebar` namespace dictionaries for shell controls and global panels. */
2
+ /** Simplified Chinese dictionary (the key-set source of truth). */
3
+ export declare const zh: {
4
+ 'shortcut.hint': string;
5
+ 'session.new': string;
6
+ 'session.new.label': string;
7
+ 'toggle.open': string;
8
+ 'toggle.collapse': string;
9
+ 'panels.label': string;
10
+ };
11
+ /** The sidebar namespace key union. */
12
+ export type SidebarKey = keyof typeof zh;
13
+ /** English dictionary, checked complete against the zh key set. */
14
+ export declare const en: {
15
+ 'shortcut.hint': string;
16
+ 'session.new': string;
17
+ 'session.new.label': string;
18
+ 'toggle.open': string;
19
+ 'toggle.collapse': string;
20
+ 'panels.label': string;
21
+ };
22
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Host loader entry for the browser-only sidebar plugin. */
2
+ /** Provides no host-side behavior. */
3
+ export declare function apply(): void;
4
+ //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@qilin-agent/client-ui-sidebar",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Sidebar plugin: session multi-level tree, search, grouping, state dots",
4
+ "version": "3.1.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/client/ui-sidebar"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./client": {
25
+ "types": "./lib/types/client/index.d.ts",
26
+ "default": "./lib/client.js"
27
+ },
28
+ "./src/*": "./src/*",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "qilin": {
32
+ "client": {
33
+ "inject": [
34
+ "@qilin-agent/api-workspace-controller",
35
+ "@qilin-agent/client-locale",
36
+ "@qilin-agent/client-shortcuts",
37
+ "@qilin-agent/client-ui-conversation",
38
+ "@qilin-agent/client-ui-layout",
39
+ "@qilin-agent/client-ui-renderer",
40
+ "@qilin-agent/client-ui-session",
41
+ "@qilin-agent/client-ui-workspace"
42
+ ],
43
+ "platform": "web"
44
+ }
45
+ },
46
+ "license": "MIT",
47
+ "peerDependencies": {
48
+ "@qilin-agent/kylin": "~4.0.3"
49
+ },
50
+ "devDependencies": {
51
+ "@types/react": "~18.3.1",
52
+ "clsx": "^2.0.0",
53
+ "react": "^18.2.0",
54
+ "@qilin-agent/api-workspace-controller": "3.1.3",
55
+ "@qilin-agent/client-locale": "3.1.3",
56
+ "@qilin-agent/client-shortcuts": "3.1.3",
57
+ "@qilin-agent/client-store": "3.1.3",
58
+ "@qilin-agent/client-test-runtime": "3.1.3",
59
+ "@qilin-agent/client-ui-conversation": "3.1.3",
60
+ "@qilin-agent/client-ui-layout": "3.1.3",
61
+ "@qilin-agent/client-ui-primitives": "3.1.3",
62
+ "@qilin-agent/client-ui-renderer": "3.1.3",
63
+ "@qilin-agent/client-ui-session": "3.1.3",
64
+ "@qilin-agent/client-ui-slots": "3.1.3",
65
+ "@qilin-agent/client-ui-workspace": "3.1.3",
66
+ "@qilin-agent/kylin": "~4.0.3"
67
+ },
68
+ "files": [
69
+ "lib/index.js",
70
+ "lib/client.js",
71
+ "lib/types/**/*.d.ts"
72
+ ],
73
+ "scripts": {
74
+ "bundle": "tsdown",
75
+ "watch": "tsdown --watch"
76
+ }
6
77
  }