# Workspace 输入卡顿修复计划

## Current Design

- Workspace 页面由 `frontend/app/workspace/[id]/page.tsx` 组合 `SourcesPanel`、`ChatPanel`、`StudioPanel`。
- `ChatPanel` 通过 `/api/workspace/{workspace_id}/messages` 读取 `messages_v2`，先取 compact history，再 hydrate parts。
- 用户输入保存在 `ChatPanel` 顶层 `input` state；`textarea onChange` 每次键入都会触发整个 `ChatPanel` render。
- 消息列表、Markdown、tool group、feedback、usage bar 和输入框目前在同一个 React component render 树里。

## Risks

- LCP 正常只能说明初次主内容加载快，不能证明输入交互不卡。
- 如果 workspace 历史消息或 tool parts 很大，每次键入都可能重新执行消息列表 render、Markdown render 和 tool grouping。
- 如果直接隐藏或删掉 parts，会破坏历史诊断证据展示。
- 如果改后端接口作为短期优化，可能改变 `messages_v2` 的 canonical source of truth。

## Target Contract

- `messages_v2` 继续是聊天历史 canonical source of truth。
- 输入框本地编辑只更新输入区状态，不重新渲染已稳定的历史消息列表。
- 读接口保持只读；不在前端输入路径里做后端清理、迁移或 evidence 修复。
- 已完成 assistant/tool 历史仍可按当前规则展示；只把稳定历史列表从每次键入的 render 路径中隔离。
- 不兼容旧 workspace 文件布局；本次只处理当前标准 `messages_v2` / frontend 渲染路径。

## Implementation Steps

1. 查询线上 workspace `ws-1779849323231-f013bf8a9` 的 `messages_v2` 数量、content/parts 大小、状态和最近更新时间。
2. 全局搜索旧兼容、hydrate、fallback、raw/parts payload 路径，确认哪些仍需保留、哪些不应进入输入热路径。
3. 将 `ChatPanel` 的历史消息列表拆成 memoized 子组件，确保 `input`、command palette、attachments 等输入态变化不触发历史消息重渲染。
4. 将每条消息和 tool group 的派生 segment 计算移入 memoized 组件，避免父级 render 时重复做大数组/Markdown 工作。
5. 保持 i18n 约束：新增或改动用户可见文案必须走 `t()`；本次优先不新增文案。

## Verification Checklist

- 线上 DB 查询能说明该 workspace 的真实消息/parts 规模。
- `cd frontend && pnpm tsc --noEmit` 通过。
- 本地用同类大消息 workspace 或构造数据验证：在 textarea 输入时历史消息列表组件不重复 render。
- 手动检查发送消息、slash command、图片附件、反馈 banner、read-only 展示不回归。
