---
name: create-vmax-tools-page
url: https://vmax.ai/skill/template
description: Clone the /-/vmax-tools-template shell into a CSS-perfect replica — the same measurements, fonts, spacing, layout, and structure — for any context (an evaluation viewer, a data explorer, a dashboard, an annotation tool, anything). Use when standing up or restyling a tools page, reusing the ApplicationNavigation / ToolbarControlsFixed chrome and the sidebar, composing a body from the next-vmax-tools Form* primitives (tables, diffs, JSON explorer, graph compares, inputs, modals, a terminal window), or adding a new Form* component for a case they do not cover.
---

# create-vmax-tools-page

## Overview

The `/-/vmax-tools-template` page is a **reference implementation to clone for any context**. The whole point of this skill is that anyone can stand up a new tools page that is a CSS-perfect replica of it — the same measurements, fonts, spacing, layout, and structure — for whatever they need: sharing controls, an evaluation viewer, a data explorer, a training dashboard, an annotation tool. The shared chrome and the theme tokens stay constant; the body, the columns, and the interactions are what you compose.

**These are guidelines, not a cage.** The invariants are the structure — the shared `ApplicationNavigation` / `ToolbarControlsFixed` chrome, the `--theme-*` tokens and grid-block measurements, the `TOOLS_PAGES` registry, and the `Form*` kit. WITHIN those you are meant to build genuinely bespoke experiences, not only swap the content well: new column layouts, new sidebar compositions, new cross-page flows and interactions. The `client` / campaign-template variants are the worked proof — they add a double sidebar, a settings rail, purpose-built `columnVariant`s, and handoffs that seed one page's state from another, all still on the same chrome and tokens. When a context needs a layout or interaction the catalog below doesn't have, add a new `columnVariant`, a new sidebar composition (from the same `Sidebar` / `SidebarItem` / `SidebarSubItem` trio), or a new `Form*` — that IS staying within the structure. What you do not do is fork the chrome, inline a value a token already names, or drift the measurements; that discipline is what keeps every bespoke page reading as one system.

The whole kit lives in `next-vmax-tools/` (alias `@nextjs-vmax-tools/*`):

- **`AppContainer` is the shell** — `ApplicationNavigation` (the top bar), a sidebar, and `ToolbarControlsFixed` (the fixed bottom toolbar), wrapping the body columns a caller passes.
- **Reuse the chrome as-is.** `ApplicationNavigation` and `ToolbarControlsFixed` are shared so every tools page reads the same; lean on the **sidebar** for overflow — a long page's table of contents, links out — wherever it earns its place.
- **Compose the body from `Form*` components** (tables, diffs, JSON explorer, graphs, inputs, a modal, a terminal window). When a case isn't covered, **add a new `Form*.tsx` with its paired `.module.css`** — that is the expected way to grow the kit, not a one-off.
- **It stays a pixel replica because everything is built off the variables** — sizes off `--theme-grid-block` / `--theme-grid-block-applications`, colour/border/font off `--theme-*`, the body on `--theme-font-family-client-apps`. Never inline a value a variable already names (see [Conventions](#conventions)); that discipline is what makes a clone match the template exactly across both themes.

The routes read the `TOOLS_PAGES` registry in `tools-pages.tsx`, which also holds the default sidebar's `EXAMPLE_TEMPLATE_SECTIONS` and `SHARED_SIDEBAR_GROUPS` — the one sidebar the `authenticated`, `islands`, and `client-legacy` pages all render.

This skill is the narrow one for the tools shell. For the broader `next-vmax` application-component library (`ApplicationWindow`, `DefaultLayout`, `Logo`, the graph references), read [`create-old-vmax-application-ui`](../create-old-vmax-application-ui/SKILL.md) too (deprecated).

## The registry is the single source

`next-vmax-tools/tools-pages.tsx` holds one `TOOLS_PAGES` row per page, the way `next-vmax/islands/island-scenes.ts` holds one row per scene. A row **is** the route and its metadata:

```tsx
export interface ToolsPage {
  label: string;
  description: string;
  icon: React.ReactNode;
  sections: ToolsPageSection[];
  columns: React.ReactNode[];
  emptySidebar?: boolean;
  columnVariant?: 'client-ui-columns' | 'client-usage-columns' | 'horizontal-columns' | 'island-scene';
  sidebarGroups?: ToolsSidebarGroup[];
  rightLinks?: NavigationRightLinkItem[];
  trailingLink?: { label: string; href: string };
  serverData?: 'research-posts';
}
```

`ToolsSidebarGroup` / `ToolsSidebarRoute` (a group is a sidebar `Item`, each route a `SubItem`):

```tsx
export interface ToolsSidebarRoute {
  path: string;
  description?: string;
  icon: React.ReactNode;
  href?: string;
  tone?: 'system';
  action?: 'write' | 'modal' | 'api-key' | 'sign-out';
  viewerProfile?: boolean;
  viewerOnly?: boolean;
  signedOutOnly?: boolean;
  viewerPath?: boolean;
}

export interface ToolsSidebarSelect {
  label: string;
  icon: React.ReactNode;
  selectDefaultHref?: string;
  routes: ToolsSidebarRoute[];
}

export interface ToolsSidebarGroup {
  label: string;
  description?: string;
  icon: React.ReactNode;
  isViewer?: boolean;
  preserveMobileLabel?: boolean;
  selects?: ToolsSidebarSelect[];
  routes?: ToolsSidebarRoute[];
}
```

A group that declares `isViewer` (even `false`) opts into the focus-primary state: its Item glass button renders `active` when the value is `true`. The `[...tool]` route computes the real value from `Server.setup` (the signed-in viewer) and injects it into any group whose `isViewer` is defined; no shipped group declares it today, since the `Signed in` audience group was replaced by the shared `Vmax Tools` group, whose auth awareness lives on its rows instead (`signedOutOnly`, `viewerProfile`, `viewerOnly`, and the gated `api-key` / `sign-out` actions).

`label` is the route metadata title; `description` feeds the page metadata (title + OpenGraph + Twitter); `icon` (a `next-vmax/Icon` glyph) and `sections` are page metadata not drawn while the template is one page; `columns` holds one node per available content column; `emptySidebar` (default off) renders the shell's sidebar frame with no items — the blank-canvas variant, used by the `client` page (`FormClientExperience` + `FormClientExperienceOptions`); `columnVariant: 'client-ui-columns'` swaps the default equal-width column grid for the top-nav's fixed-rail-plus-fluid-centre technique (a measured fluid first column, a fixed 240px second column, reverting to the default grid under 880px — see `next-vmax-tools/AGENTS.md`); `columnVariant: 'horizontal-columns'` (the `horizontal` page) drops the sidebar and renders every column as a horizontally scrollable rail of 424px full-height columns, ignoring `?columns` so all of them are always shown — a layout that reads one column at a time on mobile (see `next-vmax-tools/AGENTS.md`). `columnVariant: 'island-scene'` (the `islands` page) renders one fluid full-height column holding `FormIslandScene` (the isometric engine, filling `calc(100dvh - 48px)`) and hides the sidebar under 568px so the scene is full-screen on mobile. `columnVariant: 'client-usage-columns'` (the `campaign-template` page — the key renamed from `client-usage`, whose spelling the variant keeps) is the `client` experience as a self-driven state machine with no query params: campaign history always shown, the task sidebar appears only after a campaign is clicked, and the middle shows the prompt+`SettingsColumn` when no task is selected or the two-column kitchen-sink when one is — its `columns` are positional (`[prompt, FormTemplate, FormTemplateGraphs]`) and the "Start a campaign" control replaces `Col-1`/`Col-2`/`Settings` (see `next-vmax-tools/AGENTS.md`); `hideColumnControls: true` drops the `Col-1` / `Col-2` items from BOTH control bars for a single-column page where side-by-side columns are meaningless (the `profile` / `profile-empty` rows set it, and the live `/{username}` and `/waitlist` pages pass it to `AppContainer` directly — see **Col-1 / Col-2** below); `rightLinks` (the `research` page) renders the public home/sign-in top bar instead of the tools controls (no Holy/Light/Grain/Debug, no fixed pill) — see **`rightLinks`** under [The shell](#the-shell-appcontainer). The catch-all `[...tool]` route reads these flags and passes them to `AppContainer`.

- A page may supply its own sidebar with `sidebarGroups` (`ToolsSidebarGroup[]` — each group a `label`, optional `description`, an `icon`, and `routes` of `{ path, description, icon, href?, tone? }`). When set, `AppContainer` renders each group as a **`SidebarItem`** header followed by a **`SidebarSubItem`** per route — the same components (`Sidebar` / `SidebarItem` / `SidebarSubItem` / `SubItemSelect`, each in `next-vmax-tools/` with its own `.module.css`) that render the default template sidebar, so there is ONE Item/SubItem pattern, not two. A group may also carry `selects` — dropdown SUB-ITEMS rendered between its header and its `routes`: each `ToolsSidebarSelect` (a `label`, `icon`, optional `selectDefaultHref`, and its own `routes`) collapses a flat catalog into ONE **`SubItemSelect`** — the SubItem row shape (bordered-square icon left) carrying a compact `FormSelect`-surface dropdown whose options are the href routes and whose change navigates; the icon box is a button that opens the currently selected option; the current pathname selects the active option, `selectDefaultHref` names the option a base route without a matching segment should show. The `World Examples` group is the only consumer — its two selects hold the `Distant Islands` scene and the ~40-preset `Island` catalog, so a new preset grows an option list, not the sidebar; route inventories with descriptions, actions, or static patterns stay SubItem lists. Every field is optional and hides when absent: a `description` stacks a muted second line under the label (smallest FormTemplate font, single-line ellipsis with the full text on `title` hover); `href` makes a row a link (absent, it renders as static text, e.g. a `/[username]/-/[id]` pattern you cannot visit — unless the row sets `viewerPath: true`, which substitutes the signed-in viewer's real path for BOTH the label and the href; a kit capability with no shipped caller since the `/[username]` placeholder row was removed with the `Signed in` group); `action: 'write' | 'modal' | 'api-key' | 'sign-out'` makes it a clickable button instead (a serializable token the client `AppContainer` maps to a handler — `'write'` creates a post and opens the editor, live only for a signed-in viewer; `'modal'` opens `FormModal`; `'api-key'` replays the legacy get-API-key flow, a `window.confirm` warning then the `vmax` cookie key in an `alert`, and its row renders ONLY when the route passed `viewerVerified`, a signed-in viewer at the verified tier; `'sign-out'` replays the legacy sign-out, a `window.confirm` then removing the `vmax` cookie and redirecting `/`, and its row renders ONLY when the route passed `viewerSignedIn`; a `'write'` row may add `viewerOnly: true` to hide entirely for a signed-out visitor instead of degrading to static text, and `signedOutOnly: true` is the inverse: the row hides for a signed-in viewer — the `Vmax Tools` group carries the signed-out-only `Sign in` row and the viewer-gated `Profile`, `Waitlist` (`viewerOnly`, a plain link to the owner-only `/waitlist` page), `Create new paper`, `API key`, and `Sign out` rows); `viewerProfile` is the same serializable-token idea for a LINK: `AppContainer` renders the row (labelled by its `path`) as a link to `/{viewerUsername}` when the route passed a signed-in viewer and drops it entirely otherwise — the `[...tool]` route fetches the viewer for any page whose groups carry one, and the `Vmax Tools` group's `Profile` row works this way; `tone: 'system'` strikes a SubItem's label through for plumbing routes no user should open; a group's `isViewer` lights its header glass with the focus-primary state; a group's `preserveMobileLabel` opts its header AND all its routes out of the sub-568px icon-only collapse, so the rail keeps its text at every width (the `research` sidebar sets it, since a column of identical post icons identifies nothing). Setting it on ANY group also switches the shell to the pinned-rail layout: the sidebar holds 240px at every width and the row becomes an `overflow-x: auto` scroller under 880px with `.content` floored at `min-width: 568px`, so a narrow viewport scrolls sideways rather than squeezing the column, matching the `.campaignRoot` convention and the `.column` floor every multi-column page uses. Build a new sidebar by composing these components; never re-inline the row JSX. Reuse whole groups across sidebars by composing the same group object: the `Vmax Tools` group is the model — the one `VMAX_TOOLS_SIDEBAR_GROUP` const leads `SHARED_SIDEBAR_GROUPS` and is spread into `researchSidebarGroups` with `preserveMobileLabel: true`, so `/research` and every `SHARED_SIDEBAR_GROUPS` page render the same rows from one object (the old `sharedSidebarRoutes` per-row lookup helper was deleted with the audience groups it read from). `SHARED_SIDEBAR_GROUPS` is the reference — ONE definition (the `Vmax Tools` auth group, the `World Examples` scene dropdowns, the `Designer Preview` route inventory) that the `authenticated` page (over a single `FormEmptyColumn`), the `islands` page (over the scene), and the `client-legacy` page (over the deprecated component gallery) all render, so the sidebar cannot drift between them. Both route groups keep their rows alphabetized by `path` — the standing convention for every static sub-item route inventory (`next-vmax-tools/AGENTS.md`).
- The sidebar (`AppContainer`) is otherwise **not** a page switcher; on the default template it holds one fixed group from `tools-pages.tsx`: a "Template" Item whose SubItems (`EXAMPLE_TEMPLATE_SECTIONS`) anchor the default page's headings by id — `FormTemplate` in the first column, `FormTemplateGraphs` in the second; sibling examples share one grouped section ("Comparisons", "Elements", "Tables", "Graphs") to keep the list short, and the list is alphabetized by label. Each Item's icon is the app's glass square button (`ButtonActionGlass`); its plain-square SubItems read a level apart, and each SubItem is a link (`sectionAnchorHref`) that carries the current query string forward: it jumps to its heading by `id`, forcing `?columns=2` first for a section marked `columns: 2` (a second-column `FormTemplateGraphs` heading) so it is rendered before the scroll. Every SubItem id MUST match the heading id it anchors — no test guards this, so when adding or renaming a section check the id against the `FormTemplate` / `FormTemplateGraphs` heading it points at by hand.
- The bare `/-/vmax-tools-template` route serves `DEFAULT_TOOLS_PAGE_KEY`; the `[...tool]` catch-all route 404s on any key that is not registered.
- `?columns=N` opens N of a page's `columns` (AppContainer clamps to what the page actually provides).

The module is neutral (no `'use client'`): the server routes read `columns` + metadata.

## Reference routes (the shipped templates)

These are the live templates to clone from — each is one `TOOLS_PAGES` row. Open the route, then copy its row and body. Every URL is under `/-/vmax-tools-template`. When you cannot open the route (no repo or no running dev server), the row shape in each cell plus [Design tokens & measurements](#design-tokens--measurements-the-values-that-make-a-clone-pixel-perfect) below is enough to rebuild it pixel-for-pixel — that section carries every measurement these six were built against.

| Route | Registry key + shape | What it demonstrates |
|---|---|---|
| `/` (bare) and `/?columns=2` | `DEFAULT_TOOLS_PAGE_KEY` (default). `columns: [FormTemplate, FormTemplateGraphs]`; default template-TOC sidebar (`EXAMPLE_TEMPLATE_SECTIONS`). | The two-column kitchen-sink page — every `Form*` primitive over the sharing form (col 1) and the graph catalogue (col 2, `?columns=2`). The default clone target. |
| `/islands` and `/islands/{key}` | `islands`. `columnVariant: 'island-scene'`; one fluid `FormIslandScene` column; sidebar hidden under 568px. | A single full-height isometric scene filling `calc(100dvh - 48px)`; `FormIslandScene` reads `{key}` from the pathname (`distant-islands` → `WorldStage`, any `isIslandKey` → `IslandStage`, else `DEFAULT_ISLAND_KEY`). The scene catalog. The scene CONTENT is the `three-isometric-engine` + `next-vmax/islands` presets, not something these tokens can rebuild — without the repo, clone the shell/variant and treat the column as an embed slot. |
| `/authenticated?columns=2` | `authenticated`. `sidebarGroups: SHARED_SIDEBAR_GROUPS` (the same sidebar the `islands` page renders); one `FormEmptyColumn`. | The shared data-driven sidebar in isolation — the auth-aware `Vmax Tools` group over the `World Examples` scene dropdowns and the `Designer Preview` route inventory; the `[...tool]` route passes the viewer trio from `Server.setup`, so the `Sign in` / `Profile` / `Create new paper` / `API key` / `Sign out` rows track the real session. The payload IS the sidebar. |
| `/client-legacy` | `client-legacy`. `sidebarGroups: SHARED_SIDEBAR_GROUPS`; one `next-vmax/Application` column. | The deprecated `next-vmax` component gallery ([`create-old-vmax-application-ui`](../create-old-vmax-application-ui/SKILL.md)) hosted in the tools shell's content well beside the shared sidebar — the old standalone `/-/app-components` route redirects here. Its body is the `next-vmax` library, not the `Form*` kit; clone the hosting pattern (a registry row wrapping an existing client component as one column), not the body. |
| `/horizontal` | `horizontal`. `columnVariant: 'horizontal-columns'`; eight `FormHorizontalColumn`s. | The sidebar-less horizontal rail — every column present as a 424px full-height card, ignoring `?columns` (so `Col-2` is hidden via `ToolsControls.singleColumn`); reads one column at a time on mobile. |
| `/client?columns=2&settings=true` | `client`. `columnVariant: 'client-ui-columns'`; fluid `FormClientExperience` + fixed `FormClientExperienceOptions`. | The double-sidebar client app **kitchen-sink**: `SidebarCampaigns` (clickable campaign history — selecting one focuses it and re-rolls a fresh task list via `campaigns.ts` `tasksForCampaign`, a deterministic seeded shuffle so SSR is stable) + `SidebarTasks` (`{campaign}’s Tasks`, one selectable `WebTerminalWindow` per task with a `»` primary-accent caret on the selected one). The selection state lives in `AppContainer`. `?settings=true` swaps the second column for `SettingsColumn` and forces `columns=2`; `Col-1` / `Col-2` / `Settings` are the three mutually-exclusive views of that column. The three CTAs hand off to `/-/vmax-tools-template/campaign-template`: the sidebar's "Start a campaign" button opens its create screen, clicking a task opens it with that campaign+task selected (the kitchen-sink), and the prompt's arrow button opens the task-selected end result. Double-sidebar + settings + handoff detail: `next-vmax-tools/AGENTS.md`. |
| `/research` (productionized at `/research`, `RESEARCH_URL`) | `research`. `serverData: 'research-posts'`; `rightLinks: HOME_NAVIGATION_RIGHT_LINKS`; one `FormResearch` column. | The public research index, and the worked example of a **server-backed registry page**. `PUBLISHED_POSTS` (`common/navigation-links.ts`) curates only WHICH posts appear and in what order; every rendered field is fetched live per request by `Server.getResearchPosts()`: title, date, authors, and abstract. The route resolves the `serverData` token, then builds the column (`researchColumns`) and the sidebar (`researchSidebarGroups`) from that ONE result, so the two cannot drift. Each item is text only: the date as an uppercase label, the title in a `FormLongFormHeading` (the post `h1` serif recipe), the authors as a two-column `FormList`, the abstract. No images, no buttons, no category kicker. Its `rightLinks` + `trailingLink` give it the `/sign-in` top bar verbatim (Research, Careers, the Team dropdown, then Home), with NO Holy/Light/Grain/Debug cluster and no fixed pill. |
| `/campaign-template` | `campaign-template` (renamed from `client-usage`). `columnVariant: 'client-usage-columns'` (the historical spelling); positional `columns: [FormClientExperience, FormTemplate, FormTemplateGraphs]`. | The **same client app as page states** — for showing the flow clearly. Direct visits carry no query params; a `/client` handoff may seed the entry state through `?campaign=&roll=&task=`, which the shell consumes and strips on arrival. Campaign history always shows; clicking a campaign reveals its task sidebar; clicking a task swaps the middle to the two-column kitchen-sink and hides the settings panel; the prompt + `SettingsColumn` show only when no task is selected; the "Start a campaign" control (nav + fixed bar, replacing the `Col`/`Settings` items) and the sidebar's matching "Start a campaign" button both reset to the prompt. State machine detail: `next-vmax-tools/AGENTS.md`. |
| `/profile` and `/profile-empty` | `profile` / `profile-empty`. `sidebarGroups: RESEARCH_SIDEBAR_GROUPS` (the exact `/research` rail); one `FormProfile` column each. | The author profile ported onto the research layout, in its two states. Both open on two explainer paragraphs (posts are written in full markdown; the markdown generates the post's three.js island) at the research body measure, held by an `.intro` wrapper: the measure cap sits on the intro (paragraphs + divider / empty sentence), while the post feed beneath the divider runs FULL BLEED across the content well. `/profile` renders five fixture posts (`profile-fixtures.ts`) as the legacy `PostListWithLayout` rows (markup unchanged, paddings snapped to the `--theme-grid-block-applications` half-step) — the owner variant with every option: Edit, Public ≫ 🌎 / Private ≫ 🔓, 📃 Paper, Created/Updated, × Delete (inert here; the live port wires `onDeletePost`) — beside the SAME sidebar `/research` renders (a maintainer decision: templates use the prod rail, never a template-only composition). `/profile-empty` drops the list for the empty-state sentence. Both rows set `hideColumnControls: true`, so the top bar and fixed pill drop `Col-1` / `Col-2` (a profile is one column). The waitlist is NOT part of `FormProfile`; it is its own `FormWaitlist` body on the live `/waitlist` page. The SAME component is the live `/{username}` profile body (mounted there with real posts, `isViewer`, and `live`); these templates are its fixture-driven twins. Row anatomy: `next-vmax-tools/AGENTS.md`. |
| `/input-page-template` | `input-page-template`. `sidebarGroups: RESEARCH_SIDEBAR_GROUPS`; `columns: [FormUserSetup, FormInputPageTemplate]`. | The reusable **one-field page**, shown as a worked flow: `FormUserSetup`, the REAL account-setup screen the `/{username}` route serves, rendered here in demo mode (no `viewer` prop: same copy and validation, HTTP save and resend simulated); `?columns=2` reveals the bare template demo beside it. The template's every piece is a prop with a worked default (`heading`, `paragraph`, `text`, `label`, `placeholder`, `defaultValue`, `submittedLabel`, `submittedDisplay`, `preview`, `validate`, `onSubmit`, `children`), so a clone re-labels or re-wires it without touching the component. Its sidebar is the STATIC `RESEARCH_SIDEBAR_GROUPS`, deliberately not `serverData: 'research-posts'` (the route's serverData branch would replace the columns with the research feed too); it keeps the full tools controls, unlike `/research`. See `next-vmax-tools/AGENTS.md`. |

## Add a tools page

1. **Build the body** — a `'use client'` component in `next-vmax-tools/` composing the primitives below, paired with its `.module.css`. `next-vmax-tools/FormTemplate.tsx` is the worked reference: it composes nearly every primitive over the sharing form. Reuse an existing body (`FormTemplate`, `FormTemplateGraphs`) when it already fits.
2. **Register it** — add a `TOOLS_PAGES` row in `tools-pages.tsx`: a `label`, `description`, an `icon` from `@nextjs-vmax/Icon`, `sections`, and `columns` (one `<YourBody />` per column). Give each column element a stable `key`. Serve it at the bare path by pointing `DEFAULT_TOOLS_PAGE_KEY` at its key.
3. **The route renders it.** `app/-/vmax-tools-template/[...tool]/page.tsx` (a catch-all) renders any registered key from `tool[0]`, so a deeper path like `/islands/{type}` reaches the one `islands` page and the body reads `{type}` from the pathname — no per-type folders and `buildToolsPageMetadata` gives it a title and card. The sidebar is not a page switcher today — reintroduce a registry-driven page-nav in `AppContainer` if a page needs to be reachable from the sidebar.

### Host a whole application on the chrome (`campaignShell`)

Some surfaces are a live application, not a registry body: they need a caller-composed sidebar (live records, not the demo `SidebarCampaigns`) and their own state machine. Rather than force these through `TOOLS_PAGES`, `AppContainer` takes an additive `campaignShell?: { sidebar, tasksSidebar?, settingsColumn?, showSettings?, settingsActive?, onToggleSettings? }` prop — a purely additive render branch (checked first, so every existing variant stays byte-identical) that reuses the shell chrome (nav, grain, fixed toolbar) and the campaign-template column geometry (the 'client-usage-columns' variant) (fluid content well; optional fixed 480px settings rail when `showSettings`), with a sticky independent sidebar (`.campaignSidebar`) instead of `ScrollSpacer`. The **`/-/vmax-tools-template/campaign-template/mulberry`** console is the worked reference: `FormCampaignConsole` reads `?campaign=` (via `useSearchParams` in a `useState` initializer, so SSR matches the first client render) and a checked-in fixtures module, then routes to a composer, an environment-creation build console, a completed dashboard, or a failed state — all composed from base `Form*` primitives plus a small set of campaign `Form*` components (`FormCampaignSummary`, `FormCampaignBuildConsole`, `FormCampaignResults`, `FormCampaignTasksSidebar`, `FormCampaignTaskTrace`, `FormCampaignActivityLog`, `FormCampaignComposer`, `FormCampaignConfigRail`, `FormCampaignSidebar`). The campaign components are COMPOSITIONS over the shared kit, never parallel implementations: `FormCampaignSidebar` renders its history rows through `Sidebar` + icon-less clickable `SidebarItem`s (with a `trailingAction` Fork), and `FormCampaignConfigRail` is built entirely from `SettingsColumn`'s exported `SettingsRail` / `Section` / `Row` primitives over the mini controls — when a campaign surface looks like an existing kit surface, reuse the kit component rather than re-inlining its rendering. Structural lessons it encodes: the second sidebar (`campaignShell.tasksSidebar`) is STRUCTURE, not content — it holds two `.subHeading`-banded sections mirroring the main sidebar ("Campaign Summary" = `FormCampaignSummary`, a list-style stat rail with per-metric `InlineLoader` braille spinners (from `next-vmax/Loader`) for numbers still being computed; "Campaign Task History" = `FormCampaignTasksSidebar`, the task cards) with selection owned by the controller so the sidebar and the in-content `FormCampaignTaskTrace` stay in sync; the campaign identity lives in the sidebars, so the content well carries NO title banner and is a plain `FormTemplate`-measure body (`padding: grid-block; max-width: var(--tools-body-measure, 768px)`, `.block` wrappers spaced by `margin-top: grid-block`); and its headings/paragraphs are the shared `FormSubHeading` / `FormParagraph`, never bespoke section classes. It is the reference for what the kit can express for a live app and where bespoke components are still required (streaming logs, file-tree explorers, live dashboard parsing). Full measurements: [The campaign-console anatomy](#the-campaign-console-anatomy-rebuild-the-mulberry-console-without-repo-context) below. Detail: `next-vmax-tools/AGENTS.md`.

To stand up another console like it:

1. **Create the static nested route** — `app/-/vmax-tools-template/{registry-page}/{name}/page.tsx`, beneath the registry page it instantiates (App Router resolves a static segment ahead of the `[...tool]` catch-all; this is the ONE sanctioned non-registry route, see [Rules](#rules)). The route file is an ordinary server component with `export const dynamic = 'force-dynamic'` and its OWN `generateMetadata` (not the registry-driven `buildToolsPageMetadata`), rendering the client controller as its body. Copy `app/-/vmax-tools-template/campaign-template/mulberry/page.tsx`.
2. **Check in a fixtures module** — a typed data file in `next-vmax-tools/` (`campaign-console-fixtures.ts` is the model): one fixture per page state, plus pure helpers that DERIVE every view and label from a fixture without mutating it (`campaignConsoleTasks`, `campaignSummaryMetrics`, `stoppedCampaignFixture`). Keep every derived value deterministic — timestamps as fixed UTC stamps, no `Date.now()` — so SSR and any client render agree. Pin the helpers' invariants with a vitest file beside the module (`campaign-console-fixtures.test.ts` is the model).
3. **Write ONE controller** — a `'use client'` component in `next-vmax-tools/` (`FormCampaignConsole` is the model): read the entry state from the URL in a `useState` lazy initializer (SSR matches the first client render), keep it addressable through `window.history.replaceState` (never a soft navigation), own every selection the sidebars and content share, and hand `AppContainer.campaignShell` the composed `sidebar`, `tasksSidebar`, `settingsColumn`, and content.
4. **Compose every surface from the kit** — reuse the campaign `Form*` set where a surface matches; where none fits, add a new `Form*` + `.module.css` that is a COMPOSITION over the shared primitives (`FormCampaignConfigRail` over `SettingsRail`/`Section`/`Row`, `FormCampaignSidebar` over `Sidebar`/`SidebarItem` are the models), measured per [the campaign-console anatomy](#the-campaign-console-anatomy-rebuild-the-mulberry-console-without-repo-context).

## Body primitives (`next-vmax-tools/`)

| Component | What it renders |
|---|---|
| `FormTemplate` | The template settings form — `GlassButton` actions (its "Open test modal" opens `FormModal` through the modal system) and inline examples of every primitive below, grouped so sibling examples share one section and one sidebar row: "Comparisons" (both text diffs over a paraphrase plus the code edit), the `FormCode` section (a python verifier gate, the shell commands that reproduce its run, and its verdict log), the `FormJSONExplorer` walk of a run record, "Elements" (the `FormInput` and `FormTextArea` demos, the `Checkbox` sharing toggles, the `FormList` pair, and the "Text styles" reference — including the `InlineCode` `tone` variants), the three live experiment-control sections (`FormSelect` picking an evaluation suite whose note echoes beneath it, `FormSlider` scaling test-time compute — the K slider reprices a labeled corpus live — plus stepped context-window and LoRA-rank knobs, `FormSegmentedControl` sweeping the planner and an uncontrolled memory-module strip), "Tables" (the three `FormTable` variants, the LoRA-adapter card, and the grouped main-results table), then the UNIX-CTF + LoRA sections (a `FormMetric` training-signal strip, a `FormEquation` pair — the frontier band and a `$$`-wrapped cases reward, a `FormRatioBar` parameter-efficiency meter plus a harvest-yield funnel, a `FormStackedBar` pair — the difficulty mix on semantic band colours and a palette-cycled environment-dressing row, a `FormPipeline` pair — the harvest stages and a looped self-play step, a `FormPreferencePair`). The default page's first column. |
| `FormModal` | A dismissable modal modelled on `next-vmax/ModalAuthentication` (gradient-edged brand-over-content panel) but on the AppContainer client-app font: a small VMAX logo, a gradient divider, body copy, and a "Close this Modal" `GlassButton`. Opened via `useModals().open(FormModal, {})` (`@components/ModalContext`); the root layout's `Providers` mounts the `ModalRenderer`. |
| `FormTemplateGraphs` | A `WebTerminalWindow` section (the full `Loader` set) with the `FormTranscript` agent episode directly beneath it, the `FormDisclosure` rollout inspector (three stacked rollouts — the open one carrying toned-`InlineCode` gate marks and a `FormCode` verdict log, the third deep-linkable by hash), a `FormTokenHeatmap` token-credit run, a `FormMatrix` capability grid, a `FormRollout` world-model rollout, a `FormSparkline` training-dynamics pair, a `FormDumbbell` benchmark-category-shift pair (UNIX-CTF / world-model themed), and a `FormLineage` figure of how the capstone task was bred, then one "Graphs" section holding every chart: the `FormDistributionCompare` grouped bars, the `FormGraphCompare` before/after pair, and the rest of the document-system graph catalogue over frontier-model evaluation data, each catalogue entry titled at the caption weight below the one anchored heading (the Line entry is the multi-series form — two self-play solve-rate curves with confidence bands). The `FormInput` demo lives in `FormTemplate`'s "Elements" section. The default page's second column (`?columns=2`). Exports `MODEL_GRAPHS`. |
| `FormIslandScene` | Fills a `calc(100dvh - 48px)` fluid column with an isometric scene, following the tools theme (no `Providers`). Reads the pathname's last segment: `distant-islands` renders the whole-world scene through `next-vmax/islands/WorldStage`, any `isIslandKey` value renders through `next-vmax/islands/IslandStage`, else `DEFAULT_ISLAND_KEY`. Used by the `islands` page under the `island-scene` variant. |
| `FormResearch` | The research feed body, TEXT ONLY: two intro `FormParagraph`s (the company statement, no page heading), then one `<article>` per post (newest first): the shared gradient divider, the post date as an uppercase label (the `.styleLabel` idiom, with a `--theme-grid-block-applications` gap beneath it), the title as a link inside a `FormLongFormHeading` (its `id` the post slug, so entries are anchorable), the authors as a two-column `FormList` whose markers are per-author X links (omitted for authors with no entry in `SOCIAL_LINKS`), and the abstract as a `FormParagraph`. The linked title is the only affordance: no preview image, no "Read the post" button, no card frame. It takes `posts: ResearchPost[]` and renders only that: the route fetches them live (see the `/research` row above), so nothing on the page is checked in. Adding an entry to `PUBLISHED_POSTS` (`common/navigation-links.ts`) grows this page, its sidebar, and the home hero together. The `research` page's single column. |
| `FormProfile` | The author-profile body on the research frame: two markdown-and-island explainer `FormParagraph`s and the shared gradient divider inside a measure-capped `.intro`, then the legacy `PostListWithLayout` post rows FULL BLEED across the content well (`'Mono'` items on the six-column grid, link chips, paddings on the `--theme-grid-block-applications` half-step, viewer and public variants selected by `isViewer`) or, with no posts, a single empty-state `.notice` sentence in the intro. Takes `{ username, posts, isViewer?, live?, onDeletePost? }`; the live `/{username}` profile mounts it with `live` (Delete runs the confirm / `HTTP.deletePost` / refetch flow in-component), while the templates pass fixtures and no handler, so their Delete is inert. The `profile` / `profile-empty` pages' single column AND the production profile body (the legacy `PostListWithLayout` is deleted). |
| `FormInputPageTemplate` | The one-field page body: `FormHeading`, two `FormParagraph`s, the shared gradient divider, then a single `FormInput` with the `ArrowRight` glass-square submit. Every text and field prop has a worked username-picker default, and the flow hooks are props too: `preview(value)` renders live beneath the field as you type (hidden while empty), `validate(value)` returns an error string drawn as a mono `--theme-diff-delete` line, `onSubmit` takes the value (or leave it off and the component echoes it back — `submittedDisplay(value)` replaces the echo line), and `children` render as extra blocks behind a second divider. The `input-page-template` page's template, made to be cloned for any pick-a-value or change-a-setting tool. |
| `FormUserSetup` | The account-setup screen (formerly `next-slate-markdown/SectionUserSetup`, now deleted), live on `/{username}` for an unverified or username-less viewer and rendered in demo mode (no `viewer` prop) as the `input-page-template` page's first column. Welcome copy that re-words per remaining step, a username field with a live `createSlug` preview and the real validation (`isReservedUsername`, 3-character floor), and a verify-e-mail block. Live mode reports errors through `alert(...)` like the original screen and calls `HTTP.setUserUsername` / `HTTP.resendEmailVerification`, redirecting to `/{slug}` on save; demo mode simulates both and shows the template's inline error line instead. The model for wiring a real flow onto the template — see `next-vmax-tools/AGENTS.md`. |
| `FormEmptyColumn` | An empty full-height fluid column body (`min-height: calc(100dvh - 48px)`, no content). For a page whose payload is the sidebar or chrome, not the content well — the `authenticated` route uses it as its single column. |
| `FormHorizontalColumn` | A minimal column body — a `heading` and a `paragraph` on the shared `FormTemplate` typographic scale, nothing else. The `horizontal` page fills its 424px full-height rail with eight of these to label each column. Copy it as the skeleton when a horizontal-rail column needs real content. |
| `FormTable` | Data table capped at 768px, scrolling sideways once a `fluid` column pushes it wider. `columns` (`{ key, label, fluid?, align?, sortable? }`), `rows` OR `groups` (`{ label, rows }[]` — labelled row batches behind full-width header rows, the paper results-table shape; `<strong>` marks best values), `caption?`, `variant` (`gradient` \| `ruled` \| `striped`). A `sortable` column's whole header cell sorts the rows — descending first, then ascending, shown by a stacked up/down caret that lights the active direction; groups sort within themselves so a sectioned table keeps its sections — comparing what a cell means (a string sorts by its leading figure, a bold-wrapped best-value ranks by its figure too, a text-less cell sinks). Exports the pure `cellSortValue` / `sortTableRows`. |
| `FormTextCompare` | Dependency-free unified before/after diff (LCS line diff); removed lines wash red, added green, each snapped to a grid block. `before`, `after`, `caption?`. Exports the pure `diffLines`. The `--theme-diff-*` tokens (root `global.css`) shared by every before/after surface are DELIBERATELY theme-independent — translucent washes for line/row backgrounds, solid values for markers and graph-diff bar fills — because the light red / light green reads on both VMAX themes; do not add per-theme overrides. |
| `FormTextCompareSideBySide` | The split-view sibling — before left, after right, row-for-row aligned with hatched fillers. Reuses `diffLines`; exports `pairDiffRows`. |
| `FormJSONExplorer` | Collapsible JSON tree in the same measured code-diff rows, a `+`/`-` toggle per object/array. `data`, `caption?`. Exports the pure `flattenJson`. |
| `FormCode` | Syntax-highlighted code in the same measured rows: a numbered gutter, one grid block per line, 768px cap with sideways scroll. `code`, `language?` (`python` \| `json` \| `shell` \| `log` \| `text`, default `text`), `caption?`, `copyable?` (a copy-to-clipboard control on the caption line, for command blocks). A dependency-free per-line regex tokenizer (backtracking-hardened string classes — keep that shape when editing or adding a language pattern; `next-vmax-tools/AGENTS.md`) colours tokens by mixing `--theme-graph-option-*` toward `--theme-text`, so both themes stay legible; log verdict words (`PASSED` / `FAILED` / `ERROR` / `WARNING`) take the diff tones. Lines past 400 chars render plain; rendering caps at 512 lines behind a counted footer. Exports the pure `tokenizeCode`. |
| `FormDisclosure` | The fold: a native `<details>` on the kit's hairline panel. `label`, `meta?` (right-aligned monospace facts — a reward, a step count), `headerAction?` (a node pinned to the far right of the summary next to `meta` — e.g. a `ButtonActionGlass` copy button; its `onClick` must `preventDefault()`/`stopPropagation()` so it does not toggle the fold), `children`, `defaultOpen?`, `id?` (deep-linkable: a matching URL hash opens it before scrolling), `caption?`. The `+`/`-` marker is `FormJSONExplorer`'s toggle in the focus accent; adjacent disclosures pull flush so a stack reads as one accordion — the per-rollout inspector, the verifier reference, the raw-artifact dump. |
| `FormLineage` | The ancestry figure — how a task, an adapter, or a population member was bred. `nodes` (`{ id, label, detail?, note?, tone? }`), `edges` (`{ from, to }`), `caption?`. Layers a DAG by longest path, orders each layer by its parents' mean position, and places every node at the fraction an equal-flex row centres its cell at — so the SVG edges land on the DOM cells by construction (a multi-generation edge resumes on course after each gap; a cycle is guarded). Cells read like a `FormPipeline` stage; `tone` washes a line (`accent` seeds, `positive` survivors, `negative` dead ends). Exports the pure `lineageLayout`. |
| `FormInput` | An input field derived from the app's Input fields, re-dressed to the tools small scale. `label?`, `placeholder?`, `buttonIcon?` (the sidebar `ButtonActionGlass` square, centred on the right with grid-block-applications gutters), `onSubmit?` (fires on Enter and the button), `value?`/`defaultValue?` (controlled or uncontrolled), `attached?` (drops the top hairline so it seats flush beneath a `WebTerminalWindow`). No side borders; the top and bottom edges are transparent→border→transparent gradient hairlines. |
| `FormTextArea` | The multiline sibling of `FormInput` on the same hairline surface, with its own `.module.css` — a `<textarea>` that auto-grows to fit its content instead of a single-line input, matched to the input's one-line height when empty. Same props as `FormInput` plus `rows?`; a one-line textarea equals a `FormInput`. Enter inserts a newline, Cmd/Ctrl+Enter (or the `buttonIcon` glass square) submits. The auto-resize measures against the `placeholder` when the field is empty, so it opens sized to its example, and reruns on every value/placeholder change and on window resize — see `next-vmax-tools/AGENTS.md`. Used for the `/client` "Describe your Campaign" field. |
| `FormSelect` | The pick-one control: a native select restyled onto `FormInput`'s hairline surface, so keyboard, form, and screen-reader behaviour come free and the open list stays the platform's (both option colours pinned for OS/page theme mismatches). `label?` (omit it for a thin, label-less variant — a compact inline filter/sort control), `name?`, `options` (`{ value, label, disabled? }[]`), `value?`/`defaultValue?` (controlled or uncontrolled), `onChange?(value)`. Unlike the text fields it also draws left and right gradient hairlines (transparent-tipped, on `.row`) so a select never reads as an editable input. For the run, model, or benchmark picker that feeds a query or API call. |
| `FormSlider` | The continuous control: `FormRatioBar`'s labelled meter made live — field label + monospace figure over the grid-block track, filled by the shared `--theme-table-ltr-gradient-background` ramp ending at a flush square thumb of solid `--theme-border`. `label`, `min`, `max`, `step?`, `value?`/`defaultValue?`, `format?` (prints the figure the way the field reads it — `128k tokens`, `r = 16`), `onChange?(value)`. For test-time compute budgets, context windows, adapter ranks. `bare` drops the label/value head (and its margin + max-width) so the raw track sits inline in a dense row — how `SettingsColumn` places it beside a `FormFieldMini`. Exports the pure `sliderFraction`. |
| `FormFieldMini` / `FormSelectMini` | The smallest-scale input and select — a single grid-block (24px) tall box for a dense figma-style control panel, the density siblings of the 48px `FormInput` / `FormSelect`. Their value text is the `'SFMonoSquare-Regular'` monospace so digits column-align in the tight boxes. Both take optional `prefix` (`FormFieldMini` also `suffix`, `align`) and controlled/uncontrolled `value` / `defaultValue`; `FormSelectMini` reuses `FormSelectOption`. Reach for them only inside a panel like `SettingsColumn`, not for a page's main form fields. |
| `SettingsColumn` | The figma-style control panel the `client` page swaps in for its second column when `?settings=true` — a compute-fleet configurator over twelve collapsible gradient-headed `Section`s (one per computer), split into two internal columns of six with a hairline divider, stacked flush so there is no gap between computers. Each section is a stack of `Row`s whose `controls` flex reads as 1–4 equal cells, composing `FormFieldMini`, `FormSelectMini`, `FormSlider bare`, and a local `ToggleMini` (gradient-hairline segmented buttons); a section head is a `−`/`+` toggle that shows or hides its rows. That gradient section-header band (`.sectionHead` / `.sectionTitle` — `--theme-table-ltr-gradient-background`, `--theme-grid-block` tall, uppercase 600 title at `0.6` opacity) is the sidebar's shared heading idiom: `SidebarCampaigns` re-creates it for its "Campaign History" heading (minus the `−`/`+` marker) so the campaign and settings sidebars match — a deliberate cross-stylesheet duplicate to keep in sync (`next-vmax-tools/AGENTS.md`). Widens its rail to a fixed 480px (kept on mobile so it scrolls horizontally). Rendered by `AppContainer` (via the `Settings` control), not registered as a page column. |
| `FormSegmentedControl` | The pick-one-of-few: native radios (clipped, not hidden, so the group keeps focus and arrow keys) behind equal segments on the `FormInput` surface, split by `FormMetric`'s vertical hairlines. `label?`, `name`, `options` (`{ value, label, disabled? }[]`), `value?`/`defaultValue?`, `onChange?(value)`. Unselected segments sit at the muted-label opacity; the selection is just full text at label weight — no box — with one grid-block-applications gutter on every side keeping the strip tight. For the two-to-four-way choices an experiment sweeps — planner, initialization, memory module. |
| `FormGraphCompare` / `FormGraphCompareSideBySide` | Before/after chart diffs (stacked / split). `before`, `after`, `graphIdBase`, `caption?`. `colorizeByChange` recolors the after chart against the before. |
| `FormMetric` | A stat strip for RL headline numbers: `metrics` (`{ label, value, delta?, deltaSuffix?, goodDirection? }[]`), `caption?`. Coloured `▲`/`▼` delta via the diff tokens; `goodDirection: 'down'` makes a fall in loss/KL/refusals read green. Exports the pure `deltaTone`. |
| `FormPreferencePair` | The RLHF chosen-vs-rejected split: `prompt`, `chosen`/`rejected` (`{ text, reward? }`), `caption?`. Insert-green and delete-red panes with the reward margin beneath. Exports the pure `preferenceMargin`. |
| `FormDistributionCompare` | Student-teacher distillation: `tokens`, `teacherLogits`, `studentLogits`, `temperature?`, `graphId?`, `caption?`. Softmaxes both logit sets and draws a grouped bar chart via `GraphBlockWrapper`, with the KL and top-1 agreement beneath. Exports the pure `softmax` and `klDivergence`. |
| `FormTokenHeatmap` | Per-token credit: `tokens` (`{ token, value }[]`), `min?`/`max?`, `caption?`. An inline token run washed by its scalar through a diverging red↔green scale. Exports the pure `signedIntensity` / `scalarRange`. |
| `FormRollout` | A world-model / trajectory strip: `steps` (`{ label, value? }[]`), `real?` (a second trajectory → imagined-vs-real two-row grid), `min?`/`max?`, `caption?`. Timestep cells (`t0 › t1 › …`) tinted by the per-step scalar through the same diverging scale, scrolling sideways when long. Reuses `FormTokenHeatmap`'s `signedIntensity`. |
| `FormTranscript` | An agent episode as logs inside a `WebTerminalWindow`: `turns` (`{ role: system\|user\|assistant\|tool, text, timestamp, name? }[]` — `name` overrides the displayed alias while keeping the role's tone, so a dialogue can name its speakers), `title`, `caption?`. Each line is three columns: a coloured name alias, a fluid message, and a fixed-width timestamp, so the container stays measured. The log body caps at 88 `--theme-grid-block-applications` and scrolls vertically past it on the shared scrollbar. |
| `FormMatrix` | A labelled grid (models × benchmarks, predicted × actual) with cells tinted by value: `columns`, `rows` (`{ label, values[] }`), `diverging?` (signed red↔green vs sequential green), `min?`/`max?`, `format?`, `caption?`. 768px cap, sideways scroll. Reuses `signedIntensity`/`scalarRange`. |
| `FormRatioBar` | A stack of proportion meters: `ratios` (`{ label, value, max?, display? }[]`), `caption?`. One labelled bar per ratio (a `value/max` fill with a 1px floor), for LoRA trainable-%, KL budget, context used. |
| `FormStackedBar` | The composition figure: `segments` (`{ label, color? }[]` — colours default to the `--theme-graph-option-*` palette cycle), `rows` (`{ label, values[] }[]`, values aligned to segments by index), `caption?`. One 100% bar per row, each normalized against its own total so differently-sized samples compare, with one legend naming every segment. For a difficulty mix, an outcome split, an environment dressing. Exports the pure `segmentShares`. |
| `FormDumbbell` | The per-category before/after: `items` (`{ label, before, after, display?, goodDirection? }[]`), `min?`/`max?`, `caption?`. One row per category — a hollow dot where the run started, a filled dot where it ended, the span washed in the change's tone — on one shared axis so a jump reads big against a flat row. `deltaTone` judges after against before with the per-row `goodDirection` flip. Exports the pure `dumbbellPositions`. |
| `FormEquation` | Display math on the tools scale: `formula`, `caption?`. Server-safe KaTeX in the 768px column, scrolling sideways when wide, each block's height snapped to a grid-block multiple after paint. Takes a formula the way a paper carries it — `\[..\]` / `$$..$$` / `\(..\)` wrappers strip (multi-pair blocks split into separate equations), environments and raw math pass through. Exports the pure `extractEquations`. |
| `FormPipeline` | The method-flow figure as measured stages instead of an image: `stages` (`{ label, detail?, note? }[]` — the note is a monospace count/cost), `loop?` (appends a return cell back to the first stage, the training-loop shape), `caption?`. Stage panels on the FormMetric surface joined by › arrows, scrolling sideways past the cap. |
| `FormSparkline` | Labelled trend rows — the small multiples of a training-dynamics section: `items` (`{ label, series, display?, goodDirection? }[]`), `caption?`. Each row is a field label, an inline polyline of the series, and the monospace final value; line and value take the `deltaTone` colours judged last-against-first, with `goodDirection: 'down'` making a falling loss/KL/episode-length read green. Exports the pure `sparklinePoints`. |
| `FormList` | The list primitive: `items` (`React.ReactNode[]`), `ordered?`, `columns?`, `caption?`. `markers` (index-aligned to `items`) swaps the square/number for caller-supplied nodes, a `null` entry rendering an empty but still-reserved gutter so rows stay aligned when a marker is conditional (the `/research` author X links). `columns: N` splits the items across N balanced side-by-side lists (a flex row of `flex: 1 1 0` columns that stacks back to one under 568px), filling COLUMN-MAJOR so DOM and reading order still run top-to-bottom then across, and `ordered` numbering continues across the break rather than restarting. Exports the pure `splitListColumns(items, columnCount)`. For a wide roster that would otherwise run as one long thin column: the `/research` author blocks are the reference. Real `<ul>`/`<ol>` semantics with the native markers stripped: unordered rows take the kit's outline square (the Checkbox hairline at bullet size), `ordered` swaps it for the code-diff gutter's right-aligned muted monospace number. Both markers share one fixed gutter, so the two forms keep a single text edge and wrapped lines hang off it. |
| `FormTaskSearch` | The live filter for a task rail that can grow unbounded: a label-less `FormInput` search box (the same prompt-input hairline surface as the composer) over a FormTemplate-scale `{shown} of {total} tasks` summary (the shown count turns `--theme-focused-foreground` while filtering). `value`, `onChange`, `shownCount`, `totalCount`, `placeholder?`. Also exports `HighlightedText` (`{ text, terms }`), which paints matched runs in the active accent via a `<mark>`. Both sit over `task-search.ts`: `searchTasks(tasks, query)` indexes each `{ id, title, body }` through the vendored `@modules/minisearch` (MiniSearch, `{ prefix, fuzzy, combineWith: 'AND' }`) and returns `matchedIds` + the matched `terms`; `highlightSegments(text, terms)` is the pure split `HighlightedText` renders. `SidebarTasks` and `FormCampaignTasksSidebar` are the reference callers; drop it into any second-sidebar task list that can get long. |

From the wider library, bodies also use `GraphBlockWrapper` (`@document-system-components`), and `WebTerminalWindow` (a titled terminal window, cloned from `ApplicationWindow`), `Loader` (the animated braille-spinner set), `ButtonActionGlass` (the sidebar glass square `FormInput` mounts as its submit button), `Checkbox`, `GlassButton`, and `InlineCode` (all `@nextjs-vmax`; `InlineCode` takes a `tone` — `positive` \| `negative` \| `accent` — that makes it the status mark for a pass/fail/running word). A common composition is a `WebTerminalWindow` over a `Loader`, with a `FormInput attached` docked flush beneath it.

## Open a modal from a page

The modal system is global: the root layout's `Providers` mounts a `ModalProvider` and a `ModalRenderer`, so any client body can open one without extra wiring. Call `useModals().open(Component, props)` from a handler; the component renders centred over the page and closes itself with `useModals().close()`, or on an outside click unless it sets a static `disableOutsideDismiss`. `FormModal` is the model to copy for a tools-page modal: a `next-vmax-tools/` client component with its paired `.module.css`, on the client-app font, gradient-edged like `ModalAuthentication`.

```tsx
'use client';
import { useModals } from '@components/ModalContext';
import FormModal from '@nextjs-vmax-tools/FormModal';

function RestoreButton() {
  const { open } = useModals();
  return <GlassButton onClick={() => open(FormModal, {})}>Open test modal</GlassButton>;
}
```

## The shell (`AppContainer`)

`<AppContainer columns={n}>{page.columns}</AppContainer>` — the route passes the registry row's `columns` as children and the `?columns=N` count. The shell owns the live controls and hands the same handlers (`ToolsControls`, in `tools-controls.ts`) to both the top `ApplicationNavigation` and the fixed `ToolbarControlsFixed`:

- **Light** — swaps `theme-light` / `theme-dark` via `Utilities.setTheme`.
- **Holy** — toggles the Mekzantine display font via `Utilities.toggleFontClassName('font-use-mekzantine')`, scoped to the shell so it never leaks. `?holy=true` on the URL opens the page with it already on (applied once on mount, so a manual toggle-off still sticks).
- **Grain** — a `GrainOverlay` hosted on a wrapper that isolates it over the nav, sidebar, and content but never the fixed toolbar.
- **Hide Overview** — toggles the leftmost sidebar (the overview rail) off so the content well takes its width; rendered directly after Grain in both control bars and lit active while the rail is hidden. Shown on every layout except `horizontal-columns`, the one variant with no sidebar (`ToolsControls.showHideOverview`). Plain shell state like Grain, so a hard navigation resets it. On the double-sidebar and campaign layouts only the FIRST rail hides (the tasks rail stays). See `next-vmax-tools/AGENTS.md`.
- **Debug** — routes to `/-/vmax-tools-template/islands` (the scene catalog, the engine's debug surface); lights active while any islands route is open (`isDebug` = pathname prefix match). Rendered by both control bars after the Grain / Hide Overview pair.
- **`rightLinks`** (`ApplicationNavigation` only) — an optional array of `{ label, href?, target?, menuItems? }` that REPLACES the right section's column controls with plain links (`menuItems` wraps the label in a `DropdownMenuTrigger`, forced to `inline-flex`/centered via its `style` prop so the trigger's `inline-block` line box cannot sink the label below its sibling links). The link data is `HOME_NAVIGATION_RIGHT_LINKS` (`common/navigation-links.ts`, typed `NavigationRightLinkItem[]`): Research (→ `RESEARCH_URL`, `/-/vmax-tools-template/research`), Careers, and the Team dropdown, whose entries derive from `DOCUMENT_NAVIGATION_ITEMS`' `Team: ` rows so the menus share one source. The home page (`components/VmaxAppNavigation.tsx`) renders it as its top chrome, and a `TOOLS_PAGES` row may declare `rightLinks` (plus an optional `trailingLink`) to render the SAME public nav on a registry page: `AppContainer`'s `isPublicNavigation` branch mounts `ApplicationNavigation` with ONLY the links — no Holy/Light/Grain/Debug cluster, no dividers — and drops `ToolbarControlsFixed` entirely, so the page's top bar is identical to `/` and `/sign-in` (the grain overlay itself stays at its default, on; only the controls go). The `research` row is the reference: `rightLinks: HOME_NAVIGATION_RIGHT_LINKS` + `trailingLink` "Home" → `/`, matching the `/sign-in` bar. Two more optional props ride on `ApplicationNavigation` (both default off, so the tools shell is untouched): `hideMenu` hides the top-left `DocumentHeading` logo but keeps the `.left` section rendered, so it holds its `min-width: 240px` footprint and nothing slides — the logo is simply not drawn; `trailingLink` (`{ label, href }`) appends an `.itemDivider` + a plain link to the right section, after the `rightLinks`. The two `VmaxAppNavigation` callers set them: the home page (`app/page.tsx`) passes `hideMenu` + an auth-aware `trailingLink` ("Profile" → `/{username}` for a signed-in viewer with a username, else "Sign in" → `/sign-in`); the `/sign-in` sign-in screen (`components/SignInScreen.tsx`) keeps the clickable dropdown logo and passes `trailingLink` "Home" → `/`.
- **Col-1 / Col-2** — sets `?columns=N` via a hard `window.location.href` navigation (the repo forbids soft navigation — root `AGENTS.md`). The `Col-2` option is hidden (in both the nav and the fixed toolbar) when the layout uses the content space in a way that makes side-by-side columns impossible: `AppContainer` sets `ToolsControls.singleColumn` for the `horizontal-columns` and `island-scene` variants, and both control bars read it. BOTH `Col-1` and `Col-2` (and `Settings`) are dropped entirely when `AppContainer.hideColumnControls` sets `ToolsControls.hideColumns` — the whole column-control block plus the trailing divider before it renders `null` — for single-column pages where the controls are meaningless (the live profile `/{username}`, the `/waitlist` page, and the `profile` / `profile-empty` registry rows).
- **Settings** — an action sitting next to `Col-1` / `Col-2` that writes `?settings=true` (and forces `columns=2`) on the URL, so selecting it opens `SettingsColumn` in the fixed second column. `Col-1` / `Col-2` / `Settings` act as three mutually-exclusive views of that column: the column buttons clear `settings` (so `Col-1` hides the panel), and only one lights active at a time. It renders only on the `client-ui-columns` variant (`ToolsControls.showSettings`). See `next-vmax-tools/AGENTS.md`.
- **Start a campaign** — on the `client-usage-columns` variant only (`ToolsControls.showCreateCampaign`), a single item REPLACES `Col-1` / `Col-2` / `Settings` in both control bars. It resets the page state machine to the create screen (prompt + `SettingsColumn`), the same action as the sidebar's matching "Start a campaign" button, and lights active while that screen is showing (`isCreateCampaign`). See `next-vmax-tools/AGENTS.md`.

In dark mode the shell steps `--theme-background` one shade below the global dark theme, scoped to this route.

The shell's ScrollSpacer wiring is a pinned contract (`next-vmax-tools/AGENTS.md`): `.content` is the spacer's `limitRef` and must NOT get `align-self: stretch`, and the sidebar subtree keeps `overflow-anchor: none` — preserve both in any shell edit; neither is a cleanup target.

## Design tokens & measurements (the values that make a clone pixel-perfect)

The reason every shipped route reads as one system is that all of them measure off the SAME handful of tokens, defined in the repo's root stylesheets (`global-block-size-app.css` for the grid, `global.css` for fonts/diff tones, `global-themes.css` for the theme palette, `global-colors.css` for the raw ramp). **Inside this repo you reference the tokens by name and never inline these values** ([Conventions](#conventions)). This table exists for the other case — cloning the shell **without repo context** (a standalone tool, a design handoff, an agent given only this skill): recreate the token layer from these exact resolved values FIRST, then keep every size and colour expressed as a token so both themes and the ≤1024px shift keep working for free. The numbers below are the ground truth the six reference routes were built against.

### The grid — everything snaps to it

| Token | Default | `@media (max-width: 1024px)` | Notes |
|---|---|---|---|
| `--theme-grid-block` | `24px` | `22px` | The base rhythm. Line-heights, row heights, and paddings are this or a multiple. |
| `--theme-grid-block-applications` | `12px` | `11px` | Always `--theme-grid-block * 0.5`. The half-step used for tight gutters, scrollbar size, mini-control insets. |

The top bar (`ApplicationNavigation`) locally pins `--theme-grid-block: 24px` so it stays `min-height: 48px` (two grid blocks) even below 1024px. `48px` is therefore the one hard-coded literal the shell repeats — every full-height column is `min-height: calc(100dvh - 48px)`.

The fixed bottom toolbar (`ToolbarControlsFixed`) is NOT a bar and pins nothing — it is a centred **floating pill** fixed to the bottom of the viewport: `max-width: 568px; width: 100%`, `border-radius: 8px`, `border: 1px solid var(--theme-border)`, `background: var(--theme-background)`, `box-shadow: var(--theme-box-shadow-modal)`, `padding: 8px 24px`, `margin: 0 auto 24px`, its internal divider a 2px × 32px vertical gradient hairline — and the whole pill is `display: none` under 768px, so on mobile only the top bar offers the controls.

### Fonts

- **Body / UI:** `--theme-font-family-client-apps: -apple-system, BlinkMacSystemFont, 'helvetica neue', helvetica, sans-serif`. Every body and the shell sit on this (NOT the site's `'EB-Garamond', serif` document font). Keep bodies on it so a clone matches `AppContainer` and `ModalAuthentication`.
- **Monospace:** `'SFMonoSquare-Regular'` — figures, mini-field values, code gutters, `FormCode`, odometer/heatmap numerics. This is what column-aligns digits in the tight `FormFieldMini` / `FormSelectMini` boxes.
- **Display toggle:** `'Mekzantine-Regular'` — swapped in only while the **Holy** control (`font-use-mekzantine`) is on, scoped to the shell.

### Type scale

| Token | Default (size / line-height) | `@media (max-width: 1024px)` |
|---|---|---|
| `--font-size-large` / `--line-height-large` | `20px` / `24px` | `16px` / `20px` |
| `--font-size-medium` / `--line-height-medium` | `18px` / `22px` | unchanged |
| `--font-size` / `--line-height` (base) | `16px` / `20px` | `14px` / `18px` |
| `--font-size-small` / `--line-height-small` | `14px` / `18px` | unchanged |
| `--font-size-very-small` | `12px` | unchanged |

**Body typographic scale** (the `root` / `heading` / `subHeading` / `paragraph` / `divider` every `Form*` shares — copy these exactly for a new body): `root` = `font-size-small` (14px) on `line-height: var(--theme-grid-block)` (24px), `padding: var(--theme-grid-block)`, `max-width: 768px`, on the client-apps font. `heading` = same size, `font-weight: 600`. `subHeading` = same size at `line-height-small`, `600`, `padding-top: grid-block-applications`. `paragraph` = same size / `grid-block` line-height, `400`. The `heading` / `subHeading` / `paragraph` steps are also available as shared components — `FormHeading` / `FormSubHeading` / `FormParagraph` from `@nextjs-vmax-tools/FormTypography` — so a body can drop its bespoke section-header classes and use them directly (do NOT use `@nextjs-vmax/FormTypography`, which is the serif document scale). The mulberry console's results body is the reference. One step sits ABOVE this body scale, and it is the only place a tools body leaves the client-apps font: `FormLongFormHeading` (same module) reproduces the POST `h1` recipe from `document-system-components/Document.module.css` `.h1` token for token, so a feed title and its rendered post title are one typeface at one size. That is `'Silvana-Regular'` (the document serif) at `calc(var(--font-size) * 1.5)` on `calc(var(--theme-line-height-base) * var(--multiple-header-size))`, weight `400`, `text-transform: var(--theme-h2-text-transform-uppercase)`. It renders an `h2` because its caller is a feed of titles under a page `FormHeading`; `FormResearch` is its only consumer, and a section header still uses `FormSubHeading`. A section label (`.styleLabel` idiom) is `font-size-very-small`, `600`, `letter-spacing: 0.2px`, `text-transform: uppercase`, `opacity: 0.5`. Data figures use the mono font at `font-size-large`.

### Theme palette (light / dark)

The tools shell follows the global `theme-light` / `theme-dark` body class and adds ONE scoped override: in dark mode `.shell` steps `--theme-background` down to `--color-gray-100` (`rgba(22, 22, 22, 1)`), one shade below the global dark `--color-black-95`. Load-bearing tokens:

| Token | `theme-light` | `theme-dark` |
|---|---|---|
| `--theme-background` | `rgba(255,255,255,1)` (white) | `rgba(30,30,30,1)` → shell steps to `rgba(22,22,22,1)` |
| `--theme-background-foreground` | `rgba(57,57,57,1)` | `rgba(111,111,111,1)` |
| `--theme-text` | `rgba(0,0,0,1)` | `rgba(224,224,224,1)` |
| `--theme-border` | `rgba(224,224,224,1)` | `rgba(82,82,82,1)` |
| `--theme-border-subdued` | `rgba(168,168,168,0.2)` | `rgba(82,82,82,0.6)` |
| `--theme-border-translucent` | `rgba(244,244,244,0.4)` | `rgba(0,0,0,0.4)` |
| `--theme-focused-foreground` | `rgba(92,255,59,1)` (neon green) | `rgba(239,99,0,1)` (daybreak orange) |
| `--theme-table-ltr-gradient-background` | `linear-gradient(to right, transparent, var(--theme-border-subdued))` | same recipe |

`--theme-focused-foreground` is the ONE accent both themes flip: it drives the `»` selected-task caret, `FormDisclosure`'s marker, and every `active` glass button (`isViewer`) — so a focus accent is never a hard-coded colour.

### Theme-independent tokens (identical in both themes — do NOT add per-theme overrides)

- **Diff tones** (`global.css`, shared by every before/after surface): `--theme-diff-insert: rgba(63,185,80,1)`, `--theme-diff-delete: rgba(248,81,73,1)`, `--theme-diff-insert-wash: rgba(63,185,80,0.18)`, `--theme-diff-delete-wash: rgba(248,81,73,0.16)`. Solids mark markers/graph-diff fills; washes tint line/row backgrounds. Chosen because light-red / light-green read on both themes.
- **Graph palette** (`--theme-graph-option-1..8`, the `FormStackedBar` / chart cycle): `#FF0000`, `#0000FF`, `#00CC00`, `#FFCC00`, `#FF6600`, `#00CCCC`, `#CC00CC`, `#FF3399`.
- **The hairline divider** every column/sidebar seam uses — a 1px line, gradient-faded at both ends so it never touches the edges: `background: linear-gradient(to bottom, transparent 0%, var(--theme-border) 25%, var(--theme-border) 75%, transparent 100%)` (swap `to bottom`→`to right` for a horizontal seam).

### The shell skeleton & per-variant column measurements

`AppContainer` always renders this DOM order; a clone reproduces the layout by reproducing these numbers, not by eyeballing:

```
<div .shell>                         background: --theme-background (dark: stepped to gray-100)
  <div #grainHost .grainHost>        position: relative; isolation: isolate  (the Grain overlay host)
    <ApplicationNavigation/>         the 48px top bar (theme / holy / grain / hide-overview / debug / column controls)
    <div .root>                      display:flex; align-items:flex-start; min-height: calc(100dvh - 48px)
      [ .sidebar + .divider ]…       one or more 240px sidebars, each split by the 1px gradient .divider
      <div .content>                 width:100%; min-width:10%  (the ScrollSpacer limitRef — never align-self:stretch)
        └ one column layout ↓
    { isGrain && <GrainOverlay/> }   last child of .grainHost — the isolate scope ends here
  <ToolbarControlsFixed/>            the floating bottom pill — a SIBLING of .grainHost, outside the
                                     isolate scope, which is exactly why the grain never covers it
```

Column layouts, one per `columnVariant` (default = none):

| Layout | Container | Column sizing | Responsive |
|---|---|---|---|
| **default** (`.columns`) | `display:flex; overflow-x:auto` | `.column` = `flex: 1 1 50%; min-width: 568px` (so two columns scroll side-by-side; `?columns=1` shows one) | the `568px` floor is what forces the horizontal scroll at two columns |
| **`client-ui-columns`** (`.clientUiColumns`) | flex, `justify-content: space-between` | `.fluidColumn` = `width:100%; min-width:10%` (always shown) + `.fixedBarColumn` = `max-width:240px; width:100%` (2nd, shown at 2 cols); `Settings` swaps in `.fixedBarColumnSettings` = fixed `480px` | reverts to default (`flex:1 1 50%; min-width:568px`) under `880px`; solo fluid column drops its `568px` floor to `min-width:0` (the `clientColumnsSolo` modifier) so one column stays full-width |
| **`horizontal-columns`** (`.horizontalScroller`) | `display:flex; overflow-x:auto; align-items:stretch`; **no sidebar** | `.horizontalColumn` = `flex: 0 0 424px; min-height: calc(100dvh - 48px)`; renders EVERY column (ignores `?columns`) | reads one 424px column at a time on mobile; only the nav stays fixed |
| **`island-scene`** | default `.root`, one fluid `.content` column | `FormIslandScene` fills `calc(100dvh - 48px)` | sidebar + divider hidden under `568px` (`.sidebarHideMobile`) so the scene is full-screen on mobile |
| **`client-usage-columns`** | state-driven: campaign sidebar always shown; task sidebar after a campaign click; middle is prompt+`480px` settings (no task) or the two-column kitchen-sink (task) | reuses `.fluidColumn` / `.fixedBarColumnSettings` / `.columns` per state | same `880px` revert as the client variant |
| **`campaignShell`** (`.root.campaignRoot`, the mulberry console; not a `columnVariant`) | flex; `overflow-x: auto` under `1200px` | one or two `.campaignSidebar` rails pinned at every width (sticky, own `overflow-y`): the history rail `flex: 0 0 240px`, the tasks rail just under 1.6x that via `.campaignSidebarTasks` (`flex: 0 0 calc(var(--theme-grid-block) * 15.96)` = `383.04px` at the 24px grid) + fluid `.content` (`min-width: 568px` under `1200px`; sets `--tools-body-measure: calc(var(--theme-grid-block) * 38.4)` so its bodies widen 20%); `showSettings` splits content into `.fluidColumn` + the fixed `480px` settings rail | columns NEVER squish: under `1200px` (240 + 1 + 383.04 + 1 + 568, rounded up) the row scrolls horizontally, one column at a time on phones; history rows keep labels via `SidebarItem preserveMobileLabel` |

**Breakpoints that matter:** `1024px` (grid + type scale step down), `1200px` (the campaign shell switches to its horizontal-scroll rail), `880px` (client/settings variants revert to the stacked two-column behaviour), `568px` (sidebar collapses to icon-only / hides on the island scene; the default column floor). The sidebar itself is `max-width: 240px; width: 100%`, collapsing to `width:auto; min-width:0` under `568px`; the campaign shell's rails opt out of that collapse and stay pinned everywhere: the history rail at `240px`, the tasks rail at `calc(var(--theme-grid-block) * 15.96)`.

**Two mobile schemes, pick one deliberately.** A layout either COLLAPSES its rail (the default: icon-only sidebar under `568px`, right when the content column is the payload) or PINS its columns and scrolls horizontally (the campaign shell: full-width rails + `preserveMobileLabel` rows, right when the sidebars are the payload). The `horizontal-columns` and `island-scene` variants are the single-column forms of the same two choices.

### The chrome & sidebar anatomy (rebuild the top bar + rows without repo context)

The skeleton above places the top bar and sidebars but treats their interiors as boxes. These are the ground-truth measurements the shipped routes were built against — enough to rebuild `ApplicationNavigation`, the four sidebar row types, and the two client-only sidebars pixel-for-pixel from the skill alone. Every value is a token or a token multiple; keep them expressed that way.

**Top bar — `ApplicationNavigation` (the 48px bar).** `display: flex; justify-content: space-between; align-items: center`, with a bottom hairline (an `::after` horizontal `transparent → --theme-border → transparent` gradient rule). Three sections split by the vertical `.divider` hairline (`width: 1px`, the vertical gradient, `display: none` under `768px`):

- `.left` — `min-width: 240px; min-height: 48px; flex-shrink: 0`, `padding: 0 var(--theme-grid-block)` with `--theme-grid-block` pinned to `24px` here (so the bar holds `48px` below `1024px` too), holds the `DocumentHeading` logo. `min-width: auto` under `768px`. `hideMenu` drops the logo but keeps the section so nothing slides.
- `.stretch` — the middle control cluster, `min-width: 10%; width: 100%`, `padding: 0 12px`: the `Holy` / `Light` / `Grain` / `Hide Overview` items, then an `.itemDivider` (`width: 1px` vertical gradient, `margin: 0 12px`), then `Debug`. `display: none` under `768px`.
- `.right` — `min-width: 240px; min-height: 48px; flex-shrink: 0; justify-content: flex-end`, `padding: 0 12px`: `Col-1` / `Col-2` / `Settings` (or `rightLinks`, or the single `Start a campaign` item on the usage variant). `min-width: auto; width: 100%` under `768px`.
- Every control is a `.item`: `font-size: var(--type-scale-fixed-tiny)` (12px), `letter-spacing: 0.2px`, `font-weight: 600`, `padding: 0 12px`, `color: var(--theme-background-foreground)`, `cursor: pointer`, hover `opacity: 0.8`; the `.active` (selected) item lifts to `color: var(--theme-text)`. The fixed bottom pill (`ToolbarControlsFixed`) reuses the same item/active recipe at `font-size-small`.

**Sidebar rows.** The sidebar is a list-reset `<ul>` (`Sidebar`) holding these four row types (each its own component + `.module.css`):

- **`SidebarItem`** (a group header / campaign row) — `padding: 0 var(--theme-grid-block)`, `display: flex; align-items: flex-start`, `font-weight: 600`, `margin-top: var(--theme-grid-block)`, base `opacity: 0.6` (`active` → `1` via `.itemActive`, `dim` → `0.4` via `.itemDim`). Its optional glass icon is a `ButtonActionGlass` square `calc(var(--theme-grid-block-applications) * 2)` (24px) tall, `margin-right: 16px`, SVG `16×16`; omit the icon for a label-only row. `.name` label: `line-height: var(--theme-grid-block)`, single-line ellipsis; an optional `.description` stacks beneath at `font-size-very-small` / `line-height-small`, `opacity: 0.7`. An optional `trailingAction` node pins after the label (`.trailing`: `flex-shrink: 0`, self-centred, hidden with the label under `568px` unless the row sets `preserveMobileLabel`, the opt-out for rails that never collapse, like the campaign shell's pinned 240px sidebars) — a per-row control outside the label's click target; the campaign console's Fork text-button is the reference. A `divider` row is a horizontal gradient hairline, `margin: var(--theme-grid-block) var(--theme-grid-block-applications) 0 var(--theme-grid-block)`.
- **`SidebarSubItem`** (a route row) — `padding-left: calc(var(--theme-grid-block-applications) * 3)` (36px, → `*2` = 24px under `568px`), `padding-right: var(--theme-grid-block)`, `margin: var(--theme-grid-block-applications) 0`, `opacity: 0.6`, `align-items: center`. Icon in a `.square`: `20×20`, `1px solid var(--theme-border)`, `border-radius: 2px`, `margin-right: 12px`, SVG `12×12`; on a two-line row (a `description` present) the square adds `margin-top: calc((var(--theme-grid-block) - 20px) / 2)` so it centres on the first label line's grid-block box rather than top-aligning against it. `tone: 'system'` strikes the label through; the label hides under `568px` (icon-only rail).
- **`SubItemSelect`** (a dropdown row — scene catalogs only) — the SubItem shape (same 36px indent, 20px square) with a `FormSelect`-surface dropdown in place of the label: a `.field` on `--theme-border-translucent` with top/bottom AND left/right `transparent → --theme-border → transparent` hairlines (so it never reads as an editable input), an `appearance: none` `<select>` at `height: var(--theme-grid-block)` (24px), `padding: 0 calc(var(--theme-grid-block-applications) * 2) 0 var(--theme-grid-block-applications)`, `font-size-small`, and a `.caret` pinned `calc(var(--theme-grid-block-applications) * 0.5)` (6px) from the right at `opacity: 0.5`. The square icon is a button that opens the selected option.

**Client-variant sidebars (rebuild `/client` and `/-/vmax-tools-template/campaign-template`).**

- **`SidebarCampaigns`** — a `.create` block padded `var(--theme-grid-block)`: a `.heading` (14px / 600), a muted `.paragraph` (`opacity: 0.7`), and a full-width `GlassButton` ("Start a campaign"). Then a "Campaign History" heading — `.subHeading`, a `--theme-table-ltr-gradient-background` band `height: var(--theme-grid-block)`, `padding: 0 var(--theme-grid-block-applications)`, its `.subHeadingTitle` at `font-size-very-small` / `600` / `0.2px` / uppercase / `opacity: 0.6` (the shared settings-band idiom, minus the ±marker) — over icon-less clickable `SidebarItem` rows (campaign name + timestamp description), the selected row `active`, the rest `dim`.
- **`SidebarTasks`** — a possessive `.heading` (`{Campaign}'s Tasks`, 14px / 600, `padding: var(--theme-grid-block) var(--theme-grid-block-applications) 0`) over a `.list` (`display: flex; flex-direction: column; gap: var(--theme-grid-block-applications); padding: var(--theme-grid-block-applications)`) of `.task` `<button>` wrappers, each a `WebTerminalWindow` whose body is `.log` lines (`font-size-very-small`, `line-height-small`, `opacity: 0.75`, single-line ellipsis). The selected task's window title carries a `»` `.caret` in `--theme-focused-foreground`; a `running` task appends an `'SFMonoSquare-Regular'` braille-spinner loader line.

**`SettingsColumn` (the `?settings=true` rail, 480px wide).** `min-height: calc(100dvh - 48px)`, `font-size-very-small`. A `.grid` flex row of two `.gridColumn`s (`flex: 1 1 0`) split by a `.gridDivider` vertical hairline. Each of the twelve computers is a `Section`: a `.sectionHead` `<button>` — the `--theme-table-ltr-gradient-background` band, `height: var(--theme-grid-block)`, `padding: 0 var(--theme-grid-block-applications)`, a `.sectionTitle` (`font-size-very-small` / `600` / uppercase / `opacity: 0.6`) and a `.sectionMarker` (`−` open / `+` collapsed, mono, `opacity: 0.4`) — that shows/hides its rows; sections stack flush, an open one adding `padding-bottom: calc(var(--theme-grid-block-applications) * 0.5)`. A `.row`: `display: flex; align-items: center; gap: var(--theme-grid-block-applications); min-height: var(--theme-grid-block)`, a fixed `.rowLabel` (`width: calc(var(--theme-grid-block) * 2)` = 48px, `opacity: 0.5`) + a `.controls` flex whose direct children each take `flex: 1 1 0` (so a row reads as 1–4 equal cells). Controls are the mini primitives — `FormFieldMini` / `FormSelectMini` (a single `--theme-grid-block` = 24px tall box, `'SFMonoSquare-Regular'` values), `FormSlider bare` (the track with no head), and a local `ToggleMini` (segmented plain buttons, `transparent → border → transparent` top/bottom hairlines and inter-item dividers, the active item filled `--theme-border-subdued`).

### The `Form*` body-primitive anatomy (rebuild the `?columns=2` kitchen-sink without repo context)

The default route (`/-/vmax-tools-template?columns=2`) is `FormTemplate` + `FormTemplateGraphs`, composed entirely from the primitives in [Body primitives](#body-primitives-next-vmax-tools). Their per-component BEHAVIOUR is catalogued above; these are the measurements that catalogue does not carry — enough to rebuild each layout from the skill alone. They resolve to **four shared surfaces**; learn the surface once and every primitive on it follows.

**Shared body frame.** Every primitive `.root` is `max-width: var(--tools-body-measure, 768px)`, which resolves to `768px` everywhere except inside the campaign shell's content well (that well raises the token to `calc(var(--theme-grid-block) * 38.4)`, `921.6px`, so campaign data breathes; a standalone clone can hard-code `768px`), with `margin-top: var(--theme-grid-block)` (the vertical rhythm between stacked primitives) — a caption/heading uses the `subHeading` idiom above. A `caption`, when present, is `font-size-small` / `line-height-small` / `font-weight: 600`, `padding-bottom: var(--theme-grid-block-applications)`, sitting above its figure.

**Surface 1 — the measured code-row** (`FormCode`, `FormJSONExplorer`, `FormTextCompare`, `FormTextCompareSideBySide`). A `.scroll` wrapper (`overflow-x: auto`) around `.lines`: `'SFMonoSquare-Regular'` at `font-size-small`, `min-width: 100%; width: max-content`. The filled forms (`FormCode`, `FormJSONExplorer`) add `background: var(--theme-border-translucent)`, `padding: var(--theme-grid-block-applications) 0`, and top/bottom `transparent → --theme-border → transparent` hairlines; the diff forms leave `.lines` bare and wash whole rows instead. Every line is a `.row` exactly `height: var(--theme-grid-block)` at `line-height: var(--theme-grid-block)` (one grid block per line), split into a fixed `.gutter` (`min-width: calc(var(--theme-grid-block) * 2)` = 48px, `padding: 0 var(--theme-grid-block-applications)`, right-aligned, `opacity: 0.3`) and a fluid `.code` (`flex: 1 0 auto`, `padding: 0 var(--theme-grid-block-applications)`, `white-space: pre`). `FormCode`'s tokens colour by `color-mix(in srgb, var(--theme-graph-option-N) ~65%, var(--theme-text))` (strings → option-3, keywords → option-2, numbers/constants → option-5, definitions/calls → option-6, decorators → option-7), with `PASSED`/`FAILED`/`ERROR`/`WARNING` taking the diff tones; a numbered gutter, 400-char plain-render cutoff, 512-line cap. The diff forms wash a `.row` with `--theme-diff-delete-wash` / `--theme-diff-insert-wash` and put a `−`/`+` `.marker` (`width: var(--theme-grid-block)`, centred, in the solid diff tone) before the gutter; the side-by-side sibling splits into two `.pane`s (`flex: 1 1 0`) over a vertical `.divider`, unpaired rows getting a 45° hatched `.filler`, under a `.labels` row of two `flex: 1 1 0` mono captions. `FormJSONExplorer` adds a `.toggle` (`+`/`-`) in `--theme-focused-foreground` at the row's indent.

**Surface 2 — the hairline field** (`FormInput`, `FormSelect`, `FormSegmentedControl`, `FormMetric`, and `FormTextArea`). The `.root` (or `.metrics` / `.segments`) is `background: var(--theme-border-translucent)` with top/bottom `transparent → --theme-border → transparent` hairlines and NO side border — the "never reads as an editable input" identity — at the **48px field scale**: the input/select is `height: calc(var(--theme-grid-block-applications) * 4)` (48px), `font-size-small`. An uppercase `.label` (`font-size-very-small` / `600` / `letter-spacing: 0.2px` / `opacity: 0.5`, `padding: var(--theme-grid-block-applications) var(--theme-grid-block) 0`) sits above. `FormSelect` additionally draws left/right vertical hairlines on `.row` and pins a `.caret` `right: var(--theme-grid-block)` (so a select never reads as a text field), and its `option`s pin `color: var(--theme-text); background: var(--theme-background)` for OS/page theme mismatch. `FormSegmentedControl` / `FormMetric` split their strip into `flex: 1 1 0` cells over vertical `.divider` hairlines: a segment is `opacity: 0.5` unselected, `opacity: 1; font-weight: 600` selected (its radio clipped to a 1×1 `.input`, not hidden, so arrow keys work); a `.metric` stacks a mono `.value` (`font-size-large`), a `.label` (`font-size-small`, `opacity: 0.5`), and a `.delta` (mono `font-size-small` / `600` in the diff tone, `▲`/`▼`). `FormSlider` is the same translucent panel as a `height: var(--theme-grid-block)` track — `background-color: var(--theme-border-translucent)` under a `background-image: var(--theme-table-ltr-gradient-background)` fill — with a flush square thumb (`width: var(--theme-grid-block-applications)` × `height: var(--theme-grid-block)`, solid `var(--theme-border)`); its `.head` is the `.label` + a mono `.value` readout, which `bare` drops along with the margin and max-width.

**Surface 3 — the data table** (`FormTable`). A `.scroll` (`overflow-x: auto`) around a `border-collapse` `.table` at `font-size-small`, `max-width: 768px`. Cells are `padding: var(--theme-grid-block-applications) var(--theme-grid-block-applications) var(--theme-grid-block-applications) 0`, `min-width: calc(var(--theme-grid-block) * 4)` (96px, `white-space: nowrap`); a `fluid` column is `white-space: normal; min-width: calc(var(--theme-grid-block) * 18)` (432px) and takes the slack. Variants tint via the table gradients: `gradient` gives headings the `rtl` gradient and body cells the `ltr` gradient, `ruled` a `1px var(--theme-border-subdued)` row rule (`--theme-border` under the head), `striped` an even-row `--theme-border-translucent` wash. A grouped table's full-width group header row takes the `rtl` gradient; a `sortable` header is a `.sortButton` with a stacked 7px up/down caret (`opacity: 0.25`, active `1`).

**Surface 4 — the fold & the list** (`FormDisclosure`, `FormList`). `FormDisclosure` is a `<details>` on `.details` (`background: var(--theme-border-translucent)`, top/bottom hairlines); adjacent disclosures pull flush (`.root + .root { margin-top: calc(var(--theme-grid-block-applications) * 0.5) }`) so a stack reads as one accordion. Its `.summary` is `display: flex; align-items: baseline; gap: var(--theme-grid-block-applications); padding: var(--theme-grid-block-applications)`, led by a mono `+`/`-` `.marker` in `--theme-focused-foreground`, with an optional right-aligned mono `.meta` (`opacity: 0.5`); the `.body` is padded and carries a top hairline. `FormList` shares one fixed gutter for both marker forms: unordered rows get a 6×6 `.square` (`box-shadow: 0 0 0 1px var(--theme-border)`, centred on the line), `ordered` swaps it for a right-aligned mono `.number` in a `calc(var(--theme-grid-block-applications) * 1.5)` gutter (`opacity: 0.5`).

**Left to the catalogue on purpose.** The chart/graph primitives — `FormGraphCompare(SideBySide)`, `FormDistributionCompare`, `FormTokenHeatmap`, `FormRollout`, `FormMatrix`, `FormRatioBar`, `FormStackedBar`, `FormDumbbell`, `FormSparkline`, `FormPipeline`, `FormEquation`, `FormLineage`, `FormPreferencePair`, `FormTranscript` — render through `GraphBlockWrapper` (`@document-system-components`), server-safe KaTeX, or their own SVG/heatmap layout, all on the shared body frame (768px, `margin-top: grid-block`, the diverging red↔green scale for signed cells, the `--theme-graph-option-*` cycle for series). Their [Body primitives](#body-primitives-next-vmax-tools) rows are the spec; rebuild them from the graph reference (`create-old-vmax-application-ui`), not from a measurement here.

### The campaign-console anatomy (rebuild the mulberry console without repo context)

The console lives at `/-/vmax-tools-template/campaign-template/mulberry`, beside the `/-/vmax-tools-template/campaign-template` walkthrough it instantiates.

The campaign console is the worked proof that the kit hosts a LIVE application: four page states on the `campaignShell` chrome, all fixture-driven (no API, no polling). These are the ground-truth measurements it was built against — with the shell skeleton and the four surfaces above, they are enough to rebuild every state from the skill alone.

**The `campaignShell` skeleton.** When `AppContainer.campaignShell` is set, the standard chrome (top bar, grain host, floating pill) wraps this instead of a variant layout:

```
<div .root.campaignRoot>
  <div .sidebar.campaignSidebar>   pinned 240px rail (flex: 0 0 240px at EVERY width, no
    {sidebar}                      sub-568px collapse); position: sticky; top: 0;
                                   align-self: flex-start; max-height: 100dvh;
                                   overflow-y: auto  (scrolls independently — no ScrollSpacer)
  <div .divider>                   the 1px vertical gradient hairline
  [ <div .sidebar.campaignSidebar.campaignSidebarTasks> {tasksSidebar} </div> + <div .divider> ]
                                   only when set; the tasks rail is just under 1.6x the history rail:
                                   flex: 0 0 calc(var(--theme-grid-block) * 15.96) = 383.04px
                                   at the 24px grid, so summary values and task cards breathe
  <div .content>                   width: 100%; min-width: 10% (568px floor under 1200px);
                                   sets --tools-body-measure: calc(var(--theme-grid-block) * 38.4)
                                   (921.6px, 20% over the 768px default) so every Form* body
                                   cap — max-width: var(--tools-body-measure, 768px) — widens here
    showSettings ? .fluidColumn + .columnDivider + .fixedBarColumnSettings (fixed 480px, {settingsColumn})
                 : {children} fill the well alone
```

**The campaign columns never squish.** Under `max-width: 1200px` (the 240px rail + the 383.04px tasks rail + two 1px dividers + the 568px content floor, 1193.04px, rounded up) `.campaignRoot` becomes an `overflow-x: auto` rail and `.content` takes `min-width: 568px`, so a `?campaign=` view scrolls horizontally on narrow viewports, one full-width column at a time. Wide viewports keep `overflow: visible` so the rails' `position: sticky` still engages against the page scroll. History rows keep their labels and Fork on phones via `SidebarItem preserveMobileLabel` (see the sidebar anatomy above).

With `onToggleSettings` set, ONE `Settings` item replaces `Col-1`/`Col-2` in both control bars (active while `settingsActive`); the top bar's Holy/Light/Grain/Hide Overview group wraps in a `calc(var(--theme-grid-block) * 14.96)` container (one grid block narrower than the tasks rail, on the live grid token) so the following divider lands on the second-sidebar/content seam.

**The four states.** One controller (`FormCampaignConsole`) reads `?campaign=` in a `useState` lazy initializer (so SSR matches the first client render) and keeps the id addressable via `window.history.replaceState` (a same-page URL update, not a navigation). It also owns the selected-task id (reset by a `useEffect` on the memoized task list), because the task list is a sidebar and the trace is content — one owner keeps them in sync.

| State | URL | Second sidebar | Content well | Settings rail |
|---|---|---|---|---|
| Composer (create) | bare | none | `FormCampaignComposer` | shown by default |
| Env-creation build | `?campaign=fixture-running` | Summary + Task History (one smoke task) | `FormCampaignBuildConsole` | hidden by default |
| Mid-generation build | `?campaign=fixture-generating` | Summary + Task History (accepted tasks + one `running` card) | `FormCampaignBuildConsole` (no files section) | hidden by default |
| Completed results | `?campaign=fixture-completed` | Summary + Task History | `FormCampaignResults` | hidden by default |
| Failed build | `?campaign=fixture-failed` | Summary + Task History (empty note) | `FormCampaignBuildConsole`, error state | hidden by default |

A failed campaign has `generated: 0`, so it is NOT results-ready (the live gate: `generated > 0 && terminal`) — it stays a build console with an error activity row and never renders a results document. Rail visibility is `settingsOverride ?? !selected` (shown on the composer, hidden on a campaign; the override resets on every id change). Create/Fork/Stop are simulated client-side, each exercising its real case: **Stop** (a `GlassButton tone="negative"` as the summary's `action`, building campaigns only) derives a terminal STOPPED view of the fixture — history row re-labels `stopped`, stream closes, the activity log gains a red "Campaign stopped by user" row, workload metrics settle to em-dashes under the kept `Elapsed`, and every task loader stops (the controller passes derived summaries into the history sidebar, so one owner re-labels both). **Fork** re-opens the composer PRE-FILLED with the parent's goal. **Submit** validates (a too-short prompt raises the composer's mono red `error` line) then runs a short disabled beat (`ButtonLoader` in the textarea's `buttonIcon`) before landing on the env-creation campaign. A build task flagged `running` shows a `$ working…` card line, and its empty solver tab shows the trace's "No solver rollouts captured" fallback; the `smoke` flag titles the trace "Smoke rollout evidence" vs "Task rollout evidence".

**Campaign history (the first sidebar, `FormCampaignSidebar`).** A `.create` block (`padding: var(--theme-grid-block)`: a 14px/600 heading, a muted paragraph, a full-width `GlassButton`), the "Campaign History" gradient band (the `.subHeading` idiom at `padding-left: var(--theme-grid-block)`), then `Sidebar` + one icon-less clickable `SidebarItem` per campaign — `label` = the goal, `description` = `{status} · {progress} · {updated}` (the timestamp a deterministic UTC stamp, so SSR and any client timezone agree), the selected row `active` and every other `dim`, each row `preserveMobileLabel` (the campaign rail never collapses, so goal + Fork stay readable at phone widths), plus a `trailingAction` Fork: a chrome-reset text `<button>` at `font-size-very-small` in `--theme-focused-foreground`, `padding-left: var(--theme-grid-block-applications)`.

**The second sidebar is STRUCTURE, not content** — two `.subHeading`-banded sections whose selection the controller owns:

- **"Campaign Summary" (`FormCampaignSummary`)** — a stat list padded `var(--theme-grid-block-applications) 0`: each row `min-height: var(--theme-grid-block)`, `padding: 0 var(--theme-grid-block)`, `justify-content: space-between`, `gap: var(--theme-grid-block-applications)`; the label at `font-size-small`, `opacity: 0.6`, single-line ellipsis; the value in `'SFMonoSquare-Regular'` at `font-size-small`. `value === undefined` renders the `InlineLoader` braille spinner (a number still being computed — the running state leads with one REAL row, the `Elapsed` duration, over four loaders); the failed state passes em-dashes. An optional `action` slot beneath (`padding: var(--theme-grid-block-applications) var(--theme-grid-block) var(--theme-grid-block)`) holds the running state's red Stop button.
- **"Campaign Task History" (`FormCampaignTasksSidebar`)** — a column of chrome-reset `<button>` cards (`gap: var(--theme-grid-block-applications)`, the list padded the same vertically), each a `WebTerminalWindow`. The card title insets `padding: 0 var(--theme-grid-block-applications) 0 var(--theme-grid-block)` so it shares the sidebar's 24px text edge, with a `»` caret in `--theme-focused-foreground` (gap `calc(var(--theme-grid-block-applications) * 0.5)`) on the SELECTED card only; log lines are `'SFMonoSquare-Regular'` `font-size-very-small` / `line-height-small` at `opacity: 0.75`, single-line ellipsis, with `min-width: 0` down the whole flex chain so long task ids ellipsize inside the 240px rail. The selected card shows all its lines; the others show one. No tasks → a muted note (`font-size-very-small`, `opacity: 0.55`).

**Content bodies share the `FormTemplate` measure** (`FormCampaignBuildConsole`, `FormCampaignResults`): `max-width: var(--tools-body-measure, 768px)` (`921.6px` inside the campaign shell, where `.content` sets the token to `calc(var(--theme-grid-block) * 38.4)`), `padding: var(--theme-grid-block) var(--theme-grid-block) calc(var(--theme-grid-block) * 10)` (a deep scroll-past bottom gutter), direct children in `.block` wrappers spaced `margin-top: var(--theme-grid-block)` (first flush; `.block > :first-child { margin-top: 0 }` so the block owns the rhythm). NO title banner — the identity lives in the sidebars. Headings and paragraphs are `FormSubHeading` / `FormParagraph`; every `FormDisclosure` and rollout is `defaultOpen` (the console is a demo — nothing starts collapsed). The build body stacks the activity disclosure, the implementation disclosure (prompt paragraph + a NESTED per-file `FormDisclosure` list — the exported `CampaignFileDisclosure`: a short-path label, a byte-size `meta`, a copy `ButtonActionGlass` (`Clipboard` icon, lit `active` for 1.2s after a copy) in the `headerAction` slot, a `FormCode` body), then the trace; the failed state drops implementation + trace. The results body stacks the trace, workload analysis (`FormSubHeading` + `FormParagraph` + `FormStackedBar`), and reproducibility (`FormDisclosure`s over a striped `FormTable` + the activity log).

**Activity log (`FormCampaignActivityLog`).** A `role="log"` mono (`'SFMonoSquare-Regular'`) body capped at `max-height: calc(var(--theme-grid-block) * 22)`, `overflow-y: auto`, `scrollbar-gutter: stable`, padded `var(--theme-grid-block-applications) 0`. Its header line is uppercase `font-size-small` at `letter-spacing: 0.06em`, `opacity: 0.5`. Each row is a grid `calc(var(--theme-grid-block) * 4.75) minmax(0, 1fr)` with `column-gap: var(--theme-grid-block-applications)` and `padding: 1.5px 0`: a right-aligned uppercase tag (`max-width: 90px`, `letter-spacing: 0.04em`), then wrap-anywhere text. Tag colours stay legible on both themes by mixing toward the text colour: demiurge = `color-mix(in srgb, var(--theme-graph-option-2) 60%, var(--theme-text))`, bash = option-4 at 70%, setter = option-6 at 60%; write/ok = `--theme-diff-insert`; error rows take `--theme-diff-delete` on tag AND text; read/solver sit at `opacity: 0.65`. While running, the LAST row carries a `ButtonLoader` (`var(--theme-grid-block-applications)` square) before its tag.

**Task trace (`FormCampaignTaskTrace`).** An ordinary content section — `FormSubHeading` title + `FormParagraph` (the selected task id) + a `FormSegmentedControl` toggling Setter/Solver (reset to `setter` whenever the selection changes) over `FormDisclosure` stacks. When the task carries prompts (a completed campaign's tasks do), each tab's accordion LEADS with that side's prompt as a `FormDisclosure` over a `FormParagraph`, flush with the rollouts; after the tabs, a "Task files" `FormDisclosure` (meta: file count) of nested per-file `CampaignFileDisclosure`s renders the task's harbor files — the same file-fold the build console uses. Inside each rollout, a timeline column (`gap: var(--theme-grid-block-applications)`, padded the same vertically) of tone-coded steps: each step sits `padding-left: var(--theme-grid-block-applications)` behind a `border-left: 2px solid` in the tone colour — `--theme-diff-insert` (good), `--theme-diff-delete` (bad), `--theme-graph-option-4` (warn), `--theme-border-subdued` (muted, the default) — an uppercase mono step label (`font-size-small` at `line-height-small`, matching the `FormParagraph` body size, never a smaller mono scale; `letter-spacing: 0.04em`, `opacity: 0.6`, numbered `{n} · {label}`) over a detail line (`font-size-small` / `line-height-small`, `opacity: 0.85`).

**Composer (`FormCampaignComposer`).** A centred flex column filling `min-height: calc(100dvh - 48px)` (`padding: var(--theme-grid-block)`): a centred `font-size-large` / `line-height-large` 600 heading capped at 768px; the `FormTextArea` (auto-grown to its multi-line placeholder, its `buttonIcon` the `ArrowRight` glyph — or a `ButtonLoader` while submitting); a 768px-cap file list — each row `min-height: var(--theme-grid-block)` on a `border-bottom: 1px solid var(--theme-border-subdued)` hairline, a mono filename ellipsized left and a muted chrome-reset "remove" text button right (`font-size-very-small`, `opacity: 0.5`, hover `--theme-diff-delete`); deliberately NO bullet or `FormList` square that could read as a checkbox — then a right-aligned square `ButtonActionGlass` + `Plus` add-file button; an optional mono `--theme-diff-delete` error line.

**Config rail (`FormCampaignConfigRail`).** Nothing new to measure: it composes `SettingsColumn`'s exported primitives — `SettingsRail` (the root + two equal `.gridColumn`s split by the vertical hairline), `Section` (the gradient `−`/`+` band), `Row` (the fixed 48px label + equal-flex control cells) — over `FormSelectMini` / `FormFieldMini` + `FormSlider bare` pairs, every section open. Rebuild it from the `SettingsColumn` anatomy above with campaign fields (Environment / Setter / Solver) instead of computers. `ToggleMini` is NOT exported — it stays local to `SettingsColumn`; a composed rail renders an on/off knob as a two-option `FormSelectMini` (the campaign rail's Network-access field is the precedent).

## Conventions

- **Route files are server components.** No `'use client'` on `page.tsx`; export `dynamic = 'force-dynamic'` and `generateMetadata` (route metadata comes from `buildToolsPageMetadata`).
- **Bodies are client components.** `'use client'` at the top, paired with a `.module.css`. Match the `root` / `heading` / `subHeading` / `paragraph` / `divider` typographic scale the existing bodies share (small font, `--theme-grid-block` line height, 768px max width).
- **Grid-snap and theme tokens only.** Size off `--theme-grid-block` / `--theme-grid-block-applications`; reference `--theme-*` custom properties in CSS, never inline theme values.
- **Client-app font.** The shell sets `font-family: var(--theme-font-family-client-apps)` (the system stack); keep bodies on it so they match `AppContainer` and `ModalAuthentication`.
- **Brand casing.** `Vmax` in prose and PascalCase identifiers; `VMAX` only for the wordmark/product/domain and `SCREAMING_SNAKE_CASE` constants.
- **No comments.** Comments are banned everywhere in this codebase (see the root `AGENTS.md`). Only machine-read directives like `@ts-expect-error` or `eslint-disable` survive where technically required; knowledge that would have been a comment lives in the deepest owning `AGENTS.md`, citing the exact function or symbol name.

## Rules

- **One registry.** Add pages to `TOOLS_PAGES`; do not hand-wire a route under `/-/vmax-tools-template`. The `[...tool]` route derives from the registry, so a one-off route there drifts from it. The default sidebar's Template TOC (`EXAMPLE_TEMPLATE_SECTIONS`) and the shared sidebar (`SHARED_SIDEBAR_GROUPS`) both live in that same file, not a per-page nav. A registry page may ALSO be productionized at a short public URL by a thin route that composes the same registry helper rather than re-implementing it: `app/research/page.tsx` fetches the posts and renders `researchPageProps(posts)`, the exact helper the `[...tool]` route uses for the `research` key, with its own public `generateMetadata` instead of `buildToolsPageMetadata` (whose "Vmax Tools —" title and `/-/vmax-tools-template/...` URL are wrong for a public page). The SAME shell backs the explanatory-notice surfaces through the optional `notice` on `researchColumns`/`researchPageProps` (`/research?access=false` also uses it for the rejected-sign-in explanation): `app/not-found.tsx` uses `researchNotFoundPageProps(notice)` to render an EMPTY post list (no feed, sidebar `Research` group dropped, only `Vmax Tools`) plus a `notice` paragraph, wrapped in a `<Suspense>` because the global not-found is statically prerendered and `AppContainer` calls `useSearchParams` (the CSR-bailout the `force-dynamic` routes avoid); and `app/[username]/page.tsx`, for a non-reserved username with no user, uses `researchPageProps(posts, notice)` to render the FULL research index plus one explanatory paragraph naming the missing user (a `force-dynamic` route, so no `<Suspense>`). The registry row stays the single definition of the page's columns, sidebar, and chrome; the second route is only a URL. The ONE sanctioned exception is a `campaignShell` application as a STATIC nested route beneath a registry page — the `/-/vmax-tools-template/campaign-template/mulberry` precedent (`app/-/vmax-tools-template/campaign-template/mulberry/page.tsx`): App Router resolves the static segment ahead of the `[...tool]` catch-all, and the route file is an ordinary server component with `export const dynamic = 'force-dynamic'` and its OWN `generateMetadata` (not `buildToolsPageMetadata`, which is registry-driven), rendering the `'use client'` controller as its body. Old paths (`/-/vmax-tools-template/client-usage`, `/-/mulberry-campaigns`) redirect in `next.config.ts`.
- **Every new body pairs a `.module.css`.** A component that introduces styling of its own always carries its module; the only components without one are pure compositions/controllers that add zero classes (`FormCampaignConsole`, `FormCampaignConfigRail`).
- **Stable column keys.** Each element in a row's `columns` needs a `key` (they are module-level elements).
- **Keep the default on the bare path.** Route new pages through `toolsPageHref`; only change which key is default via `DEFAULT_TOOLS_PAGE_KEY`.
