> For the complete documentation index, see [llms.txt](https://beamline.gitbook.io/beamline-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://beamline.gitbook.io/beamline-docs/reference/conventions.md).

# Conventions

The one API every Beamline component follows: files and imports, one props vocabulary (variant, size, tone, value), the rendering contract, styling.

> The original of this page is on beamline.io: [beamline.io/docs/conventions](https://beamline.io/docs/conventions).

> Beamline code and your custom style files stay in your repo; your app has no Beamline runtime dependency. This documentation ships with the complete system (docs/CONVENTIONS.md in the kit). Use [local kit tools](https://beamline.io/docs/install) or [connect an agent](https://beamline.io/connect) to install parts.

One API shape for every component, so an agent (or a person) that has used one can use any other.

## Files and imports

```
ui/components/<id>/
  <id>.tsx          component(s): named exports, plus a default export of the main one
  <id>.module.css   optional; styles read tokens only
  demo.tsx          default export Demo[]: every variant and state, live
  meta.json         catalog entry + spec (see below)
ui/blocks/<id>/     same shape, for composed screens
```

* **One import path per component:** `import { Button } from "@/components/button/button"`. Nothing re-exports another component's parts. Internal helpers live in folders starting with `_` and are never imported by products.
* Ids are kebab-case nouns (`order-book`, `date-range-picker`). Exports are PascalCase (`OrderBook`). Parts are prefixed with the component (`DialogContent`, `SidebarMenuItem`).
* `@/lib/utils` → `cn()`; `@/hooks/use-motion-tokens` → host-local durations, eases, springs; `@/hooks/*` for shared hooks.

## Props — one vocabulary

| concern         | name                                                                                                       | values                                                                                                                                                                                               |
| --------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| visual weight   | `variant`                                                                                                  | `primary` · `secondary` · `ghost` · `outline` · `danger` (use the subset that makes sense)                                                                                                           |
| semantic colour | `tone`                                                                                                     | `neutral` · `accent` · `success` · `warning` · `danger` · `info`                                                                                                                                     |
| size            | `size`                                                                                                     | `sm` · `md` · `lg` (default `md`); icon-only buttons need `aria-label`                                                                                                                               |
| row density     | `density`                                                                                                  | `compact` · `default` · `comfortable`                                                                                                                                                                |
| value           | `value` / `defaultValue` / `onValueChange(value)`                                                          | controlled + uncontrolled, always both                                                                                                                                                               |
| open state      | `open` / `defaultOpen` / `onOpenChange(open)`                                                              | same                                                                                                                                                                                                 |
| selection       | `selected` / `defaultSelected` / `onSelectedChange`                                                        | same                                                                                                                                                                                                 |
| busy            | `loading`                                                                                                  | keeps size and focus, sets `aria-busy`                                                                                                                                                               |
| disabled        | `disabled`                                                                                                 | native semantics where possible                                                                                                                                                                      |
| empty data      | `emptyLabel`                                                                                               | text shown instead of an empty frame                                                                                                                                                                 |
| formatting      | `format(value) => string`; plain: `formatOptions` (Intl.NumberFormatOptions), `locale`, `prefix`, `suffix` | numbers are passed raw, formatting is a function; the plain settings say the same with values a server component can pass (a function cannot cross to the client); `format` wins when both are given |
| missing data    | `null`                                                                                                     | a `null` value renders as unmeasured (hatched), never 0                                                                                                                                              |

Callbacks are `on<Thing>Change` for state and `on<Verb>` for events (`onSubmit`, `onRowClick`). Booleans are positive (`interactive`, not `disableInteraction`).

## Lists

DataGrid, Table record mode, Timeline and record-list screens share one contract, exported as `ListProps`, `ListSort`, `ListFilters` and `ListWindowProps` from `ui/lib/list.ts` (compiled: `@beamline/lib-list`). Paging state and controls live in `ui/components/_shared/list-state.ts` and `list-controls.tsx`; products never import those internal helpers.

| Concern             | Props                                               | Meaning                                                                                                                                                                                                                                                                   |
| ------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Server ownership    | `manual`                                            | The caller owns ordering, filtering, search and page slicing. Supplied rows are already the requested result; never transform that page again. Default false.                                                                                                             |
| Page                | `page` / `defaultPage` / `onPageChange`             | One-based, positive integers; default 1.                                                                                                                                                                                                                                  |
| Rows per page       | `pageSize` / `defaultPageSize` / `onPageSizeChange` | Positive integer; default 25. A user size change resets page to 1.                                                                                                                                                                                                        |
| Matching records    | `rowCount`                                          | Count of matching records before paging; `null` or omitted in manual mode means unknown. Local mode derives it before slicing. `total` is a deprecated alias; explicit `rowCount`, including null, wins.                                                                  |
| Paging controls     | `pagination`, `hasMore`                             | A page, size or row-count prop enables pagination; `pagination={false}` suppresses it. With an unknown count, `hasMore={false}` disables Next. `Pagination.pageCount` still means pages, never records.                                                                   |
| Sort                | `sort` / `defaultSort` / `onSortChange`             | Ordered `{ id: string, desc: boolean }[]`; `[]` means no sorting. Local missing values sort last in either direction.                                                                                                                                                     |
| Filters             | `filters` / `defaultFilters` / `onFiltersChange`    | `Record<string, string[]>`; keys name fields or the screen's documented view preset. An absent key or empty array clears that filter.                                                                                                                                     |
| Search              | `query` / `defaultQuery` / `onQueryChange`          | A string, including the controlled empty string. Query, filter and sort actions reset page to 1.                                                                                                                                                                          |
| Selection           | `selected` / `defaultSelected` / `onSelectedChange` | Stable string row IDs, including IDs on other server pages. Never derive selection from row indices or silently drop unseen IDs. `selectable` exposes selection controls.                                                                                                 |
| All matching        | `allowSelectAllMatching`, `onSelectAllMatching`     | Explicit request with `{ rowCount, query, filters }`; it does not manufacture IDs for unloaded rows. The app owns the server bulk action.                                                                                                                                 |
| Loading and failure | `loading`, `loadingMore`, `error`, `onRetry`        | Busy/error are caller-owned facts, not mutable list controls. Retain the last completed rows when a request temporarily supplies `[]`; show a load error and retry without losing selection. An Error renders its message. A successful empty result clears the old rows. |
| Append              | `onEndReached`, `hasMore`, `loadingMore`            | Caller appends records; requests stop at `hasMore={false}` and after a rejected callback until an explicit retry. This mode disables inferred page controls.                                                                                                              |

Table still accepts its native compound children. Its optional `data`, `columns` and `getRowId` activate record mode; `className` and the ref still address the native table. A block retains its domain data prop (`deals`, `users`, `invoices`, `tickets`, etc.). WorkflowBuilder's embedded run list uses the same props inside `runsList`; its editor node selection remains separate. SocialScheduler's post queue uses `queueList`; the calendar layout and per-post publishing retry remain separate. ApiKeys applies the contract to the active keys/webhooks tab. OrderHistory's manual fills view accepts a `fills` page of `HistoryFillRow` records, each with its parent `order`; `rowCount` then counts fills. Charts, navigation choices, editor nodes and the parts of one record are not record-list state.

List controls are optional additions: existing native tables and local screens keep their existing interactions. A custom list uses the shared selection menu when `selectable` is enabled, keeping opening a record as a separate action. Watchlist keeps its chosen local order while market values tick; manual mode preserves server order. Aggregates computed from supplied records cover only those records, never the complete server dataset.

Compatibility: preserve old spellings and callback payloads, and warn once after mounting in development only. Crm `scope`, MarketScreener `screen`, HelpdeskInbox/TeamMembers/SupportConsole `view`, and ChangelogFeed `filter` map to `filters.scope`, `filters.screen`, `filters.view` and `filters.tags`, with corresponding default/change forms. KnowledgeBase/SupportConsole retain scalar `selected` as an alias for `openSource`/`openConversation`; arrays are list selection. Use `manual`, `selectable`, an array `selected/defaultSelected`, or the new open-record props to opt into the array callback contract. AuditLog's legacy structured `onFiltersChange(AuditFilters)` remains active until `manual`, `filters` or `defaultFilters` opts into the standard field map. AgentRuns uses `onRetryRun(id)` for execution retry; the old one-argument `onRetry` remains a legacy execution callback outside manual/load-error mode. Standard `onRetry()` retries loading the list. New names win when both are supplied.

## Rendering contract

* **One root, nothing outside it.** Every token and base style exists only inside a `.pui` root (`<html class="pui">` or `UIRoot`), and every name the system defines starts with `pui` (`--pui-surface`, `@keyframes pui-shimmer`). A component never writes to `:root`, `html`, `body` or a global class, and never adds a keyframe or custom property without the prefix. The package build confines every remaining global rule to roots (`scripts/lib/scope-css.mjs`). Compiled utilities use `:where(.pui, .pui *)` inside the `utilities.pui` sublayer. Scoping adds no specificity, and standard host Tailwind utilities win regardless of stylesheet import order, so `hidden md:flex` still becomes visible at md. This ordering applies to normal declarations in the host's direct `utilities` layer. Browser capability reads are optional in host test DOMs: missing media queries keep the server default; missing resize observation keeps initial content; unsupported CSS features use the existing fallback.
* **Overlays open in the root's layer.** Anything portalled (dialog, sheet, popover, menu, select, tooltip, hover card, toast, a floating toolbar) reads `usePortalContainer()` from `@/runtime/appearance`. `undefined` means no provider; `null` means a managed scope whose host is pending. Keep hooks and triggers mounted, but render no portal content while the value is `null`. Only `undefined` may fall back to `document.body`. An initially open overlay must mount directly in its already-styled scope host; appearance updates reuse that destination. The old UIRoot component path re-exports this same runtime, never another context.
* **`className`** and `style` address the documented visual root; `classNames` addresses documented composite parts. Component CSS modules sit in `@layer components`, below Tailwind utilities and normal unlayered customer rules. CSS-module `composes` declarations are top-level composition-only rules, after the layer: Turbopack rejects them inside a layer. They emit no paint; all visual declarations remain layered. `scripts/layer-modules.mjs` enforces this. Overrides follow the real cascade: layers, importance and specificity matter; placing a class last is not a universal precedence rule. Read public override inputs through fallbacks instead of assigning them per instance.
* **`data-slot="<part>"`** on every meaningful part (`data-slot="order-book-row"`) so products can target parts without classes. State goes in data attributes: `data-state="open"`, `data-side="bid"`, `data-trend="up"`.
* **Refs:** React 19 `ref` as a prop on the documented native target. Button/Table share their visual root with it; Input's ref/native attributes/value target its input and Select's ref targets its trigger. Their visual root is the field wrapper. In 0.2 Input `style` moves to that wrapper; use `classNames.input` for native input styling. Older components that use `forwardRef` stay valid. Slot styling never replaces measurement refs, handlers or ARIA links.
* **Polymorphism:** triggers and link-able parts accept `asChild` (Radix `Slot`). Nothing else is polymorphic; text components that need it take `as`.
* **Primitives:** overlays, menus, and form controls are built on Radix. Do not add another headless library.
* **Icons:** lucide-react only, 16px in controls (`size-4`), 14px in dense rows. Pass icons as elements (`icon={<Plus />}`), not component types.
* **Styling:** tokens only (`var(--pui-surface)`, `bg-surface`, `text-ink-2`). A hex, rgb or ms literal in a component is a bug, except inside `tokens.css`, `looks.css` and `motion-tokens.ts`.
* **Paint comes from a look's role, never from the element.** A painted part names what it is with one role class (`m-control`, `m-solid`, `m-field`, `m-inset`, `m-toggle`, `m-knob`, `m-tag`, `m-surface`, `m-overlay`; DESIGN-LANGUAGE.md "Looks") and keeps only layout and its own marks. `bg-surface shadow-card` or `bg-surface-2 shadow-btn` in a className is the old way and a bug now. In a module: `.track { composes: m-toggle from global; }`. A mark on top of a role composes the role's own value: `shadow-[var(--m-surface-edge),inset_3px_0_0_var(--pui-red)]`. An element with a role lists `var(--m-transition)` in its transition. A part that follows a chosen item (a thumb, a ring, an underline) uses `useIndicator` and `glideProps` from `@/hooks/use-indicator` and wears the `glide` class (`glide-2d` on both axes), which places it and stretches it toward each new choice; its module adds only paint and shape. Type, radii and icon stroke come from tokens too (`font-title`, `rounded-control`, the heading rule in base.css), so a look restyles them; a component never names a font family or tests which look is on.
* **Motion:** import from `motion/react` and call `useMotionTokens(hostRef)` at the actual control. Its `reduced` flag follows managed/static policy and OS preference; before `ready` it exposes the pure foundation snapshot. CSS transitions use `--pui-motion-in` / `--pui-motion-out`.
* **Client-only APIs** (ResizeObserver, canvas, WebGL, audio) are touched in effects, so components render on the server without crashing. A layout chosen by width (a narrow mode, a chart's scale, items per row) reads it with `useContentWidth(ref, fallback)`, or `useWidthAs(ref, (w) => w < 640, false)` when only a flag or a count depends on it (it renders again only when that changes): measured in a layout effect, so the first frame is already right instead of a guessed width jumping a frame later.
* **Room, not window** (DESIGN-LANGUAGE.md "Responsiveness"). A block-level element's root is a size container (`@container`) and its parts use container variants (`@sm:` `@lg:` …), never window breakpoints (`sm:` `md:`); a content-sized part never gets containment. A row of items that can outgrow its room folds with `useFit` (`@/hooks/use-fit`): items marked `data-fit-item`, the "+N" `data-fit-more`, order kept, items that must show passed as `keep`. Secondary information that waits for attention uses `.reveal` inside `.reveal-host`.
* **Examples are fluid.** A `demo.tsx` root takes the stage's width up to a sensible maximum (`w-full max-w-[440px]`), never a fixed width, so the gallery's canvas and the fit sweep can give the element less room; a filling example (`layout: "fill"`) fills whatever height it is given.
* **Visually hidden text** (`sr-only`) is absolutely placed. A component that holds it in long content (table cells, rows) has a positioned root (`relative`), so the text is placed against the component itself; placed against an ancestor outside whatever scrolls the component, it falls beyond the scroller's edge and lengthens the page around it (Bench glitch kind `hidden-escape`).
* **Numbers:** tabular figures; prices keep their precision (`0.00482`), never rounded by a component.
* **Accessibility:** keyboard reachable, visible focus, roles from Radix or native elements, `aria-live="polite"` for values that change because of a user action (a total after a filter), never for high-frequency feeds (a price that ticks twice a second is readable on demand, not announced); labels on every icon-only control.

## Managed appearance runtime

`@/runtime/appearance` is the single client context entry. `@/runtime/preset` contains pure data/types only; `PresetRef = { id, visualContract: 1 }` loads no React, CSS, fonts or effects. Import the selected preset CSS explicitly. `UIRoot preset` wins over the deprecated built-in `look` shorthand. Outermost defaults are declared by the runtime; nested roots inherit omitted inputs. Sparse tokens merge by key and design defaults by component/key. `inherit={false}` starts from library defaults. A local accent replaces an inherited explicit brand; local brand wins within the same scope. Use the dedicated brand prop, never `tokens["--pui-brand"]`.

`appearanceClassName` is mirrored on the root and its independent portal host; selectors must work on each boundary. Omitting it inherits, a supplied value replaces, and `""` clears. Ordinary `className`/`style` remain local. A static `.pui`/`data-pui-preset` boundary supplies DOM styling only; arbitrary descendant CSS writes neither mirror to portals nor publish renderer invalidations.

Scoped defaults use lowercase catalog IDs, for example `defaults={{ button: { size: "sm" }, table: { density: "compact" } }}`. Each supporting client component reads only its allowed design keys through `useComponentDefaults<ButtonDefaults>("button", ["size", "variant"])`; explicit instance props win. Export lightweight `defaults.ts` types without importing component implementations. Never put handlers, domain data, value/open/loading, selection or adapters into defaults. Pure/static entries can keep explicit props and inherited CSS without acquiring a client context dependency.

Canvas/renderer code uses `useResolvedAppearance(actualHostRef, { colors, metrics, numbers })`. The server and first client render are unresolved; apply values only when `ready`. Colors are normalized to rgba, metrics to px, and unitless inputs are returned separately. Typography includes the host, body and numeric font families. Full CSS variable names are accepted for private resolved geometry. Subscribe through the nearest descriptor and font readiness, and call `refresh()` for explicit external stylesheet changes. Never resolve from the first document root or observe all document style mutations. Apply engine options in place; appearance must not reset feed markers, editor history, resource controllers, selection or an open overlay's lifetime.

Selected look SVG resources and optional edge-light are explicit application setup. The runtime does not import all look initializers. Setup returns a cleanup function and shares document resources by reference count. Semantic motion CSS and JavaScript derive from `ui/lib/motion-source.ts`; timing values remain governed by the visual contract.

## Code

* **Formatted and linted, always.** `pnpm format` (Prettier, 120 columns, Tailwind classes in the official order) and `pnpm lint` (hook rules and TypeScript's recommended checks) run clean on `ui/` and `gallery/`; `pnpm format:check` and `pnpm lint` are part of every change. A lint suppression names its rule and says why after `--`.
* **One statement per line**, and a component reads top to bottom: props and their defaults, state, derived values, effects, handlers, then the markup.
* **State lives in the stylesheet.** Utilities are for layout and spacing at the call site. A part whose className carries four or more state variants (`hover:`, `focus-visible:`, `data-[…]:`, `aria-…:`, `group-…:`, `after:`) moves to the component's `.module.css`, where states are selectors and every value is a token; so does any part that only exists to be styled (a resize grip, a prose body). `className` passed to another system component (a Button) stays utilities, since that is the documented way to override it.
* **No inline stylesheets.** Never `<style>{CSS}</style>` or a CSS string in a component; keyframes and rules live in the module. Inline `style` is only for values computed at run time, passed as custom properties (`style={{ "--pui-indicator-x":` ${x}px `}}`) that the module reads.
* **Keyframes belong to the module that plays them.** CSS modules rename every animation name, so a module that names a global `@keyframes` (`pui-…` in `ui/styles`) never plays; `node scripts/layer-modules.mjs` refuses it.
* **Variant tables are typed records** (`Record<ButtonVariant, string>`), never chains of ternaries in a className.

## meta.json — catalog entry and spec

```jsonc
{
  "name": "order-book", "title": "Order book", "category": "trading", "kind": "component",
  "description": "One sentence: what it is for.",
  "source": { "from": "Beamline", "license": "MIT", "mode": "original" },   // original | wrapper | vendored
  "dependencies": ["motion"], "tags": ["depth", "bids", "asks"],
  "whenToUse": ["…"], "notFor": ["… → use X"],
  "spec": {
    "anatomy": ["root", "header", "row (price, size, total, depth bar)", "spread"],
    "variants": { "layout": ["stacked", "side-by-side"] },
    "states": ["loading", "empty", "live", "stale"],
    "keyboard": ["↑/↓ move row focus", "Enter sends price to onPriceSelect"],
    "motion": "Changed sizes flash 90ms in / 600ms out; rows never slide.",
    "props": [{ "name": "bids", "type": "Level[]", "required": true, "description": "…" }]
  }
}
```

`minWidth` (optional, px) is the narrowest room the element is designed for. Every element works at 360 px and is checked at 390, so leave it out unless the element is not meant for a phone's panel (a pro desktop tool): then set the width below which it no longer reads, and the gallery's canvas stops there instead of at 360. It travels in the gallery's light index (`vite.config.ts`, `light-meta`) and into `catalog.json` when set.

`spec.variants` takes either shape: the object above (`{ name: [values] }`) or a list of descriptive lines when a variant needs words, as the charts use (`["stack: none · stacked · expanded", "brush"]`). Tools print both.

`scripts/gen-catalog.mjs` turns every meta.json into `catalog.json` and `CATALOG.md`. Vendored and wrapped code keeps `source.url` and `license`; third-party code inside an original element is listed in `source.credits` (name, licence, url); both end up in `NOTICE`.

meta.json ships to customers, so `source` says only what a customer may read: Beamline, or the third-party package and its licence. Where an element came from — references observed, clean-room recreations of paid kits, merged sources, lineage notes — goes in `research/provenance.json` (internal, never shipped; `scripts/acceptance.mjs` reads it).

## Categories

actions · forms · overlays · navigation · layout · feedback · display · data · charts · trading · dashboard · ai · voice · effects · blocks.

Rendered motion consumers resolve each CSS duration/easing/spring/stagger separately with `useMotionTokens(hostRef)`. The optional no-ref form resolves the nearest managed root; static scopes require a real host ref. `motionTokens` is the pure, server-safe foundation table for non-DOM use. `useResolvedAppearance` accepts `times` (seconds) and `easings` (computed CSS timing functions), resolves conditional hosts when attached, and clears readiness after removal. No module observes the first page root. Reduced timing declarations take precedence over inline timing overrides. Reading intervals, deadlines and data pacing remain caller behavior.

## Translate and localise

`UIRoot locale` is a BCP 47 language tag. Omitted values inherit the nearest managed root; the outermost root uses `document.documentElement.lang`, with `en-US` as the fallback. Server rendering without an explicit locale uses `en-US`; server-rendered applications should pass the same locale on server and client. `inherit={false}` resets locale and messages as well as appearance. The resolved language is written to the root and its stable portal host. Direction stays an explicit `dir` choice; choosing a language never silently changes a customer's layout.

`UIRoot messages` is a typed partial dictionary of `<component-id>.<key>` entries. Nested roots merge by full key; an omitted/undefined entry inherits. Each component owns one `messages.ts` English dictionary, loaded only with that component. Generated `ui/runtime/message-catalog.ts` contains types only, so a Button does not load every screen's copy. `scripts/extract-messages.mjs` generates the full key list, English defaults and interpolation names into each entry's `meta.json` for its Usage tab. Stable keys describe purpose, not the English wording or a line number; changing English copy does not change a key.

`useLocale()` resolves the nearest root. `useMessages(id, english)` returns a typed translator. Messages are plain text (never HTML); `{name}` interpolates a named value, numbers use the resolved `Intl.NumberFormat`. A plural message is an object with `other` and any `zero`, `one`, `two`, `few`, `many` branches; `Intl.PluralRules` selects by `count`. Optional exact branches such as `"=0"` win over the plural category. Each branch is a complete phrase, so translators can reorder words and choose their own number of forms. Missing overrides fall back to English; missing variables remain visibly `{name}` rather than disappearing. Do not assemble translated sentences from English fragments or implement plurals with `count === 1`.

Instance text props and `labels` win over scoped messages, including an explicitly empty string. New `labels` objects use the same short keys as the component dictionary: `label`, `description`, `empty`, `loading`, `error`, `retry`, `search`, `clear`, `close`, `previous`, `next`, `confirm`, `cancel`, `save`, `discard`, and specific action or field names as needed. Existing spellings remain deprecated aliases; a canonical key wins when both are passed. Keep existing callback labels callable. Arrays called `labels` that represent chart points or record tags are customer data, not copy dictionaries. The image-comparison caption tuple remains a deprecated alias for `{ before, after }`; `false` still hides captions.

**New components and blocks must use messages from their first implementation.** Put English defaults in their own `messages.ts`, call `useMessages(componentId, english)` inside the rendering component, and keep keys stable. A shared helper owns a small `<helper>.messages.ts` dictionary; never grow the general `_shared/messages.ts` with copy that only one family needs. Regenerate the type catalogue and metadata with `node scripts/extract-messages.mjs`.

All built-in copy belongs in dictionaries: visible text, accessibility labels, loading/empty/error states, validation fallback messages, units, default option labels and descriptions. Customer records, children, explicit labels, backend error messages, identifiers, source code and user-authored documents remain customer content. Default option lists are overridable props; validation must not reject a customer's locale merely because it was absent from the shipped options. Public pure helpers accept a locale/messages argument; they never read mutable global locale state. React consumers pass their scoped locale, including renderer/canvas adapters. Every built-in Intl number, date, relative-time and list formatter uses that locale; keep formatting callbacks as higher-priority instance overrides. Numeric input parsing follows the same locale as its displayed number. Pass translators into pure text helpers and into third-party editor callbacks through a current ref; changing language must not recreate the editor or restart a microphone stream. Never translate stored option values, filter IDs or event names; translate the display label separately. DataGrid `valueLabels` maps stored values to labels without changing filtering IDs.

`UIRoot pseudoLocale` accents built-in English and expands its text by approximately 35%, enclosing it in `⟦…⟧`. It preserves interpolation names and interpolated customer values. It is a testing switch independent of the Intl locale; explicit customer labels are not changed. Pseudo-locale gallery checks scan rendered built-in text and accessibility names, preserve a reason for each customer-data exception, and check desktop and 390px geometry. Locale proof includes German and Ukrainian messages/formats, nested scopes, open portals, reactive switching, instance precedence and RTL. Evidence records the source state, rerun commands and actual screenshots/results.

The family-wide check is `node scripts/e2e/i18n-families.mjs --url <development-gallery> --out <artifact-dir>`. Start the gallery outside the heavy slot. The finder inspects real examples and accessibility attributes; each customer-data exemption records the actual demo source and supplied prop. Retain unresolved findings as failures. Do not add a blanket route or English-word exemption to make a proof pass. Native Intl output is recorded separately.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://beamline.gitbook.io/beamline-docs/reference/conventions.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
