perf(desktop): reduce composer typing work - #1236
Conversation
Signed-off-by: Shao-Wu Hu <271197399+hushaowu-rh@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e16a55083
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR improves desktop ChatInput/composer performance by reducing React re-renders and coalescing layout- and persistence-heavy work during Tiptap editor updates, while keeping user-history identities stable during assistant streaming.
Changes:
- Introduces render gating so ChatInput only re-renders when trigger/palette state or sendable/empty state changes.
- Coalesces caret scrolling to a single cancellable animation frame instead of scheduling many independent frames.
- Debounces draft serialization/writes with explicit flushes at lifecycle boundaries, and stabilizes user history projections across streaming updates.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/desktop/src/renderer/lib/composerDraftSaveScheduler.ts | New debounced scheduler to coalesce draft save work and support flush/cancel. |
| apps/desktop/src/renderer/lib/tests/composerDraftSaveScheduler.test.ts | Unit tests for draft save scheduler behavior (latest-task wins, flush, cancel, defaults). |
| apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts | New snapshot-based gate to decide when editor activity should trigger a React render. |
| apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts | New stable projection to avoid re-allocating user history arrays when only assistant rows change. |
| apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts | New RAF scheduler that coalesces/cancels pending caret-scroll frames. |
| apps/desktop/src/renderer/components/new-chat/ChatInput.tsx | Integrates render gating, coalesced caret scrolling, debounced draft saving + lifecycle flushes, and stable history projection. |
| apps/desktop/src/renderer/components/new-chat/tests/composerRenderGate.test.ts | Unit tests covering the render gate decision logic. |
| apps/desktop/src/renderer/components/new-chat/tests/composerHistoryProjection.test.ts | Unit tests validating stable history identity and correct projection updates. |
| apps/desktop/src/renderer/components/new-chat/tests/composerFrameScheduler.test.ts | Unit tests for the coalesced requestAnimationFrame scheduler. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/components/new-chat/ChatInput.tsx | Integrates gated editor refreshes, coalesced caret scrolling, debounced draft persistence, lifecycle flushes, and stable history projection. |
| apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts | Compares the complete palette-trigger snapshot and empty/non-empty state before requesting a React refresh. |
| apps/desktop/src/renderer/lib/composerDraftSaveScheduler.ts | Implements latest-task draft-save debouncing with synchronous flush and cancellation support. |
| apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts | Coalesces repeated caret-scroll requests and supports teardown cancellation. |
| apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts | Preserves user-history array identity when assistant-only streaming updates do not alter user entries. |
Sequence Diagram
sequenceDiagram
participant User
participant Editor as Tiptap Editor
participant Gate as Render Gate
participant React as ChatInput React Tree
participant Frame as Caret Frame Scheduler
participant Draft as Draft Save Scheduler
participant Store as Draft Store
User->>Editor: Type or move selection
Editor->>Gate: Compare trigger and empty state
alt Relevant snapshot changed
Gate->>React: Refresh composer controls
else Ordinary transaction
Gate-->>React: Skip parent refresh
end
Editor->>Frame: Schedule caret scrolling
Frame->>Frame: Replace pending frame
Frame-->>Editor: Scroll caret on next frame
Editor->>Draft: Schedule latest draft save
Draft->>Draft: Coalesce updates for 120 ms
alt Timer fires
Draft->>Store: Serialize and save latest document
else Blur, send, session switch, or unmount
Draft->>Store: Flush pending save synchronously
end
Reviews (2): Last reviewed commit: "fix(desktop): guard composer draft sessi..." | Re-trigger Greptile
|
@hushaowu-rh 👋 这个 PR 还有 3 条 review conversation 没 resolve(apps/desktop/src/renderer/components/new-chat/ChatInput.tsx / apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts / apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
MagicLizi
left a comment
There was a problem hiding this comment.
格式门未通过
PR description 缺少以下必要段落(参照 .github/PULL_REQUEST_TEMPLATE.md):
- 这次改了什么(What)
- 怎么验证的(Test Plan)
- 风险(Risks)
请补齐上述段落后更新 PR description。
|
本 PR 命中 UI 路径( 此提醒不阻断合并,仅供参考。 |
|
⏸️ 本 PR 触发白名单确认门(product:命中 UI 路径)。 放行名单成员在本 PR 上 Approve 即放行;需修改请 Request Changes。 讨论 issue 见上方链接。 讨论 issue:#1256 |
Signed-off-by: Shao-Wu Hu <271197399+hushaowu-rh@users.noreply.github.com>
|
白名单确认门已放行(product,由 MagicLizi 确认),PR 恢复正常推进。 |
MagicLizi
left a comment
There was a problem hiding this comment.
审查通过,零 P0/P1。性能优化结构清晰:render gate + RAF 合并 + draft 节流 + session 切换守卫,测试覆盖到位。
|
合并了,composer 打字体验显著改善——render gate + draft 节流这套组合拳很漂亮。 |
这次改了什么
摘要
降低 Desktop composer 普通打字期间的 React 重渲染、布局读取、草稿序列化和历史投影开销:普通 Tiptap transaction 留在编辑器内部,仅在 palette trigger 或空/可发送状态变化时刷新 React;caret scroll 合并到一个可取消 RAF;草稿 JSON 序列化与写入 debounce,并在生命周期边界 flush;assistant 流式更新期间复用稳定的用户历史数组。
本轮根据 review 修复了语音输入异步停止/润色期间切换 session 时的草稿串写:延迟任务执行前重新确认其 storage key 仍属于当前 editor,避免把新 session 内容写入旧 session 草稿;同时完成两项已验证的 render gate 与 history projection 清理。
变更类型
feat新功能fix缺陷修复refactor/perf重构或性能优化docs/test/chore文档、测试或工程维护范围
UI 变化
不涉及:修改的是 composer 内部更新调度、持久化时序与对象身份,无布局、样式、交互规则或文案变化,因此无截图/录屏可提供。
怎么验证的
自动验证
手工验证
本轮未进行 production build 的交互 benchmark;无视觉变化需要截图验收。
未执行的验证
pnpm test:unit已尝试,但 Desktop Vitest 在当前 macOS/Node 环境的 threads pool 中发生原生SIGSEGV;forks pool 的诊断运行可继续,仅遇到与本 PR 无关的BillingPage.test.tsx固定日期断言失败。未删除、跳过或弱化测试来制造通过。风险
风险分类
影响与回滚
提交前检查
git commit -s,见 DCO)🤖 Generated with Claude Code