Skip to content

feat: scope AI draft and session resume state#712

Merged
binaricat merged 3 commits intobinaricat:mainfrom
tces1:dev
Apr 14, 2026
Merged

feat: scope AI draft and session resume state#712
binaricat merged 3 commits intobinaricat:mainfrom
tces1:dev

Conversation

@tces1
Copy link
Copy Markdown
Contributor

@tces1 tces1 commented Apr 14, 2026

Summary

This PR scopes AI chat draft, panel view, and active session state by terminal/workspace target instead of sharing a single global view.

That makes AI chat behavior more predictable when switching terminals or workspaces:

  • unsent drafts stay with their own scope
  • selected history/session view is restored correctly after reconnect and cold mount
  • implicit history fallback no longer overrides an existing draft
  • closed scopes are cleaned up without redundant active-session map rewrites

Changes

  • add scoped AI draft state helpers and tests
  • add scoped AI cleanup helpers and tests for terminal/workspace lifecycle
  • update useAIState to persist and restore active AI session selection per scope
  • update AI panel view resolution to prefer draft state over implicit history fallback
  • restore persisted active session on reconnect and cold mount
  • keep scoped transient state in sync when terminals/workspaces close
  • tighten related panel/session wiring in AIChatSidePanel and TerminalLayer

Validation

  • npm run lint -- --quiet
  • npm run build
  • node --test application/state/aiDraftState.test.ts application/state/aiScopeCleanup.test.ts components/ai/aiPanelViewState.test.ts components/ai/userSkillsState.test.ts

- persist drafts, panel views, and active sessions per terminal/workspace scope
- restore scoped AI session selection on reconnect and cold mount
- prefer unsent drafts over implicit history fallback
- avoid redundant active session map rewrites during scoped cleanup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75dc3dd72b

ℹ️ 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".

Comment thread application/state/aiScopeCleanup.ts Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@binaricat
Copy link
Copy Markdown
Owner

Eric 辛苦了,这个改动整体看下来挺扎实的,把 draft / panelView 按 scope 隔离这个思路解了好几个之前的体验问题,纯函数那几个模块的测试也写得非常细致,后面的 c5d15a1 那一手 churn 修复也很到位。

本地仔细看了一下,有两处小地方想和你讨论下,觉得合并前稍微收一下可能会更稳,麻烦你看看:

1. useAIState.ts 里几处 setter 的 updater 内带了副作用

showDraftView (968-988) 和 clearDraftForScope (994-1012) 都是在 setActiveSessionIdMapRaw((prev) => { ... }) 的 callback 里顺手调用了 setPanelViewByScopeRaw / emitAIStateChanged / localStorageAdapter.write。StrictMode 会刻意把 updater 跑两次来检测副作用,concurrent 渲染下理论上也可能重放,担心会出现事件重复派发、localStorage 重复写这种情况。

如果方便的话,或许可以把副作用挪到 updater 返回之后再执行,或者让 setPanelViewByScopeRaw 也用 functional form 去读最新值,这样语义上会更干净一些。

2. ensureDraftForScope (904-921) 的 next 是基于快照算出来的,再传给 setDraftsByScopeRaw(prev => next)

这种写法下,如果恰好有另一个 scope 的并发编辑,next 里不会包含那份变更,理论上会被覆盖掉。虽然这个函数实际上只会作用于当前 scope,撞上的概率很低,但和文件里其它 setter 的规范略有出入,改成在 updater 内部用 prev 重新计算的话,会和周围风格更一致一些,供参考。


另外在翻代码的时候顺手注意到,`application/state/useFileUpload.ts` 里那个 `useFileUpload` hook 重构之后好像已经没有调用点了(只剩 `convertFilesToUploads` 和 `UploadedFile` 类型再导出还在用)。不知道你是有意保留还是漏删的,如果没别的用途,顺手清掉会清爽一些;`ChatInput.tsx:14` 那处 `UploadedFile` 的 import 也可以直接指向 `infrastructure/ai/types`,少一层中转。

剩下几处都是小细节,不阻塞这个 PR,只是记录一下,有空再看:

  • `draftsByScope` / `panelViewByScope` 是纯内存的,重启后会丢失,如果这是有意的设计,补一行简短注释说明下,后面维护的同学会少走点弯路
  • `validTerminalTabIds` 这个变量名实际上也包含了 workspace id,读代码的时候容易以为只有 terminal
  • `handleSend` 在双击场景下没出问题,其实是靠 `!trimmed` 这条分支兜住的,而不是 `isStreaming`,以后动这块逻辑得稍微留意一下

以上只是一些观察,最终怎么取舍听你的。辛苦了 🙏

@tces1
Copy link
Copy Markdown
Contributor Author

tces1 commented Apr 14, 2026

OK. 我这边在修复看看

@tces1
Copy link
Copy Markdown
Contributor Author

tces1 commented Apr 14, 2026

已修复,你那边用了什么hardness来让他不停的检查吗?agent查几轮就偷懒了

@binaricat
Copy link
Copy Markdown
Owner

已修复,你那边用了什么hardness来让他不停的检查吗?agent查几轮就偷懒了

这次这个PR我用claude + superpower 简单的review一下。此前我是让claude循环调用codex的review来找问题,不过这次PR比较大,对于500+ 行数变更的PR,如果用codex不停的review 可能会一直在边缘的case打转,而且很费token。。codex单次review的时间也很长。

所以等会我再简单查一下,如果没大问题就先合并了,小问题不严重的 日后发现了再慢慢修复。

@tces1
Copy link
Copy Markdown
Contributor Author

tces1 commented Apr 14, 2026

我这边也用了superpowers,但是在迭代review方面似乎也不太理想,还得手动换新的session来review会检查出问题

@binaricat binaricat merged commit 717d8b7 into binaricat:main Apr 14, 2026
@tces1 tces1 deleted the dev branch April 14, 2026 08:22
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