Skip to content

浏览器的很大一部分由 38 个 ui-* 模块构成,每个都是一个给表面涂上一块的 Cordis 插件。它们不是靠编辑某个中央布局来接线;每个模块都把组件注册进一个具名 slot——由声明它的属主声明的扩展点。组合逻辑在 cordis.patch.yml(web-app bundle)里,渲染树挂在一个 root slot 上。

模块范式 ​

每个 ui-* 包都带一个浏览器面(./client),形状相同:

  1. 合并 SlotMap(以及 LocaleNamespaceMap)来声明它渲染或拥有的 slot 契约。
  2. ctx.slots.register({ name, children?, store?, locale?, inject? }, Component)——一次调用贡献组件,并(可选)声明子 slot、共享/独占 store、locale 命名空间与业务 inject 面。
  3. 注册/声明发生在 ctx.effect 内,卸载自动级联。

框架把每个组件的 props 合成自四份:运行时份额(属主 props + 标准套件——useSession、useSessions、useWorkspaces、sessionId、useProjection)、渲染份额(renderSlot/renderSlotChain/SessionProvider)、store 份额(useStore/actions)与inject 面(绑定到你的 <inject> 工厂的业务钩子)。见 packages/client/ui-slots/src/index.ts。

SlotKind 是 'single' | 'list' | 'keyed' | 'chain';SlotScope 是 'root' | 'session-maybe' | 'session'。声明即认领:只有声明某子键的入口才能渲染它。

全部 38 个 ui-* 模块 ​

包职责
ui-slots纯扩展点核心(SlotCore)——无 React、无 Cordis
ui-renderer运行时注册表:SlotRegistry(一个 Cordis Service,即 ctx.slots)、React slot 绑定、ctx.uiRenderer.mount
ui-primitives共享基础件(markdown、math、highlight),供 widget 使用
ui-theme主题注册表(--dsw-* 令牌)、Appearance + FontSize 设置行
ui-layoutroot AppFrame:sidebar+conversation+details 三列、shell.overlay、主题呈现器、ctx.layout 面板服务
ui-sidebar导航壳:品牌行(sidebar.brand.mark/sidebar.brand.name)、New Session、折叠导轨、承载会话树的浏览区、底部钉住的 sidebar.settings/sidebar.footer.action
ui-workspace侧栏里的 Workspace/Session 浏览器 + hero 里的 WorkspacePicker——分组或平铺行、搜索、状态圆点、fork/archive
ui-sessionSession Controller 的 React 适配:useSession/useSessions 钩子与会话作用域 slot 数据
ui-conversation组装座椅:聊天骨架、composer/input 停靠区、details 壳、header 动作(即 ui-chat 等占用的座椅)
ui-chatChat 目标:拥有 conversation.chat.node(keyed)与 conversation.message.images、节点定义、details、滚动状态
ui-input-trigger/ 与 @ 检测、候选菜单、ctx.inputTriggers 来源名册
ui-commandsctx.commandUi:命令目录缓存与三种分派
ui-skill/ 触发的技能调用来源
ui-subagent子代理目录 header 动作、composer 链、@ 引用来源
ui-jobs基于 jobsBySession 的后台任务列表 header 动作
ui-goalinput 停靠区里的 GoalBar,基于 goal 投影
ui-planconversation.input.plan 里的计划模式状态芯片
ui-tool工具调用呈现、keyed 分派 + 通用兜底(tool.call.toolview)、conversation.details.tool 里的工具详情
ui-trajectory轮次感知的事件账本,conversation.view 标签页
ui-workflow-run作为 Chat 节点的持久 workflow 运行
ui-deliverables产物文件行 + 内联文件链接
ui-message-feedback助手动作条里的赞/踩 + 备注
ui-model-selection/model popupSelect + composer 模型座椅
ui-permission-presets权限默认行 + 风险确认
ui-agent-presetAgent 预设选择 + 名册管理
ui-user-questions经 conversation.composer 渲染用户提问
ui-approval作用域化 Remote Event 瀑布之上的审批 composer 接管(conversation.approval.detail);零运行时依赖
ui-referencecomposer 的统一 Web @file/@session 引用来源
ui-scheduleSession header 里的只读活跃 Schedule 目录
ui-settings设置域基础:ctx.settingsScope + 设置 schema 服务 + slot 契约
ui-settings-general设置壳、General 分区、chrome
ui-settings-modelsDeepSeek / pi-ai provider + 模型编辑器
ui-settings-plugins宿主平面插件配置卡片
ui-settings-plugin-inventory只读插件清单标签页
ui-directory-picker-browse应用内浏览目录选择器
ui-directory-picker-native无渲染的原生 OS 选择器驱动
ui-attachment附件渲染原语——在 web-app bundle 里组合,不再是平台种子模块

(另有 ui-cordis,Cordis 动态插件定义卡片——它是 web-app cordis.patch.yml 里的普通一行,不是仅开发用的表面。)

ui-slots:扩展点 ​

SlotCore(packages/client/ui-slots/src/index.ts)无框架依赖,构造时就声明内置 root 座椅:

ts
constructor() {
  const root = this.record('root')
  root.spec = { kind: 'single', scope: 'root' }
  root.declaredBy = '(built-in)'
  root.declarationEpoch = 1
}

值得知道的注册语义:

  • 注册进未声明的 slot 会抛;声明已被声明过的子键会抛。
  • 遮蔽:single/keyed/list 单元按 priority(默认 0)升序;单元里最低的活跃条目渲染,因此 priority 0 是历史上「单占位、失败即声」的默认。
  • 卸载会递归折叠每个已声明子键——一条生命周期轴。
  • onMutate/onEntryError 接入 ctx.emit 与崩溃监管;snapshot() 导出 JSON 安全的声明树。

运行时 SlotRegistry(Cordis Service,packages/client/ui-renderer/src/client/registry.ts,构造函数约在第 95 行)增加了按 scope 的 store 实例轴、install(createSlotRenderer())(仅启动一次)、installLocale(localeFace)、installScope,并把注册销毁路由到调用者的 fiber。scope 属主死亡时,其 store 实例随之一并修剪。

ui-layout:组装 shell ​

ui-layout 通过一次 register 调用(packages/client/ui-layout/src/client/index.ts)把 AppFrame 贡献进运行时 root slot,同时声明它的四个子 slot:

ts
declare module '@deepseek-ai/dsh-client-ui-slots' {
  interface SlotMap {
    'root':      { kind: 'single'; scope: 'root' }
    'sidebar':   { kind: 'single'; scope: 'root' }
    'conversation': { kind: 'single'; scope: 'session-maybe' }
    'details':   { kind: 'single'; scope: 'session' }
    'shell.overlay': { kind: 'list'; scope: 'root' }
  }
}

AppFrame.tsx 渲染带拖拽手柄(指针捕获 + rAF 节流)的三列网格、对面板宽度做让步求解,并在固定树位置渲染每个子 slot。属主 props 已消失:details 占用者(ui-conversation 的 DetailsPanel)不再从框架收到列几何——框架注入 session 作用域的会话 id 与钩子,而新的 ctx.layout 面板服务(packages/client/ui-layout/src/client/service.ts)掌管列是否打开。ctx.layout 是一个小型跨插件面——toggleSidebar()、openDetails()、closeDetails()——由根入口的布局 store 动作支撑(attachPanels 在根挂载后接线一次),因此任何插件里的 widget 都能驱动面板迁移,无需伸进框架内部。shell.overlay 依旧是叠加式、全帧浮动座椅(徽章、toast)。

全界面的具名座椅 ​

取自模块 README 与源码合并的真实 slot 名:

座椅声明者占用者
root运行时(内置)ui-layout AppFrame
sidebar、conversation、details、shell.overlayui-layoutui-sidebar、ui-conversation、(匿名条目)、overlay 列表
sidebar.brand.mark、sidebar.brand.nameui-sidebar(single)ui-brand-official(仅官方构建)
sidebar.workspaces、sidebar.workspaces.directoryFlowui-sidebarui-workspace、ui-directory-picker-*
sidebar.settingsui-sidebar(single)ui-settings-general(Settings 触发器 + 面板)
sidebar.footer.actionui-sidebar(list)功能页脚动作
conversation.session、conversation.session.headerui-conversation(组装)组合行
conversation.session.header.lineage、.utilitiesui-conversation功能 header 格子(fork 谱系、工具)
conversation.session.header.actionsui-conversationui-subagent(目录)、ui-jobs(任务列表)
conversation.viewui-conversation(list)ui-chat(id: 'chat')、ui-trajectory
conversation.chat.nodeui-chat(keyed,scope: session)ui-tool、ui-workflow-run、ui-deliverables 行
conversation.chat.commandviewui-chat(keyed)命令行
conversation.message.imagesui-chat(single)随附画廊(ui-chat 自身)
conversation.chat.turnTailui-chat(chain)ui-deliverables
conversation.chat.assistant-actionsui-chat(list)ui-message-feedback(feedback)
conversation.details.toolui-chat(single)ui-tool(ToolDetails)
conversation.approval.detailui-approval(single)ui-approval 审批 composer
conversation.composerui-conversation(chain)ui-user-questions(question)
conversation.composer.barui-conversation(single,session-maybe)composer 条
conversation.composer.dockui-conversation(list)input 区行
conversation.input.left、.right、.overlayui-conversation(list)功能 input 单元格
conversation.input.attachmentsui-conversation附件预览
conversation.input.dockui-conversationui-goal(GoalBar)、队列行
conversation.input.plan、conversation.input.modelui-conversationui-plan、ui-model-selection
conversation.hero.workspace、.directoryFlowui-conversationui-workspace、ui-directory-picker-*
conversation.hero.brand.mark、conversation.hero.agentPresetui-conversation(root)品牌标记占用者、ui-agent-preset
settings.trigger/.header/.action/.close/.section/.plugins.tab/.onboardingui-settingsui-settings-general chrome/分区
settings.general.itemui-settingsui-theme(Appearance + FontSize)、locale(Language)、ui-permission-presets、ui-agent-preset
tool.call.toolviewui-tool(keyed)业务 Tool 视图

两处值得点名的所有权变化:聊天节点与图片画廊座椅(conversation.chat.node、conversation.message.images)由 ui-chat 声明并注册——ui-conversation 只保留组装座椅(conversation.session.*、conversation.view、composer、hero.*、input.*),负责框定那些占用者渲染的位置。

SlotCore 还给监管者一条缝:reportEntryError 可以把崩溃条目让位出它的单元(渲染器将其退役,slot 落到下一个幸存者),而注册本身在 disposer 运行前始终留在台账上。Chain 种类永不让位——选举替代在选中时解析。snapshot() 以 JSON 安全方式投影活声明树(注册者、单元、优先级、活跃态),让状态面无需导入任何组件即可镜像贡献健康度。

模块作者的生命周期纪律 ​

三条规则保证 ui 模块组合安全:

  1. 在 ctx.effect 内注册(或经 slots.register Service 包装,后者把卸载路由到调用者的 fiber)。卸载随之自动级联注册 + 声明 + store 挂载。
  2. 每个 slot 只有一个声明者。 声明已被其他条目声明过的子键会抛并点名首个声明者——这正是让 renderSlot 可安全经 props 下传的独占渲染权保证。
  3. Store 所有权按 scope。 SlotRegistry 实例轴映射 handle × scope-key;会话作用域 store 每个会话一份,scope 死亡即被修剪、丢弃持久状态。根作用域记录不受会话拆除影响。

遵守这些,模块就可以在没有任何中央注册表知情的情况下被挂载或热重载(dsh-client-hmr)——组合天然成立,因为每个座椅都能在 SlotMap 里被发现、每个 widget 都是可销毁的贡献。

组合活在 bundle 补丁里 ​

没有 ui 模块在代码层面硬接进另一个。随产品发布的浏览器表面名册在 packages/bundle/web-app/cordis.patch.yml 里,作为 dsh.client 行(ui-theme、ui-layout、ui-sidebar、ui-conversation、ui-chat、ui-tool、ui-deliverables、ui-workspace、ui-settings*、ui-goal、ui-plan、ui-message-feedback、ui-model-selection、ui-approval、ui-reference、ui-schedule…)。从补丁删掉一行就移除该功能的全部贡献——视图、slot 与文案一起——因为 shell 里没有别的代码 import 它。这与宿主在 agent 预设背后禁用基础 agent 平面行的「行覆盖」机制相同;浏览器面只是骑在同一张图上。

版本表(本页涉及的包) ​

延伸阅读 ​

  • 前端:客户端运行时与链路 — ctx.slots、ctx.sessions、ctx.workspaces 立在什么之上。
  • 前端:Web 前端 — web-app bundle 挂载哪些 ui 模块、shell 如何渲染 root。
  • 前端:设置 Schema 与表单 — ui-settings 拥有的设置模型层。
  • 前端:本地化 — LocaleNamespaceMap 合并与 t 座椅。
  • packages/client/ui-slots/src/index.ts — SlotCore、SlotKind、SlotScope、ComposedProps。
  • packages/client/ui-renderer/src/client/registry.ts — SlotRegistry(运行时服务)与 packages/client/ui-layout/src/client/AppFrame.tsx(三列框架)。
  • packages/bundle/web-app/cordis.patch.yml — ui 模块名册。