Skip to content

feat(react-grab): toolbar-anchored hierarchy tree + sibling navigation#515

Merged
aidenybai merged 28 commits into
mainfrom
aiden/hierarchy-tree-dropdown-e60f
Jul 2, 2026
Merged

feat(react-grab): toolbar-anchored hierarchy tree + sibling navigation#515
aidenybai merged 28 commits into
mainfrom
aiden/hierarchy-tree-dropdown-e60f

Conversation

@aidenybai

@aidenybai aidenybai commented Jun 30, 2026

Copy link
Copy Markdown
Owner

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:

  • the ancestor spine ("the stack") leading down to the selected element,
  • the selected element's siblings (windowed around the selection), and
  • the selected element's direct children, nested underneath.

Rows are indented by depth with ├─ / └─ connectors, reusing the shared Menu.* primitives, design tokens, and anchored-dropdown positioning stack.

Behavior

  • Shows during keyboard navigation: the dropdown is gated on a frozen (keyboard-navigated) selection, so it appears when you navigate with the arrow keys / Tab and stays hidden on raw hover. It is suppressed in modes where it would be noise or intercept input (prompt, shift multi-select, copying, an open popover/context menu, or a pending mouse-handoff).
  • Display-only: the dropdown is pointer-events: none, so it never steals clicks/right-clicks meant for page elements behind it.
  • Keyboard navigation:
    • Up / Down → parent / child (ancestor z-stack traversal).
    • Left / Right and Tab / Shift+Tab → next / previous sibling (strict DOM sibling walk, matching the rows shown).

How

  • New buildElementHierarchy util → flat HierarchyEntry { element, depth, isLast } list, with bounded traversal (early-stopping collector for ancestors/siblings/children) and a centered sibling window.
  • Hierarchy entries, active index, and toolbar-anchor tracking are all derived from a hierarchySourceElement memo (store.frozenElement, gated); the imperative open/clear menu plumbing is gone.
  • New toolbar-anchored HierarchyMenu rendered through a shared AnchoredDropdownSurface (extracted from the toolbar-menu chrome; supports an interactive flag).
  • resolveNavigationKey maps arrow keys to themselves and Tab/Shift+Tab to horizontal sibling navigation. findHorizontal and the hierarchy siblings share isHorizontallyGrabbable so tree rows and Left/Right/Tab stay in sync.

Fixes

  • Right-click takes over a pending keyboard selection: when the cursor is over the discard-prompt overlay, the context menu still opens for the page element beneath (clears only once a menu opens).

Notes

  • Storybook: added a HierarchyMenu story; removed the old selection-label arrow-navigation story.
  • Scoped the context-menu e2e helpers to [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.
Open in Web Open in Cursor 

Summary by cubic

Replaced the selection dropdown in react-grab with 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

    • HierarchyMenu anchored to the toolbar; shows ancestors, a centered window of siblings filtered via isHorizontallyGrabbable, and direct children; non‑interactive; follows the toolbar; hidden while any popover is open; renders for keyboard‑nav selections and during Shift‑hover.
    • Keyboard: Left/Right and Tab/Shift+Tab navigate siblings; Up/Down go parent/child; clears vertical history after sibling moves; editable controls keep native arrow/Tab; Enter at the discard prompt discards and returns to selection (or copies when Copy is focused); right‑click resolves the element under overlays.
  • Refactors

    • Extracted AnchoredDropdownSurface and routed toolbar/hierarchy menus through it; derived hierarchy state/anchor from a source element with Solid effects (no imperative open/clear); reused createModifierTracker for Shift.
    • Added isHorizontallyGrabbable and consolidated element predicates into ElementPredicate to 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.

Review in cubic

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>
@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-grab-storybook Ready Ready Preview, Comment Jul 2, 2026 2:27am
react-grab-website Ready Ready Preview, Comment Jul 2, 2026 2:27am

@pkg-pr-new

pkg-pr-new Bot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@react-grab/cli@515
npm i https://pkg.pr.new/grab@515
npm i https://pkg.pr.new/react-grab@515

commit: d078506

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>
@cursor cursor Bot changed the title feat(react-grab): hierarchical tree view in selection dropdown feat(react-grab): toolbar-anchored hierarchy tree + sibling navigation Jun 30, 2026
…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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/react-grab/src/core/index.tsx Outdated
Comment thread packages/react-grab/src/types.ts Outdated
// "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";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread packages/react-grab/src/core/arrow-navigation.ts Outdated
Comment thread packages/react-grab/src/utils/build-element-hierarchy.ts Outdated
…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>
Comment thread packages/react-grab/src/core/index.tsx Outdated

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

The data-options script parser drops activationMode: "preview", silently falling back to the default mode for embed/script-tag consumers.

Fix on Vercel

Comment thread packages/react-grab/src/utils/resolve-activation-policy.ts Outdated
Comment thread packages/react-grab/src/utils/build-element-hierarchy.ts Outdated
Comment thread packages/react-grab/src/core/arrow-navigation.ts Outdated
- 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>
Comment thread packages/react-grab/src/core/arrow-navigation.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/react-grab/src/core/arrow-navigation.ts Outdated
…dropdown-e60f

# Conflicts:
#	packages/react-grab/src/core/keyboard-selection.ts

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix All in Cursor

❌ 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.

Comment thread packages/react-grab/src/core/index.tsx
Comment thread packages/react-grab/src/core/index.tsx
- 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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/react-grab/src/core/index.tsx Outdated
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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/react-grab/src/core/index.tsx Outdated
…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>
cursoragent and others added 2 commits July 2, 2026 02:17
…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>
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.

2 participants