Skip to content

Add hover explainer panel to create menu - #5440

Open
jbecke wants to merge 1 commit into
mainfrom
claude/menu-hover-explainers-spmigh
Open

Add hover explainer panel to create menu#5440
jbecke wants to merge 1 commit into
mainfrom
claude/menu-hover-explainers-spmigh

Conversation

@jbecke

@jbecke jbecke commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a secondary informational panel to the create menu that displays when users hover over or keyboard-navigate to menu items. The panel provides detailed descriptions of what each creatable block does, helping users understand the purpose of each option.

Key Changes

  • New component CreateMenuExplainer: A portaled panel that anchors to the currently focused/hovered menu row and displays detailed copy about that block. Uses Floating UI for intelligent positioning with fallback placement and viewport padding.
  • New explainer content file: CREATE_MENU_EXPLAINERS constant mapping hotkey tokens to title and body copy for each creatable block type (email, chat, note, task, snippet, message, channel, canvas, project, code).
  • Updated SidebarCreateMenu:
    • Tracks the focused row element alongside the focused index to enable accurate panel anchoring
    • Clears focus state when menu closes to prevent stale element references
    • Passes anchor and block data to the explainer component
    • Hides explainer on touch devices (pointer-only feature)
    • Captures row refs per item to maintain stable element references as the list changes

Implementation Details

  • The explainer is not a Kobalte submenu to avoid conflicts with item selectability and click handling
  • Panel uses pointer-events-none to prevent interfering with menu interactions
  • Opacity transition hides the panel until Floating UI has computed its position, preventing flash artifacts
  • Focus tracking distinguishes between pointer and keyboard navigation by storing the actual element reference
  • Touch device detection prevents showing the explainer on devices without hover capability

https://claude.ai/code/session_01Dssuns1YheqzqZuhWyBSHk

Hovering (or keyboard-focusing) a row in the sidebar's create menu now
opens a secondary panel beside it explaining what that block is for,
with the block's "open in a new split" shortcut when it has one.

The panel is not a Kobalte submenu: a SubTrigger can't also be a
selectable item, and every row here has to stay clickable. It's a
portaled, pointer-inert panel anchored to the active row with Floating
UI, so it can't intercept clicks or trip the menu's outside-interaction
dismissal, and it tracks arrow-key navigation as well as the pointer.
Suppressed on touch devices, where there is nothing to hover.

Copy lives in its own module rather than on CREATABLE_BLOCKS, which is
shared with the command palette, mobile dock, and global hotkey table —
all of which show only the terse `description`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dssuns1YheqzqZuhWyBSHk
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added contextual explanations to sidebar create-menu items, including titles, descriptions, and optional keyboard shortcut guidance.
    • Explanations appear when items are hovered or keyboard-focused and position themselves automatically within the viewport.
    • Improved touch-device behavior by keeping explainers hidden on touch interactions.

Walkthrough

The change adds descriptive content for supported create-menu items. The sidebar tracks the active row and its DOM element during pointer and keyboard interaction. On non-touch devices, the menu renders a portaled explainer with the item icon, title, description, and optional alternate-hotkey guidance. Floating UI positions the panel beside the active row and adjusts it within viewport bounds.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes the main change but does not follow the required Conventional Commits format. Use a Conventional Commits prefix, such as "feat: add hover explainer panel to create menu"; keep the title under 72 characters.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the explainer panel, its content, positioning, focus tracking, and touch-device behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/src/features/command/sidebar/sidebar-create-menu.tsx (2)

67-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the state-synchronization effect with a wrapped setter.

This createEffect only synchronizes local state from open(). Route every close path through a setMenuOpen helper that clears the focused row before it calls setOpen(false). Verify that handleOpenChange, the hotkey interceptor, and onSelect use that helper.

As per coding guidelines, “In SolidJS, avoid createEffect except for synchronizing with external or imperative systems; use derived signals or wrapped setters for derived state and updates.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/features/command/sidebar/sidebar-create-menu.tsx` around lines
67 - 69, Remove the createEffect that watches open() and centralize close-state
synchronization in a setMenuOpen helper that clears the focused row before
calling setOpen(false). Update handleOpenChange, the hotkey interceptor, and
onSelect to route their close behavior through setMenuOpen, preserving existing
open behavior.

Source: Coding guidelines


51-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace these cheap createMemo calls with derived accessors.

Neither lookup needs referential stability or performs expensive work.

  • apps/web/src/features/command/sidebar/sidebar-create-menu.tsx#L51-L51: use const focusedBlock = () => blocks()[focusedIndex()];.
  • apps/web/src/features/command/sidebar/create-menu-explainer.tsx#L50-L53: use a direct accessor that reads props.block?.hotkeyToken and CREATE_MENU_EXPLAINERS.

As per coding guidelines, “Use createMemo only when referential stability is needed or derivation is expensive; do not memoize cheap derivations.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/features/command/sidebar/sidebar-create-menu.tsx` at line 51,
The cheap createMemo derivations should be replaced with direct accessors. In
apps/web/src/features/command/sidebar/sidebar-create-menu.tsx lines 51-51,
update focusedBlock to read blocks()[focusedIndex()] directly; in
apps/web/src/features/command/sidebar/create-menu-explainer.tsx lines 50-53,
replace the memoized derivation with an accessor that reads
props.block?.hotkeyToken and CREATE_MENU_EXPLAINERS.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/features/command/sidebar/create-menu-explainer.tsx`:
- Around line 55-75: Update the createEffect positioning flow to clear
placedPanel when props.anchor changes and invalidate any in-flight
computePosition result during cleanup. Return and invoke the autoUpdate stopper
before each rerun, and ensure stale promise callbacks cannot update el or
setPlacedPanel after cleanup; preserve the existing positioning options and
placement behavior.

---

Nitpick comments:
In `@apps/web/src/features/command/sidebar/sidebar-create-menu.tsx`:
- Around line 67-69: Remove the createEffect that watches open() and centralize
close-state synchronization in a setMenuOpen helper that clears the focused row
before calling setOpen(false). Update handleOpenChange, the hotkey interceptor,
and onSelect to route their close behavior through setMenuOpen, preserving
existing open behavior.
- Line 51: The cheap createMemo derivations should be replaced with direct
accessors. In apps/web/src/features/command/sidebar/sidebar-create-menu.tsx
lines 51-51, update focusedBlock to read blocks()[focusedIndex()] directly; in
apps/web/src/features/command/sidebar/create-menu-explainer.tsx lines 50-53,
replace the memoized derivation with an accessor that reads
props.block?.hotkeyToken and CREATE_MENU_EXPLAINERS.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: da59323e-5975-4825-89ac-fb047b08e72a

📥 Commits

Reviewing files that changed from the base of the PR and between 5314558 and 872fcfd.

📒 Files selected for processing (3)
  • apps/web/src/features/command/sidebar/create-menu-explainer.tsx
  • apps/web/src/features/command/sidebar/create-menu-explainers.ts
  • apps/web/src/features/command/sidebar/sidebar-create-menu.tsx

Comment on lines +55 to +75
createEffect(() => {
const anchor = props.anchor;
const el = panel();
if (!anchor || !el) return;

onCleanup(
autoUpdate(anchor, el, () => {
void computePosition(anchor, el, {
strategy: 'fixed',
placement: 'right-start',
middleware: [
offset(ANCHOR_GAP_PX),
flip({ fallbackPlacements: ['left-start'] }),
shift({ padding: VIEWPORT_PADDING_PX }),
],
}).then(({ x, y }) => {
el.style.translate = `${x}px ${y}px`;
setPlacedPanel(el);
});
})
);

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'create-menu-explainer\.tsx$' . || true

echo "== file excerpt =="
if [ -f apps/web/src/features/command/sidebar/create-menu-explainer.tsx ]; then
  cat -n apps/web/src/features/command/sidebar/create-menu-explainer.tsx | sed -n '1,180p'
fi

echo "== imports/usages =="
rg -n "\b(setPlacedPanel|placedPanel|autoUpdate|computePosition|createMemo|createEffect)\b" apps/web/src/features/command/sidebar apps/web/src -g '*.ts' -g '*.tsx' || true

Repository: macro-inc/macro

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== targeted file excerpt =="
if [ -f apps/web/src/features/command/sidebar/create-menu-explainer.tsx ]; then
  cat -n apps/web/src/features/command/sidebar/create-menu-explainer.tsx | sed -n '1,140p'
fi

echo "== signal and placement usages in target file =="
rg -n "\b(placedPanel|setPlacedPanel|placed|props\.anchor|createMenuExplainer|autoUpdate|computePosition|useId)\b" apps/web/src/features/command/sidebar/create-menu-explainer.tsx || true

echo "== related wrapper usages =="
rg -n -A 8 -B 8 "createMenuExplainer|MenuExplainer|explainer" apps/web/src/features/command/sidebar apps/web/src/features/command -g '*.tsx' -g '*.ts' || true

Repository: macro-inc/macro

Length of output: 41701


Reset placement and cancel stale position updates when the anchor changes.

placedPanel stays set for the same portaled panel after props.anchor changes, so the panel can remain visible at its previous position until the second computePosition promise settles. If an earlier update resolves after cleanup, it can also overwrite the new position.

Return the autoUpdate stopper and invalidate it/stale promises on cleanup before each new positioning run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/features/command/sidebar/create-menu-explainer.tsx` around lines
55 - 75, Update the createEffect positioning flow to clear placedPanel when
props.anchor changes and invalidate any in-flight computePosition result during
cleanup. Return and invoke the autoUpdate stopper before each rerun, and ensure
stale promise callbacks cannot update el or setPlacedPanel after cleanup;
preserve the existing positioning options and placement behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants