---
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 frame stays identical; only the body changes.

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

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

The routes read the `TOOLS_PAGES` registry in `tools-pages.tsx`, which also holds the sidebar's `EXAMPLE_TEMPLATE_SECTIONS` and `MAIN_APP_LINKS`.

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/island-scenes.ts` holds one row per scene. A row **is** the route and its metadata:

```tsx
export interface ToolsPage {
  label: string;              // route metadata title
  description: string;        // page metadata (title + OpenGraph + Twitter)
  icon: React.ReactNode;      // page metadata (a next-vmax/Icon glyph); not drawn while the template is one page
  sections: ToolsPageSection[]; // page metadata; not drawn while the template is one page
  columns: React.ReactNode[]; // one node per available content column
}
```

- The template is a single page today, so the sidebar (`AppContainer`) is **not** a page switcher. It holds two fixed groups from `tools-pages.tsx`: a "Template" Item whose SubItems (`EXAMPLE_TEMPLATE_SECTIONS`) anchor the default page's headings by id — `FormTemplate` in the first column, `FormTemplateGraphs` in the second; sibling examples share one grouped section ("Comparisons", "Elements", "Tables", "Graphs") to keep the list short, and the list is alphabetized by label — and `MAIN_APP_LINKS` — glass-square links out to the authenticated main-app routes. Each Item's icon is the app's glass square button (`ButtonActionGlass`); its plain-square SubItems read a level apart, and each SubItem is a link (`sectionAnchorHref`) that carries the current query string forward: it jumps to its heading by `id`, forcing `?columns=2` first for a section marked `columns: 2` (a second-column `FormTemplateGraphs` heading) so it is rendered before the scroll.
- The bare `/-/vmax-tools-template` route serves `DEFAULT_TOOLS_PAGE_KEY`; the `[tool]` 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.

## 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` renders any registered key 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.

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

| Component | What it renders |
|---|---|
| `FormTemplate` | The template settings form — `GlassButton` actions (its "Open test modal" opens `FormModal` through the modal system) and inline examples of every primitive below, grouped so sibling examples share one section and one sidebar row: "Comparisons" (both text diffs over a paraphrase plus the code edit), "Elements" (the `FormInput` demo, the `Checkbox` sharing toggles, the `FormList` pair, and the "Text styles" reference), "Tables" (the three `FormTable` variants, the LoRA-adapter card, and the grouped main-results table), then the UNIX-CTF + LoRA sections (a `FormMetric` training-signal strip, a `FormEquation` pair — the frontier band and a `$$`-wrapped cases reward, a `FormRatioBar` parameter-efficiency meter plus a harvest-yield funnel, a `FormPipeline` pair — the harvest stages and a looped self-play step, a `FormPreferencePair`). The default page's first column. |
| `FormModal` | A dismissable modal modelled on `next-vmax/ModalAuthentication` (gradient-edged brand-over-content panel) but on the AppContainer client-app font: a small VMAX logo, a gradient divider, body copy, and a "Close this Modal" `GlassButton`. Opened via `useModals().open(FormModal, {})` (`@components/ModalContext`); the root layout's `Providers` mounts the `ModalRenderer`. |
| `FormTemplateGraphs` | A `WebTerminalWindow` section (the full `Loader` set) with the `FormTranscript` agent episode directly beneath it, a `FormTokenHeatmap` token-credit run, a `FormMatrix` capability grid, a `FormRollout` world-model rollout and a `FormSparkline` training-dynamics pair (UNIX-CTF / world-model themed), then one "Graphs" section holding every chart: the `FormDistributionCompare` grouped bars, the `FormGraphCompare` before/after pair, and the rest of the document-system graph catalogue over frontier-model evaluation data, each catalogue entry titled at the caption weight below the one anchored heading (the Line entry is the multi-series form — two self-play solve-rate curves with confidence bands). The `FormInput` demo lives in `FormTemplate`'s "Elements" section. The default page's second column (`?columns=2`). Exports `MODEL_GRAPHS`. |
| `FormTable` | Data table capped at 768px, scrolling sideways once a `fluid` column pushes it wider. `columns` (`{ key, label, fluid?, align? }`), `rows` OR `groups` (`{ label, rows }[]` — labelled row batches behind full-width header rows, the paper results-table shape; put ↑/↓ in column labels and `<strong>` on best values), `caption?`, `variant` (`gradient` \| `ruled` \| `striped`). |
| `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`. |
| `FormTextCompareSideBySide` | The split-view sibling — before left, after right, row-for-row aligned with hatched fillers. Reuses `diffLines`; exports `pairDiffRows`. |
| `FormJSONExplorer` | Collapsible JSON tree in the same measured code-diff rows, a `+`/`-` toggle per object/array. `data`, `caption?`. Exports the pure `flattenJson`. |
| `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. |
| `FormGraphCompare` / `FormGraphCompareSideBySide` | Before/after chart diffs (stacked / split). `before`, `after`, `graphIdBase`, `caption?`. `colorizeByChange` recolors the after chart against the before. |
| `FormMetric` | A stat strip for RL headline numbers: `metrics` (`{ label, value, delta?, deltaSuffix?, goodDirection? }[]`), `caption?`. Coloured `▲`/`▼` delta via the diff tokens; `goodDirection: 'down'` makes a fall in loss/KL/refusals read green. Exports the pure `deltaTone`. |
| `FormPreferencePair` | The RLHF chosen-vs-rejected split: `prompt`, `chosen`/`rejected` (`{ text, reward? }`), `caption?`. Insert-green and delete-red panes with the reward margin beneath. Exports the pure `preferenceMargin`. |
| `FormDistributionCompare` | Student-teacher distillation: `tokens`, `teacherLogits`, `studentLogits`, `temperature?`, `graphId?`, `caption?`. Softmaxes both logit sets and draws a grouped bar chart via `GraphBlockWrapper`, with the KL and top-1 agreement beneath. Exports the pure `softmax` and `klDivergence`. |
| `FormTokenHeatmap` | Per-token credit: `tokens` (`{ token, value }[]`), `min?`/`max?`, `caption?`. An inline token run washed by its scalar through a diverging red↔green scale. Exports the pure `signedIntensity` / `scalarRange`. |
| `FormRollout` | A world-model / trajectory strip: `steps` (`{ label, value? }[]`), `real?` (a second trajectory → imagined-vs-real two-row grid), `min?`/`max?`, `caption?`. Timestep cells (`t0 › t1 › …`) tinted by the per-step scalar through the same diverging scale, scrolling sideways when long. Reuses `FormTokenHeatmap`'s `signedIntensity`. |
| `FormTranscript` | An agent episode as logs inside a `WebTerminalWindow`: `turns` (`{ role: system\|user\|assistant\|tool, text, timestamp, name? }[]` — `name` overrides the displayed alias while keeping the role's tone, so a dialogue can name its speakers), `title`, `caption?`. Each line is three columns: a coloured name alias, a fluid message, and a fixed-width timestamp, so the container stays measured. The log body caps at 88 `--theme-grid-block-applications` and scrolls vertically past it on the shared scrollbar. |
| `FormMatrix` | A labelled grid (models × benchmarks, predicted × actual) with cells tinted by value: `columns`, `rows` (`{ label, values[] }`), `diverging?` (signed red↔green vs sequential green), `min?`/`max?`, `format?`, `caption?`. 768px cap, sideways scroll. Reuses `signedIntensity`/`scalarRange`. |
| `FormRatioBar` | A stack of proportion meters: `ratios` (`{ label, value, max?, display? }[]`), `caption?`. One labelled bar per ratio (a `value/max` fill with a 1px floor), for LoRA trainable-%, KL budget, context used. |
| `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?`, `caption?`. 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. |

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`). A common composition is a `WebTerminalWindow` over a `Loader`, with a `FormInput attached` docked flush beneath it.

## Open a modal from a page

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

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

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

## The shell (`AppContainer`)

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

- **Light** — swaps `theme-light` / `theme-dark` via `Utilities.setTheme`.
- **Holy** — toggles the Mekzantine display font via `Utilities.toggleFontClassName('font-use-mekzantine')`, scoped to the shell so it never leaks. `?holy=true` on the URL opens the page with it already on (applied once on mount, so a manual toggle-off still sticks).
- **Grain** — a `GrainOverlay` hosted on a wrapper that isolates it over the nav, sidebar, and content but never the fixed toolbar.
- **1 Column / 2 Column** — sets `?columns=N` on the router.

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

## Conventions

- **Route files are server components.** No `'use client'` on `page.tsx`; export `dynamic = 'force-dynamic'` and `generateMetadata` (route metadata comes from `buildToolsPageMetadata`).
- **Bodies are client components.** `'use client'` at the top, paired with a `.module.css`. Match the `root` / `heading` / `subHeading` / `paragraph` / `divider` typographic scale the existing bodies share (small font, `--theme-grid-block` line height, 768px max width).
- **Grid-snap and theme tokens only.** Size off `--theme-grid-block` / `--theme-grid-block-applications`; reference `--theme-*` custom properties in CSS, never inline theme values.
- **Client-app font.** The shell sets `font-family: var(--theme-font-family-client-apps)` (the system stack); keep bodies on it so they match `AppContainer` and `ModalAuthentication`.
- **Brand casing.** `Vmax` in prose and PascalCase identifiers; `VMAX` only for the wordmark/product/domain and `SCREAMING_SNAKE_CASE` constants.
- **Comment convention.** `// NOTE(@your-github-handle):` — space after `//`, `@` before the handle, colon after the paren, body on the next line. Comment the WHY; if a name or the JSX already shows it, write no comment.

## Rules

- **One registry.** Add pages to `TOOLS_PAGES`; do not hand-wire a route. The `[tool]` route derives from the registry, so a one-off route drifts from it. The sidebar's Template and main-app groups live in the same file (`EXAMPLE_TEMPLATE_SECTIONS`, `MAIN_APP_LINKS`), not a per-page nav.
- **Every new body pairs a `.module.css`.** No component in `next-vmax-tools/` without its module.
- **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`.
