From 7758962564e06e7e128ef207b9796f6ad0aa1937 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:41:36 +0200 Subject: [PATCH] docs: clarify search ownership and icon-rail click behavior Search: DS renders dumb controlled input, app owns state and passes filterQuery to SidebarTree instances. Icon-rail click: fires both onCollapseToggle and onToggle simultaneously, no navigation. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-02-composable-sidebar-design.md | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/docs/superpowers/specs/2026-04-02-composable-sidebar-design.md b/docs/superpowers/specs/2026-04-02-composable-sidebar-design.md index 0da57b4..d6d8560 100644 --- a/docs/superpowers/specs/2026-04-02-composable-sidebar-design.md +++ b/docs/superpowers/specs/2026-04-02-composable-sidebar-design.md @@ -60,9 +60,12 @@ The outer shell. Renders the sidebar frame with an optional search input and col | `collapsed` | `boolean` | `false` | Render as ~48px icon rail | | `onCollapseToggle` | `() => void` | - | Collapse/expand toggle clicked | | `onSearchChange` | `(query: string) => void` | - | Search input changed. Omit to hide search. | +| `searchValue` | `string` | `''` | Controlled value for the search input | | `children` | `ReactNode` | - | Sidebar.Header, Sidebar.Section, Sidebar.Footer | | `className` | `string` | - | Additional CSS class | +**Search state ownership:** The DS renders the search input as a dumb controlled input and calls `onSearchChange` on every keystroke. The consuming application owns the search state and passes it to each `SidebarTree` as `filterQuery`. This lets the app control filtering behavior (e.g., clear search when switching sections, filter only certain sections). The DS does not hold any search state internally. + **Rendering rules:** - Expanded: full width (~260px), all content visible - Collapsed: ~48px wide, only icons visible, tooltips on hover @@ -129,7 +132,9 @@ v [icon] APPLICATIONS [icon] <- centered, tooltip shows label on hover ``` -Header has: chevron (left), icon, label. Chevron rotates on collapse/expand. Active section gets the amber left-border accent (existing pattern). Clicking the header calls `onToggle`. In icon-rail mode, clicking the icon calls both `onCollapseToggle` (to expand the sidebar) and `onToggle`. +Header has: chevron (left), icon, label. Chevron rotates on collapse/expand. Active section gets the amber left-border accent (existing pattern). Clicking the header calls `onToggle`. + +**Icon-rail click behavior:** In collapsed mode, clicking a section icon fires both `onCollapseToggle` and `onToggle` simultaneously on the same click. The sidebar expands and the section opens in one motion. No navigation occurs — the user is expanding the sidebar to see what's inside, not committing to a destination. They click a tree item after the section is visible to navigate. ### `` @@ -278,6 +283,7 @@ useEffect(() => { setSidebarCollapsed(v => !v)} + searchValue={filterQuery} onSearchChange={setFilterQuery} > } title="cameleer" version="v3.2.1" /> @@ -310,6 +316,78 @@ useEffect(() => { ``` +## Mock App Migration — LayoutShell + +The 11 page files currently duplicating `}>` will be consolidated into a single `LayoutShell` component. + +### `src/layout/LayoutShell.tsx` + +Composes the sidebar once using the new compound API. All page-specific content is rendered via ``. + +```tsx +// src/layout/LayoutShell.tsx +export function LayoutShell() { + const [sidebarCollapsed, setSidebarCollapsed] = useState(false) + const [filterQuery, setFilterQuery] = useState('') + const [appsCollapsed, setAppsCollapsed] = useState(false) + const [agentsCollapsed, setAgentsCollapsed] = useState(false) + const [routesCollapsed, setRoutesCollapsed] = useState(false) + const { starredIds, isStarred, toggleStar } = useStarred() + const location = useLocation() + // ... build tree nodes from SIDEBAR_APPS, starred section, etc. + + return ( + setSidebarCollapsed(v => !v)} + searchValue={filterQuery} + onSearchChange={setFilterQuery} + > + + setAppsCollapsed(v => !v)}> + + + setAgentsCollapsed(v => !v)}> + + + setRoutesCollapsed(v => !v)}> + + + {/* Starred section built from useStarred + SIDEBAR_APPS */} + + + + + + } + > + + + ) +} +``` + +### Route structure change + +`App.tsx` switches from per-page `}>` to a layout route: + +```tsx +}> + } /> + } /> + ...all existing routes... + +``` + +All tree-building helpers (`buildAppTreeNodes`, `buildRouteTreeNodes`, `buildAgentTreeNodes`), starred section logic (`collectStarredItems`, `StarredGroup`), `formatCount`, and `sidebarRevealPath` handling move from `Sidebar.tsx` into `LayoutShell.tsx`. Each page file loses its `` wrapper and becomes just the page content. + +The Inventory page's `LayoutSection` keeps its own inline `` demo with `SAMPLE_APPS` data — it's a showcase, not a navigation shell. + ## Breaking Change This is a **breaking change** to the `Sidebar` API. The old `` signature is removed entirely. The consuming application must migrate to the compound component API in the same release cycle.