feat: the delete toast names its blast radius (goal 0103) - #225
Merged
Conversation
… 0103) TombstoneResult extends additively with LinksRemoved/ChildrenPromoted counts (computed against pre-delete state), so the undo toast can name what a delete touches -- links hidden, children moved up -- instead of letting them vanish silently into the undo window. Undo already restored both (goal 0093); this only adds visibility. No new confirmation dialog: with single-level undo already in place, the count-in-toast is the informing layer, matching the canvas-tool convergence (undo present -> no pre-delete confirm on object deletes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd
alicoding
enabled auto-merge (squash)
August 18, 2026 04:23
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd
alicoding
added a commit
that referenced
this pull request
Aug 18, 2026
… fixture The spec landed on main (PR #225) after this branch's e2e migration pass, so the rebase brought it in still importing openViaFlip and matching the old 'Flip <title>' aria-label; module-load failure took down all four CI shards. Now uses the shared noteCard/openCard fixtures (the local helper duplicated what atlasBoard.ts already exports). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd
alicoding
added a commit
that referenced
this pull request
Aug 18, 2026
…ent, keyboard nav (goals 0102/0104/0106 slice A) (#227) * feat: Atlas interaction overhaul slice A -- click model, flip retirement, keyboard nav (goal 0102/0104/0106 slice A) Retires the flip gesture across every node type (note card, region frame, region chip): plain click now selects/replaces per the researched gesture table, a click on the already-selected node commits (leaf opens its page, a place zooms), shift-click toggles, cmd-click commits instantly, and a real double-click reproduces the same outcome via two plain clicks -- no dedicated dblclick handler needed. The retired back face's typed-link slot-drag relocates onto the note card's own hover-revealed right-edge link handle, dragging the board's default (first-declared) link kind; kind changed afterward via the existing edge menu. Keyboard: arrows nudge the selection (1px, 10px with Shift) and persist via a keyup-batched SetPosition/SetNotePosition, or pan the camera with no selection; Tab/Shift+Tab walk top-level nodes in reading order; Option+Arrow jumps to the nearest node in a direction (atlasKeyboardNavGeometry.ts, unit-tested); Enter commits the sole selected card. Escape's ladder (clear-selection, then go up one level) now shares atlasFocusContainment.ts's own guard with the keyboard-nav hook, so it never fires on top of a portaled Dialog or an editable field that's already consuming the same keypress -- regression: an Escape meant to close the Lens dialog was also drilling the board up a level. Fixes a real click-model bug surfaced by the new "already-selected commits" rule: releasing a slot-drag back onto its own origin card synthesizes a native click on their common DOM ancestor (the card itself), which would otherwise reopen its page uninvited -- a one-shot capture-phase listener swallows exactly the click that follows a drag's release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd * test: migrate atlas e2e suite off the retired flip gesture (goal 0106 contract item 6) Every flip-dependent helper/assertion across the atlas e2e suite migrates to the click-select-then-commit model: fixtures/atlasBoard. ts's openViaFlip becomes openCard (plays both clicks, retrying past the same micro-drag race the old flip click already guarded against) plus a new closeCard helper that waits for Primer's own Dialog backdrop to fully clear, not just its content -- a tight open/close/ interact cycle can outrun that animation otherwise. Every `aria-label="Flip …"` locator becomes `"Open …"`; every data-flipped/atlas-*-card-back assertion is replaced with a selection-wrapper check or a page-content check, since that content now lives on the card's own page exclusively. Reaching a place's own page (previously a body-click flip then Open) is now a ⌘-click on the frame body, the pointer twin of ⌘↵. New coverage per the gesture/key tables: click-replaces-selection, click-an-already-selected-node-commits, a real double-click producing the identical outcome, ⌘-click's instant commit, and click-empty deselect (atlas-gestures.spec.ts); the hover link-handle's slot-drag relocation (atlas-slots.spec.ts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd * test: quarantine atlas-folder-import.spec.ts's own contention-only flake "Add 1 cards" times out only under 4-worker parallel local load (own dedicated server, so not a cross-spec data race) -- passes clean in isolation and on retry every time observed, same signature layout.spec.ts:163 already carries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd * fix: Escape's clear-selection rung raced React Flow's own per-node Escape/Enter/Arrow handling React Flow ships its own per-node keyboard accessibility (Escape unselects the FOCUSED node, Enter/Space toggles it, arrow keys nudge it) as a second, uncoordinated keyboard system layered on the exact same DOM nodes this board's own hooks already own. Reproduced live: selecting a card then pressing Escape once both cleared the selection AND climbed a level in the same press, because React Flow's own unselect ran first (attached directly to the focused node) and had already cleared the selection by the time this board's own Escape ladder read it -- true whether that read came from the selectedCards state mirror or a live DOM query, since React Flow's own handling had already mutated both. `disableKeyboardA11y` on the board's `<ReactFlow>` makes this board's own hooks (useAtlasKeyboardNav, useAtlasSelectionTray's Escape ladder, each card's own onKeyDown) the sole keyboard authority, matching the goal 0102/0104 gesture and key tables exactly rather than a blend of two systems. Also swaps useAtlasKeyboardNav's own Tab/Enter/Arrow reads and the Escape ladder's own clear-selection check from the selectedIDsRef/ selectedCards state mirror to a direct DOM read (atlasFocusContainment.ts's new readSelectedNodeIDs) -- that mirror updates off React Flow's own onSelectionChange callback, a render behind the `.selected` class itself, a gap a keydown handler firing right after the click that just selected something can still hit even with the accessibility layer disabled. New coverage: atlas-gestures.spec.ts's Escape ladder test pins the two-rung sequence (clear, then up-a-level) that the race broke. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd * fix: plain-click selection ring invisible -- Primer's [role="button"] focus reset beat the inner-element box-shadow Live-reproduced, screenshot-confirmed: a plain click on a single card selected it with zero visible change. Primer's global focus reset, [role="button"]:focus:not(:focus-visible):not(.focus-visible) { box-shadow: none } (specificity 0,4,0), beats every ring rule of the shape :global(.react-flow__node.selected) .card (0,3,0) whenever the clicked node's inner div is :focus without :focus-visible -- exactly the state every mouse click leaves it in. The ring only ever survived on non-focused members of a multi-selection and under keyboard :focus-visible, which is why the prior computed-style e2e assertions passed while the live gesture was broken. role="button"/tabIndex pre-existed on main, so the collision itself is pre-existing, but the new click model makes plain-click-select the PRIMARY gesture, so it cannot ship this way. Moves the ring from the inner element to the React Flow node WRAPPER for all four node types, keyed off the type-scoped class React Flow already stamps per nodeTypes key (.react-flow__node-<type>.selected) -- the wrapper carries no role, so Primer's reset structurally cannot match it, rather than out-specificity-ing a reset that could always be bumped again. The matching border-radius moves with it so the ring follows each shape. The sticky's border-color flip stays on the inner element -- Primer's reset only touches box-shadow, so that half already survived. New e2e coverage pins the exact broken state: a plain click on a single card, then the wrapper's own computed box-shadow while that card still holds DOM focus (confirmed failing against the pre-fix selector, passing after). Existing multi-select ring assertions (atlas-select-group.spec.ts) now measure the wrapper instead of the inner card, for the same reason. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd * test: migrate atlas-delete-relationships.spec.ts off the retired flip fixture The spec landed on main (PR #225) after this branch's e2e migration pass, so the rebase brought it in still importing openViaFlip and matching the old 'Flip <title>' aria-label; module-load failure took down all four CI shards. Now uses the shared noteCard/openCard fixtures (the local helper duplicated what atlasBoard.ts already exports). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd --------- Co-authored-by: Claude Fable 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.
Deleting a card with relationships now says so: "Deleted 2 · 3 links hidden · 1 moved up" — links are 'hidden' (undo restores them, 0093-proven), children 'moved up' (the virtual promotion the user sees). Counts computed against pre-delete state on TombstoneResult (additive), aggregated across multi-select batches.
Friction verdict recorded: with single-level undo standing, the canvas convergence is confirm-free deletes — the count-in-toast is the informing layer; no new dialogs.
Proofs: 3 Go cases (leaf-with-links / container / mixed batch), e2e round-trip on the seeded linked card (toast text → undo → edge back). New spec file keeps atlas.spec under the LOC cap and off the interaction-overhaul's collision surface.
Goal: docs/goals/0103-relationship-aware-delete.md.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FW5GkkAG8du7tNdYLk2zSd