Factory docs, home
Page navigation

Reference document, shown as written except that local paths appear as placeholders. Where it describes the Factory as intended, read it as design, not current state: only local infrastructure is accepted, no release, customer value or scheduled automation is established, and all 27 customer-value epics remain open. Current state: Factory model.

Factory's design language is the design system Factory · Plumb, iterated in Claude Design (version 1790613544-ec35, synced 2026-09-28). Its link is recorded in the Notion status, not here. tokens.json is the repository's canonical copy, byte for byte as the design system exported it.

Brand guide · Dashboard design · Vector geometry · Brand assets

What the tokens hold

  • 20 colours, one light theme. ok, warn and problem mean status only, always beside a word, and never appear in artwork.
  • Two families, sans and mono, both system stacks, with 21 type styles in three groups: Display, Text and Code.
  • Spacing space-1 to space-12 (4 to 48 px), radii radius-sm, radius-md and radius-lg (4, 6 and 8 px), the focus ring (the only shadow) and layout and brand sizes such as rail-width, inset, control-height, touch-min, mark-min and illustration.
  • Responsive values at 1280 and 960 px live in each token's usage note and in the stylesheets, not as separate tokens.

The rules for voice, colour, type, layout, the mark, Plumb, icons and accessibility are in the pages above; the tokens carry values only.

Where the tokens go

  • Dashboard. apps/dashboard/src/styles.css declares :root custom properties under the token names (--ink, --blue, --rail-width, --inset, --focus and the rest); other palette colours appear there as literal values. The tokens were read from this stylesheet, so names match.

  • Docs site. scripts/build-docs-site.ts reads this file at build time and generates the token block of site/assets/site.css: every colour, spacing, radius, shadow and size token as a :root custom property, the two families as --font-sans and --font-mono, a .type-<name> class for each type style, and the 1280 and 960 px values the usage notes state. Every other rule refers to those properties, so the token block holds the only colour literals. The design system has no token for a reading measure or a minimum width, so the builder names six site measures once (SITE_MEASURES: the 70ch reading measure, as in the dashboard, the narrowest page, table, wide table, diagram and module card); every other length is a token or a 1 to 3 px line or offset. The builder stops on a second theme, a font file, a value it cannot carry, or a token that takes a site measure's name.

  • Docs site layout. As in the dashboard: a rail-width navigation rail with the mark from factory-mark.png beside the live wordmark (mark-lg, mark-md or mark-sm, never below mark-min), the main column inset by inset, and a top bar at 960 px and below. The mark and the dashboard's favicon.svg are copied byte for byte. Module status is a word in a badge: Accepted and In progress on the default badge, Planned quiet. The site shows no stale, blocked or failed state, so it uses no ok, warn or problem colour. Limits, what is not established and the reference pages' note are notices. Plumb appears on the home page below its words with empty alt text. Module pages show their whole plate, up to 768 px wide so its labels stay legible. The Design page ends with the decision-domain studies as text, and its note says the site added that section.

  • Diagrams and PDFs. scripts/render_docs.py reads the colours and the sans family from this file for the six architecture diagrams in diagrams/ and the four PDFs in output/pdf/: ink and muted text, rail boxes (select for stores) with rule edges, muted arrows, blue links, and weights 400, 650 and 750. The PDFs embed Arial, a member of the sans stack, because a PDF needs a font file. The hand-drawn module map uses the same tokens.

  • test/design-tokens.test.ts reads both stylesheets and the drawings with allow-lists, so a value it does not know fails:

    • Colour. Palette colours only, in any CSS syntax: every named, system or escaped colour fails. A property that takes a colour holds only palette colours, transparent, currentcolor and its own keywords. No gradient, recolouring filter or blend mode. A dashboard variable named after a token equals it.
    • Type. The sans or mono family. Every font property, line-height and letter-spacing takes a type token's value; any other font property fails, except font-smoothing: antialiased. The font shorthand only as font: inherit.
    • Radius, spacing, shadow and outline. Every radius property (each corner, physical or logical, both axes) takes radius tokens. Every margin, padding and gap is on the spacing scale; the dashboard also keeps its 26 listed values. Every shadow is none, and box-shadow may be focus. outline is none or 3px solid var(--blue), outline-offset is 2px or -3px, and other outline properties fail. calc() appears only in the forms the test lists, on the properties that use them: the mark's clear space in both stylesheets, and the site's skip link, plate width and sticky navigation height. Any other math function fails. Every other site dimension (in any unit) and percentage is a token, a site measure or a listed structural value: 1 to 3 px lines, the underline offset, 100vh, 100%, 50% and 1fr. The site uses no function beyond var(), those calc() forms, and the grid, clip, transform and attr() functions it needs.
    • Variables and resets. A custom property is declared only on :root for a token (the site adds its font stacks and measures), on :root in a single max-width query at a breakpoint the token's note names (only the rail width and inset change, to the notes' values), or as --mark on .wordmark (the mark token for that width, never below mark-min), each width's value once and in order. var() names only those, with no fallback. No CSS-wide keyword but inherit, and no all.
    • Scheme, at-rules and escapes. Light only; no at-rule but @media; no CSS escape (a backslash) outside a string, so no property, function, unit or keyword hides behind one. The test reads CSS through a tokenizer that follows CSS Syntax Module Level 3 and re-serialises both stylesheets byte for byte, so a comment delimiter inside a string or url() never hides a rule. CSS it cannot read as a browser would fails: a bad string or url(), an unclosed block or comment, or a nested rule. Every check also reads escapes decoded, as the browser would.
    • Drawings in docs/diagrams/ and docs/guide/: the same colour, family, weight, reset and escape rules, with no currentColor, custom property or var(), and <style> as plain CSS (no CDATA, XML comment or character reference). Every shape and text fills from a palette value, its own or inherited. Only static shapes, text, groups and markers, with no at-rule, processing instruction or reference outside the file. Every attribute is one the drawings use: structure and geometry take plain values. The presentation attributes are fill, stroke, stroke-width, stroke-dasharray, font-family, font-size, font-weight and text-anchor, plus letter-spacing and marker-end in <style>, each read with the stylesheets' declaration checks. Any other attribute or presentation property fails, such as filter, opacity, transform, clip-path or mask.

    Mutation cases alter copies in memory to show each rule failing. The test lists any dashboard colour variable that has no token. test/docs-site.test.ts checks the site's use of these rules on every page.

Re-syncing

Either the owner starts /design-sync, or by hand:

  1. Copy the design system's exported tokens over docs/brand/tokens.json without editing them.
  2. Run node --test test/design-tokens.test.ts and bring any dashboard value it names into line, or report the difference to the design system.
  3. If a colour or the sans family changed, regenerate the diagrams and PDFs with python3 scripts/render_docs.py (it needs ReportLab and pypdf).
  4. Rebuild the site with rm -rf site && npm run docs, then run npm run check and, for the dashboard, npm --prefix apps/dashboard run check.

Not covered yet

  • No dark theme. The design system is light only, so the dashboard and the docs site render light only. The docs site's earlier dark-mode rules were removed, not given invented colours. A dark palette needs new tokens from the design system first.
  • No font files. Both families are system stacks.
  • No measure tokens. Reading measures and minimum widths are not tokens. The dashboard sets its own (60 to 90ch and fixed widths); the docs site names six in SITE_MEASURES. Tokens from the design system would replace both.
  • The dashboard's in-between spacing. The dashboard's stylesheet is also the design system's component bundle, copied verbatim, and 26 of its margins, paddings and gaps sit between the spacing steps (for example 20, 28 and 36 px). The test lists them and fails on any new one; they change only when the design system changes them.
  • Other dashboard lengths. Positions, border widths and element sizes in the dashboard are not on a scale; the rules above cover its colour, type, radius, spacing, shadow and outline. The site's other lengths are tokens or site measures.
  • Opacity. opacity is not checked: the dashboard's disabled states (0.7 and 0.45) blend palette colours with the canvas, so they render tints outside the palette. A disabled-state token from the design system would replace them.
  • Brand artwork. The checks read stylesheets and drawings as text. The brand artwork (the mark, the favicon, the lockup and the plates) keeps its reviewed bytes and is not read by them.
  • The static lockup's stack. factory-lockup.svg keeps its reviewed bytes (provenance), so its text names Helvetica where the sans token names 'Helvetica Neue'. Changing it needs a new review and provenance record.

Source: docs/brand/design-language.md