feat(editor-shell): confirm before deleting a file from the tree - #115
Merged
Conversation
The trash control sat 8px from Rename on a 24px row that only reveals its actions on hover, and fired `onDeleteFile` straight through. There is no undo: the file leaves the workspace immediately, so a mis-click was unrecoverable from inside the app. `FileTree` now holds `pendingDelete` next to its existing `adding`/`renaming` state and puts up the `Dialog` primitive from the same package, so `Sidebar`'s `onDeleteFile` plumbing is unchanged. Deliberately unlike the demo-delete confirm in `MyDemos`: no busy state, no disabled buttons, no in-flight guard on `onClose`. That one revokes a share link over the network; this one is a synchronous in-memory splice of the workspace, and nothing persists until save or fork. The copy says that instead of borrowing "this can't be undone". Three details that are load-bearing rather than cosmetic: - The confirm button is named "Delete file", not "Delete". A row's trash control is already named "Delete" by its `title`, and Playwright matches accessible names by substring, so two identically-named buttons make every unscoped locator ambiguous while the dialog is open. - Cancel carries `data-autofocus`. Without it focus lands on the first content control, which is the destructive one, and Space or Enter would carry out the delete the dialog exists to ask about (see `Dialog`'s own note). - The buttons outline with `controlBorder`, not `border`. Dark's `border` is #222222, the same value as the `surfaceRaised` dialog card, so that outline would have no visible edge — the same trap as d1cfb17 on the top bar. The dialog renders outside the `!collapsed` branch so collapsing the section cannot strand an open modal, and `pendingDelete` is cleared when its path leaves `paths` — a path that left and came back would otherwise re-open the dialog unprompted, asking about a delete nobody started. Verified the overlay escapes the 240px sidebar track it is nested in, since `toBeVisible()` would pass on a card clipped into that column just as well: a throwaway probe measured it at 0,0 1280x720 against a 1280x720 viewport, with the 360px card centred and `elementFromPoint` landing inside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Deleting a file from the sidebar tree now asks first.
Why
The trash control sat 8px from Rename on a 24px row that only reveals its actions on hover, and fired
onDeleteFilestraight through. There is no undo — the file leaves the workspace immediately — so a mis-click was unrecoverable from inside the app.How
FileTreeholdspendingDeletenext to its existingadding/renamingstate and puts up theDialogprimitive from the same package, soSidebar'sonDeleteFileplumbing is untouched.Deliberately unlike the demo-delete confirm in
MyDemos: no busy state, no disabled buttons, no in-flight guard ononClose. That one revokes a share link over the network; this one is a synchronous in-memory splice of the workspace, and nothing persists until save or fork. The copy says that rather than borrowing "this can't be undone".Three details that are load-bearing rather than cosmetic:
title, and Playwright matches accessible names by substring — two identically-named buttons make every unscoped locator ambiguous while the dialog is open.data-autofocus. Without it focus lands on the first content control, which is the destructive one, and Space or Enter would carry out the delete the dialog exists to ask about (seeDialog's own note on the hatch).controlBorder, notborder. Dark'sborderis#222222, the same value as thesurfaceRaiseddialog card, so that outline would have no visible edge — the same trap as d1cfb17 on the top bar.Two staleness guards: the dialog renders outside the
!collapsedbranch so collapsing the section cannot strand an open modal, andpendingDeleteis cleared when its path leavespaths. A path that left and came back — example switched away and back, or the same name re-added — would otherwise re-open the dialog unprompted, asking about a delete nobody started.Testing
Full deterministic e2e suite passes (52 tests, 0 failures; baseline on the branch point is 51/0).
packages/editor-shelltypechecks clean.sidebar-crud.spec.tsandeditor-tabs.spec.tsroute their existing delete click-throughs via the dialog, and each asserts the row survives the click that only asks.sidebar-crud.spec.ts: Cancel is focused on open, Space on it is harmless, Escape also dismisses, and the file survives both.Verified separately that the overlay escapes the 240px sidebar track it is nested inside, since
toBeVisible()would pass on a card clipped into that column just as well. A throwaway probe measured it at0,0 1280×720against a1280×720viewport, with the 360px card centred at x=460 andelementFromPointlanding inside it. No portal needed; the probe is not part of the diff.Notes
apps/authoringtypecheck reports two pre-existing errors onDemoRuntime.reload(App.tsx:1277,1280). Confirmed present on the branch point, untouched here.MyDemos.tsx:775has the same invisible-outline bug on its Cancel button (borderon asurfaceRaisedcard). Left alone to keep this diff to the ask — worth a one-token follow-up.🤖 Generated with Claude Code
Note
Low Risk
UI-only guard around existing synchronous delete; no auth, persistence, or API changes.
Overview
Sidebar file delete is no longer immediate: trash opens a Delete this file? dialog, and
onDeleteFileruns only after Delete file. Copy clarifies the change is in-memory until save/fork.FileTreetrackspendingDelete, clears it when the path leaves the workspace or the section collapses (dialog renders outside the collapsed branch), and uses Cancel withdata-autofocusso keyboard focus does not land on the destructive control. The confirm button is labelled Delete file to avoid clashing with row trash Delete in tests and accessibility.E2E flows confirm through the dialog; a new test covers Cancel focus, Space, and Escape without deleting.
Reviewed by Cursor Bugbot for commit 308f166. Bugbot is set up for automated code reviews on this repo. Configure here.