---
title: Composition & Extensibility
description: Organization OS Toolkit layer guidance for customizing shared shell systems without forking, including exported nav arrays, optional sidebar props, composable layout primitives, and the manifest.sidebar override pattern.
---
<!-- @generated by packages/sdk/scripts/copy-reference-docs.mjs -- DO NOT EDIT -->
<!-- Regenerate: pnpm scaffold:sync -->


## Overview

Within Organization OS, this doc covers the **Toolkit** layer: the primitives consumers use to customize shared shell systems by composition instead of fork. Consumers extend nav items, inject panels, or wrap pages by combining published primitives and assigning a custom component to `manifest.sidebar`. Copying source is physically possible but unsupported -- a fork owns upstream drift forever.

The core pattern: set `sidebar` on your manifest to a component that composes the system's published pieces. If the published pieces can't express what you need, the missing export is a bug. File an issue instead of forking.

Shipped in `@elevasis/ui >= 2.8.1`.

## The Override Pattern

Every `SystemModule.sidebar` is a `ComponentType`. Consumers customize by:

1. Importing the system's published sidebar wrapper and middle component.
2. Wrapping or replacing the middle with custom content.
3. Spreading the stock manifest and setting `sidebar` to their component.
4. Passing the customized manifest to `ElevasisSystemsProvider`.

No new manifest fields. No discriminated unions. No runtime machinery.

## Decision Tree

- **Adding or changing nav items?** -> Extend the exported item array and pass to `<*SidebarMiddle items={...}>`.
- **Structural changes (injecting panels, wrapping sections)?** -> Compose `SidebarTop`, `SidebarMiddle`, and exported panels directly in your own component.
- **Wrapping pages?** -> Import the system's page component and wrap with custom chrome in a route file.
- **None of these fits?** -> Missing export is the bug. File an issue.

## Exported Primitives

### Nav item arrays

Re-exported from each system sidebar barrel:

- `CRM_ITEMS` from `@elevasis/ui/features/crm`
- `LEAD_GEN_ITEMS` from `@elevasis/ui/features/lead-gen`
- `DELIVERY_PROJECT_ITEMS`, `DELIVERY_WORK_ITEMS`, `DELIVERY_COMMUNICATION_ITEMS` from `@elevasis/ui/features/delivery`

Shape is the shared `NavItem` type exported from `@elevasis/ui/layout`.

### Sidebar wrappers

Thin Top+Middle composers. Each accepts optional `children?: ReactNode` that replaces the default middle:

- `CrmSidebar`, `LeadGenSidebar`, `ProjectsSidebar`

### Middle components with optional item props

- `CrmSidebarMiddle` -- `items?: NavItem[]` (defaults to `CRM_ITEMS`)
- `LeadGenSidebarMiddle` -- `items?: NavItem[]` (defaults to `LEAD_GEN_ITEMS`)
- `ProjectsSidebarMiddle` -- `projectItems?`, `workItems?`, `communicationItems?` (each defaults to its respective exported array)

Delivery's three sections are preserved intentionally -- a real UX pattern consumers may want to extend with additional sections.

### Composable layout primitives

From `@elevasis/ui/layout`:

- `SubshellNavItem` -- individual nav link
- `SubshellNavList` -- router-context-aware list renderer (accepts `items: NavItem[]`, derives active state via `useRouterContext()`)
- `SubshellSidebarSection` -- section header with icon, label, optional top border
- `NavItem` -- shared item shape type
- `SubshellContainer` -- full-width, full-height flex wrapper that composes sidebar and content side by side
- `SubshellRightSideContainer` -- content area column (`flex: 1`, `minWidth: 0`) that prevents sidebar from squeezing content
- `SubshellContentContainer` -- scrollable content area with standard `md` padding and automatic topbar offset applied via `paddingTop`
- `SubshellLoader` -- loading placeholder centered in the subshell viewport; reads the active loader from `AppearanceContext`

`SystemShell` wires these together at `width={250}` as the default sidebar width. Consumers composing their own shell layout can pass a different `width` prop to `SubshellSidebar`.

### SubshellSidebar behavior

`SubshellSidebar` is more than a thin wrapper. Key behaviors verified in source:

- **Collapsible** -- controlled by `collapsible` prop (defaults to `true`) and `defaultOpen` prop (defaults to `true`). When collapsed, the sidebar animates to zero width and a small toggle button remains visible at the bottom-right edge.
- **Glass morphism** -- applies `color-mix(in srgb, var(--glass-background) 80%, transparent)` as background and `var(--glass-blur)` as `backdropFilter`, consistent with the theme token system.
- **Animated transitions** -- width and opacity animate on collapse/expand using `sidebarTransitionDuration` from the layout constants.
- **Width** -- no built-in default; the caller supplies `width`. `SystemShell` passes `250` and this is the canonical default for stock system sidebars.

From `@elevasis/ui/router/context`:

- `useRouterContext` -- access route state for custom active-state logic

### RouterAdapter interface

`RouterAdapter` is the interface that bridges any router implementation to the shared layout primitives. Defined in `packages/ui/src/router/context.ts`:

- `currentPath: string` -- current pathname, used by `SubshellNavList` to derive active state
- `currentSearch?: string` -- current search string (optional)
- `navigate: (to: string) => void` -- imperative navigation
- `Link: ElementType<LinkProps>` -- router-aware anchor component; `LinkProps` extends `AnchorHTMLAttributes\<HTMLAnchorElement>` with a required `to: string` field

Consumers calling `useRouterContext()` get back a `RouterAdapter`. Any component that needs to read `currentPath` or call `navigate` can use the hook directly rather than prop-drilling from a parent.

### TanStackRouterBridge

`TanStackRouterBridge` is the concrete `RouterAdapter` implementation for TanStack Router. Exported from `packages/ui/src/router/providers/TanStackRouterBridge.tsx` and available via `@elevasis/ui/router`.

It wraps `RouterProvider` and supplies the adapter by reading `useLocation` and `useRouter` from `@tanstack/react-router`. The adapter value is stabilized with `useMemo` to prevent unnecessary re-renders in downstream consumers. Place it inside TanStack's `RouterProvider`:

```tsx
<TanStackRouterBridge>
  <ElevasisSystemsProvider ...>
    <SystemShell>...</SystemShell>
  </ElevasisSystemsProvider>
</TanStackRouterBridge>
```

Consumers using a different router can implement `RouterAdapter` directly and pass it to `RouterProvider` instead of using this bridge.

### System panels (CRM)

- `MyTasksPanel`, `QuickCreateActions` -- exported from `@elevasis/ui/features/crm`, included by default in `CrmSidebarMiddle`; consumers composing their own middle can re-include them explicitly.

### System pages

Every page each system renders is exported from its system barrel, so consumers can wrap with custom chrome without forking:

- CRM: `DealsListPage`, `DealDetailPage`
- Lead-gen: `LeadGenOverviewPage`, `LeadGenListsPage`, `LeadGenListDetailPage`, `LeadGenCompaniesPage`, `LeadGenContactsPage`
- Delivery: `AllTasksPage`, `UpcomingMilestonesPage`, `ProjectsListPage`, `ProjectDetailPage`
- Operations, monitoring, settings -- full page inventories exported from their system barrels

## Consumer Patterns

### 1. Extend nav items (shortest path)

```tsx
import { CrmSidebar, CrmSidebarMiddle, CRM_ITEMS } from '@elevasis/ui/features/crm'
import { IconFileText } from '@tabler/icons-react'

const customItems = [
  ...CRM_ITEMS,
  { label: 'Reports', to: '/crm/reports', icon: IconFileText, exact: false },
]

const MyCrmSidebar = () => (
  <CrmSidebar>
    <CrmSidebarMiddle items={customItems} />
  </CrmSidebar>
)

const customCrmManifest = { ...crmManifest, sidebar: MyCrmSidebar }
```

### 2. Inject a new delivery section (compose from primitives)

```tsx
import {
  ProjectsSidebar,
  DELIVERY_PROJECT_ITEMS,
  DELIVERY_WORK_ITEMS,
  DELIVERY_COMMUNICATION_ITEMS,
} from '@elevasis/ui/features/delivery'
import { SubshellSidebarSection, SubshellNavList } from '@elevasis/ui/layout'
import { Stack } from '@mantine/core'
import { IconChartBar, IconListCheck, IconMessageCircle } from '@tabler/icons-react'

const ANALYTICS_ITEMS = [
  { label: 'Dashboard', to: '/projects/analytics', icon: IconChartBar, exact: true },
]

const MyDeliveryMiddle = () => (
  <Stack gap={0} style={{ flex: 1, overflowY: 'auto' }}>
    <Stack gap={0} p="sm"><SubshellNavList items={DELIVERY_PROJECT_ITEMS} /></Stack>
    <SubshellSidebarSection icon={IconListCheck} label="Work" withTopBorder />
    <Stack gap={0} p="sm"><SubshellNavList items={DELIVERY_WORK_ITEMS} /></Stack>
    <SubshellSidebarSection icon={IconMessageCircle} label="Communication" withTopBorder />
    <Stack gap={0} p="sm"><SubshellNavList items={DELIVERY_COMMUNICATION_ITEMS} /></Stack>
    <SubshellSidebarSection icon={IconChartBar} label="Analytics" withTopBorder />
    <Stack gap={0} p="sm"><SubshellNavList items={ANALYTICS_ITEMS} /></Stack>
  </Stack>
)

const MyDeliverySidebar = () => (
  <ProjectsSidebar>
    <MyDeliveryMiddle />
  </ProjectsSidebar>
)
```

### 3. Wrap a page with custom chrome

```tsx
// in a route file
import { DealsListPage } from '@elevasis/ui/features/crm'

export default function CustomDealsRoute() {
  return (
    <MyChromeWrapper>
      <DealsListPage />
    </MyChromeWrapper>
  )
}
```

## Operations & Unmodified Systems

- **Operations** -- uses `sidebar?: ComponentType` with route-aware dispatch; the items-prop pattern does not apply.
- **Monitoring, Settings** -- no subshell sidebar. No customization surface needed.
- **SEO** -- has sidebar components (`SEOSidebar`, `SEOSidebarTop`, `SEOSidebarMiddle`) but no page inventory in `@elevasis/ui`.

## Publish-Surface Discipline

All primitives above flow through published subpaths (`@elevasis/ui/features/<system>`, `@elevasis/ui/layout`, `@elevasis/ui/router/context`). Any new primitive must sync four files per `.claude/rules/ui-package.md`: `package.json` `exports`, `publishConfig.exports`, `rollup.dts.config.mjs`, and `tsup.config.ts`.

The published barrel (`packages/ui/src/provider/published.ts`) remains headless -- no Mantine-dependent visual pieces leak into the external contract.

## Knowledge Browser Customization

The Knowledge Browser follows the same pattern described in this document. The three tiers map directly:

- **Tier 1** -- import `knowledgeManifest` from `@elevasis/ui/features/knowledge` and pass it to `ElevasisSystemsProvider`. No further code required.
- **Tier 2** -- spread the manifest, override `sidebar` to a component that composes `KnowledgeSidebar` + `KnowledgeSidebarMiddle items={customItems}`. Same shape as the CRM example above.
- **Tier 3** -- skip the manifest, own the route, call `bySystem` / `byKind` / `byOwner` from `@elevasis/core/knowledge` directly.

One additional wiring step is required for Knowledge Browser that does not apply to CRM or Lead Gen: add `knowledgePlugin()` from `@elevasis/ui/vite-plugin-knowledge` to `vite.config.ts`. The plugin runs build-time MDX codegen so rendered body components are available at runtime.

See [recipes/customize-knowledge-browser.md](../recipes/customize-knowledge-browser.md) for the full walkthrough including code examples, the CSS import requirement, and the full exports reference.

Phase 1.5 adds two further extension surfaces documented in the same recipe file under the "Phase 1.5" section:

- **Extending the SegmentedControl** -- supply a `segments` prop to `KnowledgeSidebarMiddle` to add custom mount-axis tabs beyond the default five (By Feature, By Kind, By Owner, Governs, Governed By).
- **Replacing `DescribeNodeView`** -- override the `/knowledge/:nodeId` route component to swap the entire main-pane dispatcher, or wrap `DescribeNodeView` from `@elevasis/ui/knowledge` to intercept specific node-kind views while falling back to the platform defaults for the rest.

## What Not to Do

- **Don't fork sidebar files into your app tree.** You own upstream drift forever.
- **Don't redefine `NavItem` locally.** Import it from `@elevasis/ui/layout` so future shape changes stay coherent.
- **Don't monkey-patch `SYSTEM_MANIFESTS`.** It's a published convenience constant; consumers build their own manifest array and pass it to the provider.
- **Don't add discriminated-union sidebar configuration to manifests.** The single pattern (assign a component to `manifest.sidebar`) is intentional.
