From ba361af2d7d5acfae511ae778694df0caea4d202 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:38:01 +0200 Subject: [PATCH] docs: composable sidebar design spec for #112 Replaces the previous "hide sidebar on admin" approach with a composable compound component design. DS provides shell + building blocks (Sidebar, Section, Footer, SidebarTree); consuming app controls all content, section ordering, accordion behavior, and icon-rail collapse. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...6-04-02-admin-context-separation-design.md | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-02-admin-context-separation-design.md diff --git a/docs/superpowers/specs/2026-04-02-admin-context-separation-design.md b/docs/superpowers/specs/2026-04-02-admin-context-separation-design.md new file mode 100644 index 00000000..4e903970 --- /dev/null +++ b/docs/superpowers/specs/2026-04-02-admin-context-separation-design.md @@ -0,0 +1,366 @@ +# Composable Sidebar with Accordion & Collapse + +**Issue:** [#112](https://gitea.siegeln.net/cameleer/cameleer3-server/issues/112) +**Date:** 2026-04-02 +**Scope:** Design system refactor + server UI migration + +## Problem + +The current `Sidebar` component in `@cameleer/design-system` is monolithic — it hardcodes three navigation sections (Applications, Agents, Routes), a starred section, bottom links (Admin, API Docs), and all tree-building logic. This makes it impossible for the consuming application to: + +1. Add new sections (e.g., Admin sub-pages) without modifying the DS +2. Control section ordering or visibility based on application state +3. Implement accordion behavior (expanding one section collapses others) +4. Collapse the sidebar to an icon rail + +## Solution + +Refactor the Sidebar into a **composable compound component** where the DS provides the shell and building blocks, and the consuming application controls all content. + +## New DS API + +### Compound Component Structure + +```tsx + setSidebarCollapsed(v => !v)} + onSearchChange={setFilterQuery} +> + } + title="cameleer" + version="v3.2.1" + /> + + } + collapsed={appsCollapsed} + onToggle={() => setAppsCollapsed(v => !v)} + > + + + + } + collapsed={agentsCollapsed} + onToggle={() => setAgentsCollapsed(v => !v)} + > + + + + } + collapsed={routesCollapsed} + onToggle={() => setRoutesCollapsed(v => !v)} + > + + + + } + collapsed={adminCollapsed} + onToggle={() => setAdminCollapsed(v => !v)} + > + + + + + } label="API Docs" onClick={() => nav('/api-docs')} /> + + +``` + +### Component Specifications + +#### `` + +The outer shell. Provides the sidebar frame, search input, scrollable content area, and collapse toggle. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `collapsed` | `boolean` | `false` | When true, renders as ~48px icon rail | +| `onCollapseToggle` | `() => void` | - | Called when user clicks the collapse/expand toggle | +| `onSearchChange` | `(query: string) => void` | - | Called on search input change. Omit to hide search. | +| `children` | `ReactNode` | - | `Sidebar.Header`, `Sidebar.Section`, `Sidebar.Footer` | +| `className` | `string` | - | Additional CSS class | + +**Expanded layout (default):** +``` ++---------------------------+ +| [Header] [<<] | +|---------------------------| +| [Search...] | +| | +| [Section 1] | +| [Section 2] | +| [Section ...] | +| | +| [Footer] | ++---------------------------+ + ~260px +``` + +**Collapsed layout:** +``` ++------+ +| [>>] | +|------| +| [i1] | <- Section icons, centered +| [i2] | <- Tooltip on hover shows label +| [i3] | +| [i4] | +|------| +| [f1] | <- Footer link icons ++------+ + ~48px +``` + +- `[<<]` / `[>>]` toggle button in top-right corner (chevron icon) +- Width transition: CSS `width` + `transition: width 200ms ease` +- When collapsed, search input is hidden +- When collapsed, clicking a section icon calls `onCollapseToggle` (to expand) and that section's `onToggle` (to open it) + +#### `` + +Renders logo, title, and version. In collapsed mode, renders only the logo (centered). + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `logo` | `ReactNode` | - | Logo icon/image (required) | +| `title` | `string` | - | App name shown next to logo | +| `version` | `string` | - | Version badge | + +#### `` + +An accordion section with a collapsible header and content area. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string` | - | Section header text (rendered uppercase) | +| `icon` | `ReactNode` | - | Icon shown in header and in collapsed rail | +| `collapsed` | `boolean` | `false` | Whether content is hidden | +| `onToggle` | `() => void` | - | Called when header is clicked | +| `children` | `ReactNode` | - | Content rendered when expanded (typically `SidebarTree`) | +| `active` | `boolean` | auto | Override active state highlight. Auto-detected if omitted (any descendant matches current path). | + +**Expanded state:** +``` +v APPLICATIONS <- clickable header, chevron rotates + > backend-app 3.4k + > caller-app 891 + > sample-app 9.6k +``` + +**Collapsed state:** +``` +> APPLICATIONS <- single line, clickable +``` + +**In sidebar collapsed (icon rail) mode:** +``` +[B] <- icon only, tooltip "Applications" +``` + +Header styling: uppercase label, muted color, chevron left of label, icon left of chevron. Active section gets amber accent (same as current active highlighting pattern). + +#### `` + +Pinned to the bottom of the sidebar. Renders children (typically `Sidebar.FooterLink` items). + +In collapsed mode, footer links render as centered icons. + +#### `` + +A single bottom link item. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `icon` | `ReactNode` | - | Link icon | +| `label` | `string` | - | Link text (hidden when sidebar collapsed, shown as tooltip) | +| `onClick` | `() => void` | - | Click handler | +| `active` | `boolean` | `false` | Active state highlight | + +#### `` (unchanged, newly exported) + +The existing `SidebarTree` component stays as-is. It already accepts all its data via props (`nodes`, `selectedPath`, `filterQuery`, `onNavigate`, `persistKey`, `autoRevealPath`, `isStarred`, `onToggleStar`). It just needs to be exported from the package. + +The `SidebarTreeNode` type is also exported so consuming apps can build tree data. + +#### `useStarred` hook (unchanged, newly exported) + +The existing `useStarred` hook stays as-is. Export it so the consuming app can pass `isStarred`/`onToggleStar` to `SidebarTree`. + +### What Gets Removed from DS + +The current monolithic `Sidebar` component contains ~300 lines of application-specific logic that moves to the server UI: + +1. **Tree-building functions**: `buildAppTreeNodes()`, `buildRouteTreeNodes()`, `buildAgentTreeNodes()` — these transform `SidebarApp[]` into `SidebarTreeNode[]`. They move to the server UI. +2. **Starred section rendering**: `collectStarredItems()` and `StarredGroup` — starred items become a regular `Sidebar.Section` in the server UI (or inline within sections via `SidebarTree`'s existing star support). +3. **Hardcoded bottom links**: Admin and API Docs links — move to `Sidebar.Footer` + `Sidebar.FooterLink` in server UI. +4. **Section collapse state management**: localStorage persistence of `cameleer:sidebar:*-collapsed` — moves to server UI. +5. **Auto-reveal logic**: `sidebarRevealPath` effect that auto-expands sections — moves to server UI. +6. **`SidebarApp` / `SidebarRoute` / `SidebarAgent` types**: These are application-domain types, not DS types. Move to server UI. DS only exports `SidebarTreeNode`. + +### New DS Exports + +```typescript +// layout/index.ts — updated exports +export { Sidebar } from './Sidebar/Sidebar' +export { SidebarTree } from './Sidebar/SidebarTree' +export type { SidebarTreeNode } from './Sidebar/SidebarTree' +export { useStarred } from './Sidebar/useStarred' +``` + +The `SidebarApp`, `SidebarRoute`, `SidebarAgent` type exports are removed (they move to the consuming application). + +## Server UI Migration + +### LayoutShell.tsx Changes + +The current `LayoutShell` already does most of the data preparation (building `sidebarApps`, handling `handleSidebarNavigate`). After migration: + +1. Move tree-building functions (`buildAppTreeNodes`, etc.) from DS into a local `sidebar-utils.ts` +2. Manage section collapse states with localStorage persistence +3. Implement accordion logic: when on `/admin/*`, expand Admin section and collapse operational sections; when navigating away, restore previous states +4. Pass `filterQuery` from search to each `SidebarTree` +5. Compose the new `` with sections + +### AdminLayout.tsx Changes + +Remove the `` navigation — sidebar now handles admin sub-page navigation. Keep just the content wrapper: + +```tsx +export default function AdminLayout() { + return ( +
+ +
+ ); +} +``` + +### ContentTabs + +Still hidden on admin pages (existing `isAdminPage` guard). The tab strip is irrelevant when the sidebar shows the admin context. + +### TopBar + +No changes. Stays visible on all pages. + +## Accordion Behavior (Server UI Logic) + +The accordion is not a DS concept — it's application logic in the server UI: + +```typescript +// When navigating to /admin/*: +// 1. Remember current operational collapse states +// 2. Collapse all operational sections +// 3. Expand Admin section + +// When navigating away from /admin/*: +// 1. Collapse Admin section +// 2. Restore operational collapse states from memory +``` + +This means the DS sections are purely controlled components. The app decides which are open/closed based on current route. + +## Visual States + +### Operational Mode +``` ++---------------------------+ +| [C] cameleer v3.2.1 [<<] | +|---------------------------| +| [Search...] | +| | +| v APPLICATIONS | +| > backend-app 3.4k | +| > caller-app 891 | +| > sample-app 9.6k | +| | +| v AGENTS | +| > backend-app 3/3 live | +| > caller-app 2/2 live | +| | +| > ROUTES | +| > ADMIN | +| | +| [API Docs] | ++---------------------------+ +``` + +### Admin Mode (accordion) +``` ++---------------------------+ +| [C] cameleer v3.2.1 [<<] | +|---------------------------| +| [Search...] | +| | +| v ADMIN | +| > User Management | +| > Audit Log | +| > OIDC | +| > App Config | +| > Database | +| > ClickHouse | +| | +| > APPLICATIONS | +| > AGENTS | +| > ROUTES | +| | +| [API Docs] | ++---------------------------+ +``` + +### Collapsed (Icon Rail) +``` ++------+ +| [C] | +| [>>] | +|------| +| [B] | <- Applications +| [C] | <- Agents +| [G] | <- Routes +| [S] | <- Admin +|------| +| [F] | <- API Docs ++------+ +``` + +## Implementation Order + +This is a two-repo change: + +1. **DS refactor** (design-system repo): + - Create `Sidebar` compound component shell (`Sidebar`, `Sidebar.Header`, `Sidebar.Section`, `Sidebar.Footer`, `Sidebar.FooterLink`) + - Extract `SidebarTree` and `useStarred` as standalone exports + - Add `collapsed` / icon-rail mode + - Remove application-specific logic (tree builders, hardcoded sections, bottom links) + - Update barrel exports + - Bump version + +2. **Server UI migration** (cameleer3-server repo): + - Move tree-building functions to local utils + - Rewrite sidebar composition in `LayoutShell` using new compound API + - Add accordion logic for admin mode + - Add sidebar collapse toggle with localStorage persistence + - Simplify `AdminLayout` (remove tabs) + +## Acceptance Criteria + +- [ ] DS: `Sidebar` accepts `collapsed` prop and renders as icon rail when true +- [ ] DS: `Sidebar.Section` renders as accordion item with icon, label, chevron, and children +- [ ] DS: `Sidebar.Footer` / `Sidebar.FooterLink` render bottom links +- [ ] DS: `SidebarTree`, `SidebarTreeNode`, `useStarred` exported as standalone +- [ ] DS: No application-specific logic remains in Sidebar (no hardcoded sections, no tree builders) +- [ ] UI: Sidebar shows operational sections (Apps, Agents, Routes) on operational pages +- [ ] UI: Clicking Admin expands Admin section, collapses operational sections +- [ ] UI: Clicking an operational section header exits admin mode, restores previous state +- [ ] UI: Sidebar collapse/expand toggle works with icon rail mode +- [ ] UI: Admin tabs removed from AdminLayout (sidebar handles navigation) +- [ ] UI: All icons passed by the consuming application, not hardcoded in DS