# Conflicts: # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md # .agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md # .agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.i18n.yaml # .agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.zh.md # .agents/notes/implemented/bug-fix/2026-08-18-request-image-payload-bound.i18n.yaml # .agents/notes/implemented/bug-fix/2026-08-18-request-image-payload-bound.zh.md # .agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml # .agents/notes/implemented/feature/2026-07-06-sandbox.zh.md # .agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md # .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml # .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md # .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml # .agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml # .agents/notes/implemented/feature/2026-08-03-web-search-source-scroll.i18n.yaml # .agents/notes/implemented/feature/2026-08-03-web-search-source-scroll.zh.md # .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml # .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md # .agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml # .agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md # .agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.i18n.yaml # .agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.i18n.yaml # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md # .agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.i18n.yaml # .agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.zh.md # .agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml # .agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md # .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml # .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md # .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml # .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md # .agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml # README.i18n.yaml # README.zh.md # docs/architecture.i18n.yaml # docs/architecture.zh.md # docs/development.i18n.yaml # docs/development.zh.md # docs/persistence-catalog.i18n.yaml # docs/persistence-catalog.zh.md # docs/subsystems/README.i18n.yaml # docs/subsystems/README.zh.md # docs/subsystems/agent-team.i18n.yaml # docs/subsystems/agent-team.zh.md # docs/subsystems/client-modules.i18n.yaml # docs/subsystems/client-modules.zh.md # docs/subsystems/commands.i18n.yaml # docs/subsystems/commands.zh.md # docs/subsystems/persistence.i18n.yaml # docs/subsystems/persistence.zh.md # docs/subsystems/session-reference.i18n.yaml # docs/tool-catalog.i18n.yaml # docs/tool-catalog.zh.md # docs/user/guide/providers.i18n.yaml # docs/user/guide/providers.zh.md # packages/README.i18n.yaml # packages/README.zh.md # packages/bundle/web-app/README.i18n.yaml # packages/bundle/web-app/README.zh.md # packages/client/README.i18n.yaml # packages/client/README.zh.md # packages/client/connection/README.i18n.yaml # packages/client/connection/README.zh.md # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/README.zh.md # packages/client/ui-primitives/README.i18n.yaml # packages/client/ui-primitives/README.zh.md # packages/client/ui-sidebar/README.i18n.yaml # packages/client/ui-sidebar/README.zh.md # packages/client/ui-workspace/README.i18n.yaml # packages/client/ui-workspace/README.zh.md # packages/context/README.i18n.yaml # packages/context/README.zh.md # packages/core/agent-loop/README.i18n.yaml # packages/credentials/README.i18n.yaml # packages/credentials/README.zh.md # packages/experimental/agent-team/README.i18n.yaml # packages/experimental/agent-team/README.zh.md # packages/experimental/tool-agent-team/README.i18n.yaml # packages/experimental/tool-agent-team/README.zh.md # packages/host/frontend-static/README.i18n.yaml # packages/host/frontend-static/README.zh.md # packages/host/webserver/README.i18n.yaml # packages/host/webserver/README.zh.md # packages/interaction/commands/README.i18n.yaml # packages/interaction/commands/README.zh.md # packages/plan/plan-mode/README.i18n.yaml # packages/plan/plan-mode/README.zh.md # packages/sandbox/sandbox-local/README.i18n.yaml # packages/sandbox/sandbox-local/README.zh.md # packages/session/README.i18n.yaml # packages/session/README.zh.md # packages/session/session-persistence-sqlite/README.i18n.yaml # packages/session/session-persistence-sqlite/README.zh.md # packages/session/session-projection-cache/README.i18n.yaml # packages/session/session-projection-cache/README.zh.md # packages/shell/tool-pwsh/README.i18n.yaml # packages/shell/tool-pwsh/README.zh.md # packages/subagent/subagent-codex/README.i18n.yaml # packages/subagent/subagent-codex/README.zh.md # packages/subagent/subagent/README.i18n.yaml # packages/subagent/subagent/README.zh.md # packages/web/tool-web/README.i18n.yaml # packages/web/tool-web/README.zh.md # scripts/snapshots/translation-prompt-v4/request-response.expected.json
4 KiB
Agent Note: 插件激活前的主题引导
Status: implemented
English | 中文
问题
Web 壳在浏览器侧插件树激活前呈现 Loading plugins…。ui-theme 的 token 样式随动态客户端 bundle 到达,因此不依赖框架的加载页使用私有的明暗回退配色。如果不提前写入 color-scheme 与 body[data-ds-dark-theme],持久化偏好为深色时,该页面仍会先按浅色回退绘制,再在 ui-theme 的 ThemeRuntime 与 ui-layout 的 ThemePresenter 激活后切为深色。
dshClient.immediately 只把 bundle 纳入第一阶段预取,不会让插件在 HTML 解析或壳首次渲染前执行。仅调整客户端插件的加载档位无法关闭这段时间窗口。
决策
ui-theme 的主机侧以一条 body 定位的 script 行(bootThemeInjection)回应每次 webserver/index-inject 收集,renderIndex 把它渲染为 <body> 起始标签后紧接的一段同步内联脚本。订阅是无条件的——没有 web server 的组合根本不会 emit 该事件,ui-theme 照常激活且不贡献任何行。HTML 解析器执行该脚本时,body 已存在,而壳的模块脚本与不依赖框架的启动页尚未运行。
settings provider 存在时,主机侧会注册 ui-theme.preference settings 分节。它为每份 index 响应把经过 schema 校验的内建偏好嵌入内联脚本;不存在 settings provider 或有效注册时则嵌入默认值 system。浏览器通过 prefers-color-scheme 解析 system,不支持 matchMedia 时回退为浅色。脚本只写 ThemePresenter 后续拥有的两项 DOM 状态:document.documentElement.style.colorScheme 与 body[data-ds-dark-theme]。
引导逻辑只认识内建的 light、dark、system 语义,不注册监听器,也不解析第三方主题或 token 覆盖。浏览器侧插件树激活后,ThemeRuntime 仍是主题状态的权威来源,ThemePresenter 会把完整解析结果重新写入同一组 DOM 状态并负责后续更新与释放。
验证
ui-theme 的单元测试覆盖不含任一可选 Host 服务时的激活、脚本位置、Host 设置优先级、系统偏好、缺少 matchMedia、不含 body 的输入、实时读取 settings,以及 Host 注册随插件 fiber 一同释放。真实 Web 组合的 Chromium 场景会选择持久化深色偏好并拦住插件 bundle 请求,使加载页保持可观察,再断言 index 响应产生了深色背景、body 属性和根元素 color-scheme。该变化不改变可访问性树,因此不产生新的页面 golden。
考虑过的替代方案
把逻辑固定写进 apps/web/index.html。 这样能在相同时机执行,但静态 HTML 无法嵌入当前 Host 设置,还会复制 ui-theme 拥有的偏好解析和 DOM 字段;Host 转换会跟随主题插件的生命周期,并让应用壳无需了解主题领域。
让 ui-theme 客户端 bundle 同步或更早激活。 immediately 只控制预取,插件实例化仍发生在壳开始运行之后;把首次渲染阻塞到 ThemeRuntime 激活会延后可见的加载与报错界面,也会让壳的故障呈现依赖被它监测的插件树。
只依赖 prefers-color-scheme 的 CSS。 媒体查询无法读取显式持久化选择,因此操作系统为浅色而用户选择深色时仍会闪烁。
在 <head> 中执行并给 html 添加临时类。 body 此时尚不存在,还需要一套与正式调色板属性不同的临时选择器。紧接 <body> 是能够直接写正式 DOM 字段的最早解析位置。
后果
加载页首帧与持久化内建偏好一致;未组合 settings provider 时则默认采用系统偏好。index 转换会为每份响应读取 Host settings,而内联脚本只包含选定的内建值与 system 解析逻辑。内建偏好语义或 ThemePresenter DOM 字段变化时,必须同时更新脚本与 ThemeRuntime。自定义主题仍会在浏览器插件激活后才完整应用;加载期间,页面使用自己的浅色或深色回退配色。