Skip to content

feat: per-window themes ("This window" theme scope)#279

Open
TranscriptionFactory wants to merge 1 commit into
zerx-lab:mainfrom
TranscriptionFactory:pr/per-window-themes
Open

feat: per-window themes ("This window" theme scope)#279
TranscriptionFactory wants to merge 1 commit into
zerx-lab:mainfrom
TranscriptionFactory:pr/per-window-themes

Conversation

@TranscriptionFactory

Copy link
Copy Markdown

What

Adds a per-window theme override: a window can use a different theme from the global one, and the choice persists across restart. The active theme stays global by default; selecting "This window" in the theme chooser scopes the theme to the current window only.

Why

Users running multiple windows (e.g. prod vs. dev SSH sessions) often want a visual distinction per window. Today the theme is global only.

How

  • Ambient render window: a thread-local "current render window" in warpui_core, set by the three render chokepoints (build_scene / render_view / render_views) via an RAII guard. Appearance::theme() / ui_builder() consult it to resolve a per-window override from new HashMaps, with a zero-cost is_empty() fast path so there is no cost when the feature is unused.
  • Appearance: theme_overrides / ui_builder_overrides maps; set_window_theme / clear_window_theme invalidate only the target window.
  • Theme chooser: an "All windows" / "This window" scope toggle (default All windows). "This window" routes to the per-window path and records the override on the Workspace.
  • Lifecycle: Workspace::on_window_closed clears the override so the maps don't leak.
  • Persistence: a theme_override TEXT column (migration + schema + model), serialized via serde_json so ThemeKind::Custom round-trips, re-applied on window restore.

Testing

cargo check -p warp passes. A new Appearance unit test covers override resolution and the empty-map fast path. Please run ./script/presubmit to verify the app crate, clippy/fmt, and the new test in CI (the Metal toolchain was unavailable in my local sandbox for a full app build).

Let a user set a theme for one window without affecting others, persisted
across restart. The active theme stays global by default; a per-window
override is resolved during that window's render pass.

Mechanism: an ambient "current render window" thread-local in warpui_core,
set by the 3 render chokepoints (build_scene/render_view/render_views) via
an RAII guard. Appearance::theme()/ui_builder() consult it to resolve a
per-window override from new HashMaps, with a zero-cost is_empty() fast
path so the feature costs nothing when unused. (The plan called for setting
the ambient from Appearance directly in warpui_core, but warpui_core cannot
depend on warp_core/Appearance, so the ambient lives in warpui_core and
Appearance reads it.)

- Appearance: theme_overrides/ui_builder_overrides maps; set/clear_window_theme
  invalidate only the target window.
- AppearanceManager: set/clear_window_theme; Workspace::on_window_closed clears
  the override so maps don't leak.
- Theme chooser: "All windows"/"This window" scope toggle (default All);
  "This window" routes to the per-window path and records the override on the
  Workspace for persistence.
- Persistence: theme_override TEXT column (migration + schema + model), saved
  via serde_json so ThemeKind::Custom round-trips, re-applied on window restore.

Verification: warp_core, warpui_core and persistence compile cleanly and a new
Appearance unit test covers override resolution + the fast path. The warp app
crate could not be built/tested in this environment (xcrun "metal" toolchain
unavailable; wasm fallback blocked by clang) -- run ./script/presubmit to
verify the app crate, clippy/fmt, and the new test.

(cherry picked from commit 80f5c59)

@zerx-lab zerx-lab left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(详见 inline comments 和下方总结 comment)

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code

let window_id = self.window_id;
AppearanceManager::handle(ctx).update(ctx, |appearance_manager, ctx| match scope {
ThemeScope::AllWindows => appearance_manager.set_transient_theme(kind, ctx),
ThemeScope::ThisWindow => appearance_manager.set_window_theme(window_id, kind, ctx),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug(阻塞):悬停预览在 "This window" 作用域下不可撤销

AllWindows 作用域下,set_transient_theme 是一个临时预览状态,用户按 Escape 或关闭主题选择器时,Workspace 会通过既有逻辑还原 previous_theme

但在 ThisWindow 作用域下,set_window_theme 直接写入 theme_overrides HashMap(永久覆盖),没有对应的"撤销悬停预览"路径。结果是:

  1. 用户切换到 "This window" 作用域
  2. 鼠标悬停在几个主题上(每次 hover 都调用 select_theme_for_previewset_window_theme
  3. 用户按 Escape 取消——previous_theme 的还原逻辑只处理全局 transient 主题,当前窗口的 per-window override 不会被撤销
  4. 最后一个悬停过的主题被永久应用到此窗口,用户没有选择任何主题

建议修复:在 set_remote_markdown_rendered 之前(即进入 hover 循环前)记录当前窗口的 override(或 None),在 ThemeChooserEvent::Close 处理时若用户取消,调用 clear_window_theme(或恢复到 cancel 前的 override)。具体可参考全局 transient 主题的 previous_theme 模式,在 per-window 维度做相同的处理。


Generated by Claude Code

@@ -0,0 +1 @@
ALTER TABLE windows DROP COLUMN theme_override;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: SQLite DROP COLUMN 兼容性

ALTER TABLE ... DROP COLUMN 是 SQLite 3.35.0(2021-03)才支持的特性。如果应用分发版本中附带了更老的 SQLite(macOS 系统库),执行 down.sql migration 回滚时会失败。

如果回滚路径在实际部署中不被使用(或总是用打包的新版 SQLite),可以不管;否则建议改为创建新表 + 复制数据 + 重命名的传统写法,或在注释里说明最低支持的 SQLite 版本。


Generated by Claude Code

Copy link
Copy Markdown
Owner

Review 总结

整体判断:Request Changes。架构设计合理——thread-local ambient render window + RAII guard + per-window HashMap override 的组合清晰且零开销。但有一个用户可见的行为 bug 需要先修复再合并。


阻塞问题

悬停预览在 "This window" 作用域下不可撤销(见 theme_chooser.rs inline comment):

AllWindows 作用域下,select_theme_for_preview 调用 set_transient_theme(临时预览),用户按 Escape 关闭选择器时 Workspace 会还原 previous_theme。但 ThisWindow 作用域下,同一函数调用 set_window_theme(永久写入 theme_overrides),没有对应的 cancel 还原路径。

结果:用户进入 "This window" 作用域、鼠标悬停几个主题后按 Escape,最后一个悬停过的主题被静默地永久应用到该窗口——这不是用户选择的主题。


建议

  • down.sql 的 SQLite 兼容性ALTER TABLE ... DROP COLUMN 需要 SQLite ≥ 3.35.0(2021-03)。如果发布包附带了更老的系统 SQLite,migration 回滚会失败。建议注释说明最低版本要求,或采用传统的新表 + 复制 + 重命名写法(见 down.sql inline comment)。

看起来不错的地方

  • CurrentRenderWindowGuard RAII 设计正确:析构时还原 前一个 ambient 值而非硬置 None,nested render pass 语义安全
  • theme_overrides.is_empty() fast path 保证无 override 时 theme() / ui_builder() 零开销,现有用户无感知
  • on_window_closed 调用 clear_window_theme 防止 override map 随窗口关闭泄漏
  • DB migration + schema + model + persistence 四处联动修改完整,ThemeKind::Custom 通过 serde_json 正确 round-trip
  • 单元测试覆盖了 fast path、override 路径、ambient None 和无 override 窗口的 fallback

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants