@deepseek-ai/dsh-client-ui-sidebar-right 0.1.6-alpha.1 → 0.1.6-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-sidebar-right/README.md
5
- README.md: e5d6e1bef68296e77c6986137bba83ad3f888f8d
6
- README.zh.md: 75cd092b5cc8602975340b1e434ade2abf7fb442
5
+ README.md: dbe09becc8f5f322720c21f89e5042a1f423e6f0
6
+ README.zh.md: 357f9366298dcc4313068d78f73105691d82c129
package/README.md CHANGED
@@ -43,6 +43,8 @@ Normal and fullscreen presentations share the same content tree, so switching do
43
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
44
  | `fullscreen` | Retains the wide-screen normal track; automatic narrow-screen fullscreen takes no track | Covers the entire viewport |
45
45
 
46
+ In Windows Electron, `html[data-windows-titlebar]` keeps fullscreen panels below the caption and to the right of the expanded sidebar. The frame's `--dsh-windows-sidebar-width` supplies that inset and maximum width; `--dsh-windows-content-radius` supplies the top-left radius. Only fullscreen panels clip overflow for that corner.
47
+
46
48
  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
49
 
48
50
  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 tab capsules with close controls where allowed, 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.
@@ -67,7 +69,9 @@ After every action the kit's settle planner keeps the expanded surface populated
67
69
 
68
70
  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
71
 
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.
72
+ The store saves each Session's layout, tab identities, selection, split ratios, floating rectangles, presentation and identity counter as JSON under `dsh.sidebar-right.v1.<sessionId>` in localStorage. Reload restores that layout before tab bodies render; switching Sessions keeps their layouts independent. Providers restore their own content from the retained tab identity and resource address, including [terminal reconnection](../ui-sidebar-terminal/README.md#use-this-package). Navigation parameters, resource contents and live connections are not layout state. Undo history stays in memory and resets on reload. Saved field types, node membership, selection and identity counters are validated before adoption or startup discovery; invalid data clears only its Session key. Storage failures leave the current layout usable in memory. Windows at the same origin share the last saved layout per Session; each active window keeps its own current layout until reload.
73
+
74
+ `sidebarRight.openTabs` publishes stable metadata for every open tab across saved and adopted Sessions. Startup discovery reads the layout keys without mounting dormant content, pinning files or activating Agents. Adopted stores replace their own metadata on membership commits, and permanently cleared scopes remove it. Other windows' storage writes do not overwrite this window's live membership. Providers use this inventory to restore their own resource lifetimes.
71
75
 
72
76
  <a id="extension-seats"></a>
73
77
  ## Extension seats
@@ -86,12 +90,14 @@ Two more seats extend what is already there: `sidebar.right.tab.guide` (chain) r
86
90
 
87
91
  `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
92
 
93
+ `preferNewPane: true` first asks the target docked pane to split under the normal two-pane and room rules, then falls back to that pane when no split is available. `replaceTab` takes precedence and disables this preference.
94
+
89
95
  `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
96
 
91
97
  <a id="the-tab-domain"></a>
92
98
  ## The Tab domain
93
99
 
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.
100
+ The Tab domain retains navigation, an abort signal, and bound actions per (Session, tab id). A private assembly callback adopts each Session's store, reconciles restored records immediately, and follows 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
101
 
96
102
  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
103
 
@@ -124,7 +130,7 @@ None; this package neither assembles nor sends a provider request.
124
130
 
125
131
  <a id="known-limitations-and-deferred-work"></a>
126
132
 
127
- - **Memory-only layout.** Sidebar state is not persisted; content-specific recovery cannot reproduce the previous layout or selection.
133
+ - **Browser-local layout.** Layout does not synchronize between devices; provider-specific state requires its provider's recovery support.
128
134
  - **No surface without a session.** State is keyed by session id, so the hero screen shows nothing on the right.
129
135
  - **Hard-coded stacking.** The panel and the float host use fixed z-index values because the client has no z-index token layer yet.
130
136
  - **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
@@ -43,6 +43,8 @@ kind: "package-reference"
43
43
  | `push`(默认) | 面板宽度:会话区让出空间 | 在轨道内;它的左缘与会话区的右缘沿框架自己的曲线一起移动 |
44
44
  | `fullscreen` | 保留宽屏普通轨道;窄屏自动全屏不占轨道 | 覆盖整个窗口 |
45
45
 
46
+ 在 Windows Electron 中,`html[data-windows-titlebar]` 让全屏面板保持在顶栏下方及展开侧栏的右侧。框架的 `--dsh-windows-sidebar-width` 提供该缩进及最大宽度;`--dsh-windows-content-radius` 提供左上圆角。仅全屏面板为该圆角裁剪溢出内容。
47
+
46
48
  席位通过 `ctx.layout.openRightbar(track, fullscreen)` / `closeRightbar()` 报告呈现,框架不注入本包。宽屏切换全屏不改变中栏宽度;宽度拖拽区只在普通展开态显示。独立浮窗及 `float`/`dock` 操作保持可用。
47
49
 
48
50
  面板没有标题行。它的两个控件——形态切换与折叠按钮——搭在套件 chrome 席位上,位于右上格 tab 条的最末端,因此 tab 条就是面板的整条上边。每条 tab 条从左到右读作:在允许时带关闭按钮的 tab 胶囊,添加控件(只在该格没有引导 tab 时绘制;它通过 `ctx.sidebarRight.openTab` 在该格打开引导页),该格的分栏控件,以及右上格里的两个面板控件。窄格里只有 chip 让位;其后的控件从不收缩或被裁切。
@@ -67,7 +69,9 @@ kind: "package-reference"
67
69
 
68
70
  停靠面的最后一个 tab 还多带一条规则,由 store 的 `closeTab` 决定并经 `canCloseTab` 镜像给套件:作为唯一停靠 tab 的引导页不画关闭控件也不画菜单里的关闭项——它的 chip 呈安静样式,在没有扩展条目时次键按下也不弹出菜单——对它的编程式关闭什么都不记录;任何其它 tab 独自留下时,点击关闭会连同整列一起收起,记为一条历史,布局保持为空,直到下次展开时创建当时的默认页。浮动面板不参与这条规则:它们无论列是否展开都会渲染,其 tab 照常关闭。
69
71
 
70
- 布局、打开的标签页、导航参数和选中项只在内存中。刷新会构造折叠的默认态;内容插件随后可重新打开自己仍存活的内容,例如[终端恢复](../ui-sidebar-terminal/README.zh.md#use-this-package)。切换会话则让每个停靠面留在原处。
72
+ store 将每个 Session 的布局、标签身份、选中项、分栏比例、浮窗矩形、呈现方式和身份计数以 JSON 保存到 localStorage 的 `dsh.sidebar-right.v1.<sessionId>`。刷新时在渲染标签正文前恢复布局;不同 Session 的布局独立。provider 根据保留的标签身份和资源地址恢复自身内容,包括[终端重连](../ui-sidebar-terminal/README.zh.md#use-this-package)。导航参数、资源内容和活动连接不属于布局状态。撤销历史只保留在内存中,刷新后清空。采用布局或启动探测前会校验保存字段类型、节点成员关系、选中项和身份计数;无效数据只清除对应 Session 的 key。存储失败时,当前布局仍可在内存中使用。同源窗口共享每个 Session 最后保存的布局;活动窗口在刷新前保留各自的当前布局。
73
+
74
+ `sidebarRight.openTabs` 发布所有已保存和已采用 Session 的打开标签元数据,并保持快照引用稳定。启动时读取布局 key,不挂载非当前内容、pin 文件或激活 Agent。已采用的 store 在标签成员变化时更新自己的元数据,永久清除的 scope 删除对应记录。其他窗口写入 storage 不会覆盖当前窗口的活动成员关系。provider 使用该清单恢复自己的资源生命周期。
71
75
 
72
76
  <a id="extension-seats"></a>
73
77
  ## 扩展席位
@@ -86,12 +90,14 @@ tab 类型分两阶段注册,随包发布的引导类型走的正是别的包
86
90
 
87
91
  `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 始终只在目标分栏内去重,不受该选项影响;否则新 tab 落到 `options.replaceTab` 所在的格与位置(并关掉那个 tab),再退而落到 `options.paneId`,再退而落到活跃停靠格;面板展开,因为用户看不到的内容不算打开。随后 Tab 域记录这次导航——`params` 以 `navigation.params` 抵达正文,`revision` 递增——不进布局历史。`params` 按所开之物定型:某资源类型的查看器把自己那项并入 `SidebarRightResourceParamsMap`(文本预览声明 `{ line?: number }`);接受参数的页类型按其 kind 并入 `SidebarRightTabParamsMap`;值约定为 JSON 形状,运行时不校验。`dsh-resource://` 之外的地址、无人认领的地址、或未注册的 kind 都会 throw:那是接线错误,不是用户错误。
88
92
 
93
+ `preferNewPane: true` 会先按普通的两格上限与空间规则尝试分割目标停靠格,无法分栏时回退到该格。`replaceTab` 优先并会禁用这项偏好。
94
+
89
95
  `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,而不是写进一个没人绘制的面里。
90
96
 
91
97
  <a id="the-tab-domain"></a>
92
98
  ## Tab 域
93
99
 
94
- Tab域按(Session,Tab id)保留导航、中止信号与绑定动作;私有装配回调收养各会话的store,并在每次提交时对齐记录。记录消失或插件卸载才中止signal,收起和切会话不销毁记录;undo恢复的是新occurrence。`useTabInfo()` 组合框架绑定的store与导航hook,不在组件中手写订阅或在渲染时创建记录。`tab.actions` 始终作用于自己的会话;`tab.visible` 区分正文与标题,浮窗不受整栏收起影响。`adopt` 不在公开控制器上。
100
+ Tab 域按(Session,Tab id)保留导航、中止信号与绑定动作。私有装配回调采用各 Session 的 store,立即对齐恢复的记录,再跟随其提交。记录消失或插件卸载才中止 signal,收起和切会话不销毁记录;undo 恢复的是新 occurrence。`useTabInfo()` 组合框架绑定的 store 与导航 hook,不在组件中手写订阅或在渲染时创建记录。`tab.actions` 始终作用于自己的会话;`tab.visible` 区分正文与标题,浮窗不受整栏收起影响。`adopt` 不在公开控制器上。
95
101
 
96
102
  标签页所有者通过 effect 注册 `registerCloseHandler(kind, handler)`。handler 在允许显式关闭或替换前同步保存后台清理任务。资源所有者跟踪完成和重试,侧栏不等待清理。handler 抛错时保留标签页。折叠、展示方式改变和插件卸载不调用关闭 handler;tab abort signal 标识 occurrence 卸载,不代表显式关闭。
97
103
 
@@ -124,7 +130,7 @@ Tab域按(Session,Tab id)保留导航、中止信号与绑定动作;私
124
130
 
125
131
  <a id="known-limitations-and-deferred-work"></a>
126
132
 
127
- - **布局只在内存中。** 侧栏状态不持久化;内容自身的恢复无法还原此前的布局或选中项。
133
+ - **布局保存在当前浏览器。** 布局不跨设备同步;provider 专属状态需要由该 provider 支持恢复。
128
134
  - **没有会话就没有停靠面。** 状态按会话 id 键控,因此 hero 画面右侧什么都不显示。
129
135
  - **硬编码的层叠。** 面板与浮窗宿主使用固定的 z-index 值,因为客户端还没有 z-index token 层。
130
136
  - **未暴露撤销。** 记录的序列只能通过 `@internal` 服务方法步进;产品控件是有意缺席的。