@nadicodeai/design-system 12.1.110 → 12.1.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md CHANGED
@@ -2,9 +2,11 @@
2
2
  version: alpha
3
3
  name: nadicode Design System
4
4
  description: >-
5
- nadicode's design system and its only design authority: the tokens and the
6
- rules every component, website section, and product screen in the website,
7
- the Portal, the Nadia desktop app, and the Alveare desktop app builds on.
5
+ nadicode's design system and its design authority for tokens, visual
6
+ direction and the rules every component and page shares: every component,
7
+ website section, and product screen in the website, the Portal, the Nadia
8
+ desktop app, and the Alveare desktop app builds on it. A component's own
9
+ contract is the usage page in its source in @nadicodeai/ui.
8
10
 
9
11
  colors:
10
12
  action: "#111111"
@@ -522,7 +524,7 @@ spacing:
522
524
 
523
525
  ## Overview
524
526
 
525
- This file is nadicode's design system and its only design authority. The tokens above are the values; the sections below are the rules for applying them. Every component in `@nadicodeai/ui`, every website section, and every screen in the Portal, Nadia and Alveare builds on it. Another document may say how one product applies a rule, and links here for the rule; it never restates one. A rule that is missing here is added here first.
527
+ This file is nadicode's design system and its design authority for tokens, visual direction and the rules every component and page shares; a component's own contract is the usage page in its source in `@nadicodeai/ui`. The tokens above are the values; the sections below are the rules for applying them. Every component in `@nadicodeai/ui`, every website section, and every screen in the Portal, Nadia and Alveare builds on it. Another document may say how one product applies a rule, and links here for the rule; it never restates one. A shared rule that is missing here is added here first; a rule of one component is added to its usage page.
526
528
 
527
529
  nadicode puts AI Agents to work inside small and mid-sized companies. Its sites and products read as a technology company with a real product: the interface is neutral and exact, the product is shown working, and colour and depth give the page its energy. Three ideas carry every screen:
528
530
 
@@ -614,7 +616,7 @@ Every chart and KPI card is a stock shadcn registry component owned by `@nadicod
614
616
  - A multi-series chart has a legend below the plot, left-aligned and wrapping: a round dot and the entity name, in series order.
615
617
  - A signed comparison keeps a visible zero line so losses and gains share one baseline, and colours each bar by its consequence: `success` where the value is favourable, `destructive` where it is not.
616
618
  - A chart is a named section: its card title is the heading that names the plot.
617
- - Every chart card has one header, the same on every page (`ChartCard`): its title and its period on the left ("Usage", "Per day · today is partial"), its controls, such as the metric switch, on the right, then the way to its owner as a small ghost link ending in a chevron, as OpenRouter's "Explore ›" ("Explore", or "Open in Analytics" when the owner is another page), which stays top right on a phone. The period's choice sits on the page's title row where the page has one. The footer holds the reading time on the left, "Read at 12:42", and at most a command on the reading on the right, such as Refresh. A daily axis labels every second day from the first, fewer in a half-width card, and the y axis five round ticks. A ranked list beside a chart is one line per entity (place, colour, name, share, figure), so it never stands taller than the chart; a category axis is as wide as its longest name up to 128 px, a longer name wrapping to a second line; a short window's activity calendar draws larger cells and shares its row with a sibling card, never a lone narrow card.
619
+ - Every chart card has one header and footer, the same on every page: `ChartCard`, whose usage page holds them. The period's choice sits on the page's title row where the page has one. A daily axis labels every second day from the first, fewer in a half-width card. A ranked list beside a chart is one line per entity (place, colour, name, share, figure), so it never stands taller than the chart; a category axis is as wide as its longest name up to 128 px, a longer name wrapping to a second line; a short window's activity calendar draws larger cells and shares its row with a sibling card, never a lone narrow card.
618
620
 
619
621
  No pie, donut, radar or dual-axis chart, and no mark or logo inside a legend. The one ring is `Gauge`, a control for a single ratio out of 100 against a fixed maximum, not a chart.
620
622
 
@@ -634,8 +636,7 @@ A hue follows its entity, never its rank or position: one assignment per surface
634
636
 
635
637
  ### Meters, deltas and labels
636
638
 
637
- - **Meter**: the stock `Progress` in one tone, default, secondary, success, warning or critical, with an accessible name and the real value printed beside it; `stops` mark genuine stages, each named. Over its cap it is a full bar in the critical tone; the printed figure states the overage.
638
- - **Gauge**: a ring for one ratio out of 100 where the comparison is the point (a hit rate, an uptime), in one product-chosen tone from the same set, with an adjacent label that names the measure and carries the unit. Its figure is the bare number, never a percent sign. It has no indeterminate form: an unknown wait is the waiting line.
639
+ - **Meter** and **Gauge**: `Progress` for consumption against a cap and `Gauge` for one ratio out of 100 where the comparison is the point; their usage pages hold their tones, stops, overage and figure. Neither has an indeterminate form: an unknown wait is the waiting line.
639
640
  - **Delta**: an outline Badge with a trend icon (`TrendingUp`, `TrendingDown`, `Minus`) and the signed value, followed by a muted "vs {period}". Direction crossed with whether up is good sets the tone; a neutral direction stays neutral. Never bare signed text, colour alone, or a ▲▼ glyph. A figure with no comparison shows no badge.
640
641
  - **Labels**: legend, axis and label text are ink roles and never wear the series colour. A single series is titled, not legended. Tooltips enhance a chart and never hold the only copy of a value; where a page needs the numbers as text, it shows a real table built from the same data.
641
642
 
@@ -735,7 +736,7 @@ Control geometry is the same in every product:
735
736
  | `{spacing.icon-stroke}`, `{spacing.icon-stroke-sm}` | The icon line ("Iconography") |
736
737
  | `{spacing.focus-outline-width}`, `{spacing.focus-outline-offset}` | The focus outline |
737
738
 
738
- Product chrome uses the 36 px control. Compact controls use 32 px and prominent controls 40 px; an embedded text button inside a field is 24 px (`InputGroupButton` `xs`); an embedded icon button fills the field's row and sits flush against its edge, except the combobox's open-list button, which is 30 px high and centred at the default size and fills the row at 32 and 40 px, as in Geist. A page header, a toolbar, a card's header and footer, a filter row and a collection row's actions take `{spacing.control-height}` with a `{typography.button}` label, and so does the three-dot trigger, wherever it stands; a button's `sm` and `xs` sizes are for a control nested inside dense content, such as a notice's or a banner's one action, a descriptive item's trailing controls, a composer or an input group, and never for chrome. A `SegmentedControl` has options 24, 28 or 32 px high (`{spacing.control-height-xs}`, 28 px, `{spacing.control-height-sm}`) inside a 4 px padded track, so the control itself is 32, 36 or 40 px high like every other control.
739
+ Product chrome uses the 36 px control: a page header, a toolbar, a card's header and footer, a filter row, a collection row's actions and the three-dot trigger take `{spacing.control-height}` with a `{typography.button}` label. Compact controls use 32 px and prominent controls 40 px; a button's `sm` and `xs` sizes are for a control nested inside dense content, such as a notice's or a banner's one action, a descriptive item's trailing controls, a composer or an input group, and never for chrome. A control nested in a field, and `SegmentedControl`'s options, keep the same outer heights; their usage pages hold how.
739
740
 
740
741
  ### The website column
741
742
 
@@ -757,25 +758,14 @@ The Portal, Nadia and Alveare are product shells: persistent navigation on the l
757
758
  | Getting the app | Above the account | Downloads, an outlined row | The update card, only when there is one | The update card |
758
759
  | The page's actions | The page's title row | Secondary, primary, the three dots | The window header's trailing edge: the Agent's three dots, after the chat switcher | The channel header's trailing edge |
759
760
 
760
- - **The sidebar swaps, and is never joined by a second.** Inside an object, the same column shows a back link, what you are in (its identity mark at 24 px and its name) and its sections, and the top bar keeps the path (`SidebarSections`). No rail, second column or tab row of an object's sections appears beside it. In the Portal an Agent is the exception, because she is a deployment: the sidebar stays on the places, and her sections stand as the `TabsNav` band under the top bar, as Vercel draws a deployment's tabs, at most six (Overview, Scheduled tasks, Usage, Capabilities, Messaging, Settings); a tab that holds several sections opens on the first and shows them as line tabs at the top of its content. Her Overview is her `ObjectPage`: her facts, then rows of checks, each summed up by `CheckCounts` and closed by a `CheckState`.
761
+ - **The sidebar swaps, and is never joined by a second.** Inside an object, the same column shows a back link, what you are in (its identity mark at 24 px and its name) and its sections, and the top bar keeps the path (`SidebarSections`). No rail, second column or tab row of an object's sections appears beside it. In the Portal an Agent is the exception, because she is a deployment: the sidebar stays on the places, and her sections stand as the `TabsNav` band under the top bar, as Vercel draws a deployment's tabs, at most six (Overview, Scheduled tasks, Usage, Capabilities, Messaging, Settings); a tab that holds several sections opens on the first and shows them as line tabs at the top of its content.
761
762
  - **An area whose pages are siblings opens as a sub-menu, as Vercel's sidebar does.** Vercel marks such a row with a › at its end ("Logs", "Observability", "Firewall", "Settings"), and its press replaces the places with one row, a ‹ at its start and the area's name centred, above the area's pages as words alone, the open one filled like a selected row; a row that only opens a page has no ›. In the Portal these areas are Settings (nadicode), Company (whoever manages a company) and Library (everyone who sees it: Agent templates, Skills, Plugins), each with its › row, and Account, opened from the account menu; their pages are never tabs (`SidebarSections` without an identity, its row ending in `SidebarMenuChevron`). “Tabs imply the views share scope, URL parent, and data model” (https://vercel.com/geist/tabs): Library's pages hold different objects with different lists and different ways to add, so they are a sub-menu, as Vercel's AI Gateway is. A destination whose pages are views of one reading, Analytics, keeps its tabs, as Vercel's Analytics and OpenRouter's Activity do. The ‹ returns to where the person opened the area from.
762
763
  - **The top bar holds only the path, never an action.** It names where the open page sits, ending at the place or the object, and never the page's own title; an object's own status sits beside its name in the path and nowhere else on the page. A top bar that only says where you are cannot be mistaken for a toolbar.
763
764
  - **Find goes to places.** It opens from the outlined field at the top of the sidebar and on ⌘K, and names every sidebar place, chat and settings page; it never runs a command. Its field names what it searches, never a bare "Search"; ⌘K or Ctrl+K opens it and no other field takes that binding; a shortcut on a row is a `CommandShortcut`; and a polite live region reads how many results remain as the person types.
764
765
  - **The account lives at the sidebar's foot.** `AccountMenu` names who is signed in and for whom, opens the account's settings from its gear, and holds Theme, Language and Sign out; the offer to get or update the app stands just above it.
765
766
  - **Navigation names stable destinations.** An object opened from search or a link still shows its parent in the path; a return link remembers a visit and never replaces the parent. A scope selector keeps the current section when the next object has it, and otherwise opens its overview.
766
767
  - **Compact screens keep the same relationships.** Tabs scroll horizontally, primary content stacks before supporting facts, and no second sidebar appears.
767
- - **The desktop window has one top band.** In Nadia, the window's header row and the left column's top band are one 44 px band that holds the shared chrome controls and centres the macOS window buttons; the Portal keeps its own top bar.
768
-
769
- **Nadia's window** sorts every thing by what it belongs to, in four scopes: the company (what every Agent here shares), one Agent (her work and her setup), one chat (what is happening now) and the person on this computer (how the app behaves). The company's pages of work, Wiki and Skills, sit in the list; what it sets once, its Memory, Mail and Alveare, sits in Settings beside this computer's settings.
770
-
771
- - **The list at rest** holds, top to bottom: Find; Scheduled tasks; the company's Wiki and Skills; the group "Agents", Nadia first, then by name ("Agent orbs"); then the update card and the account. Nothing else, and no chats under an Agent: her chats are in the switcher by her name.
772
- - **The company's pages**, Wiki and Skills, are rows under Scheduled tasks at the top of the list, each its own page with its own title and its actions on that title's row, never tabs under the company's name.
773
- - **Scheduled tasks** is a place: one page of every Agent's scheduled work, each row with its Agent's orb, its schedule and its last or next run, failures first, filtered by Agent, with New task on the title row; a task opens for editing in its row. The row carries a red count of the tasks that failed (`SidebarMenuBadge tone="attention"`), so the person sees a failure before looking for it.
774
- - **⋯ → Edit** stays as it is: it swaps the list to the Agent's Profile, Skills, Apps, MCP, Messaging, Plugins and Passwords. The chat switcher by her name stays as it is.
775
- - **Settings** is what is set once rather than worked in: how the app behaves on this computer (its language, look, notifications, services and updates) and the company's Memory, Mail and Alveare. It opens from the account menu's gear and swaps the list to a back link to the Agents and its sections, each its own page: General, Appearance, Notifications, and the company's Memory, Mail and Alveare, then Updates; the account menu holds the gear in its header, then Usage (only once billing has answered with its page), Theme, Language and Sign out, and never a second Settings row.
776
- - **The window header** is the Agent's title row: her orb and name, the chat switcher, then, on a chat, the side panel's toggle, and her three dots last (New chat, Edit, Open in Portal, Delete).
777
- - **The side panel is opened, never pushed.** On a chat, the header's toggle opens the window's right side on the panel the person last chose: the Browser, or Artifacts, the images, documents and links in the Agent's chats, as Hermes names them, newest first, one opening large in their place. Its 44 px band names the panel, then the switch between the two, then ×; while she browses behind Artifacts or with the panel closed, the toggle and the switch's Browser carry a dot, and nothing moves by itself. × or Esc closes it and gives focus back to the toggle, while a page she is browsing keeps loading behind. Opened, it takes 44% of the window, held between 28rem and 48rem, and yields first: by its drag edge it narrows to 352 px and widens only as far as leaves the Agents list and 360 px of chat; under 1024 px it lies over the chat at the same width, without a drag edge. It is not a `Sheet`: it never blocks the chat, takes focus or dims the page.
778
- - **The list narrows.** From 640 to 899 px it becomes a column of 48 px squares: Find, Scheduled tasks, Wiki and Skills, then the orbs, each named with its state line in a tooltip; under 640 px it leaves for a sheet the header's sidebar button opens.
768
+ Nadia's window, its list, its Settings and its side panel are Nadia's register ([Nadia's design doctrine](../../apps/nadia/docs/design-doctrine.md#the-window)).
779
769
 
780
770
  **Alveare** keeps the same arrangement in Buzz's components: places in the sidebar, Inbox first; the channel header names the channel and holds only the channel's own controls; the update card and the package's `AccountMenu` stand at the sidebar's foot; the top band keeps only the sidebar toggle.
781
771
 
@@ -785,17 +775,19 @@ A product page is one of five page compositions, each answering one question fir
785
775
 
786
776
  | Composition | Owner | First answer | Structure |
787
777
  | --- | --- | --- | --- |
788
- | Overview | `OverviewPage` | What needs attention? | The period on the title row; the figures in one row of `KpiCard`s; the chart cards, each with its one header; then the exceptions that need a person, a `RecordList` with its heading, each row opening its owner. Never a stack of previews of every section. |
789
- | Collection | `CollectionPage` | Which record? | The title with the page's actions on its row and nothing under it; the page's sibling collections as line tabs, when it has them; one toolbar (search, filters, sort); then one line per record (`RecordList`, "Tables and missing values"). One row is one hover and one open target. The Portal's Agents are its exception, because an Agent is a deployment: they are Vercel's Deployments list, a `DataTable` with `rows="entities"` under the same title row and toolbar: no headings, one 72 px entity row per Agent (her orb at 32 and her name over her model, or over why she needs a look when she does; her status as a `StatusDot` and its word; her Customer as a `Tag`; her Installation in mono with its system's mark; when she last synced). Below the width those columns need, her status, her Customer, her Installation's name and that time stand in the one muted line under her name, so two Agents of one name on two machines stay told apart on a phone. A collection whose reader must also see what waits on them across all its records, as nadicode's Customers and Partners and a Partner's Customers do, takes a `rail`: under the toolbar, from 1280 px, a 300 px column of titled cards at the left of the records, as Vercel's Projects page stands Usage, Alerts and Recent Previews beside its projects; each card has its title above it at `{typography.heading-xs}`, an optional 44 px muted summary row whose one outline button stands at its end, never in a row of its own, then its items, 36 px for a figure (its title, its number at the trailing edge) and a 24 px mark over a title and a muted line that wraps to a second before it is cut, for a record, the item opening its record, or its `cleared` or `nothingYet` line, one 44 px row like an item (`EmptyState` `scope="line"`), never a tall empty tile. Beside a rail the records are Vercel's project rows (`RecordList` `layout="line"`), one line from a 448 px card: the 32 px mark and the name over its address, then the facts and the exception's status on one muted line joined by a middle dot ("5 of 5 Agents active · Partner Alpha"), left out first on a card under 576 px, then the time, the checklist ring and the three dots, which keep even a lone command; only on a phone do the facts and the time move under the name, as one muted line joined by middle dots in the same row, with no labels and no rule between them. Below 1280 px the records come first and the rail follows. The records' card takes no heading of its own: the page's title names it. |
790
- | Object | `ObjectPage` | What is this, what can I do? | The parent in the path and its sections in the sidebar; then the object card, as Vercel's Deployment Details: a header bar with its name and, at the end, one secondary action, then the three dots, never a primary; under it its identity mark at 64 px centred in a 300 px tile on the muted ground, and beside it one optional line saying what it is and its key facts in a grid of four columns (two on phones), each a muted label over its value; then, in a card of its own 12 px under it, the rows that open one by one, chevron first and summary at the end (`CheckCounts`, `CheckState`, whose pass is a blue check); then its sections, each a card only where it owns a decision. An Agent is the exception: her first section, Profile, opens with her card, the same in every product (`AgentProfileCard`): on the left her orb at 64 px, her name with a ghost pencil that turns her name, description and, where the product holds it, her look into fields in place, saved by the card's own Save, with no drawer or overlay, her description, read whole and never cut, and her facts (status, last activity, the template she was made from); on the right her model, drawn with its maker's mark, her reasoning and, where the product holds them, her permissions, saved from the card's footer; her instructions follow as the next card. The page's commands sit in the window's three dots where the product has one, and otherwise at the end of her name's row. |
791
- | Settings | `SettingsPage` | What can I change? | The page's title row, then section cards across the content width 24 px apart, each with its title and one-line description (the card's only help) and a label above each control; a card of choices applies each as it is made, and a card of typed values has its own Save in its footer. | The danger zone closes the page: transfer, pause, delete.
792
- | Library | `LibraryShelf` for Agent templates; `CollectionPage` for Skills and Plugins | What reusable thing can I use? | Agent templates as Vercel's Templates gallery: the search, then `AgentCard`s three to a row. Skills and Plugins as an entity collection: the search and the category filter, then one row per entry (its picture or its kind's tile, its name over one line, who it is for and which Agents have it, Add, the three dots), staff's hub beside them as the collection's rail; detail shows content, requirements, source and revision, with Add as its filled primary. |
778
+ | Overview | `OverviewPage` | What needs attention? | The period, the figures, the chart cards, then the exceptions that need a person. Never a stack of previews of every section. |
779
+ | Collection | `CollectionPage` | Which record? | The title and the page's actions, the sibling collections as line tabs, one toolbar, then one line per record ("Tables and missing values"); one row is one hover and one open target. A collection whose reader must also see what waits on them across its records takes a `rail`. The Portal's Agents are its exception, a `DataTable` of entity rows, as Vercel's Deployments list. |
780
+ | Object | `ObjectPage` | What is this, what can I do? | The parent in the path and its sections in the sidebar, then the object card, then its sections, each a card only where it owns a decision. An Agent's first section, Profile, opens with her `AgentProfileCard`, the same in every product. |
781
+ | Settings | `SettingsPage` | What can I change? | Section cards, each saving its own fields; the danger zone closes the page. |
782
+ | Library | `LibraryShelf` for Agent templates; `CollectionPage` for Skills and Plugins | What reusable thing can I use? | Agent templates as Vercel's Templates gallery; Skills and Plugins as an entity collection, with staff's hub as its rail. |
783
+
784
+ Each composition's usage page holds its slots, geometry and states.
793
785
 
794
786
  The pages share the parts below them, each with one owner: one line per record is `RecordList`, which `CollectionPage`, an overview's exceptions and an object's "Needs attention" section draw; an absence is `EmptyState` ("Elevation & Depth"); an overlay is the owner its job names ("Menus and commands"); what happened is `ActivityFeed` ("Activity"); a record's mark is `IdentityMark` ("Identity marks").
795
787
 
796
- Every setting is that Settings composition, whether it belongs to the person, to the platform or to one record's Settings tab, so the column keeps its width and its field layout from one entry of a settings menu to the next, and each section saves its own fields from its own footer ("Cards and sections"). In the Portal the settings cards take the content's width under the page's title row, as every other page does and as Vercel's settings cards fill their column; Nadia keeps its settings column at most 42rem. The sections stand 24 px apart. Inputs are bounded at 20rem under their labels, and related fields may share two columns when the section can accommodate them. Section headings use heading-sm. The header and fields share one uninterrupted body; only the local save footer has a divider. A profile avatar is the upload button in the section header. Structured tables keep visible column names and remain tabular while their container can accommodate the data; only compact containers move supporting facts beneath the identity, and a matrix whose every column must stay, such as notification audiences by group, keeps them all and lets its names wrap instead.
788
+ Every setting is that Settings composition, whether it belongs to the person, to the platform or to one record's Settings tab, so the column keeps its width and its field layout from one entry of a settings menu to the next, and each section saves its own fields from its own footer ("Cards and sections"). A card's header and fields share one uninterrupted body, and only its save footer has a divider. `SettingsPage`'s, `SettingsSection`'s and `SettingsField`'s usage pages hold the column, the cards and the fields; Nadia keeps its settings column at most 42rem. A structured table keeps visible column names while its container can hold the data, and a matrix whose every column must stay, such as notification audiences by group, keeps them all and lets its names wrap.
797
789
 
798
- Collections and working pages open with the same header (`PageHeader`), which the product's chrome or the composition's `title` draws. Settings pages open directly with their section cards and retain a screen-reader h1 for the destination; an extra visible title would duplicate the navigation and delay the first setting. The top bar holds only the path to the page ("Product shells"): its sidebar group or its parents, ending at the destination or the object, such as "Business › Agents" or "Beta Studio › Beta Desk", and never the page's own title. The content then starts with the title at `{typography.heading-md}`, on the left, and on the same row at the trailing edge the page's actions: an overview's period first, then at most one `outline` secondary action, then the `default` primary action, then the three dots at the trailing edge. On a phone the title keeps one action on its row, as Vercel's does; the actions wrap under the title only when they would leave it less than 128 px. `PageActions` is their only owner: it takes a `period`, a `secondary`, a `primary` and a `menu`, and draws their order and variants. The menu is data, the record it acts on and its commands, drawn by `ActionMenu`, and a page's three dots open only for two commands or more; one command is a button. An action always sits on the title of the thing it acts on, never on a tab's. Navigation remains a visible link or part of the navigation, never an overflow command. Nothing sits under the title: no line introduces, explains or counts the page, and the toolbar, the list or the page follows the title row directly. Inside an object worked from a section sidebar, the title is the section's name ("Apps"); a page that opens with the object's own header or card, such as an Agent's Profile (`AgentProfileCard`), has it as its title and no title row above it.
790
+ Collections and working pages open with the same header, `PageHeader`, with the page's actions on the title's row through `PageActions`, whose usage pages hold their order, variants and wrapping. Settings pages open directly with their section cards and keep a screen-reader h1 for the destination; an extra visible title would duplicate the navigation and delay the first setting. An action always sits on the title of the thing it acts on, never on a tab's, and one command is a button, never a menu of one. Navigation remains a visible link or part of the navigation, never an overflow command. Under the title sits at most the one muted line saying what the page's records are for; nothing introduces, explains or counts the page. Inside an object worked from a section sidebar, the title is the section's name ("Apps"); a page that opens with the object's own header or card, such as an Agent's Profile, has it as its title and no title row above it.
799
791
 
800
792
  A collection is named once, by the page's title, and the collection's card never adds a header that only describes it. A populated collection carries no picture; its first use shows one, in every product ("Elevation & Depth"). The table's footer holds only a count or the paging, never a help sentence: guidance a step needs lives in that step.
801
793
 
@@ -834,18 +826,9 @@ The owner approved the materials on 18 October 2026: Vercel's surfaces with a li
834
826
 
835
827
  **Product views on the website** preserve the real interface. Nadia uses captures of the running application, with the unchanged full-size image linked and capture provenance recorded in the repository, following the founder's 12 October correction of invented UI. Portal and Alveare may use inert scenes built from real components with fictional example data recorded in the repository. Never invent interface controls or frame a view in a device mockup. A product window takes the raised height on its ground; a card that overlaps it floats. Live components use dark mode in a black section; captures preserve the application's captured theme.
836
828
 
837
- Absence follows its container, and only a first use has a picture. In every product, the Portal included, a collection's first use shows its section's picture, and every other kind of absence goes without one. A picture sits on a working screen only there: never in a populated collection, a row or any other card; whole-page moments keep theirs ("Entry screens"). A page or region absence is a resting card with its own edge, never a dashed boundary; an absence inside a card is unframed, because the card already owns the edge, and `CollectionPage` puts every absence it draws in its card. `EmptyState` (`@nadicodeai/ui/components/empty-state`) is the only owner of an absence: nothing else draws one (`empty-state-owner`), its `scope` (`page`, `region` or `card`) sets that frame, and it has no presentation option, so every product draws each kind the same way. A composition takes its absences as props (`empty`, `noMatch`, `failed`), as a `DataTable` takes `empty`. Its kind determines the cause it names, the mark or picture and the recovery:
829
+ Absence follows its container, and only a first use has a picture. In every product, the Portal included, a collection's first use shows its section's picture, and every other kind of absence goes without one. A picture sits on a working screen only there: never in a populated collection, a row or any other card; whole-page moments keep theirs ("Entry screens"). A page or region absence is a resting card with its own edge, never a dashed boundary; an absence inside a card is unframed, because the card already owns the edge, and `CollectionPage` puts every absence it draws in its card. `EmptyState` is the only owner of an absence: nothing else draws one (`empty-state-owner`), its `scope` (`page`, `region` or `card`) sets that frame, and it has no presentation option, so every product draws each kind the same way. A composition takes its absences as props (`empty`, `noMatch`, `failed`), as a `DataTable` takes `empty`. Its kind names the cause, and `EmptyState`'s usage page holds what each kind draws: `firstUse` (a collection with no records yet: its section's picture from the approved set in `@nadicodeai/design-system/assets/empty`, which [the art direction](../../skills/design/art-direction/references/surfaces.md#the-empty-state-set) keeps, and the one action that adds the first record, which the page's title row also keeps as its filled primary; a create action carries the leading +), `nothingYet`, `noResults`, `cleared`, `denied` and `failed`; a card-sized failure is `failed` with `scope="card"`, and there is no `ErrorCard`.
838
830
 
839
- - `firstUse`: a collection with no records yet, whoever the viewer is and however the collection fills. Its section's picture from the approved set in `@nadicodeai/design-system/assets/empty`, 240 × 176 on a page and 200 × 144 in a region or a card, which arrives once as it first renders ("Motion"); a title and one line saying what will live there; and, when this viewer can add the first record, the one action that adds it, as the filled primary button, beside an optional outline second path, as Vercel's Connect draws *Create Connector* beside *Browse Connectors* in its empty card; the page's title row keeps that same action as its filled primary whether the collection is empty or not. A create action carries the leading +. The set's pictures share one canvas, one ground line and one grammar, which [the art direction](../../skills/design/art-direction/references/surfaces.md#the-empty-state-set) keeps. One link to the product's documentation stands beside the action when the section has a page that explains it.
840
- - `nothingYet`: a list that is empty now and fills by itself. One line, no picture, nothing to press.
841
- - `noResults`: a search or filter that matches nothing. One line and Clear filters (or Clear search), no picture. A single typed query is quoted in curly quotes.
842
- - `cleared`: work that is finished. The success mark and one line.
843
- - `denied`: this viewer's role or plan does not open the page or region. The lock mark, a title (a heading), one line naming the feature and, when the plan gates it, the plan, and the one way out. One gated tile inside an open page is a `MessageNotice`, never a `denied`.
844
- - `failed`: a read that failed where content would be. The warning mark, the message's title and body, at most two controls with Retry first, and, for a failure on our side, its identifier in a collapsed disclosure, in monospace, with a copy button. A failure that fills a page moves focus to its first control. A card-sized failure is `failed` with `scope="card"`; there is no `ErrorCard`.
845
-
846
- The quiet lines (`nothingYet`, `noResults`, `cleared`) carry no live role of their own: the wrapper that stays mounted while rows and absence swap announces them, once.
847
-
848
- A persistent notice sits in the section it concerns, action feedback is a toast, and field validation stays with its field. A notice carries one action, a 32 px outline button, and at most a second one after it, such as Nadia's Ask Nadia after Try again, and has no close button: it stays until the state changes, and one note stands for one concept. A section notice is always the framed note, with or without its action; the page or the account gets a `NoticeBanner`, which carries its one fix as a command (never a ready-made button), reads at 16 px and draws its button as a pill. A toast leaves after 6 s, which is also the Undo window; a failure the person must handle is never a toast alone but a very short toast beside a persistent notice. A notice, a failed read, a confirmation's error and a toast each show a message its product's catalogue worded, never a sentence written where it shows: `MessageNotice` under the control whose write failed (`action`) or atop the section it concerns (`section`), `EmptyState` `failed`, `ConfirmDialog`'s error and the toast. Each wraps a long word, never truncates, closes its title with a full stop before a body that follows on the same line, and shows a body only when it says more than the title ([the decision](../../docs/adr/2026-10-08-every-message-a-customer-reads-is-a-code-its-products-catalogue-words.md)).
831
+ A persistent notice sits in the section it concerns, action feedback is a toast, and field validation stays with its field. A notice has no close button: it stays until the state changes, and one note stands for one concept. A section notice is `MessageNotice`; the page or the account gets a `NoticeBanner`; a failure the person must handle is never a toast alone but a very short toast beside a persistent notice. A notice, a failed read, a confirmation's error and a toast each show a message its product's catalogue worded, never a sentence written where it shows ([the decision](../../docs/adr/2026-10-08-every-message-a-customer-reads-is-a-code-its-products-catalogue-words.md)). Each wraps a long word, never truncates, closes its title with a full stop before a body that follows on the same line, and shows a body only when it says more than the title; their usage pages hold how each draws it.
849
832
 
850
833
  Dialogs, sheets, drawers and the command palette sit over the scrim ("Colors"); popovers, selects and toasts float without one. Focus is not elevation: it is always the focus outline.
851
834
 
@@ -890,16 +873,16 @@ answer keeps its component, density, value format and action placement.
890
873
 
891
874
  | Information | Component and context |
892
875
  | --- | --- |
893
- | Records a person finds one of and acts on | `RecordList`, inside `CollectionPage` on a page of its own: one line per record that names itself, a few facts, the exception, the time, the row's command and its menu. |
894
- | Records with the same comparable facts | `DataTable`: one identity per row, typed columns each with a `size` (one column takes the rest), visible headings, sortable headings when the headings sort, and a menu when secondary mutations exist. |
895
- | A descriptive item with supporting prose | `ItemGroup` and `Item`: identity, description and at most two trailing actions, rather than invented table columns. |
896
- | What happened, in order | `ActivityFeed`: one row per event under its day ("Activity"). |
897
- | The identity of a record in a collection | `RecordIdentity`: its name, one muted line and its `IdentityMark`; the containing list owns navigation. |
898
- | Facts about one object | `ObjectPage`'s facts, `ObjectFacts` or `SettingsFactsSection`: labelled values in one coherent section. A relation uses its same identity or `Tag`; it does not become a separate card. |
899
- | A coherent set of changes | `SettingsSection` with its `save` and `SettingsField`: the section owns its fields, its one-line help, recovery and local Save, with no help line under a field (a card of several fields gives a row that needs it an (i) with a tooltip), and compares its fields with what is saved. |
900
- | A classification, lifecycle or operation | `Badge`, `StatusBadge` or `ActionStatus`, according to the existing state contract. A missing value, an empty list and a failed read remain distinct. |
876
+ | Records a person finds one of and acts on | `RecordList` inside `CollectionPage`, on a page of its own. |
877
+ | Records with the same comparable facts | `DataTable`. |
878
+ | A descriptive item with supporting prose | `ItemGroup` and `Item`, rather than invented table columns. |
879
+ | What happened, in order | `ActivityFeed` ("Activity"). |
880
+ | The identity of a record in a collection | `RecordIdentity`; the containing list owns navigation. |
881
+ | Facts about one object | `ObjectPage`'s facts, `ObjectFacts` or `SettingsFactsSection`, in one coherent section. A relation uses its same identity or `Tag`; it does not become a separate card. |
882
+ | A coherent set of changes | `SettingsSection` with its `save` and `SettingsField`. |
883
+ | A classification, lifecycle or operation | `Badge`, `StatusBadge` or `ActionStatus`. A missing value, an empty list and a failed read remain distinct. |
901
884
  | Another view or sibling collection of the current scope | `TabsNav` or line `Tabs` ("Controls"). A tab never introduces a new scope or a second collection owner. |
902
- | A measurement over time or across objects | The existing KPI or chart composition for the reading; a named comparison uses the same `DataTable`, with units and visible headings. |
885
+ | A measurement over time or across objects | The KPI or chart composition for the reading ("Data Visualization"); a named comparison is a `DataTable` with units and visible headings. |
903
886
 
904
887
  One section holds one complete task or answer. A status, explanatory sentence
905
888
  or Change button belongs beside the facts or fields it qualifies; it does not
@@ -917,7 +900,7 @@ Every component has a usage page, as Geist documents each of its own: what it is
917
900
  | Words | Which strings the product words, and how. |
918
901
  | Access | Its role, its name, its keyboard path and its focus. |
919
902
 
920
- The page is written in the component's own source, as the TSDoc of its main export (`@family`, `@when`, `@instead`, `@behaviour`, `@words`, `@access`), so an editor shows it on hover in every product; `@nadicodeai/ui`'s showcase renders every page, family by family, with the component drawn in its states. A component without its usage page, or a page without its specimen, fails the package's checks. A usage page applies the rules in this file and never restates one.
903
+ The page is written in the component's own source, as the TSDoc of its main export (`@family`, `@when`, `@instead`, `@behaviour`, `@words`, `@access`), so an editor shows it on hover in every product; `@nadicodeai/ui`'s showcase renders every page, family by family, with the component drawn in its states. A component without its usage page, or a page without its specimen, fails the package's checks. A usage page holds its component's own rules, applies the shared rules in this file and never restates one.
921
904
 
922
905
  The table below maps all 73 entries in the [Geist component reference](https://vercel.com/geist/introduction) to our shared owners; how each owner is used is on its usage page. **Component** means a public owner exists for the job; it does not promise every Geist prop. **Composition** means existing parts provide the presentation together. An **enhanced capability gap** names specific behavior, not the absence of a matching component name. Use our exported APIs and names, not Geist's prop names: for example, our Button appearance is `variant`, while HTML submission behavior is `type`. A dedicated name in Geist does not require another primitive in our library; prefer existing compositions before adding another registry or dependency.
923
906
 
@@ -927,79 +910,79 @@ Every example and consuming surface covers its reachable populated, empty, loadi
927
910
 
928
911
  | Geist reference | Shared owner |
929
912
  | --- | --- |
930
- | [Address Input](https://vercel.com/geist/address-input) | Composition: `Field` parts in a `FieldGroup` 16 px apart: a `NativeSelect` country first, a street `Combobox` (or `Input` when there is no lookup), Address line 2 with (optional) written in its label, then pairs such as State and ZIP or Postal code and City two-up. A failed lookup always ends in the manual fields, never a dead end. No dedicated export: it needs a provider only a product has. |
931
- | [Avatar](https://vercel.com/geist/avatar) | Component: `Avatar` (16, 24, 32 and 48 px), `AvatarImage`, `AvatarFallback`, `AvatarGroup` (`members`, `limit`, `reverse`, `overlap`, one `label`; it draws its own `+N` count), `AvatarWithIcon` (name required, optional `label` for the state), `AvatarBadge` in `avatar.tsx`, and `avatarInitials`; a record's mark, a person's included, is `IdentityMark`, and an Agent is her `AgentOrb`. Not adopted: the loading `placeholder` (a still skeleton is ours), git-provider avatars and the username fetch. |
932
- | [Badge](https://vercel.com/geist/badge) | Component: `Badge` (`sm` 16, `md` 20, `lg` 24 px; one `icon`; `pill` as the link look through `render`); `StatusBadge` for state; `Tag` for a categorical fact. Kept ours: sentence case, and the tone mark on every `StatusBadge` so a status reads without colour. Choose a typed tone; the owner supplies label geometry. |
933
- | [Banner](https://vercel.com/geist/banner) | Component: `NoticeBanner`; no announcement strip. |
934
- | [Book](https://vercel.com/geist/book) | Optional composition: `Card` / `PictureLabel` + `AspectRatio` and cover artwork. No dedicated book-frame export. |
935
- | [Breadcrumbs](https://vercel.com/geist/breadcrumbs) | Component: `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink` (with `disabled` for an ancestor the person may not open), `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis`. The menu type (each crumb opens its siblings) is not built. |
936
- | [Browser](https://vercel.com/geist/browser) | Optional composition: `Card` + `AspectRatio` and decorative browser chrome. `DesktopShell` remains real product chrome. |
937
- | [Button](https://vercel.com/geist/button) | Component: `Button` with `loading`, `loadingLabel`, `prefix`, `suffix` and `shape`; `ButtonLink` for links drawn as buttons; `buttonVariants` for elements that are neither. Our appearance prop is `variant`, submission is `type`. No `CustomButton` and no `warning` look: colour is ours. |
938
- | [Calendar](https://vercel.com/geist/calendar) | Component: `Calendar` (month grid, `minValue`/`maxValue` for the data window); `DateRangeControl` for metric windows, one button that opens the presets as buttons beside the calendar. |
939
- | [Checkbox](https://vercel.com/geist/checkbox) | Component: `Checkbox` + `FieldSet`, `FieldLegend`, `FieldLabel`. A box is named by `aria-label`, `aria-labelledby` or the `id` of its `FieldLabel`; one with none does not compile. Mixed is an unfilled box with a grey dash. |
940
- | [Choicebox](https://vercel.com/geist/choicebox) | Component: `ChoiceboxGroup` with `ChoiceboxGroupItem`; `type` is `radio` or `checkbox`, each tile has a title, a required description, optional grey `specs` chips, one `badge` such as Recommended, a `disabledReason` and selected-only content. Tiles share one row and wrap to a column on a narrow screen. The selected tile is the one place blue sits on a choice: border, tint behind the row, words and corner mark; an unavailable tile stays grey. |
941
- | [Clearable Input](https://vercel.com/geist/clearable-input) | Component: `ClearableInput` (clear button once there is text, Escape empties and keeps focus, `onClear`, `label` and `error` as on `Input`, optional `shortcut` keys that become Esc); other fixed parts are `InputGroup` compositions. |
942
- | [Code](https://vercel.com/geist/code) | Composition: semantic `code` with the shared mono typography roles. Conversation `MarkdownText` already owns its inline code; no standalone `Code` export is required for that use. |
943
- | [Code Block](https://vercel.com/geist/code-block) | `CodeBlock`: numbered lines that link to themselves, `highlightedLinesNumbers`, `addedLinesNumbers`, `removedLinesNumbers`, `hideLineNumbers` (marks still show and are announced), a `switcher` (select) or `tabs` for the language, a header only with a `filename`, `switcher` or `tabs`, and a `CopyButton` (32 px, not Geist's 28 px: CopyButton has no 28 px size). No syntax colour until a highlighter is adopted. Conversation `MarkdownText` keeps its job; `FileDiff` computes a before and after. |
944
- | [Collapse](https://vercel.com/geist/collapse) | Component: `Accordion` (`size` default, small, large; `multiple` for independent sections), `Collapsible`; complete `Disclosure`. Closed panels stay in the page, hidden until found, so find-in-page opens them; `hiddenUntilFound={false}` mounts a heavy panel only when open. A collapse never nests in another. |
945
- | [Combobox](https://vercel.com/geist/combobox) | Component: `Combobox`, input (sizes `sm`/default/`lg`, fills its container, leading search mark, named open-list and clear buttons)/content/list/item/group/label/empty/loading/chips parts. The list opens 8 px under the box at its width (a narrower width centres), in a 6 px inset, up to 210 px tall, rows 36 px with no gap and a 16 px check; the open-list and clear buttons are muted until hovered. |
946
- | [Command Menu](https://vercel.com/geist/command-menu) | Component: `CommandDialog` (title, description and `resultCount` required; it draws `Command` itself), `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandShortcut` (a `Kbd`). Find goes to places and runs no command, so there is no page stack. |
947
- | [Context Card](https://vercel.com/geist/context-card) | Component: `HoverCard`, `HoverCardTrigger`, `HoverCardContent` and `HoverCardSummary` (name, one identifying line, two to four label and value facts, at most one link that takes the product's router link through `render`). Opens above after 150 ms. |
948
- | [Context Menu](https://vercel.com/geist/context-menu) | Component: `ActionContextMenu`, the same `RecordMenu` as the row's `ActionMenu`, over the `ContextMenu` family. |
949
- | [Copy Button](https://vercel.com/geist/copy-button) | `CopyButton` owns Button + existing clipboard behavior, pending state and an announced result. `TextWithCopyButton` binds a shown value to it. |
950
- | [Description](https://vercel.com/geist/description) | Component: `ObjectPage`'s `facts`, `ObjectFacts` (a fact takes `tooltip`, `ellipsis` and `right`), `SettingsFactsSection`, `TableValue`. |
951
- | [Destructive Action Modal](https://vercel.com/geist/destructive-action-modal) | Component: `ConfirmDialog`, with `verify` for the typed-name gate and `irreversible` for the band. |
952
- | [Dots Menu](https://vercel.com/geist/dots-menu) | Component: `ActionMenu` for a record, `PageActions`' `menu` for a page. |
953
- | [Drawer](https://vercel.com/geist/drawer) | Component: `Drawer`, the bottom sheet for a short job on a phone, with `height` for the case where the default clips the action and `unsaved` to hold back a swipe, Escape or the back gesture while input is typed ([Menus and commands](#menus-and-commands)). `CatalogDrawer` and `FormDrawer` are Geist's Sheet in shape and are listed on the Sheet row. |
954
- | [Empty State](https://vercel.com/geist/empty-state) | Component: [EmptyState](../ui/src/components/empty-state.tsx) with the kinds `firstUse`, `nothingYet`, `noResults`, `cleared`, `denied` and `failed`, following the [absence contract](#elevation--depth). |
955
- | [Entity](https://vercel.com/geist/entity) | Component: `RecordList` for records (72 px entity rows, an optional `select` column); `Item`, `ItemGroup` and `ItemSelect` for a descriptive list; `CatalogEntry` in `LibraryShelf` and `CatalogDrawer`; `AgentListRow`. |
956
- | [Error](https://vercel.com/geist/error) | Component: `FieldError` (a field), `MessageNotice` (a control or section), `EmptyState kind="failed"` with its reference (a block). |
957
- | [Error Card](https://vercel.com/geist/error-card) | `EmptyState kind="failed" scope="card"`; no ErrorCard export. |
958
- | [Feedback](https://vercel.com/geist/feedback) | Composition: `Popover`, `Field`, `Textarea`, `RadioGroup`, optional `Select`, and `Button`. The app supplies the submission destination and result; no collection backend is implied. |
959
- | [Fieldset](https://vercel.com/geist/fieldset) | Component: `SettingsSection` with `save`, `tone`, `lockedReason` and `unlock`; `SettingsField`; `FieldSet`/`FieldLegend` for semantic subgroups. Required: the title, the description and the footer's left line (`footerHint`, `save.hint` or `lockedReason`); only a card of title and line has no footer. Tone error or warning: a 1px border in the hue and a footer tinted in it with its line in the hue, the body and the buttons neutral. Its DisabledWall is `lockedReason`. |
960
- | [File Tree](https://vercel.com/geist/file-tree) | Composition: `SidebarMenu` + `Collapsible` + `FileTypeIcon` can follow sidebar-11 for nested files. Full tree-widget selection and roving arrow-key navigation require additional behavior. |
961
- | [Gauge](https://vercel.com/geist/gauge) | Component: `Gauge` in `gauge.tsx`, one ratio out of 100 on Base UI `Meter`, `xs`/`sm`/`md`/`lg` (16, 32, 64 and 128 px), the number or an icon in the centre, our `Progress` tones. Not adopted: `indeterminate` (the waiting line), the two-arc `equal` ratio and the `colors` map (the tone is the product's threshold). |
962
- | [Grid](https://vercel.com/geist/grid) | Composition: layout grid, `CardRow`, website `nc-page`; no ruled `Grid` API. |
963
- | [Input](https://vercel.com/geist/input) | Component: `Input` (sizes `sm` 32, default 36, `lg` 40; props `label` (with `id`), `error`, `prefix` and `suffix` as filled cells, `look="plain"` for marks on the box fill, `rounded` for a pill), `InputGroup` for a button inside the box, a block addon, a textarea toolbar or a read-only server-given value with its copy button, `SettingsField`/`Field` for the card around it. Geist's `prefixContainer` and `suffixContainer` are compositions of `InputGroup`. |
964
- | [JSON View](https://vercel.com/geist/json-view) | Composition for static JSON: code display with exact serialized text. `Collapsible` supports grouped disclosure; Geist-style keyboard tree navigation, search highlighting and depth controls remain an enhanced capability gap. |
965
- | [Keyboard Input](https://vercel.com/geist/keyboard-input) | Component: `Kbd`, which takes `meta`, `shift`, `alt` and `ctrl` flags, draws the person's platform (⌘ ⇧ ⌥ ⌃ on Apple systems, Ctrl Shift Alt elsewhere) and has two sizes, `default` (24 px) and `sm` (20 px) in a menu row, a command item or a table cell; `CommandShortcut` draws the same `Kbd`. There is no `KbdGroup`: several modifiers are one key. |
966
- | [Label](https://vercel.com/geist/label) | Component: `Label`, `FieldLabel`, `FieldLegend`, at regular weight. Geist's `value`, `withInput` and `bypassCasing` are not needed: the label is children, the `Field` spaces it 8 px above the box at 13 px regular, and casing is never transformed. `htmlFor` ties it to the control's id. |
967
- | [Load More Button](https://vercel.com/geist/load-more-button) | Component: `LoadMoreButton`, the full-width outline button under a list that grows in place; `ActivityFeed`'s `older` is one, or a `ButtonLink` when it navigates. It sits one `{spacing.md}` step below its list, and `noGap` removes the step for a list inside a card. |
968
- | [Loading Dots](https://vercel.com/geist/loading-dots) | Personalization: `Button`'s `loading` and `WaitingLine`; no LoadingDots. |
969
- | [Menu](https://vercel.com/geist/menu) | Component: `ActionMenu`, `ActionContextMenu`, `PageActions`' `menu` and `AccountMenu`, which draw their rows with `DropdownMenuCommand` (a command can be `disabled` with a reason, or `locked` with the reason it is locked); the `DropdownMenu` family directly only for a choice menu such as a sort, with `DropdownMenuLink` for a destination row. Every row, of any kind, is 36 px (44 on touch) with `{rounded.sm}` corners. |
970
- | [MiddleTruncate](https://vercel.com/geist/middle-truncate) | `MiddleTruncate`: a path, URL, ID, hash or branch cut in the middle with one ellipsis glyph, the last third (at most 16 characters) kept whole, the full value in the page and, while cut, on the clipboard. Prose still ends in an end ellipsis. |
971
- | [Modal](https://vercel.com/geist/modal) | Component: `Dialog` family (a bottom sheet on a phone), `FormDialog` for short forms with the `unsaved` guard, and `ConfirmDialog` for consequences. |
972
- | [Multi Select](https://vercel.com/geist/multi-select) | Component: `MultiSelect` (checkbox menu, count in the trigger; a row is a checkbox square plus a button holding the name, whose word is Select all on a chosen row while some rows are unchosen and Select only otherwise); `Combobox multiple` when typing to find matters more; `CollectionFilters` for query facets, where a suggested value waits as a dashed, muted chip led by a +, never looking like a filter in force. |
973
- | [Note](https://vercel.com/geist/note) | Component: `MessageNotice placement="section"` (the framed note, one action and at most a second after it); `Alert` is its primitive. No success, violet or cyan tone: our tones are neutral, info, warning and destructive. |
974
- | [Pagination](https://vercel.com/geist/pagination) | Not built until a page needs it: the pages of a table of records are `DataTable`'s own Previous and Next under its card, and `RecordList`'s footer paging. |
975
- | [Phone](https://vercel.com/geist/phone) | Optional composition: `AspectRatio` + decorative frame and captured content. No dedicated device-frame export. |
976
- | [Pill](https://vercel.com/geist/badge#pill) | `Badge variant="pill"` with `render={<a href=... />}` for a link-like badge; static category: `Tag`; filters: `CollectionFilters`; buttons keep their semantics. |
977
- | [Progress](https://vercel.com/geist/progress) | Component: `Progress` (tones, `stops`; the track is 4 px), `ProgressTrack`, `ProgressIndicator`, `ProgressLabel`, `ProgressValue`; `UsageMeter`. aria-valuenow follows the reading at most once a second. |
978
- | [Project Banner](https://vercel.com/geist/project-banner) | Component: `NoticeBanner` (label, icon, one action as a command, no close, one at a time). |
979
- | [Radio](https://vercel.com/geist/radio) | Component: `RadioGroup`, `RadioGroupItem` with FieldSet/legend. Rows are 24 px apart. The chosen radio keeps an empty circle with an ink edge and an 8 px ink dot; each option is named like a checkbox. |
980
- | [Relative Time Card](https://vercel.com/geist/relative-time-card) | Component: `RecordTime`: the time in its verb's sentence or the clock time, the exact moment in UTC and the local zone in its hover card (above, after 150 ms); `children` carries a state no time describes (Pending, Queued). The product's words stay: Intl relative words inside the phrase, not Geist's 2m / 5h short form. |
981
- | [Scroller](https://vercel.com/geist/scroller) | Component: `ScrollArea` (`overflow` y, x or both; an edge fade on each clipped edge; `buttonLabels` for two round buttons that scroll to the next direct child) and `ScrollBar`. `Carousel` stays a separate component until its fold into `ScrollArea` is done. |
982
- | [Search Input](https://vercel.com/geist/search-input) | Component: `CollectionSearch` in `CollectionToolbar`: search mark or custom `prefix`, clear button and Escape, `loading` (busy plus the waiting line after a second), optional `shortcut` keys that become Esc. |
983
- | [Select](https://vercel.com/geist/select) | Component: `Select` family and `NativeSelect` family, sizes `sm`/default/`lg`, optional `prefix` and `suffix` (a suffix takes the chevron's place); `label` and `error` as props on `SelectTrigger` and `NativeSelect`. A list of two or three static options is `RadioGroup`, as Geist says. |
984
- | [Separator](https://vercel.com/geist/separator) | Component: `Separator` (hidden from assistive technology by default; `inset` keeps 1 px clear at each end where a divider meets a frame painted over the content); `FieldSeparator` and menu/group separators for scoped roles. |
985
- | [Sheet](https://vercel.com/geist/sheet) | Component: `Sheet`, not modal by default (`modal` dims the page, `noOverlay` drops the scrim), with `RecordSheet` for one record looked at from a list, and `FormDrawer`/`CatalogDrawer` as its modal catalogue and steps forms. |
986
- | [Show more](https://vercel.com/geist/show-more) | Not built until a page needs it: a list that grows from the server takes `LoadMoreButton`. `Disclosure` and `Collapsible` stay for optional sections. |
987
- | [Skeleton](https://vercel.com/geist/skeleton) | Component: `Skeleton` (still; with a size it shows a bar until its content arrives, unsized it wraps a control and `show` covers it) and `SkeletonRegion` (the aria-busy region). Geist's shimmer and `animated` option are not taken. |
988
- | [Slider](https://vercel.com/geist/slider) | Component: `Slider` takes a label and the real `min`, `max` and `step`, draws its live value and, on request, start and end boxes (`Input` `sm`); values are arrays and a range has two thumbs named by the label and their position. The track is 8 px, the filled part is blue and turns grey when disabled, and the thumb is a 6 by 14 px bar that grows by a fifth while dragged or focused. |
989
- | [Snippet](https://vercel.com/geist/snippet) | `Snippet`: one command (or a few lines) after a prompt the component draws and never copies, with `CopyButton`, `placeholder` for the empty state, `copyText` for a highlighted element, `copied` and `onCopy`. No `dark`, `type` or `width`: colour and size are ours. Longer scripts are a `CodeBlock`. |
990
- | [Spinner](https://vercel.com/geist/spinner) | Personalization: `Button`'s `loading` and `WaitingLine`, no Spinner export. |
991
- | [Split Button](https://vercel.com/geist/split-button) | Not built until a page needs it. |
992
- | [Status Dot](https://vercel.com/geist/status-dot) | Component: [StatusDot](../ui/src/components/status-dot.tsx) and `StatusBadge`. Kept ours: the dot is still, has four generic tones and no deployment states; the word beside it is its name. |
993
- | [Switch](https://vercel.com/geist/switch) | Component: `SegmentedControl` for two or three exclusive views of one surface (radio semantics, option heights `sm` 24, `default` 28, `lg` 32 inside a 4 px inset, so the control is 32, 36 or 40 px high); `Tabs` for panels; `ToggleGroup` for pressed buttons that can be several or none; the theme stays `ThemeModeSwitcher`. |
994
- | [Table](https://vercel.com/geist/table) | Component: `Table` with `TableColgroup`/`TableCol` and a `TableBody` that takes `striped`, `bordered` and `interactive`; `DataTable` for records with sizes and sortable headings; `RecordList` for rows that name themselves; `TableCard` and `TableValue`. Virtualization remains a separate capability. |
995
- | [Tabs](https://vercel.com/geist/tabs) | Component: `Tabs`, `TabsList` (`variant` default or line), `TabsTrigger` (`disabledReason` with `disabled`), `TabsContent`; URL views use `TabsNav`/`TabsLink`. |
996
- | [Text With Copy Button](https://vercel.com/geist/text-with-copy-button) | `TextWithCopyButton`: the value in the mono label type plus `CopyButton` (32 px); `ellipsis` cuts it in the middle and the whole value still copies. The Portal uses it for identifiers and checksums. |
997
- | [Textarea](https://vercel.com/geist/textarea) | Component: `Textarea` (100 px minimum, grows with its content, `lg` for 16 px type, `label` with `id` and `error` as props) + Field/SettingsField. Its height is its content with a 100 px floor; `rows` is not a size. The save of `FormDialog`, `FormDrawer`, `RowEditForm` and `SettingsSection` trims its ends. |
998
- | [Theme Switcher](https://vercel.com/geist/theme-switcher) | Component: `ThemeModeSwitcher` (three named tiles with their icons, System, Light, Dark, the one order every theme choice keeps) on the settings page, `ThemeModeMenu` in the account menu, `ThemeModeSegments` in the corner of a page. |
999
- | [Toast](https://vercel.com/geist/toast) | Component: `Toaster` (6 s); `showMessageToast` from `@nadicodeai/ui/components/toast`. Gaps held in `toast.ts`: `preserve`, a literal Undo, a neutral tone. |
1000
- | [Toggle](https://vercel.com/geist/toggle) | Component: our `Switch` for a boolean setting; a bare `Switch` is the 28 by 14 px track, on is blue and a disabled one is grey. It sits in a card's body with its state word, or at the end of a row of a multi-row card. Our `Toggle` is a pressed button. |
1001
- | [Tooltip](https://vercel.com/geist/tooltip) | Component: `TooltipProvider`, `Tooltip`, `TooltipTrigger`, `TooltipContent`. Opens after 150 ms (`delay` on the trigger or provider; `delay={0}` for none); `arrow={false}` is Geist's `tip={false}` and `centerText={false}` its `center={false}`. Geist's coloured `type` is not provided. |
1002
- | [Video](https://vercel.com/geist/video) | Composition: `AspectRatio` + native video, controls, poster and caption tracks. A richer player needs a concrete media requirement; no shared Video export is needed for native playback. |
913
+ | [Address Input](https://vercel.com/geist/address-input) | `Field` parts in a `FieldGroup` 16 px apart: a `NativeSelect` country first, a street `Combobox` (or `Input` when there is no lookup), Address line 2 with (optional) written in its label, then pairs such as State and ZIP or Postal code and City two-up; a failed lookup ends in the manual fields. No dedicated export: it needs a provider only a product has |
914
+ | [Avatar](https://vercel.com/geist/avatar) | The `Avatar` family; a record's mark is `IdentityMark`, and an Agent is her `AgentOrb` |
915
+ | [Badge](https://vercel.com/geist/badge) | `Badge`; `StatusBadge` for state, `Tag` for a categorical fact |
916
+ | [Banner](https://vercel.com/geist/banner) | `NoticeBanner` |
917
+ | [Book](https://vercel.com/geist/book) | `Card` / `PictureLabel` + `AspectRatio` and cover artwork; no book-frame export |
918
+ | [Breadcrumbs](https://vercel.com/geist/breadcrumbs) | The `Breadcrumb` family |
919
+ | [Browser](https://vercel.com/geist/browser) | `Card` + `AspectRatio` and decorative browser chrome; `DesktopShell` stays real product chrome |
920
+ | [Button](https://vercel.com/geist/button) | `Button`, `ButtonLink` and `buttonVariants` |
921
+ | [Calendar](https://vercel.com/geist/calendar) | `Calendar`; `DateRangeControl` for a metric's window |
922
+ | [Checkbox](https://vercel.com/geist/checkbox) | `Checkbox` with the Field parts |
923
+ | [Choicebox](https://vercel.com/geist/choicebox) | `ChoiceboxGroup` |
924
+ | [Clearable Input](https://vercel.com/geist/clearable-input) | `ClearableInput` |
925
+ | [Code](https://vercel.com/geist/code) | Semantic `code` with the shared mono typography roles |
926
+ | [Code Block](https://vercel.com/geist/code-block) | `CodeBlock` |
927
+ | [Collapse](https://vercel.com/geist/collapse) | `Accordion`, `Collapsible` and the complete `Disclosure`; a collapse never nests in another |
928
+ | [Combobox](https://vercel.com/geist/combobox) | `Combobox` |
929
+ | [Command Menu](https://vercel.com/geist/command-menu) | `CommandDialog` |
930
+ | [Context Card](https://vercel.com/geist/context-card) | `HoverCard` with `HoverCardSummary` |
931
+ | [Context Menu](https://vercel.com/geist/context-menu) | `ActionContextMenu` |
932
+ | [Copy Button](https://vercel.com/geist/copy-button) | `CopyButton`; `TextWithCopyButton` binds a shown value to it |
933
+ | [Description](https://vercel.com/geist/description) | `ObjectPage`'s `facts`, `ObjectFacts`, `SettingsFactsSection`, `TableValue` |
934
+ | [Destructive Action Modal](https://vercel.com/geist/destructive-action-modal) | `ConfirmDialog` |
935
+ | [Dots Menu](https://vercel.com/geist/dots-menu) | `ActionMenu` for a record, `PageActions`' `menu` for a page |
936
+ | [Drawer](https://vercel.com/geist/drawer) | `Drawer`, the phone's bottom sheet for a short job |
937
+ | [Empty State](https://vercel.com/geist/empty-state) | `EmptyState`, following the [absence contract](#elevation--depth) |
938
+ | [Entity](https://vercel.com/geist/entity) | `RecordList` for records; `Item`, `ItemGroup` and `ItemSelect` for a descriptive list; `CatalogEntry` in `LibraryShelf` and `CatalogDrawer`; `AgentListRow` |
939
+ | [Error](https://vercel.com/geist/error) | `FieldError` (a field), `MessageNotice` (a control or section), `EmptyState kind="failed"` with its reference (a block) |
940
+ | [Error Card](https://vercel.com/geist/error-card) | `EmptyState kind="failed" scope="card"`; no ErrorCard export |
941
+ | [Feedback](https://vercel.com/geist/feedback) | `Popover`, `Field`, `Textarea`, `RadioGroup`, optional `Select`, and `Button` |
942
+ | [Fieldset](https://vercel.com/geist/fieldset) | `SettingsSection` with `SettingsField`; `FieldSet` and `FieldLegend` for semantic subgroups |
943
+ | [File Tree](https://vercel.com/geist/file-tree) | `SidebarMenu` + `Collapsible` + `FileTypeIcon` can follow sidebar-11 for nested files |
944
+ | [Gauge](https://vercel.com/geist/gauge) | `Gauge` |
945
+ | [Grid](https://vercel.com/geist/grid) | Layout grid, `CardRow`, website `nc-page`; no ruled `Grid` API |
946
+ | [Input](https://vercel.com/geist/input) | `Input`; `InputGroup` for parts inside the box; `Field` or `SettingsField` for the card around it |
947
+ | [JSON View](https://vercel.com/geist/json-view) | Composition for static JSON: code display with exact serialized text |
948
+ | [Keyboard Input](https://vercel.com/geist/keyboard-input) | `Kbd` |
949
+ | [Label](https://vercel.com/geist/label) | `Label`, `FieldLabel` and `FieldLegend` |
950
+ | [Load More Button](https://vercel.com/geist/load-more-button) | `LoadMoreButton` |
951
+ | [Loading Dots](https://vercel.com/geist/loading-dots) | `Button`'s `loading` and `WaitingLine`; no LoadingDots |
952
+ | [Menu](https://vercel.com/geist/menu) | `ActionMenu`, `ActionContextMenu`, `PageActions`' `menu` and `AccountMenu`; the `DropdownMenu` family directly only for a choice menu such as a sort |
953
+ | [MiddleTruncate](https://vercel.com/geist/middle-truncate) | `MiddleTruncate` |
954
+ | [Modal](https://vercel.com/geist/modal) | The `Dialog` family, `FormDialog` for a short form and `ConfirmDialog` for a consequence |
955
+ | [Multi Select](https://vercel.com/geist/multi-select) | `MultiSelect`; `Combobox multiple` when typing to find matters more; `CollectionFilters` for query facets |
956
+ | [Note](https://vercel.com/geist/note) | `MessageNotice placement="section"` |
957
+ | [Pagination](https://vercel.com/geist/pagination) | Not built until a page needs it: the pages of a table of records are `DataTable`'s own Previous and Next under its card, and `RecordList`'s footer paging |
958
+ | [Phone](https://vercel.com/geist/phone) | `AspectRatio` + decorative frame and captured content; no device-frame export |
959
+ | [Pill](https://vercel.com/geist/badge#pill) | `Badge` through `render` for a link-like badge; `Tag` for a static category; `CollectionFilters` for filters |
960
+ | [Progress](https://vercel.com/geist/progress) | The `Progress` family; `UsageMeter` |
961
+ | [Project Banner](https://vercel.com/geist/project-banner) | `NoticeBanner` |
962
+ | [Radio](https://vercel.com/geist/radio) | `RadioGroup` |
963
+ | [Relative Time Card](https://vercel.com/geist/relative-time-card) | `RecordTime` |
964
+ | [Scroller](https://vercel.com/geist/scroller) | `ScrollArea` and `ScrollBar` |
965
+ | [Search Input](https://vercel.com/geist/search-input) | `CollectionSearch` in `CollectionToolbar` |
966
+ | [Select](https://vercel.com/geist/select) | The `Select` and `NativeSelect` families |
967
+ | [Separator](https://vercel.com/geist/separator) | `Separator` |
968
+ | [Sheet](https://vercel.com/geist/sheet) | `Sheet`; `RecordSheet` for one record looked at from a list; `FormDrawer` and `CatalogDrawer` for its steps and catalogue forms |
969
+ | [Show more](https://vercel.com/geist/show-more) | Not built until a page needs it: a list that grows from the server takes `LoadMoreButton` |
970
+ | [Skeleton](https://vercel.com/geist/skeleton) | `Skeleton` and `SkeletonRegion` |
971
+ | [Slider](https://vercel.com/geist/slider) | `Slider` |
972
+ | [Snippet](https://vercel.com/geist/snippet) | `Snippet` |
973
+ | [Spinner](https://vercel.com/geist/spinner) | `Button`'s `loading` and `WaitingLine`, no Spinner export |
974
+ | [Split Button](https://vercel.com/geist/split-button) | Not built until a page needs it |
975
+ | [Status Dot](https://vercel.com/geist/status-dot) | `StatusDot` and `StatusBadge` |
976
+ | [Switch](https://vercel.com/geist/switch) | `SegmentedControl` for two or three exclusive views of one surface; `Tabs` for panels; `ToggleGroup` for pressed buttons; `ThemeModeSwitcher` for the theme |
977
+ | [Table](https://vercel.com/geist/table) | `Table`; `DataTable` for records; `RecordList` for rows that name themselves; `TableCard` and `TableValue` |
978
+ | [Tabs](https://vercel.com/geist/tabs) | `Tabs`, and `TabsNav` with `TabsLink` for URL views |
979
+ | [Text With Copy Button](https://vercel.com/geist/text-with-copy-button) | `TextWithCopyButton` |
980
+ | [Textarea](https://vercel.com/geist/textarea) | `Textarea` with `Field` or `SettingsField` |
981
+ | [Theme Switcher](https://vercel.com/geist/theme-switcher) | `ThemeModeSwitcher`, `ThemeModeMenu` and `ThemeModeSegments` |
982
+ | [Toast](https://vercel.com/geist/toast) | `Toaster` and `showMessageToast` |
983
+ | [Toggle](https://vercel.com/geist/toggle) | `Switch` for a boolean setting; our `Toggle` is a pressed button |
984
+ | [Tooltip](https://vercel.com/geist/tooltip) | The `Tooltip` family |
985
+ | [Video](https://vercel.com/geist/video) | `AspectRatio` + native video, controls, poster and caption tracks |
1003
986
 
1004
987
  ### Additional shared building blocks
1005
988
 
@@ -1008,47 +991,40 @@ These shared components also follow the same foundations, although Geist does no
1008
991
  | Owner | Use and composition contract |
1009
992
  | --- | --- |
1010
993
  | `Sidebar`, `SidebarSections`, `UpdateCard`, `AccountMenu`, `DesktopShell`; the Portal's `PortalShellFrame` | Persistent product navigation and workspace chrome ("Product shells"). The product owns destinations and active state; the shell owns compact behavior, landmarks and layout. `SidebarSections` is the sidebar's swap into an object's sections or an area's sub-menu, `UpdateCard` the offer to update above the account. Never add a competing sidebar to a page. |
1011
- | `Popover` | A bounded contextual control such as filters or a scope chooser. Name its trigger, keep essential meaning visible outside hover, and restore focus when it closes. Which form surface a job takes is decided in [Menus and commands](#menus-and-commands). |
1012
- | `InputOTP` and its group/slot parts | A fixed-length verification code. Keep one logical input with paste and keyboard behavior, a visible label, invalid/retry state and a real resend policy. The product owns length, expiry and submission. |
1013
- | `Carousel` | A finite horizontal rail where adjacent media or items have a meaningful sequence. Give previous/next controls clear names and boundaries; preserve keyboard access and reduced motion. A sortable record collection uses a table or entity list. |
1014
- | `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle` | A workspace with genuinely adjustable panes. Keep keyboard resizing, meaningful minimum sizes, readable content and a compact alternative. The product owns persistence of pane sizes. |
1015
- | `AspectRatio` | Stable layout for real media while it loads. Supply a meaningful ratio and correct media alternative; the wrapper creates neither playback controls nor a device-frame component. |
1016
- | Field parts and form adapters | Connect actual validation to controls, help and errors. Use the field owner that matches the product's form engine; preserve values through recoverable failure. Do not change the form engine during visual styling. |
1017
- | `PairingCode` | A short code two screens show at once so a person can tell they are talking to each other, such as the four digits Nadia and Alveare Server both show while a Nadia asks to join. Each digit stands in its own cell at `{typography.heading-lg}` with tabular figures, large enough to compare from across a desk, and assistive technology reads the label, then the digits one by one. It only shows a code: a code a person types is `InputOTP`. |
1018
- | `QrCode` | A real scannable value with a readable fallback and an alternative action when scanning is unavailable. Keep secrets out of accessible labels and show expiry or invalidity from actual state. |
1019
- | `FileDiff` | Existing code-change comparison, with file identity and additions/removals explained. A diff is not a general code editor or JSON viewer. Preserve exact source and the established conversation/tool owner. |
1020
994
  | Chart kit and `KpiCard` | Choose the reading through [Data Visualization](#data-visualization), then use its shared chart owner with units, period, legend and accessible data. Geometry never decides business thresholds. |
1021
995
 
996
+ `Popover`, `InputOTP`, `Carousel`, the resizable panels, `AspectRatio`, the Field parts, `PairingCode`, `QrCode` and `FileDiff` each hold their contract on their usage page. Which form surface a job takes is decided in [Menus and commands](#menus-and-commands).
997
+
1022
998
  ### Complete compositions
1023
999
 
1024
1000
  Choose the owner for the whole task. Keep its slots and state handling together so pages supply content without recreating card zones, toolbar geometry or overlay behavior.
1025
1001
 
1026
- | Task | Shared composition | What the product adapter supplies |
1027
- | --- | --- | --- |
1028
- | Settings | `SettingsPage` → `SettingsSection` with its `save` → `SettingsField`. | The section's title and one line, its fields, what saving does (`save.action`), the product's toast for a save that went through (`save.saved`), the save's words and, where the card has something specific to say, its hint. The section owns the changed state, Save and a failure under its field. |
1029
- | Read-only facts | `ObjectPage`'s `facts`, `SettingsFactsSection` or `ObjectFacts`, with `TableValue` for missing facts. | Label/value pairs, actual timestamps, identifier format, links and permission-aware actions. Unknown remains distinct from zero. |
1030
- | Entity collection | `CollectionPage` on `RecordList`: the title's actions, the line tabs, search, filters and sort, then rows that identify themselves (`RecordIdentity` with its `IdentityMark` and `detail`, a status only for the exception, `RecordTime`), the row's command or menu, an action's failure inside its row, the edit inside its row (`RowEditForm`), and its loading, empty and no-match states. | The records, what each one says, its commands as data, its catalogue message when an action fails, and the edit form. Never its own toolbar, table, rows or menus. |
1031
- | A list inside another page | `RecordList` with its `heading`: an overview's exceptions, an object's "Needs attention". | The same as a collection's rows, and the heading's title. |
1032
- | Comparable collection | `DataTable` within `TableCard`, under `PageActions` and `CollectionToolbar`. | Stable record IDs, columns, visible headings, search/filter/sort, URL state, row commands, pagination and `empty`. Numeric columns align at the trailing edge; the first and last columns stand in from the card's edges by the card's spacing, so the first lines up with the title; totals and paging use their own footer zone. On a phone the toolbar's search takes the first line and its filters and sort one scrolling line under it. |
1033
- | Overview | `OverviewPage` with its `KpiCard` figures, its `ChartCard`s and its exceptions. | Measurement, unit, comparison period, truthful delta and stable categorical assignment; the reading time and the way to each figure's owner. Status colour and series colour retain different meanings. |
1034
- | Object | `ObjectPage` with `ObjectSection`, `ObjectFacts`, a `RecordList` with its heading, a `SettingsSection` or an `ActivityFeed` as its sections. | The object, its mark, its facts, its one secondary action and its menu, and its sections. An Agent's Profile keeps `AgentProfileCard`. |
1035
- | A reusable thing to add | `LibraryShelf` with `AgentCard`s for a gallery of templates; `CollectionPage` for a catalog a person finds, opens and adds from (the Portal's Skills and Plugins); `CatalogDrawer` with `CatalogEntry`s where the choice opens from a page. | The cards or the rows, one line each, the add and its state. |
1036
- | What happened | `ActivityFeed`; one run's steps are `RunTimeline` ("Activity"). | The events, each with its time, actor, sentence, the exception and a detail line, grouped by day. |
1037
- | Getting something set up | `NumberedSteps` for the steps on a page; `FormDrawer job="steps"` for a setup with a wait. | The steps, the current one where the sequence tracks real progress, and each step's own action. |
1038
- | Short form: a new record, or one or two values from a command | `FormDialog` with Field parts; Portal binds it through `ActionDialogForm`. A record in a list edits inside its row (Entity collection); a catalog uses `CatalogDrawer` and a setup in steps `FormDrawer` ([Menus and commands](#menus-and-commands)). | Submit operation, labels, validation, dirty state, retained input, permissions and dismissal policy. Use the owned scroll body and fixed footer; do not nest another overlay. |
1039
- | One record looked at from a list | `RecordSheet`. | The record's name, one line, its facts, an optional payload and the way to its owner. |
1040
- | Destructive confirmation | `ConfirmDialog`; the app's confirm-action adapter. | A title naming the record as a statement, the real consequence, a confirm repeating the verb and the noun, pending state, failure and operation. `verify` asks for the record's name before the confirm enables, for a high-stakes action (delete, rotate, revoke, disconnect, downgrade, a protection switched off), even a reversible one; `irreversible` names what cannot be undone and is left out when the action can be reversed. A routine confirmation (discard a draft) takes neither. |
1041
- | Global commands or record actions | `CommandDialog` for Find; `ActionMenu` and `ActionContextMenu` for one record; `PageActions` for a page. | Real commands as data, the record they act on, permissions, disabled reasons and keyboard shortcuts. The destructive command stays last. |
1042
- | Absence or failed read | `EmptyState`, through the composition's `empty`, `noMatch` and `failed` or `DataTable.empty`; `MessageNotice` for persistent conditions. | First use with its section's picture, nothing yet, no matches, finished work, a denial or a failed read, with the real recovery action. Empty state belongs outside the semantic table body, inside the composition's persisting polite wrapper. |
1043
- | Rich choices | `ChoiceboxGroup` with `ChoiceboxGroupItem`, in the group's own `FieldSet` and legend. | Known options (no more than six), selection model, labels, a description for every tile, disabled reasons. The whole tile selects, its corner always shows the dot or the check, a selected tile is blue (border, tint behind its row, words and corner mark) unless it is unavailable, and only the tile draws the focus ring; a tile's content is for reading, never a link or a button. |
1044
- | Copyable identifier or command | `TextWithCopyButton` for a value, `Snippet` for a command; the prompt is the component's and is never copied. | Exact `value` or `text`, translated `copyLabel`, `copiedLabel`, `copyFailedLabel`. The shown text is the copied text; a long one takes `ellipsis`. |
1045
- | Plain code or static JSON | `CodeBlock`. | Exact `code`, accessible `label`, `filename` only when the code has a place to be pasted into, copy labels. Serialize static JSON in the adapter; use FileDiff for a before/after comparison. |
1046
- | A record's time | `RecordTime`: the time in its verb's sentence, or the clock time in a feed, with UTC and the local zone in its hover card. | The machine-readable timestamp, the phrase, the locale and the product's refresh policy. |
1047
- | Expandable file navigation | `SidebarMenu` and `Collapsible`, following shadcn sidebar-11, with file icons and real file links or commands. | Hierarchy, stable identity, current file, labels and actions. Preserve semantic nested lists; a full tree widget needs a separately verified keyboard contract. |
1048
- | Feedback collection | `Popover` with Field, Textarea, optional RadioGroup/Select and Button; inline rendering uses the same form parts. | Prompt, topic choices, permitted context, destination, pending/result state and retained input on failure. |
1049
- | Native media playback | `AspectRatio` around a native video with its controls, poster and caption tracks. | Media source, captions, transcript where needed, aspect ratio, playback policy and unavailable-media recovery. |
1050
-
1051
- Use `CopyButton`, `TextWithCopyButton`, `Snippet` and `CodeBlock` for their complete compositions. Feedback and expandable file navigation combine existing parts with product adapters. Syntax colour and full JSON/tree-widget navigation remain specific behavior gaps; do not invent undocumented props or copy a local implementation into every caller. [shadcn Button Group](https://ui.shadcn.com/docs/components/base/button-group), [Field](https://ui.shadcn.com/docs/components/base/field) and [Data Table](https://ui.shadcn.com/docs/components/base/data-table) provide the underlying composition patterns; our public wrappers and this document own their product use.
1002
+ | Task | Shared composition |
1003
+ | --- | --- |
1004
+ | Settings | `SettingsPage` → `SettingsSection` with its `save` → `SettingsField`. |
1005
+ | Read-only facts | `ObjectPage`'s `facts`, `SettingsFactsSection` or `ObjectFacts`, with `TableValue` for missing facts. |
1006
+ | Entity collection | `CollectionPage` on `RecordList`, with `RowEditForm` for the edit inside a row. The page supplies its records and their commands as data, never its own toolbar, table, rows or menus. |
1007
+ | A list inside another page | `RecordList` with its `heading`: an overview's exceptions, an object's "Needs attention". |
1008
+ | Comparable collection | `DataTable` within `TableCard`, under `PageActions` and `CollectionToolbar`. |
1009
+ | Overview | `OverviewPage` with its `KpiCard` figures, its `ChartCard`s and its exceptions; the product supplies the reading time and the way to each figure's owner. |
1010
+ | Object | `ObjectPage` with `ObjectSection`, `ObjectFacts`, a `RecordList` with its heading, a `SettingsSection` or an `ActivityFeed` as its sections. |
1011
+ | A reusable thing to add | `LibraryShelf` with `AgentCard`s for a gallery of templates; `CollectionPage` for a catalog a person finds, opens and adds from (the Portal's Skills and Plugins); `CatalogDrawer` with `CatalogEntry`s where the choice opens from a page. |
1012
+ | What happened | `ActivityFeed`; one run's steps are `RunTimeline` ("Activity"). |
1013
+ | Getting something set up | `NumberedSteps` for the steps on a page; `FormDrawer job="steps"` for a setup with a wait. |
1014
+ | Short form: a new record, or one or two values from a command | `FormDialog` with Field parts; Portal binds it through `ActionDialogForm`. A record in a list edits inside its row (Entity collection); a catalog uses `CatalogDrawer` and a setup in steps `FormDrawer` ([Menus and commands](#menus-and-commands)). |
1015
+ | One record looked at from a list | `RecordSheet`. |
1016
+ | Destructive confirmation | `ConfirmDialog`; the app's confirm-action adapter. |
1017
+ | Global commands or record actions | `CommandDialog` for Find; `ActionMenu` and `ActionContextMenu` for one record; `PageActions` for a page. |
1018
+ | Absence or failed read | `EmptyState`, through the composition's `empty`, `noMatch` and `failed` or `DataTable.empty`; `MessageNotice` for persistent conditions. |
1019
+ | Rich choices | `ChoiceboxGroup` with `ChoiceboxGroupItem`, in the group's own `FieldSet` and legend. |
1020
+ | Copyable identifier or command | `TextWithCopyButton` for a value, `Snippet` for a command; the prompt is the component's and is never copied. |
1021
+ | Plain code or static JSON | `CodeBlock`. |
1022
+ | A record's time | `RecordTime`: the time in its verb's sentence, or the clock time in a feed, with UTC and the local zone in its hover card; the product supplies its refresh policy. |
1023
+ | Expandable file navigation | `SidebarMenu` and `Collapsible`, following shadcn sidebar-11, with file icons and real file links or commands. Keep the nested lists semantic; selection and roving arrow-key navigation, a full tree widget, need a separately verified keyboard contract. |
1024
+ | Feedback collection | `Popover` with Field, Textarea, optional RadioGroup/Select and Button; inline rendering uses the same form parts. The product supplies the prompt, the topic choices, the permitted context and the destination; no collection backend is implied. |
1025
+ | Native media playback | `AspectRatio` around a native video with its controls, poster and caption tracks. The product supplies the source, the captions, a transcript where needed, the playback policy and the recovery when the media is unavailable; a richer player needs a concrete media requirement. |
1026
+
1027
+ Use `CopyButton`, `TextWithCopyButton`, `Snippet` and `CodeBlock` for their complete compositions. Feedback and expandable file navigation combine existing parts with product adapters. Syntax colour, full JSON or tree-widget navigation, and a JSON view's search highlighting and depth controls remain behaviour gaps: do not invent undocumented props or copy a local implementation into every caller.
1052
1028
 
1053
1029
  ### Personalization boundaries
1054
1030
 
@@ -1061,95 +1037,65 @@ The owner's decisions where Geist and ours differ:
1061
1037
  - A settings toggle, the theme included, is a draft that its section's Save applies, where Geist applies a toggle on change. The exception is a switch in a card's header: it applies at once, and that card has no Save, only its footer line.
1062
1038
  - Find goes to places only: it runs no command and has no page stack.
1063
1039
  - A wait stays still, with no spinner, loading dots or shimmer; a `Button` takes Geist's `loading` behaviour with its pending word in place of the spinner.
1064
- - `Gauge` is a control for one ratio, never a chart.
1065
1040
  - `ChoiceboxGroup`, `ClearableInput`, `Snippet` and `LoadMoreButton` are components, never recipes a page assembles.
1066
1041
  - The three-dot trigger stays 36 px, ruled by the owner on 14 October 2026.
1067
1042
 
1068
1043
  ### Controls
1069
1044
 
1070
- - **Buttons.** A button is 24, 32, 36 or 40 px high (`{spacing.control-height-xs}`, `{spacing.control-height-sm}`, `{spacing.control-height}`, `{spacing.control-height-lg}`) with `{rounded.sm}` corners, `{rounded.md}` at 40 px and `{rounded.xs-plus}` on the 24 px icon button. The label is `{typography.button}` at 32 and 36 px, `{typography.button-lg}` at 40 px and `{typography.button-xs}` at 24 px, and the inline padding is `{spacing.button-padding-inline-sm}` at 32 px, `{spacing.button-padding-inline}` at 36 px and `{spacing.button-padding-inline-lg}` at 40 px ("Layout" says where the smaller sizes may go). An icon leads or trails the label as the button's `prefix` or `suffix`; an icon-only button is named by its `aria-label`. A working button is `loading`: it says the label's -ing form in the label's place, ignores a second press, keeps focus and shows no spinner. A disabled filled button drops to the muted fill, the muted text and the hairline at full opacity; `ghost` and `link` dim instead. A link drawn as a button is a `ButtonLink`. `default` is the action (ink, white label; `{colors.dark-action}` with a `{colors.dark-action-foreground}` label in dark); `outline` and `secondary` are the quieter steps, edged with the `{colors.line}` hairline; `ghost` has no edge and serves a tertiary text action or an icon-only command (the three dots, close, collapse); `destructive` is the error pair; `link` is inline. On a colour or black ground a control takes a variant whose label clears AA there: `brandSolid` (white, ink label) or `brandGhost` (white label, no fill) on cobalto, rosso and black, and the default ink button on verde, arancio and giallo.
1045
+ `Button`'s usage page holds its sizes, variants and loading; `textLinkVariants`' holds the link treatments; `Checkbox`'s and `RadioGroup`'s hold their checked marks.
1046
+
1071
1047
  - **Dialog footers.** A dialog's Cancel sits at the footer's leading edge and its primary at the trailing edge; a dialog with one button fills the footer with it. A step back (Previous) is grouped with Cancel in one wrapper at the leading edge, so the footer keeps two ends. A choice to stop being asked is a checkbox at the end of the body, remembered by whichever action is taken, never a third button. On a phone the dialog and the confirmation are bottom sheets whose footer keeps its one row, Cancel leading and the primary trailing.
1072
- - **Tabs** switch between sibling collections or views of one scope, such as a company's Members, Invitations and Teams, or Billing's Subscriptions, Approvals and Stripe events. One shows at a time; the choice lives in the URL, as a route or `?tab=` with the first tab left out, so it survives a reload, Back and a shared link; and switching never changes whose things you are looking at. A tab never introduces a new scope: another company or Agent is another place, reached from the sidebar or a scope selector, and a choice of whose things are listed is a filter in the toolbar. A tab title is a noun with no icon and no count; the count belongs to the list's footer. A page's collections take the line tabs (`Tabs` with `TabsList variant="line"`), under the title row and above the toolbar: the current tab in ink on a 2 px ink underline, the rest muted until hover. The page's actions stay on its title row whichever tab is shown, the toolbar belongs to the tab shown and switching clears its search and page, and two lists are never stacked with a tab row between them. A section's views take `TabsNav` and `TabsLink` in the band under the top bar, the current link on the selection fill, scrolling sideways on a phone and fading out over 32 px at each end where more links stand beyond it. A set of different things, such as Nadia's company sections, is pages in the sidebar, never tabs. A pill tab (`TabsList`, `TabsNav`) is 32 px high with 12 px sides, `{typography.label-sm}` at 400 and no gap between tabs. A line tab (`variant="line"`) is 50 px high with 2 px sides, `{typography.body-sm}` at 400, 24 px between tabs and a 1 px rule under a full-width row. A row of line tabs holds at most 7 tabs on a desktop and 4 on a phone; a `TabsNav` band, which scrolls sideways on a phone, holds at most 7 at every width, as an Agent's six views do. One tab is disabled only for a permission or an empty state and says why with `disabledReason`. Label the row with `aria-label` or `aria-labelledby` and keep keyboard navigation. A choice that changes a value rather than a view uses radio or toggle semantics instead.
1073
- - **Links** read as text. A link inside a sentence, or offered alone as a section's way on, is `{colors.link}` and underlined at rest; a link that is itself the value, such as a record's name or a fact, keeps the text's colour and underlines on hover; a secondary way out beside the content, such as a card header's "see everything", uses the same blue link treatment. Breadcrumb and back navigation retain their quieter navigation treatment. The underline sits 4 px under the text.
1074
- - **Fields** draw their edge as a 1 px ring outside the box in `{colors.input}`, darker on hover and full strength with a 4 px halo on focus, `{rounded.sm}` (`{rounded.md}` at 40 px; `rounded` makes a pill), in three heights that Input, Textarea, Select, NativeSelect, Combobox, MultiSelect and InputGroup share: `{spacing.control-height-sm}`, `{spacing.control-height}` and `{spacing.control-height-lg}`. Inline padding is `{spacing.control-padding-inline}`. A prefix or suffix is a filled cell with a divider, or `plain` on the box fill, with a 14 px mark; an icon button inside a field fills the row flush to its edge, is muted until hovered and never fills. Every field has a name: a visible label of 13 px in the secondary text colour, 8 px above the box, tied by id (or `aria-label` where the name is drawn elsewhere). Colour sits in one place: an error turns the ring red, fills the halo (also while the box has focus) and colours the 13 px message under it (8 px below, with an alert mark; 16 px beside a 40 px field), and a Combobox colours its typed text; the label, fill, text, placeholder and marks stay neutral. A disabled box, and the label of a disabled control, step down in fill and text, never in opacity, and ignore hover. A field fills its container. Help is the one-line description of the card that holds the field, one setting to a card, never a line under each field; the error takes its place while it shows. A field that may stay empty says so in its label, never in its placeholder. A field validates when it loses focus, stays focusable while it saves, and the save of a `FormDialog`, `FormDrawer`, `RowEditForm` or `SettingsSection` trims the ends of its text.
1048
+ - **Tabs** switch between sibling collections or views of one scope, such as a company's Members, Invitations and Teams, or Billing's Subscriptions, Approvals and Stripe events. One shows at a time; the choice lives in the URL, as a route or `?tab=` with the first tab left out, so it survives a reload, Back and a shared link; and switching never changes whose things you are looking at. A tab never introduces a new scope: another company or Agent is another place, reached from the sidebar or a scope selector, and a choice of whose things are listed is a filter in the toolbar. A page's collections take the line tabs (`Tabs` with `TabsList variant="line"`), under the title row and above the toolbar. The page's actions stay on its title row whichever tab is shown, the toolbar belongs to the tab shown and switching clears its search and page, and two lists are never stacked with a tab row between them. A set of different things, such as Nadia's company sections, is pages in the sidebar, never tabs. A choice that changes a value rather than a view uses radio or toggle semantics instead. `Tabs`' usage page holds the tabs' geometry and words.
1049
+ - **Fields.** Input, Textarea, Select, NativeSelect, Combobox, MultiSelect and InputGroup share one box in three heights, `Input`'s, and `Field`'s label and message: their usage pages hold the geometry, the states and the validation. Help is the one-line description of the card that holds the field, one setting to a card, never a line under each field; a field that may stay empty says so in its label, never in its placeholder.
1075
1050
  - **Focus** is drawn once, by the base layer, on every `:focus-visible` element. A component never repaints it; it may move it to a proxy that stands for the focused element, as a field group does for its input.
1076
- - Enabled buttons show a pointer; a disabled control advertises nothing. On coarse or non-hover input every target is at least `{spacing.touch-target}`, while a checkbox or radio mark stays compact and its label supplies the target. An unchecked checkbox or radio mark is its `{colors.input}` edge with no fill of its own, so it shows the surface it sits on, and greys on hover unless disabled. A checked checkbox fills with the action pair; a checked radio stays an empty circle with an ink edge and an 8 px ink dot; a mixed checkbox stays unfilled with a grey dash.
1051
+ - Enabled buttons show a pointer; a disabled control advertises nothing. On coarse or non-hover input every target is at least `{spacing.touch-target}`, while a checkbox or radio mark stays compact and its label supplies the target. An unchecked checkbox or radio mark is its `{colors.input}` edge with no fill of its own, so it shows the surface it sits on, and greys on hover unless disabled.
1077
1052
 
1078
1053
  ### Cards and sections
1079
1054
 
1080
- `Card` is the product's container, at the resting height by default ("Elevation & Depth"). A row of cards is a `CardRow`: siblings share one height, and the row collapses to one column below its breakpoint. A card or settings section is composed around a real task, collection or form; its header names the content and may explain scope or consequence, and it is never a card holding only an introduction. Its one header action, a single button, badge or three dots, stays at the title's right at every width, as Vercel keeps a card's Add on its title row on a phone; only an action of several controls moves under the description in a header narrower than 28rem. A card's or a section's title carries no icon, unless the card stands for one external system, whose mark it then carries. A settings section owns its heading, a one-line description of what the setting changes, its fields and its local save. On a settings page the shape follows the data, and when it saves follows whether a half-done edit makes sense. A reversible choice (a switch, a theme, a language, a pick from a short list) applies at once, with no Save, and the product's toast confirms it; a change that fails says so in the toast and the control shows the stored value again. A typed value (a name, a URL, a server, a port, a key) gets its own card, which saves its fields through its own `save`, with its own Save in its footer: always visible and off until something in that card differs from what is saved; while saving, its pending word; a save that succeeds calls `save.saved`, and the product announces it through its own toast, a catalogue message: "Completion toasts follow `{Noun} {past-participle}`: `Blob deleted`, `Domain added`, `Environment variable saved`. Never include `successfully`; the action name implies it." (https://vercel.com/geist/toast), and Save returns to disabled with no saved state in the footer; a failure keeps every value and shows under the field it is about. A collection is a table whose Add opens a form and whose every row carries **⋯**, and a destructive action there confirms first. A status is a read-only card with the one action that fixes it. A section that reports a condition of its own (a failed payment, a trial ending) takes `tone="error"` or `tone="warning"`: a 1px border in the hue and a footer tinted in it with its line in the hue, while the title, description, fields and buttons stay neutral, and says the condition in words in the footer line. A button takes the hue of its command, not of the card: red for a permanent loss (Delete), amber for a disruptive command that can be undone (Pause, Archive). A section the person may not change takes `lockedReason`: its fields stay in view, their text muted and out of reach under a not-allowed cursor, the title and description unchanged; the footer line says why, and `unlock` is the way to get access when there is one; a locked section has no Save or header action. An object's section that edits in place swaps to the same section with Cancel before Save. Its footer is a strip one tone off the card body inside the card's edge, the `recessed` role behind a `{colors.line}` seam: `{colors.canvas-soft}` below the white card in light, and `{colors.dark-canvas}` below the resting body in dark: an optional hint on the left, only where it says something specific to that card (a limit such as "Please use 32 characters at maximum.", what a value is for, the plan a feature needs or a link to learn more) and never one sentence every card repeats, and the section's action on the right; a facts section shows labelled facts as plain pairs, the label above its value, in two equal columns, stacked on compact screens, with every label kept in place when a value is missing; a fact never gets a box of its own inside the card. A KPI card is one figure: label, value, the change as a badge in the top right, and a footer with the comparison; its label is words alone, with no icon. A figure that carries its own values over the period, as on Analytics' Activity, reads as Vercel Observability's overview tile: the label across the tile, the value with the change badge at its right, a thin line across the tile's width under them, and the comparison under the line on up to two lines that every tile of the row reserves, with no footer, so nothing in a row of five is cut. A row of five figures sits in one row from 1280 px, three over two below that, two by two with the fifth across on a tablet, and one column on a phone; it never leaves one figure alone beside a hole.
1055
+ A card or settings section is composed around a real task, collection or form; its header names the content and may explain scope or consequence, and it is never a card holding only an introduction. A card's or a section's title carries no icon, unless the card stands for one external system, whose mark it then carries. On a settings page the shape follows the data, and when it saves follows whether a half-done edit makes sense. A reversible choice (a switch, a theme, a language, a pick from a short list) applies at once, with no Save, and the product's toast confirms it; a change that fails says so in the toast and the control shows the stored value again. A collection is a table whose Add opens a form and whose every row carries **⋯**, and a destructive action there confirms first. A status is a read-only card with the one action that fixes it. A row of five figures sits in one row from 1280 px, three over two below that, two by two with the fifth across on a tablet, and one column on a phone; it never leaves one figure alone beside a hole.
1056
+
1057
+ `Card`, `CardRow`, `SettingsSection`, `SettingsFactsSection` and `KpiCard` hold their own shapes on their usage pages.
1081
1058
 
1082
1059
  ### Menus and commands
1083
1060
 
1084
- A record's primary destination is its visible name or a labelled link. Navigation is never hidden in its action menu. Keep the main task visible: Edit opens the record inside its row, as in Vercel Domains; Connect belongs beside an available connection. Additional mutations use an end-aligned `ActionMenu`, the same commands on a right-click through `ActionContextMenu`; a menu is not required when the row has no secondary mutations. Commands reach a person only through `PageActions` (a page), `ActionMenu` and `ActionContextMenu` (a record) and `AccountMenu` (the account), each taking its commands as data (`menu-owner`). The three-dot trigger is a `{spacing.control-height}` ghost icon button named for its record, "More for pec@acme.it", and comes last, after any button. A record's or a page's menu opens under its trigger at the floating height and draws, top to bottom: its first line naming the record it acts on, in muted type; the commands, each with its icon leading the label, as Geist and Vercel draw them; then, after a divider, the destructive command last, in the error colour. A command that cannot run now is disabled and stays in place with its reason under its label; a command the person may not run is locked: it stays in place, inert, with a lock at its end and the reason under its label, which is required. A menu holds about ten commands at most, and one level of submenu at most, never for a destructive command. A destructive command never becomes the loudest repeated action: it is never a button on every row. The lists of a Select, a Combobox and a MultiSelect share this geometry: a 12 px corner, a 6 px inset (8 px for MultiSelect), rows 36 px (28 px for MultiSelect) with no gap, opened 8 px from their trigger; they are solid, as every menu is ("Elevation & Depth"), and a row that cannot be chosen turns its text muted, never dimmed. Ruled by the owner on 14 October 2026. The account menu is the one exception, drawn as Vercel's below: its icons trail their labels and Sign out is in the body colour.
1061
+ A record's primary destination is its visible name or a labelled link. Navigation is never hidden in its action menu. Keep the main task visible: Edit opens the record inside its row, as in Vercel Domains; Connect belongs beside an available connection. Additional mutations use an end-aligned `ActionMenu`, the same commands on a right-click through `ActionContextMenu`; a menu is not required when the row has no secondary mutations. Commands reach a person only through `PageActions` (a page), `ActionMenu` and `ActionContextMenu` (a record) and `AccountMenu` (the account), each taking its commands as data (`menu-owner`). A menu names the record it acts on, keeps a command that cannot run in place with its reason, and puts the destructive command last after a divider; `ActionMenu`'s usage page holds how it draws them, and `AccountMenu`'s the account menu's one exception. A destructive command never becomes the loudest repeated action: it is never a button on every row.
1085
1062
 
1086
1063
  Choose the surface by where the thing being changed is shown and how big the job is, never by the page that happens to need it: the same job takes the same surface in every product. Adding a skill is the catalogue drawer in the Portal and in Nadia, and editing a task or an MCP server opens its row in both. Each surface has one owner:
1087
1064
 
1088
1065
  | The person wants to | Surface | Owner | Example |
1089
1066
  | --- | --- | --- | --- |
1090
- | Change a record shown in a list | Its row opens into the form, with Cancel and Save in the form's footer, Save off until something changes; the other rows stay where they are | `RowEditForm` in `CollectionPage`'s `editor` | Vercel Domains › Edit; a task, a mailbox, an MCP server, a channel |
1067
+ | Change a record shown in a list | Its row opens into the form | `RowEditForm` in `CollectionPage`'s `editor` | Vercel Domains › Edit; a task, a mailbox, an MCP server, a channel |
1091
1068
  | Change a fact on a record's own page or card | In place: a pencil turns the fact into its field, saved by the card's Save | `AgentProfileCard`; an `ObjectSection` that swaps to its `SettingsSection` | An Agent's name and description |
1092
1069
  | Add a record through a few fields, or change one or two values from a command | A centred dialog | `FormDialog`; the Portal's `ActionDialogForm` | New task, add a mailbox, add an MCP server |
1093
- | Choose from a catalogue | A modal sheet at the right edge: the search, then the entries, each with its Add | `CatalogDrawer` | Add a skill, in both products; Vercel Domains › Add |
1094
- | Set up in several steps with a wait | The same sheet, its progress, "Step 2 of 3", in mono over the title, which stays the job's name | `FormDrawer job="steps"` | Connect a messaging channel; Telegram's QR setup |
1095
- | Look at one record from a list, then go back to the list | A sheet from the right that leaves the page usable, its title naming the record, its facts and its payload; a press outside does not close it, Close and Escape do and give focus back to the row | `RecordSheet` | One Stripe event in Billing |
1070
+ | Choose from a catalogue | A modal sheet at the right edge | `CatalogDrawer` | Add a skill, in both products; Vercel Domains › Add |
1071
+ | Set up in several steps with a wait | The same sheet, with its progress | `FormDrawer job="steps"` | Connect a messaging channel; Telegram's QR setup |
1072
+ | Look at one record from a list, then go back to the list | A sheet from the right that leaves the page usable | `RecordSheet` | One Stripe event in Billing |
1096
1073
  | Do a workflow a person could link to | Its own page | The page's composition | Vercel Add New › Project; New Agent |
1097
1074
  | Confirm a consequence | A confirmation | `ConfirmDialog` | Remove, Disconnect |
1098
1075
 
1099
- A record already on screen is never edited in a dialog or a sheet, and a sheet is never chosen because an action takes input: a catalogue is `CatalogDrawer`, a setup in steps is `FormDrawer` with `job="steps"`, and a sheet owns its one width, 512 px from 1024 px and the window less its margins below it, which no caller sets. A `Drawer` is only the phone's bottom sheet for a short job, never a desktop surface and never a confirmation. A confirmation's title names the record as a statement, never a question: "Delete Bandi di Bergamo"; its body says what happens, and its button repeats the verb and the noun, "Delete task". A settings page is the Settings composition. These choices follow the live Vercel Domains, Projects and settings pages and the [Geist Modal](https://vercel.com/geist/modal), [Sheet](https://vercel.com/geist/sheet) and [Fieldset](https://vercel.com/geist/fieldset) patterns.
1100
-
1101
- Collection search and filters stay immediately above their rows. The title row holds the collection's primary action, unless an inline form already exposes that task. Scope selectors preserve the authorized organization or account. Filters do not compete with primary actions. The account menu identifies the signed-in person and exposes their account settings, theme and language, drawn as Vercel's: 288 px wide, opening 6 px above its trigger; a header of the name and the lines under it with the gear at its end, and no picture; then one divider and the rows, 36 px high, each its label leading and its icon or its control trailing in muted ink, Theme and Language as a small round pill of choices, a product's own reading before its icon, and Sign out last in the body colour, since signing out destroys nothing. Ruled by the owner on 16 October 2026.
1076
+ A record already on screen is never edited in a dialog or a sheet, and a sheet is never chosen because an action takes input: a catalogue is `CatalogDrawer` and a setup in steps is `FormDrawer` with `job="steps"`. A `Drawer` is only the phone's bottom sheet for a short job, never a desktop surface and never a confirmation. A settings page is the Settings composition. These choices follow the live Vercel Domains, Projects and settings pages and the [Geist Modal](https://vercel.com/geist/modal), [Sheet](https://vercel.com/geist/sheet) and [Fieldset](https://vercel.com/geist/fieldset) patterns.
1102
1077
 
1078
+ Collection search and filters stay immediately above their rows. The title row holds the collection's primary action, unless an inline form already exposes that task. Scope selectors preserve the authorized organization or account. Filters do not compete with primary actions.
1103
1079
 
1104
1080
  ### Tables and missing values
1105
1081
 
1106
- A table is native table structure: a head row of 36 px labels over 12 px of room and then its rule, so the first body row starts at the rule like every other, 40 px body rows for comparable facts (a cell that holds a control, such as the three dots, is padded just enough to keep the row at 40), every cell padded 8 px sideways (10 px above and below in the body), headings at 14 px / 20 px and weight 500, visible because people compare and sort by them. Numeric columns align to the trailing edge with tabular figures. A sortable heading is a button inside its header cell: the arrow is decorative, the cell carries `aria-sort`, and the button names the next sort. `striped`, `bordered` and `interactive` belong to the table body; an interactive, striped or bordered row rounds the outer corners of its first and last cell to 6 px.
1082
+ `Table`, `DataTable`, `TableCard`, `RecordList`, `Item` and `Tag` hold their geometry, columns and rows on their usage pages; `page-composition` refuses a `DataTable` without column headings. No heading sorts the rows, so no heading is a button: the page holds the order, through a `CollectionSort` above the list, and `aria-sort` marks the column the rows follow.
1107
1083
 
1108
- A record row is one line per record, drawn by `RecordList`, which `CollectionPage` renders, and nothing else draws one (`page-composition` refuses a `DataTable` without column headings): a 72 px entity row, with 16 px above and below, whose columns stand left to right at the widths the list declares, so a person reads every list the same way:
1109
-
1110
- | Slot | Width | Holds |
1111
- | --- | --- | --- |
1112
- | Selection | 40 px, only when the list is picked from | A box named "Select {name}" |
1113
- | Identity | The rest: the one column without a size | The record's 32 px identity mark ("Identity marks"), then its name (600) over one muted 14 px line saying what it is or where it comes from, each on one line ending in an ellipsis (`RecordIdentity`'s `detail`: "Company skill · every Agent has it") |
1114
- | Facts | Each fact's `size` | A few facts on one line, ending in an ellipsis |
1115
- | Status | `status.size` | Only the exception, the status that tells this row apart from the others; empty for the usual record |
1116
- | Time | `time.size`, tabular | The time in its verb's sentence (`RecordTime`: "Updated 3 hours ago", the date after a week, the exact moment on hover) |
1117
- | Checklist | 52 px, only when the record has steps to be ready | Its `ProgressChecklist` ring ("Status") |
1118
- | The row's command and the three dots | `commandsSize`; left out, 64 px for the three dots alone, or as wide as the widest lone menu command drawn as a button, never cutting its label | One command that is not destructive, as an `outline` button, then the row's `ActionMenu`; or a muted chevron when the row only opens |
1119
-
1120
- A status column stands only while a record in the list shows its exception, the commands column only while a record has a command, a menu or a message or the rows open, and the commands column widens for a failed action's next steps only while a row shows one, so the name and its line keep the room the row leaves. Never the three dots before the button, a decorative icon for the record's mark, a status on every row, or a destructive button on every row. An action that failed or was refused shows as its catalogue message inside its own row, with its next step, never a notice under the row, and an edit opens inside its row. A screen gives `RecordList` the records and what each one says, never its own toolbar, table, rows or menus, and anything more than a few facts lives on the record's page. A descriptive list uses `ItemGroup` and `Item` instead of table semantics: 16 px padding, a 600 title over one 20 px description with no gap, a row of 72 px, and at most two trailing controls (`size="sm"`, 32 px), the rest in the three dots. A list a person picks from starts each row with `ItemSelect`, whose box is named "Select {name}". The table inherits its enclosing surface, whose Card owns the outer edge; a visible header and every record except the last end with a quiet 1 px `border` divider. An interactive body row takes the full `accent` fill on hover, on keyboard focus within it and while its menu is open; a passive table does not imply an action on hover; a selected row takes `muted`; headers and footers never highlight. A collection fits its container without sideways scrolling: each column is as wide as its definition's `size` in pixels, the one column without a size takes what is left, and a card narrower than the sized columns plus 160 px for the unsized one keeps the identity and the actions in their columns and takes every other column out of the row, as Vercel's lists read on a phone: each joins one muted line under the name, joined by a middle dot and ending in an ellipsis, with no labels on screen, or is dropped when its column says `compact: "dropped"`, for a fact the record's page holds and the phone can spare; a status keeps its own mark. A row on a phone is the name over that one line, never a stack of labelled values: in a record row the facts join the identity's own line after a middle dot, so the record stays one 72 px row, the name over one line. A table whose every column must stay, a settings matrix, names them all in `primaryColumns` and lays out by its content on a narrow card, so its names wrap beside the columns. A titled table card carries its toolbar on the title's row, title left and controls right, as Vercel heads a card's list; under 576 px the toolbar takes its own line under the title. A value too long for its column holds one line and ends in an ellipsis, whole in its title, as a long name does. A document's table, such as a legal page's, is read whole instead: its columns take the `wraps` option, so their words wrap and the row grows, and it shows its header row. In the compact layout every cell but the identity wraps between words. Text anywhere in the product breaks between words, and inside a word only when that word alone cannot fit a line; a machine string such as a hash or a key breaks at any character in a row of wrapped text, or is cut in the middle with `MiddleTruncate` on one line. One categorical fact in a row, such as an installation or a partner, is a `Tag`: a 24 px pill with its mark, dashed when the fact states an absence. An app in a row is its brand mark, its identity mark at 32 px.
1084
+ A collection fits its container without sideways scrolling, and on a phone a row is the name over one line, never a stack of labelled values, where a status keeps its own mark; `DataTable`'s and `RecordList`'s usage pages hold how their columns give way. Text anywhere in the product breaks between words, and inside a word only when that word alone cannot fit a line; a machine string such as a hash or a key breaks at any character in wrapped text, or is cut in the middle with `MiddleTruncate` on one line.
1121
1085
 
1122
1086
  An unknown or not-applicable value in a table, a comparison list or a set of facts is a muted `—` with screen-reader text, in the same slot as a present value; zero stays zero. An absent report is not `None`: `None` is only for a complete report that found nothing. A stale value keeps its last reading with a short `Stale` qualifier. Loading, failure, missing permission and actionable setup each keep their own visible state and never turn into a dash. Essential reasons and actions never live only in a hover tooltip.
1123
1087
 
1124
1088
  ### Status
1125
1089
 
1126
- A status is one `StatusBadge`: a mark and a word in one of five tones, so it reads without colour.
1127
-
1128
- | Tone | Means | Pair |
1129
- | --- | --- | --- |
1130
- | Gray | Default, and every terminal state that needs no action | `{colors.canvas-soft-2}` / `{colors.body}` |
1131
- | Green | Healthy, complete | `{colors.success-soft}` / `{colors.success-deep}` |
1132
- | Red | Unusable, failed | `{colors.error-soft}` / `{colors.error-deep}` |
1133
- | Amber | Waiting on a person's decision | `{colors.warning-soft}` / `{colors.warning-deep}` |
1134
- | Blue | A step in motion | `{colors.info-soft}` / `{colors.info-deep}` |
1090
+ A status is one `StatusBadge`: a mark and a word in one of five tones, so it reads without colour. `StatusBadge`'s usage page holds the tones; `ProgressChecklist`'s holds how far a record is through the steps that make it ready.
1135
1091
 
1136
1092
  The Portal, Nadia and Alveare draw every status with the one `StatusBadge` in `@nadicodeai/ui`, which owns the tones and their marks; each app keeps only its value-to-tone map in one module, and nothing else chooses a status colour. A status never moves.
1137
1093
 
1138
- How far a record is through the steps that make it ready is a `ProgressChecklist`, as Vercel ends a project's row with its Production Checklist: a 24 px blue `Gauge`, its track and its arc the share of steps done, a whole ring when all are, with the double check inside in ink, on a 36 px ghost button before the row's commands whose tooltip names the checklist and how far it is ("Customer setup 3/4"), that opens the steps in a sheet at the right: the ring, the title, one line, then a card per step with its icon on a tile of its hue, its title, one line and the outline button that leads where it is done; every step is the same hairline card, and a done step differs only by the blue Done chip after its title and by having no button, so the steps still to do lead the eye. A step is done because the thing it asks for exists, so the checklist remembers nothing and has no Skip. The step's hue says which kind of step it is, never whether it is done.
1139
-
1140
1094
  In a list or a feed a status shows only for the exception: the record or the event that differs from the others, such as a failure, a revocation or a wait for a person's decision. The usual state shows nothing, so a status never stands on every row. An object's own status sits beside its name in the path ("Product shells").
1141
1095
 
1142
1096
  ### Activity
1143
1097
 
1144
- What happened has one shape in every product, `ActivityFeed`, and an event is one row: its time, the actor's mark, one sentence, then one muted line of detail.
1145
-
1146
- - **Time.** The time of day in the viewer's own zone, tabular (`RecordTime`); UTC and the local zone in its hover card. One rule for every feed, never a time printed in UTC.
1147
- - **Actor.** The actor's identity mark at 24 px: a person's initials, an Agent's real orb, an Installation's tile. The system's own work has no mark, and never takes an orb.
1148
- - **Sentence.** The actor's name, the action, then the object as a link that keeps the text's colour; a status after it only for the exception.
1149
- - **Order.** Rows group under their day, its name over its date, newest first, then "Show older events", never a page count. The search and filters stand right above the rows and live in the URL.
1150
- - **Frame.** The tab is the feed's title: the feed adds no card header of its own. It loads as still skeleton rows in the row's shape; an empty feed is one quiet line, never a picture; a failed read is the warning mark and Retry, never "no events".
1151
-
1152
- One run's steps are its run timeline (`RunTimeline`): a neutral rail whose nodes mark done, current and to come and never grade a step; the outcome that needs a person is the `StatusBadge` inside its step, "Needs your decision", and each step's time stands in the feed's column.
1098
+ What happened has one shape in every product, `ActivityFeed`, whose usage page holds its row and frame; one run's steps are `RunTimeline`'s.
1153
1099
 
1154
1100
  ### System activity
1155
1101
 
@@ -1162,11 +1108,11 @@ Who does the work decides how it looks. An Agent's work is that Agent's orb in i
1162
1108
  | Measurable | A progress line with a written count, "Step 2 of 3" or "12 of 40 MB". Never a percentage. A ratio of a fixed maximum is a `Gauge`, which prints the bare number and leaves the unit to its label. |
1163
1109
  | A page load | Still skeleton placeholders in the shape of the content, inside one `SkeletonRegion`, never pulsing and never paired with a waiting line. |
1164
1110
 
1165
- One wait gets one indicator. A request waiting for a person is not a wait: queued, paused, blocked, failed and done take the status or feedback treatment. A wait is ink; info blue never marks one. Under reduced motion the waiting line breathes in opacity instead of travelling.
1111
+ One wait gets one indicator. A request waiting for a person is not a wait: queued, paused, blocked, failed and done take the status or feedback treatment. A wait is ink; info blue never marks one.
1166
1112
 
1167
1113
  ### Failure surfaces
1168
1114
 
1169
- A page that cannot show what was asked for opens with a picture, the way a Portal email does: one whole cut-out in art direction's editorial register, centred above the words, whose idea says what happened. The picture carries a label, the way a museum labels a work: its title says what the figure is doing, which is the page's one joke, and a mono line gives its materials and year. The label stands centred under the picture at every width. Below the label, on the same centre line, stand a mono eyebrow naming the situation, the page's one heading, one plain sentence, and the recovery action, which leads. The cut-out's transparent ground takes the page's own, so one file serves light and dark, and it stays whole at every width: it scales down with the column and is never cropped by the viewport. No orb turns on a failure page.
1115
+ A page that cannot show what was asked for opens with a picture, the way a Portal email does: one whole cut-out in art direction's editorial register, centred above the words, whose idea says what happened. The picture carries its label (`LabelledPicture`). Below the label, on the same centre line, stand a mono eyebrow naming the situation, the page's one heading, one plain sentence, and the recovery action, which leads. The cut-out's transparent ground takes the page's own, so one file serves light and dark, and it stays whole at every width: it scales down with the column and is never cropped by the viewport. No orb turns on a failure page.
1170
1116
 
1171
1117
  A Portal email, whether the Portal or Knock sends it, follows the same order under its picture, in one structure: the picture, the eyebrow and a short heading keep the centre line; one sentence of consequence, a list between hairlines when the email is about several things (each a semibold heading over its one sentence), a table of its facts between hairlines when it has facts, one to-do sentence, the fallback link and the footer stand flush left; the button stays centred.
1172
1118
 
@@ -1179,39 +1125,21 @@ An absence inside a product's own chrome, such as an address or a record the Por
1179
1125
 
1180
1126
  ### Agent orbs
1181
1127
 
1182
- An Agent is its orb, in every product; there is no other Agent picture. An orb is one of seven forms, `ribbon`, `lattice`, `orbit`, `globe`, `network`, `braid` and `morph`, in one colour a person chose: cobalto, verde or arancio, or none, which draws it in the text colour. Giallo is not offered because it fails 3:1 on white; rosso is not offered because it is the error red. nadicode's own two Agents are the only ones with a figure nobody chooses, the same at every size and in every state: Nadia is her striped ribbon, the form `nadia`, azzurro, verde, the text colour and rosso along its whole length; Sofia, the Agent that looks up a new company, is her sister, the form `sofia`, the same ribbon turned to face Nadia's, striped cobalto, giallo, the text colour and arancio, the identity hues Nadia does not wear. Neither figure is ever stored as an Agent's look.
1128
+ An Agent is its orb, in every product; there is no other Agent picture. `AgentOrb`'s usage page holds its forms, colours, sizes, states and reduced motion, and `AgentListRow`'s holds an Agent's row in Nadia's list. A surface that only knows a machine checked in draws idle.
1183
1129
 
1184
- - **Stored look.** An Agent's form is stored as `orb:<form>` and its colour as the identity hex or nothing; a template declares its starting look the same way. The root Agent always draws as Nadia. An unknown form draws `orbit`, an unknown colour the text colour, and a stored rosso draws arancio. The look is a person's choice and is never derived from a name, id or role.
1185
- - **Sizes** are 20, 24, 32, 40, 64, 96 and 160 px. On a photograph an orb takes the stronger `over-picture` weight; everywhere else it is `plain`.
1186
- - **States.** Idle turns at its form's own pace and breathes. Working and thinking move with their cue, the ribbon around working and three dots above thinking, waiting holds, blocked makes one interrupted effort and stops, and done celebrates once and settles back into idle. A surface that only knows a machine checked in draws idle. No state draws a circle around the orb, and a label names the state wherever the exact state matters.
1187
- - **In Nadia's list.** Each Agent's row (`AgentListRow`) is 52 px high: her orb at 32 px in her real state, her name, and one state line in words, "Working", "Idle" or "Needs you". Only "Needs you" takes a 6 px status dot before its words; the orb shows working by itself. In the list's narrow column (from 640 to 899 px) each orb stands at 40 px in a 48 px square, named with its state line in a tooltip. Only an orb moves in the list.
1188
- - **Status and ambient.** An orb that stands for an Agent shows its real state. An orb in decorative artwork, such as a sign-in picture, takes ambient motion: its form moves at its own pace with no state pose or cue, and it claims no activity.
1189
- - **Reduced motion.** Movement stops; a thinking or working orb breathes between full ink and 0.45 opacity so busy still reads as busy, and an ambient orb holds still.
1190
1130
  - **In a conversation.** Her states are the conversation's own: the orb in its header follows them, and every mark of her activity in the thread is her own orb in her real state, never another Agent's figure, a spinner, a ticker or a pulse. Her steps and tool calls hold still and say in words what they do or did, with a tool's kind as a decorative icon beside the words and no state of its own; the outcome of a step or a request is the `StatusBadge`, and a failed turn is the `MessageNotice`. A conversation has two actors, her and the person, and every wait in it belongs to one of them. Everything she does, including what follows the person's approval or answer, is her orb in its state: working once Run is pressed, thinking once a question is answered, waiting while an approval or a question waits for the person, blocked when a turn fails, done once when she finishes. The person's own action in progress shows on what they made: a file stays veiled on its own chip until it is uploaded, and fills only where its upload reports progress; the microphone shows it records. A pressed control keeps its place and changes only its word, a button through its `loading`. The waiting line is for the window's own loading, outside the conversation. Ruled by the owner on 11 October 2026: the Agent's states are the chat's default states, not random icons; and on 13 October 2026: the person never meets "the app" in a conversation, only the Agent.
1191
1131
 
1192
- Text sits beside an orb, never on it.
1193
-
1194
1132
  ### Agent card
1195
1133
 
1196
- An Agent is shown as one card shape wherever it appears as an object, in the Portal's Library and Nadia's gallery alike, as Vercel's Templates gallery draws a template: the name over the job, cut after two lines, the small meta and a status row under them, the one action at the name's right, and under them the preview tile, inset 32 px from the leading edge and running off the trailing and bottom edges, her orb at 96 px in its middle on the recessed ground. On an Agent's own page the name is the page heading.
1134
+ An Agent shown as an object, in the Portal's Library and Nadia's gallery alike, is one `AgentCard`, whose usage page holds its shape.
1197
1135
 
1198
1136
  ### Company card
1199
1137
 
1200
- The sign-up page draws the account being made as the company's card: one object of the ID-1 proportion, dark by nature in every theme, carrying on its top line the wordmark and, once Sofia has found it on the company's site, the company's own logo after a hairline, stamped in `{colors.identity-white}` as one ink (a company with no logo found has the wordmark alone); below, the holder's name and the company's domain. No Agent stands on it, and nothing sits in the engraving: a guilloché engraving in `{colors.identity-white}` at 10%. Beside a wide form it fills in as the form is typed and answers the pointer; on a phone the form keeps the screen, and the card appears once it is issued. It is the one object besides an Agent that moves on its own, once: when it first appears, and again when the account is asked for and it is issued, it turns in the light in 2.2 s, leaning one way and back while the light crosses it from corner to corner, so a person sees it is a card that answers the hand, and then it is still. The foil wakes as the pointer comes within one card width and eases toward it; a pointer that near ends the turn, and a pointer anywhere else on the page moves nothing. The account's Profile shows the person the same card, and the Partner invitation panel opens with its card, the Partner edition, on the identity plane, as the invitation it shares. Arriving through a Partner's referral link, the sign-up plane stands the Partner's card behind the one being made, leaning back, so the person's own card stays in front and whole; the Partner's email invitation dialog shows the card the invited company will be issued. Where the browser has WebGPU, its foil is vgpu's holographic card shader: matte graphite (`{colors.card-graphite}`, its own tone, so the card keeps its look whatever the heights do) whose etched contours diffract the pointer's light, turning toward it by at most 9°. The diffraction and its pearlescent wash fall in pure bands of the identity hues, never a colour between two of them: a company's card takes the cool half, cobalto, azzurro and verde, and a Partner's the warm half, giallo, arancio and rosso. Without WebGPU the engraving catches the light as flat, hard-edged stripes of the same hues. Under reduced motion the card keeps still and the light still finds the foil. The form owns every word the card repeats, so the card is hidden from assistive technology.
1138
+ The company's card is `CompanyCard`, whose usage page holds where it appears (sign-up, the account's Profile, the Partner invitation), its face, foil and motion.
1201
1139
 
1202
1140
  ### Identity marks
1203
1141
 
1204
- An identity mark says which thing a record is, and `IdentityMark` is its one owner: a row's, an object's, a feed's and a sidebar's mark is one of its kinds, never a decorative icon. A list gets its richness from real identity marks, never from decoration.
1205
-
1206
- | Kind | The mark |
1207
- | --- | --- |
1208
- | An Agent | Her real orb from the orb engine, at its largest size not above the mark's, with no tile and no ring; never a painted circle ("Agent orbs") |
1209
- | An app | Its own logo in the tile |
1210
- | A company | Its logo: the one white ink Sofia keeps from its site, on a black tile (`{colors.identity-ink}`) in both themes, as its card stamps it, or a square SVG icon in its own colours filling the tile, as Vercel shows a project's favicon; its two initials in a ringed tile on the card ground until a logo is found |
1211
- | A person | Their picture, or their initials, round |
1212
- | A record of a kind | The kind's own icon in a ringed tile on the card ground, the same icon its section has in the navigation: Mail for a mailbox, a server for an MCP server, a key for a password. A Library skill takes the purple category ground with the Skills icon and a plugin the `info-soft` ground with the Plugins icon, both without a ring, so the ground says which kind it is; an entry that has its own object picture draws it on that ground in the icon's place |
1213
-
1214
- Initials stand only for a company or a person. The mark is 32 px in a record's row, 64 px at the head of an object's page, 40 px on a Library entry, 24 px in a feed and on a sidebar's identity line, and 28 px for the company at the top of Nadia's list; a tile takes 6 px corners, 8 px at 40 and 64.
1142
+ An identity mark says which thing a record is, and `IdentityMark` is its one owner: a row's, an object's, a feed's and a sidebar's mark is one of its kinds, never a decorative icon. A list gets its richness from real identity marks, never from decoration. `IdentityMark`'s usage page holds its kinds and sizes.
1215
1143
 
1216
1144
  A recognised app, platform or model maker is drawn with its genuine mark beside its readable name; an unknown one keeps its name and a text monogram, never a guessed logo. A model's maker is 24 px, in a model list, a routing table or a setting, and on the website's lists of apps and makers. A mark grows only where it stands for the object itself, as on its card or at the head of its page, and there every surface of that object draws it at one size. A mark identifies and never replaces the label, and its colour identifies the maker, never a connection's health. Marks are used nominatively, to name a platform, an integration or a provider, never next to wording that implies endorsement. A brand mark never stands in for a UI icon, and a UI icon never stands in for a brand; a catalogue icon is one of the catalogue's fixed set, never a brand mark, and an app keeps its brand mark. In a chart, a model is named by its label, and marks stay out of legends.
1217
1145
 
@@ -1225,9 +1153,9 @@ These three ship as framework-neutral CSS; `@nadicodeai/ui` wraps the last two f
1225
1153
 
1226
1154
  **`dotted-field`** — the ground of a canvas where a person or an Agent places and arranges objects: a diagram of an Agent's work, a board of its outputs. The dots say that things here can be moved, so the field never sits behind a dashboard, a list, settings or a website section, and it never stands in for elevation.
1227
1155
 
1228
- **`usage-meter`** — consumption shown against its allowance: tokens, cost, latency or runs, with the figure printed.
1156
+ **`usage-meter`** — consumption against its allowance; `UsageMeter`'s usage page holds the rest.
1229
1157
 
1230
- **`run-timeline`** — the vertical thread of an Agent's run, a handoff or a workflow, one step per row.
1158
+ **`run-timeline`** — the thread of an Agent's run, one step per row; `RunTimeline`'s usage page holds the rest.
1231
1159
 
1232
1160
  ## Iconography
1233
1161
 
@@ -1293,1560 +1221,10 @@ The clear space is one stroke width of the rendered logo on every side. The mini
1293
1221
  - Do say a status with a `StatusBadge` tone, a mark and a word. Don't use colour alone or a bare dot.
1294
1222
  - Do open a failure page with one whole cut-out whose idea says what happened, labelled like a work in a museum, above the words on one centre line, with the recovery action leading and the diagnostic code only for a request that stopped. Don't apologise, don't crop the picture, don't joke about lost work, and don't put an orb on a failure page.
1295
1223
  - Do use the dotted field only on a canvas where things are arranged. Don't put it behind lists, dashboards, settings or website sections.
1296
- - Do build a page from its composition, put the row's command before its three dots, and give a record its identity mark. Don't put the three dots before the button, a decorative icon in place of a record's mark, a status on every row or a destructive button on every row.
1297
- - Do name a tab with a noun, and keep a list's count in its footer. Don't put a count in a tab, or switch whose things are shown with a tab.
1224
+ - Do build a page from its composition. Don't assemble a page's header, toolbar, rows or menus by hand.
1298
1225
  - Do put the path alone in the top bar and the page's actions on its title row. Don't put an action in the top bar or a second sidebar beside the first.
1299
- - Do add a rule here before a product needs it. Don't restate a rule in another document; link to it.
1226
+ - Do add a shared rule here, and a component's own rule to its usage page, before a product needs it. Don't restate a rule in another document; link to it.
1300
1227
 
1301
1228
  ## Token extension
1302
1229
 
1303
- The YAML front matter holds the four groups the DESIGN.md format defines: colors, typography, rounded and spacing. Everything else is authored in the one `json design-tokens` fence below, in the W3C design-tokens format: `core.material` (the scrim and glass values, a `dark-` value beside one that differs in dark), `core.elevation` (the four heights and the glass edge, per mode), `core.motion` (durations and easings), `semantic` (the shared colour roles and radius) and `register.website` (the website column). An alias is written `{core.color.canvas}`. In CSS a token is `--nc-<name>`, an elevation is reached only through `nc-elevation-<height>`, and Tailwind names the values through the `nc-` and `nadicode-` namespaces. How the build turns this file into CSS and token files is described in `packages/design-system/README.md`.
1304
-
1305
- ```json design-tokens
1306
- {
1307
- "core": {
1308
- "material": {
1309
- "$type": "number",
1310
- "scrim-opacity": {
1311
- "$value": 0.8
1312
- },
1313
- "dark-scrim-opacity": {
1314
- "$value": 0.6
1315
- },
1316
- "glass-opacity": {
1317
- "$value": 0.72
1318
- },
1319
- "glass-saturation": {
1320
- "$value": 1.8
1321
- },
1322
- "glass-blur": {
1323
- "$type": "dimension",
1324
- "$value": {
1325
- "value": 20,
1326
- "unit": "px"
1327
- }
1328
- }
1329
- },
1330
- "elevation": {
1331
- "$type": "shadow",
1332
- "light": {
1333
- "resting": {
1334
- "$value": [
1335
- {
1336
- "offsetX": {
1337
- "value": 0,
1338
- "unit": "px"
1339
- },
1340
- "offsetY": {
1341
- "value": 0,
1342
- "unit": "px"
1343
- },
1344
- "blur": {
1345
- "value": 0,
1346
- "unit": "px"
1347
- },
1348
- "spread": {
1349
- "value": 1,
1350
- "unit": "px"
1351
- },
1352
- "color": {
1353
- "colorSpace": "srgb",
1354
- "components": [
1355
- 0,
1356
- 0,
1357
- 0
1358
- ],
1359
- "alpha": 0.08
1360
- }
1361
- },
1362
- {
1363
- "offsetX": {
1364
- "value": 0,
1365
- "unit": "px"
1366
- },
1367
- "offsetY": {
1368
- "value": 2,
1369
- "unit": "px"
1370
- },
1371
- "blur": {
1372
- "value": 2,
1373
- "unit": "px"
1374
- },
1375
- "spread": {
1376
- "value": 0,
1377
- "unit": "px"
1378
- },
1379
- "color": {
1380
- "colorSpace": "srgb",
1381
- "components": [
1382
- 0,
1383
- 0,
1384
- 0
1385
- ],
1386
- "alpha": 0.04
1387
- }
1388
- }
1389
- ]
1390
- },
1391
- "raised": {
1392
- "$value": [
1393
- {
1394
- "offsetX": {
1395
- "value": 0,
1396
- "unit": "px"
1397
- },
1398
- "offsetY": {
1399
- "value": 0,
1400
- "unit": "px"
1401
- },
1402
- "blur": {
1403
- "value": 0,
1404
- "unit": "px"
1405
- },
1406
- "spread": {
1407
- "value": 1,
1408
- "unit": "px"
1409
- },
1410
- "color": {
1411
- "colorSpace": "srgb",
1412
- "components": [
1413
- 0,
1414
- 0,
1415
- 0
1416
- ],
1417
- "alpha": 0.08
1418
- }
1419
- },
1420
- {
1421
- "offsetX": {
1422
- "value": 0,
1423
- "unit": "px"
1424
- },
1425
- "offsetY": {
1426
- "value": 2,
1427
- "unit": "px"
1428
- },
1429
- "blur": {
1430
- "value": 2,
1431
- "unit": "px"
1432
- },
1433
- "spread": {
1434
- "value": 0,
1435
- "unit": "px"
1436
- },
1437
- "color": {
1438
- "colorSpace": "srgb",
1439
- "components": [
1440
- 0,
1441
- 0,
1442
- 0
1443
- ],
1444
- "alpha": 0.04
1445
- }
1446
- },
1447
- {
1448
- "offsetX": {
1449
- "value": 0,
1450
- "unit": "px"
1451
- },
1452
- "offsetY": {
1453
- "value": 8,
1454
- "unit": "px"
1455
- },
1456
- "blur": {
1457
- "value": 16,
1458
- "unit": "px"
1459
- },
1460
- "spread": {
1461
- "value": -4,
1462
- "unit": "px"
1463
- },
1464
- "color": {
1465
- "colorSpace": "srgb",
1466
- "components": [
1467
- 0,
1468
- 0,
1469
- 0
1470
- ],
1471
- "alpha": 0.08
1472
- }
1473
- }
1474
- ]
1475
- },
1476
- "floating": {
1477
- "$value": [
1478
- {
1479
- "offsetX": {
1480
- "value": 0,
1481
- "unit": "px"
1482
- },
1483
- "offsetY": {
1484
- "value": 0,
1485
- "unit": "px"
1486
- },
1487
- "blur": {
1488
- "value": 0,
1489
- "unit": "px"
1490
- },
1491
- "spread": {
1492
- "value": 1,
1493
- "unit": "px"
1494
- },
1495
- "color": {
1496
- "colorSpace": "srgb",
1497
- "components": [
1498
- 0,
1499
- 0,
1500
- 0
1501
- ],
1502
- "alpha": 0.08
1503
- }
1504
- },
1505
- {
1506
- "offsetX": {
1507
- "value": 0,
1508
- "unit": "px"
1509
- },
1510
- "offsetY": {
1511
- "value": 1,
1512
- "unit": "px"
1513
- },
1514
- "blur": {
1515
- "value": 1,
1516
- "unit": "px"
1517
- },
1518
- "spread": {
1519
- "value": 0,
1520
- "unit": "px"
1521
- },
1522
- "color": {
1523
- "colorSpace": "srgb",
1524
- "components": [
1525
- 0,
1526
- 0,
1527
- 0
1528
- ],
1529
- "alpha": 0.02
1530
- }
1531
- },
1532
- {
1533
- "offsetX": {
1534
- "value": 0,
1535
- "unit": "px"
1536
- },
1537
- "offsetY": {
1538
- "value": 4,
1539
- "unit": "px"
1540
- },
1541
- "blur": {
1542
- "value": 8,
1543
- "unit": "px"
1544
- },
1545
- "spread": {
1546
- "value": -4,
1547
- "unit": "px"
1548
- },
1549
- "color": {
1550
- "colorSpace": "srgb",
1551
- "components": [
1552
- 0,
1553
- 0,
1554
- 0
1555
- ],
1556
- "alpha": 0.06
1557
- }
1558
- },
1559
- {
1560
- "offsetX": {
1561
- "value": 0,
1562
- "unit": "px"
1563
- },
1564
- "offsetY": {
1565
- "value": 16,
1566
- "unit": "px"
1567
- },
1568
- "blur": {
1569
- "value": 32,
1570
- "unit": "px"
1571
- },
1572
- "spread": {
1573
- "value": -8,
1574
- "unit": "px"
1575
- },
1576
- "color": {
1577
- "colorSpace": "srgb",
1578
- "components": [
1579
- 0,
1580
- 0,
1581
- 0
1582
- ],
1583
- "alpha": 0.1
1584
- }
1585
- }
1586
- ]
1587
- },
1588
- "modal": {
1589
- "$value": [
1590
- {
1591
- "offsetX": {
1592
- "value": 0,
1593
- "unit": "px"
1594
- },
1595
- "offsetY": {
1596
- "value": 0,
1597
- "unit": "px"
1598
- },
1599
- "blur": {
1600
- "value": 0,
1601
- "unit": "px"
1602
- },
1603
- "spread": {
1604
- "value": 1,
1605
- "unit": "px"
1606
- },
1607
- "color": {
1608
- "colorSpace": "srgb",
1609
- "components": [
1610
- 0,
1611
- 0,
1612
- 0
1613
- ],
1614
- "alpha": 0.08
1615
- }
1616
- },
1617
- {
1618
- "offsetX": {
1619
- "value": 0,
1620
- "unit": "px"
1621
- },
1622
- "offsetY": {
1623
- "value": 8,
1624
- "unit": "px"
1625
- },
1626
- "blur": {
1627
- "value": 24,
1628
- "unit": "px"
1629
- },
1630
- "spread": {
1631
- "value": -12,
1632
- "unit": "px"
1633
- },
1634
- "color": {
1635
- "colorSpace": "srgb",
1636
- "components": [
1637
- 0,
1638
- 0,
1639
- 0
1640
- ],
1641
- "alpha": 0.12
1642
- }
1643
- },
1644
- {
1645
- "offsetX": {
1646
- "value": 0,
1647
- "unit": "px"
1648
- },
1649
- "offsetY": {
1650
- "value": 32,
1651
- "unit": "px"
1652
- },
1653
- "blur": {
1654
- "value": 72,
1655
- "unit": "px"
1656
- },
1657
- "spread": {
1658
- "value": -12,
1659
- "unit": "px"
1660
- },
1661
- "color": {
1662
- "colorSpace": "srgb",
1663
- "components": [
1664
- 0,
1665
- 0,
1666
- 0
1667
- ],
1668
- "alpha": 0.12
1669
- }
1670
- }
1671
- ]
1672
- },
1673
- "glass": {
1674
- "$value": [
1675
- {
1676
- "offsetX": {
1677
- "value": 0,
1678
- "unit": "px"
1679
- },
1680
- "offsetY": {
1681
- "value": 1,
1682
- "unit": "px"
1683
- },
1684
- "blur": {
1685
- "value": 0,
1686
- "unit": "px"
1687
- },
1688
- "spread": {
1689
- "value": 0,
1690
- "unit": "px"
1691
- },
1692
- "color": {
1693
- "colorSpace": "srgb",
1694
- "components": [
1695
- 1,
1696
- 1,
1697
- 1
1698
- ],
1699
- "alpha": 0.75
1700
- },
1701
- "inset": true
1702
- },
1703
- {
1704
- "offsetX": {
1705
- "value": 0,
1706
- "unit": "px"
1707
- },
1708
- "offsetY": {
1709
- "value": 0,
1710
- "unit": "px"
1711
- },
1712
- "blur": {
1713
- "value": 0,
1714
- "unit": "px"
1715
- },
1716
- "spread": {
1717
- "value": 1,
1718
- "unit": "px"
1719
- },
1720
- "color": {
1721
- "colorSpace": "srgb",
1722
- "components": [
1723
- 1,
1724
- 1,
1725
- 1
1726
- ],
1727
- "alpha": 0.35
1728
- },
1729
- "inset": true
1730
- },
1731
- {
1732
- "offsetX": {
1733
- "value": 0,
1734
- "unit": "px"
1735
- },
1736
- "offsetY": {
1737
- "value": 0,
1738
- "unit": "px"
1739
- },
1740
- "blur": {
1741
- "value": 0,
1742
- "unit": "px"
1743
- },
1744
- "spread": {
1745
- "value": 1,
1746
- "unit": "px"
1747
- },
1748
- "color": {
1749
- "colorSpace": "srgb",
1750
- "components": [
1751
- 0,
1752
- 0,
1753
- 0
1754
- ],
1755
- "alpha": 0.08
1756
- }
1757
- },
1758
- {
1759
- "offsetX": {
1760
- "value": 0,
1761
- "unit": "px"
1762
- },
1763
- "offsetY": {
1764
- "value": 4,
1765
- "unit": "px"
1766
- },
1767
- "blur": {
1768
- "value": 8,
1769
- "unit": "px"
1770
- },
1771
- "spread": {
1772
- "value": -4,
1773
- "unit": "px"
1774
- },
1775
- "color": {
1776
- "colorSpace": "srgb",
1777
- "components": [
1778
- 0,
1779
- 0,
1780
- 0
1781
- ],
1782
- "alpha": 0.06
1783
- }
1784
- },
1785
- {
1786
- "offsetX": {
1787
- "value": 0,
1788
- "unit": "px"
1789
- },
1790
- "offsetY": {
1791
- "value": 16,
1792
- "unit": "px"
1793
- },
1794
- "blur": {
1795
- "value": 32,
1796
- "unit": "px"
1797
- },
1798
- "spread": {
1799
- "value": -8,
1800
- "unit": "px"
1801
- },
1802
- "color": {
1803
- "colorSpace": "srgb",
1804
- "components": [
1805
- 0,
1806
- 0,
1807
- 0
1808
- ],
1809
- "alpha": 0.08
1810
- }
1811
- }
1812
- ]
1813
- }
1814
- },
1815
- "dark": {
1816
- "resting": {
1817
- "$value": [
1818
- {
1819
- "offsetX": {
1820
- "value": 0,
1821
- "unit": "px"
1822
- },
1823
- "offsetY": {
1824
- "value": 0,
1825
- "unit": "px"
1826
- },
1827
- "blur": {
1828
- "value": 0,
1829
- "unit": "px"
1830
- },
1831
- "spread": {
1832
- "value": 1,
1833
- "unit": "px"
1834
- },
1835
- "color": {
1836
- "colorSpace": "srgb",
1837
- "components": [
1838
- 1,
1839
- 1,
1840
- 1
1841
- ],
1842
- "alpha": 0.145
1843
- }
1844
- },
1845
- {
1846
- "offsetX": {
1847
- "value": 0,
1848
- "unit": "px"
1849
- },
1850
- "offsetY": {
1851
- "value": 0,
1852
- "unit": "px"
1853
- },
1854
- "blur": {
1855
- "value": 0,
1856
- "unit": "px"
1857
- },
1858
- "spread": {
1859
- "value": 2,
1860
- "unit": "px"
1861
- },
1862
- "color": {
1863
- "colorSpace": "srgb",
1864
- "components": [
1865
- 0,
1866
- 0,
1867
- 0
1868
- ],
1869
- "alpha": 1
1870
- }
1871
- },
1872
- {
1873
- "offsetX": {
1874
- "value": 0,
1875
- "unit": "px"
1876
- },
1877
- "offsetY": {
1878
- "value": 1,
1879
- "unit": "px"
1880
- },
1881
- "blur": {
1882
- "value": 2,
1883
- "unit": "px"
1884
- },
1885
- "spread": {
1886
- "value": 0,
1887
- "unit": "px"
1888
- },
1889
- "color": {
1890
- "colorSpace": "srgb",
1891
- "components": [
1892
- 0,
1893
- 0,
1894
- 0
1895
- ],
1896
- "alpha": 0.5
1897
- }
1898
- }
1899
- ]
1900
- },
1901
- "raised": {
1902
- "$value": [
1903
- {
1904
- "offsetX": {
1905
- "value": 0,
1906
- "unit": "px"
1907
- },
1908
- "offsetY": {
1909
- "value": 1,
1910
- "unit": "px"
1911
- },
1912
- "blur": {
1913
- "value": 0,
1914
- "unit": "px"
1915
- },
1916
- "spread": {
1917
- "value": 0,
1918
- "unit": "px"
1919
- },
1920
- "color": {
1921
- "colorSpace": "srgb",
1922
- "components": [
1923
- 1,
1924
- 1,
1925
- 1
1926
- ],
1927
- "alpha": 0.06
1928
- },
1929
- "inset": true
1930
- },
1931
- {
1932
- "offsetX": {
1933
- "value": 0,
1934
- "unit": "px"
1935
- },
1936
- "offsetY": {
1937
- "value": 0,
1938
- "unit": "px"
1939
- },
1940
- "blur": {
1941
- "value": 0,
1942
- "unit": "px"
1943
- },
1944
- "spread": {
1945
- "value": 1,
1946
- "unit": "px"
1947
- },
1948
- "color": {
1949
- "colorSpace": "srgb",
1950
- "components": [
1951
- 1,
1952
- 1,
1953
- 1
1954
- ],
1955
- "alpha": 0.16
1956
- }
1957
- },
1958
- {
1959
- "offsetX": {
1960
- "value": 0,
1961
- "unit": "px"
1962
- },
1963
- "offsetY": {
1964
- "value": 0,
1965
- "unit": "px"
1966
- },
1967
- "blur": {
1968
- "value": 0,
1969
- "unit": "px"
1970
- },
1971
- "spread": {
1972
- "value": 2,
1973
- "unit": "px"
1974
- },
1975
- "color": {
1976
- "colorSpace": "srgb",
1977
- "components": [
1978
- 0,
1979
- 0,
1980
- 0
1981
- ],
1982
- "alpha": 1
1983
- }
1984
- },
1985
- {
1986
- "offsetX": {
1987
- "value": 0,
1988
- "unit": "px"
1989
- },
1990
- "offsetY": {
1991
- "value": 12,
1992
- "unit": "px"
1993
- },
1994
- "blur": {
1995
- "value": 24,
1996
- "unit": "px"
1997
- },
1998
- "spread": {
1999
- "value": -8,
2000
- "unit": "px"
2001
- },
2002
- "color": {
2003
- "colorSpace": "srgb",
2004
- "components": [
2005
- 0,
2006
- 0,
2007
- 0
2008
- ],
2009
- "alpha": 0.8
2010
- }
2011
- }
2012
- ]
2013
- },
2014
- "floating": {
2015
- "$value": [
2016
- {
2017
- "offsetX": {
2018
- "value": 0,
2019
- "unit": "px"
2020
- },
2021
- "offsetY": {
2022
- "value": 1,
2023
- "unit": "px"
2024
- },
2025
- "blur": {
2026
- "value": 0,
2027
- "unit": "px"
2028
- },
2029
- "spread": {
2030
- "value": 0,
2031
- "unit": "px"
2032
- },
2033
- "color": {
2034
- "colorSpace": "srgb",
2035
- "components": [
2036
- 1,
2037
- 1,
2038
- 1
2039
- ],
2040
- "alpha": 0.08
2041
- },
2042
- "inset": true
2043
- },
2044
- {
2045
- "offsetX": {
2046
- "value": 0,
2047
- "unit": "px"
2048
- },
2049
- "offsetY": {
2050
- "value": 0,
2051
- "unit": "px"
2052
- },
2053
- "blur": {
2054
- "value": 0,
2055
- "unit": "px"
2056
- },
2057
- "spread": {
2058
- "value": 1,
2059
- "unit": "px"
2060
- },
2061
- "color": {
2062
- "colorSpace": "srgb",
2063
- "components": [
2064
- 1,
2065
- 1,
2066
- 1
2067
- ],
2068
- "alpha": 0.16
2069
- }
2070
- },
2071
- {
2072
- "offsetX": {
2073
- "value": 0,
2074
- "unit": "px"
2075
- },
2076
- "offsetY": {
2077
- "value": 0,
2078
- "unit": "px"
2079
- },
2080
- "blur": {
2081
- "value": 0,
2082
- "unit": "px"
2083
- },
2084
- "spread": {
2085
- "value": 2,
2086
- "unit": "px"
2087
- },
2088
- "color": {
2089
- "colorSpace": "srgb",
2090
- "components": [
2091
- 0,
2092
- 0,
2093
- 0
2094
- ],
2095
- "alpha": 1
2096
- }
2097
- },
2098
- {
2099
- "offsetX": {
2100
- "value": 0,
2101
- "unit": "px"
2102
- },
2103
- "offsetY": {
2104
- "value": 24,
2105
- "unit": "px"
2106
- },
2107
- "blur": {
2108
- "value": 48,
2109
- "unit": "px"
2110
- },
2111
- "spread": {
2112
- "value": -12,
2113
- "unit": "px"
2114
- },
2115
- "color": {
2116
- "colorSpace": "srgb",
2117
- "components": [
2118
- 0,
2119
- 0,
2120
- 0
2121
- ],
2122
- "alpha": 0.9
2123
- }
2124
- }
2125
- ]
2126
- },
2127
- "modal": {
2128
- "$value": [
2129
- {
2130
- "offsetX": {
2131
- "value": 0,
2132
- "unit": "px"
2133
- },
2134
- "offsetY": {
2135
- "value": 1,
2136
- "unit": "px"
2137
- },
2138
- "blur": {
2139
- "value": 0,
2140
- "unit": "px"
2141
- },
2142
- "spread": {
2143
- "value": 0,
2144
- "unit": "px"
2145
- },
2146
- "color": {
2147
- "colorSpace": "srgb",
2148
- "components": [
2149
- 1,
2150
- 1,
2151
- 1
2152
- ],
2153
- "alpha": 0.1
2154
- },
2155
- "inset": true
2156
- },
2157
- {
2158
- "offsetX": {
2159
- "value": 0,
2160
- "unit": "px"
2161
- },
2162
- "offsetY": {
2163
- "value": 0,
2164
- "unit": "px"
2165
- },
2166
- "blur": {
2167
- "value": 0,
2168
- "unit": "px"
2169
- },
2170
- "spread": {
2171
- "value": 1,
2172
- "unit": "px"
2173
- },
2174
- "color": {
2175
- "colorSpace": "srgb",
2176
- "components": [
2177
- 1,
2178
- 1,
2179
- 1
2180
- ],
2181
- "alpha": 0.18
2182
- }
2183
- },
2184
- {
2185
- "offsetX": {
2186
- "value": 0,
2187
- "unit": "px"
2188
- },
2189
- "offsetY": {
2190
- "value": 0,
2191
- "unit": "px"
2192
- },
2193
- "blur": {
2194
- "value": 0,
2195
- "unit": "px"
2196
- },
2197
- "spread": {
2198
- "value": 2,
2199
- "unit": "px"
2200
- },
2201
- "color": {
2202
- "colorSpace": "srgb",
2203
- "components": [
2204
- 0,
2205
- 0,
2206
- 0
2207
- ],
2208
- "alpha": 1
2209
- }
2210
- },
2211
- {
2212
- "offsetX": {
2213
- "value": 0,
2214
- "unit": "px"
2215
- },
2216
- "offsetY": {
2217
- "value": 48,
2218
- "unit": "px"
2219
- },
2220
- "blur": {
2221
- "value": 96,
2222
- "unit": "px"
2223
- },
2224
- "spread": {
2225
- "value": -24,
2226
- "unit": "px"
2227
- },
2228
- "color": {
2229
- "colorSpace": "srgb",
2230
- "components": [
2231
- 0,
2232
- 0,
2233
- 0
2234
- ],
2235
- "alpha": 1
2236
- }
2237
- }
2238
- ]
2239
- },
2240
- "glass": {
2241
- "$value": [
2242
- {
2243
- "offsetX": {
2244
- "value": 0,
2245
- "unit": "px"
2246
- },
2247
- "offsetY": {
2248
- "value": 1,
2249
- "unit": "px"
2250
- },
2251
- "blur": {
2252
- "value": 0,
2253
- "unit": "px"
2254
- },
2255
- "spread": {
2256
- "value": 0,
2257
- "unit": "px"
2258
- },
2259
- "color": {
2260
- "colorSpace": "srgb",
2261
- "components": [
2262
- 1,
2263
- 1,
2264
- 1
2265
- ],
2266
- "alpha": 0.1
2267
- },
2268
- "inset": true
2269
- },
2270
- {
2271
- "offsetX": {
2272
- "value": 0,
2273
- "unit": "px"
2274
- },
2275
- "offsetY": {
2276
- "value": 0,
2277
- "unit": "px"
2278
- },
2279
- "blur": {
2280
- "value": 0,
2281
- "unit": "px"
2282
- },
2283
- "spread": {
2284
- "value": 1,
2285
- "unit": "px"
2286
- },
2287
- "color": {
2288
- "colorSpace": "srgb",
2289
- "components": [
2290
- 1,
2291
- 1,
2292
- 1
2293
- ],
2294
- "alpha": 0.14
2295
- }
2296
- },
2297
- {
2298
- "offsetX": {
2299
- "value": 0,
2300
- "unit": "px"
2301
- },
2302
- "offsetY": {
2303
- "value": 16,
2304
- "unit": "px"
2305
- },
2306
- "blur": {
2307
- "value": 32,
2308
- "unit": "px"
2309
- },
2310
- "spread": {
2311
- "value": -12,
2312
- "unit": "px"
2313
- },
2314
- "color": {
2315
- "colorSpace": "srgb",
2316
- "components": [
2317
- 0,
2318
- 0,
2319
- 0
2320
- ],
2321
- "alpha": 0.8
2322
- }
2323
- }
2324
- ]
2325
- }
2326
- }
2327
- },
2328
- "motion": {
2329
- "duration": {
2330
- "$type": "duration",
2331
- "control": {
2332
- "$value": {
2333
- "value": 100,
2334
- "unit": "ms"
2335
- }
2336
- },
2337
- "close": {
2338
- "$value": {
2339
- "value": 140,
2340
- "unit": "ms"
2341
- }
2342
- },
2343
- "confirm": {
2344
- "$value": {
2345
- "value": 180,
2346
- "unit": "ms"
2347
- }
2348
- },
2349
- "open": {
2350
- "$value": {
2351
- "value": 220,
2352
- "unit": "ms"
2353
- }
2354
- },
2355
- "msg-in": {
2356
- "$value": {
2357
- "value": 240,
2358
- "unit": "ms"
2359
- }
2360
- },
2361
- "transition": {
2362
- "$value": {
2363
- "value": 240,
2364
- "unit": "ms"
2365
- }
2366
- },
2367
- "arrive": {
2368
- "$value": {
2369
- "value": 300,
2370
- "unit": "ms"
2371
- }
2372
- },
2373
- "drawer-swipe": {
2374
- "$value": {
2375
- "value": 400,
2376
- "unit": "ms"
2377
- }
2378
- },
2379
- "drawer": {
2380
- "$value": {
2381
- "value": 450,
2382
- "unit": "ms"
2383
- }
2384
- },
2385
- "wait": {
2386
- "$value": {
2387
- "value": 1.6,
2388
- "unit": "s"
2389
- }
2390
- }
2391
- },
2392
- "ease": {
2393
- "$type": "cubicBezier",
2394
- "emphasized": {
2395
- "$value": [
2396
- 0.3,
2397
- 0.7,
2398
- 0.2,
2399
- 1
2400
- ]
2401
- },
2402
- "out-strong": {
2403
- "$value": [
2404
- 0.23,
2405
- 1,
2406
- 0.32,
2407
- 1
2408
- ]
2409
- },
2410
- "in-out-strong": {
2411
- "$value": [
2412
- 0.77,
2413
- 0,
2414
- 0.175,
2415
- 1
2416
- ]
2417
- },
2418
- "drawer": {
2419
- "$value": [
2420
- 0.32,
2421
- 0.72,
2422
- 0,
2423
- 1
2424
- ]
2425
- }
2426
- }
2427
- }
2428
- },
2429
- "semantic": {
2430
- "radius": {
2431
- "$type": "dimension",
2432
- "$value": "{core.rounded.md}"
2433
- },
2434
- "color": {
2435
- "$type": "color",
2436
- "light": {
2437
- "background": {
2438
- "$value": "{core.color.canvas}"
2439
- },
2440
- "foreground": {
2441
- "$value": "{core.color.ink}"
2442
- },
2443
- "card": {
2444
- "$value": "{core.color.material-solid}"
2445
- },
2446
- "card-foreground": {
2447
- "$value": "{core.color.ink}"
2448
- },
2449
- "popover": {
2450
- "$value": "{core.color.material-solid}"
2451
- },
2452
- "popover-foreground": {
2453
- "$value": "{core.color.ink}"
2454
- },
2455
- "primary": {
2456
- "$value": "{core.color.action}"
2457
- },
2458
- "primary-foreground": {
2459
- "$value": "{core.color.action-foreground}"
2460
- },
2461
- "primary-hover": {
2462
- "$value": "{core.color.action-hover}"
2463
- },
2464
- "primary-active": {
2465
- "$value": "{core.color.action-active}"
2466
- },
2467
- "secondary": {
2468
- "$value": "{core.color.canvas-soft}"
2469
- },
2470
- "secondary-foreground": {
2471
- "$value": "{core.color.ink}"
2472
- },
2473
- "muted": {
2474
- "$value": "{core.color.canvas-soft}"
2475
- },
2476
- "muted-foreground": {
2477
- "$value": "{core.color.body}"
2478
- },
2479
- "placeholder": {
2480
- "$value": "{core.color.muted}"
2481
- },
2482
- "accent": {
2483
- "$value": "{core.color.canvas-soft-2}"
2484
- },
2485
- "accent-foreground": {
2486
- "$value": "{core.color.ink}"
2487
- },
2488
- "destructive": {
2489
- "$value": "{core.color.error}"
2490
- },
2491
- "destructive-foreground": {
2492
- "$value": "{core.color.identity-white}"
2493
- },
2494
- "warning": {
2495
- "$value": "{core.color.warning}"
2496
- },
2497
- "warning-foreground": {
2498
- "$value": "{core.color.ink}"
2499
- },
2500
- "success": {
2501
- "$value": "{core.color.success}"
2502
- },
2503
- "success-foreground": {
2504
- "$value": "{core.color.ink}"
2505
- },
2506
- "info": {
2507
- "$value": "{core.color.info}"
2508
- },
2509
- "info-foreground": {
2510
- "$value": "{core.color.identity-white}"
2511
- },
2512
- "destructive-soft": {
2513
- "$value": "{core.color.error-soft}"
2514
- },
2515
- "destructive-soft-foreground": {
2516
- "$value": "{core.color.error-deep}"
2517
- },
2518
- "warning-soft": {
2519
- "$value": "{core.color.warning-soft}"
2520
- },
2521
- "warning-soft-foreground": {
2522
- "$value": "{core.color.warning-deep}"
2523
- },
2524
- "success-soft": {
2525
- "$value": "{core.color.success-soft}"
2526
- },
2527
- "success-soft-foreground": {
2528
- "$value": "{core.color.success-deep}"
2529
- },
2530
- "info-soft": {
2531
- "$value": "{core.color.info-soft}"
2532
- },
2533
- "info-soft-foreground": {
2534
- "$value": "{core.color.info-deep}"
2535
- },
2536
- "border": {
2537
- "$value": "{core.color.line}"
2538
- },
2539
- "input": {
2540
- "$value": "{core.color.input}"
2541
- },
2542
- "ring": {
2543
- "$value": "{core.color.focus-ring}"
2544
- },
2545
- "scrim": {
2546
- "$value": "{core.color.scrim}"
2547
- },
2548
- "chart-1": {
2549
- "$value": "{core.color.chart-1}"
2550
- },
2551
- "chart-2": {
2552
- "$value": "{core.color.chart-2}"
2553
- },
2554
- "chart-3": {
2555
- "$value": "{core.color.chart-3}"
2556
- },
2557
- "chart-4": {
2558
- "$value": "{core.color.chart-4}"
2559
- },
2560
- "chart-5": {
2561
- "$value": "{core.color.chart-5}"
2562
- },
2563
- "selection": {
2564
- "$value": "{core.color.navigation-selection-bg}"
2565
- },
2566
- "selection-foreground": {
2567
- "$value": "{core.color.navigation-selection-fg}"
2568
- },
2569
- "recessed": {
2570
- "$value": "{core.color.canvas-soft}"
2571
- },
2572
- "sidebar": {
2573
- "$value": "{core.color.canvas-soft}"
2574
- },
2575
- "sidebar-foreground": {
2576
- "$value": "{core.color.ink}"
2577
- },
2578
- "sidebar-primary": {
2579
- "$value": "{core.color.navigation-selection-bg}"
2580
- },
2581
- "sidebar-primary-foreground": {
2582
- "$value": "{core.color.navigation-selection-fg}"
2583
- },
2584
- "sidebar-primary-hover": {
2585
- "$value": "{core.color.navigation-selection-hover}"
2586
- },
2587
- "sidebar-accent": {
2588
- "$value": "{core.color.canvas-soft-2}"
2589
- },
2590
- "sidebar-accent-foreground": {
2591
- "$value": "{core.color.ink}"
2592
- },
2593
- "sidebar-border": {
2594
- "$value": "{core.color.line}"
2595
- },
2596
- "sidebar-ring": {
2597
- "$value": "{core.color.focus-ring}"
2598
- },
2599
- "category-teal": {
2600
- "$value": "{core.color.category-teal-soft}"
2601
- },
2602
- "category-teal-foreground": {
2603
- "$value": "{core.color.category-teal-deep}"
2604
- },
2605
- "category-purple": {
2606
- "$value": "{core.color.category-purple-soft}"
2607
- },
2608
- "category-purple-foreground": {
2609
- "$value": "{core.color.category-purple-deep}"
2610
- },
2611
- "category-pink": {
2612
- "$value": "{core.color.category-pink-soft}"
2613
- },
2614
- "category-pink-foreground": {
2615
- "$value": "{core.color.category-pink-deep}"
2616
- }
2617
- },
2618
- "dark": {
2619
- "background": {
2620
- "$value": "{core.color.dark-canvas}"
2621
- },
2622
- "foreground": {
2623
- "$value": "{core.color.dark-ink}"
2624
- },
2625
- "card": {
2626
- "$value": "{core.color.dark-material-solid}"
2627
- },
2628
- "card-foreground": {
2629
- "$value": "{core.color.dark-ink}"
2630
- },
2631
- "popover": {
2632
- "$value": "{core.color.dark-material-solid}"
2633
- },
2634
- "popover-foreground": {
2635
- "$value": "{core.color.dark-ink}"
2636
- },
2637
- "primary": {
2638
- "$value": "{core.color.dark-action}"
2639
- },
2640
- "primary-foreground": {
2641
- "$value": "{core.color.dark-action-foreground}"
2642
- },
2643
- "primary-hover": {
2644
- "$value": "{core.color.dark-action-hover}"
2645
- },
2646
- "primary-active": {
2647
- "$value": "{core.color.dark-action-active}"
2648
- },
2649
- "secondary": {
2650
- "$value": "{core.color.dark-canvas-soft}"
2651
- },
2652
- "secondary-foreground": {
2653
- "$value": "{core.color.dark-ink}"
2654
- },
2655
- "muted": {
2656
- "$value": "{core.color.dark-canvas-soft}"
2657
- },
2658
- "muted-foreground": {
2659
- "$value": "{core.color.dark-body}"
2660
- },
2661
- "placeholder": {
2662
- "$value": "{core.color.dark-muted}"
2663
- },
2664
- "accent": {
2665
- "$value": "{core.color.dark-canvas-soft-2}"
2666
- },
2667
- "accent-foreground": {
2668
- "$value": "{core.color.dark-ink}"
2669
- },
2670
- "destructive": {
2671
- "$value": "{core.color.dark-error}"
2672
- },
2673
- "destructive-foreground": {
2674
- "$value": "{core.color.dark-on-primary}"
2675
- },
2676
- "warning": {
2677
- "$value": "{core.color.dark-warning}"
2678
- },
2679
- "warning-foreground": {
2680
- "$value": "{core.color.dark-on-primary}"
2681
- },
2682
- "success": {
2683
- "$value": "{core.color.dark-success}"
2684
- },
2685
- "success-foreground": {
2686
- "$value": "{core.color.dark-on-primary}"
2687
- },
2688
- "info": {
2689
- "$value": "{core.color.dark-info}"
2690
- },
2691
- "info-foreground": {
2692
- "$value": "{core.color.dark-on-primary}"
2693
- },
2694
- "destructive-soft": {
2695
- "$value": "{core.color.dark-error-soft}"
2696
- },
2697
- "destructive-soft-foreground": {
2698
- "$value": "{core.color.dark-error-deep}"
2699
- },
2700
- "warning-soft": {
2701
- "$value": "{core.color.dark-warning-soft}"
2702
- },
2703
- "warning-soft-foreground": {
2704
- "$value": "{core.color.dark-warning-deep}"
2705
- },
2706
- "success-soft": {
2707
- "$value": "{core.color.dark-success-soft}"
2708
- },
2709
- "success-soft-foreground": {
2710
- "$value": "{core.color.dark-success-deep}"
2711
- },
2712
- "info-soft": {
2713
- "$value": "{core.color.dark-info-soft}"
2714
- },
2715
- "info-soft-foreground": {
2716
- "$value": "{core.color.dark-info-deep}"
2717
- },
2718
- "border": {
2719
- "$value": "{core.color.dark-line}"
2720
- },
2721
- "input": {
2722
- "$value": "{core.color.dark-input}"
2723
- },
2724
- "ring": {
2725
- "$value": "{core.color.dark-focus-ring}"
2726
- },
2727
- "scrim": {
2728
- "$value": "{core.color.dark-scrim}"
2729
- },
2730
- "chart-1": {
2731
- "$value": "{core.color.dark-chart-1}"
2732
- },
2733
- "chart-2": {
2734
- "$value": "{core.color.dark-chart-2}"
2735
- },
2736
- "chart-3": {
2737
- "$value": "{core.color.dark-chart-3}"
2738
- },
2739
- "chart-4": {
2740
- "$value": "{core.color.dark-chart-4}"
2741
- },
2742
- "chart-5": {
2743
- "$value": "{core.color.dark-chart-5}"
2744
- },
2745
- "selection": {
2746
- "$value": "{core.color.dark-navigation-selection-bg}"
2747
- },
2748
- "selection-foreground": {
2749
- "$value": "{core.color.dark-navigation-selection-fg}"
2750
- },
2751
- "recessed": {
2752
- "$value": "{core.color.dark-canvas}"
2753
- },
2754
- "sidebar": {
2755
- "$value": "{core.color.dark-canvas}"
2756
- },
2757
- "sidebar-foreground": {
2758
- "$value": "{core.color.dark-ink}"
2759
- },
2760
- "sidebar-primary": {
2761
- "$value": "{core.color.dark-navigation-selection-bg}"
2762
- },
2763
- "sidebar-primary-foreground": {
2764
- "$value": "{core.color.dark-navigation-selection-fg}"
2765
- },
2766
- "sidebar-primary-hover": {
2767
- "$value": "{core.color.dark-navigation-selection-hover}"
2768
- },
2769
- "sidebar-accent": {
2770
- "$value": "{core.color.dark-canvas-soft-2}"
2771
- },
2772
- "sidebar-accent-foreground": {
2773
- "$value": "{core.color.dark-ink}"
2774
- },
2775
- "sidebar-border": {
2776
- "$value": "{core.color.dark-line}"
2777
- },
2778
- "sidebar-ring": {
2779
- "$value": "{core.color.dark-focus-ring}"
2780
- },
2781
- "category-teal": {
2782
- "$value": "{core.color.dark-category-teal-soft}"
2783
- },
2784
- "category-teal-foreground": {
2785
- "$value": "{core.color.dark-category-teal-deep}"
2786
- },
2787
- "category-purple": {
2788
- "$value": "{core.color.dark-category-purple-soft}"
2789
- },
2790
- "category-purple-foreground": {
2791
- "$value": "{core.color.dark-category-purple-deep}"
2792
- },
2793
- "category-pink": {
2794
- "$value": "{core.color.dark-category-pink-soft}"
2795
- },
2796
- "category-pink-foreground": {
2797
- "$value": "{core.color.dark-category-pink-deep}"
2798
- }
2799
- }
2800
- }
2801
- },
2802
- "register": {
2803
- "website": {
2804
- "$extensions": {
2805
- "ai.nadicode.targets": [
2806
- "css",
2807
- "data"
2808
- ]
2809
- },
2810
- "layout": {
2811
- "$type": "dimension",
2812
- "content-max": {
2813
- "$value": {
2814
- "value": 1200,
2815
- "unit": "px"
2816
- }
2817
- },
2818
- "gutter-mobile": {
2819
- "$value": {
2820
- "value": 20,
2821
- "unit": "px"
2822
- }
2823
- },
2824
- "gutter-tablet": {
2825
- "$value": {
2826
- "value": 40,
2827
- "unit": "px"
2828
- }
2829
- },
2830
- "gutter-desktop": {
2831
- "$value": {
2832
- "value": 64,
2833
- "unit": "px"
2834
- }
2835
- },
2836
- "section-step-mobile": {
2837
- "$value": {
2838
- "value": 64,
2839
- "unit": "px"
2840
- }
2841
- },
2842
- "section-step-desktop": {
2843
- "$value": {
2844
- "value": 96,
2845
- "unit": "px"
2846
- }
2847
- }
2848
- }
2849
- }
2850
- }
2851
- }
2852
- ```
1230
+ The YAML front matter holds the four groups the DESIGN.md format defines: colors, typography, rounded and spacing. Every other value is authored in `src/tokens/extension.tokens.json`, in the W3C design-tokens format: `core.material` (the scrim and glass values, a `dark-` value beside one that differs in dark), `core.elevation` (the four heights and the glass edge, per mode), `core.motion` (durations and easings), `semantic` (the shared colour roles and radius) and `register.website` (the website column). An alias is written `{core.color.canvas}`. In CSS a token is `--nc-<name>`, an elevation is reached only through `nc-elevation-<height>`, and Tailwind names the values through the `nc-` and `nadicode-` namespaces. The build composes the two into the package's token files and CSS, as the package README describes.