Skip to content

feat(editor-shell): confirm before deleting a file from the tree - #115

Merged
demtario merged 1 commit into
feat/DEV-2027-redesignfrom
feat/file-delete-confirm
Aug 5, 2026
Merged

feat(editor-shell): confirm before deleting a file from the tree#115
demtario merged 1 commit into
feat/DEV-2027-redesignfrom
feat/file-delete-confirm

Conversation

@demtario

@demtario demtario commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

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 onDeleteFile straight through. There is no undo — the file leaves the workspace immediately — so a mis-click was unrecoverable from inside the app.

How

FileTree 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 untouched.

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 rather than 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 — 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 on the hatch).
  • 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.

Two staleness guards: 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 — 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-shell typechecks clean.

  • sidebar-crud.spec.ts and editor-tabs.spec.ts route their existing delete click-throughs via the dialog, and each asserts the row survives the click that only asks.
  • New case in 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 at 0,0 1280×720 against a 1280×720 viewport, with the 360px card centred at x=460 and elementFromPoint landing inside it. No portal needed; the probe is not part of the diff.

Notes

apps/authoring typecheck reports two pre-existing errors on DemoRuntime.reload (App.tsx:1277,1280). Confirmed present on the branch point, untouched here.

MyDemos.tsx:775 has the same invisible-outline bug on its Cancel button (border on a surfaceRaised card). 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 onDeleteFile runs only after Delete file. Copy clarifies the change is in-memory until save/fork.

FileTree tracks pendingDelete, clears it when the path leaves the workspace or the section collapses (dialog renders outside the collapsed branch), and uses Cancel with data-autofocus so 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.

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>
@demtario demtario self-assigned this Aug 5, 2026
@demtario
demtario merged commit 7ca9279 into feat/DEV-2027-redesign Aug 5, 2026
3 checks passed
@demtario
demtario deleted the feat/file-delete-confirm branch August 5, 2026 07:17
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.

1 participant