Skip to content

feat: the delete toast names its blast radius (goal 0103) - #225

Merged
alicoding merged 2 commits into
mainfrom
relationship-delete
Aug 18, 2026
Merged

feat: the delete toast names its blast radius (goal 0103)#225
alicoding merged 2 commits into
mainfrom
relationship-delete

Conversation

@alicoding

Copy link
Copy Markdown
Owner

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

… 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
alicoding enabled auto-merge (squash) August 18, 2026 04:23
@alicoding
alicoding merged commit ca14acb into main Aug 18, 2026
26 checks passed
@alicoding
alicoding deleted the relationship-delete branch August 18, 2026 04:44
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>
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