@deepseek-ai/dsh-client-ui-sidebar-right 0.1.6-alpha.2 → 0.1.7-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 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: dbe09becc8f5f322720c21f89e5042a1f423e6f0
6
- README.zh.md: 357f9366298dcc4313068d78f73105691d82c129
5
+ README.md: 14014cb0986222a73e962e390c2675dd7354df1c
6
+ README.zh.md: 7686d7afa8b164bf780b4ce6f927d8829e7b3a96
package/README.md CHANGED
@@ -56,7 +56,7 @@ While the panel is hidden, one button in the conversation header's corner seat (
56
56
 
57
57
  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.
58
58
 
59
- The `rightbar` entry is a root-scoped controller. It reads `usePanelInfo` and mounts the Session-scoped `rightbar.session` subtree only while the Conversation is selected. Switching to a global panel hides the right Sidebar and releases its frame track without deleting the Session's tab state.
59
+ The root-scoped `rightbar` entry renders the selected Session and background Sessions with initialized `keepMounted` bodies through separate `rightbar.session` subtrees. Each View owns its Session reference. Only the foreground Conversation reports a frame track and binds public navigation; other subtrees stay hidden without deleting tab state.
60
60
 
61
61
  <a id="state"></a>
62
62
  ## State
@@ -67,9 +67,9 @@ Carrying the mint counter in the surface is what makes a recorded sequence repla
67
67
 
68
68
  After every action the kit's settle planner keeps the expanded surface populated: a docked pane whose last tab was moved out or floated is merged away, and an emptied root pane receives the default page. A collapsed surface holds no such backfill — a session starts collapsed and empty, and a close that collapses the column leaves it empty — the expansion that would first show an empty layout is what seeds the default page. Explicit closing follows [the default-page and close rules](#the-guide). While expanded there is always at least one tab, and never an empty pane — so there is no separate "close pane" gesture.
69
69
 
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.
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: the foreground Session's floats render whether or not the column is expanded, and their tabs close freely.
71
71
 
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.
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, identity counters and the one-pane or two-horizontal-pane dock layout 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
73
 
74
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.
75
75
 
@@ -78,7 +78,7 @@ The store saves each Session's layout, tab identities, selection, split ratios,
78
78
 
79
79
  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-documentpreview` 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.
80
80
 
81
- 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.
81
+ 1. **The type** — `ctx.sidebarRightTabs.register({ id, kind, patterns?, priority?, canOpen?, title, guide?, keepMounted? })`, 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.
82
82
  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.
83
83
 
84
84
  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`.
@@ -92,12 +92,14 @@ Two more seats extend what is already there: `sidebar.right.tab.guide` (chain) r
92
92
 
93
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
94
 
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.
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. `mounted` is the observable of the session whose seat is on screen, `undefined` while a global panel replaces the Conversation or no session is selected. A component that opens content from its own mount effect reads it through a bound hook and acts once it is defined: the frame mounts the Conversation column ahead of this seat, and the seat publishes its binding from a passive effect of the same commit, so an effect that assumed a bound seat would run first and throw. The plan review's automatic open reads it this way.
96
96
 
97
97
  <a id="the-tab-domain"></a>
98
98
  ## The Tab domain
99
99
 
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.
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. Reconstructing a Session store replaces its adoption, releasing the old subscription and cleanup reference. 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 excludes background Sessions; foreground floating tabs remain visible when the sidebar closes. `adopt` is absent from the public controller.
101
+
102
+ A type's `keepMounted: true` retains visited bodies across tab and Session changes, collapse, and docking. Each View supplies its stable hold callback through owner props, so rebuilding Session injection bindings does not release a body. Unvisited bodies are not mounted eagerly.
101
103
 
102
104
  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.
103
105
 
@@ -132,7 +134,8 @@ None; this package neither assembles nor sends a provider request.
132
134
 
133
135
  - **Browser-local layout.** Layout does not synchronize between devices; provider-specific state requires its provider's recovery support.
134
136
  - **No surface without a session.** State is keyed by session id, so the hero screen shows nothing on the right.
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.
137
+ - **Hard-coded stacking.** The content tree uses a fixed stacking layer; floats reorder within it without raising that layer above menus.
138
+ - **Retained Session deletion.** Catalog absence alone does not retire an initialized retained View; its tab lifetimes and plugin teardown own release.
136
139
  - **Undo is not exposed.** The recorded sequence is stepped only through the `@internal` service methods; product controls are deliberately absent.
137
140
  - **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.
138
141
  - **No content navigation stack.** Stepping back replays layout operations; an editor-style back/forward over visited content is not built.
package/README.zh.md CHANGED
@@ -56,7 +56,7 @@ kind: "package-reference"
56
56
 
57
57
  面板取会话区的底色与正文字号,而不是自成一层浮起的表面:它是页面的一列,不是压在页面上的卡片。
58
58
 
59
- `rightbar` 入口是 root 作用域的控制器。它读取 `usePanelInfo`,仅在选中会话界面时挂载 session 作用域的 `rightbar.session` 子树。切换到全局面板会隐藏右侧 Sidebar 并释放框架列宽,但不删除会话的 tab 状态。
59
+ root 作用域的 `rightbar` 入口通过独立的 `rightbar.session` 子树渲染选中的 Session,以及拥有已初始化 `keepMounted` 正文的后台 Session。每个 View 拥有自身的 Session reference。只有前台 Conversation 上报框架列宽并绑定公共导航;其他子树保持隐藏,不删除 tab 状态。
60
60
 
61
61
  <a id="state"></a>
62
62
  ## 状态
@@ -67,9 +67,9 @@ kind: "package-reference"
67
67
 
68
68
  每个动作之后,套件的 settle planner 保证展开的停靠面有内容:最后一个 tab 被搬走或浮出的停靠格会被并掉;根格被清空时填入默认页。折叠的停靠面没有这种回填——会话以折叠且为空的停靠面开始,收起整列的关闭也让它保持为空——首次会展示空布局的那次展开才播种默认页。显式关闭遵循[默认页与关闭规则](#the-guide)。展开期间永远至少有一个 tab,永远没有空格——因此没有单独的「关闭格」手势。
69
69
 
70
- 停靠面的最后一个 tab 还多带一条规则,由 store 的 `closeTab` 决定并经 `canCloseTab` 镜像给套件:作为唯一停靠 tab 的引导页不画关闭控件也不画菜单里的关闭项——它的 chip 呈安静样式,在没有扩展条目时次键按下也不弹出菜单——对它的编程式关闭什么都不记录;任何其它 tab 独自留下时,点击关闭会连同整列一起收起,记为一条历史,布局保持为空,直到下次展开时创建当时的默认页。浮动面板不参与这条规则:它们无论列是否展开都会渲染,其 tab 照常关闭。
70
+ 停靠面的最后一个 tab 还多带一条规则,由 store 的 `closeTab` 决定并经 `canCloseTab` 镜像给套件:作为唯一停靠 tab 的引导页不画关闭控件也不画菜单里的关闭项——它的 chip 呈安静样式,在没有扩展条目时次键按下也不弹出菜单——对它的编程式关闭什么都不记录;任何其它 tab 独自留下时,点击关闭会连同整列一起收起,记为一条历史,布局保持为空,直到下次展开时创建当时的默认页。浮动面板不参与这条规则:前台 Session 的浮窗无论列是否展开都会渲染,其 tab 照常关闭。
71
71
 
72
- store 将每个 Session 的布局、标签身份、选中项、分栏比例、浮窗矩形、呈现方式和身份计数以 JSON 保存到 localStorage 的 `dsh.sidebar-right.v1.<sessionId>`。刷新时在渲染标签正文前恢复布局;不同 Session 的布局独立。provider 根据保留的标签身份和资源地址恢复自身内容,包括[终端重连](../ui-sidebar-terminal/README.zh.md#use-this-package)。导航参数、资源内容和活动连接不属于布局状态。撤销历史只保留在内存中,刷新后清空。采用布局或启动探测前会校验保存字段类型、节点成员关系、选中项和身份计数;无效数据只清除对应 Session 的 key。存储失败时,当前布局仍可在内存中使用。同源窗口共享每个 Session 最后保存的布局;活动窗口在刷新前保留各自的当前布局。
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
73
 
74
74
  `sidebarRight.openTabs` 发布所有已保存和已采用 Session 的打开标签元数据,并保持快照引用稳定。启动时读取布局 key,不挂载非当前内容、pin 文件或激活 Agent。已采用的 store 在标签成员变化时更新自己的元数据,永久清除的 scope 删除对应记录。其他窗口写入 storage 不会覆盖当前窗口的活动成员关系。provider 使用该清单恢复自己的资源生命周期。
75
75
 
@@ -78,7 +78,7 @@ store 将每个 Session 的布局、标签身份、选中项、分栏比例、
78
78
 
79
79
  tab 类型分两阶段注册,随包发布的引导类型走的正是别的包的类型走的同一条公开路径(`ui-sidebar-documentpreview` 是活的证明)。两个阶段都在类型自己的 `ctx.effect` 里,因此注册与创建它的插件同生共死。
80
80
 
81
- 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 各占一个格位,席位渲染生效的那个。
81
+ 1. **类型**——`ctx.sidebarRightTabs.register({ id, kind, patterns?, priority?, canOpen?, title, guide?, keepMounted? })`,一份没有运行时钩子的静态声明,返回 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 各占一个格位,席位渲染生效的那个。
82
82
  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;未注册标题时使用打开时保存的文本。
83
83
 
84
84
  由哪个类型打开资源遵循编辑器解析器的惯例:`patterns` 命中的类型先按 `priority` 档排序——`extension`(产品外的类型,最高档,也是未命名时的默认)、`builtin`、`fallback`(任何更具体的类型都应胜过的通用查看器)——再按命中模式的长度,再按注册顺序;`canOpen` 会剔除候选。各档是字符串字面量,因此别的包里的类型不需要从这里做运行时导入。`candidates(address)` 返回排序,`claim(address, kind?)` 返回决定;指定 `kind` 时跳过它的 glob 但保留它的 `canOpen`。
@@ -92,12 +92,14 @@ tab 类型分两阶段注册,随包发布的引导类型走的正是别的包
92
92
 
93
93
  `preferNewPane: true` 会先按普通的两格上限与空间规则尝试分割目标停靠格,无法分栏时回退到该格。`replaceTab` 优先并会禁用这项偏好。
94
94
 
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,而不是写进一个没人绘制的面里。
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,而不是写进一个没人绘制的面里。`mounted` 是「席位正在屏幕上的那个会话」的可观察值,全局面板替代 Conversation 或没有选中会话时为 `undefined`。要在自己的挂载 effect 里打开内容的组件通过绑定的 hook 读取它,并在它有值后再行动:框架把 Conversation 列挂载在这个席位之前,而席位在同一次提交的 passive effect 里才发布绑定,一个假定席位已绑定的 effect 会先运行并 throw。计划审阅的自动打开就是这样读取它的。
96
96
 
97
97
  <a id="the-tab-domain"></a>
98
98
  ## Tab 域
99
99
 
100
- Tab 域按(Session,Tab id)保留导航、中止信号与绑定动作。私有装配回调采用各 Session 的 store,立即对齐恢复的记录,再跟随其提交。记录消失或插件卸载才中止 signal,收起和切会话不销毁记录;undo 恢复的是新 occurrence。`useTabInfo()` 组合框架绑定的 store 与导航 hook,不在组件中手写订阅或在渲染时创建记录。`tab.actions` 始终作用于自己的会话;`tab.visible` 区分正文与标题,浮窗不受整栏收起影响。`adopt` 不在公开控制器上。
100
+ Tab 域按(Session,Tab id)保留导航、中止信号与绑定动作。私有装配回调采用各 Session 的 store,立即对齐恢复的记录,再跟随其提交。同一 Session 的 store 重建会替换其 `adopt` 注册,同时释放旧订阅与清理引用。记录消失或插件卸载才中止 signal,收起和切会话不销毁记录;undo 恢复的是新 occurrence。`useTabInfo()` 组合框架绑定的 store 与导航 hook,不在组件中手写订阅或在渲染时创建记录。`tab.actions` 始终作用于自己的会话;`tab.visible` 区分正文与标题,并排除后台 Session;前台浮窗不受整栏收起影响。`adopt` 不在公开控制器上。
101
+
102
+ 类型声明 `keepMounted: true` 后,已访问正文会在切 tab、切 Session、收起和停靠切换期间保留。每个 View 通过 owner props 提供自身稳定的持有回调,因此 Session injection binding 重建不会释放正文。未访问的正文不会提前挂载。
101
103
 
102
104
  标签页所有者通过 effect 注册 `registerCloseHandler(kind, handler)`。handler 在允许显式关闭或替换前同步保存后台清理任务。资源所有者跟踪完成和重试,侧栏不等待清理。handler 抛错时保留标签页。折叠、展示方式改变和插件卸载不调用关闭 handler;tab abort signal 标识 occurrence 卸载,不代表显式关闭。
103
105
 
@@ -132,7 +134,8 @@ Tab 域按(Session,Tab id)保留导航、中止信号与绑定动作。私
132
134
 
133
135
  - **布局保存在当前浏览器。** 布局不跨设备同步;provider 专属状态需要由该 provider 支持恢复。
134
136
  - **没有会话就没有停靠面。** 状态按会话 id 键控,因此 hero 画面右侧什么都不显示。
135
- - **硬编码的层叠。** 面板与浮窗宿主使用固定的 z-index 值,因为客户端还没有 z-index token 层。
137
+ - **硬编码的层叠。** 内容树使用固定层叠层;浮窗在该层内重排,不会把整层抬到菜单之上。
138
+ - **保活 Session 的删除。** 仅从目录消失不会使已初始化的保活 View 退休;释放由 tab 生命周期和插件卸载负责。
136
139
  - **未暴露撤销。** 记录的序列只能通过 `@internal` 服务方法步进;产品控件是有意缺席的。
137
140
  - **标题在打开时固定。** 类型的 `title(address)` 被捕获进记录;会变的标题只来自可选的标题席位。
138
141
  - **没有内容导航栈。** 后退回放的是布局操作;编辑器式的「已访问内容」前进/后退尚未构建。