<!-- @generated by packages/sdk/scripts/copy-reference-docs.mjs -- DO NOT EDIT -->
<!-- Regenerate: pnpm scaffold:sync -->

﻿---
title: Customize Knowledge Browser
description: Author knowledge nodes and customize the Knowledge Browser in a template-derived project -- from zero-config authoring through sidebar composition, custom dispatchers, and direct query access.
---

# Customize Knowledge Browser

The Knowledge Browser is a built-in feature surface that renders MDX documents called knowledge nodes. In template-derived projects it is pre-wired and ready to use without any configuration changes.

## How It Works

In template-derived projects, `ui/vite.config.ts` imports `elevasisVite` from `@elevasis/ui/vite` and spreads it into the plugins array:

```ts
// ui/vite.config.ts
import { tanstackRouter } from '@tanstack/router-plugin/vite'
import { elevasisVite } from '@elevasis/ui/vite'

export default defineConfig({
  plugins: [tanstackRouter({ autoCodeSplitting: true }), react(), ...elevasisVite()],
})
```

`elevasisVite()` includes `knowledgePlugin()` as one of the plugins it returns. The plugin runs MDX codegen at `buildStart` and handles hot-module updates during `vite dev`. You do not need to touch `vite.config.ts` for the Knowledge Browser to work.

Knowledge node icons are authored as semantic data tokens in MDX frontmatter, not as UI library component names. Use `playbook`, `strategy`, or `reference` by node kind, or a project-owned `custom.*` token when the platform catalog does not cover the concept.

## Author a Knowledge Node

Drop an `.mdx` file anywhere under `core/config/knowledge/nodes/`. Subdirectories are fine -- codegen walks the tree recursively.

Required frontmatter fields:

- `id` -- unique string; convention is `knowledge.{slug}`
- `kind` -- one of `playbook`, `strategy`, or `reference`
- `title` -- display title shown in the Browser tree
- `description` -- one-sentence summary shown in search results
- `updatedAt` -- ISO date string (e.g. `2026-05-04`)

Optional frontmatter fields:

- `icon` -- semantic token such as `playbook` (defaults by kind if omitted)
- `links` -- array of node IDs or System references this node relates to
- `ownerIds` -- array of role IDs that own or maintain this node

Example:

```mdx
---
id: knowledge.internal-runbook
kind: playbook
title: Internal Runbook
description: Operating notes for project-specific support and escalation.
icon: playbook
ownerIds:
  - role.operator
links:
  - system:operations
updatedAt: 2026-05-04
---

## Body content here

Write plain markdown. A small JSX component allowlist is available in the body:
`Card`, `Cards`, `Step`, `Steps`, `Callout`, `Tab`, `Tabs`.
```

On the next dev hot-reload or production build the node appears in the Browser. No manual command is required.

The template ships a starter node at `core/config/knowledge/nodes/welcome.mdx` so the first `pnpm -C ui dev` produces a non-empty Browser. Delete or rewrite it once you have authored your own nodes.

## `elevasis-sdk knowledge:generate` -- Manual One-Shot

The CLI command `elevasis-sdk knowledge:generate` refreshes `core/config/knowledge/_generated/nodes.ts` outside a dev or build cycle. It is useful for two situations:

- Force-refreshing generated output without starting `vite dev` or running `vite build`
- Pre-build pipelines that must compile MDX before TypeScript starts

The template's `pnpm -C ui build` script calls `pnpm -C .. knowledge:generate` explicitly before `tsc` and `vite build`, so production builds regenerate nodes as a first step without requiring a running dev server.

During development the Vite plugin handles codegen automatically on every save. You do not need to run the CLI command manually unless you are operating outside a dev or build cycle.

## CSS

Import the knowledge subpath CSS in your app entry point:

```ts
// ui/src/main.tsx
import '@elevasis/ui/knowledge/index.css'
```

This is required for Mantine-based knowledge components to render correctly. The CSS is not auto-loaded -- per the `@elevasis/ui` publish rules, consumers import CSS subpaths explicitly.

## Customization

The sections below cover optional customization for projects that need to diverge from the platform default. The authoring steps above are sufficient for the common case.

### Default Mount (What the Template Ships)

The template's `ui/src/routes/__root.tsx` registers `knowledgeManifest` from `@elevasis/ui/features/knowledge` in the `ElevasisSystemsProvider` manifest array. That is all that is required for the default Knowledge Browser to appear. The template already does this -- you do not need to add it yourself unless you scaffolded the project manually without using the template.

```tsx
// ui/src/routes/__root.tsx
import { knowledgeManifest } from '@elevasis/ui/features/knowledge'
import { ElevasisSystemsProvider } from '@elevasis/ui'

<ElevasisSystemsProvider
  organizationModel={canonicalOrganizationModel}
  systems={[crmManifest, leadGenManifest, knowledgeManifest]}
>
  {/* ... */}
</ElevasisSystemsProvider>
```

A "Knowledge" sidebar entry appears, routes resolve under `/knowledge`, and the Browser renders with the default tree and node viewer. No additional code required.

### Extend (Custom Nav Items, Default Chrome)

Spread the manifest and override `sidebar` to a custom component. The custom component composes `KnowledgeSidebarMiddle` with a modified items array. This is the shortest path to adding project-specific nav entries while keeping the platform chrome.

```tsx
// ui/src/features/knowledge/sidebar.tsx
import {
  KnowledgeSidebarMiddle,
  KNOWLEDGE_ITEMS,
} from '@elevasis/ui/knowledge'
import { knowledgeManifest, KnowledgeSidebar } from '@elevasis/ui/features/knowledge'
import { IconBook } from '@tabler/icons-react'

const customItems = [
  ...KNOWLEDGE_ITEMS,
  {
    label: 'Internal Runbooks',
    to: '/knowledge/by-owner/internal',
    icon: IconBook,
    exact: false,
  },
]

const CustomKnowledgeSidebar = () => (
  <KnowledgeSidebar>
    <KnowledgeSidebarMiddle items={customItems} />
  </KnowledgeSidebar>
)

export const customKnowledgeManifest = {
  ...knowledgeManifest,
  sidebar: CustomKnowledgeSidebar,
}
```

Register the custom manifest in place of the default:

```tsx
// ui/src/routes/__root.tsx
import { customKnowledgeManifest } from '../features/knowledge/sidebar'

<ElevasisSystemsProvider
  systems={[crmManifest, leadGenManifest, customKnowledgeManifest]}
>
  {/* ... */}
</ElevasisSystemsProvider>
```

`KNOWLEDGE_ITEMS` is the exported default items array (matches the `CRM_ITEMS` shape from `@elevasis/ui/features/crm`). Spread it and append project-specific entries.

### Replace (Project-Owned UI)

Skip `knowledgeManifest` entirely and build a project-owned route that calls `@elevasis/core/knowledge` directly. Use this when the project's knowledge UI has a different layout, navigation structure, or data sources than the platform default.

```tsx
// ui/src/routes/knowledge/index.tsx
import { bySystem } from '@elevasis/core/knowledge'
import { useElevasisSystems } from '@elevasis/ui'

function MyCustomKnowledgeView() {
  const { graph } = useElevasisSystems()
  const nodes = bySystem(graph, 'sales.crm')
  return <MyOwnLayout nodes={nodes} />
}
```

The project owns the renderer, layout, search, and navigation. The data layer is shared -- the org model remains the source of truth, and query results are consistent with what `elevasis-sdk knowledge:*` returns at the CLI.

The `knowledgePlugin()` Vite plugin is still needed in this path because it generates the `KNOWLEDGE_BODIES` component map that any project-local renderer will need to display MDX bodies. The plugin is included automatically via `elevasisVite()` in the template's `vite.config.ts`.

## Exports Reference

| Export                                                   | Package                              | Purpose                                                   |
| -------------------------------------------------------- | ------------------------------------ | --------------------------------------------------------- |
| `elevasisVite`                                           | `@elevasis/ui/vite`                  | Umbrella plugin factory; includes `knowledgePlugin`       |
| `knowledgeManifest`                                      | `@elevasis/ui/features/knowledge`    | Default `SystemModule` manifest; spread or pass directly  |
| `KnowledgeSidebar`                                       | `@elevasis/ui/features/knowledge`    | Default sidebar wrapper                                   |
| `KnowledgeSidebarMiddle`                                 | `@elevasis/ui/features/knowledge`    | Sidebar middle section; accepts `items?: NavItem[]`       |
| `KNOWLEDGE_ITEMS`                                        | `@elevasis/ui/knowledge`             | Default sidebar nav items array                           |
| `KnowledgeBrowser`                                       | `@elevasis/ui/knowledge`             | Composed browser: search bar + detail view                |
| `KnowledgeTree`                                          | `@elevasis/ui/knowledge`             | Tree for one mount axis                                   |
| `KnowledgeNodeList`                                      | `@elevasis/ui/knowledge`             | Flat list of node cards                                   |
| `KnowledgeNodeView`                                      | `@elevasis/ui/knowledge`             | Single node: title, kind badge, body, edge sidecar        |
| `KnowledgeSearchBar`                                     | `@elevasis/ui/knowledge`             | FlexSearch over title, summary, and body                  |
| `KnowledgeMDXProvider`                                   | `@elevasis/ui/knowledge`             | React context supplying the component allowlist           |
| `knowledgePlugin`                                        | `@elevasis/ui/vite-plugin-knowledge` | Vite plugin for build-time MDX codegen + HMR (direct use) |
| `bySystem`, `byOntology`, `byKind`, `byOwner`, `governs`, `governedBy` | `@elevasis/core/knowledge`           | Pure query functions for project-owned custom UI          |

For template-derived projects, import `elevasisVite` from `@elevasis/ui/vite` rather than `knowledgePlugin` directly. The direct `knowledgePlugin` import from `@elevasis/ui/vite-plugin-knowledge` remains available for advanced or non-template wiring.

## Decision Tree

- **Just want knowledge nodes to show up?** Author `.mdx` files under `core/config/knowledge/nodes/`. That is it.
- **Need extra sidebar nav entries or a different items order?** Spread the manifest, override `sidebar` with a composed `KnowledgeSidebarMiddle` using custom `items`.
- **Need a completely different layout, data sources, or knowledge UX?** Skip the manifest, own the route, call `@elevasis/core/knowledge` directly.
- **Need to extend the MDX component allowlist for custom body components?** Pass `extraComponents` to `KnowledgeMDXProvider` in your custom renderer.
- **Need to add a 6th segment to the sidebar strip?** See the SegmentedControl extension section below.
- **Need a custom main-pane dispatcher?** See the DescribeNodeView section below.

## Phase 1.5 Extensions

Phase 1.5 adds two extension surfaces on top of the customization tiers above: a mount-axis `SegmentedControl` strip in the sidebar and a `DescribeNodeView` dispatcher in the main pane. Both are composable -- you can extend the defaults or replace them entirely.

### Extending the SegmentedControl (add a 6th segment)

Phase 1.5 ships `KnowledgeSidebarMiddle` with a five-segment strip above the search bar:

```
[ By System ] [ By Kind ] [ By Owner ] [ Governs ] [ Governed By ]
```

Each segment maps to a mount axis exposed by `@elevasis/core/knowledge`. The strip is a `SegmentedControl` from `@mantine/core`; selected axis is local sidebar state and does NOT affect the URL.

To add a 6th segment, supply a `segments` prop to `KnowledgeSidebarMiddle`:

```tsx
import {
  KnowledgeSidebar,
  KnowledgeSidebarMiddle,
  KNOWLEDGE_SEGMENTS,
} from '@elevasis/ui/knowledge'
import { knowledgeManifest } from '@elevasis/ui/features/knowledge'
import { byOwner } from '@elevasis/core/knowledge'

// KNOWLEDGE_SEGMENTS is the default five-item array; spread and append.
const customSegments = [
  ...KNOWLEDGE_SEGMENTS,
  {
    value: 'by-team',
    label: 'By Team',
    // renderBody receives (graph, knowledgeNodes, selectedNodeId, onSelectNode).
    // Return any ReactNode -- the strip swaps the tree area for your component.
    renderBody: ({ graph, knowledgeNodes, onSelectNode, selectedNodeId }) => {
      const teamNodes = byOwner(graph, 'team:product', knowledgeNodes)
      return (
        <MyTeamList
          nodes={teamNodes}
          onSelect={onSelectNode}
          activeId={selectedNodeId}
        />
      )
    },
  },
]

const CustomKnowledgeSidebar = () => (
  <KnowledgeSidebar>
    <KnowledgeSidebarMiddle segments={customSegments} />
  </KnowledgeSidebar>
)

export const customKnowledgeManifest = {
  ...knowledgeManifest,
  sidebar: CustomKnowledgeSidebar,
}
```

Rules and constraints:

- `value` must be unique across all segments in the array.
- `label` is the display string inside the `SegmentedControl`; keep it short (under 10 characters) to fit the 320 px sidebar width.
- `renderBody` receives the same props as the built-in axis renderers. The tree area is replaced by whatever the function returns while that segment is active.
- The first segment in the array is the default selection on mount.
- Segment selection is reset to the first item whenever the user navigates to `/knowledge/command-view*` (the command-view sub-route bypasses the strip entirely and renders `CommandViewSidebarContent` instead).

### Replacing `DescribeNodeView` Entirely

Phase 1.5 introduces `DescribeNodeView` as the main-pane dispatcher. It receives any graph node (not just knowledge nodes) and routes to a per-kind view component:

```tsx
function DescribeNodeView({ node, graph, onNavigateToNode }) {
  switch (node.kind) {
    case 'knowledge':     return <KnowledgeNodeView node={node} graph={graph} onNavigateToNode={onNavigateToNode} />
    case 'system':       return <SystemDescribeView node={node} graph={graph} onNavigateToNode={onNavigateToNode} />
    case 'resource':      return <ResourceDescribeView node={node} graph={graph} onNavigateToNode={onNavigateToNode} />
    case 'organization':  return <OrganizationDescribeView node={node} graph={graph} onNavigateToNode={onNavigateToNode} />
    default:              return <GenericDescribeView node={node} graph={graph} onNavigateToNode={onNavigateToNode} />
  }
}
```

**Keep sidebar chrome, replace main pane:** Override the route component in your project's route tree. The default route for `/knowledge/:nodeId` renders `DescribeNodeView`. Replace it with a project-owned component that receives the same `node` and `graph` props:

```tsx
// ui/src/routes/knowledge/$nodeId.tsx
import { useParams } from '@tanstack/react-router'
import { useElevasisSystems } from '@elevasis/ui'

export default function KnowledgeNodeRoute() {
  const { nodeId } = useParams({ from: '/knowledge/$nodeId' })
  const { graph } = useElevasisSystems()
  const node = graph.nodes.find((n) => n.id === nodeId)

  if (!node) return <MyNotFoundView />

  // Completely custom dispatcher -- no platform chrome in the main pane.
  return <MyDescribeNodeView node={node} graph={graph} />
}
```

The sidebar (managed by the manifest) continues to operate normally. Tree clicks still navigate to `/knowledge/:nodeId`. Only the main pane changes.

**Own everything:** Skip `knowledgeManifest` and build your own route tree as described in the Replace section above. Call `@elevasis/core/knowledge` directly for data and supply your own dispatcher.

### Overriding Per-Kind Views

If you want the default dispatcher behavior for most kinds but need a custom layout for one kind, wrap the exported `DescribeNodeView` and intercept the specific kind before falling back to the platform dispatcher:

```tsx
import { DescribeNodeView, SystemDescribeView } from '@elevasis/ui/knowledge'
import type { DescribeNodeViewProps } from '@elevasis/ui/knowledge'

function MyDescribeNodeView(props: DescribeNodeViewProps) {
  if (props.node.kind === 'system') {
    return <MyFeatureDetailView node={props.node} graph={props.graph} onNavigateToNode={props.onNavigateToNode} />
  }

  return <DescribeNodeView {...props} />
}
```

`MyDescribeNodeView` has the same props interface as `DescribeNodeView` (`node`, `graph`, `onNavigateToNode`, plus optional context props). You can drop it anywhere the default dispatcher is used.

The platform kind-specific components (`KnowledgeNodeView`, `SystemDescribeView`, etc.) are exported from `@elevasis/ui/knowledge` so you can compose them inside your wrapper rather than replacing them from scratch:

```tsx
function MyDescribeNodeView(props: DescribeNodeViewProps) {
  if (props.node.kind === 'system') {
    return (
    <>
      {/* Platform view for the standard fields */}
        <SystemDescribeView {...props} />
      {/* Project-specific extension panel below */}
        <MySystemAuditPanel systemId={props.node.id} />
    </>
    )
  }

  return <DescribeNodeView {...props} />
}
```

### Phase 1.5 Exports Reference

The following exports are added in Phase 1.5 on top of the exports table above:

| Export                         | Package                  | Purpose                                                                      |
| ------------------------------ | ------------------------ | ---------------------------------------------------------------------------- |
| `DescribeNodeView`             | `@elevasis/ui/knowledge` | Default graph-node dispatcher; routes by `node.kind`                         |
| `SystemDescribeView`           | `@elevasis/ui/knowledge` | Minimal System node view: path, icon, lifecycle, hierarchy, edges            |
| `ResourceDescribeView`         | `@elevasis/ui/knowledge` | Minimal resource node view: resourceType, description, edges                 |
| `OrganizationDescribeView`     | `@elevasis/ui/knowledge` | Minimal organization node view: label, edges                                 |
| `GenericDescribeView`          | `@elevasis/ui/knowledge` | Key/value fallback for unknown kinds                                         |
| `KNOWLEDGE_SEGMENTS`           | `@elevasis/ui/knowledge` | Default five-segment array for `KnowledgeSidebarMiddle`                      |

`KnowledgeSidebarMiddle` gains an optional `segments?: KnowledgeSegment[]` prop (defaults to `KNOWLEDGE_SEGMENTS`). The `KnowledgeSegment` type is exported from `@elevasis/ui/knowledge`.

## See Also

- [Composition and Extensibility](../composition-extensibility.mdx) -- the general sidebar override pattern this recipe follows (CRM + delivery examples)
- [Query the Knowledge Graph](query-the-knowledge-graph.md) -- CLI walkthrough of the same six query axes
- [Contracts Reference](../reference/contracts.md) -- `SystemModule`, `NavItem`, `OrgKnowledgeNode` TypeScript shapes
