feat(web-shell): add /delete command with batch delete support#4603
Conversation
Add a /delete slash command to the web-shell that allows users to permanently delete session data files. Supports both single-session and multi-select batch deletion with proper error handling. Changes: - Add POST /sessions/delete batch endpoint to daemon server - Add deleteSessionsData() to SDK DaemonClient - Add DeleteSessionDialog with multi-select (Space to toggle, Enter to confirm) and search/filter support - Add deleteSession/deleteSessions workspace actions and hooks - Distinguish errors vs notFound in single-delete action (throw on real errors, return false only for notFound) - Surface failure reasons in batch delete (allFailed / partialFail messages include first error detail) - Normalize Error objects to string messages in server JSON response - Add tests for server route, SDK client, and workspace provider
📋 Review SummaryThis PR implements a comprehensive 🔍 General Feedback
🎯 Specific Feedback🟡 High
🟢 Medium
🔵 Low
✅ Highlights
|
- Pass clientId to deleteSessionsData for ownership validation - Add sessionIds max length (100) and deduplication - Parallelize bridge.closeSession via Promise.allSettled - Add server-side logging for close failures - Reconcile selectedIds with search filter before delete - Prune selectedIds when search query changes - Fix notFound counting: only errors are failures - Fix partial failure double toast: single error message - Fix empty-state: show error message when load fails - Fix hardcoded English "matches" → i18n key - Remove dead targetSession parameter - Align checkbox for current session ([-] instead of spaces) - Add happy path test for batch delete - Reload session list on notFound-only response
…and improve UX - Remove clientId validation from batch delete endpoint since workspace-level access is sufficient authorization. The per-tab clientId check prevented cross-tab deletion of active sessions without real security benefit (user can bypass by resuming the session first). - Wrap filtered sessions list in useMemo to stabilize reference and prevent unnecessary keydown listener teardown/re-register on each render. - Include notFound sessions in onDeleted callback so the toast correctly reports the total count of cleaned-up sessions. Generated with AI
- Add comment documenting intentional no-clientId in batch delete - Log removeSessions filesystem errors to stderr for debuggability - Count notFound as success in deleteSession for proper UI reload - Remove dead if (!deleteSessions) / if (!deleteSession) guards - Fix partial-failure double-wrapped toast message - Reset selectedIdx when exiting search mode via Enter - Add 5 batch tests: mixed outcomes, max-100 cap, non-string validation, dedup, and file preservation on error Generated with AI
Verification Report — PR #4603Reviewer: wenshao Test Results
TypeScript Check
Build
Code Review SummaryServer (
SDK (
UI (
Workspace actions & hooks:
Verdict✅ Ready to merge — All claimed tests pass, no regressions introduced, implementation is well-structured with comprehensive error handling and test coverage. Verified locally by wenshao |
… test - Revert partial-failure handler to use delete.partialFail i18n key through onError only, removing contradictory onDeleted call - Add test for removeSessions unexpected throw (500 catch block)
| const failed = res.errors.length; | ||
|
|
||
| if (failed > 0 && succeeded > 0) { | ||
| onError( |
There was a problem hiding this comment.
[Suggestion] delete.partialFail message is double-wrapped by App.tsx's onError handler, producing a contradictory toast
The onError callback in App.tsx (line 1170-1178) wraps error.message inside t('delete.failed', { reason }):
delete.failed → "Failed to delete session. {reason}"
delete.partialFail → "3 deleted, 2 failed: disk full"
Combined result: "Failed to delete session. 3 deleted, 2 failed: disk full" — the prefix says total failure while the suffix reports partial success. Same in Chinese: "删除会话失败。 已删除 3 个,2 个失败: disk full".
The onDeleted() removal was correct to fix the dual-toast issue, but the remaining single toast is self-contradictory.
Possible fixes:
- Have
App.tsx'sonErrordispatch the rawerror.messagewhen it's already a user-facing i18n string (e.g., check an error code/tag property) - Add a dedicated
onPartialFailurecallback to avoid overloadingonError - Change
delete.failedto not prepend the "Failed to delete session." prefix whenreasonalready contains structured content
— qwen3.7-max via Qwen Code /review
Summary
/deleteslash command to web-shell for permanently deleting session data filesPOST /sessions/deletebatch endpoint to daemon server with proper error handling (only swallowsSessionNotFoundError, surfacesInvalidClientIdErrorand other errors per-session)deleteSessionsData()to SDKDaemonClient, unified single/batch delete to one endpointDeleteSessionDialogwith multi-select support (Space to toggle, Enter to confirm), search/filter, and keyboard navigationfalseonly for notFoundallFailedshows first error reason,partialFailincludes error detailErrorobjects to string messages in server JSON responseTest plan
npx vitest run src/serve/server.test.ts— 345 tests passnpx vitest run test/unit/DaemonClient.test.ts— 137 tests passnpx vitest run src/daemon/workspace/DaemonWorkspaceProvider.test.tsx— 9 tests passnpx tsc --noEmit— clean🤖 Generated with Qwen Code