---
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` 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`** ([`/glossary#app-container`](/glossary#app-container)): its chrome is `ApplicationNavigation` (top bar) and `ToolbarControlsFixed` (fixed bottom toolbar).
- **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.
- **Theme and Clear Distractions use `NavigationDioramaButton` in both bars.** Each is three grid blocks wide and 27.2px tall, vertically centered in its bar, and shows one simple 16px icon set 16px left of center to balance the dissolve: a crescent or a sun for the theme, and an interface window with or without its sidebar for Clear Distractions. Clicking exchanges it for the other icon at the same size: the old icon Bayer dithers out while sliding right and the new one dithers in from the left. The right edge dissolves through a stationary Bayer mask into the bar, and hover, focus, and press darken the frame. Keep `THEME_TOGGLE_LABEL` and `hideOverviewLabel` as the accessible names, and the existing callbacks as the actions. The control has no tooltip. The contract lives in `next-vmax-tools/AGENTS.md`.
- **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 `SHARED_SIDEBAR_GROUPS` — the one sidebar the default, `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[];
  columnVariant?: 'client-ui-columns' | 'horizontal-columns' | 'island-scene';
  standalone?: 'campaign-template' | 'slides-template';
  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;
  publicAccessStaffOnly?: 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;
  appIcon?: React.ReactNode;
  href?: string;
  isViewer?: boolean;
  signedInOnly?: boolean;
  signedOutAction?: 'campaign-access';
  preserveMobileLabel?: boolean;
  selects?: ToolsSidebarSelect[];
  routes?: ToolsSidebarRoute[];
}
```

A group header can also be an ENTRY rather than a label: `href` makes it a link and lights it `active` on a pathname match; `appIcon` puts a complete icon in the square slot, with its label outside; `signedInOnly` hides the group when signed out; `signedOutAction` swaps its link for an action through a serializable token. `CAMPAIGNS_SIDEBAR_GROUP` is labelled `Campaign`, carries the ember `VmaxAppIcon`, and links to `/campaigns` when signed in or opens `FormCampaignAccess` otherwise. `PRESENTATIONS_SIDEBAR_GROUP` follows it, labelled `Presentations` (`PRESENTATIONS_LABEL` from `common/slides-copy.ts`), linking to `/slides` and hidden when signed out. Its `VmaxPresentationsIcon` uses the shared square glass frame in yellow around the glowing book SVG. Both product entries use the standard icon-and-text row. Detail: `next-vmax-tools/AGENTS.md`; icon composition: `app/AGENTS.md`.

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; a row with no `sidebarGroups` 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. `standalone: 'campaign-template'` (the `campaign` page) is a token the `[...tool]` route resolves through its `STANDALONE_BODIES` map and returns with NO `AppContainer` around it, for a body that renders the shell itself (a token, not the element, because the body imports `AppContainer`, which imports the registry): `FormCampaignTemplate` mounts the `campaignShell` exactly as the live `/campaigns` application does (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 `app-interface` row sets 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/Toggle Theme/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`) on every page, 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`, `Settings` (`viewerOnly`, a plain link to the viewer's own `/settings` page), `Waitlist` (`viewerOnly`, a plain link to the owner-only `/waitlist` page), `Create paper`, `API key`, and `Sign out` rows); a route or a group may carry `hint`, a `CampaignHint` from `next-vmax-tools/campaign-hints.tsx`, which the shell renders as the `?` glyph beside the row's icon square, before the label, with its bubble opening to the right; `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 `Debug Paper` and `Debug Vmax Tools` route inventories) that the default `sharing` page (over the kitchen-sink), 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. All 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 **not** a page switcher, and there is no per-page table of contents: the default template renders `SHARED_SIDEBAR_GROUPS` like the other reference pages. To locate a `Form*` primitive on the default page, use the browser's find-in-page. Each Item's icon is the app's glass square button (`ButtonActionGlass`); its plain-square SubItems read a level apart. The `FormTemplate` / `FormTemplateGraphs` headings keep their `id`s as direct-link anchors; a second-column heading needs `?columns=2` in the URL to be rendered before the jump.
- 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]`; `sidebarGroups: SHARED_SIDEBAR_GROUPS`. | 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. |
| `/islands/chess-stage` | `islands`. The preset's `presentation` token selects `FormChessStage` through `ISLAND_PRESENTATIONS`. | The closer Immortal Game view, catalogued in the Island dropdown with `Mode:Zoom`; `/chess-stage` redirects here, with no separate tools or sidebar row. The body measures its content well after mount and passes explicit dimensions to `ChessStage`, which shares the existing game preset and fits the island to the canvas. |
| `/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 `Debug Paper` / `Debug Vmax Tools` route inventories; the `[...tool]` route passes the viewer trio from `Server.setup`, so the `Sign in` / `Profile` / `Create 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. |
| `/characters` | `characters`. `columnVariant: 'horizontal-columns'` with `sidebarGroups: SHARED_SIDEBAR_GROUPS`; one `FormCharacterPortrait` per `CHARACTERS` entry (`next-vmax-tools/characters.ts`). | The talking portraits on the horizontal rail: each column is a name, a role, the default `FormHintAvatar` the help tooltips seat (same size, same yaw, its `READ` button), and the speech it reads as `SpokenText` paragraphs that light word by word; columns cap at the viewport height and scroll inside themselves. Seth, the World Captain, reciting the opening of the Odyssey. |
| `/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`: the sidebar's "Start a campaign" button opens the campaign template, 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`. |
| `/app-interface` | `app-interface`. `sidebarGroups: SHARED_SIDEBAR_GROUPS` (the same sidebar as the bare template); `hideColumnControls: true`; one `FormAppInterface` column. | Every production page body of the app in ONE place, each as a titled section beside the shared tools sidebar: `FormUserSetup` (the live `/{username}` setup screen in demo mode), `FormInputPageTemplate` (the one-field template `/settings` and the setup screen run on), `FormProfile` on fixture posts as the owner sees it, `FormProfile` empty, and `FormResearch` on `STATIC_RESEARCH_POSTS`. It replaced the `forms`, `profile`, `profile-empty`, and `research` rows (all four paths redirect here, `/research` to the public route), so a body is reviewed in one place instead of four. The live routes keep rendering the same components: `/research` through `researchPageProps` with a per-request fetch, `/{username}` and `/settings` directly. Row anatomy: `next-vmax-tools/AGENTS.md`. |
| `/glossary` | `glossary`. `serverData: 'research-sidebar'` (the viewer's readable research posts); `hideColumnControls: true`; one `FormGlossary` column. | The term reference `/glossary` serves in production, previewed on the tools shell: one alphabetical `FormTable` over `common/glossary.ts`, terms cross-linked inline. Every hint's `terms` footer links here. |
| `/help` | `help`. `serverData: 'research-sidebar'` (the viewer's readable research posts); `hideColumnControls: true`; one `FormHelp` column. | Every help tip in the product (`CAMPAIGN_HINTS`), each as the `FormHintPreview` card its surface opens, under the surface name; the production `/help` page previewed on the tools shell. |
| `/campaign` | `campaign` (the key was `client-usage`, then `campaign-template`; both redirect). `standalone: 'campaign-template'`, resolved by the route to `FormCampaignTemplate`; `columns: []`. | The **live campaign application on fixtures**: the same `campaignShell`, history rail, composer, launch rail, task rail, and content bodies as `/campaigns`, with every state a row in the rail (building with Stop, stopped, loading, unreadable with its error modal, plus the four example campaigns and the Atlas) and every interaction simulated at 1.2s (Stop, an alternating success / 500 create, the 400 empty-prompt modal, Refine, Fork). No query params are needed; `?campaign=` and `?view=tasks` are addressable like the live app. Detail: `next-vmax-tools/AGENTS.md`. |
| `/slides` | `slides`. `standalone: 'slides-template'`, resolved by the route to `FormSlidesApplication` on `feed="fixtures"`; `columns: []`. | The **live presentations application on fixtures**: the same `campaignShell`, presentation rail, and two-column well (the Markdown column with matching top and bottom action toolbars, image drop sections, and the Downloads and Publish folds; the preview column with a template select for each slide) as `/slides`, the two example decks read-only in the rail, Fork producing an editable copy, and every save, image upload, render job, and publish simulated in memory at 1.2s. `?deck=` is addressable like the live app. Detail: `next-vmax-tools/AGENTS.md`, `## /slides`; the Markdown dialect: `skills/create-vmax-presentation/SKILL.md`; the application and API client: `skills/use-slides-api/SKILL.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 and fixed toolbar) and the fixed-rail-plus-fluid column geometry (fluid content well; optional settings rail at `--tools-settings-width` (24 grid blocks) when `showSettings`; with `columns={2}` and two children the well itself splits on the default `.columns` grid, which is how `/slides` seats its Markdown column beside its slide preview column), with a sticky independent sidebar (`.campaignSidebar`) instead of `ScrollSpacer`. The **`/campaigns`** application is the worked reference: `FormCampaignApplication` reads `?campaign=` (via `useSearchParams` in a `useState` initializer, so SSR matches the first client render) and 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`, `FormCampaignTableOfContents`, `FormCampaignTaskTrace`, `FormCampaignActivityLog`, `FormCampaignComposer`, `FormCampaignLaunchRail`, `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 `FormCampaignLaunchRail` 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 Table of Contents" = `FormCampaignTableOfContents`, including its nested task links) 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`.

**Build fixtures-first; that ordering is the pattern to copy.** Fixture states come up as a component catalog, never as a second application: today they render component by component on the default tools page through `FormTemplateCampaign`, while `FormCampaignApplication` lists only what the API returns. **Do not leave a demo controller standing once the live one works.** Two controllers over one component set drift; keep the fixtures, because they are the only place several components render at all, but mount them as a catalog. Fixtures-first is what makes the live controller cheap: the live work is a typed client (`campaign-api.ts`), a set of PURE adapters onto the shared view model (`campaign-live-view.ts`), and a controller, with no second set of components. Three structural rules come with it. The view-model types belong in their own neutral module (`campaign-view-model.ts`), re-exported from the fixtures module so nothing else has to move, so live code and fixtures are peers over one contract rather than live code importing from a file named "fixtures". Anything a component hard-codes that a real backend words differently has to be data (`CampaignResults.workload` is `{ label, segments, values }`, so one `FormStackedBar` renders the API's three outcome buckets and the fixture's four bands). And an existing component grows an OPT-OUT prop rather than a fork when one case has less to show than the other (`FormCampaignComposer.showFiles`, `FormCampaignSidebar.profileHref`), so a second feed changes no rendering for the first. Errors go through the modal system (`FormCampaignErrorModal` + `campaignErrorDetail`, deduped by signature so a failing poll raises one card, not one per tick), which stacks as cards on mobile and windows on desktop; that is a deliberate departure from the `alert()` surface the older live flows use. The card says what happened and what to do, then lists the next actions as `FormStrip` rows (retry, sign in, new campaign, dismiss) whose handlers the controller passes in as `CampaignErrorRecovery`; a failed or stopped campaign additionally leads its content with `FormCampaignFailureNotice` (the worker error, a `FormHint` on why a worker failure cannot resume, and Fork / New / Read-the-log strips), and a completed one with `FormCampaignCompletionNotice` (the same card on `FormCard`'s `insert` tone, glowing the success green, with a result band holding a `FormMetric` strip of the campaign's own metrics (never a dotted sentence) and Read-the-workload / Read-the-evidence / Fork / New strips). Detail: `next-vmax-tools/AGENTS.md`.

To stand up another console like it:

1. **Create ONE route, and make it the real one** — 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/campaigns/page.tsx`. Do not stand up a second `/-/` route for the fixture states; they belong to this controller as an example feed (step 3). A static nested route under `/-/vmax-tools-template/{registry-page}/{name}/` still resolves ahead of the `[...tool]` catch-all if you genuinely need a non-registry reference surface, but the campaign case retired its own, and the cost is worth naming: the design reference then sits behind whatever auth the product route enforces.
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`). 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, serving BOTH feeds** — a `'use client'` component in `next-vmax-tools/` (`FormCampaignApplication` 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. Resolve a fixture id BEFORE any fetch (`const fixture = useMemo(() => fixtureById(selectedId), [selectedId])`) and return early from the polling effect on it, so an example issues no request and can raise no error. Switch the shared derived list on the same value, so one selection effect serves both feeds. Render examples READ-ONLY: no destructive action, a disabled settings rail, and any create-like action routed into the real flow.
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 (`FormCampaignLaunchRail` over `SettingsRail`/`Section`/`Row`, `FormCampaignSidebar` over `Sidebar`/`SidebarItem` are the models), measured per [the campaign anatomy](#the-campaign-application-anatomy-rebuild-it-without-repo-context).

### Compose a Campaign application

Use `CampaignAppAction` for short action controls, passing a plain label so the component supplies exactly one bracket pair. Keep native selects and data nodes on their existing components. Application launcher buttons use the shared decorative `VmaxFlag` inside their glass squares.

Use `CampaignApp` for the common top toolbar, trailing count, flexible work area, and bottom campaign identity/return action. `CampaignAppField` wraps the existing action select, `CampaignAppControls` groups related controls, and `CampaignAppViewControls` aligns diagram display and zoom controls above the footer. Keep application-only components under the `CampaignApp*` names and their data/capability contract in `campaign-app-types.ts`; general fields, code, Markdown, and comparisons remain the existing `Form*` primitives. The owner, shared, and fixture controllers supply readers and capabilities. Keep terminal content mounted through presentation changes and scope its reset inside the frame. The detailed rules belong in [Campaign Applications](../../next-vmax-tools/AGENTS.md#campaign-applications).

`CampaignAppConclusionTimeseries` is the comparison-chart composition: the existing `GraphBlock` line renderer with precise elapsed-time ticks, stepped observations, shared percentage axes, and system palette colors. It owns interactive metric controls and a cursor/zoom overlay using the renderer's exported `lineGraphPlot` geometry, while the graph itself remains memoized on primitive props. Its work table is the existing `FormTable`, and its data comes from the controller's typed raw timing projection plus authorized schedule and inventory reads. Follow the [Campaign API skill](../use-campaign-api/SKILL.md) for measurement timing and shared-reader limits; the chart's observed solve rates never become a learning claim.

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

`FormPublicAccess` follows the waitlist/post-list composition for Vmax partner domains: a capped introduction and `FormInput`, full-bleed metadata rows, inline action chips, and the shared confirmation/error modals. It imports the waitlist row styles so these account lists stay visually aligned. `/public-access` runs it live; `app-interface` demonstrates fixture-only interactions. Its access policy and Vmax-only API procedure belong to [post-permissions-and-viewing](../post-permissions-and-viewing/SKILL.md), not the tools layout.

For code comparisons inside a post, the [posting guide](../../public/SKILL.md#code-diffs) documents `::diff()`. Its `DiffBlock` composition reuses `FormTextCompareSideBySide` from FormTemplate inside `ApplicationWindow`, with labelled, unwrapped source columns and horizontal scrolling on narrow screens. `/-/sample` demonstrates the article use; keep the row renderer and differ shared.

`FormInput`, `FormTextArea`, and `FormFieldMini` use `FormFieldSurface` as their outer element. Clicking their padding, border, label, or decoration focuses the field; embedded actions retain their own click behavior. Use this same surface for a new decorated text-entry control, keeping the field as its only keyboard stop. Native fields use the field ref, while `FormMarkdownInput` supplies `FormMarkdownInputHandle.focus` so Slate owns the caret. A custom editor's empty background also invokes that focus handle. The whole empty placeholder block is pointer-transparent so every wrapped line reaches that background; entered text retains native caret placement. The liquid field adds a 4px rounded hit area beyond its layout box to include the outer reflection, below its text and action rows. Its focus handle synchronizes the browser selection through Slate's DOM range conversion even when the editor already holds DOM focus. Surface presses focus before the browser's default mouse-down action can clear selection, with click handling for other activation paths. `FormTextArea` labels are unselectable and still focus their field; Markdown labels use the same focus handle on press and click. Disabled fields stay inactive and read-only fields can focus for copying. The focus and modal-drag contracts live in `next-vmax-tools/AGENTS.md` under `FormFieldSurface`.

| Component | What it renders |
|---|---|
| `FormTemplate` | The default page's FIRST column, and the split between the two columns is one rule: column one holds every primitive the live `/campaigns` application is built from, column two everything it is not. Intro paragraph plus the "Open test modal" greyscale `FormLiquidButton` (opens `FormModal` through the modal system), then in the order a viewer meets them on `/campaigns`: the `FormCode` section (a python verifier gate, the shell commands that reproduce its run, and its verdict log), the unified `FormTextCompare` file comparison, `FormInput` (the text field and the `FormTextArea` prompt field, mounted with the composer's `labelHint`, placeholder, and Generate / Refine `secondaryActions`, so the demo is the composer's field), `FormSelect` (an evaluation suite whose note echoes beneath it, plus the label-less sort variant), `FormSlider` (the K slider repricing a corpus live, plus stepped context-window and LoRA-rank knobs), `FormSegmentedControl` (the planner sweep and an uncontrolled memory strip), `FormHint` (whose second example carries a diagram and which then draws the whole `CAMPAIGN_HINTS` catalog through `FormHintPreview`, each hint inside the tooltip's own card with the blank `FormHintAvatar` box beside it), `FormStrip` (four strips, one holding a local `Checkbox` control), a live `FormTaskSearch` filtering four sample tasks through `searchTasks` with `HighlightedText` marking the matches, "Tables" (the three `FormTable` variants, the LoRA-adapter card, and the grouped main-results table), `FormStackedBar` (the difficulty mix on semantic band colours and a palette-cycled environment-dressing row), 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), and the `FormMatrix` capability grid; then `FormTemplateCampaign`, every campaign component on the four example campaigns. |
| `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" greyscale `FormLiquidButton`. Opened via `useModals().open(FormModal, {})` (`@components/ModalContext`); the root layout's `Providers` mounts the `ModalRenderer`. |
| `FormTemplateCampaign` | The campaign component catalog, mounted at the end of `FormTemplate` so the bare `/-/vmax-tools-template` carries it: every `FormCampaign*` component rendered as its own titled section on the four checked-in example campaigns (`campaign-console-fixtures.ts`), the rails framed at their shell widths (240px history, the tasks-rail measure, 24-grid-block settings), the page-level bodies passed `inline`. It is where the fixtures live now that `/campaigns` lists only API examples, and the only place the trace's prompt folds, the build console's implementation files, and the failure notice's stopped state render. Contract: `next-vmax-tools/AGENTS.md`, "Example campaigns". |
| `FormTemplateGraphs` | The default page's SECOND column (`?columns=2`): every primitive the live `/campaigns` application does not use. The `FormJSONExplorer` walk of a run record, "Elements" (the `Checkbox` sharing toggles, a greyscale `FormLiquidButton` carrying `ButtonLoader`, the `FormList` pair, and the "Text styles" reference including the `InlineCode` `tone` variants), the paper figures (a `FormMetric` training-signal strip and its regressed twin, a `FormEquation` pair, a `FormRatioBar` parameter-efficiency meter plus a harvest-yield funnel, a `FormPipeline` pair, a `FormPreferencePair`), then a `WebTerminalWindow` section (the full `Loader` set) with the `FormTranscript` agent episode beneath it, a `FormTokenHeatmap` token-credit run, a `FormRollout` world-model rollout, a `FormSparkline` training-dynamics pair, a `FormDumbbell` benchmark-category-shift pair, a `FormLineage` figure of how the capstone task was bred, and 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. 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 selects its registered presentation or `next-vmax/islands/IslandStage`, else `DEFAULT_ISLAND_KEY`. The `chess-stage` preset selects the shared `FormChessStage` through `ISLAND_PRESENTATIONS`. Used by the `islands` page under the `island-scene` variant. |
| `FormAppInterface` | The `app-interface` page body: the five production bodies below (`FormUserSetup`, `FormInputPageTemplate`, `FormProfile` full and empty, `FormResearch` on the static posts) stacked as titled sections split by the gradient divider, each on its real props, so the app's pages are reviewed on one route. Its own module carries only the section heading / paragraph / divider recipe. |
| `FormGlossary` | The `/glossary` body on the `FormAppInterface` layout: the heading and intro over ONE alphabetical `ruled` `FormTable`, `Term` (with `id={slug}`) / fluid `Definition` with `See also` links beneath; no groups, one meaning per word. Data only from `common/glossary.ts`; never define a term inline. |
| `FormHelp` | The `/help` body on the `FormAppInterface` layout: Seth's codex intro (a 2× `FormHintAvatar` beside a 2× serif `TooltipCard` of `HELP_CODEX_INTRO` in smart quotes, no heading, which its `READ` reads aloud), then one section per `CAMPAIGN_HINTS` entry (surface heading + `FormHintPreview`). The only enumeration of the hint catalog. |
| `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 `app-interface` page's one-field template, made to be cloned for any pick-a-value or change-a-setting tool. TWO live pages run on it and they are the shapes to copy: `FormUserSetup` claims a username with the field, and `FormSettings` (`/settings`) CHANGES one with it while its `children` carry the read-only sections beneath. `showField` doubles as the permission gate on both (`FormUserSetup` hides the field once a username exists, `FormSettings` shows it only to a verified viewer). A read-only fact renders as a `readOnly` `FormInput` with a `placeholder` fallback, not as a bespoke label-and-value row. Four optional hooks are what a real write needs on top of the demo field: `error` (a caller-owned message, losing to the internal `validate` result so a stale request failure cannot outlive a new keystroke), `disabled` (greys the field mid-request; the glass submit square stays live, so guard re-entry in your handler), `belowField` (an unwrapped slot after the preview and error lines, for block content the `<p class=preview>` slot cannot hold), and `onValueChange` (every keystroke, so a caller can run a live check without taking ownership of the value). `/settings` uses all four: it checks username availability live against the public lookup and makes the arrow open a confirm rather than commit, since a rename breaks every published link. |
| `FormUserSetup` | The account-setup screen, live on `/{username}` for an unverified or username-less viewer and rendered in demo mode (no `viewer` prop) as the `app-interface` page's first section. 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; a non-fluid cell stays on one line up to sixteen grid blocks (384px) and word-wraps past that, so one long string cannot stretch the table. `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. Optional `beforeLabel`, `afterLabel`, `beforeHeader`, `afterHeader`, `showSummary`, and `wrap`; wrapping pairs share a CSS grid row so long paragraphs stay aligned. Reuses `diffLines`; exports `pairDiffRows`. Its demo is in `FormTemplate` because campaign task inspections use it. |
| `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. The optional `appearance="source"` renders all lines without a gutter or frame, inheriting typography; shell heredocs can highlight known Python and JSON bodies. Exports the pure `tokenizeCode`. |
| `FormMarkdownSource` | Read-only Markdown source with the same `MarkdownSourceLeaf` decorations as `FormMarkdownInput`. Takes `source`, optional `className` and `style`. Preserves markers, line breaks, and literal HTML; shared heading, emphasis, code, and link styling supplies hierarchy without changing text size. |
| `FormDisclosure` | The fold: a native `<details>` on the kit's hairline panel. `label`, `meta?` (right-aligned monospace facts — a reward, a duration, a status), `stats?` (`FormMetricItem[]`: the fold's measured facts, rendered as a full `FormMetric` strip directly UNDER the fold's heading, inside the `<details>` above the body so it collapses with it, one tile per fact, number over noun; on `/campaigns` every fact goes here and nothing goes in `meta`, since a fact stuffed into a heading bar was rejected in review), `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` | `onMarkdownFilesDrop` opts Markdown fields into source-line-aware file drops; secondary actions accept `disabled` and `busy` so another toolbar can share their state. Auto-growing multiline field with `appearance="hairline"` by default and `appearance="liquid"` for the Campaign composer. The composer also sets `markdown` and `onValueChange`, selecting `FormMarkdownInput`: a Slate source editor with full-opacity prose, heading opacity from 0.9 (H1) to 0.4 (H6), bold/italic font styles, and visible Markdown markers. Cmd/Ctrl+B, I, E, and Shift+X toggle bold, italic, inline code, and strikethrough; native undo/redo and plain-text clipboard operations preserve the source. The liquid surface combines a white CSS body in light mode and graphite in dark mode with `FormLiquidSurface`'s shared-context silver shader rim, inset footer divider, label/hint, and individual SVG `FormActionButton`s with tooltips and native accessible names. Hairline actions collapse below 568px of field width; liquid actions are always icon-only. `repeatActions` reuses the toolbar above a long draft (the composer enables it past 400 words). Cmd/Ctrl+Enter submits outside IME composition; Enter inserts a newline. Growth follows value, placeholder, width, and font changes. Disabled requests block the native buttons and keyboard submission. The liquid text and reflection palette follows the page theme. Run keeps a translucent primary gradient and glow at rest, with a gentle lift on enabled hover or keyboard focus. Reduced motion freezes its animation. Full contracts: `next-vmax-tools/AGENTS.md`. |
| `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. `appearance="action"` opts a toolbar into one inherited line of height, uppercase theme text, square corners, subdued border-color fill, and a down caret, with no field hairlines or vertical padding. It preserves the native select and its keyboard focus. |
| `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`. |
| `FormBayerSlider` | The dithered slider ported from the `www-set` `OrbSlider`: an uppercase label and an editable number over a track whose fill is a Bayer-dithered strip (`bayerSliderStripPath`, `BAYER_4X4` thresholded against a density falling across the width), a flat one-character square thumb, and end labels. Linear `minimum` to `maximum` integers, `onChange(value)`, `formatValue?` for `aria-valuetext` and default end labels, `endLabels?`, `disabled?`. Arrow keys step 1, Shift steps 10, Home and End jump. Used as the Vmax Campaigns Overview step scrubber. |
| `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 label that exceeds its three-grid-block inline slot takes the full line and places fluid controls underneath, with ordinary wrapping for labels wider than the row; 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 `--tools-settings-width` of 24 grid blocks (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. Optional `labelHint` / `labelHintDiagram` / `labelHintLabel` seat a `FormHint` after the label, the way `FormTextArea` does; the campaign trace's Setter / Solver control carries the trials hint through them. `headingLabel` renders the label in `FormSubHeading`'s typography (`font-size-small`, `line-height-small`, 600, no uppercase, full opacity) instead of the muted uppercase field label, with a `--theme-grid-block-applications` gap beneath it (`.labelRowHeading`) since the heading's tighter line-height no longer clears the segments on its own, for a control that heads a section rather than a field; the trace's `Trajectory` control sets it. |
| `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`. `FormDisclosure.stats` renders this same strip directly under a fold's heading, which is where every campaign fold puts its facts. |
| `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?`, `captionAction?` (a node after the caption text, the `FormStackedBar` slot; the Atlas matrix seats its hint there). 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 the Atlas are reference callers; drop it into any second-sidebar task list that can get long. |
| `FormCampaignTaskLibrary` | The results entry point for actual generated problems: a wrapping two-column task table with measured solve rates, Read task, individual INSTRUCTION.md downloads, and a named all-prompts JSONL action. Composes the shared heading, hint, table, and glass button. `campaign-training-export.ts` resolves complete solver instructions through the caller’s authenticated readers; task-summary CSV remains metadata. Owner, shared, and completed fixture results reuse this surface. |
| `FormExportActions` | The inline export control: three 16px `ButtonActionGlass` squares (`MARKDOWN` / `JSON` / `CSV`, mono uppercase labels) sized to sit beside a `FormHint` in a heading or a `FormDisclosure` `headerAction`. Takes `subject` (for the `title`), `baseName` (the file name stem), and `render(format)` returning the text; it owns the `Blob` download. The campaign trace's `Task instruction` fold and the results body's `Trial configuration` fold are the callers, with the pure writers in `campaign-task-export.ts`. The same module exports `FormDownloadAction` (`subject`, `fileName`, `mimeType?`, `render`): ONE square with the `Download` glyph and a `DOWNLOAD` label on the same `.action` / `.label` styles, for a surface with a single whole-text artifact rather than three formats. It sits on the `Campaign INSTRUCTION.md` fold (`INSTRUCTION-M-D-YYYY-HH-mm.md`, generated at click time through `campaignInstructionFileName`), every file fold beside its copy button (the file, by its own extension), the `Campaign agent activity` folds (`campaign-activity.txt`, through `campaignActivityExportText`), and a truncated trace step (`step-{n}-{label}.txt`). Its `fileName` can be a string or a function resolved only on click, so a timestamp uses the actual download time. Reach for it wherever the page holds a complete text the reader may want as a file. |
| `FormHint` | The hint ([`/glossary#hint`](/glossary#hint)): a 16px glass square with a mono `?` that opens the product's one tooltip (`next-vmax/Tooltip`, restyled to prose on the client-apps font at one step down the type scale, 360px max width) on hover or keyboard focus. `text`, `diagram?` (a `FormHintDiagram` figure drawn above the text, which also widens the bubble to `min(520px, 100vw - 2 grid blocks)`), `table?` (a `FormHintTable` reference drawn below the text; widens the bubble the same way), `label?` (the accessible name), `position?`. Blank lines inside `text` become paragraphs. Optional `children` replace the glyph with a custom trigger while retaining the same card and caller dimensions. Settings labels remain plain text; their field cards are available on `/help`. The default glyph sits inline after a heading or band title; its click is swallowed so it is safe inside a `FormDisclosure` summary, and a `:has` rule centres it on the text's line box. |
| `FormHintAvatar` | The hint avatar ([`/glossary#hint-avatar`](/glossary#hint-avatar)): four grid blocks wide, a five-block `stage` (a 2D canvas painted by the shared WebGL portrait stage in `hint-avatar-stage.ts`: the shared raven rig with layered black feathers, subtle dithered fluid, occasional fire blazes, fireball eyes, curved gold-yellow mandibles, and a tilted woven straw wizard hat with a black Vmax mark; geometry and material contracts live in `three-isometric-engine/world-meshes/AGENTS.md`; it lifts its near wing while idle and opens its beak to the voice while speaking) over a one-block `controls` row holding a full-width `READ` `GlassButton`, on the card's hairline edges and shadow, sitting outside the bubble one `--theme-grid-block` to its leading side with tops aligned (through `Tooltip`'s `tooltipAside` slot, which holds the bubble open while the pointer is over the box). Takes `text`, the hint to read; pressing `READ` fetches `/api/speak` (ElevenLabs, server-held key) once per text and plays it, `STOP` while playing. `FormHint` and `FormHintPreview` both render it. |
| `FormHintDiagram` | The tooltip figure: a dependency-free SVG schematic on the kit's tokens. `nodes` (`{ id, label, detail?, column, tone? }`) and `edges` (`{ from, to, label?, tone?, loop? }`); the layout is column-major and automatic (columns centre against the tallest one), an edge routes straight, orthogonal, vertical, or through a lane under the diagram when it runs backward or declares `loop`, and `tone` is `neutral`/`accent`/`good`/`bad`/`muted` mapped onto the theme's border, focus, and diff tokens. Renders synchronously with no dependency, which is why a tooltip uses this and NOT `MermaidGraph` (async, large, and it would resize a bubble already placed under the cursor). |
| `FormHintTable` | Optional tabular reference for a helptip, accepting rows of setting, meaning, optional value, and emphasis. Campaign, Setter, and Solver section help uses compact overviews with no table; their individual concepts are taught by the cards in `campaign-setting-hints.tsx` on `/help`. |
| `FormCard` | The card shell four surfaces share: the `--theme-background` fill with four gradient-hairline edges and the elevation shadow, a head of mono uppercase `eyebrow` (`eyebrowTone: 'delete'` for a fault) + 600 `title` + optional `headerAction`, a full-width divider, then a padded content well. `role` picks `alert` / `dialog`; `fluid` swaps the 488px modal cap for `--tools-body-measure` and trims the bottom pad, which is what makes an INLINE card out of a modal one; `eyebrowTone: 'insert'` and `tone: 'insert'` are the success state, the eyebrow in the diff-insert green and the whole card ringed and glowing in it. `FormCampaignErrorModal`, `FormConfirmModal`, `FormCampaignFailureNotice`, and `FormCampaignCompletionNotice` render it. |
| `FormCampaignTaskAtlas` | The Atlas body ([`/help#all-tasks`](/help#all-tasks)): a counted heading, a List/Atlas `FormSegmentedControl`, a `FormTaskSearch` filter over every campaign's tasks, then either a sortable `FormTable` of one row per task or a `FormMatrix` of task counts by campaign and setter iteration over one clickable, solve-rate-shaded cell per task. `groups` (`CampaignAtlasGroup[]`, each with a `pending`/`failed`/`loaded` state), `view`, `onChangeView`, `onOpenTask`, `onOpenCampaign`. |
| `FormStrip` | The option row (the reynhome `Strip` on the kit surface): a hairline-edged translucent row with a bold label and muted `description` on the left and an `action` glass button (`{ label, onSelect \| href, tone?, active? }`) or a caller `control` on the right; `onClick` alone makes the whole row a button, `fluid` lifts the measure. Adjacent strips pull flush into one list. It is how a surface says "here is what you can do next": the campaign error modal's options and the failure notice's Fork / New / Read-the-log rows are the reference callers. |

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';
import FormLiquidButton from '@nextjs-vmax-tools/FormLiquidButton';

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

## 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`:

- **Toggle Theme** — swaps `theme-light` / `theme-dark` via `Utilities.setTheme`, reading the body class so it toggles in whichever direction the page is currently in. It is one label string in `ApplicationNavigation.tsx` and `ToolbarControlsFixed.tsx` and the two must stay equal; it is also the widest item in either bar, so it sets the fixed pill's `max-width` (see `next-vmax-tools/AGENTS.md`).
- **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).
- **Keep grain confined to its owning surface.** Published posts mount `GrainOverlay` in their route layout. Home marketing cards opt into `PanelShell.grain`, a static texture confined to each card. The tools shell, help cards, portrait stages, navigation menus, and home-post blurbs have no grain layer. Keep document grain out of shared providers; owning contracts live in `app/AGENTS.md` and `document-system-components/AGENTS.md`.
- **Clear Distractions** — toggles the leftmost sidebar (the overview rail) off so the content well takes its width, and CLOSES an open settings rail as it turns on (through the caller's own toggle, not a render gate). While active the item reads **Show everything** (`hideOverviewLabel` in `tools-controls.ts`, one source for both bars), so the lit control names the way back. The `Settings` control and its rail are otherwise independent of it: the control never leaves the bar, and pressing it while distraction-free opens the settings column with the sidebar still hidden (see `next-vmax-tools/AGENTS.md`); rendered directly after the theme item 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, 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 Clear Distractions.
- **`rightLinks`** replaces `ApplicationNavigation`'s right-hand controls with plain links or labelled `DropdownMenuTrigger` buttons. `menuItems` supplies a flat list; `menuSections` supplies the shared section hierarchy. The trigger wrapper stays `inline-flex` and centered so its label aligns with adjacent links.
- **`HOME_NAVIGATION_RIGHT_LINKS`** (`common/navigation-links.ts`) supplies Careers, Research, and Team in alphabetical order. Team uses `TEAM_NAVIGATION_SECTION`, the same alphabetized avatar roster as the logo menu. Reuse that section instead of rebuilding team entries in a caller.
- **The logo menu is `DOCUMENT_NAVIGATION_SECTIONS` through `NavigationMenuTrigger`.** Let `DocumentHeading` use its default menu to keep `vmax.ai`, `team`, and `social` consistent across pages. The shared renderer imports HelpTip's surface styling directly; session visibility is resolved when opened. Navigation contracts live in the root `AGENTS.md`.
- **`AppContainer.isPublicNavigation`** renders the public `rightLinks` bar and omits tools controls and `ToolbarControlsFixed`. A registry row can pass `rightLinks` and an optional `trailingLink`; the research page uses this composition.
- **`ApplicationNavigation.hideMenu`** hides the mark while preserving its left-hand footprint. `onSelectHeading` delegates the mark's action to `DocumentHeading.onSelect`; `trailingLink` appends a separated plain link. On the home page the mark restores the marketing panels and appears only while they are closed. Its trailing link is Profile for a viewer with a username, otherwise Sign in. The sign-in screen keeps the default logo menu and a Home trailing link.
- **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 renders `null` (the floating toolbar also drops its divider; the top nav draws a right structural divider only for an open settings panel) — for single-column pages where the controls are meaningless (the live profile `/{username}`, `/waitlist`, `/settings`, 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`.

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 two grid blocks tall even below 1024px, and its `.root` declares a FIXED `height: 48px` (plus `flex-shrink: 0`) rather than leaning on its children's `min-height`, so a label that grows overflows the bar instead of changing its height. `48px` is therefore the one hard-coded literal the shell repeats — every full-height surface under the bar is `calc(100dvh - 48px)`, `min-height` or `max-height` alike. A rule that reads plain `100dvh` is a bug: it makes the document 48px taller than the viewport and raises a phantom scrollbar (see `next-vmax-tools/AGENTS.md`, where `.campaignSidebar` did exactly that).

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: 728px; 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. Its fixed `.root` layer spans the full viewport width, so it carries `pointer-events: none` and the `.toolbar` pill re-enables `pointer-events: auto`. Those two move TOGETHER: without the root rule the invisible strip across the bottom of every tools page swallows clicks on whatever sits under it, and without the pill rule the root's `none` inherits down and kills the pill's own items. A production route drops the pill entirely through `AppContainer`'s `hideDeveloperControls` (see `next-vmax-tools/AGENTS.md`), which also withholds the `Holy` and `Debug` handlers; both items render only when their handler is present. `/campaigns`, `/settings`, `/waitlist`, and the live profile `/{username}` all pass it, so the pill is a reference-route affordance only. The `728px` cap is measured against the widest configuration the pill still carries, the `client-ui-columns` page's controls ending in `Col-1` / `Col-2` / `Settings`; it was `568px` until the theme control grew a long label (`Toggle Appearance`, since retitled `Toggle Theme`), and `.toolbar` is `overflow: hidden` with no `flex-wrap`, so an undersized cap wraps and then clips rather than scrolling. Re-measure it whenever an item is added or a label grows.

### 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`. One more step above `subHeading` stays on the client-apps font: `FormTaskIdHeading` (same module) is `subHeading` at one-and-a-half scale (`calc(var(--font-size-small) * 1.5)` on `calc(var(--line-height-small) * 1.5)`, `overflow-wrap: break-word`), the heading for a task id, the thing a viewer selects in the task links in the table of contents and then reads at the head of its trace; use it whenever a task id titles a surface, never a classed `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(248,81,73,1)` (signal red) |
| `--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`. **A segment that MEANS something takes its colour from the meaning, not from its position in the cycle.** Success is GREEN (`option-3`), failure is RED (`option-1`), and a neutral or absent state is BLUE (`option-2`); assign those first and let the remaining segments fall through the cycle. The rule is worth stating because it routinely lands out of index order, and the reason failure is pinned rather than merely avoided is that red is the one colour a reader already reads as a fault, so a failure segment painted anything else is actively misleading. The campaign `Task outcomes` bar is the reference (`MEASURED_COLOR` green / `REJECTED_COLOR` red / `UNMEASURED_COLOR` blue in `campaign-live-view.ts`), and the Atlas cells paint the same three states from the same three tokens, so one state never reads as two colours across two surfaces. Leave the plain cycle alone only where the segments carry no success or failure meaning at all.
- **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 .frame>                      position: relative; isolation: isolate
    <ApplicationNavigation/>         the 48px top bar (theme / holy / 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 ↓
  </div>
  <ToolbarControlsFixed/>            the floating bottom pill, a sibling of .frame
```

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` = `--tools-settings-width` (24 grid blocks) | 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 |
| **`campaignShell`** (`.root.campaignRoot`, the `/campaigns` application; not a `columnVariant`) | flex; `overflow-x: auto` under `1248px` | 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 `1248px`; sets `--tools-body-measure: calc(var(--theme-grid-block) * 38.4)` so its bodies widen 20%); `showSettings` splits content into `.fluidColumn` + the settings rail on `--tools-settings-width` (24 grid blocks); `columns={2}` with two children renders the default `.columns` grid inside `.fluidColumn` (`/slides`) | columns NEVER squish: at or below `1248px` 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), `1248px` (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, with a vertical `.divider` between the logo and tools controls, and another only before an open settings panel (`width: 1px`, the vertical gradient, hidden 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`. Its direct logo wrapper uses `.root .left > * { display: flex }` to remove inline descender space. The two-class specificity must outrank the dropdown and action wrappers' own display rules regardless of CSS load order, so the 32px mark stays centered in the 48px bar. The 27.2px scene buttons share its vertical center through flex alignment. `hideMenu` drops the logo but keeps the section so nothing slides; `onSelectHeading` keeps the logo and replaces its dropdown with a click action (the home page's reopen-the-panels control).
- `.stretch` — the middle control cluster, `min-width: 10%; width: 100%`, `padding: 0 12px`: the `Holy` / `Toggle Theme` / `Clear Distractions` items and `Debug`, without an internal divider. With a settings panel open, `.rootSettings .stretch` switches to `flex: 1 0 auto; min-width: 0; width: auto` so the right section can hold the settings width. `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`. An open settings panel adds `.rightSettings`, which prefers `--tools-settings-width` (24 grid blocks) with `flex: 0 1 var(--tools-settings-width)` and a `max-content` minimum, so its leading divider aligns with the panel when room permits and controls remain reachable on narrower headers.
- Every control is a `.item`: `font-size: var(--type-scale-fixed-tiny)` (12px), `letter-spacing: 0.2px`, `font-weight: 600`, `padding: 0 12px`, `white-space: nowrap`, `color: var(--theme-background-foreground)`, `cursor: pointer`, hover `opacity: 0.8`; the `.active` (selected) item lifts to `color: var(--theme-text)`. Under `768px` the item, `.right`, and `.left` paddings halve (`0 6px` / `0 6px` / `0 12px`) so a phone-width bar seats the logo plus four public links (`Research`, `Careers`, `Team`, `Sign in`) on one line; the `nowrap` is what stops a two-word link (`Sign in`) folding onto two lines when the row runs out of room, since a wrapped item is the one failure the bar must never show. 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)`. Product entries use `appIcon` for their complete 24px mark with the label alongside; the Presentations glass frame stays inside that icon slot.
- **`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`).**

- **`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, 24 grid blocks 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, the filled chevrons `FormMetric`'s deltas use, 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; flex-wrap: wrap; align-items: center; column-gap: var(--theme-grid-block-applications); row-gap: calc(var(--theme-grid-block-applications) * 0.25); min-height: var(--theme-grid-block)`, an intrinsic-width `.rowLabel` (three-grid-block inline slot, `line-height: var(--line-height-small)`, `opacity: 0.5`) + a `.controls` flex whose direct children each take `flex: 1 1 0` (so a row reads as 1–4 equal cells). The label uses `flex: 1 0 max-content`, a minimum of the three-block slot, and a maximum of 100 percent. Controls reserve `calc(100% - var(--settings-row-label-width) - var(--theme-grid-block-applications))` as their flex basis, so a longer label pushes them to the next line and both fill the row. Labels remain fully visible without clipping or JavaScript measurement. Campaign setting names are plain text with no hover, focus, or click help trigger, so changing nearby controls cannot open a card accidentally. 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` (the primitives `/campaigns` uses, then the campaign components) + `FormTemplateGraphs` (everything else), 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 the default hairline `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`), each non-fluid body cell's content in a `.cellMeasure` `inline-block` at `width: max-content; max-width: calc(var(--theme-grid-block) * 16)` (384px) with `white-space: normal; overflow-wrap: break-word`, so a value stays on one line until it passes the cap and wraps after it; 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 application anatomy (rebuild it without repo context)

The application lives at `/campaigns`. `/-/vmax-tools-template/campaign` is the SAME shell driven by fixtures (`FormCampaignTemplate`), with every state a row in its history rail, and the bare `/-/vmax-tools-template` mounts each campaign component on the same fixtures as a titled section (`FormTemplateCampaign`). Their prompts are demo material rather than something to hand a customer to fork, so the live controller never lists them; the table below names the fixture each state was built against.

The campaign application is the worked proof that the kit hosts a LIVE application: four page states on the `campaignShell` chrome, each reachable from the checked-in example fixtures with no API and no polling, and the same four driven by the Campaign HTTP API for a viewer's own campaigns. 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, content frame, 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); sticky top: 48px wide, 0 under 1248px;
                                   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 contents breathe
  <div .content>                   width: 100%; min-width: 10% (568px floor under 1248px);
                                   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 (--tools-settings-width, {settingsColumn})
                 : {children} fill the well alone
```

**The campaign columns never squish.** At or below `max-width: 1248px` `.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` and `top: 48px` so the rails stick beneath the bar against the page scroll. At or below the breakpoint the horizontal scroll container already begins below the bar, so both `.campaignSidebar` rails use `top: 0` to avoid a duplicated header offset. History rows keep their labels and Fork on phones via `SidebarItem preserveMobileLabel` (see the sidebar anatomy above).

When Campaign Settings is visible at or below 1248px, the prompt-bearing `.fluidColumn` has `flex: 1 0 768px` and `min-width: 768px`. This floor belongs to the column beside the settings rail, not their shared `.content` wrapper. The inner `.clientUiColumns` keeps visible overflow so the Campaign root owns the horizontal scroll, including below the shared 880px breakpoint. The `.clientColumnsSolo` state stays fluid when Settings closes.

With `onToggleSettings` set, ONE `Settings` item replaces `Col-1`/`Col-2` in both control bars (active while `settingsActive`); the centre control group has no internal divider. `AppContainer` passes the actual panel visibility as `ApplicationNavigation.settingsPanelOpen`, independently of the active-control token, so only an open panel receives the right divider and the shared `--tools-settings-width` geometry.

**The four states.** One controller (`FormCampaignApplication`) 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 (kept while present, cleared by a `useEffect` on the memoized task list, never auto-picked: nothing is selected until a card is clicked, so a mounting campaign shows no trace and does not scroll), 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 |
| Atlas (cross-campaign) | `?view=tasks` | `FormCampaignSummary` retitled `Atlas Summary` | `FormCard` | The card shell four surfaces share: the `--theme-background` fill with four gradient-hairline edges and the elevation shadow, a head of mono uppercase `eyebrow` (`eyebrowTone: 'delete'` for a fault) + 600 `title` + optional `headerAction`, a full-width divider, then a padded content well. `role` picks `alert` / `dialog`; `fluid` swaps the 488px modal cap for `--tools-body-measure` and trims the bottom pad, which is what makes an INLINE card out of a modal one; `eyebrowTone: 'insert'` and `tone: 'insert'` are the success state, the eyebrow in the diff-insert green and the whole card ringed and glowing in it. `FormCampaignErrorModal`, `FormConfirmModal`, `FormCampaignFailureNotice`, and `FormCampaignCompletionNotice` render it. |
| `FormCampaignTaskAtlas` | none |
| Env-creation build | `fixture-running` (`FormCampaignBuildConsole` + `FormCampaignActivityLog` sections) | Summary + Contents (one smoke task) | `FormCampaignBuildConsole` | hidden by default |
| Mid-generation build | `fixture-generating` (`FormCampaignSummary` second example) | Summary + Contents (accepted tasks + one running task) | `FormCampaignBuildConsole` (no files section) | hidden by default |
| Completed results | `fixture-completed` (`FormCampaignResults`, `FormCampaignTableOfContents`, `FormCampaignTaskTrace`, `FormCampaignLaunchRail` sections) | Summary + Contents | `FormCampaignResults` | hidden by default |
| Failed build | `fixture-failed` (`FormCampaignFailureNotice` section) | Summary + Contents (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; on the live application it is `disabled` and reads `Stopping campaign` while the request is in flight AND while the record's status is `stopping`) 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 row first: a `Sidebar` holding one `active` `SidebarItem` whose `appIcon` is the ember `VmaxAppIcon` (`SPELL_GRID_APP_ICON`, the same mark the tools rail's `Campaign` product header carries) labelled `Start a new campaign` (`CAMPAIGN_START_LABEL`, `onClick`, `preserveMobileLabel`), so the top-left of `/campaigns` reads as one system with the rest of the product's rails; it replaced a padded `.create` block holding 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` = `{phase or status} · {progress} · {updated}` (`campaignPhaseLabel`: the API's raw `queued` / `running` / `stopping` while the folded status is `building`, the folded status otherwise, so a fixture row still reads `building`; 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 the goal stays readable at phone widths), with no trailing action: forking starts from the completion and failure notices and from an explorer's Fork from here. An optional `capLabel` (the live app's `Showing the 50 most recent campaigns.` at the API's list cap) renders under the owned rows in the same `.empty` treatment as the no-campaigns note.

**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; on the live application a `queued` record has no `started_at`, so `liveCampaignMetrics` sends `Elapsed` as a loader until the status is `running`, and the clock then counts from `started_at`; an active live campaign also carries a `Worker` row after `Elapsed`, a loader until `worker_campaign_version` is set and the version after, dropped once terminal); 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.
- **Task navigation (`FormCampaignTableOfContents`)** belongs under Generated tasks and downloads inside the contents hierarchy. Full task IDs use the ordinary sidebar text style and wrap when needed; each keeps Task instruction, Trajectory, Stored evidence, and Task files expanded underneath. Selecting an ID or child link delegates to the controller, which restores the document, selects the task, and focuses the requested section after commit. The list has no search field or card logs. Application buttons carry the shared `VmaxFlag` in the same positioned 16px glass-icon box, and the final contents root owns the four-grid-block bottom gutter.

**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). Both bodies open with an optional `Campaign INSTRUCTION.md` fold (`FormCampaignPrompt`: a `defaultOpen` `FormDisclosure` with `CAMPAIGN_PROMPT_HINT` in its `headerAction`, the character count as a `characters` tile in the strip above it, the prompt as `FormParagraph`s split on blank lines) when the live controller passes `prompt`; the fixtures pass none. Both also carry an optional `Campaign trials` section (`FormCampaignTrials`: `FormSubHeading` + hint, a `TooltipCard` with the READ ALL portrait, and the trace's `TraceStack` of rollouts) for campaign preparation trials, when the controller passes a non-empty `trials`, with each trial independently anchored in the contents rail; after the activity block in the build body, after the trace in the results body. The build body then stacks the activity disclosure (its action count in the `FormMetric` strip above it), the implementation disclosure (its file count in the strip above it; prompt paragraph + a NESTED per-file `FormDisclosure` list — the exported `CampaignFileDisclosure`: a short-path label, its byte size as a `size` tile in the strip above it, 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. When any row carries a clock (`CampaignAction.at`, the live app's UTC `HH:MM:SS` from the event's `created_at`; fixtures carry none) every row takes `.activityLineTimed`, a third trailing column `calc(var(--theme-grid-block) * 3)` holding a `.time` cell (right-aligned, `opacity: 0.5`, `white-space: nowrap`), empty on a row without one. 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 (on the live app: a `live` stream with `statusText` `running` or `stopping`, since a stopping worker is still writing rows; never `queued`), the LAST row carries a `ButtonLoader` (`var(--theme-grid-block-applications)` square) before its tag.

**Task trace (`FormCampaignTaskTrace`).** An ordinary content section — a `FormTaskIdHeading` (the typography kit's task-id heading: `.subHeading` at one-and-a-half scale, `calc(var(--font-size-small) * 1.5)` on `calc(var(--line-height-small) * 1.5)`, `overflow-wrap: break-word`) carrying the selected task name with its stable id beneath it + a `defaultOpen` `Task instruction` fold ([`/help#task-instruction`](/help#task-instruction); above the tabs so every role sees it; it states why it is empty when the API does not carry one) + an optional muted `logNotice` paragraph (`.logNotice`: `margin-top: var(--theme-grid-block)`, `opacity: 0.6`, a `FormParagraph` inside; rendered only when the live controller reports that its retained-log cap dropped rows, never on the fixtures) (the section scrolls itself into view when a task ID or subsection is selected, through the controller's `focusToken` and `focusAnchor`, never on an automatic selection) + a `FormSegmentedControl` labelled `Trajectory` in the heading typography (`headingLabel`) toggling Setter, Validator, Solver, and Judge when present (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` (its file count in the `FormMetric` strip above it) 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) — a `.stepHead` flex row (`justify-content: space-between`, baseline-aligned, `gap: var(--theme-grid-block-applications)`) holding 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}`) and, only when the step carries `at` (the live app's UTC `HH:MM:SS` from the first log of the step; fixtures carry none), a right-aligned `.stepAside` holding a `FormDownloadAction` (the glass `DOWNLOAD` square, only when the step's detail was cut and the full text rides on `step.source`) and the `.stepTime` at the same mono face, size, and opacity, over a detail line (`font-size-small` / `line-height-small`, `opacity: 0.85`).

**Composer (`FormCampaignComposer`).** A centred column without an island, capped at `--tools-body-measure`, with the shared heading and `FormTextArea appearance="liquid"`. The prompt field has a white glass body in light mode and graphite in dark mode, two theme-adjusted silver reflections and an animated halo from `FormLiquidSurface`, with its label and prompt hint on the left of the inset footer and Generate / Refine / Import / Download / Run campaign SVG glass buttons on the right. Run points right and keeps the theme primary fill and glow at rest, with the existing glass-button gradient fading from transparent through subdued primary to transparent. Enabled hover or keyboard focus gently lifts its tint. It carries the native accessible name and pending state and repeats with the toolbar above prompts past 400 words. The `markdown` field edits source directly through `FormMarkdownInput`, retains natural growth and Cmd/Ctrl+Enter, and supplies `onValueChange` with plain Markdown. Paragraphs stay at full opacity; H1 through H6 step from 0.9 to 0.4, with bold and italic styling and their Cmd/Ctrl shortcuts. The empty placeholder and caret share a grid cell inside the same padded text region; Slate default styles are disabled so placeholder measurement cannot shrink the CSS minimum height. The native textarea branch retains its autofill protection for ordinary fields. `showFiles` controls the fixture attachment list; live controllers pass `false`. Capacity metrics and validation remain below the field. The live and fixture controllers reuse this component; the primitive gallery opts into the same liquid appearance. Styling, shader lifecycle, and accessibility contracts are in `next-vmax-tools/AGENTS.md`.

**Settings rail (`FormCampaignLaunchRail`).** Campaign and Campaign secrets occupy the left column; Solver and then Setter stack in the right column, grouping the two agent configurations. Their section hints are concise overviews without tables. Each setting name is plain text; individual field cards remain on `/help`, and explicit section `?` buttons open the overview cards. 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` (a three-block inline label slot, stacking longer labels above full-width equal-flex control cells) — over `FormSelectMini` / `FormFieldMini` + `FormSlider bare` pairs, every section open. Rebuild it from the `SettingsColumn` anatomy above with campaign fields (Campaign / Setter / Solver) instead of computers. It is a CONTROLLED rail over the real `CampaignCreateRequest`, and that is the rule: a settings rail holds no `useState` per field over invented options, because one that does not name the request the backend accepts reads as configuration and is fiction. Pass `disabled` to show a request read-only. `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).

Campaign secrets use the same `Section` / `Row` kit: `Row.labelFor` links labels to `FormFieldMini`, role permissions use individual `FormSelectMini` rows, and actions use `FormActionButton`. The revealed multiline value shares the mini-field frame through `FormFieldSurface`; preserve the raw value across masking and paste. Do not create a parallel form style for a new settings group. The secret lifecycle and rendering contracts stay in `next-vmax-tools/AGENTS.md`.

## Conventions

- **[Campaign Applications](/help#campaign-applications) use the existing third column.** `FormCampaignApplications` sits after Campaign Summary and before the contents, presenting name-only rows with glass flag buttons; primary state belongs to the square. `useCampaignApplication` selects the body through `campaignShell.application`, preserving the hidden standard document and its CLI state. Each explorer is independent, with no mutual switcher or header return control. Nodes open `CampaignAppInspection` through the shared modal system with a 920px width preference. An inspection is limited to that node's fully expanded data, with no tabs or navigation to other nodes. A modified task instruction uses one full-width `FormTextCompareSideBySide`: original campaign Markdown on the left and resulting task Markdown on the right. Keep Campaign Rollout File Explorer as an independent application with its own name-only sidebar entry and glass flag button. Every graph node opens an inspection modal over the active explorer, including file nodes. Its 14px workspace fills the third column, with campaign/task/trial scope controls, a file tree, and baseline/selected content using the shared side-by-side diff. File-node modals reuse the capture-only file view and the existing authenticated reader. View task files and View trial files reveal the shared browser inside the same modal, initially focused on the task instruction or a file from the inspected trial, preferring exact paths recorded by the selected steps. Set file pins the current capture on the left, browsing changes the right, and Unset file restores a single-source view. There is no automatic parent comparison. A set Base file and a complete selected text file always use the shared highlighted diff, including large retained files. The shared differ uses linear search space instead of a full comparison matrix; incomplete or binary captures keep their explicit source status. Outcome Takeaway is a read-only textarea containing the retained final response. Node actions never switch Campaign Applications, and closing the modal preserves the explorer canvas and selection. Preserve source identities and incomplete-inventory notices. Enable wrapping and keep additions on the right, removals on the left; do not repeat the task source beside a combined diff. Both surfaces follow the site's light/dark background, text, border, graph, and primary tokens and use Iosevka at 16px on 20px lines, with shared `FormTextCompare` content one scale smaller at 14px. Text diffs retain the shared addition/removal markers and before/after line numbers; identical sources use one highlighted source view. Use `FormCode` with `appearance="source"` for complete highlighted commands and output without gutters; source text and exports remain intact. Use `FormMarkdownSource` for retained Markdown, sharing the campaign prompt editor’s decorations while keeping markers and line breaks. JSON display formatting must preserve numeric lexemes, duplicate keys, and string escapes. Use `FormTextCompare` with captured rows for patch hunks, preserving their actual line numbers instead of reconstructing missing file contents. The explorer also uses the same component to preview proposed-step changes. Retain the spatial node geometry and scrolling, put each node trait on its own line, and emphasize recorded rewards through color rather than larger type. Use the shared node CSS for a subdued version of the campaign prompt’s rounded liquid frame: gradient rim, inset contour, and soft neutral halo. Reward nodes tint the rim and glow through `--explorer-reward`; the treatment adds no per-node shader or animation loop and adapts to both themes. The reward banner belongs only to an outcome-node inspection; a trial carrying a stored reward must not enable it on a Setter or Solver inspection. Task nodes use stable task IDs, counts stay in the upper toolbar, and Every Step / Group Steps shares the bottom area with zoom as a separate button group. Toolbar selects use `FormSelect appearance="action"` to match the link buttons. Explorer bars and their controls have only horizontal padding at every breakpoint, with no vertical row gaps. Chrome labels are uppercase. Bars, file-tree rows, comparison panes, and notices share one 12px horizontal gutter; the tree heading has no vertical padding and folder/file rows use 2px above and below their contents. Keep filename and retained-source casing intact. File capture labels read `Sample 1, Solver <trial ID>`, and generated file-app labels use no dot separators. Both scrollbar axes are thin within these applications. [VIEW IN CAMPAIGN] returns to the matching source anchor. All inspection actions share its bracketed uppercase theme text and subdued border-color background. Working surfaces show evidence and actions; explanation belongs in help. The owning contracts are in `next-vmax-tools/AGENTS.md`, Campaign Applications.

- **CLI concept links use `FormCampaignConceptActions`.** Keep the `[CONCEPT: ACTION]` prefix and primary background at rest, hover, and focus. Place small groups around task collections, selected task results, rollouts, and taxonomy. Omit concepts that duplicate existing product or API support, including campaign summaries, fork/refine, pack revision, and pack reuse. Keep settings, prompts, file rows, and logs clear. At substantive trajectory steps, `FormCampaignStepConcepts` uses the same primary-filled `[CONCEPT: EXPLORE STEP]` tag and reveals two examples only when selected; `CampaignStepConceptScope` keeps one selection per trace. `CAMPAIGN_CLI_CONCEPTS` owns labels and short hypothetical explanations; the renderer returns nothing in standard mode. `FormCampaignConceptModal` uses the shared tooltip surface without a bird, narration, technical solution, or export controls. All modal text shares one 16px size, including token allowances and context notes. Describe what an agent could help discover or propose. Step examples may show an illustrative token budget with its assumed context and input/reasoning-and-reply breakdown; never present it as measured usage or include test execution in that allowance. Keep activation local, preserve existing handlers, and close through `useCloseSelf`.

- **Campaign's [CLI view](/glossary#cli-view) is the scoped presentation exception.** `campaignShell.resultsAvailable` admits Campaign Terminal View through Campaign Applications. The toolbar has no terminal icon. `campaignShell.terminalView` selects the existing mounted document presentation, and `AppContainer` provides `CampaignCliContext` only around the content well. Standard view, or selecting Campaign Terminal View again, returns to the regular document, and opening any other app turns the terminal off. `CampaignAppTerminal` places its document inside the common `CampaignApp` frame. The toolbar and footer share the explorers' 16px type, 12px gutters, and locally served Iosevka face. The terminal work area has zero padding and fills the frame edge to edge. The document retains 14px text on 18px lines, its black/white palette, two-character hierarchy, and bracketed native actions; its scoped reset cannot reach the frame. Standard view sits in the footer, and the work area scrolls independently. Loaded sections and text start expanded; the selected task's roles render sequentially. Every section uses an independent native `[+]` / `[-]` disclosure, with two-character nesting through `FormCampaignSection` and `FormDisclosure`. The leading `Collapse all` and `Expand all` actions issue fresh commands through `CampaignCliExpansionContext`, keeping loaded children mounted and initiating no fetch; individual branches remain adjustable after either command. Links and actions keep their filled backgrounds at rest and on hover. Mark actions that can fetch additional data through `requestsData` so they receive the inherited primary fill, while local actions stay monochrome. Keep navigation at its standard stacking level and reset themed card shadows explicitly. Extend the shared components through `useCampaignCli` and the documented `data-cli` hooks; keep handlers, data, disabled states, and ordinary rendering shared. Scope `FormDisclosure.plain` resets to direct children so nested standard disclosures retain their padded heading bars and action alignment. The deepest contract is `next-vmax-tools/AGENTS.md`, CLI results presentation. The font, grid, and glass rules below describe standard presentation; never apply the terminal reset to the whole shell or to a modal portal.

- **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 one non-registry route that existed (the mulberry campaign console) drifted exactly that way and was folded into `/campaigns` as an example feed; prefer that shape, an extra data feed inside the real controller, over a second route with a second controller. The shared sidebar (`SHARED_SIDEBAR_GROUPS`) lives 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 former `/-/vmax-tools-template/campaign-template/mulberry` and `.../examples` precedents (both since deleted; every campaign path now redirects to `/campaigns`): 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`.

## Terms and help

- `FormCampaignTrials` is the reference for narration beside an inline help card. Compose `TooltipCard` and `FormHintAvatar readAllOnly` with the caller's `readAll` control and `readerRef`. Use `campaignTrialSpeech.entryCount` for the contents count of reasoning entries plus agent messages, and keep their word offsets in that same pure builder and render through `SpokenText follow`. Snapshot a live feed when playback starts, cancel pending requests and audio on user interaction, and never treat the narrator's own scroll as input. The speech lifecycle contracts live in `next-vmax-tools/AGENTS.md`.

- **Do not define terms or explain surfaces in this skill.** Meanings live in [`/glossary`](https://vmax.ai/glossary) (`demiurge/public/www/common/glossary.ts`, `GLOSSARY_TERMS`) and surface explanations live in [`/help`](https://vmax.ai/help) (`next-vmax-tools/campaign-hints.tsx`, `CAMPAIGN_HINTS`). Link `/glossary#{slug}` or `/help#{surface}` rather than restating; that is what keeps this file short.
- **Introducing a new term or surface means updating those first**: add the term to `GLOSSARY_TERMS` (one word, one meaning; `common/glossary.test.ts` pins it), add a `CampaignHint` to `CAMPAIGN_HINTS` if the surface needs a "what is this", then reference them here by link. See the root `AGENTS.md`, "Terms are defined once, in `/glossary`".
