Global main panels without default UI additions
不增加默认界面的全局主面板
Plugins need application-wide views that do not belong to a Session. A Session-scoped Conversation view cannot provide that lifetime, and replacing the Conversation's single slot removes the ordinary conversation surface. Adding this extension must not add navigation controls or reserved space to the default application.
English
Problem
Plugins need application-wide views that do not belong to a Session. A Session-scoped Conversation view cannot provide that lifetime, and replacing the Conversation's single slot removes the ordinary conversation surface. Adding this extension must not add navigation controls or reserved space to the default application.
Decision
The layout declares a root-scoped keyed main slot. The reserved conversation key belongs to Conversation. ui-session derives the root Session binding from uiWorkspace's mainView ownership marker; main.conversation and its associated right Sidebar inherit that Provider binding. Other main entries receive no implicit Session binding. Client Session references owns acquisition and source metadata; this note owns global panel selection.
The sidebar owns the root-scoped sidebar.panellist list. Each list entry supplies its icon and an id matching its main entry; its string or locale-aware label provides plain visible text, the accessible name, and the collapsed tooltip. The shipped composition registered no panel entry when this landed, so an empty list has no DOM or spacing; the web bundle's plugin manager now registers the first one (plugin management moves to the Web sidebar). Selection validates the live main entry and rejects a missing key without replacing the current panel.
One eagerly created root store is shared by the renderer and layout controller. Its panelInfo and layoutInfo objects preserve independent references. The framework supplies usePanelInfo; individual rows and main content subscribe to their required selection values, while AppFrame reads only layout information. The right Sidebar's root controller decides whether to mount its Session subtree and reports the resulting track requirements to the frame.
uiWorkspace.openSession(target) acquires the explicit target before replacing its main reference and returning the main area to Conversation. openWorkspace and forkSession keep the layout's existing beginNavigation() signal and service lifetime. openWorkspace runs its existing synchronous preparation after acquisition and before replacing the main reference. Supersession prevents a late UI commit, not Session creation. Direct Session opening adds no global navigation cancellation. Panel navigation neither releases the retained main Session nor writes a Session event.
DOM focus is not navigation selection. Search and directory-picker controls can receive focus while the global panel and its selected sidebar row remain visible; opening a Session changes the main selection.
Alternatives considered
Session-scoped main views. Their lifetime and standard props bind application-wide state to whichever Session happens to be current.
A second navigation stack. Back buttons and saved return destinations are unnecessary when New Session and workspace Session rows already provide explicit destinations.
React title slots. Navigation entries use the same plain label for visible text and accessibility; a separate title registration is outside that presentation.
Flat selection and layout state with shallow comparison. Separating the two stored objects preserves reference equality directly and avoids allocating and comparing a fresh layout projection on every panel selection.
Consequences
The default sidebar snapshots remain unchanged. Extension panels have no right Sidebar, and selecting a different global panel does not change layout preferences. Switching between a Conversation with a visible right Sidebar and a global panel still changes the required column widths; this is not a promise of zero browser layout work.
Panel selection is transient and resets on reload. Plugin disposal removes its contributions; removing the selected main entry returns the main area to the Conversation. Tests register real temporary panels and cover row interaction, focus, independent stored references, invalid ids, superseded asynchronous navigation, declaration lifetimes, and the empty default sidebar. The Slots reference owns the composition API.
中文
问题
插件需要不属于任何会话的应用级视图。会话作用域的 Conversation 视图无法提供这种生命周期,而替换 Conversation 的 single slot 又会移除普通会话界面。增加此扩展不能在默认应用中增加导航控件或预留空间。
决策
布局声明 root 作用域的 keyed main slot。保留的 conversation key 属于 Conversation。ui-session 根据 uiWorkspace 的 mainView 所有权标记得出根 Session binding;main.conversation 及其关联右 Sidebar 继承该 Provider binding。其他主面板条目不获得隐式会话绑定。Client 会话引用拥有引用获取与来源元数据;本篇拥有全局面板选择。
侧栏拥有 root 作用域的 sidebar.panellist list。每个 list 条目提供图标,以及与主面板条目匹配的 id;字符串或随语言变化的标签提供普通可见文字、无障碍名称和折叠提示。本决定落地时默认组合不注册面板条目,因此空列表没有 DOM 或间距;现在 web bundle 的插件管理器注册了第一个条目(插件管理移到 Web 侧栏)。选中操作检查实时主面板条目,对缺失的 key 报错而不替换当前面板。
渲染器与布局控制器共享一个直接创建的 root 存储。其 panelInfo 和 layoutInfo 对象保持独立的引用。框架提供 usePanelInfo;各行和中央内容订阅所需的选中态值,AppFrame 仅读取布局信息。右侧 Sidebar 的 root 控制器决定是否挂载其会话子树,并把最终所需的列宽报告给框架。
uiWorkspace.openSession(target) 先获取显式目标,再替换主引用并让中央区域返回 Conversation。openWorkspace 和 forkSession 保留布局既有的 beginNavigation() 信号与服务生命周期。openWorkspace 在获取完成后、替换主引用前执行既有的同步准备动作。请求被替代会阻止迟到的 UI 提交,不阻止会话创建。直接打开会话不增加全局导航取消。面板导航既不释放所持主会话,也不写入会话事件。
DOM 焦点不是导航选中态。搜索和目录选择控件可以获得焦点,同时保留全局面板及其侧栏行的选中态;打开会话才改变中央区域的选中态。
考虑过的替代方案
会话作用域的主视图。 其生命周期和标准 props 会把应用级状态绑定到恰好处于当前态的会话。
另一套导航栈。 新会话和工作区会话行已经提供明确目标,不需要返回按钮或保存返回目的地。
React 标题 slot。 导航条目的可见文字与无障碍名称使用同一个普通标签;独立的标题注册不属于这一呈现方式。
平铺选中态和布局状态,再做浅比较。 将两者存为独立对象可以直接保持引用相等,避免每次选择面板都分配并比较新的布局投影。
后果
默认侧栏快照保持不变。扩展面板没有右侧 Sidebar,选择另一个全局面板不会改变布局偏好。在显示右侧 Sidebar 的 Conversation 与全局面板之间切换时,所需列宽仍会变化;这并不保证浏览器完全不计算布局。
面板选中态是瞬时状态,刷新后重置。插件 dispose(资源释放)会移除其贡献;移除当前选中的主面板条目会使中央区域回到 Conversation。测试注册真实临时面板,覆盖行交互、焦点、存储引用的独立性、无效 id、过期异步导航、声明生命周期和默认空侧栏。Slots 参考拥有组合 API 的说明。