Skip to content

fix: migrate legacy native chat runtime configs to React#267

Open
RachelForster wants to merge 1 commit into
mainfrom
codex/force-react-ui
Open

fix: migrate legacy native chat runtime configs to React#267
RachelForster wants to merge 1 commit into
mainfrom
codex/force-react-ui

Conversation

@RachelForster

@RachelForster RachelForster commented Jul 23, 2026

Copy link
Copy Markdown
Owner

What changed

  • remove the native/React runtime selector and native theme JSON field from Program Settings
  • normalize legacy chat_ui_runtime_mode values to react in the backend config model
  • force the frontend bridge runtime resolver and settings save payload to use React
  • keep React theme and experimental controls available for users with legacy native settings
  • add regression coverage for config migration, settings persistence, snapshots, and launch behavior

Why

Native UI is no longer a supported user-selectable runtime. Existing installations may still persist chat_ui_runtime_mode: native, so hiding the selector alone would leave those users on the old launch path.

Impact

Existing native-mode configurations are migrated in memory and saved back as React the next time Program Settings are saved. Chat launches now always create and attach the React stream session. The legacy native implementation remains in the codebase for internal compatibility, but it is no longer reachable through configuration.

Checks

  • python -m pytest test/unit/frontend_bridge_core/test_chat_init.py test/unit/frontend_bridge_core/test_frontend_chat_launch_paths.py test/unit/frontend_bridge_core/test_chat_runtime_mode.py test/unit/config/test_schema.py -q (48 passed)
  • pnpm test -- src/test/features/system-settings/SystemSettingsPage.test.tsx (8 passed)
  • pnpm lint:types
  • Prettier check for changed frontend files
  • pre-push presubmit: Python 1511 passed, 10 skipped; frontend 717 passed; format and type checks passed

Summary by Ameath

小爱先把这次核对的重点轻轻理顺。

此 PR 将遗留 native 聊天配置统一迁移到 React Chat Stage,并从程序设置中移除了运行时选择器和原生主题 JSON 入口。

Enhancements

  • SystemConfig、桥接启动路径和设置保存均固定使用 React 流式运行时。
  • React 聊天主题与实验性控制项在迁移后的配置中保持可用。

Tests

  • 新增配置迁移、程序设置保存及 legacy native 启动时创建流式会话的回归覆盖。
Original summary in English

This PR migrates legacy native chat configurations to React Chat Stage and removes the runtime selector and native theme JSON entry from Program Settings.

Enhancements

  • SystemConfig, the bridge launch path, and settings persistence now consistently use the React streaming runtime.
  • React chat themes and experimental controls remain available after migration.

Tests

  • Adds regression coverage for configuration migration, settings persistence, and stream-session creation when a legacy native configuration launches chat.

@RachelForster
RachelForster marked this pull request as ready for review July 23, 2026 15:21
@ameath-review

ameath-review Bot commented Jul 23, 2026

Copy link
Copy Markdown

Reviewer's Guide by Ameath

小爱整理了一条便于跟进的核对思路。

运行时决定点位于 frontend_bridge_core/chat.py_chat_runtime_modeSystemConfig 和 Program Settings 会将 legacy 值归一为 React,启动处理器随后创建 stream session 并把 producer endpoint 传给 main.py。重点检查这次迁移后可达的流式边界:项目根外的已保存初始立绘、实时事件和重连快照的命名旁白一致性、active.json/active.json.tmp/branches.json 的联合持久化,以及 Tauri 聊天窗在 bridge 关闭失败时的回退。

文件级变更

Change Details Files
修改文件 8 个文件,+69 / -63 config/schema.py
frontend/src/entities/config/schema.ts
frontend/src/features/system-settings/SystemSettingsPage.tsx
frontend/src/shared/platform/types.ts
frontend/src/test/features/system-settings/SystemSettingsPage.test.tsx
frontend_bridge_core/chat.py
test/unit/config/test_schema.py
test/unit/frontend_bridge_core/test_chat_runtime_mode.py

关联 Issue

  • #259 Force chat launch to use the new React UI:标题语义候选,需审查者确认关联。

Tips and commands
  • 重新审查: @Ame4th review
  • 重新生成摘要: @Ame4th summary
  • 重新生成指南: @Ame4th guide
  • 生成标题: @Ame4th title
  • 解决本机器人线程: @Ame4th resolve
  • 清理本机器人审查: @Ame4th dismiss
  • 从行级评论创建 Issue: 回复 @Ame4th issue
  • 继续讨论: 直接回复任一行级评论。
  • 反馈偏好: 对行级评论点 👍 或 👎。
Original review guide in English

The runtime decision point is _chat_runtime_mode in frontend_bridge_core/chat.py; SystemConfig and Program Settings normalize legacy values to React, after which the launch handler creates a stream session and passes its producer endpoint to main.py. Focus review on stream boundaries newly reachable after migration: saved initial sprites outside the project root, named-narrator consistency between live events and reconnect snapshots, joint persistence of active.json/active.json.tmp/branches.json, and the Tauri chat window fallback when bridge close fails.

@ameath-review ameath-review Bot changed the title fix: force React chat UI fix: migrate legacy native chat runtime configs to React Jul 23, 2026

@ameath-review ameath-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

小爱先从需要留意的细节开始看。

小爱已完成全部变更审查,共发布 4 条行级建议(高风险 0,中风险 3)。

合并前关注点(风险 1 · 建议测试 0)

风险

  • 贡献规范:PR 描述没有明确链接 #259;数据包只将其标为可能关联。
Prompt for AI Agents

请逐项处理以下审查问题;修改前先核实上下文,修改后运行相关测试。

1. 将活动历史与分支树作为同一崩溃一致提交保存 / Commit active history and branch tree atomically

frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>在 fork 或 switch 后,_persist_branch_state() 依次覆盖 active.json、删除 active.json.tmp、再覆盖 branches.json,三者没有同一提交点。若进程在活动历史写入后、分支树写入前崩溃,磁盘历史已属于新分支 B,而树仍将旧分支 A 标为活动;恢复时 reconcile_active_branch_state() 会把 B 的内容写入 A,破坏分支身份而不只是丢失最后消息。开启现有 fork/tree 实验功能的 legacy native 用户会因本行开始可达此路径。应使用共享 generation 的原子 manifest 提交,或在恢复时验证活动历史与分支树身份。

After fork/switch, _persist_branch_state() overwrites active.json, removes active.json.tmp, and only then overwrites branches.json, without a common commit point. A crash between writes leaves B's history while A is still active in the tree; recovery assigns B's contents to A. Legacy native users with the existing fork/tree experimental controls enabled newly reach this path. Commit a shared generation atomically or validate branch identity during recovery.</issue_to_address>

2. 保留项目外自定义初始立绘的加载路径 / Preserve external custom initial sprites

frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>启动器会保留未知的 initialSpritePathinitial_sprite_path_for_characters() 也会将它传入运行时;原生 UIUpdateManager 会直接加载该绝对路径。此处强制 legacy native 配置走流式运行时后,StreamingUIUpdateManager 会把路径转成 /api/media,该端点通过 safe_project_path() 拒绝项目根目录以外的绝对路径。因此此前可在原生窗口显示的 D:/.../home/... 自定义初始立绘会在 React Stage 中加载失败。迁移时应导入此类资产到受控目录,或让流式媒体通道安全地提供已选择的文件。

The launcher retains unknown initialSpritePath values and the native UI loads those absolute paths directly. Forcing a legacy native configuration into streaming turns it into /api/media, which safe_project_path() rejects outside the project root. Existing external custom initial sprites therefore no longer load in React Stage; import them into controlled storage during migration or serve selected files safely.</issue_to_address>

3. 使旁白对话的快照与实时事件一致 / Keep narrator snapshots consistent with live events

frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>SystemMiscUiHandler 会以非空 speaker 和 isSystem=True 发出具名 NARR/旁白;实时 React reducer 将其渲染为具名对话。但 fold_event_into_snapshot() 会对所有系统事件清空 characterNameChatStreamService 将该快照用于重连、轮询恢复及稍后打开的 Chat Stage,因此这些路径会把同一条旁白显示为没有名称栏的对话。此次强制迁移使此前 native 用户首次受该流式快照不一致影响;折叠规则应仅在 speaker 为空时清空名称。

SystemMiscUiHandler emits named NARR/narrator events with a nonempty speaker and isSystem=True, and the live React reducer renders them as named dialogs. However, fold_event_into_snapshot() clears characterName for every system event. Reconnect, polling recovery, and later-opened Chat Stage clients therefore hydrate the same narrator without its name. Preserve nonempty speakers while folding snapshots.</issue_to_address>

4. 关闭 React 聊天窗时不要吞掉运行时关闭失败 / Do not swallow React runtime close failures

frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>Tauri 关闭路径会吞掉 closeRuntime() 的拒绝后继续执行 desktop_chat_window_destroy();该命令仅调用 window.destroy(),并未像 desktop_window_close 一样显式请求 bridge 关闭会话。因此当 /api/chat/close 在请求送达前失败时,聊天窗会消失,但 main.py 子进程和流式会话仍可能运行并阻止下一次启动。本行使 legacy native 配置首次进入这条 React 桌面关闭链路;应在销毁前保留窗口并报告失败,或让 Rust destroy 命令可靠地请求 bridge 关闭。

The Tauri close path swallows a closeRuntime() rejection and still calls desktop_chat_window_destroy(). That command only invokes window.destroy() and does not explicitly request bridge shutdown like desktop_window_close. If /api/chat/close fails before delivery, the window can disappear while the main.py process and stream session remain running and block the next launch. Preserve the window on failure or make the Rust destroy command reliably request bridge shutdown.</issue_to_address>

return "native" if mode == "native" else "react"
def _chat_runtime_mode(_state: BridgeState) -> str:
"""Return the only supported chat UI runtime."""
return "react"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM · correctness] 将活动历史与分支树作为同一崩溃一致提交保存 / Commit active history and branch tree atomically

在 fork 或 switch 后,_persist_branch_state() 依次覆盖 active.json、删除 active.json.tmp、再覆盖 branches.json,三者没有同一提交点。若进程在活动历史写入后、分支树写入前崩溃,磁盘历史已属于新分支 B,而树仍将旧分支 A 标为活动;恢复时 reconcile_active_branch_state() 会把 B 的内容写入 A,破坏分支身份而不只是丢失最后消息。开启现有 fork/tree 实验功能的 legacy native 用户会因本行开始可达此路径。应使用共享 generation 的原子 manifest 提交,或在恢复时验证活动历史与分支树身份。

Original in English

After fork/switch, _persist_branch_state() overwrites active.json, removes active.json.tmp, and only then overwrites branches.json, without a common commit point. A crash between writes leaves B's history while A is still active in the tree; recovery assigns B's contents to A. Legacy native users with the existing fork/tree experimental controls enabled newly reach this path. Commit a shared generation atomically or validate branch identity during recovery.

置信度 97% · 可直接回复讨论 · 👍/👎 会影响后续审查

return "native" if mode == "native" else "react"
def _chat_runtime_mode(_state: BridgeState) -> str:
"""Return the only supported chat UI runtime."""
return "react"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM · correctness] 保留项目外自定义初始立绘的加载路径 / Preserve external custom initial sprites

启动器会保留未知的 initialSpritePathinitial_sprite_path_for_characters() 也会将它传入运行时;原生 UIUpdateManager 会直接加载该绝对路径。此处强制 legacy native 配置走流式运行时后,StreamingUIUpdateManager 会把路径转成 /api/media,该端点通过 safe_project_path() 拒绝项目根目录以外的绝对路径。因此此前可在原生窗口显示的 D:/.../home/... 自定义初始立绘会在 React Stage 中加载失败。迁移时应导入此类资产到受控目录,或让流式媒体通道安全地提供已选择的文件。

Original in English

The launcher retains unknown initialSpritePath values and the native UI loads those absolute paths directly. Forcing a legacy native configuration into streaming turns it into /api/media, which safe_project_path() rejects outside the project root. Existing external custom initial sprites therefore no longer load in React Stage; import them into controlled storage during migration or serve selected files safely.

置信度 96% · 可直接回复讨论 · 👍/👎 会影响后续审查

return "native" if mode == "native" else "react"
def _chat_runtime_mode(_state: BridgeState) -> str:
"""Return the only supported chat UI runtime."""
return "react"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM · correctness] 使旁白对话的快照与实时事件一致 / Keep narrator snapshots consistent with live events

SystemMiscUiHandler 会以非空 speaker 和 isSystem=True 发出具名 NARR/旁白;实时 React reducer 将其渲染为具名对话。但 fold_event_into_snapshot() 会对所有系统事件清空 characterNameChatStreamService 将该快照用于重连、轮询恢复及稍后打开的 Chat Stage,因此这些路径会把同一条旁白显示为没有名称栏的对话。此次强制迁移使此前 native 用户首次受该流式快照不一致影响;折叠规则应仅在 speaker 为空时清空名称。

SystemMiscUiHandler emits named NARR/narrator events with a nonempty speaker and isSystem=True, and the live React reducer renders them as named dialogs. However, fold_event_into_snapshot() clears characterName for every system event. Reconnect, polling recovery, and later-opened Chat Stage clients therefore hydrate the same narrator without its name. Preserve nonempty speakers while folding snapshots.

置信度 95% · 可直接回复讨论 · 👍/👎 会影响后续审查

return "native" if mode == "native" else "react"
def _chat_runtime_mode(_state: BridgeState) -> str:
"""Return the only supported chat UI runtime."""
return "react"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[LOW · correctness] 关闭 React 聊天窗时不要吞掉运行时关闭失败 / Do not swallow React runtime close failures

Tauri 关闭路径会吞掉 closeRuntime() 的拒绝后继续执行 desktop_chat_window_destroy();该命令仅调用 window.destroy(),并未像 desktop_window_close 一样显式请求 bridge 关闭会话。因此当 /api/chat/close 在请求送达前失败时,聊天窗会消失,但 main.py 子进程和流式会话仍可能运行并阻止下一次启动。本行使 legacy native 配置首次进入这条 React 桌面关闭链路;应在销毁前保留窗口并报告失败,或让 Rust destroy 命令可靠地请求 bridge 关闭。

Original in English

The Tauri close path swallows a closeRuntime() rejection and still calls desktop_chat_window_destroy(). That command only invokes window.destroy() and does not explicitly request bridge shutdown like desktop_window_close. If /api/chat/close fails before delivery, the window can disappear while the main.py process and stream session remain running and block the next launch. Preserve the window on failure or make the Rust destroy command reliably request bridge shutdown.

置信度 88% · 可直接回复讨论 · 👍/👎 会影响后续审查

@RachelForster RachelForster added p0 p1 and removed p1 labels Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant