# UI architecture

**How the look is built: four design token files at the bottom, one site map at the top, and a test beside every layer.** A design token is a named CSS custom property (a CSS variable) holding one design decision.

[![TheYard's UI, layer by layer: the four design token files at the bottom; base.css, panels.css and the width steps above them; then the component sheets, the site map and the document layout, each reading only the layers below it](/api/docs/images/ui-architecture.svg#1200x420)](https://theyard.stevenstout.biz/api/docs/diagrams/ui-architecture)

*[Open the UI architecture diagram in a new page](https://theyard.stevenstout.biz/api/docs/diagrams/ui-architecture). It reads down, never up.*

## In plain words

The look is built in layers: a few files of named colours and sizes at the bottom, shared styles above them, then one small style file per component, and one site map that feeds the menus. Each layer uses only the ones below it, and each has a test.

What that is worth: a developer changes a colour in one place and it changes everywhere, and the organization can restyle the whole site without touching every screen.

## The layers

| Layer | Where it lives |
| --- | --- |
| Design tokens | [`colors.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/colors.css), [`sizes.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/sizes.css), [`typography.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/typography.css), [`effects.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/effects.css); three tiers in `colors.css` |
| Base and panels | [`base.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/base.css), [`panels.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/panels.css), the width steps in [`breakpoints.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/breakpoints.ts) |
| Component modules | One CSS module beside each component under [`src/components`](https://github.com/SteveStout/TheYard/tree/main/src/components), design tokens only, one job each. The main column, the inventory grid and the landing tiles size themselves with a container query, by their own box rather than the window. |
| Shell | [`src/app`](https://github.com/SteveStout/TheYard/tree/main/src/app): the frame, the header, the footer; [`Ribbons.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/Background/Ribbons.tsx) and [`Watermark.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/Background/Watermark.tsx) mounted once |

## One site map

[`src/lib/siteMap.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/siteMap.ts) feeds both the side rail and the landing page, so a section added there shows up in both places. What each section holds lives in [`src/library/sections.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/sections.ts); the documents themselves in [`records.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/records.ts) and [`pages.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/pages.ts).

```ts
{
  menu: 'look',
  icon: 'style',
  group: 'built',
  blurb: 'Colour, type and the rules the build enforces.',
},
```
*Live from [`src/lib/siteMapEntries.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/lib/siteMapEntries.ts#L50-L55), region `look-row`, read from this build at 1d6b50b.*

```ts
look: {
  label: 'Style',
  items: [
    { key: 'styleGuide' },
    { key: 'colorStyle', sub: true },
    { key: 'backgroundRibbon', sub: true },
    { key: 'uiArchitecture', sub: true },
    { key: 'documentStyle', sub: true },
  ],
},
```
*Live from [`src/library/sections.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/library/sections.ts#L150-L159), region `look-menu`, read from this build at 1d6b50b.*

## Every document in panels

[`src/lib/docLayout.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/docLayout.ts) turns each second-level heading into a glass panel, and [`src/library/DocDialog.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/library/DocDialog.tsx) is the window it opens in. That is why this page looks like every other page.

```ts
const PANEL = 'doc-panel';

/** Splits at every second-level heading that starts a line of markup, never inside a code block. */
const AT_H2 = /(?=<h2[ >])/;

/**
 * The rendered document as panels. `lead` carries the title and any words
 * before the first heading; each heading after it opens a panel of its own.
 */
export function layoutDocument(html: string): string {
  const parts = html.split(AT_H2);
  const lead = /^<h2[ >]/.test(parts[0]) ? '' : parts[0];
  const sections = lead === '' ? parts : parts.slice(1);
  const panels = [
    ...(lead.trim() === ''
      ? []
      : [`<section class="${PANEL} op-glass doc-lead">${statusReading(lead)}</section>`]),
    ...sections.map((section) => `<section class="${PANEL} op-glass">${section}</section>`),
  ];
  return `<div class="doc-page" data-testid="doc-page">${panels.join('')}</div>`;
}
```
*Live from [`src/lib/docLayout.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/lib/docLayout.ts#L20-L40), region `layout-document`, read from this build at 1d6b50b.*

## Where a change goes

| The change | The file | The test that holds it |
| --- | --- | --- |
| A colour | [`src/styles/colors.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/colors.css) | [`StyleRulesTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/StyleRulesTests.cs), [`colors.test.ts`](https://github.com/SteveStout/TheYard/blob/main/src/styles/colors.test.ts) |
| A panel's look | [`src/styles/panels.css`](https://github.com/SteveStout/TheYard/blob/main/src/styles/panels.css) | [`panels.test.ts`](https://github.com/SteveStout/TheYard/blob/main/src/styles/panels.test.ts), [`glass.spec`](https://github.com/SteveStout/TheYard/blob/main/tests/e2e/glass.spec.ts) |
| The ground | [`src/lib/ribbons.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/ribbons.ts) | [`ribbons.test.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/ribbons.test.ts) |
| A section or tile | [`src/lib/siteMap.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/siteMap.ts), [`src/library/sections.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/sections.ts) | [`siteMap.test.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/siteMap.test.ts), [`DocumentationCatalogTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/DocumentationCatalogTests.cs) |
| A document | [`src/library/pages.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/pages.ts) or [`records.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/records.ts) | [`DocumentationCatalogTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/DocumentationCatalogTests.cs), [the page sweep](https://github.com/SteveStout/TheYard/blob/main/tests/e2e/coverage.spec.ts) |
| A width step | [`src/lib/breakpoints.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/breakpoints.ts) | [`coverage.spec`](https://github.com/SteveStout/TheYard/blob/main/tests/e2e/coverage.spec.ts), [`StyleRulesTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/StyleRulesTests.cs) |
| The frame or a hook | [`src/app/`](https://github.com/SteveStout/TheYard/tree/main/src/app) | [`FileHeaderTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/FileHeaderTests.cs), the Back and Forward specs |
| A stylesheet | the sheet beside its component, or [`src/styles`](https://github.com/SteveStout/TheYard/tree/main/src/styles) | [`FileHeaderTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/FileHeaderTests.cs), [`StyleRulesTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/StyleRulesTests.cs) |

## One job per file

A file grows a job at a time and never complains. So the rule is a test, not a habit.

Every file under `src/app` and `src/library`, and every stylesheet under `src`, opens with three lines before anything else: what it does, what it does not, and which files use it. It stays under 300 lines unless it is pure data. A real one, from the hook that owns the address bar:

```ts
/**
 * Does:      Holds the open view (the page, the vehicle, the Admin card, the document, the filters and sort)
 *            and keeps the address bar in step with it, both ways: the first-load reads, deep links, Back and Forward.
 * Does not:  Decide when a view opens (useNavigation.ts does), fetch the vehicle list, or draw anything.
 * Used by:   App.tsx, useNavigation.ts, useInventory.ts, useOpenVehicle.ts.
 */
```
*Live from [`src/app/hooks/useAddressBar.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/app/hooks/useAddressBar.ts#L1-L6), region `header`, read from this build at 1d6b50b.*

And one from a stylesheet, which carries the same three lines:

```css
/**
 * Does:      Styles the live swatches a served document carries: the grid, each chip painted with its own design
 *            token, the label, the contrast figures, and a swatch whose design token is missing.
 * Does not:  Choose a colour (src/lib/swatches.ts paints each chip with the design token itself) or style anything
 *            else in a document.
 * Used by:   DocDialog.tsx.
 */
```
*Live from [`src/library/DocSwatches.module.css`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/library/DocSwatches.module.css#L1-L7), region `header`, read from this build at 1d6b50b.*

The table of contents the code split left behind: [`src/app/App.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/app/App.tsx) names each hook once, with what it gives back.

```tsx
const address = useAddressBar(); // which view the address names; the only code that writes it
const { account, changeAccount } = useAccount(); // who is signed in
const inventory = useInventory(address, account.email); // the vehicle list, with this visitor's bids on it
const vehicle = useOpenVehicle(address, inventory); // the vehicle on screen
const rail = useRail(); // the sidebar: docked, collapsed, or a drawer
const go = useNavigation(address, inventory.loadState); // open and close views, move focus, announce
const build = useRunningBuild(); // the version line, asked of the API
const now = useNow(1000, useFirstLoad()?.nowMs); // the clock every countdown reads
const inBrowser = useInTheBrowser(); // Admin and the account page draw only here, never on a server
```
*Live from [`src/app/App.tsx`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/app/App.tsx#L38-L46), region `table-of-contents`, read from this build at 1d6b50b.*

[`FileHeaderTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/FileHeaderTests.cs) fails the build for any file without the header, checks "Used by" against the files that really import it, and holds the 300-line cap. The numbers below are read from the repository at build time.

```csharp
/// <summary>The two folders the rule holds for code and stylesheets alike, relative to the repository root.</summary>
private static readonly string[] Folders = ["src/app", "src/library"];

/// <summary>What a file in them can be: code, and the stylesheets beside it.</summary>
private static readonly string[] Kinds = [".ts", ".tsx", ".css"];

/// <summary>And every stylesheet anywhere under this folder: a sheet grows a job at a time the way code does.</summary>
private const string Sheets = "src";

/// <summary>The three labels, in the order a header gives them.</summary>
private static readonly string[] Labels = ["Does:", "Does not:", "Used by:"];

/// <summary>
/// Past three hundred lines, each with why. One job per file keeps a file
/// short. The four component sheets named here each hold one
/// component's look, shared by that component and the parts split out of
/// it, and are over the line because the look is one piece; each comes
/// off this list when its sheet is split by job.
/// </summary>
private static readonly Dictionary<string, string> LongAllowed = new(StringComparer.Ordinal)
{
    ["src/components/landing/Landing/Landing.module.css"] = "the landing page's one look (hero, featured tiles, section tiles); split with Landing.tsx in the component split lane",
    ["src/components/layout/SideNav/SideNav.module.css"] = "the side rail's two shapes, one set of rows: one sheet shared by SideNav.tsx and the rows split out of it",
    ["src/components/admin/AdminPanel/AdminPanel.module.css"] = "the Admin workbench's frame: one sheet shared by AdminPanel.tsx and the parts split out of it",
    ["src/components/vehicle/VehicleDetail/VehicleDetail.module.css"] = "a vehicle's own page; split with VehicleDetail.tsx in the component split lane",
};
```
*Live from [`api/TheYard.Tests/FileHeaderTests.cs`](https://github.com/SteveStout/TheYard/blob/1d6b50b/api/TheYard.Tests/FileHeaderTests.cs#L19-L44), region `the-header-rule`, read from this build at 1d6b50b.*

```text
Every_file_opens_with_what_it_does_what_it_does_not_and_who_uses_it
Used_by_names_exactly_the_files_that_import_it
No_file_runs_past_three_hundred_lines_but_the_ones_named_with_why
```

```readouts
Files with the header | 88 of 88
Stylesheets under src | 56
Over 300 lines, named | 4
```

| The file | Before | After | Jobs it held |
| --- | --- | --- | --- |
| `src/App.tsx` | 1,100 lines | [`src/app/App.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/app/App.tsx) 136, eight hooks in [`src/app/hooks`](https://github.com/SteveStout/TheYard/tree/main/src/app/hooks) | Seven: the address bar, navigation, the inventory, the open vehicle, the account, the rail, the frame |
| `DocsMenu.tsx` | 1,308 lines | [`src/library`](https://github.com/SteveStout/TheYard/tree/main/src/library): [`records.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/records.ts) 52 is data, [`sections.ts`](https://github.com/SteveStout/TheYard/blob/main/src/library/sections.ts) 242, [`DocDialog.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/library/DocDialog.tsx) 236 | Four: the records, the pages, the sections, the document window |
| `DocDialog.module.css` | 828 lines | [`DocDialog.module.css`](https://github.com/SteveStout/TheYard/blob/main/src/library/DocDialog.module.css) 266, [`DocProse`](https://github.com/SteveStout/TheYard/blob/main/src/library/DocProse.module.css) 215, [`DocSwatches`](https://github.com/SteveStout/TheYard/blob/main/src/library/DocSwatches.module.css) 60, [`DocPanels`](https://github.com/SteveStout/TheYard/blob/main/src/library/DocPanels.module.css) 70, [`AuthorPage`](https://github.com/SteveStout/TheYard/blob/main/src/library/AuthorPage.module.css) 280 | Five: the window, the prose, the swatches, the reading panels, the Author page |
| `App.module.css` | 425 lines | [`App.module.css`](https://github.com/SteveStout/TheYard/blob/main/src/app/App.module.css) 285, [`InventoryView.module.css`](https://github.com/SteveStout/TheYard/blob/main/src/app/InventoryView.module.css) 151 | Two: the frame, the inventory page |

The "Before" column is measured at b9ae11a (1.0.3.35) for the code and a69b292 (1.0.3.39) for the sheets; the "After" column is this build. Four component sheets are still over the line, each named in the test with its one job and the lane that will split it with its component. Records: [ADR-019](https://theyard.stevenstout.biz/?doc=adr-react), the addenda on the split and on the stylesheets; [ADR-074](https://theyard.stevenstout.biz/?doc=adr-highlighting), code that reads like code; [ADR-075](https://theyard.stevenstout.biz/?doc=adr-rules), the rule rows.

## The rest of the architecture

The API, the stores and the data live in [App Architecture](https://theyard.stevenstout.biz/?doc=architecture). This page covers only the look.
