@deepseek-ai/dsh-client-ui-sidebar-right 0.1.5-rc.2 → 0.1.6-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/README.i18n.yaml +2 -2
- package/README.md +11 -5
- package/README.zh.md +13 -7
- package/lib/client.js +154 -41
- package/lib/types/client/contract/slots.d.ts +19 -0
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/service.d.ts +12 -30
- package/lib/types/client/shell/SidebarRight.d.ts +6 -3
- package/lib/types/client/tab-info.d.ts +2 -2
- package/lib/types/client/tab-registry.d.ts +6 -0
- package/lib/types/client/tabs/guide/GuideBody.d.ts +2 -2
- package/package.json +15 -14
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-sidebar-right/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: e5d6e1bef68296e77c6986137bba83ad3f888f8d
|
|
6
|
+
README.zh.md: 75cd092b5cc8602975340b1e434ade2abf7fb442
|
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ After every action the kit's settle planner keeps the expanded surface populated
|
|
|
67
67
|
|
|
68
68
|
The docked surface's last tab carries one more rule, decided in the store's `closeTab` and mirrored to the kit through `canCloseTab`: the guide standing as the only docked tab draws no close control and no menu close item — its chip sits quiet, and with no extension item contributed a secondary press opens no menu — and a programmatic close of it records nothing; any other tab standing alone closes together with the column in one entry — the layout stays empty until the next expansion seeds its current default page. Floating panels take no part in the rule: they render whether or not the column is expanded, and their tabs close freely.
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
Layout, open tabs, navigation parameters and selection are memory-only. A reload constructs the collapsed default; content plugins can then reopen their own live content, as [terminal recovery](../ui-sidebar-terminal/README.md#use-this-package) does. Switching sessions keeps each surface where it was.
|
|
71
71
|
|
|
72
72
|
<a id="extension-seats"></a>
|
|
73
73
|
## Extension seats
|
|
@@ -84,7 +84,7 @@ Two more seats extend what is already there: `sidebar.right.tab.guide` (chain) r
|
|
|
84
84
|
<a id="ctxsidebarright"></a>
|
|
85
85
|
## `ctx.sidebarRight`
|
|
86
86
|
|
|
87
|
-
`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 resource tab already showing the same (kind, contentId) is focused wherever it sits unless `revealIfOpened: false`; page tabs always deduplicate within the target pane, regardless of that option; 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.
|
|
87
|
+
`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 resource tab already showing the same (kind, contentId) is focused wherever it sits unless `revealIfOpened: false`; ordinary page tabs always deduplicate within the target pane, regardless of that option; 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.
|
|
88
88
|
|
|
89
89
|
`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.
|
|
90
90
|
|
|
@@ -93,12 +93,18 @@ Two more seats extend what is already there: `sidebar.right.tab.guide` (chain) r
|
|
|
93
93
|
|
|
94
94
|
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.
|
|
95
95
|
|
|
96
|
+
Tab owners register `registerCloseHandler(kind, handler)` through an effect. A handler synchronously retains any background cleanup before allowing explicit close or replacement. The resource owner tracks completion and retry; the sidebar does not wait. A thrown handler preserves the tab. Collapse, presentation changes and plugin disposal do not invoke close handlers; the tab abort signal identifies occurrence disposal, not an explicit close.
|
|
97
|
+
|
|
96
98
|
<a id="the-guide"></a>
|
|
97
99
|
## The guide
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
Each `guide` entry has an `id` unique within its provider. The guide renders `sidebar.right.tab.guide.entry` by the active definition id, forwarding the entry id, resolved title and optional description, plus the framework `useTabInfo` hook. A provider can register a custom card under its definition id; otherwise the standard icon/title button renders. The sidebar owns width and placement, while custom components own their markup and interaction. Entry identity remains stable across sorting and unrelated registrations.
|
|
102
|
+
|
|
103
|
+
A tab type can declare `multiple: true` for independent instances such as terminals. Each open receives a distinct content address, so placement and docking preserve those instances; ordinary pages retain per-pane uniqueness.
|
|
104
|
+
|
|
105
|
+
Default pages depend on the number of registered guide entries, not the number of tab types or open tabs. Exactly one entry opens its page directly; zero or multiple entries open the guide. Explicitly adding a guide still opens the guide, even with one entry. The sole docked guide is the only tab that cannot close; closing any other sole tab also collapses the column. The chip, context menu, and `close` API apply the same rule.
|
|
100
106
|
|
|
101
|
-
The guide tab is a muted compass over one entry capsule per `guide` entry the registered types contributed, in `order`, centred in the body; the guide has no words of its own. A capsule shows the entry's glyph — or the guide's quieter cube placeholder when the entry registered none — and its title; while at most four entries are listed, an entry that registered a `description` shows it under the title, and a longer list drops every description. Picking a capsule 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, an expanded empty root pane, and the pane a sole tab vacates by dropping on its own edge use the same default-page rule, one tab per new pane; the self-edge drop leaves the dragged tab focused. A plain `openTab('guide')` opens or focuses the guide only within the active or named pane. Splitting an empty pane does nothing and returns no new pane. 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.
|
|
107
|
+
The guide tab is a muted compass over one entry capsule per `guide` entry the registered types contributed, in `order`, centred in the body; the guide has no words of its own. A capsule shows the entry's glyph — or the guide's quieter cube placeholder when the entry registered none — and its title; while at most four entries are listed, an entry that registered a `description` shows it under the title, and a longer list drops every description. Picking a default capsule 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, an expanded empty root pane, and the pane a sole tab vacates by dropping on its own edge use the same default-page rule, one tab per new pane; the self-edge drop leaves the dragged tab focused. A plain `openTab('guide')` opens or focuses the guide only within the active or named pane. Splitting an empty pane does nothing and returns no new pane. 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.
|
|
102
108
|
|
|
103
109
|
<a id="copy"></a>
|
|
104
110
|
## Copy
|
|
@@ -118,7 +124,7 @@ None; this package neither assembles nor sends a provider request.
|
|
|
118
124
|
|
|
119
125
|
<a id="known-limitations-and-deferred-work"></a>
|
|
120
126
|
|
|
121
|
-
- **Memory-only.**
|
|
127
|
+
- **Memory-only layout.** Sidebar state is not persisted; content-specific recovery cannot reproduce the previous layout or selection.
|
|
122
128
|
- **No surface without a session.** State is keyed by session id, so the hero screen shows nothing on the right.
|
|
123
129
|
- **Hard-coded stacking.** The panel and the float host use fixed z-index values because the client has no z-index token layer yet.
|
|
124
130
|
- **Undo is not exposed.** The recorded sequence is stepped only through the `@internal` service methods; product controls are deliberately absent.
|
package/README.zh.md
CHANGED
|
@@ -67,7 +67,7 @@ kind: "package-reference"
|
|
|
67
67
|
|
|
68
68
|
停靠面的最后一个 tab 还多带一条规则,由 store 的 `closeTab` 决定并经 `canCloseTab` 镜像给套件:作为唯一停靠 tab 的引导页不画关闭控件也不画菜单里的关闭项——它的 chip 呈安静样式,在没有扩展条目时次键按下也不弹出菜单——对它的编程式关闭什么都不记录;任何其它 tab 独自留下时,点击关闭会连同整列一起收起,记为一条历史,布局保持为空,直到下次展开时创建当时的默认页。浮动面板不参与这条规则:它们无论列是否展开都会渲染,其 tab 照常关闭。
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
布局、打开的标签页、导航参数和选中项只在内存中。刷新会构造折叠的默认态;内容插件随后可重新打开自己仍存活的内容,例如[终端恢复](../ui-sidebar-terminal/README.zh.md#use-this-package)。切换会话则让每个停靠面留在原处。
|
|
71
71
|
|
|
72
72
|
<a id="extension-seats"></a>
|
|
73
73
|
## 扩展席位
|
|
@@ -93,12 +93,18 @@ tab 类型分两阶段注册,随包发布的引导类型走的正是别的包
|
|
|
93
93
|
|
|
94
94
|
Tab域按(Session,Tab id)保留导航、中止信号与绑定动作;私有装配回调收养各会话的store,并在每次提交时对齐记录。记录消失或插件卸载才中止signal,收起和切会话不销毁记录;undo恢复的是新occurrence。`useTabInfo()` 组合框架绑定的store与导航hook,不在组件中手写订阅或在渲染时创建记录。`tab.actions` 始终作用于自己的会话;`tab.visible` 区分正文与标题,浮窗不受整栏收起影响。`adopt` 不在公开控制器上。
|
|
95
95
|
|
|
96
|
+
标签页所有者通过 effect 注册 `registerCloseHandler(kind, handler)`。handler 在允许显式关闭或替换前同步保存后台清理任务。资源所有者跟踪完成和重试,侧栏不等待清理。handler 抛错时保留标签页。折叠、展示方式改变和插件卸载不调用关闭 handler;tab abort signal 标识 occurrence 卸载,不代表显式关闭。
|
|
97
|
+
|
|
96
98
|
<a id="the-guide"></a>
|
|
97
99
|
## 引导页
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
每个 `guide` 入口具有 provider 内唯一的 `id`。开始页按当前生效的定义 id 渲染 `sidebar.right.tab.guide.entry`,传入入口 id、解析后的标题、可选描述,以及框架提供的 `useTabInfo` hook。provider 可按自己的定义 id 注册自定义卡片;未注册时使用默认图标与标题按钮。侧栏负责宽度和排列,自定义组件负责内部结构和交互。排序或其他注册变化不会改变入口身份。
|
|
102
|
+
|
|
103
|
+
终端等需要独立实例的 tab 类型可声明 `multiple: true`。每次打开都会获得独立内容地址,因此移动和停靠会保留这些实例;普通页面仍在每个格内去重。
|
|
104
|
+
|
|
105
|
+
默认页取决于已注册的引导入口数,不取决于 tab 类型数或已打开的 tab 数。恰好一个入口时直接打开对应页面;没有入口或有多个入口时打开引导页。即使只有一个入口,显式添加引导页仍会打开引导页。只有作为唯一停靠 tab 的引导页不可关闭;关闭其它任何唯一 tab 时会同时收起整列。chip、上下文菜单与 `close` API 使用同一规则。
|
|
100
106
|
|
|
101
|
-
引导 tab 是一枚弱化的罗盘,下方是各已注册类型贡献的每个 `guide` 条目一个入口胶囊,按 `order` 排列并在正文中居中;引导页自己没有文字。胶囊显示条目的图标——条目没注册图标时用引导页自己更浅的立方体占位符——和标题;列出的条目不超过四个时,注册了 `description`
|
|
107
|
+
引导 tab 是一枚弱化的罗盘,下方是各已注册类型贡献的每个 `guide` 条目一个入口胶囊,按 `order` 排列并在正文中居中;引导页自己没有文字。胶囊显示条目的图标——条目没注册图标时用引导页自己更浅的立方体占位符——和标题;列出的条目不超过四个时,注册了 `description` 的条目在标题下显示它,更长的列表则去掉所有描述。点击默认胶囊会调用 `tab.actions.openTab(entry.kind, { replaceTab: true })`,于是引导页让位给它打开的页。一个格最多持有一个引导 tab。tab 条的添加控件只在该格没有引导 tab 时绘制,并以 `openTab('guide', { paneId, revealIfOpened: false })` 在该格打开一个,这样别的格里的引导页不会截走这次点击;把引导页开进已有引导页的格则改为聚焦它;把引导页拖入、放入或收回到这样的格会合并进去——来者关闭,该格自己的被聚焦;对引导页 `duplicateTab` 不记录任何东西。分栏、展开且为空的根格、以及唯一 tab 拖到本格边缘分屏后腾出的格使用相同的默认页规则,每个新格一个 tab;这种本格边缘拖放让被拖的 tab 保持聚焦。普通的 `openTab('guide')` 只在活跃或指定分栏内打开或聚焦引导页。对空分栏执行 split 不产生变化,也不返回新分栏。产品最多保留左右两格,默认均分,分隔条限定在 20%~80%。宽度不足以容纳两格时不允许新分栏;已有两格时,正文拖放用于跨格移动,不再创建第三格。达到两格上限时隐藏分栏控件;关闭回单格后恢复。
|
|
102
108
|
|
|
103
109
|
<a id="copy"></a>
|
|
104
110
|
## 文案
|
|
@@ -108,17 +114,17 @@ Tab域按(Session,Tab id)保留导航、中止信号与绑定动作;私
|
|
|
108
114
|
<a id="model-experience"></a>
|
|
109
115
|
## 模型体验
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
无,因为本包是浏览器侧 UI 插件层,不注册任何面向模型的内容。
|
|
112
118
|
|
|
113
|
-
#### KV Cache
|
|
119
|
+
#### KV Cache 影响
|
|
114
120
|
|
|
115
|
-
|
|
121
|
+
无;本包既不组装也不发送提供方请求。
|
|
116
122
|
|
|
117
123
|
## 已知限制与延期工作
|
|
118
124
|
|
|
119
125
|
<a id="known-limitations-and-deferred-work"></a>
|
|
120
126
|
|
|
121
|
-
-
|
|
127
|
+
- **布局只在内存中。** 侧栏状态不持久化;内容自身的恢复无法还原此前的布局或选中项。
|
|
122
128
|
- **没有会话就没有停靠面。** 状态按会话 id 键控,因此 hero 画面右侧什么都不显示。
|
|
123
129
|
- **硬编码的层叠。** 面板与浮窗宿主使用固定的 z-index 值,因为客户端还没有 z-index token 层。
|
|
124
130
|
- **未暴露撤销。** 记录的序列只能通过 `@internal` 服务方法步进;产品控件是有意缺席的。
|
package/lib/client.js
CHANGED
|
@@ -34,7 +34,7 @@ window.__ModuleLoader__.load({
|
|
|
34
34
|
let _deepseek_ai_dsh_client_ui_dockkit = require("@deepseek-ai/dsh-client-ui-dockkit");
|
|
35
35
|
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
36
36
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideBody.module.css.mjs
|
|
37
|
-
const css$2 = ".geFEbW_guide{box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:14px;min-height:100%;padding:0 24px;display:flex}.geFEbW_guide:after{content:\"\";flex:0 10%}.geFEbW_hero{color:var(--dsw-static-neutral-200);margin-bottom:16px;display:flex}body[data-ds-dark-theme] .geFEbW_hero{color:var(--dsw-static-neutral-700)}.geFEbW_entry{box-sizing:border-box;width:380px;max-width:100%;min-height:56px;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;background:var(--dsw-alias-bg-layer-1);border:.5px solid var(--dsw-alias-border-l4);cursor:pointer;border-radius:24px;align-items:center;gap:14px;padding:14px 20px;display:flex}.geFEbW_entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.geFEbW_entryIcon{width:26px;height:26px;color:var(--dsw-alias-label-secondary);flex:none;justify-content:center;align-items:center;display:flex}.geFEbW_placeholderInk{color:var(--dsw-alias-label-tertiary)}.geFEbW_entryText{flex-direction:column;gap:3px;min-width:0;display:flex}.geFEbW_entryTitle{white-space:nowrap;text-overflow:ellipsis;font-size:15px;line-height:1.4;overflow:hidden}.geFEbW_entryDescription{color:var(--dsw-alias-label-caption);white-space:nowrap;text-overflow:ellipsis;font-size:13px;line-height:1.4;overflow:hidden}.geFEbW_titleIcon{color:var(--dsw-alias-label-tertiary);flex:none}";
|
|
37
|
+
const css$2 = ".geFEbW_guide{box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:14px;min-height:100%;padding:0 24px;display:flex}.geFEbW_guide:after{content:\"\";flex:0 10%}.geFEbW_hero{color:var(--dsw-static-neutral-200);margin-bottom:16px;display:flex}body[data-ds-dark-theme] .geFEbW_hero{color:var(--dsw-static-neutral-700)}.geFEbW_entry{box-sizing:border-box;width:380px;max-width:100%;min-height:56px;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;background:var(--dsw-alias-bg-layer-1);border:.5px solid var(--dsw-alias-border-l4);cursor:pointer;border-radius:24px;align-items:center;gap:14px;padding:14px 20px;display:flex}.geFEbW_entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.geFEbW_entryIcon{width:26px;height:26px;color:var(--dsw-alias-label-secondary);flex:none;justify-content:center;align-items:center;display:flex}.geFEbW_placeholderInk{color:var(--dsw-alias-label-tertiary)}.geFEbW_entryText{flex-direction:column;gap:3px;min-width:0;display:flex}.geFEbW_entryTitle{white-space:nowrap;text-overflow:ellipsis;font-size:15px;line-height:1.4;overflow:hidden}.geFEbW_entryDescription{color:var(--dsw-alias-label-caption);white-space:nowrap;text-overflow:ellipsis;font-size:13px;line-height:1.4;overflow:hidden}.geFEbW_titleIcon{color:var(--dsw-alias-label-tertiary);flex:none}.geFEbW_entryCell{width:380px;max-width:100%}";
|
|
38
38
|
const tagId$2 = "@deepseek-ai/dsh-client-ui-sidebar-right/GuideBody.module.css";
|
|
39
39
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
40
40
|
const tag = document.createElement("style");
|
|
@@ -45,6 +45,7 @@ window.__ModuleLoader__.load({
|
|
|
45
45
|
}
|
|
46
46
|
var GuideBody_module_css_default = {
|
|
47
47
|
"entry": "geFEbW_entry",
|
|
48
|
+
"entryCell": "geFEbW_entryCell",
|
|
48
49
|
"entryDescription": "geFEbW_entryDescription",
|
|
49
50
|
"entryIcon": "geFEbW_entryIcon",
|
|
50
51
|
"entryText": "geFEbW_entryText",
|
|
@@ -155,7 +156,7 @@ window.__ModuleLoader__.load({
|
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
158
|
/** The shipped guide: the tab's own compass over the doors out of the column. */
|
|
158
|
-
function ShippedGuide({
|
|
159
|
+
function ShippedGuide({ children }) {
|
|
159
160
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
160
161
|
className: GuideBody_module_css_default.guide,
|
|
161
162
|
"data-sidebar-right-guide": true,
|
|
@@ -163,24 +164,38 @@ window.__ModuleLoader__.load({
|
|
|
163
164
|
className: GuideBody_module_css_default.hero,
|
|
164
165
|
"aria-hidden": "true",
|
|
165
166
|
children: (0, react_jsx_runtime.jsx)(CompassGlyph, { size: 56 })
|
|
166
|
-
}),
|
|
167
|
-
entry,
|
|
168
|
-
described: entries.length <= MAX_DESCRIBED_ENTRIES,
|
|
169
|
-
onPick
|
|
170
|
-
}, `${entry.kind}:${index}`))]
|
|
167
|
+
}), children]
|
|
171
168
|
});
|
|
172
169
|
}
|
|
173
170
|
/** The guide tab's body, replaceable through its chain child. */
|
|
174
|
-
function GuideBody({ useTabInfo, useGuideEntries, renderSlotChain }) {
|
|
171
|
+
function GuideBody({ useTabInfo, useGuideEntries, renderSlot, renderSlotChain }) {
|
|
175
172
|
const { tab } = useTabInfo();
|
|
173
|
+
const entries = useGuideEntries((entries) => entries);
|
|
176
174
|
return renderSlotChain("sidebar.right.tab.guide", {}, {
|
|
177
175
|
hookContext: useTabInfo,
|
|
178
|
-
fallback: (0, react_jsx_runtime.jsx)(ShippedGuide, {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
176
|
+
fallback: (0, react_jsx_runtime.jsx)(ShippedGuide, { children: entries.map((entry) => {
|
|
177
|
+
const described = entries.length <= MAX_DESCRIBED_ENTRIES;
|
|
178
|
+
const description = described ? entry.description?.() : void 0;
|
|
179
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
180
|
+
className: GuideBody_module_css_default.entryCell,
|
|
181
|
+
children: renderSlot("sidebar.right.tab.guide.entry", {
|
|
182
|
+
entryId: entry.id,
|
|
183
|
+
kind: entry.kind,
|
|
184
|
+
title: entry.title(),
|
|
185
|
+
...description === void 0 ? {} : { description }
|
|
186
|
+
}, {
|
|
187
|
+
entryKey: entry.providerId,
|
|
188
|
+
hookContext: useTabInfo,
|
|
189
|
+
fallback: (0, react_jsx_runtime.jsx)(EntryBox, {
|
|
190
|
+
entry,
|
|
191
|
+
described,
|
|
192
|
+
onPick: (selected) => {
|
|
193
|
+
tab.actions.openTab(selected.kind, { replaceTab: true });
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
}, JSON.stringify([entry.providerId, entry.id]));
|
|
198
|
+
}) })
|
|
184
199
|
});
|
|
185
200
|
}
|
|
186
201
|
//#endregion
|
|
@@ -664,7 +679,7 @@ window.__ModuleLoader__.load({
|
|
|
664
679
|
* @param openTab - the navigation face's `openTab`, which the strip's add control asks for a guide through.
|
|
665
680
|
* @returns the intents the kit reports gestures to.
|
|
666
681
|
*/
|
|
667
|
-
function intentsFor(sessionId, actions, openTab) {
|
|
682
|
+
function intentsFor(sessionId, actions, openTab, closeTab) {
|
|
668
683
|
return {
|
|
669
684
|
focusTab: (tabId) => {
|
|
670
685
|
actions.focusTab(sessionId, tabId);
|
|
@@ -681,9 +696,9 @@ window.__ModuleLoader__.load({
|
|
|
681
696
|
revealIfOpened: false
|
|
682
697
|
});
|
|
683
698
|
},
|
|
684
|
-
closeTab: (tabId) => {
|
|
699
|
+
closeTab: closeTab ?? ((tabId) => {
|
|
685
700
|
actions.closeTab(sessionId, tabId);
|
|
686
|
-
},
|
|
701
|
+
}),
|
|
687
702
|
duplicateTab: (tabId) => {
|
|
688
703
|
actions.duplicateTab(sessionId, tabId);
|
|
689
704
|
},
|
|
@@ -866,7 +881,7 @@ window.__ModuleLoader__.load({
|
|
|
866
881
|
minPaneFraction: .2,
|
|
867
882
|
canAddTab: (paneId) => guideIn(surface.layout, paneId) === void 0,
|
|
868
883
|
canCloseTab: (tabId) => canCloseTab(surface, tabId),
|
|
869
|
-
intents: intentsFor(sessionId, actions, openTab),
|
|
884
|
+
intents: intentsFor(sessionId, actions, openTab, panel.closeTab),
|
|
870
885
|
labels: dockLabels(t),
|
|
871
886
|
renderTab: bodiesFor(panel),
|
|
872
887
|
renderTabTitle: titlesFor(panel),
|
|
@@ -896,7 +911,7 @@ window.__ModuleLoader__.load({
|
|
|
896
911
|
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_dockkit.FloatLayer, {
|
|
897
912
|
state: surface.layout,
|
|
898
913
|
canCloseTab: (tabId) => canCloseTab(surface, tabId),
|
|
899
|
-
intents: intentsFor(sessionId, actions, openTab),
|
|
914
|
+
intents: intentsFor(sessionId, actions, openTab, panel.closeTab),
|
|
900
915
|
labels: dockLabels(t),
|
|
901
916
|
renderTab: bodiesFor(panel),
|
|
902
917
|
renderTabTitle: titlesFor(panel)
|
|
@@ -909,7 +924,7 @@ window.__ModuleLoader__.load({
|
|
|
909
924
|
* frame learns the panel's presentation, and where `ctx.sidebarRight` learns
|
|
910
925
|
* which session it is acting on, because this is the seat that knows both.
|
|
911
926
|
*/
|
|
912
|
-
function RightbarSeat({ sessionId, width, viewportWidth, canShow, useStore, actions, t, renderSlot, syncPresentation, bindService, openTab, useTabTypes, useTabNavigation, occurrence }) {
|
|
927
|
+
function RightbarSeat({ sessionId, width, viewportWidth, canShow, useStore, actions, t, renderSlot, syncPresentation, bindService, openTab, closeTab, useTabTypes, useTabNavigation, occurrence }) {
|
|
913
928
|
const surfaces = useStore((state) => state.bySession);
|
|
914
929
|
const surface = surfaces[sessionId];
|
|
915
930
|
const shown = surface !== void 0 && surface.layout.expanded;
|
|
@@ -989,6 +1004,7 @@ window.__ModuleLoader__.load({
|
|
|
989
1004
|
renderSlot,
|
|
990
1005
|
surface,
|
|
991
1006
|
openTab,
|
|
1007
|
+
closeTab,
|
|
992
1008
|
useTabTypes,
|
|
993
1009
|
useTabNavigation,
|
|
994
1010
|
useStore,
|
|
@@ -1019,6 +1035,19 @@ window.__ModuleLoader__.load({
|
|
|
1019
1035
|
}) });
|
|
1020
1036
|
}
|
|
1021
1037
|
//#endregion
|
|
1038
|
+
//#region ../../util/crypto/src/index.ts
|
|
1039
|
+
/**
|
|
1040
|
+
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
1041
|
+
* @returns the UUID string.
|
|
1042
|
+
*/
|
|
1043
|
+
function randomUUID() {
|
|
1044
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
1045
|
+
const hex = Array.from(bytes, (byte, index) => {
|
|
1046
|
+
return (index === 6 ? byte & 15 | 64 : index === 8 ? byte & 63 | 128 : byte).toString(16).padStart(2, "0");
|
|
1047
|
+
}).join("");
|
|
1048
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1049
|
+
}
|
|
1050
|
+
//#endregion
|
|
1022
1051
|
//#region lib/types/client/tab-domain.js
|
|
1023
1052
|
/** Every session's occurrences. */
|
|
1024
1053
|
var TabDomain = class {
|
|
@@ -1150,6 +1179,35 @@ window.__ModuleLoader__.load({
|
|
|
1150
1179
|
//#endregion
|
|
1151
1180
|
//#region lib/types/client/service.js
|
|
1152
1181
|
/**
|
|
1182
|
+
* `ctx.sidebarRight`: what other plugins may ask of this column.
|
|
1183
|
+
*
|
|
1184
|
+
* The surface is per session and its state lives in that session's store
|
|
1185
|
+
* instance, which the slot runtime mints per session and a root service cannot
|
|
1186
|
+
* reach on its own. Two paths lead in. The mounted seat publishes its binding —
|
|
1187
|
+
* session id, bound actions, its surface — for exactly as long as it is mounted,
|
|
1188
|
+
* and every command on the public face goes through that binding; a command
|
|
1189
|
+
* arriving with no seat mounted has no session to act on and fails loudly rather
|
|
1190
|
+
* than writing into a surface nobody is drawing. And the plugin adopts each
|
|
1191
|
+
* session's store instance as the runtime mints it, so the controller reaches
|
|
1192
|
+
* any session's store by id and syncs the Tab domain from that store's commits.
|
|
1193
|
+
*
|
|
1194
|
+
* A tab's own actions (`tabActions`) aim at the session the tab is in, not at
|
|
1195
|
+
* the mounted one: they run through that session's adopted store, so a callback
|
|
1196
|
+
* fired after the user switched sessions still lands where its tab is, and they
|
|
1197
|
+
* do nothing for a session whose store was never minted.
|
|
1198
|
+
*
|
|
1199
|
+
* `openResource` and `openTab` are the navigation controller, and every way
|
|
1200
|
+
* into the column is a call to one of them: the conversation's file links, a
|
|
1201
|
+
* tool row's line reference, the strip's add control, a guide entry box, a file
|
|
1202
|
+
* tree's rows. A resource is claimed through the registry by address; a page is
|
|
1203
|
+
* named by kind and recorded at the address this package composes for it. Both
|
|
1204
|
+
* hand the store one settled intent and record the navigation in the Tab
|
|
1205
|
+
* domain. Placement is the caller's option, never a type's property.
|
|
1206
|
+
*
|
|
1207
|
+
* The registration adopts Session stores and injects the mounted seat binding;
|
|
1208
|
+
* callers use the service's navigation methods.
|
|
1209
|
+
*/
|
|
1210
|
+
/**
|
|
1153
1211
|
* Create the public controller and the plugin-private store adoption callback.
|
|
1154
1212
|
* Adoption subscribes without reconciling; the first store commit creates occurrences.
|
|
1155
1213
|
* @param tabs - registered tab types.
|
|
@@ -1186,6 +1244,20 @@ window.__ModuleLoader__.load({
|
|
|
1186
1244
|
tabs;
|
|
1187
1245
|
adopted;
|
|
1188
1246
|
binding;
|
|
1247
|
+
closeHandlers = /* @__PURE__ */ new Map();
|
|
1248
|
+
/**
|
|
1249
|
+
* Register resource cleanup before explicit removal. Failure preserves the tab.
|
|
1250
|
+
* @param kind - tab kind owned by the registering plugin.
|
|
1251
|
+
* @param handler - saves any background cleanup before returning and allowing removal.
|
|
1252
|
+
* @returns an effect-scoped unregister callback.
|
|
1253
|
+
*/
|
|
1254
|
+
registerCloseHandler(kind, handler) {
|
|
1255
|
+
if (this.closeHandlers.has(kind)) throw new Error(`sidebarRight: close handler already registered for ${kind}`);
|
|
1256
|
+
this.closeHandlers.set(kind, handler);
|
|
1257
|
+
return () => {
|
|
1258
|
+
if (this.closeHandlers.get(kind) === handler) this.closeHandlers.delete(kind);
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1189
1261
|
/**
|
|
1190
1262
|
* The Tab domain this controller navigates into; synced from each adopted
|
|
1191
1263
|
* store's commits, read by the seat for each body's owner share.
|
|
@@ -1265,7 +1337,17 @@ window.__ModuleLoader__.load({
|
|
|
1265
1337
|
*/
|
|
1266
1338
|
closeIn(sessionId, tabId) {
|
|
1267
1339
|
const actions = this.actionsFor(sessionId);
|
|
1268
|
-
|
|
1340
|
+
const surface = this.adopted.get(sessionId)?.store.getSnapshot().bySession[sessionId];
|
|
1341
|
+
if (actions === void 0 || surface === void 0) return;
|
|
1342
|
+
const tab = surface.layout.tabs[tabId];
|
|
1343
|
+
if (tab === void 0 || !canCloseTab(surface, tabId)) return;
|
|
1344
|
+
this.removeAfterCleanup(sessionId, tab, () => {
|
|
1345
|
+
actions.closeTab(sessionId, tabId);
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
removeAfterCleanup(sessionId, tab, commit) {
|
|
1349
|
+
this.closeHandlers.get(tab.kind)?.(sessionId, tab);
|
|
1350
|
+
commit();
|
|
1269
1351
|
}
|
|
1270
1352
|
/** Claim a resource and place it in one session; an address outside the scheme or one no type claims throws. */
|
|
1271
1353
|
placeResource(sessionId, actions, address, options) {
|
|
@@ -1276,7 +1358,7 @@ window.__ModuleLoader__.load({
|
|
|
1276
1358
|
placeTab(sessionId, actions, kind, options) {
|
|
1277
1359
|
const definition = this.tabs.get(kind);
|
|
1278
1360
|
if (definition === void 0) throw new Error(`sidebarRight: no tab type is registered as "${kind}"`);
|
|
1279
|
-
const address = pageAddress(kind);
|
|
1361
|
+
const address = definition.multiple === true ? `${pageAddress(kind)}/${randomUUID()}` : pageAddress(kind);
|
|
1280
1362
|
this.place(sessionId, actions, {
|
|
1281
1363
|
kind,
|
|
1282
1364
|
contentId: address,
|
|
@@ -1285,19 +1367,29 @@ window.__ModuleLoader__.load({
|
|
|
1285
1367
|
}
|
|
1286
1368
|
/** The steps both opens share: one store intent, and the navigation record for the tab it settles on. */
|
|
1287
1369
|
place(sessionId, actions, claim, address, placement, params) {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1370
|
+
const commit = () => {
|
|
1371
|
+
actions.openContent(sessionId, {
|
|
1372
|
+
kind: claim.kind,
|
|
1373
|
+
contentId: claim.contentId,
|
|
1374
|
+
title: claim.title,
|
|
1375
|
+
...placement.paneId === void 0 ? {} : { paneId: placement.paneId },
|
|
1376
|
+
...placement.replaceTab === void 0 ? {} : { replaceTab: placement.replaceTab },
|
|
1377
|
+
...placement.revealIfOpened === void 0 ? {} : { revealIfOpened: placement.revealIfOpened }
|
|
1378
|
+
}, (tabId) => {
|
|
1379
|
+
this.tabDomain.navigate(sessionId, tabId, {
|
|
1380
|
+
address,
|
|
1381
|
+
params
|
|
1382
|
+
});
|
|
1299
1383
|
});
|
|
1300
|
-
}
|
|
1384
|
+
};
|
|
1385
|
+
const layout = this.adopted.get(sessionId)?.store.getSnapshot().bySession[sessionId]?.layout;
|
|
1386
|
+
const replaced = placement.replaceTab === void 0 ? void 0 : layout?.tabs[placement.replaceTab];
|
|
1387
|
+
const revealed = layout === void 0 || placement.revealIfOpened === false ? void 0 : (0, _deepseek_ai_dsh_client_ui_dockkit.findContentTab)(layout, claim.contentId, claim.kind);
|
|
1388
|
+
if (replaced === void 0 || replaced.id === revealed) {
|
|
1389
|
+
commit();
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
this.removeAfterCleanup(sessionId, replaced, commit);
|
|
1301
1393
|
}
|
|
1302
1394
|
/**
|
|
1303
1395
|
* Close one tab of the mounted session; the sole docked guide remains open.
|
|
@@ -1305,6 +1397,10 @@ window.__ModuleLoader__.load({
|
|
|
1305
1397
|
*/
|
|
1306
1398
|
close(tabId) {
|
|
1307
1399
|
const { sessionId, actions } = this.require();
|
|
1400
|
+
if (this.adopted.has(sessionId)) {
|
|
1401
|
+
this.closeIn(sessionId, tabId);
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1308
1404
|
actions.closeTab(sessionId, tabId);
|
|
1309
1405
|
}
|
|
1310
1406
|
/**
|
|
@@ -3354,6 +3450,8 @@ window.__ModuleLoader__.load({
|
|
|
3354
3450
|
*/
|
|
3355
3451
|
register(definition) {
|
|
3356
3452
|
const { id, kind } = definition;
|
|
3453
|
+
const entries = definition.guide ?? [];
|
|
3454
|
+
if (new Set(entries.map((entry) => entry.id)).size !== entries.length) throw new Error(`sidebarRight: duplicate guide entry id in "${id}"`);
|
|
3357
3455
|
const band = definition.priority ?? DEFAULT_BAND;
|
|
3358
3456
|
if (this.ids.has(id)) throw new Error(`sidebarRight: tab type id "${id}" is already registered`);
|
|
3359
3457
|
const held = this.kinds.get(kind);
|
|
@@ -3506,7 +3604,8 @@ window.__ModuleLoader__.load({
|
|
|
3506
3604
|
this.cached = this.active().map((entry) => entry.definition);
|
|
3507
3605
|
this.guideEntries = this.cached.flatMap((definition) => (definition.guide ?? []).map((entry) => ({
|
|
3508
3606
|
...entry,
|
|
3509
|
-
kind: definition.kind
|
|
3607
|
+
kind: definition.kind,
|
|
3608
|
+
providerId: definition.id
|
|
3510
3609
|
}))).sort((left, right) => left.order - right.order);
|
|
3511
3610
|
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.listeners, "[ui-sidebar-right] tab registry");
|
|
3512
3611
|
}
|
|
@@ -3634,7 +3733,7 @@ window.__ModuleLoader__.load({
|
|
|
3634
3733
|
};
|
|
3635
3734
|
};
|
|
3636
3735
|
/**
|
|
3637
|
-
* Forward the framework-bound tab hook to
|
|
3736
|
+
* Forward the framework-bound tab hook to guide content.
|
|
3638
3737
|
* @param _standard - the guide's framework standard props.
|
|
3639
3738
|
* @param useTabInfo - the enclosing tab's framework-bound reader.
|
|
3640
3739
|
* @returns the same reader for the replacement.
|
|
@@ -3732,6 +3831,13 @@ window.__ModuleLoader__.load({
|
|
|
3732
3831
|
store,
|
|
3733
3832
|
inject: (sessionId) => ({
|
|
3734
3833
|
...injected,
|
|
3834
|
+
closeTab: (tabId) => {
|
|
3835
|
+
try {
|
|
3836
|
+
controller.closeIn(sessionId, tabId);
|
|
3837
|
+
} catch (error) {
|
|
3838
|
+
console.error("Sidebar tab close failed:", error);
|
|
3839
|
+
}
|
|
3840
|
+
},
|
|
3735
3841
|
keyedHooks: { tabNavigation: (key) => controller.tabDomain.occurrence(sessionId, { id: key }).navigation },
|
|
3736
3842
|
occurrence: (tab) => controller.tabDomain.occurrence(sessionId, tab)
|
|
3737
3843
|
})
|
|
@@ -3749,11 +3855,18 @@ window.__ModuleLoader__.load({
|
|
|
3749
3855
|
const disposeGuide = ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
|
|
3750
3856
|
name: "sidebar.right.pane.tab",
|
|
3751
3857
|
key: GUIDE_ID,
|
|
3752
|
-
children: {
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3858
|
+
children: {
|
|
3859
|
+
"sidebar.right.tab.guide.entry": {
|
|
3860
|
+
kind: "keyed",
|
|
3861
|
+
scope: "session",
|
|
3862
|
+
inject: { hooks: { tabInfo: guideTabInfoFactory } }
|
|
3863
|
+
},
|
|
3864
|
+
"sidebar.right.tab.guide": {
|
|
3865
|
+
kind: "chain",
|
|
3866
|
+
scope: "session",
|
|
3867
|
+
inject: { hooks: { tabInfo: guideTabInfoFactory } }
|
|
3868
|
+
}
|
|
3869
|
+
},
|
|
3757
3870
|
inject: () => guideInjected
|
|
3758
3871
|
}, GuideBody));
|
|
3759
3872
|
const disposeGuideTitle = ctx.slots.inject("sidebar.right.pane.tab.title", () => ctx.slots.register({
|
|
@@ -58,6 +58,18 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
58
58
|
};
|
|
59
59
|
};
|
|
60
60
|
};
|
|
61
|
+
/** One provider's guide card, with the standard card as the owner's fallback. */
|
|
62
|
+
'sidebar.right.tab.guide.entry': {
|
|
63
|
+
kind: 'keyed';
|
|
64
|
+
scope: 'session';
|
|
65
|
+
owner: SidebarRightGuideEntryOwnerProps;
|
|
66
|
+
hookContext: UseSidebarRightTabInfo;
|
|
67
|
+
inject: {
|
|
68
|
+
hooks: {
|
|
69
|
+
tabInfo: SlotHookFactory<'sidebar.right.tab.guide.entry', UseSidebarRightTabInfo>;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
};
|
|
61
73
|
/**
|
|
62
74
|
* Extra items at the end of one tab's actions menu, in registration order.
|
|
63
75
|
* Entries decide their own visibility from the tab they are given. Without a
|
|
@@ -156,4 +168,11 @@ export interface SidebarRightTabMenuOwnerProps {
|
|
|
156
168
|
*/
|
|
157
169
|
dismiss: () => void;
|
|
158
170
|
}
|
|
171
|
+
/** Resolved guide copy and entry identity supplied to a provider's card renderer. */
|
|
172
|
+
export interface SidebarRightGuideEntryOwnerProps {
|
|
173
|
+
readonly entryId: string;
|
|
174
|
+
readonly kind: string;
|
|
175
|
+
readonly title: string;
|
|
176
|
+
readonly description?: string;
|
|
177
|
+
}
|
|
159
178
|
//# sourceMappingURL=slots.d.ts.map
|
|
@@ -27,9 +27,9 @@ export type { RightbarSeatProps, SidebarRightInjected, SidebarRightPresentation
|
|
|
27
27
|
export type { GuideBodyProps, GuideInjected } from './tabs/guide/GuideBody.tsx';
|
|
28
28
|
export type { ExpandButtonProps } from './shell/ExpandButton.tsx';
|
|
29
29
|
export type { SidebarRightState, SurfaceState } from './stores.ts';
|
|
30
|
-
export type { ISidebarRight, SidebarRightBinding, SidebarRightOpenResourceOptions, SidebarRightOpenTabOptions, SidebarRightPlacement, SurfaceActions, } from './service.ts';
|
|
30
|
+
export type { ISidebarRight, SidebarRightBinding, SidebarRightOpenResourceOptions, SidebarRightOpenTabOptions, SidebarRightPlacement, SidebarRightCloseHandler, SurfaceActions, } from './service.ts';
|
|
31
31
|
export type { SidebarRightGuideBox, SidebarRightGuideEntry, SidebarRightTabClaim, SidebarRightTabDefinition, SidebarRightTabPriority, } from './tab-registry.ts';
|
|
32
|
-
export type { SidebarRightTabInfo, SidebarRightTabInjected, UseSidebarRightTabInfo, SidebarRightTabActions, SidebarRightTabMenuOwnerProps, SidebarRightTabNavigation, SidebarRightTabPlacement, } from './contract/slots.ts';
|
|
32
|
+
export type { SidebarRightTabInfo, SidebarRightTabInjected, UseSidebarRightTabInfo, SidebarRightTabActions, SidebarRightTabMenuOwnerProps, SidebarRightTabNavigation, SidebarRightTabPlacement, SidebarRightGuideEntryOwnerProps, } from './contract/slots.ts';
|
|
33
33
|
export type { SidebarRightNavigationParams, SidebarRightResourceParams, SidebarRightResourceParamsMap, SidebarRightTabParams, SidebarRightTabParamsFor, SidebarRightTabParamsMap, } from './contract/params.ts';
|
|
34
34
|
export type { FloatRect, PaneId, TabId, TabRecord } from '@deepseek-ai/dsh-client-ui-dockkit';
|
|
35
35
|
export type { PinResource, SidebarRightNavigator, TabOccurrence } from './tab-domain.ts';
|
|
@@ -1,38 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `ctx.sidebarRight`: what other plugins may ask of this column.
|
|
3
|
-
*
|
|
4
|
-
* The surface is per session and its state lives in that session's store
|
|
5
|
-
* instance, which the slot runtime mints per session and a root service cannot
|
|
6
|
-
* reach on its own. Two paths lead in. The mounted seat publishes its binding —
|
|
7
|
-
* session id, bound actions, its surface — for exactly as long as it is mounted,
|
|
8
|
-
* and every command on the public face goes through that binding; a command
|
|
9
|
-
* arriving with no seat mounted has no session to act on and fails loudly rather
|
|
10
|
-
* than writing into a surface nobody is drawing. And the plugin adopts each
|
|
11
|
-
* session's store instance as the runtime mints it, so the controller reaches
|
|
12
|
-
* any session's store by id and syncs the Tab domain from that store's commits.
|
|
13
|
-
*
|
|
14
|
-
* A tab's own actions (`tabActions`) aim at the session the tab is in, not at
|
|
15
|
-
* the mounted one: they run through that session's adopted store, so a callback
|
|
16
|
-
* fired after the user switched sessions still lands where its tab is, and they
|
|
17
|
-
* do nothing for a session whose store was never minted.
|
|
18
|
-
*
|
|
19
|
-
* `openResource` and `openTab` are the navigation controller, and every way
|
|
20
|
-
* into the column is a call to one of them: the conversation's file links, a
|
|
21
|
-
* tool row's line reference, the strip's add control, a guide entry box, a file
|
|
22
|
-
* tree's rows. A resource is claimed through the registry by address; a page is
|
|
23
|
-
* named by kind and recorded at the address this package composes for it. Both
|
|
24
|
-
* hand the store one settled intent and record the navigation in the Tab
|
|
25
|
-
* domain. Placement is the caller's option, never a type's property.
|
|
26
|
-
*
|
|
27
|
-
* The registration adopts Session stores and injects the mounted seat binding;
|
|
28
|
-
* callers use the service's navigation methods.
|
|
29
|
-
*/
|
|
30
1
|
import type { FloatRect, PaneId, TabId, TabRecord } from '@deepseek-ai/dsh-client-ui-dockkit';
|
|
31
2
|
import type { BoundActions } from '@deepseek-ai/dsh-client-ui-slots';
|
|
32
3
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
33
4
|
import type { SidebarRightResourceParams, SidebarRightTabParamsFor } from './contract/params.ts';
|
|
34
5
|
import type { SidebarRightTabRegistry } from './tab-registry.ts';
|
|
35
|
-
import type
|
|
6
|
+
import { type SidebarRightState, type SurfaceState } from './stores.ts';
|
|
36
7
|
import type { createSidebarRightStore } from './stores.ts';
|
|
37
8
|
import { TabDomain, type PinResource } from './tab-domain.ts';
|
|
38
9
|
/** The seat's bound action set. */
|
|
@@ -101,6 +72,8 @@ export interface SidebarRightOpenTabOptions<K extends string = string> extends S
|
|
|
101
72
|
/** That kind's navigation parameters, typed by kind; delivered as `navigation.params`. */
|
|
102
73
|
readonly params?: SidebarRightTabParamsFor<K>;
|
|
103
74
|
}
|
|
75
|
+
/** Synchronous close/replacement hook; resource owners retain any background cleanup. */
|
|
76
|
+
export type SidebarRightCloseHandler = (sessionId: SessionId, tab: TabRecord) => void;
|
|
104
77
|
/** The outward right-Sidebar face (`ctx.sidebarRight`). */
|
|
105
78
|
export interface ISidebarRight {
|
|
106
79
|
/**
|
|
@@ -171,6 +144,14 @@ export declare class SidebarRightController implements ISidebarRight {
|
|
|
171
144
|
private readonly tabs;
|
|
172
145
|
private readonly adopted;
|
|
173
146
|
private binding;
|
|
147
|
+
private readonly closeHandlers;
|
|
148
|
+
/**
|
|
149
|
+
* Register resource cleanup before explicit removal. Failure preserves the tab.
|
|
150
|
+
* @param kind - tab kind owned by the registering plugin.
|
|
151
|
+
* @param handler - saves any background cleanup before returning and allowing removal.
|
|
152
|
+
* @returns an effect-scoped unregister callback.
|
|
153
|
+
*/
|
|
154
|
+
registerCloseHandler(kind: string, handler: SidebarRightCloseHandler): () => void;
|
|
174
155
|
/**
|
|
175
156
|
* The Tab domain this controller navigates into; synced from each adopted
|
|
176
157
|
* store's commits, read by the seat for each body's owner share.
|
|
@@ -228,6 +209,7 @@ export declare class SidebarRightController implements ISidebarRight {
|
|
|
228
209
|
* @param tabId - the tab to close.
|
|
229
210
|
*/
|
|
230
211
|
closeIn(sessionId: SessionId, tabId: TabId): void;
|
|
212
|
+
private removeAfterCleanup;
|
|
231
213
|
/** Claim a resource and place it in one session; an address outside the scheme or one no type claims throws. */
|
|
232
214
|
private placeResource;
|
|
233
215
|
/** Place a page type in one session at the address pages are recorded under; an unregistered kind throws. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { HostObservable, InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
-
import type { DockIntents, TabRecord } from '@deepseek-ai/dsh-client-ui-dockkit';
|
|
3
|
+
import type { DockIntents, TabId, TabRecord } from '@deepseek-ai/dsh-client-ui-dockkit';
|
|
4
4
|
import type { HalvesFit, PaneId } from '@deepseek-ai/dsh-client-ui-dockkit';
|
|
5
5
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
6
6
|
import type { SidebarRightOpenTabOptions } from '../service.ts';
|
|
@@ -52,6 +52,8 @@ export interface SidebarRightInjected {
|
|
|
52
52
|
* the guide opened by kind, through the same path as every other open.
|
|
53
53
|
*/
|
|
54
54
|
readonly openTab: (kind: string, options?: SidebarRightOpenTabOptions) => void;
|
|
55
|
+
/** Close through the resource owner's cleanup handler. */
|
|
56
|
+
readonly closeTab: (tabId: TabId) => void;
|
|
55
57
|
readonly hooks: {
|
|
56
58
|
readonly tabTypes: HostObservable<readonly SidebarRightTabDefinition[]>;
|
|
57
59
|
};
|
|
@@ -71,6 +73,7 @@ interface PanelProps {
|
|
|
71
73
|
readonly t: RightbarSeatProps['t'];
|
|
72
74
|
readonly renderSlot: Children['renderSlot'];
|
|
73
75
|
readonly openTab: SidebarRightInjected['openTab'];
|
|
76
|
+
readonly closeTab: SidebarRightInjected['closeTab'];
|
|
74
77
|
readonly useTabTypes: RightbarSeatProps['useTabTypes'];
|
|
75
78
|
readonly useTabNavigation: RightbarSeatProps['useTabNavigation'];
|
|
76
79
|
readonly useStore: Store['useStore'];
|
|
@@ -87,13 +90,13 @@ interface PanelProps {
|
|
|
87
90
|
* @param openTab - the navigation face's `openTab`, which the strip's add control asks for a guide through.
|
|
88
91
|
* @returns the intents the kit reports gestures to.
|
|
89
92
|
*/
|
|
90
|
-
export declare function intentsFor(sessionId: SessionId, actions: Store['actions'], openTab: PanelProps['openTab']): DockIntents;
|
|
93
|
+
export declare function intentsFor(sessionId: SessionId, actions: Store['actions'], openTab: PanelProps['openTab'], closeTab?: PanelProps['closeTab']): DockIntents;
|
|
91
94
|
/**
|
|
92
95
|
* The right column's occupant: the panel, anchored to the column's edge and
|
|
93
96
|
* shown or hidden by sliding, plus the floating layer. It is also where the
|
|
94
97
|
* frame learns the panel's presentation, and where `ctx.sidebarRight` learns
|
|
95
98
|
* which session it is acting on, because this is the seat that knows both.
|
|
96
99
|
*/
|
|
97
|
-
export declare function RightbarSeat({ sessionId, width, viewportWidth, canShow, useStore, actions, t, renderSlot, syncPresentation, bindService, openTab, useTabTypes, useTabNavigation, occurrence, }: RightbarSeatProps): ReactNode;
|
|
100
|
+
export declare function RightbarSeat({ sessionId, width, viewportWidth, canShow, useStore, actions, t, renderSlot, syncPresentation, bindService, openTab, closeTab, useTabTypes, useTabNavigation, occurrence, }: RightbarSeatProps): ReactNode;
|
|
98
101
|
export {};
|
|
99
102
|
//# sourceMappingURL=SidebarRight.d.ts.map
|
|
@@ -20,10 +20,10 @@ export interface TabHookContext {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare const tabInfoFactory: SlotHookFactory<'sidebar.right.pane.tab', UseSidebarRightTabInfo>;
|
|
22
22
|
/**
|
|
23
|
-
* Forward the framework-bound tab hook to
|
|
23
|
+
* Forward the framework-bound tab hook to guide content.
|
|
24
24
|
* @param _standard - the guide's framework standard props.
|
|
25
25
|
* @param useTabInfo - the enclosing tab's framework-bound reader.
|
|
26
26
|
* @returns the same reader for the replacement.
|
|
27
27
|
*/
|
|
28
|
-
export declare const guideTabInfoFactory:
|
|
28
|
+
export declare const guideTabInfoFactory: (_standard: unknown, useTabInfo: UseSidebarRightTabInfo) => UseSidebarRightTabInfo;
|
|
29
29
|
//# sourceMappingURL=tab-info.d.ts.map
|
|
@@ -43,6 +43,8 @@ import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
|
43
43
|
export type SidebarRightTabPriority = 'extension' | 'builtin' | 'fallback';
|
|
44
44
|
/** One entry capsule the guide page offers, contributed by the type it opens (picking it opens that type as a page). */
|
|
45
45
|
export interface SidebarRightGuideEntry {
|
|
46
|
+
/** Stable entry identity within its provider. */
|
|
47
|
+
readonly id: string;
|
|
46
48
|
/** Ascending position among every registered type's entries. */
|
|
47
49
|
readonly order: number;
|
|
48
50
|
/**
|
|
@@ -62,6 +64,8 @@ export interface SidebarRightGuideEntry {
|
|
|
62
64
|
}
|
|
63
65
|
/** A guide entry as the registry lists it: with the kind of the type that contributed it, which is what picking it opens. */
|
|
64
66
|
export interface SidebarRightGuideBox extends SidebarRightGuideEntry {
|
|
67
|
+
/** Active implementation identity used to dispatch the entry renderer. */
|
|
68
|
+
readonly providerId: string;
|
|
65
69
|
readonly kind: string;
|
|
66
70
|
}
|
|
67
71
|
/** One registered tab type: its static face, and nothing else. */
|
|
@@ -76,6 +80,8 @@ export interface SidebarRightTabDefinition {
|
|
|
76
80
|
readonly id: string;
|
|
77
81
|
/** Type discriminator: what the tabs of this type are, and what `openTab` names. */
|
|
78
82
|
readonly kind: string;
|
|
83
|
+
/** Each open by kind creates independent content; omission keeps one page per kind in each pane. */
|
|
84
|
+
readonly multiple?: boolean;
|
|
79
85
|
/**
|
|
80
86
|
* Resource-address globs this type recognizes; omit for a page type, which is
|
|
81
87
|
* opened by kind and recognizes no address.
|
|
@@ -29,7 +29,7 @@ export interface GuideInjected {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
/** The guide body's composed props: the tab it draws, its chain child, and the entries. */
|
|
32
|
-
export type GuideBodyProps = PropsRuntime<'sidebar.right.pane.tab'> & PropsRenderSlots<'sidebar.right.tab.guide'> & InjectFace<GuideInjected>;
|
|
32
|
+
export type GuideBodyProps = PropsRuntime<'sidebar.right.pane.tab'> & PropsRenderSlots<'sidebar.right.tab.guide' | 'sidebar.right.tab.guide.entry'> & InjectFace<GuideInjected>;
|
|
33
33
|
/** The guide tab's body, replaceable through its chain child. */
|
|
34
|
-
export declare function GuideBody({ useTabInfo, useGuideEntries, renderSlotChain }: GuideBodyProps): ReactNode;
|
|
34
|
+
export declare function GuideBody({ useTabInfo, useGuideEntries, renderSlot, renderSlotChain }: GuideBodyProps): ReactNode;
|
|
35
35
|
//# sourceMappingURL=GuideBody.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
3
3
|
"description": "Right Sidebar: the docking surface's session-bound state, its panel and header expand control, and the navigation service over it",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -50,20 +50,21 @@
|
|
|
50
50
|
"react-dom": "^18.2.0",
|
|
51
51
|
"picomatch": "^4.0.4",
|
|
52
52
|
"clsx": "^2.0.0",
|
|
53
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
53
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.6-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.1",
|
|
55
55
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
56
|
-
"@deepseek-ai/dsh-client-resources": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-client-
|
|
58
|
-
"@deepseek-ai/dsh-client-
|
|
59
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-session": "^0.1.
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
56
|
+
"@deepseek-ai/dsh-client-resources": "^0.1.6-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-client-test-runtime": "^0.1.6-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-client-store": "^0.1.6-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.6-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-dockkit": "^0.1.6-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.6-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.1",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.1",
|
|
65
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
66
|
+
"@deepseek-ai/dsh-util-crypto": "^0.1.6-alpha.1",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1"
|
|
67
68
|
},
|
|
68
69
|
"files": [
|
|
69
70
|
"lib/index.js",
|