Web UI 插件架构
客户端 Cordis、Slot、SessionRuntime 与会话投影
结论:Web UI 是第二棵受治理的插件树,React 只是最后一公里
DeepSeek Harness 的浏览器端不是一组被 Host 直接打包进页面的 React 组件。Host Cordis 树先从当前 Loader 组合计算客户端插件图、提供带内容 revision 的 bundle,并把 manifest 写进 HTML;页面再创建一棵独立的浏览器 Cordis 树,用同一份图装载插件。业务状态停在 React-free 的 ConnectionController、SessionManager、Session、SlotRegistry 与 ConversationNodeAssembler 中,web-react 把裸 observable 绑定成 hooks、合成终端 props,最终由一个 root Slot 进入 React。
这套架构把“代码怎样到达浏览器”“插件何时有权贡献界面”“断线后什么需要重取”“一个会话的视图状态何时死亡”分成不同协议。其价值不只是可插拔:动态包可以在不让 Shell 知道业务清单的前提下声明位置、状态和能力;代价则是启动、授权、scope 与重放边界都必须保持一致,否则系统会选择显式失败,而不是悄悄渲染一棵半有效的树。
1. 两棵 Cordis 树:Host 决定可到达代码,Browser 决定活的对象
Web bundle 的 Host 配置同时列出普通 Host 服务(API proxy、runner、webserver)和带 dsh.client 的浏览器插件 roster;其中 modules 是明确的双面包:Node half 扫描 Host Loader 树并发布图,browser half 则成为页面内模块表。connection、runtime、theme、locale、layout、conversation 与 tool 都是图中的独立客户端 entry。WEBUI-DUAL-ROSTER
Host Cordis tree
Loader entries ──scan dsh.client──► manifest + /plugins bundles
│ │
└── webserver / API / SSE ▼
window.__DSH_BOOT__
│
Browser Cordis tree ▼
Context + Loader ◄── ClientModuleSystem ◄── manifest
│
└── connection → runtime → UI plugins → root Slot → React
ClientModuleRegistry 在 Host 上订阅 Loader fiber 变化,初次激活与增量变动走同一条扫描路径;它只接纳仍有 fiber、未 disabled 且声明 web client 的 entry,注册 /plugins 路由,并用 index tap 注入当前 graph。WEBUI-HOST-GRAPH页面端则解析该 graph、构造自己的 Context,再启动浏览器 Loader;这不是把 Host context 序列化到页面,而是 Host 交付组成事实、Browser 重建生命周期。WEBUI-BROWSER-CONTEXT
信任边界因此是双层的:Host 决定哪些包可被页面取到,Browser 的 Cordis fiber 再决定它们是否满足依赖、是否激活、何时卸载。manifest 是组成协议,不是运行中 service container 的远程镜像。
2. Manifest 与两阶段启动:代码到达不等于插件激活
每个 graph row 带 id、bundle url、内容 rev、信息性 inject 边以及可选 immediately。entry 顺序明确不承载激活语义;真正的依赖顺序由 Cordis fiber 的 inject waiting 决定。wire 在浏览器边界完成行字段形状校验后,拆成 module view 与 plugin view。WEBUI-MANIFEST-CONTRACT
| 阶段 | 动作 | 提交条件 |
|---|---|---|
| Module face | 并行 prefetch immediately rows;执行脚本只注册 factory | 所有 prefetch 尝试已结算;失败留给 entry import 显式报告 |
| Plugin face | 先挂 Loader 并注入 module system,再并发创建 entries | Loader quiesced |
| UI gate | 扫描每个 entry 的 root fiber | 全部 ACTIVE 才从 loading 一次切换到完整 UI |
启动实现先等待立即层 barrier,再创建 modules entry、manifest entries 和 shell-own app-shell entry;单项 prefetch 失败只在优化层被吞下,随后由 entry import 重试并显式呈现结果。loader.await() 之后还会完整扫描 fiber:无 fiber 表示 import 失败,PENDING 会列出缺失 service,FAILED 则保留失败状态;任何一个 entry 未激活都会让 loading 页面继续承载错误报告,不提供部分 UI。WEBUI-BOOT-SETTLE
3. 动态模块表与 HMR:先替换代码,再替换 fiber
ClientModuleSystem 使用惰性 CJS 表:外部 classic script 调用 window.__ModuleLoader__.load 只登记 factory;第一次 import 或同步 require 才执行 factory,并缓存 exports、依赖边与它注入的样式。同步 require 只能命中平台 seed、shell static、cache 或已登记 factory,不能临时发起网络请求;循环依赖和未知模块都会显式报错。WEBUI-LAZY-MODULES
Host HMR 端以默认 500ms stat poll 观察 graph 中每个 bundle,内容 hash 真正变化后更新 graph revision,并通过 /plugins/events SSE 广播 rebuilt;连接时还发送 graph snapshot。WEBUI-HMR-HOSTBrowser 端把 reload 串行化:invalidate → prefetch 在旧 fiber 仍服务时取得新 factory,然后先删 registry runtime、排空旧 fiber、移除 owned styles、清除 entry.fiber,最后 entry.refresh() 重新 materialize 与 apply。失败不回滚旧 bundle;prefetch 早期失败时旧 fiber 还在,apply 失败则留下 FAILED fiber。WEBUI-HMR-SWAP
HMR 的一致性单位是插件 fiber,不是 React component。这样 service provider 替换会通过 Cordis activation epoch 自然级联到依赖者,避免 HMR 自己维护第二张依赖图;相应地,重载插件内部的 React local state 不被保留。
4. Shell 极薄:只安装 renderer,并渲染一个 root
app-shell entry 等待 slots、sessions、layout,安装由 web-react 提供的 Slot renderer,并暴露 identity-stable 的 renderApp closure。WEBUI-SHELL-INSTALL真实 assembly 除了会话标题投影,只做一次 ctx.slots.renderSlot('root', {});AppFrame、sidebar、conversation、details 与 overlay 均由后续注册形成。WEBUI-SHELL-ROOT
Shell 不枚举业务页面,也不 import 每个 UI package。它知道的是启动状态、renderer contract 和根 Slot;具体 surface roster 来自 Host graph,具体树形来自 Slot declarations。
5. React-free 对象层:连接、会话与投影先成立,组件随后订阅
runtime 插件先创建 SlotRegistry、Conversation event/view registries、SessionRuntime 和 WorkspaceRuntime,再由唯一消费者启动 connection streams。Mux 帧进入 sessions,Host 帧同时进入 sessions/workspaces,通用 remote event 则转交 remote dispatch;连接建立和死亡只调用对象层方法。WEBUI-RUNTIME-WIRING
WebSocket / HTTP
│
ConnectionController 物理连接代际、backoff、frame pump
│
SessionManager 实例目录、冷会话缓冲、列表与投影
│
Session 连续 seq window、pending、Conversation snapshots
│
bare getSnapshot/subscribe sources
│
web-react useSyncExternalStore selector hooks
│
components
runtime 明确拥有 snapshot-store engine 并导出裸 observable;React binding 不反向进入 object layer。WEBUI-RUNTIME-STORE-OWNER这使同一 Session 可以被非 React 观察者、Slot renderer 和测试消费,也把 connection generation、history repair 等业务状态排除在组件 mount/unmount 之外。
6. WebSocket 重连:重建代际,不假装续接旧流
每个 ConnectionController generation 同时打开 mux 与 host 两条 downlink,并并发执行 host.describe。describe 必须成功;stream readiness 通常等待两条 onOpen,但 3s carrier-open 防楔死 guard 超时后也会放行。readiness 收敛后,stream 结束或 stream/error 会 abort 整代,进入带 jitter 的指数退避,随后创建新 generation。业务 sink 抛错被隔离,不能拖垮 pump。但 host.describe 自身没有 generation signal 或 timeout:若它永久挂起,即使 stream 已结束,控制流仍卡在 handshake 的 Promise.all,不能进入共享重连路径。WEBUI-CONNECTION-GENERATION
runtime 在 generation 死亡时先清除仅对旧连接有效的 pending interactions;新代 ready 后刷新 Session 与 Workspace baseline、发出 connection/reset。WEBUI-RUNTIME-RECONNECTSessionManager 还会重新拉会话列表与已打开的 subagent catalogs,并对所有 resident Session 调用 resync()。WEBUI-MANAGER-RECONNECT从未 open 的 cold resident 在此路径 no-op。WEBUI-SESSION-RESYNC
refreshList() 是 single-flight;请求开始后到达的 live mutations 会重放在响应 baseline 之上。因此“重取”不是用一个较旧列表覆盖重连期间已经观察到的状态。WEBUI-LIST-REFETCH
7. 冷会话、refetch 与 seq gap repair 是三种不同路径
SessionManager 不会因为任意 mux frame 就创建 Session。未实例化会话的普通 event 被丢弃,日后 open() 由 history 回填;需要随 Session 重放的 pending buffer 只保留 history 不能恢复的 approval、question 和最新 queue snapshot。projection 与 jobs 则落在 manager 级镜像,不触发 Session 创建。WEBUI-COLD-SESSION-DISPATCHSession 实例在真正解析 scope 时才惰性创建并重放 pending buffer。WEBUI-LAZY-SESSION
| 情况 | 权威来源 | 处理 |
|---|---|---|
| 从未打开的冷会话 | history + list baseline | 不维护 live window;打开时整页回填 |
| 重连后、此前已打开的 resident 会话 | 新 generation baseline | 清空旧 window,generation-guarded resync() |
已打开 window 中出现 seq > tail + 1 | history tail | 缓冲新帧并 repull;UI 保留旧 window,不闪 loading |
Session 的首次 open 拉 history tail;若 session/subscribed.lastSeq 已超过 tail 且 live buffer 没覆盖缺口,会再拉一次。安装 window 后只按 seq 去重并 stitch buffer。运行中发现 hole 时进入 repairGap():暂存后续帧、重拉 tail,再走同一安装路径;失败时不把有洞的序列直接追加到 UI。WEBUI-SESSION-GAP-REPAIR
8. SlotRegistry:children 声明同时是结构、规格与渲染授权
SlotMap 给每个位置定义 kind、scope 与 owner props;一个 registration 的 children 表不仅记录子 Slot 的 runtime spec,还声明“此 entry 是唯一可渲染这些 key 的主体”。WEBUI-SLOT-TYPE-CONTRACTSlotCore 只先验声明 root。注册未声明目标、重复声明 child、kind 参数缺失或同一 shared store handle 跨 scope 都在 load time 抛错。WEBUI-SLOT-CORE-COMMIT成功提交后,entry disposer 会递归坍缩它声明的整棵子树。WEBUI-SLOT-CASCADE
ctx.slots.inject(key, callback) 让独立激活的贡献者等待声明出现:声明已经存在就同步运行;声明坍缩就逆向处置;之后重新声明则按新的 epoch 再运行。controller 与 callback effect 都归调用方 fiber,因此卸载贡献插件既能取消等待,也能移除已激活注册。WEBUI-SLOT-DECL-INJECT
ui-layout 对 root 的一次注册同时声明 sidebar、conversation、details 与 shell.overlay,并挂入 layout store 与 business inject。WEBUI-ROOT-DECLARATION这说明 layout 不是硬编码 import 树,而是第一位拥有子位置授权的 registrant。
9. 四份 props share:所有权信息只在一个来源出现
| Share | 来源 | 组件获得什么 |
|---|---|---|
PropsRuntime | SlotMap 的 owner/key/scope/inject,以及父 registration 的 child spec | 父级 owner props、slot-level inject、global kit、按 scope 的 session kit |
PropsRenderSlots | 本次注册的 children | 只允许渲染已声明 key 的 renderSlot;需要时附带 SessionProvider |
PropsStore | 注册声明的 store handle | useStore selector 与去掉 draft 参数的完整 actions |
InjectFace | registrant 的 inject 返回值 | 业务 callbacks;hooks compartment 被转成 useXxx |
runtime share 的 scope 与 owner 类型来自 SlotMap;child-render、business inject 与最终 ComposedProps 在类型层汇合。WEBUI-FOUR-SHARESstore contract 把 actions 定义为唯一写集合,组件看不到实例级 set/update,只能通过 useStore 读取、通过 baked actions 写入。WEBUI-STORE-CONTRACT
10. web-react 是 binding terminal,不是业务 runtime
bindSnapshotSelector() 是客户端栈统一的通用 selector-hook constructor:把任何 getSnapshot/subscribe source 绑定成 useSyncExternalStoreWithSelector hook,并稳定捕获 source methods。WEBUI-REACT-BINDINGrenderer 在 entry 渲染时合成 global/session hooks、projection hook、store pair、授权后的 renderSlot、SessionProvider 与 locale t;runtime 交到这条组合边界的仍是裸 source。WEBUI-RENDER-KIT
保留的 renderSlot closure 还会在每次调用时检查 entry 是否仍在 ledger、目标 key 是否由其 children 声明;卸载后的旧 closure 或越权 key 都会抛出专用错误。WEBUI-STALE-AUTHORIZATION
React 负责订阅一致性与组件隔离,SlotCore 负责组合事实,Session/Store 负责状态事实。把 hooks 限制在 terminal 层,使 HMR、测试和非 React 消费者不需要模拟组件生命周期才能保持业务对象正确。
11. UI state 与业务 state 的所有权不是按文件夹,而是按生命周期
| 状态类别 | 真实 owner | 例子 | 重置 / 存续边界 |
|---|---|---|---|
| 连接/业务事实 | ConnectionController、SessionManager、Session | generation、list、pending、event window、projections | 连接代际、Session scope 或 runtime fiber 结束 |
| 跨组件会话 UI | 声明式 Slot store | 选中 call、draft、active view、inspect target | 对应 session scope 被 prune |
| 瞬时局部 UI | 组件 | 一条 Tool row 是否展开 | 该 component unmount/HMR |
| 父级已知的渲染输入 | owner props | 冻结 Tool block、cwd、openFile/inspect callback | 父级下一次投影替换 |
| 持久用户偏好 | domain service + SettingsScope | locale、theme、busy-enter 等 | Host mode 跟随文档 revision;memory mode 只活到页面/runtime 结束 |
conversation 的 chat store 在 apply 时创建 handle,初始状态包含 selection、draft、view 与 inspect,并声明唯一 actions 与持久化 key。WEBUI-CHAT-STORE相反,ToolRow 的 expanded 明确是 component-local useState,不污染 Session snapshot 或 slot store。WEBUI-TOOL-LOCAL-STATE
12. Session scope:选择负责 staging,解析负责惰性对象,prune 统一回收
SessionRuntime 同时拥有列表投影、持久 selection、SessionManager、scope map 与当前 provide bundle;provider roster 改变时,即使 id 不变也会重建 provide bundle。WEBUI-SESSION-RUNTIMEdsh.sessions.current 只在启动时恢复候选选择,运行中的 selected 由 manager 保留;只有仍出现在 list items、或仍有 retained subagent address 的 selected id 才投影为 current。WEBUI-SELECTION-ADDRESSABILITYmasked gap 会清除持久 cell,让 reload 留在空态,但同一进程里的选择在条目返回后仍可重新出现。WEBUI-SESSION-SELECTION
followCurrent() 把 stage 移到 current 并触发 idempotent history open;resolve() 才惰性创建 Cordis Agent scope、Session 对象、binding 与裸 provide sources。会话不再 eligible 且已离开 stage 时,统一 disposal scope fiber、解绑 Session、清除 session-keyed Slot stores,并从 manager 删除内存实例;若它在 staged 状态被移除,teardown 会延迟到 stage 移走,期间保留冻结 scope。Host Session log 始终是之后重新 open 回填的权威事实源。WEBUI-SESSION-SCOPE-LIFECYCLE
Slot store instance 以 handle × scope key 缓存;root 用固定 key,session 用 session id。scope prune 还会清除对应 persisted value,防止已死亡会话留下孤儿 UI 状态。WEBUI-STORE-SCOPE-LIFECYCLEweb-react 的根 provider 跟随 current;严格 SessionProvider 以 key={sessionId} 重挂整个 session subtree,确保跨会话 local component state 不串线。WEBUI-SESSION-PROVIDER
13. Conversation Node:把日志折叠成业务 Context,再投影到多个视图
Conversation Definition 的 match(event) 只能看当前 event,返回稳定业务 id 与 start/update 角色;start/update 折叠 State,buildLocationData 可向 Turn/Step 发布自有数据,buildViewNode 则为一个 target 生成最终 Node。WEBUI-CONVERSATION-DEFINITION
contiguous SessionEvent window
│ match(event) → {kind, id, role}
▼
Context(kind:id): start + ordered updates + State
│ │
│ └── Location index: Session / Turn / Step
▼
target-specific stable Node ──► chat / trajectory / other view builders
replaceWindow 留给完整 open/resync/gap repair 与低频 registry rebuild。普通 append 不扫描旧 Context:只更新 location boundary、匹配这一个 input,并重放真正受依赖变化影响的 Context;prepend 只收集新旧页中的 fresh events,刷新改变 location 的 matches,再重放 affected closure。WEBUI-CONVERSATION-INCREMENTAL同一个 event 可被多个 Definition 接纳;fallback 仅在没有普通 Definition 匹配并认领同一 target 时运行。Context key 是 kind + id;重复 start,或在日志顺序中位于 start 之前的 update,都会失败。WEBUI-CONVERSATION-FOLDDefinition 返回不稳定 view key 或错误 target 同样会失败。WEBUI-CONVERSATION-VIEW-VALIDATION
Location index 重建时保留未变化 Turn/Step 对象的引用,并返回 location 改变的 seq 集合;live 非边界 append 只记录当前坐标,边界 append 只重访所属 Turn 的 seq。WEBUI-LOCATION-INDEX因此历史分页可以补齐更早的 start 或 predecessor,又不必让所有可见 Node 更换 identity。
14. Tool 呈现:递归拓扑归 runtime,原子外观按 wire name 分派
ui-tool 先以 tool-call key 占据 conversation.chat.node,并在同一次注册中声明 session-scoped keyed child tool.call.toolview;随后 shell、read、mutation、search、web、todo、question 等视图各自注册。WEBUI-TOOL-REGISTRATION原子视图的 owner payload 只包含 call id、wire tool name、冻结的 running/settled block、cwd,以及 Host-bound openFile 与 local inspect callback;框架仍按 session scope 注入 standard kit,registrant 也可加入自己的 inject share。WEBUI-TOOLVIEW-CONTRACTWEBUI-FOUR-SHARES它不负责遍历 root/subcall 拓扑,也不重新配对事件。
ToolCallTree 对根 call 与每层 subCalls 走同一 keyed dispatch,entryKey 就是 wire tool name;无注册者时回退 GenericToolCard。WEBUI-TOOL-DISPATCHgeneric renderer 选择 terminal/read/diff/search/web card。WEBUI-GENERIC-TOOLVIEW其纯模型从冻结 slice 派生 variant、lifecycle、summary、path 与 input/output;专用 keyed registrant 命中时直接替换这一 fallback,而不是在通用卡片外再叠一层。WEBUI-TOOL-MODEL
扩展一个新 Tool UI 的最小面是“注册 wire name 对应的原子 renderer”。Session 事件配对、Code Mode 子调用树与 ChatFlow 位置仍由上游投影拥有,避免每个业务插件各自实现一套 transcript parser。
15. Locale、Theme 与 Settings:功能拥有偏好,Shell 只提供容器
| 域 | 状态 owner | 呈现 owner | 同步方式 |
|---|---|---|---|
| Settings transport | 每个 namespace 的 SettingsScope | 功能自己的 setting row | 后台 read;带 expectedRevision 的串行单字段 write;reset/event refetch |
| Locale | LocaleRuntime 的 active locale 与 dictionary registry | Slot renderer 合成 t | snapshot revision;active namespace → zh namespace → active common → zh common → key |
| Theme | ThemeRuntime 的 preference、system resolution 与 token layers | ui-layout ThemePresenter | theme/change;纯 DOM projection,不经 React |
| Settings shell | Slot ledgers 与 shell-local navigation projection | ui-settings-general | 同时订阅 Slot version 与 locale revision |
SettingsScope 以 namespace 串行读写,write 携带最近 revision;拒绝或 transport 失败时只让最新 write 触发回读。旧 read 仍可推进 revision/base/user metadata,但不会发布 decoded value 或 ready status。Binder 在调用方 context 上先订阅 invalidation,再非阻塞启动首读;非 loopback browser 被放入 memory mode,不调用 privileged settings RPC。WEBUI-SETTINGS-SCOPE
LocaleRuntime 从浏览器语言得到 provisional 值,再接纳 Host selection;typed registration 要求同一 namespace 同时提供 zh/en。WEBUI-LOCALE-OWNER服务自身作为 LocaleFace 安装到 SlotRegistry,并把 Language row 注册进 General section。WEBUI-LOCALE-INSTALLThemeRuntime 拥有 light/dark/system、OS media query 与 token override layers,但不写 DOM。WEBUI-THEME-OWNERui-layout 从 getter 取初值并监听 theme/change。WEBUI-THEME-PRESENTERThemePresenter 只写 color-scheme、dark attribute、owned CSS vars 与自有 meta。WEBUI-THEME-DOM
ui-settings-general 占据 sidebar.settings,再声明 trigger、header、action、close、section 与 onboarding children;导航 rows 从 Slot ledger 派生,文案刷新同时跟随 locale revision。各功能只把自己的 row 或 section 注册到这些位置。WEBUI-SETTINGS-SHELL
16. 已知缺口:Developer Preview 的架构边界仍在移动
项目根 README 将当前版本标为 Developer Preview,并明确提示会有破坏兼容的变更。WEBUI-DEVELOPER-PREVIEW
| 缺口 | 当前行为 | 实际影响 |
|---|---|---|
Hung host.describe 可楔住重连 | stream-open wait 有界,但并发 unary describe 没有 generation timeout 或 signal | describe 结算前,已死亡的 stream 也不能推进到 backoff |
| 启动没有 progressive rendering | 任一 entry 未 ACTIVE,页面停在 loading/error | 一致性强,但单插件失败会阻止整页可用 |
| HMR 没有 rollback | import/apply 失败留下 fiberless 或 FAILED entry | 需要下一次 rebuild 修复;插件内 React local state 会丢失 |
| 通用客户端 unload chain 仍是 stub | loader.unload 未实现;HMR 走专用 registry-first swap | 不能把开发期热替换外推成任意插件的通用卸载能力WEBUI-UNLOAD-LIMIT |
| 远端 browser settings 不持久 | non-loopback SettingsScope 为 memory mode | locale/theme 可改本地 runtime,但刷新后丢失;依赖 Host scope 的设置面保持 unavailable |
| Session scope 是单舞台模型 | 一个 list.current 驱动单一 staged occupant;masked/cleared gap 保留上一 occupant,直到下一次 open 移动 stage | 多 pane/并发观察者需要把 teardown 从单 occupant 扩展为真实 observer ownershipWEBUI-SINGLE-STAGE-LIMIT |
| Tool details 入口未闭合 | openDetails 已实现但 assembled UI 没有调用点 | selected-call 原始详情在当前产品组合中不可达WEBUI-DETAILS-LIMIT |
17. 本章验证范围
所有仓库事实固定在 commit 47f943859bef60e4160492346772ded9b24f765a。本次只运行与本章控制流直接相关的 15 个现有测试文件,321 个用例全部通过;没有运行全量构建、全量 GUI、E2E、性能或压力测试。
pnpm exec vitest run \
packages/client/modules/tests/loader.client.spec.ts \
packages/client/hmr/tests/node-half.client.spec.ts \
packages/client/connection/tests/connection.client.spec.ts \
packages/client/runtime/tests/manager.client.spec.ts \
packages/client/runtime/tests/session.client.spec.ts \
packages/client/runtime/tests/sessions-service.client.spec.ts \
packages/client/runtime/tests/conversation-assembler.client.spec.ts \
packages/client/ui-slots/tests/core.client.spec.ts \
packages/client/web-react/tests/scoped-slots-real-core.client.spec.tsx \
packages/client/web-react/tests/session-provider.client.spec.tsx \
packages/client/ui-tool/tests/toolview-slot.client.spec.tsx \
packages/client/ui-settings/tests/settings-scope.client.spec.ts \
packages/client/locale/tests/locale.client.spec.ts \
packages/client/ui-theme/tests/theme.client.spec.ts \
packages/client/ui-layout/tests/theme-presenter.client.spec.ts
Test Files 15 passed (15)
Tests 321 passed (321)
Duration 9.03s
其中 SessionProvider 的 fail-loud 用例在断言异常边界时按预期输出了一段 stderr stack;命令退出码仍为 0,未出现失败用例。
其中 loader/HMR tests 固定 factory arrival、materialization、invalidate、graph watch 与 disposal。WEBUI-MODULE-TESTSWEBUI-HMR-TESTSconnection tests 固定 generation;manager/Session tests 固定 refetch、stitch 与 gap repair,sessions-service tests 固定 scope 惰性创建与 staged teardown 延迟。WEBUI-CONNECTION-TESTSWEBUI-MANAGER-TESTSWEBUI-SESSION-TESTSWEBUI-SESSION-SCOPE-TESTSSlot/web-react tests 固定 declaration gate、lifecycle cascade、真实 uSES binding 与 keyed remount。WEBUI-SLOT-TESTSWEBUI-REACT-BINDING-TESTSWEBUI-WEB-REACT-TESTSConversation tests 固定 append/prepend 与 location replay。WEBUI-CONVERSATION-TESTSWEBUI-LOCATION-TESTSTool、Settings、Locale 与 Theme tests 固定 keyed fallback、偏好所有权和 DOM 投影边界。WEBUI-TOOL-TESTSWEBUI-SETTINGS-TESTSWEBUI-PREFERENCE-TESTSWEBUI-THEME-TESTSWEBUI-THEME-PRESENTER-TESTS
我的学习体会
内容仅自动保存到当前浏览器,不上传、不进入仓库。你可以导出 Markdown 自行归档。