feat(react-grab): toolbar-anchored hierarchy tree + sibling navigation#515
Conversation
Replace the flat z-stack list in the arrow-navigation dropdown with a terminal-style tree showing the selected element's ancestor spine, its siblings, and its direct children. Every row is clickable to select that element. Keyboard arrow navigation is unchanged; the dropdown now re-centers the tree on the current selection. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
Move the element-hierarchy tree out of the selection label and render it as a toolbar-anchored dropdown (new HierarchyMenu component) using the existing anchored-dropdown positioning stack shared with the toolbar menu and edit panel. The selection label is back to a compact badge; the tree follows the toolbar via the rAF anchor tracker and is dismissed by the existing clear-arrow-navigation paths. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
With the hierarchy dropdown moved to the toolbar, moving the mouse toward a right-click target now triggers the keyboard-selection discard prompt, whose overlay sits under the cursor. Clear the pending selection on contextmenu and resolve the page element beneath the overlay so the context menu opens for the right-clicked element. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
ArrowLeft/ArrowRight now walk strictly to the previous/next grabbable DOM sibling instead of climbing to the parent or descending into children (parent/child traversal stays on ArrowUp/ArrowDown). The hierarchy dropdown stays open and re-centers on the new sibling. Updates the keyboard-navigation specs and the eval description to the new semantics. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…to preview) New activationMode "preview": a quick tap of the activation key (released before keyHoldDuration) toggles a persistent session on/off, while holding the key previews the overlay (active while held, hidden on release). The hold timer drives the preview; a pre-timer release is treated as a tap. Pairs well with activationKey: "Space". Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
4 issues found across 14 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/types.ts">
<violation number="1" location="packages/react-grab/src/types.ts:138">
P2: Update script option parsing to accept `activationMode: "preview"`; otherwise embed/JSON configuration cannot enable the new mode and silently falls back to toggle.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // "hold": overlay is active only while the activation key is held. | ||
| // "preview": a quick tap toggles persistent activation; holding the key | ||
| // previews the overlay (active while held, hidden on release). | ||
| export type ActivationMode = "toggle" | "hold" | "preview"; |
There was a problem hiding this comment.
P2: Update script option parsing to accept activationMode: "preview"; otherwise embed/JSON configuration cannot enable the new mode and silently falls back to toggle.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/types.ts, line 138:
<comment>Update script option parsing to accept `activationMode: "preview"`; otherwise embed/JSON configuration cannot enable the new mode and silently falls back to toggle.</comment>
<file context>
@@ -131,7 +131,11 @@ export interface AgentContext<T = unknown> {
+// "hold": overlay is active only while the activation key is held.
+// "preview": a quick tap toggles persistent activation; holding the key
+// previews the overlay (active while held, hidden on release).
+export type ActivationMode = "toggle" | "hold" | "preview";
export type OverlayDismissSource = "keyboard" | "pointer";
</file context>
…olicy Code-quality pass: - Extract the duplicated toolbar-anchored dropdown chrome (fixed container, mount/measure lifecycle, spring animation, pointer-event suppression, optional outside-dismiss) into a shared AnchoredDropdownSurface, and route the toolbar menu and hierarchy menu through it. - Replace the scattered activationMode === "hold"/"preview" checks in the activation handlers with a derived ActivationPolicy (keepsOverlayWhileHeld, persistsCompletedHold, tapTogglesSession, suppressActivationKeyDefault). Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- get-script-options: accept activationMode "preview" (was silently dropped to toggle for embed/JSON config). - arrow-navigation: clear vertical nav history when a horizontal sibling move succeeds, so the next ArrowDown does not retrace the previous branch. - build-element-hierarchy: bound traversal via a shared early-stopping collector (ancestors/siblings/children) instead of filtering whole child collections; keeps a centered sibling window. - contextmenu handler: drop the pending-dismiss pre-clear; rely on the existing overlay branch + openContextMenu so nothing is torn down when a right-click resolves no target (Bugbot). Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/types.ts">
<violation number="1" location="packages/react-grab/src/types.ts:138">
P2: Update script option parsing to accept `activationMode: "preview"`; otherwise embed/JSON configuration cannot enable the new mode and silently falls back to toggle.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…dropdown-e60f # Conflicts: # packages/react-grab/src/core/keyboard-selection.ts Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fa4aa6a. Configure here.
- Shift-hover now reveals the element under the cursor: prefer the freshly hovered element over a lingering frozenElement during the hovering phase (cancelDrag / finishJustDragged return to hovering without clearing it), while still showing the frozen selection during keyboard-nav freeze. - tryHandleNavigationKey now bails on events from editable controls, so Tab / Shift+Tab / arrows no longer hijack native editing or focus traversal when a page input or textarea is focused. Flagged by cubic, Bugbot, and Vercel. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/types.ts">
<violation number="1" location="packages/react-grab/src/types.ts:138">
P2: Update script option parsing to accept `activationMode: "preview"`; otherwise embed/JSON configuration cannot enable the new mode and silently falls back to toggle.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The input guard ran inside tryHandleNavigationKey, after the active-mode event.preventDefault(), so native caret movement and focus traversal were still suppressed in focused page inputs. Move the check ahead of that preventDefault so navigation keys from editable controls pass through untouched. Flagged by cubic. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/types.ts">
<violation number="1" location="packages/react-grab/src/types.ts:138">
P2: Update script option parsing to accept `activationMode: "preview"`; otherwise embed/JSON configuration cannot enable the new mode and silently falls back to toggle.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…y too The discard-prompt pass-through and overlay paths call tryHandleNavigationKey before the main keydown's editable-control guard, so arrows could still hijack a focused page input while the discard prompt was visible. Restore the input guard inside the shared helper so every caller is protected, keeping the main-path guard for the preventDefault ordering. Flagged by cubic. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… default action When the keyboard-selection discard prompt was open, Enter only confirmed the discard if its Yes/Copy button had DOM focus; otherwise the window keydown ran a bare-key shortcut first and dropped into the default action (e.g. Comment). The Yes button shows the return-key affordance, so hand every Enter to the prompt's confirmation handler — it discards (or copies when Copy is focused) and returns to selection. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Add regression tests for the discard-prompt Enter behavior: Enter with no button focus discards and resumes selection (no comment, no copy) even when a comment action is bound to Enter, and Enter with the Copy button focused still copies. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…dropdown-e60f Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Collapse the duplicated isKeyboardEventTriggeredByInput checks (one inside tryHandleNavigationKey, one before the active-mode preventDefault) into a single guard at the top of the keydown handler. All navigation-key paths (discard-prompt pass-through, overlay branch, main path) flow through that one listener, so the single guard dominates them and precedes preventDefault. Also document the listener-ordering invariant behind the discard-prompt Enter hand-off. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

What
Adds a small, elegant terminal-style tree view of the selected element's place in the DOM, rendered as a dropdown anchored to the toolbar that appears while keyboard-navigating a selection.
The dropdown shows:
Rows are indented by depth with
├─/└─connectors, reusing the sharedMenu.*primitives, design tokens, and anchored-dropdown positioning stack.Behavior
pointer-events: none, so it never steals clicks/right-clicks meant for page elements behind it.How
buildElementHierarchyutil → flatHierarchyEntry { element, depth, isLast }list, with bounded traversal (early-stopping collector for ancestors/siblings/children) and a centered sibling window.hierarchySourceElementmemo (store.frozenElement, gated); the imperative open/clear menu plumbing is gone.HierarchyMenurendered through a sharedAnchoredDropdownSurface(extracted from the toolbar-menu chrome; supports aninteractiveflag).resolveNavigationKeymaps arrow keys to themselves and Tab/Shift+Tab to horizontal sibling navigation.findHorizontaland the hierarchy siblings shareisHorizontallyGrabbableso tree rows and Left/Right/Tab stay in sync.Fixes
Notes
HierarchyMenustory; removed the old selection-label arrow-navigation story.[data-react-grab-context-menu]so the hierarchy menu's items don't pollute context-menu detection.pnpm build,pnpm typecheck,pnpm lint,pnpm format, the full Playwright e2e suite (751), and vitest (58) all pass.Summary by cubic
Replaced the selection dropdown in
react-grabwith a toolbar‑anchored, display‑only hierarchy tree that appears during keyboard navigation (or while holding Shift on hover) and recenters as you move. It stays out of the way of page clicks and context menus.New Features
HierarchyMenuanchored to the toolbar; shows ancestors, a centered window of siblings filtered viaisHorizontallyGrabbable, and direct children; non‑interactive; follows the toolbar; hidden while any popover is open; renders for keyboard‑nav selections and during Shift‑hover.Refactors
AnchoredDropdownSurfaceand routed toolbar/hierarchy menus through it; derived hierarchy state/anchor from a source element with Solid effects (no imperative open/clear); reusedcreateModifierTrackerfor Shift.isHorizontallyGrabbableand consolidated element predicates intoElementPredicateto keep sibling rows and Left/Right/Tab navigation in sync; unified the editable‑control navigation guard across key paths.Written for commit d078506. Summary will update on new commits.