FE-512: Create FilterableListSubView with keyboard navigation and search#8532
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
PR SummaryMedium Risk Overview Introduces a sidebar-wide fuzzy search panel (powered by Updates subview/header plumbing and styling ( Written by Cursor Bugbot for commit 981a51a. This will update automatically on new commits. Configure here. |
libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/multi-selection-panel.tsx
Show resolved
Hide resolved
🤖 Augment PR SummarySummary: Refines Petrinaut’s vertical subview headers to better match updated UI behavior and adds reusable list subviews. Changes:
Technical Notes: New filterable list subviews add keyboard navigation and multi-select range selection, and move destructive actions into an ellipsis menu using the shared 🤖 Was this summary useful? React with 👍 or 👎 |
| <item.icon size={LIST_ITEM_ICON_SIZE} /> | ||
| </span> | ||
| )} | ||
| <span className={listItemNameStyle}> |
There was a problem hiding this comment.
renderItem() is wrapped in a <span>, but some callers (e.g. parameters) return a <div> which creates invalid HTML (div inside span) and can lead to layout/hydration warnings. Consider using a block wrapper or constraining renderItem to inline content.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Fixed — changed the <span> wrapper to <div> so block-level renderItem content doesn't create invalid HTML.
| [items, getSelectionItem, setSelection], | ||
| ); | ||
|
|
||
| const handleKeyDown = useCallback( |
There was a problem hiding this comment.
The listbox-level onKeyDown will also fire when focus is on nested controls (e.g. the row menu IconButton), so Enter/Space/arrow keys can interfere with opening/navigating menus. Consider ignoring key handling unless the event originated from the container itself.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Fixed — added event.target \!== event.currentTarget guard so the listbox-level onKeyDown ignores events bubbling from nested controls like menu buttons.
...ashintel/petrinaut/src/views/Editor/panels/LeftSideBar/subviews/filterable-list-sub-view.tsx
Outdated
Show resolved
Hide resolved
0b08c25 to
44fe283
Compare
39823b5 to
ae532f6
Compare
libs/@hashintel/petrinaut/src/views/Editor/panels/LeftSideBar/subviews/parameters-list.tsx
Show resolved
Hide resolved
libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/use-keyboard-shortcuts.ts
Show resolved
Hide resolved
| }, | ||
| emptyMessage: "No global parameters yet", | ||
| renderHeaderAction: () => <ParametersHeaderAction />, | ||
| }); |
There was a problem hiding this comment.
Simulation parameter value editing feature lost in refactor
Medium Severity
The refactor from a custom component to createFilterableListSubView removed the inline NumberInput that allowed changing parameter values during simulation mode. The old code used SimulationContext (parameterValues, setParameterValue) to render an editable input for each parameter when globalMode === "simulate". The new code only hides the delete menu item in simulation mode but provides no way to edit parameter values inline.
There was a problem hiding this comment.
Valid observation — the inline NumberInput for editing parameter values during simulation mode was removed when migrating to the createFilterableListSubView pattern. This will be addressed in a follow-up since it requires a per-item renderAction slot that doesn't fit the current generic list abstraction.
...ashintel/petrinaut/src/views/Editor/panels/LeftSideBar/subviews/filterable-list-sub-view.tsx
Show resolved
Hide resolved
...intel/petrinaut/src/views/Editor/panels/LeftSideBar/subviews/differential-equations-list.tsx
Outdated
Show resolved
Hide resolved
libs/@hashintel/petrinaut/src/views/Editor/panels/LeftSideBar/subviews/search-panel.tsx
Show resolved
Hide resolved
…e entity icons Clicking in the list container but not on an item now calls clearSelection to deselect all. Also update Parameter/TokenType/DifferentialEquation icons. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase default panel content horizontal padding to 4 and use negative margin on FilterableListSubView to reduce its effective padding to 3. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…terable list and menu - Add Arrow Up/Down keyboard navigation with focused item tracking - Support Shift+Click and Shift+Arrow range selection via selectRange helper - Ctrl/Cmd+Click toggles multi-selection with anchor tracking - Escape clears selection and resets focus state - Clamp focus/anchor indices when item list shrinks - Auto-scroll focused items into view - Add ARIA listbox/option roles for accessibility - Suppress default browser focus outline on list container - Add _highlighted styles to Menu items and submenu triggers for keyboard focus Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert headerRowStyle to cva with isCollapsed variant that sets borderBottomColor to transparent, avoiding visual double-borders between collapsed sections. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace raw [44px] with token value for consistent sizing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clear focusedIndex and anchorIndex on blur when focus moves outside the list container, so stale keyboard state doesn't persist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fixed minWidth with flex: 1 so the title input expands to use all remaining space in the left section. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add isSearchOpen state and searchInputRef to EditorContext - Create SearchPanel subview with input in header via renderTitle - Add renderTitle to SubView type for custom main header content - Swap between normal subviews and search panel with CSS transition - Ctrl/Cmd+F opens search or focuses input if already open - Escape closes search globally via keyboard shortcuts hook - Wire Search button in FilterableListSubView header to open search Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Search all sidebar items (nodes, types, equations, parameters) with fuzzy matching via fuzzysort. Matched characters are highlighted in results. Shows match count at top, all items when query is empty. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… call ArrowDown from the search input moves focus to the first result. ArrowUp/Down navigates through results, selecting each item. ArrowUp on the first result returns focus to the input. Also fixes the fuzzysort highlight call to use the instance method (result.highlight) instead of the non-existent static method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hide the Filter and Sort IconButtons from FilterHeaderAction (not yet implemented). Remove all useCallback and useMemo wrappers from changed files so the React Compiler can optimize the components. Also fix search panel to show empty state when no query and prevent onFocus render loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… handler Rename getMenuItems → useMenuItems so the linter recognizes these callbacks as hooks (they call use() and useIsReadOnly() inside RowMenu's render). Add onKeyDown to the row div to satisfy the click-events-have-key-events a11y rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4531039 to
e2322e2
Compare
libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/use-keyboard-shortcuts.ts
Show resolved
Hide resolved
- Fix invalid HTML: change <span> to <div> for listItemNameStyle wrapper so block-level renderItem content (e.g. parameters) nests correctly - Guard listbox onKeyDown to ignore events bubbling from nested controls - Prevent Ctrl+F from intercepting inside Monaco/inputs by checking isInputFocused before handling the shortcut - Restrict Escape-to-close-search to only fire when search input is focused - Remove duplicate onKeyDown on search result rows (parent listbox handles navigation; rows now only handle Enter/Space for a11y) - Add alwaysShowHeaderAction to multi-selection panel for consistency - Remove commented-out CSS in tooltip.tsx - Update stale doc comment (getMenuItems → useMenuItems) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The compiler flagged useMenuItems and useItems as dynamic hook props. Fix by: - Replacing useMenuItems callback with renderRowMenu component prop, so each consumer defines a proper React component (DiffEqRowMenu, ParameterRowMenu, TypeRowMenu) that calls hooks safely - Moving useItems() call from FilterableListContent into the factory's Component, passing plain items data down instead of a hook prop - Exporting RowMenu helper for consumers to render shared menu chrome Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds overflow: hidden to editorRootStyle so portalled menus and tooltips don't render outside the component boundary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes 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.
| const listRef = useRef<HTMLDivElement>(null); | ||
| const rowRefs = useRef<(HTMLDivElement | null)[]>([]); | ||
|
|
||
| const { searchInputRef } = use(EditorContext); |
There was a problem hiding this comment.
| <div | ||
| className={contentLayerStyle({ | ||
| active: !isSearchOpen, | ||
| direction: "forward", | ||
| })} | ||
| > | ||
| <VerticalSubViewsContainer | ||
| name="left-sidebar" | ||
| subViews={LEFT_SIDEBAR_SUBVIEWS} | ||
| /> | ||
| </div> | ||
| <div | ||
| className={contentLayerStyle({ | ||
| active: isSearchOpen, | ||
| direction: "backward", | ||
| })} | ||
| > | ||
| <VerticalSubViewsContainer | ||
| name="left-sidebar-search" | ||
| subViews={searchSubViews} | ||
| /> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Both content layers (main sidebar and search) are rendered simultaneously with only opacity and pointerEvents controlling visibility. The pointerEvents: 'none' prevents mouse interaction but does not prevent keyboard focus. Users can still Tab into invisible/inactive elements in the hidden layer, causing confusing navigation.
Fix: Add visibility: 'hidden' or conditionally render the inactive layer:
compoundVariants: [
{
active: false,
css: {
transform: "translateX(-8px)",
visibility: "hidden" // Prevents keyboard focus
}
}
]| <div | |
| className={contentLayerStyle({ | |
| active: !isSearchOpen, | |
| direction: "forward", | |
| })} | |
| > | |
| <VerticalSubViewsContainer | |
| name="left-sidebar" | |
| subViews={LEFT_SIDEBAR_SUBVIEWS} | |
| /> | |
| </div> | |
| <div | |
| className={contentLayerStyle({ | |
| active: isSearchOpen, | |
| direction: "backward", | |
| })} | |
| > | |
| <VerticalSubViewsContainer | |
| name="left-sidebar-search" | |
| subViews={searchSubViews} | |
| /> | |
| </div> | |
| </div> | |
| {!isSearchOpen ? ( | |
| <div | |
| className={contentLayerStyle({ | |
| active: true, | |
| direction: "forward", | |
| })} | |
| > | |
| <VerticalSubViewsContainer | |
| name="left-sidebar" | |
| subViews={LEFT_SIDEBAR_SUBVIEWS} | |
| /> | |
| </div> | |
| ) : ( | |
| <div | |
| className={contentLayerStyle({ | |
| active: true, | |
| direction: "backward", | |
| })} | |
| > | |
| <VerticalSubViewsContainer | |
| name="left-sidebar-search" | |
| subViews={searchSubViews} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.



Kapture.2026-03-13.at.11.01.04.mp4
🌟 What is the purpose of this PR?
Introduces the
FilterableListSubViewcomponent and a global search panel for the Petrinaut editor sidebar. This provides a reusable, keyboard-navigable list pattern for all sidebar sections, along with fuzzy search across all entities.🔗 Related links
🔍 What does this change?
FilterableListSubView
createFilterableListSubViewfactory for reusable sidebar list sectionsSearch panel
fuzzysortwith highlighted match resultsVerticalSubViewsContainer improvements
renderTitlesupport for custom header content (used by search input)Other
useCallback/useMemo(React Compiler handles memoization)Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
❓ How to test this?
yarn devinlibs/@hashintel/petrinaut