@deepseek-ai/dsh-client-ui-sidebar-right 0.1.5-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +133 -0
- package/README.zh.md +133 -0
- package/lib/client.js +3614 -0
- package/lib/index.js +6 -0
- package/lib/types/client/contract/params.d.ts +47 -0
- package/lib/types/client/contract/seed.d.ts +35 -0
- package/lib/types/client/contract/slots.d.ts +152 -0
- package/lib/types/client/index.d.ts +56 -0
- package/lib/types/client/labels.d.ts +19 -0
- package/lib/types/client/locales.d.ts +48 -0
- package/lib/types/client/service.d.ts +300 -0
- package/lib/types/client/shell/ExpandButton.d.ts +24 -0
- package/lib/types/client/shell/SidebarRight.d.ts +99 -0
- package/lib/types/client/stores.d.ts +100 -0
- package/lib/types/client/tab-domain.d.ts +95 -0
- package/lib/types/client/tab-info.d.ts +29 -0
- package/lib/types/client/tab-registry.d.ts +205 -0
- package/lib/types/client/tabs/guide/GuideBody.d.ts +30 -0
- package/lib/types/client/tabs/guide/definition.d.ts +18 -0
- package/lib/types/index.d.ts +4 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
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.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-sidebar-right/README.md
|
|
5
|
+
README.md: feb75c6184098f54961ca161b4dc5ca5d0c7fc78
|
|
6
|
+
README.zh.md: 03c841dfdaac970e938cf934e7f09d151268be8b
|
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "The right Sidebar of the dsh web client: one docking surface per session, two presentations, the navigation controller ctx.sidebarRight, the tab-type registry ctx.sidebarRightTabs, and the Tab domain."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-client-ui-sidebar-right
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
The right Sidebar: where the docking kit meets this product. It holds one docking surface per session, draws it as one edge-anchored panel in the frame's right column in either of two presentations, puts the expand button in the conversation header, and owns the navigation controller (`ctx.sidebarRight`), the tab-type registry (`ctx.sidebarRightTabs`), and the Tab domain that tells each open tab how it was navigated to and how long it lives.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [What lives here, and what does not](#what-lives-here-and-what-does-not)
|
|
17
|
+
- [Presentations](#presentations)
|
|
18
|
+
- [The expand button](#the-expand-button)
|
|
19
|
+
- [State](#state)
|
|
20
|
+
- [Extension seats](#extension-seats)
|
|
21
|
+
- [`ctx.sidebarRight`](#ctxsidebarright)
|
|
22
|
+
- [The Tab domain](#the-tab-domain)
|
|
23
|
+
- [The guide](#the-guide)
|
|
24
|
+
- [Copy](#copy)
|
|
25
|
+
- [Model Experience](#model-experience)
|
|
26
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
27
|
+
- [Dev Note](#dev-note)
|
|
28
|
+
|
|
29
|
+
-----
|
|
30
|
+
|
|
31
|
+
<a id="what-lives-here-and-what-does-not"></a>
|
|
32
|
+
## What lives here, and what does not
|
|
33
|
+
|
|
34
|
+
The layout itself — the split tree, its operations, the drag gestures, the floating panels — belongs to `@deepseek-ai/dsh-client-ui-dockkit` and stays host-agnostic. This package supplies everything that kit refuses to know: the product's copy, what a tab's `kind` means, which tab a fresh pane is seeded with, where the surface is mounted, and how other plugins reach it.
|
|
35
|
+
|
|
36
|
+
<a id="presentations"></a>
|
|
37
|
+
## Presentations
|
|
38
|
+
|
|
39
|
+
Normal and fullscreen presentations share the same content tree, so switching does not remount tabs. The normal panel anchors to the right column; fullscreen covers the viewport while retaining the wide-screen columns underneath. Opening below 768px uses fullscreen automatically; leaving fullscreen on a narrow viewport closes the panel, and widening does not reopen a closed panel. A fullscreen opening keeps the underlying columns unchanged until its slide finishes, then prepares the normal track without a column transition. Before a fullscreen panel retreats, closing prepares a full-width conversation and restoring prepares the normal right track; the background does not animate during the retreat.
|
|
40
|
+
|
|
41
|
+
| Mode | The track | The panel |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `push` (default) | Panel width: the conversation makes room | In the track; its left edge and the conversation's right edge travel together, on the frame's own curve |
|
|
44
|
+
| `fullscreen` | Retains the wide-screen normal track; automatic narrow-screen fullscreen takes no track | Covers the entire viewport |
|
|
45
|
+
|
|
46
|
+
The seat reports presentation through `ctx.layout.openRightbar(track, fullscreen)` / `closeRightbar()`; the frame does not inject this package. Switching fullscreen on a wide viewport leaves the center width unchanged, and the width handle appears only in expanded normal mode. Independent floating panels and `float`/`dock` operations remain available.
|
|
47
|
+
|
|
48
|
+
The panel has no header row. Its two controls — the presentation switch and the collapse button — ride the kit's chrome seat at the far end of the top-right pane's tab strip, so the strip is the panel's whole top edge. Each strip reads, left to right: the tabs as capsules with their own close, the add control (drawn only while that pane holds no guide tab; it opens the guide there through `ctx.sidebarRight.openTab`), the pane's split control, and in the top-right pane the two panel controls. Only the chips give way in a narrow pane; the controls after them never shrink or clip.
|
|
49
|
+
|
|
50
|
+
<a id="the-expand-button"></a>
|
|
51
|
+
## The expand button
|
|
52
|
+
|
|
53
|
+
While the panel is hidden, one button in the conversation header's corner seat (`conversation.session.header.corner`, past the utilities' right edge and level with the Session log control) is the way back in. Its glyph is the left sidebar's collapse icon mirrored. It shares the panel's store (the slot runtime allows one handle across two same-scope seats); while the panel is shown it renders a same-size placeholder, so the corner keeps its width and nothing in the header row moves. A collapsed Sidebar therefore costs the conversation nothing: no rail, no width, and the transcript's scrollbar stays at the column's edge. Without a session there is no button and no panel.
|
|
54
|
+
|
|
55
|
+
The panel takes the conversation's ground colour and content font sizes rather than a raised layer of its own: it is a column of the page, not a card over it.
|
|
56
|
+
|
|
57
|
+
<a id="state"></a>
|
|
58
|
+
## State
|
|
59
|
+
|
|
60
|
+
One `SurfaceState` per session id — the layout, its recorded sequence, and how many ids it has minted — held in a store declared at the registration. Every action follows the same shape: mint the ids the intent needs, ask a kit planner which operations carry it out, record them, then assign the session's whole surface back. No action edits a layout in place, which is what keeps the kit's pure functions the only thing that computes one.
|
|
61
|
+
|
|
62
|
+
Carrying the mint counter in the surface is what makes a recorded sequence replayable: operations embed the ids they create, so replaying from the same initial state reproduces the same tree. Every action records one history entry, however many operations it needed. Expanding, collapsing, and switching presentation are recorded too.
|
|
63
|
+
|
|
64
|
+
After every action the kit's settle planner keeps the surface populated: a docked pane whose last tab was closed, moved out, or floated is merged away, and when only the root pane is left and it is empty, the guide tab is reseeded. There is always at least one tab, and never an empty pane — so there is no separate "close pane" gesture.
|
|
65
|
+
|
|
66
|
+
State is memory-only. A reload returns every session to the collapsed default; switching sessions keeps each surface where it was.
|
|
67
|
+
|
|
68
|
+
<a id="extension-seats"></a>
|
|
69
|
+
## Extension seats
|
|
70
|
+
|
|
71
|
+
A tab type registers in two stages, and the shipped guide type goes through exactly the same public path a type from another package does (`ui-sidebar-textpreview` is the live proof). Both stages sit inside the type's own `ctx.effect`, so the registration lives exactly as long as the plugin that made it.
|
|
72
|
+
|
|
73
|
+
1. **The type** — `ctx.sidebarRightTabs.register({ id, kind, patterns?, priority?, canOpen?, title, guide? })`, a static declaration with no runtime hook, returning a disposer. `id` is this implementation's identity in the tab system, unique across every registration (a package name is the natural value; the shipped guide is `@deepseek-ai/dsh-client-ui-sidebar-right/guide`): a kind is not unique once an extension may take a builtin's over, so the implementation names itself, and a second registration of an `id` throws. A resource type names `patterns`, globs over `dsh-resource://` addresses: one containing `:` matches the whole address (`dsh-resource://file/**`); one without matches the URI's path at any depth, ignoring case (`*.md`), and an address that is not a URI matches no such pattern. A page type — the guide, a file tree — names none and is opened by kind. `canOpen(address)` vetoes a match. `title(address)` is the tab chip's text, captured when the tab opens. `guide` lists entry boxes for the guide page; picking one opens the contributing type as a page. A `kind` carries at most one `builtin` and one `extension` registration (the extension is in force; the builtin resumes when it leaves); any other collision on a kind throws. The `id` is also the key the type's body and title register under, so an extension and the builtin it takes over hold distinct cells and the seat renders the one in force.
|
|
74
|
+
2. **The body** — `ctx.slots.register({ name: 'sidebar.right.pane.tab', key: definition.id }, Body)` reads `{ sidebar, panel, tab }` through the framework-injected `useTabInfo()`. `sidebar` supplies expansion and fullscreen information; `panel.id` identifies its pane; `tab` contains the record fields, `visible`, `navigation`, `signal`, and `actions`. These are not parallel owner props; the type's own store still uses `useStore`/`actions`. Optional title registrations and guide replacements share this hook; an absent title registration uses the text captured at open time.
|
|
75
|
+
|
|
76
|
+
Which type opens a resource follows the editor-resolver convention: the types whose `patterns` match are ranked by `priority` band — `extension` (a type from outside the product, the highest, and the default when none is named), `builtin`, `fallback` (plain viewers anything more specific should beat) — then by the length of the matched pattern, then by registration order; `canOpen` removes a candidate. The bands are string literals so a type in another package needs no runtime import from here. `candidates(address)` returns the ranking, `claim(address, kind?)` the decision; naming a `kind` skips its globs but keeps its `canOpen`.
|
|
77
|
+
|
|
78
|
+
Two more seats extend what is already there: `sidebar.right.tab.guide` (chain) replaces the guide tab's body without replacing the tab, and `sidebar.right.tab.menu.item` (list) appends content-level actions to a tab's menu after the kit's own layout actions. No seat exists for pane-level actions or for collapsed-state controls yet, because nothing needs one.
|
|
79
|
+
|
|
80
|
+
<a id="ctxsidebarright"></a>
|
|
81
|
+
## `ctx.sidebarRight`
|
|
82
|
+
|
|
83
|
+
`openResource(address, options?)` and `openTab(kind, options?)` are the navigation controller, and every way into the column calls one of them: the conversation's file links and a tool row's line reference (`openResource(fileAddress, { params: { line } })`), the strip's add control and a guide entry box (`openTab`), a file tree's rows (`tab.actions.openResource`). A resource address is a `dsh-resource://<type>/…` URI; without `options.kind` the registry claims it (globs and `canOpen`, best band wins), with it that kind's type in force opens it. A page is named by kind; the tab is recorded under an address this package composes and nobody else spells (`contract/seed.ts`). Both run the same steps as one history entry: a tab already showing the same (kind, contentId) is focused unless `revealIfOpened: false`; otherwise a new tab lands in `options.replaceTab`'s pane and slot (closing that tab), else `options.paneId`, else the active docked pane; the panel expands, because content the user cannot see is not opened. Then the Tab domain records the navigation — `params` reach the body as `navigation.params`, with `revision` stepped — outside the layout history. `params` is typed by what is opened: a viewer for a resource type merges its entry into `SidebarRightResourceParamsMap` (the text preview declares `{ line?: number }`); a page type that takes parameters merges into `SidebarRightTabParamsMap` under its kind; values are JSON-shaped by convention, unchecked at run time. An address outside `dsh-resource://`, one no type claims, or a kind nothing registered throws: that is a wiring mistake, not a user error.
|
|
84
|
+
|
|
85
|
+
`close(tabId)` closes a tab; `active()` reads the active tab. `isExpanded()` and `toggleExpanded()` read and drive the column's expansion; the presentation switch is the panel's own control and not part of this face. Layout operations, for callers that arrange the column programmatically, each recorded like the gesture it stands in for: `focus(tabId)` focuses a tab and its pane; `split(paneId?)` splits a docked pane (the active one by default) under the same pane budget and room rule as the strip's control and returns the new pane's id, or `undefined` — recording nothing — when it cannot; `float(tabId, rect?)` takes a docked tab out into a panel; `dock(paneId)` returns a floating panel to the active docked pane. A tab or pane that does not exist, or already is where the call would put it, is left alone. The face exposes operations only: no layout snapshot, no operation log, no lookup by address. `_undo()` / `_redo()` step the mounted surface's history; they are `@internal` — the sequence has no user-facing control, and these exist for tests. Commands need a mounted session surface; with none, they throw rather than write into a surface nobody draws.
|
|
86
|
+
|
|
87
|
+
<a id="the-tab-domain"></a>
|
|
88
|
+
## The Tab domain
|
|
89
|
+
|
|
90
|
+
The Tab domain retains navigation, an abort signal, and bound actions per (Session, tab id). A private assembly callback adopts each Session's store and reconciles records on its commits. Only record removal or plugin unload aborts the signal; closing the sidebar and switching Sessions retain records, while undo restores a new occurrence. `useTabInfo()` composes framework-bound store and navigation hooks without manual component subscriptions or render-time record creation. `tab.actions` always target their own Session; `tab.visible` distinguishes bodies from titles, and floating tabs remain visible when the sidebar closes. `adopt` is absent from the public controller.
|
|
91
|
+
|
|
92
|
+
<a id="the-guide"></a>
|
|
93
|
+
## The guide
|
|
94
|
+
|
|
95
|
+
The guide tab is a centred title, one line under it, and one entry box per `guide` entry the registered types contributed, in `order`. Picking a box calls `tab.actions.openTab(entry.kind, { replaceTab: true })`, so the guide gives way to the page it opened. A pane holds at most one guide tab. The strip's add control is drawn only while its pane holds none and opens one there with `openTab('guide', { paneId, revealIfOpened: false })`, so a guide in another pane does not capture the click; opening the guide into a pane that already has one focuses it instead; a guide dragged, dropped, or docked into such a pane merges into it — the arriving guide closes and the pane's own is focused; `duplicateTab` on the guide records nothing. A split or an emptied root pane seeds a guide through the kit's factory, one per new pane. A plain `openTab('guide')` keeps the tree-wide reveal every open has. The product allows two horizontal panes, initially equal, with divider ratios limited to 20%–80%. Insufficient width blocks a new split; with two panes already present, a body drop moves the tab between panes instead of creating a third. At the two-pane limit, split controls are hidden; closing back to one pane restores them.
|
|
96
|
+
|
|
97
|
+
<a id="copy"></a>
|
|
98
|
+
## Copy
|
|
99
|
+
|
|
100
|
+
Every string in the column comes from the `sidebarRight` locale namespace, including the kit's accessible names. A tab's title is fixed when the tab is minted; a type's display name follows the current language.
|
|
101
|
+
|
|
102
|
+
<a id="model-experience"></a>
|
|
103
|
+
## Model Experience
|
|
104
|
+
|
|
105
|
+
None, as the package is a browser-side UI plugin layer that registers nothing model-facing.
|
|
106
|
+
|
|
107
|
+
#### KV Cache effect
|
|
108
|
+
|
|
109
|
+
None; this package neither assembles nor sends a provider request.
|
|
110
|
+
|
|
111
|
+
## Known Limitations and Deferred Work
|
|
112
|
+
|
|
113
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
114
|
+
|
|
115
|
+
- **Memory-only.** Nothing is persisted; a reload starts every session collapsed.
|
|
116
|
+
- **No surface without a session.** State is keyed by session id, so the hero screen shows nothing on the right.
|
|
117
|
+
- **Hard-coded stacking.** The panel and the float host use fixed z-index values because the client has no z-index token layer yet.
|
|
118
|
+
- **Undo is not exposed.** The recorded sequence is stepped only through the `@internal` service methods; product controls are deliberately absent.
|
|
119
|
+
- **Guide copy is a draft** awaiting product review; the words live in `locales.ts`.
|
|
120
|
+
- **Titles are fixed at open time.** A type's `title(address)` is captured into the record; a live title comes only from the optional title seat.
|
|
121
|
+
- **No content navigation stack.** Stepping back replays layout operations; an editor-style back/forward over visited content is not built.
|
|
122
|
+
|
|
123
|
+
<a id="dev-note"></a>
|
|
124
|
+
### Dev Note
|
|
125
|
+
|
|
126
|
+
<details>
|
|
127
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
128
|
+
|
|
129
|
+
None.
|
|
130
|
+
|
|
131
|
+
</details>
|
|
132
|
+
|
|
133
|
+
**Runtime invariant:** No companion is published. The two services (`sidebarRight`, `sidebarRightTabs`) are provided through `ctx.reflect.provide` inside one effect and torn down with it; the seat's binding and the Tab domain's occurrence lifetimes are asserted directly by this package's specs, and no independent observation exists to diverge from them.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "dsh Web 客户端的右侧 Sidebar:每会话一个停靠面、两种呈现形态、导航控制器 ctx.sidebarRight、tab 类型注册表 ctx.sidebarRightTabs 与 Tab 域。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-client-ui-sidebar-right
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
右侧 Sidebar:停靠套件与本产品相遇的地方。它为每个会话持有一个停靠面,以两种呈现形态之一把它画成贴靠框架右列边缘的一块面板,把展开按钮放进会话 header,并拥有导航控制器(`ctx.sidebarRight`)、tab 类型注册表(`ctx.sidebarRightTabs`),以及告诉每个已开 tab 它是如何被导航到、能活多久的 Tab 域。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [什么住在这里,什么不住](#what-lives-here-and-what-does-not)
|
|
17
|
+
- [呈现形态](#presentations)
|
|
18
|
+
- [展开按钮](#the-expand-button)
|
|
19
|
+
- [状态](#state)
|
|
20
|
+
- [扩展席位](#extension-seats)
|
|
21
|
+
- [`ctx.sidebarRight`](#ctxsidebarright)
|
|
22
|
+
- [Tab 域](#the-tab-domain)
|
|
23
|
+
- [引导页](#the-guide)
|
|
24
|
+
- [文案](#copy)
|
|
25
|
+
- [模型体验](#model-experience)
|
|
26
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
27
|
+
- [开发备注](#dev-note)
|
|
28
|
+
|
|
29
|
+
-----
|
|
30
|
+
|
|
31
|
+
<a id="what-lives-here-and-what-does-not"></a>
|
|
32
|
+
## 什么住在这里,什么不住
|
|
33
|
+
|
|
34
|
+
布局本身——分裂树、它的操作、拖拽手势、浮窗——属于 `@deepseek-ai/dsh-client-ui-dockkit`,并保持与宿主无关。本包提供套件拒绝知道的一切:产品文案、tab 的 `kind` 是什么意思、新格用哪个 tab 播种、停靠面挂在哪里、其它插件如何触达它。
|
|
35
|
+
|
|
36
|
+
<a id="presentations"></a>
|
|
37
|
+
## 呈现形态
|
|
38
|
+
|
|
39
|
+
普通与全屏共用同一棵面板内容树,切换不会重挂载Tab。普通面板贴靠右栏;全屏面板覆盖窗口并保留宽屏底层列宽。窗口低于768px时打开右栏自动全屏;窄屏退出全屏会收起右栏,变宽不重新打开已关闭的右栏。 全屏打开时,底层列宽保持不变,直到滑入结束后才无过渡地准备普通轨道。 全屏面板退场前,关闭先准备全宽会话区,恢复先准备普通右轨道;退场期间底层不播放宽度动画。
|
|
40
|
+
|
|
41
|
+
| 形态 | 轨道 | 面板 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `push`(默认) | 面板宽度:会话区让出空间 | 在轨道内;它的左缘与会话区的右缘沿框架自己的曲线一起移动 |
|
|
44
|
+
| `fullscreen` | 保留宽屏普通轨道;窄屏自动全屏不占轨道 | 覆盖整个窗口 |
|
|
45
|
+
|
|
46
|
+
席位通过 `ctx.layout.openRightbar(track, fullscreen)` / `closeRightbar()` 报告呈现,框架不注入本包。宽屏切换全屏不改变中栏宽度;宽度拖拽区只在普通展开态显示。独立浮窗及 `float`/`dock` 操作保持可用。
|
|
47
|
+
|
|
48
|
+
面板没有标题行。它的两个控件——形态切换与折叠按钮——搭在套件 chrome 席位上,位于右上格 tab 条的最末端,因此 tab 条就是面板的整条上边。每条 tab 条从左到右读作:作为胶囊、各带关闭按钮的 tab,添加控件(只在该格没有引导 tab 时绘制;它通过 `ctx.sidebarRight.openTab` 在该格打开引导页),该格的分栏控件,以及右上格里的两个面板控件。窄格里只有 chip 让位;其后的控件从不收缩或被裁切。
|
|
49
|
+
|
|
50
|
+
<a id="the-expand-button"></a>
|
|
51
|
+
## 展开按钮
|
|
52
|
+
|
|
53
|
+
面板隐藏时,会话 header 角落席位里的一个按钮(`conversation.session.header.corner`,在工具组右缘之外,与 Session 日志控件齐平)是回去的路。它的图形是左侧 sidebar 折叠图标的镜像。它与面板共用一个存储(slot 运行时允许两个同作用域席位共用一个 handle);面板显示时它渲染一个同尺寸的占位,因此角落保持宽度,header 行里没有东西会移动。于是折叠的 Sidebar 不花会话区任何代价:没有轨条、没有宽度,转录的滚动条留在列的边缘。没有会话就没有按钮也没有面板。
|
|
54
|
+
|
|
55
|
+
面板取会话区的底色与正文字号,而不是自成一层浮起的表面:它是页面的一列,不是压在页面上的卡片。
|
|
56
|
+
|
|
57
|
+
<a id="state"></a>
|
|
58
|
+
## 状态
|
|
59
|
+
|
|
60
|
+
每个会话 id 一个 `SurfaceState`——布局、它记录的序列、以及它已铸造的 id 数——保存在注册时声明的存储里。每个动作都遵循同一形态:铸造意图需要的 id,向套件 planner 询问由哪些操作承载,记录它们,然后把该会话的整个停靠面赋回去。没有任何动作就地编辑布局,这正是让套件的纯函数成为唯一计算布局之处的原因。
|
|
61
|
+
|
|
62
|
+
把铸造计数器带在停靠面里,是记录的序列可回放的原因:操作内嵌它们创建的 id,因此从同一初始状态回放能复现同一棵树。每个动作记录一条历史,无论它需要多少操作。展开、折叠与切换形态也都被记录。
|
|
63
|
+
|
|
64
|
+
每个动作之后,套件的 settle planner 保证停靠面有内容:最后一个 tab 被关闭、搬走或浮出的停靠格会被并掉;只剩根格且它为空时,重新播种引导 tab。永远至少有一个 tab,永远没有空格——因此没有单独的「关闭格」手势。
|
|
65
|
+
|
|
66
|
+
状态只在内存中。刷新会让每个会话回到折叠的默认态;切换会话则让每个停靠面留在原处。
|
|
67
|
+
|
|
68
|
+
<a id="extension-seats"></a>
|
|
69
|
+
## 扩展席位
|
|
70
|
+
|
|
71
|
+
tab 类型分两阶段注册,随包发布的引导类型走的正是别的包的类型走的同一条公开路径(`ui-sidebar-textpreview` 是活的证明)。两个阶段都在类型自己的 `ctx.effect` 里,因此注册与创建它的插件同生共死。
|
|
72
|
+
|
|
73
|
+
1. **类型**——`ctx.sidebarRightTabs.register({ id, kind, patterns?, priority?, canOpen?, title, guide? })`,一份没有运行时钩子的静态声明,返回 disposer。`id` 是这个实现在 tab 系统里的身份,在全部注册中唯一(包名是天然取值;随包引导页是 `@deepseek-ai/dsh-client-ui-sidebar-right/guide`):一旦 extension 可以接管 builtin 的 kind,kind 就不再唯一,所以实现要自己命名,同一 `id` 的第二次注册会 throw。资源类型给出 `patterns`,即作用于 `dsh-resource://` 地址的 glob:含 `:` 的匹配整个地址(`dsh-resource://file/**`);不含的匹配 URI 路径的任意深度且忽略大小写(`*.md`),不是 URI 的地址不匹配任何这类模式。页类型——引导页、文件树——不给出模式,按 kind 打开。`canOpen(address)` 否决一次命中。`title(address)` 是 tab chip 的文字,在 tab 打开时捕获。`guide` 列出引导页的入口框;选中一个即把贡献它的类型作为页打开。一个 `kind` 最多承载一份 `builtin` 与一份 `extension` 注册(extension 生效;它离开后 builtin 恢复);kind 上的其它任何撞名都 throw。`id` 同时也是该类型正文与标题注册时用的 key,因此 extension 与它接管的 builtin 各占一个格位,席位渲染生效的那个。
|
|
74
|
+
2. **正文**——`ctx.slots.register({ name: 'sidebar.right.pane.tab', key: definition.id }, Body)` 通过框架注入的 `useTabInfo()` 读取 `{ sidebar, panel, tab }`。`sidebar` 提供开合与全屏信息,`panel.id` 命名所在格,`tab` 包含原记录字段、`visible`、`navigation`、`signal` 和 `actions`。这些字段不再作为平铺owner props传入;类型自己的store仍使用 `useStore`/`actions`。可选标题注册及引导替换共享该hook;未注册标题时使用打开时保存的文本。
|
|
75
|
+
|
|
76
|
+
由哪个类型打开资源遵循编辑器解析器的惯例:`patterns` 命中的类型先按 `priority` 档排序——`extension`(产品外的类型,最高档,也是未命名时的默认)、`builtin`、`fallback`(任何更具体的类型都应胜过的通用查看器)——再按命中模式的长度,再按注册顺序;`canOpen` 会剔除候选。各档是字符串字面量,因此别的包里的类型不需要从这里做运行时导入。`candidates(address)` 返回排序,`claim(address, kind?)` 返回决定;指定 `kind` 时跳过它的 glob 但保留它的 `canOpen`。
|
|
77
|
+
|
|
78
|
+
另有两个席位扩展已有之物:`sidebar.right.tab.guide`(chain)替换引导 tab 的正文而不替换 tab,`sidebar.right.tab.menu.item`(list)在套件自己的布局动作之后向 tab 菜单追加内容级动作。目前没有面向格级动作或折叠态控件的席位,因为还没有东西需要它。
|
|
79
|
+
|
|
80
|
+
<a id="ctxsidebarright"></a>
|
|
81
|
+
## `ctx.sidebarRight`
|
|
82
|
+
|
|
83
|
+
`openResource(address, options?)` 与 `openTab(kind, options?)` 是导航控制器,进入该列的每条路都调用其中之一:会话区的文件链接与工具行的行号引用(`openResource(fileAddress, { params: { line } })`),tab 条的添加控件与引导入口框(`openTab`),文件树的行(`tab.actions.openResource`)。资源地址是 `dsh-resource://<type>/…` URI;不带 `options.kind` 时由注册表认领(glob 与 `canOpen`,最高档胜出),带它时由该 kind 生效的类型打开。页按 kind 命名;tab 记录在本包拼出、别处无人书写的地址下(`contract/seed.ts`)。两者以同一组步骤作为一条历史运行:已展示同一 (kind, contentId) 的 tab 被聚焦,除非 `revealIfOpened: false`;否则新 tab 落到 `options.replaceTab` 所在的格与位置(并关掉那个 tab),再退而落到 `options.paneId`,再退而落到活跃停靠格;面板展开,因为用户看不到的内容不算打开。随后 Tab 域记录这次导航——`params` 以 `navigation.params` 抵达正文,`revision` 递增——不进布局历史。`params` 按所开之物定型:某资源类型的查看器把自己那项并入 `SidebarRightResourceParamsMap`(文本预览声明 `{ line?: number }`);接受参数的页类型按其 kind 并入 `SidebarRightTabParamsMap`;值约定为 JSON 形状,运行时不校验。`dsh-resource://` 之外的地址、无人认领的地址、或未注册的 kind 都会 throw:那是接线错误,不是用户错误。
|
|
84
|
+
|
|
85
|
+
`close(tabId)` 关闭一个 tab;`active()` 读取活动 tab。`isExpanded()` 与 `toggleExpanded()` 读取并驱动该列的展开;形态切换是面板自己的控件,不属于这个接口。布局操作供以编程方式安排该列的调用方使用,每个都像它替代的手势一样被记录:`focus(tabId)` 聚焦一个 tab 及其格;`split(paneId?)` 在与 tab 条控件相同的格预算与空间规则下分栏一个停靠格(默认活跃格),返回新格的 id,做不到时返回 `undefined`——且不记录任何东西;`float(tabId, rect?)` 把停靠 tab 浮出为浮窗;`dock(paneId)` 把浮窗放回活跃停靠格。不存在的 tab 或格、或已处于调用目标状态的,都原样不动。该接口只暴露操作:没有布局快照、没有操作日志、没有按地址查找。`_undo()` / `_redo()` 步进已挂载停靠面的历史;它们是 `@internal`——序列没有面向用户的控件,这两个只为测试存在。命令需要一个已挂载的会话停靠面;没有时它们 throw,而不是写进一个没人绘制的面里。
|
|
86
|
+
|
|
87
|
+
<a id="the-tab-domain"></a>
|
|
88
|
+
## Tab 域
|
|
89
|
+
|
|
90
|
+
Tab域按(Session,Tab id)保留导航、中止信号与绑定动作;私有装配回调收养各会话的store,并在每次提交时对齐记录。记录消失或插件卸载才中止signal,收起和切会话不销毁记录;undo恢复的是新occurrence。`useTabInfo()` 组合框架绑定的store与导航hook,不在组件中手写订阅或在渲染时创建记录。`tab.actions` 始终作用于自己的会话;`tab.visible` 区分正文与标题,浮窗不受整栏收起影响。`adopt` 不在公开控制器上。
|
|
91
|
+
|
|
92
|
+
<a id="the-guide"></a>
|
|
93
|
+
## 引导页
|
|
94
|
+
|
|
95
|
+
引导 tab 是一个居中标题、其下一行说明,以及各已注册类型贡献的每个 `guide` 条目一个入口框,按 `order` 排列。选中一个框会调用 `tab.actions.openTab(entry.kind, { replaceTab: true })`,于是引导页让位给它打开的页。一个格最多持有一个引导 tab。tab 条的添加控件只在该格没有引导 tab 时绘制,并以 `openTab('guide', { paneId, revealIfOpened: false })` 在该格打开一个,这样别的格里的引导页不会截走这次点击;把引导页开进已有引导页的格则改为聚焦它;把引导页拖入、放入或收回到这样的格会合并进去——来者关闭,该格自己的被聚焦;对引导页 `duplicateTab` 不记录任何东西。分栏或被清空的根格通过套件的工厂播种一个引导页,每个新格一个。普通的 `openTab('guide')` 保留每次打开都有的整树聚焦。产品最多保留左右两格,默认均分,分隔条限定20%~80%。宽度不足以容纳两格时不允许新分栏;已有两格时,正文拖放用于跨格移动,不再创建第三格。 达到两格上限时隐藏分栏控件;关闭回单格后恢复。
|
|
96
|
+
|
|
97
|
+
<a id="copy"></a>
|
|
98
|
+
## 文案
|
|
99
|
+
|
|
100
|
+
该列里的每个字符串都来自 `sidebarRight` 语言命名空间,包括套件的无障碍名称。tab 的标题在 tab 铸造时固定;类型的显示名跟随当前语言。
|
|
101
|
+
|
|
102
|
+
<a id="model-experience"></a>
|
|
103
|
+
## 模型体验
|
|
104
|
+
|
|
105
|
+
None, as the package is a browser-side UI plugin layer that registers nothing model-facing.
|
|
106
|
+
|
|
107
|
+
#### KV Cache effect
|
|
108
|
+
|
|
109
|
+
None; this package neither assembles nor sends a provider request.
|
|
110
|
+
|
|
111
|
+
## 已知限制与延期工作
|
|
112
|
+
|
|
113
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
114
|
+
|
|
115
|
+
- **只在内存中。** 不持久化任何东西;刷新让每个会话从折叠态开始。
|
|
116
|
+
- **没有会话就没有停靠面。** 状态按会话 id 键控,因此 hero 画面右侧什么都不显示。
|
|
117
|
+
- **硬编码的层叠。** 面板与浮窗宿主使用固定的 z-index 值,因为客户端还没有 z-index token 层。
|
|
118
|
+
- **未暴露撤销。** 记录的序列只能通过 `@internal` 服务方法步进;产品控件是有意缺席的。
|
|
119
|
+
- **引导页文案是草稿**,等待产品评审;文字住在 `locales.ts`。
|
|
120
|
+
- **标题在打开时固定。** 类型的 `title(address)` 被捕获进记录;会变的标题只来自可选的标题席位。
|
|
121
|
+
- **没有内容导航栈。** 后退回放的是布局操作;编辑器式的「已访问内容」前进/后退尚未构建。
|
|
122
|
+
|
|
123
|
+
<a id="dev-note"></a>
|
|
124
|
+
### 开发备注
|
|
125
|
+
|
|
126
|
+
<details>
|
|
127
|
+
<summary>维护者工作上下文——点击展开</summary>
|
|
128
|
+
|
|
129
|
+
无。
|
|
130
|
+
|
|
131
|
+
</details>
|
|
132
|
+
|
|
133
|
+
**运行时不变量:** 不发布 companion。两个服务(`sidebarRight`、`sidebarRightTabs`)在同一个 effect 内经 `ctx.reflect.provide` 提供并随之拆除;席位绑定与 Tab 域 occurrence 的生命周期由本包的 spec 直接断言,不存在会与之分歧的独立观察。
|