Repository navigation
feat: delete projects and bulk-delete a project's sessions - #59580
xu-xiaofei wants to merge 2 commits into
Conversation
- schema 新增 project.removed ephemeral 事件并入 manifest - protocol 新增 DELETE /api/project/:projectID(NoContent + ProjectNotFoundError) - core Project.remove:拒删 global、FK 级联删除 worktree 与全部会话、 分批清理会话事件流(event_sequence 级联 event)、回收孤儿 blob、广播事件 - server handler 先经 session.removeProject 优雅中断并清理运行中会话再删行 - client 重新生成(bun run generate),data.ts 消费 project.removed 即时移除条目, app sync 同步镜像 - Settings 项目菜单与 Home 项目卡片菜单新增"删除项目"入口(critical 样式 + 二次确认对话框 + 失败 toast),覆盖目录已删除/已迁移项目的清理场景 - core 测试:级联删除、事件流与 blob 清理、其他项目隔离、global/404 拒绝
- protocol 新增 POST /api/session/remove-project(按 projectID 清空其全部会话)
- core Session.removeProject 复用单会话删除路径(interrupt → 递归级联 →
事件流清理),父会话先行删除后的子条目 NotFound 属正常级联并精确兜底
- server handler 挂载该端点
- Settings 项目菜单新增"删除历史…"入口(保留项目条目,二次确认)
- Home 列表归档动作改调真实 session.update({archived:true}),移除 reject stub
- core/client 契约测试同步更新
There was a problem hiding this comment.
The new endpoints work as described against a source-built server: POST /api/session/remove-project clears a project's sessions and keeps the project, and DELETE /api/project/:projectID removes the project and its sessions (204, and it no longer appears with archived=true). On the base the route returns 404. The changed core, schema and client tests pass, and tsc is clean for every changed package. I didn't drive the app UI; a short recording or screenshots of the Home and Settings delete flows would help.
Public API: all additions, all backwards compatible:
DELETE /api/project/:projectID(project.remove, 204 orProjectNotFoundError)POST /api/session/remove-project(session.removeProject, payload{ projectID }, 204)- a new
project.removedevent{ projectID } - the matching
project.removeandsession.removeProjectmethods in the generated promise and effect clients
Concerns, most important first (details inline):
- Global project: the delete handler removes the sessions before core refuses the global project, so
DELETE /api/project/globalwipes all global sessions and still returns 404. - Worktrees: deleting a project silently drops its worktree rows, leaving OpenCode-created git worktrees orphaned on disk.
- Settings: after a delete from Settings, the page stays on the deleted project and the project isn't hidden from the app's saved project list, which is part of what #51197 reports.
session.removeProjectdeclaresProjectNotFoundErrorbut never raises it: unknown IDs return 204.
| .handle("project.remove", (ctx) => | ||
| Effect.gen(function* () { | ||
| // Graceful session teardown first: interrupt, release blobs, clear each event stream. | ||
| yield* session.removeProject(ctx.params.projectID) |
There was a problem hiding this comment.
session.removeProject runs before project.remove checks the ID, and the global-project refusal lives only in core. So DELETE /api/project/global deletes every global session (e.g. ones imported from V1) and then returns 404. Could you check that the project exists and isn't global before removing sessions, ideally inside Project.remove or a single core operation so this ordering can't drift again?
| ) | ||
| ).map((session) => session.id) | ||
| // The FK cascades take the sessions, their messages, inbox rows, instructions and blob refs with the row. | ||
| yield* db.delete(ProjectTable).where(eq(ProjectTable.id, projectID)).run().pipe(Effect.orDie) |
There was a problem hiding this comment.
Deleting the project row cascades WorktreeTable too, but nothing goes through Worktree.remove. Git worktrees and sandboxes OpenCode created for this project stay on disk and in the repo's git worktree list, and nothing in OpenCode can remove them afterwards. Should these be removed first, or the user told they'll be kept? Also, the row delete, event-stream cleanup and blob sweep aren't in one transaction, so a failure part-way leaves partial state.
|
|
||
| try { | ||
| await context().sdk.api.project.remove({ projectID: id }) | ||
| props.onRemoved?.() |
There was a problem hiding this comment.
Neither ProjectOptions call site (project.tsx, project-row.tsx) passes onRemoved. After deleting from Settings, the page stays on the deleted project, and unlike the Home path this doesn't call projects.hide, so the project can stay in the app's saved project list (part of what #51197 reports).
Issue for this PR
Closes #51197
Related to #41068
Type of change
What does this PR do?
A project whose folder was deleted or moved stays in the list forever: there is no server API to remove it and no UI entry. This adds
DELETE /api/project/:projectIDend to end (aproject.removedevent, core cascade delete that clears the sessions' event streams and orphaned blobs, a handler, and the generated client), plus delete entries in Settings > Projects and the Home project menu, each behind a confirm dialog.It also adds
POST /api/session/remove-projectto delete every session of a project while keeping the project itself ("Delete history" in the Settings menu), reusing the existing per-session removal path, and wires the Home archive action to the realsession.update({ archived })API instead of the rejecting stub.How did you verify your code works?
New core tests cover the cascade (sessions, event streams, blobs), global-project refusal, parent/child recursion, and other-project isolation.
bun run checkpasses; the core (36), client (234), schema (89) and app (900) suites pass. Client changes come frombun run generate.Visual
Screenshots / recordings
None; the SVG above sketches the delete flow.
Checklist
Added by the opencode bot (AI-generated).
Fixes #37280
Related: #41068
Public API: adds DELETE /api/project/:projectID, POST /api/session/remove-project and the project.removed event, with matching client methods (backwards compatible)