# Toolkit changes

This record describes public toolkit changes and consumer migrations. The package manifest owns the current version. Entries describe source changes, not proof of publication or deployment.

## 0.4.0

Compatible additions, breaking renames and removals, and changed visual defaults; each breaking change states its migration. Not published by this change.

### Components

- **`Analytics`** measures page views and declared events with the shared Umami instance, from the configuration the host serves at `/analytics-config.json`. Statistics are on by default: its privacy dialog, in the `Page` language, turns them off and on again, and Do Not Track and Global Privacy Control always turn them off. Every app shares one Umami website: the path starts with the domain, `/404` stands for missing pages, and the domain is also the distinct ID, so visitor identifiers stay specific to each site; the first measurement adds the origin of an external referrer. `track(name)` sends one of the names passed to `events`. Every workspace app mounts it in its root layout and serves the configuration from its Caddyfile; Bloom's own opt-in measurement is removed. Migration: Bloom drops `UMAMI_ENABLED`. Workspace Caddyfiles default to the shared distributed·gallery website, formerly Bloom's; `UMAMI_ENDPOINT` and `UMAMI_WEBSITE_ID` override it, and an empty website ID turns statistics off.
- **`Footer`** shows the privacy and statistics control after its notice once `Analytics` has loaded its configuration. The notice and the control wrap as two units in a row; without `Analytics`, the footer is unchanged.
- **`Dialog`** opens a native modal as a centered panel, a start or end drawer, a full-screen framed sheet or a transparent full-screen media view, with a titled bar, a close button, a blurred backdrop, scroll locking and focus return. `open()` and `close()` control it. The media placement hides its title visually, floats the close button in a framed box and closes on a press beside its content; the site's image lightbox uses it. `--dialog-width` overrides the preferred width of the panel and drawers, and an overflowing body becomes a focusable scrolling region. Bloom's reference reader and drawer use it.
- **`Header`** builds its narrow-screen menu on `Dialog`. Its appearance, keyboard behavior and focus handling are unchanged; the menu bar title is now the dialog's heading. It accepts an `actions` snippet for compact controls, placed before the theme switch and kept in the bar beside the menu button below `--medium`; `IconButton` adds a `search` icon for them. The DS opens its search from there.
- **`Heading`.** Accepts `num`, an optional section number shown before the title in `SectionHead`'s numbering style. The editorial offset above the title is `--space-8` instead of `--space-7`. Migration: pass `padding="var(--space-7) var(--layout-padding-inline) var(--layout-padding-block)"` to keep the former inset. Workspace apps adopt the new default.
- **`Section`** draws crosses at all four corners by default (`crosses="both"` instead of `"bottom"`); `Heading` and `ErrorPage` inherit it. At a junction, the new top crosses coincide with the preceding frame's bottom crosses. Migration: pass `crosses="bottom"` to keep the former corners. Workspace apps adopt the new default. Its inset preset is renamed `layout` instead of `default`, so the name states the applied tokens. Migration: replace `padding="default"` with `padding="layout"`, or omit the prop; workspace apps never pass it. `gap` accepts the presets `layout` (default), `content`, `control` and `none`, like `padding`; custom CSS gap expressions still apply unchanged. Workspace apps, `Heading` and `ErrorPage` pass `gap="none"` instead of `gap="0"`.
- **`ChatMessage`** sets the body role on its message body instead of on every paragraph inside it, and only its plain-text paragraph keeps line breaks. Paragraphs in children no longer lose their margins or collapse their whitespace, so `.formatted` content applies unchanged. Migration: pass plain text through `text`, or set `white-space: pre-wrap` on the element that holds it in children. Bloom renders user messages through `text` and drops its paragraph overrides.
- **`SectionHead`** accepts `padding`: `none` by default, or `layout` for the shared inset when the head sits in a `Section padding="none"` with full-width content, so the head stays inside the section it names.
- **`Button`.** The default `subtle` variant drops its visible border and marks its four corners with accent half-crosses, drawn like the focus marks of text fields; hover replaces them with the inverted fill. Keyboard focus keeps the outer outline, and forced-colors mode shows a system border. `solid` and `accent` are unchanged. Migration: restore `border-color: var(--border-prominent)` and `background-image: none` on `.button` in app styles to keep the former look. Workspace apps adopt the new default.
- **`Grid`.** `Card` cells of a divided `Grid` take the layout inset (`--layout-padding-*`) instead of the content inset, so a grid of cards aligns with the shell gutter. Migration: compose divided grids in `Section padding="none" gap="none"` so their dividers reach the frame; give custom cells the layout inset. Workspace apps are migrated.
- **`Cards`.** `minWidth` takes a CSS length instead of a pixel number; the default is `"17.5rem"` (280px at a 16px root). Migration: replace `minWidth={240}` with `minWidth="15rem"` or another CSS length. Workspace apps are migrated.
- **`Page`** margins come from the spacing scale: `--space-7` (48px instead of 44px) above `--narrow` and `--space-4` (16px, unchanged) below. Its default `width` is `"100rem"` instead of `"1600px"`: the same 1600px at a 16px root, but it now follows a larger text size together with the breakpoints. Migration: pass `width="1600px"` to keep a fixed pixel maximum. A direct `aside` now becomes a sidebar: `Page` places it in a start column of 14 to 18rem beside the content, between `Header` and `Footer`, and stacks it above the content below `--medium`. Compositions without an aside are unchanged. The DS catalogue uses it instead of a local grid wrapper.
- **Role alignment.** Components use the shared role tokens instead of local values: `Card`, `Heading`, `ErrorPage` and the narrow-screen menu titles take `--heading-line-height` and `--heading-letter-spacing`; `Card` content and `ChatMessage` take `--body-line-height`; `Quote` takes the prose line height and tracking; section numbers take the interface or metadata weight. `ChatComposer`'s hint uses `.feedback`, the `ChatWindow` badge the metadata size, and its launcher and suggestions the interface tracking. `ChatWindow` fullscreen locks scrolling on the root element, like `Dialog`. `IconButton` adds `previous` and `next` icons, which `Cards` uses for its scrolling controls. Migration: restore local values in app styles where the former rhythm mattered.

### Typography

- **Strong emphasis.** `--strong-font-weight` (`400`) is one emphasis weight shared by every `strong`, `b`, `th` and `dt`, inside or outside `.formatted`, above every role and keeping the family, size and color of its context. Table headers and definition terms were set apart by color only, at the surrounding weight, and `.formatted th` cancelled the native bold; a `th` outside `.formatted` took the browser's bold (700), and `strong` and `b` the browser's relative `bolder`. A role class on the element itself, such as `<th class="ui">`, sets that role's weight instead; set the role on an ancestor to keep the emphasis, as `thead` does with the interface role. Migration: set `font-weight: inherit` on `th` and `dt` in app styles to keep the former weight. www drops its local weight on press sources.
- **Type scale.** `--font-size-base` defaults to `0.95rem` instead of `1rem`. At a 16px root, body text is 15.2px, interface text 11.4px and metadata 10.45px; headings scale in proportion and spacing is unchanged. Migration: set `--font-size-base: 1rem` on `:root` to keep the former sizes.
- **Type scale steps.** `--font-size-sm`, `--font-size-md` and `--font-size-lg` are removed: `--font-size-base` is the only shared length, and each role derives its own factor from it. Migration: replace `var(--font-size-sm)` with `calc(var(--font-size-base) * 0.6875)`, `var(--font-size-md)` with `calc(var(--font-size-base) * 0.75)` and `var(--font-size-lg)` with `var(--font-size-base)`. Workspace apps never used them.
- **Code role.** `.code` sets code blocks and literal data, such as token names and values, in monospace at `--code-font-size` (`0.85` × the base, 12.92px at a 16px root, between interface and body text) with its own weight, line height and slightly tightened tracking (`-0.05em`), keeping the source casing even inside `.ui` or `.metadata`. `--code-font-size`, `--code-font-weight`, `--code-line-height` and `--code-letter-spacing` define it, and `.formatted` code blocks now use it instead of the surrounding text size. Migration: set `font-size: inherit` on `.formatted pre` in app styles to keep the former code block size.
- **Inline code.** Every `code` element takes the code role's family, weight (`200`) and tracking (`-0.05em`), with the surrounding text size and `line-height: 1`; code inside `pre` keeps the block line height. Outside `.formatted`, `code` formerly used the browser's generic monospace and the inherited weight; inside, the inherited weight without tracking. Migration: set `font-weight: inherit` and `letter-spacing: normal` on `code` in app styles to keep the former weight and tracking. Workspace apps adopt the new default.
- **Preformatted text and literals.** `pre` takes the complete code role everywhere, as `.formatted pre` already did, and `kbd` and `samp` follow inline `code`. They formerly used the browser's generic monospace, which reduces the size to about 13/16 of the surrounding text. All four are upright and keep their source casing, so code inside italic text or a `.ui` or `.metadata` parent is no longer slanted or uppercased. Every `pre` scrolls long lines inside the block instead of overflowing its container; `.formatted pre` keeps only its surface and padding, and the `0.85em` reduction of `.prose` applies to inline `code`, `kbd` and `samp` but not inside `pre`, and only once: the keys of a nested `kbd` combination inherit it instead of shrinking again. Migration: set `font-family: monospace` on `pre`, `kbd` or `samp` in app styles to keep the browser default, or `font-style: inherit` and `text-transform: inherit` to let literals follow their context. Workspace apps use `pre` only inside `.formatted` or with `.code`, so their rendering is unchanged.
- **Formatted content.** The `.prose` class is renamed `.formatted`. `.constrained-width` works on any element instead of only beside `.formatted`, filling the available width up to the measure, and `--prose-max-width` is renamed `--reading-max-width`. Inline code takes the surrounding text size instead of `0.9em`, since Roboto Mono shares Roboto's glyph heights, and sets `line-height: 1` so lines containing it keep the body rhythm. Migration: replace the class and token names; set `font-size: 0.9em` on `.formatted code` in app styles to keep the former code size. Workspace apps and svx-deck are migrated.
- **Prose role.** `.prose` now sets continuous authored writing in the serif family, with its own `--prose-font-size`, `--prose-font-weight`, `--prose-line-height` and `--prose-letter-spacing`; inline code inside it is reduced to `0.85em`. An element still using the former `.prose` loses its formatted rhythm and becomes serif; complete the rename above before adopting the role. The site's presentation and artwork texts use it.
- **Serif italics.** Teodor italics show the font's drawn slant at 10° in every browser. The face's slant range stops at `10deg` instead of `12deg`, and `body` sets `font-synthesis-style: none`: since the range stays below the browsers' italic threshold, Chromium added a synthetic skew of about 14° on top of the axis (about 25° in all) and WebKit skewed the upright glyphs by about 14°. Only `Quote`, which turned synthesis off itself, showed the drawn slant; it now relies on the document default. A font without an italic face stays upright instead of being skewed. Migration: set `font-synthesis-style: auto` on `body` in app styles to let browsers synthesize italics again. Workspace apps adopt the new default.
- **Local type scale.** `.typography` is renamed `.typescale`; behavior is unchanged. Migration: replace the class name where a local `--font-size-base` is set. The DS and svx-deck are migrated.
- **Tables and rules.** Tables replace their native rendering with one appearance, inside or outside `.formatted`. They collapse their borders, fill their container and draw a `--border` rule between rows, without an outer frame. Cells and captions align to the start instead of centering. Cells take `--space-2` and `--space-3` padding, except on the table's outer edges so the first and last columns align with the surrounding text; they align their content to the top, use tabular numbers and size columns around whole words instead of the inherited `anywhere`, and literals inside them do not wrap. `thead` takes the interface role, `th` and `dt` prominent text, and table captions `--text-subtle`, `--space-2` above their table. Figure captions take the same subtle text everywhere; formatted content keeps `--space-2` between a figure and its caption. Every `hr` draws one `--border-prominent` rule instead of the native inset line. A `.scrollable` wrapper scrolls wide tables. Migration: remove local table, caption and rule styles that duplicate these, or override them on the app's own selectors, such as `border-collapse: separate`, `min-inline-size: auto` or `text-align: center`, to keep the native rendering, and set `overflow-wrap: anywhere` on `.formatted td` to keep the former breaks. Bloom is migrated, and www drops its local term color.
- **Underlined text.** `.underlined` gives any element the underline of `.formatted` links, a 1px line offset by `0.16em`, for a link or a button presented as a text link that color alone would not distinguish. Migration: replace local underline declarations with the class. The site's exhibition links, Bloom's reference button and `Footer`'s privacy control use it; Bloom's buttons move from offsets of `0.2em` and `0.3em` to the shared one.
- **Scrollable regions.** `.scrollable` scrolls content wider than its column, such as a wide table, horizontally inside the column; the owner adds `tabindex="0"`, `role="region"` and an accessible name. Migration: replace local horizontal scrolling wrappers with the class. Bloom's Markdown tables and the DS reference tables use it instead of their local `.table` and `.table-scroll` rules.
- **Formatted blocks.** In a loose list, whose items wrap their content in paragraphs, items and their paragraphs are separated by `--content-gap-block`, as quotation paragraphs are, instead of `--space-2` between items and no gap between paragraphs; a nested list in such an item takes the same gap. An ordered list of ten items or more, or with a `start` other than 1, indents by `--space-6` instead of `--space-5`, so two-digit numbers no longer touch the column edge; Bloom sets `start` only when a list does not begin at 1. Unordered items replace the native bullet with a muted cross centered on the first line, and ordered numbers take the same `--text-muted`. `hr` takes one and a half times the content gap on both sides, like a subsection heading. Definition lists gain list styling: definitions at the list indent and `--space-2` between items, with the prominent terms that every `dt` now takes. A figure caption placed before its media no longer gets a top margin. Code blocks draw their edge with `--border-subtle` instead of `--border`, since their fill identifies them. Migration: in app styles, set `border-color: var(--border)` on `.formatted pre` to keep the former edge. Workspace apps adopt the new defaults.

### Colors, surfaces and interaction

- **Colors.** The accent has one value in both themes: the dark theme no longer redefines `--accent`, which was a different, out-of-gamut pink, and `--accent-subtle` and `--focus` derive from it at the root. `--text-subtle` sits midway between default and muted text (`47%` light, `71%` dark), and the dark `--background` takes hue `260` like the other dark neutrals instead of `340`, a difference below one sRGB level. Migration: redefine `--accent` under `[data-theme="dark"]` to keep a distinct dark accent, with `--accent-subtle` and `--focus` beside it.
- **Feedback colors.** The four light `--feedback-*` colors are more saturated; the light error moves from pink toward red, away from the accent, and the light warning toward orange. In the dark theme, error and warning are more saturated, success shifts slightly toward teal and info is unchanged. The light values stay below 4.5:1 on light surfaces, and the browser suite checks feedback contrast in the dark theme only. Migration: redefine the four tokens under `:root` and `[data-theme="dark"]` to keep the former values.
- **Dark shadows and overlay.** The dark theme redefines `--floating-control-shadow` and `--floating-shadow` at 45% and 60% black, and `--overlay` at 60% black instead of 32%, so floating surfaces and modal backdrops separate from the near-black page; light values are unchanged. Migration: redefine them under `[data-theme="dark"]` to keep the former, nearly invisible shadows and veil.
- **OKLCH notation.** Every color token and component color is written in OKLCH, and every `color-mix()` interpolates in `oklch`. `--background` reads `oklch(100% 0 0)` instead of `#fff`, and the shadows and overlay use `oklch(0% 0 0 / …)` instead of `rgb(0 0 0 / …)`; these values are exact equivalents. Mixes with `transparent` render identically. Only the `ThemeToggle` outline changes, as it mixes text and background: it is imperceptibly different in the light theme and slightly darker in the dark theme. Migration: scripts that read a token with `getPropertyValue()` receive the OKLCH string.
- **Overlay tokens.** `--overlay` (the translucent veil behind modal surfaces) and `--overlay-blur` (the blur of the page seen through it) hold the `Dialog` backdrop values; `ChatComposer`'s fade uses the same blur. Workspace modals and translucent bands use them.
- **Half-crosses.** `.half-crossed` marks the four corners of any element with accent half-crosses, drawn as background layers. The default `Button` variant and the focus of text fields and select share this drawing instead of separate copies; their rendering is unchanged.
- **Focus color.** `--focus-color` is renamed `--focus`: like `--border` beside `--border-width`, the bare name holds the color of the focus group. Its value is unchanged. Migration: replace `var(--focus-color)` with `var(--focus)`. Workspace apps are migrated.
- **Motion tokens.** Durations are named by function: `--state-duration` (`120ms`) for interaction state changes and `--motion-duration` (`220ms`) for positional transitions, with `--motion-easing`. `--duration-normal` (`160ms`) is merged into `--state-duration`, so `HeaderNav` and `ThemeToggle` color changes and the site's artwork arrow take `120ms`. Migration: replace `--duration-fast` and `--duration-normal` with `--state-duration`, `--duration-slow` with `--motion-duration` and `--ease-standard` with `--motion-easing`. Workspace apps are migrated.
- **Inset focus.** `--focus-inset-offset` (`-3px`) holds the inner focus clearance that `Card`, `Cards`, `Grid`, `HeaderNav`, `ChatThread` and `Tabs` used as local values; rendering is unchanged.
- **Radius tokens.** `--radius-small` and `--radius-medium` are removed: each served a single component, and `ChatMessage` and `ChatComposer` now keep their `0.75rem` and `1rem` corners locally. `--radius-full` remains the shared capsule. Migration: replace `var(--radius-small)` with `0.75rem` and `var(--radius-medium)` with `1rem` in app styles. Workspace apps never used them.

### Document defaults

- **Cascade layers.** A `formatted` layer between `base` and `utilities` holds the `.formatted` rules, and [cascade layers](../foundations/document.md#cascade-layers) define the question each layer answers. A class placed on an element inside formatted content, such as `.framed` on a code block, now overrides the formatted styles instead of losing to their more specific selectors. `.feedback` and `.visually-hidden` move from `base` to `utilities`, beside the classes they belong with; `.feedback` shares the rules of `.metadata`. Unlayered app styles still override every layer, so the rendering of workspace apps is unchanged.
- **Lists.** The reset removes the native markers of `ul` and `ol`, which hung outside lists left without padding; formatted content draws crosses and restores the numbers. Migration: set `list-style` on lists outside `.formatted` that should keep native markers. Workspace interface lists drop their local `list-style: none`.
- **Hidden attribute.** `hidden` hides its element even when a class, component or app style sets `display`; an element with `hidden` and `.stack`, for instance, formerly stayed visible as a flex container. `hidden="until-found"` keeps its native behavior. Migration: to show an element that carries `hidden`, remove the attribute instead of overriding `display`. The DS contents band drops its local `[hidden]` rule.
- **Dialog margins.** The reset keeps the native auto margins of `dialog`, so a native modal without component styles is centered in the viewport instead of pinned to the top-left corner. `Dialog` and `ChatWindow` set their own margins and are unchanged.
- **Text size adjustment.** The root sets `text-size-adjust: 100%`, so iOS Safari no longer enlarges text when a phone turns to landscape.

### Native forms

- **Search fields.** The clear button of a search field draws a thin cross in `--text-subtle`, turning `--accent` on hover, instead of the browser's icon and color. Migration: set `appearance: auto` and `background: none` on `input[type="search"]::-webkit-search-cancel-button` in app styles to restore the browser's button.
- **Native form controls.** Select and text fields use the `--background-prominent` fill without a visible border, and focus draws accent half-crosses at their corners instead of an outer outline, following `:focus-within` so date and time fields keep them on their internal segments and picker buttons; read-only text fields drop the fill. Checkbox and radio replace their native appearance with a 17px box: `--border-prominent` boundary, below the 3:1 contrast of a control boundary, accent square or dot when checked, thin ring for keyboard focus. `ChatComposer`'s focus turns its frame to the focus color instead of drawing an outer outline. Date and time inputs share the text field skin, fieldsets drop their native frame, and native widgets follow the active theme through `color-scheme`. Forced-colors mode keeps system indicators. Migration: in app styles, restore `border-color: var(--text-muted)` on fields, or `appearance: auto` with `accent-color` on choice controls, to keep the former look. Workspace apps adopt the new defaults.
- **Native form messages.** `.feedback` uses metadata typography (mono, uppercase, metadata size and tracking) instead of body text, and untoned feedback takes `--text-muted` instead of the inherited color. It now also carries field help and validation errors: `.field-help` and `.field-error` are removed, since they duplicated untoned and error-toned feedback. Migration: replace `class="field-help"` with `class="feedback"` and `class="field-error"` with `class="feedback" data-tone="error"`, and restore body typography on `.feedback` in app styles to keep the former look. Workspace apps are migrated.
- **Labels, legends and select.** Every `label`, `legend` and `select` takes the complete interface role, uppercase included, wherever it sits: the typography of `.ui` without the class. Labels beside a checkbox or radio follow it, and select no longer inherits from its context. Text fields keep body typography. `.field-label` is removed, since the element default replaces it; labels lose its `--text-prominent` color and its source casing. Migration: remove `class="field-label"`; a `.ui` class on a select is now redundant. To keep the former look, set `color: var(--text-prominent)` and `text-transform: none` on `label` and `legend`, or `font: inherit`, `letter-spacing: inherit` and `text-transform: none` on `select`, in app styles. Workspace apps are migrated.
- **Input zoom.** Text fields and the `ChatComposer` textarea keep at least `1rem` under `(pointer: coarse)` instead of below `--narrow`, since touch browsers zoom on focus regardless of viewport width; a narrow desktop window keeps the body size. Select gains the same floor over its interface size, so it shows 16px instead of 11.4px on touch devices. Migration: set `font-size: var(--ui-font-size)` on `select` in app styles to keep the interface size.

### Layout

- **Breakpoints.** `--narrow` is `(width < 48rem)` and `--wide` is renamed `--medium`, `(width < 70rem)`. Both thresholds follow the root size in `rem` and exclude their boundary: 768px and 1120px at a 16px root, instead of the inclusive 760px and 1100px. Use `not (--medium)` for the range above. Migration: replace `(--wide)` with `(--medium)` and check layouts at the new boundaries. Workspace apps are migrated.
- **Spacing.** `--space-8` (16 units, 64px at a 16px root) extends the scale.

### Token names

- **Naming rule.** Functional tokens name the role first, then the property, as described in [Tokens](../foundations/README.md#naming); values are unchanged. Migration: replace `--{heading,body,ui,metadata}-weight` with `--{role}-font-weight`, `--heading-{1..6}-size` with `--heading-{1..6}-font-size`, `--section-number-weight` with `--section-number-font-weight`, `--cross` with `--cross-size`, `--shadow-control` and `--shadow-floating` with `--floating-control-shadow` and `--floating-shadow`, and `--layer-floating-control`, `--layer-floating` and `--layer-skip-link` with `--floating-control-layer`, `--floating-layer` and `--skip-link-layer`, and `--text-primary` and `--text-secondary` with `--text` and `--text-subtle`, matching the default and subtle levels of the background and border colors. Workspace apps are migrated.

### Configuration

- **Configuration factories.** `createViteConfig`, `createSvelteConfig` and the shared compiler options move from the repository's `tooling/` into the package as `@dg/ui/config/vite.js`, `@dg/ui/config/svelte.js` and `@dg/ui/config/tsconfig.json`; behavior is unchanged. The package now declares the PostCSS plugins the Vite factory uses and resolves `breakpoints.css` itself, so an app's build depends only on its own directory and `@dg/ui`. Migration: replace `../../tooling/vite.js`, `../../tooling/svelte.js` and `../../tooling/tsconfig.json` with these package paths. Workspace apps are migrated.
- **SvelteKit 3.** Workspace apps move to SvelteKit 3, Vite 8, `@sveltejs/vite-plugin-svelte` 7 and `adapter-static` 4, which require Node.js 22.17 or later. SvelteKit 3 no longer reads `svelte.config.js`, so `@dg/ui/config/svelte.js` is removed and `createViteConfig(sveltekit, adapter)` passes the adapter and root-relative asset URLs to the SvelteKit plugin. `@dg/ui/config/tsconfig.json` now only keeps type checks non-strict, which TypeScript 6 no longer does by default; SvelteKit's `$app/tsconfig` provides the other options. Migration: delete `svelte.config.js`, import the adapter in `vite.config.js` and pass it as the second argument; in `jsconfig.json`, extend `["$app/tsconfig", "@dg/ui/config/tsconfig.json"]` with `"include": ["src", "*"]`; replace `$lib/` imports with `#lib/` and declare `"imports": { "#lib/*": "./src/lib/*" }` in the app's `package.json`. Workspace apps are migrated.

- **Browser test configuration.** `createPlaywrightConfig({ port, server })` from `@dg/ui/config/playwright.js` runs a package's `tests/*.spec.js` with Playwright against one app's dev server on a dedicated port, with the shared viewport, workers and failure traces. Each workspace app and `@dg/ui` keep their tests in their own `tests/` directory; Node tests (`*.test.js`) run with `node --test`, and `@dg/ui` tests its components on the DS pages. The repository's `tests/ui` suite, `tooling/playwright.js` and `tooling/playwright-toolkit.js` are removed. Migration: add a `playwright.config.js` and a `test:ui` script as in the boilerplate. Workspace apps are migrated.

### Removals

- **`GridFlicker`** is removed, with its `--grid-mask` override; it was an experiment, and the site's home hero no longer shows its dot field. Migration: remove `GridFlicker` imports and usages.
- **`.grid-bg`** is removed: no workspace app used the decorative background grid after `GridFlicker`. Migration: copy its two `linear-gradient` layers and `background-size` into the app's own styles.
- **Instrument Serif** is removed from `@dg/ui/fonts/*` and from the `--font-serif` stack, which now falls back from Teodor to the system serif. Migration: an app that names `"Instrument Serif"` must host the font itself. Workspace apps never used it.

### Documentation

- **Foundation references.** Each foundation subject is one page in `docs/foundations/`, holding its token roles, classes and usage: colors, typography, spacing, stack and row, frames and surfaces, native forms, interaction and responsive. `tokens.md` keeps loading and override guidance; `foundations/frames.md` becomes `foundations/surfaces.md`, and native form guidance moves from `foundations/css.md` to `foundations/native-forms.md`. Component pages order their sections as they are read: Usage, Notes, Parameters, Overrides, Methods, Accessibility. CSS and the public API are unchanged. Migration: update links to former `tokens.md` anchors and `foundations/frames.md`; the DS no longer redirects former gallery, token and frame URLs.
- **Sections.** Each documentation folder is a section whose `README.md` lists its pages in reading order: `getting-started/`, `foundations/`, `components/`, `guides/` and `maintain/`. `quick-reference.md` moves to `getting-started/quick-reference.md`, `guides/new-app.md` to `getting-started/new-app.md`, `guides/composition.md` to `getting-started/composition.md`, `foundations/css.md` to `getting-started/css.md`, `tokens.md` to `foundations/README.md` and `changes.md` to `maintain/changes.md`; the root `README.md` links to the sections. DS URLs drop the `/docs` prefix and mirror these paths, such as `/components/button` and `/foundations`. CSS and the public API are unchanged. Migration: update links to the moved files; former `/docs/...` URLs are not redirected.
- **One owner per subject.** `getting-started/css.md` becomes `foundations/document.md`, covering cascade layers, native element defaults and focus; its export table moves to `getting-started/new-app.md`, and font loading to `foundations/typography.md`. `foundations/layout.md` becomes `foundations/stack-and-row.md`. The maintenance process moves from `maintain/format.md` to `maintain/evolution.md#maintenance-process`, and DS rendering conventions to the DS README. `new-app.md` explains the boilerplate's files instead of reproducing them, and `composition.md` drops its component table in favor of the quick reference. Section indexes describe each section without repeating its pages, and guides group into patterns and process. CSS and the public API are unchanged. Migration: update links to the moved files and anchors; former URLs are not redirected.
- **Editorial recipe.** `guides/recipe-editorial.md` and its DS page are removed: `components/heading.md` and `foundations/typography.md#editorial-content` already document the composition, including `.formatted.prose.constrained-width`. CSS and the public API are unchanged. Migration: link to those references instead; `/guides/recipe-editorial` is not redirected.

## 0.3.0

Breaking simplification of the public contract; not published by this change. All workspace apps are migrated.

- **Fonts.** Reading text uses Roboto and interface text Roboto Mono, both self-hosted variable fonts under the SIL Open Font License; Ioskeley Mono and the bundled license files are removed.
- **Serif.** Headings use Teodor (variable) at weight 100 with `--heading-letter-spacing: -0.07em`.
- **Readability.** Body text is 16px at weight 200, interface text 12px and metadata 11px. Interface tracking is `--ui-letter-spacing: 0.1em`, applied by components instead of local values. `--font-size-xs` is removed; the scale is `--font-size-sm`, `--font-size-md` and `--font-size-lg`.
- **Type scale.** `--font-size-base` defaults to `1rem` and is no longer a registered property. Migration: set local bases in `rem` or `px`; a consumer with a container-relative base registers the property itself (see [type scale](../foundations/typography.md#type-scale)).
- **Contrast.** Select, text fields and `ChatComposer` outline with `--text-muted` (at least 3:1). `--accent-subtle` derives from `--accent`; feedback colors use oklch with unchanged values.
- **Frames.** Direct frames of `Page` and `Main` drop their bottom rule; the next frame's top rule is the shared line. A cross erases the lines around it, so neighboring crosses coincide without coordination. Migration: replace `style="border-bottom: none"` on a preceding frame with `border-top: none` on the following one; remove styles that targeted the former `::before` frame drawing.
- **`Ticker`** is removed.
- **Exports.** `CloseButton`, `FullscreenButton`, `ControlIcon`, `HeaderNav`, `InfoBlock`, `ChatContext`, `ChatControls`, `ChatHeader`, `ChatLauncher`, `ChatNotice`, `ChatSendButton` and `ChatWelcome` are no longer exported. Migration: use `IconButton icon="close"` for `CloseButton`; write an empty state, context row or notice as app markup inside `ChatPanel`; `ChatWindow` keeps its own launcher, header and controls.
- **`Tabs`** is now a WAI-ARIA tabs widget that switches panels within the page: `items` take `{ value, label, panel }` and `value` is bindable. Migration: page navigation styled as tabs uses native links instead.
- **Language.** `Page` takes `lang` (`en` or `fr`); every built-in label follows it. `SkipLink` is placed first inside `Page`. Removed props: `Header` `menuLabel`, `closeLabel`, `themeLabel`, `themeLabels`; `ThemeToggle` `label`, `lightLabel`, `darkLabel`; `Cards` `scrollLabel`, `previousLabel`, `nextLabel`; `Ticker` `pauseLabel`, `resumeLabel`; `ChatComposer` `sendLabel`, `stopLabel`; `ErrorPage` `lang`. Content-naming props such as `label` and `placeholder` remain as overrides.
- **Theme.** The outermost `Page` restores the saved theme before first paint. Migration: delete the theme script from `app.html`.
- **Fields.** Text inputs and textareas share the native field skin by default; the `.field` class is removed. `Section`'s `horizontal`, `vertical` and `no-top` padding presets are removed.
- **Responsive.** Breakpoints are `--narrow` (760px) and `--wide` (1100px); `--compact` and `--medium` are removed. `Grid` switches to one column from its own width (40rem) through a container query.
- **Component behavior.** `Button` keeps its natural width inside stacks. `Grid` equalizes row heights only with several columns, and its `divided` lines replace a `Card` cell's border. `ChatComposer` keeps the draft focused and read-only while `pending`, and returns focus to it after a stop.
- **Cascade.** `base.css` uses the `reset`, `base` and `utilities` cascade layers; unlayered component and app styles always override them.
- Every component forwards native attributes, DOM callbacks, `class` and `style` to its root element, except the names it owns. `SectionHead` no longer shows a default number.

## 0.2.1

Contract hardening; not published by this change.

- Forwarded attributes use native Svelte HTML types rather than accepting every property name. Consumer tests cover all eleven forwarding components, supported HTML attributes, invalid values and ignored content. `Tabs`' JSDoc annotation is corrected.
- Class names remain strings. `Header`, `Footer` and `Tabs` do not accept unrendered children; `Heading` keeps ownership of children and gap while forwarding `Section`'s padding and crosses. Replace invented attributes with documented props or appropriate data attributes; remove content these components never rendered.
- Generated agent context resolves prose links directly to Markdown or repository sources. Code examples and raw Markdown endpoints stay unchanged.
- `pnpm check:toolkit` runs contract checks without dev servers or browser engines. The canonical Markdown index alone owns current component grouping; legacy gallery URLs remain compatible.
- The repository's Node engine declaration matches its documented prerequisites. An independent editorial, collection and conversation evaluation protocol describes how to measure agent autonomy without treating automated checks as accessibility certification.

Existing documented runtime behavior and visual defaults are unchanged. Types now report previously unchecked unsupported usage; read each reference's Notes before forwarding attributes.

## 0.2.0

Development baseline; not published by this change.

- Spacing derives from one base unit while retaining the previous default scale. Shared radius, focus, disabled, short-motion, elevation and feedback roles are documented.
- Native text fields, field labels, help, errors and feedback have an opt-in CSS contract. Existing square frames and circular drawing geometry remain intact.
- `ThemeToggle` adds `label`, `lightLabel` and `darkLabel`. `Header` and `HeaderNav` forward `themeLabels`; `Cards` adds `scrollLabel`, `previousLabel` and `nextLabel`. English defaults and existing call sites remain compatible.
- The registered root typography base resolves from `1em`, preserving its default size while allowing WebKit to follow root text enlargement. Scoped bases still resolve once before inheritance.
- Public documentation includes composition recipes, native checkbox/radio guidance, evolution rules and an agent workflow. The DS generates agent-readable context from the canonical references and CSS rather than a separate editable catalogue.
- Toolkit checks cover documentation, APIs, consumers and critical behavior on Chromium, Firefox and WebKit. Manual screen-reader and real browser zoom review remain required where relevant.

No consumer migration is required for these optional additions. Translate labels in an application when its audience needs it. Recheck unusual local CSS overrides against the documented spacing and focus contracts before adoption.
