@nadicodeai/design-system 12.1.96 → 12.1.97

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
@@ -356,6 +356,12 @@ nadicode puts AI Agents to work inside small and mid-sized companies. Its sites
356
356
 
357
357
  The products are still: only Agents move on their own. The website moves with purpose: the product at work, Agents and its few shader objects ("Motion").
358
358
 
359
+ The products are assembled in three layers, each with one owner and one check, so the same job looks the same on every page:
360
+
361
+ - **Components.** Every component in `@nadicodeai/ui` has a usage page: when to use it, what to use instead, how it behaves, its words and its accessibility, written in the component's own source and rendered by the package's showcase ("Component selection and usage").
362
+ - **Compositions.** A page is built from one of nine compositions, each a component in `@nadicodeai/ui` with fixed slots, so the page supplies only content and the order of its actions and the place of its three dots cannot drift: the five page compositions, the record row, the empty state, the overlays and the identity marks ("Page compositions").
363
+ - **Checks.** Four lint rules fail the build in the Portal, Nadia's window and Alveare Server: a page renders a composition (`page-composition`), an absence is only `EmptyState` (`empty-state-owner`), commands go only through `PageActions`, `ActionMenu` and `AccountMenu` (`menu-owner`, with the menu modules held by `no-restricted-imports`), and a title or a label carries no icon (`no-decorative-icon`). A page that broke a rule when the checks arrived sits on its product's recorded baseline, whose count may only fall.
364
+
359
365
  One foundation serves four registers. The foundation is every value in this file. A register is one product's way of applying it: the website, the Portal, the Nadia desktop app and the Alveare desktop app ("Registers"). A register adds rules and chooses which values it uses; it never redefines a value or mints one. A value one register needs is authored here under `register.<name>`, and a value two registers share moves to `core`.
360
366
 
361
367
  ## Colors
@@ -392,11 +398,11 @@ Text on a colour ground is the pole that clears AA on that hue: `{colors.identit
392
398
  - **Feedback** pairs a hue with a soft ground and a deep text step: success (verde), warning (giallo `{colors.warning}`), error (rosso `{colors.error}`), info (cobalto `{colors.info}`). The `-soft` value is the tinted ground of a tag or banner and the `-deep` value is the AA text on it; a component reaches the pair through the `destructive-soft`, `warning-soft`, `success-soft` and `info-soft` roles and their foregrounds. On a solid fill, error takes white text and warning and success take ink in light; in dark all three take `{colors.dark-on-primary}`.
393
399
  - **Link** (`{colors.link}`, `{colors.link-deep}`, `{colors.link-bg-soft}`): blue inline links. Success uses its own green roles; link colour never doubles as a health signal.
394
400
  - **Workflow states** (`{colors.state-ready}`, `{colors.state-running}`, `{colors.state-review}`, `{colors.state-blocked}`, `{colors.state-complete}`) colour non-text marks only: meter fills and trend icons. Cobalto means in motion and verde means done. A state's word uses its `StatusBadge` foreground pair; never put a raw meter-fill token on text.
395
- - **Status** is always a Badge tone with a mark and a word ("Components").
401
+ - **Status** is always a `StatusBadge` tone with a mark and a word ("Components").
396
402
 
397
403
  ### Category colour
398
404
 
399
- Labels and their icons may carry blue, teal, purple or pink to distinguish named categories. `Tag` owns these categorical tones; `Badge` also offers the corresponding soft fill and contrasting foreground. The same category keeps the same tone across a surface and after sorting. A word remains visible, and a colour never invents status. Green, amber and red remain feedback meanings; the five-tone `StatusBadge` owns those. Icons inherit the tone of their label rather than adding a second colour. Brand marks keep their genuine colours. This uses Geist’s light fill / strong text technique with nadicode’s semantic roles; agents never choose raw palette values in pages.
405
+ Labels and their icons may carry blue, teal, purple or pink to distinguish named categories. `Tag` owns these categorical tones; `Badge` also offers the teal, purple and pink soft fills with their contrasting foregrounds, and no status fill. The same category keeps the same tone across a surface and after sorting. A word remains visible, and a colour never invents status. Green, amber and red remain feedback meanings; the five-tone `StatusBadge` owns those. Icons inherit the tone of their label rather than adding a second colour. Brand marks keep their genuine colours. This uses Geist’s light fill / strong text technique with nadicode’s semantic roles; agents never choose raw palette values in pages.
400
406
 
401
407
  ### Dark mode
402
408
 
@@ -418,6 +424,7 @@ Every chart and KPI card is a stock shadcn registry component owned by `@nadicod
418
424
  - A multi-series chart has a legend below the plot, left-aligned and wrapping: a round dot and the entity name, in series order.
419
425
  - 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.
420
426
  - A chart is a named section: its card title is the heading that names the plot.
427
+ - 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. 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 the way to its owner on the right, "Open in Analytics".
421
428
 
422
429
  No pie, donut, radar, radial or dual-axis chart, and no mark or logo inside a legend.
423
430
 
@@ -484,14 +491,34 @@ instruction never becomes a heading merely because the screen has little copy.
484
491
  Actions and stage lists use `{typography.body-sm}`, and progress counts use
485
492
  `{typography.label-mono}`. These roles retain their token sizes at every width.
486
493
 
494
+ An entry page is a whole page with one job and no product chrome: no sidebar,
495
+ no breadcrumb, no footer. Each product has one entry frame, and every entry
496
+ page renders it: the Portal's `PortalFunnelFrame` with `AccountEntryHeader`
497
+ (sign-in, sign-up, an invitation, and the pages that hand a person to Nadia,
498
+ which stand on the dark plane with her ambient orb at 96 px over the card),
499
+ Nadia's `EntryScene` (her entry, update and installer screens, her orb at
500
+ 160 px, 96 px under 640 px wide or 600 px tall) and Alveare Server's
501
+ `HiveScene`.
502
+
487
503
  A single-task entry screen centres the visible identity, message and actions as
488
504
  one group in the usable window, horizontally and vertically. Hidden content and
489
505
  reserved empty rows do not contribute to that group's height. A progress screen
490
- may reserve the lines its status needs to prevent movement between stages.
491
- Artwork stays clear of the task and retains complete architectural and figure
492
- boundaries when placed inside the window; an image edge must not masquerade as a
493
- finished object edge. Compact layouts preserve readable controls and scroll when
494
- content cannot fit.
506
+ may reserve the lines its status needs to prevent movement between stages, and
507
+ its measured progress is a written count in `{typography.label-mono}`, "Step 2
508
+ of 3", never a percentage. A picture stands clear of the task and whole, its
509
+ feet on the window's floor, inert and hidden from assistive technology, and
510
+ only from `lg`: below `lg` the picture leaves and the task keeps the screen. An
511
+ image edge must not masquerade as a finished object edge. Compact layouts
512
+ preserve readable controls and scroll when content cannot fit.
513
+
514
+ Where a picture may stand:
515
+
516
+ | Place | Picture |
517
+ | --- | --- |
518
+ | Entry: sign-in, sign-up and welcome, an invitation, handing a person to Nadia, Nadia's entry and update screens, the installer | Yes |
519
+ | A moment: a failure page, the Downloads hero, a product's first run, a collection's first use | Yes |
520
+ | A funnel task: two-factor, an interrupted setup, a legal page, impersonation, a connection's done page | No: the task is enough |
521
+ | Any other page inside a product's chrome | No, except an address that is not there, whose picture stands centred in the content ("Failure surfaces") |
495
522
 
496
523
  ## Layout
497
524
 
@@ -517,29 +544,58 @@ These values are the `register.website.layout` tokens; the breakpoints live in `
517
544
 
518
545
  ### Product shells
519
546
 
520
- The Portal and Nadia are product shells: persistent navigation on the left, the selected destination on the right, on the workspace ground ("Elevation & Depth"). They use the spacing ladder and the control geometry, not the website's column.
521
-
522
- - **Navigation names stable destinations.** An object opened from search or a link still shows its parent in a breadcrumb; local tabs directly below the top bar ask different questions about the same object, and they are always the package's Tabs ("Controls"); a return link remembers a visit and never replaces the parent. A scope selector keeps the current tab when the next object has it, and otherwise opens its overview.
547
+ The Portal, Nadia and Alveare are product shells: persistent navigation on the left, the selected destination on the right, on the workspace ground ("Elevation & Depth"). They use the spacing ladder and the control geometry, not the website's column. Every product answers "where am I?" the same way, with one place for each kind of navigation, so a person who moves between the Portal and Nadia in one afternoon finds every control where the other product put it:
548
+
549
+ | Kind | Its one place | Portal | Nadia | Alveare |
550
+ | --- | --- | --- | --- | --- |
551
+ | Where I can go | The sidebar | Groups by viewer, one selected row | The company, Tasks, then the Agents | Inbox, Channels, Direct messages |
552
+ | An object's sections | The sidebar, swapped | Back link, the object, its sections | Two swaps only: ⋯ → Edit and the company | The channel's own panels |
553
+ | The path | The top bar | Centred: the group or the parents, then the place | The window header: the Agent's orb and name, then the chat switcher | The channel header names the channel |
554
+ | Find, ⌘K | The top of the sidebar | An outlined field | Under the company | Buzz's search, in its sidebar |
555
+ | The account | The sidebar's foot | With the bell | Under the update card | The package's `AccountMenu` |
556
+ | Getting the app | Above the account | Downloads, an outlined row | The update card, only when there is one | The update card |
557
+ | 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 |
558
+
559
+ - **The sidebar swaps, and is never joined by a second.** Inside an object, the account's settings or the Platform settings, 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.
560
+ - **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.
561
+ - **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.
562
+ - **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.
563
+ - **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.
523
564
  - **Compact screens keep the same relationships.** Tabs scroll horizontally, primary content stacks before supporting facts, and no second sidebar appears.
524
565
  - **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.
525
566
 
567
+ **Nadia's window** sorts every thing by what it belongs to, in four scopes, each with one home: 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).
568
+
569
+ - **The list at rest** holds, top to bottom: the company, its mark and name over "Company · 3 Agents", where Vercel puts the team, above the Agents because it contains them; Find; Tasks; 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.
570
+ - **The company** swaps the list to a back link to the Agents, the company, and its sections, each its own page with its own title and its actions on that title's row: Skills, Memory, Mail and Alveare. They are four different things, not four views of one, so they are pages, never tabs under the company's name.
571
+ - **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.
572
+ - **⋯ → 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.
573
+ - **Settings** is how the app behaves on this computer: notifications, services, updates. It opens from the account menu's gear, as one settings page, and never swaps the list; the account menu holds Settings, Theme, Language, Usage and Sign out. The company's skills, memory, mail and Alveare are never in Settings.
574
+ - **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).
575
+ - **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.
576
+ - **The list narrows.** From 640 to 899 px it becomes a column of 48 px squares: the company, Find and Tasks, 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.
577
+
578
+ **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.
579
+
526
580
  ### Page compositions
527
581
 
528
- A product page is one of five compositions, each answering one question first:
582
+ A product page is one of five page compositions, each answering one question first and each one component in `@nadicodeai/ui` that draws its order, variants and states from the content the page gives it. A page renders one of them and never assembles its own header, toolbar, table, rows, menus or column; `page-composition` refuses a page that renders a part a composition owns:
529
583
 
530
- | Composition | First answer | Structure |
531
- | --- | --- | --- |
532
- | Overview | What needs attention? | Scoped facts, the exceptions that matter, links to their owners. Never a stack of previews of every section. |
533
- | Collection | Which object? | The page header (its title with the page's actions on the title's row, and nothing under it), then one toolbar (filters, search, sort), one line per record with its identity and a few comparable facts, and the row's menu. One row is one hover and one open target. |
534
- | Object | What is this, where does it belong, what can I do? | The parent in the breadcrumb and its sections (tabs, or the sidebar for an object worked inside); then the object's header on the page ground, never in a card: its picture, its name at `{typography.heading-md}`, one line saying what it is, a row of key facts as label and value, and at the trailing edge the page's one primary action and the three dots; then grouped facts and settings sections as the cards below. 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 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. |
535
- | Configuration | What can I change? | A centred configuration column of titled sections, labels and help above each control, a local save and recovery. |
536
- | Library | What reusable thing can I use? | The shelf's title with its one definition line, the search, then the entries as two-column rows: each its kind's tile, its name, one line of description and its add control; detail shows content, requirements, source and revision. |
584
+ | Composition | Owner | First answer | Structure |
585
+ | --- | --- | --- | --- |
586
+ | 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. |
587
+ | 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. |
588
+ | Object | `ObjectPage` | What is this, what can I do? | The parent in the path and its sections in the sidebar; then the object's header on the page ground, never in a card: its identity mark at 64 px, its name at `{typography.heading-md}`, one line saying what it is, a row of key facts as label and value, and on the name's row one secondary action, then the three dots, never a primary; 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 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. |
589
+ | Settings | `SettingsPage` | What can I change? | A centred column of section cards, each with its title and one line, labels and help above each control, and its own Save in its footer. |
590
+ | Library | `LibraryShelf` | What reusable thing can I use? | The shelf's title with its one definition line, the search, then the entries in two columns: each its kind's tile, its name, one line of description and its one Add control; detail shows content, requirements, source and revision. |
591
+
592
+ 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").
537
593
 
538
- Every setting is that Configuration 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"). The Portal configuration column is at most 56rem. Inputs are bounded at 20rem under their labels and descriptions, 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.
594
+ 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"). The settings column is at most 56rem. Inputs are bounded at 20rem under their labels and descriptions, 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.
539
595
 
540
- Collections and working pages open with the same header (`PageHeader`). Configuration pages open directly with their titled settings sections 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: 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: at most one `outline` secondary action, then the `default` primary action, then the overflow menu at the trailing edge. `PageActions` is their only owner: it takes a `primary`, a `secondary` and a `menu`, and draws their order and variants. An existing record-command menu can occupy that same menu slot without rebuilding its confirmation logic. 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. The one exception is a catalog shelf of the Library (Skills, Plugins): one line under its title says what kind of thing the shelf holds, because the shelf explains how its kind of item is used; it never counts or introduces anything else. 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.
596
+ 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. `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. The one exception is a catalog shelf of the Library (Skills, Plugins): one line under its title says what kind of thing the shelf holds, because the shelf explains how its kind of item is used; it never counts or introduces anything else. 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.
541
597
 
542
- 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; a product that uses illustrated presentation may show one only for a collection's first use ("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.
598
+ 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.
543
599
 
544
600
  A section earns a card when it owns a decision, not for every label and value. A row links to its object and keeps secondary mutations in its menu ("Menus and commands"). A summary on an overview opens the same owner as its tab. Supporting detail collapses only behind a summary that keeps a count, state or consequence.
545
601
 
@@ -569,13 +625,11 @@ Product UI sits on one continuous workspace ground, `{colors.material-workspace}
569
625
 
570
626
  Content stays solid: cards, KPIs, tables, chat, forms, and dialog, sheet and drawer bodies. Menus stay solid: a dropdown menu and a context menu open over dense rows their labels would compete with, and one set of commands reached both ways looks the same both ways. Glass over a plain white or black ground shows nothing, so it never goes there. Reduced transparency and browsers without backdrop-filter replace glass with the same solid surface at the same height. Transparency never reduces the opacity of labels or controls.
571
627
 
572
- Related membership collections share one tabbed workspace: Members, Invitations and, where supported, Teams. One collection is visible at a time and its selection is retained in the URL.
573
-
574
628
  **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.
575
629
 
576
- Absence follows its container. The Portal uses plain operational empty states throughout: no collection illustrations, a solid boundary for a page or region, and the same spacing and typography for first use, no data, and no results. Other products may use the illustrated register with a dashed boundary. In every product, an empty state inside a card is unframed, because the card already owns the edge. `EmptyState` (`@nadicodeai/ui/components/empty-state`) is the only owner of an absence: nothing else draws one, and its `scope` (`page`, `region` or `card`) sets that frame. A collection's `DataTable` takes the same props as `empty`. Its kind determines the message and recovery; presentation determines whether first-use artwork is present:
630
+ 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 nothing yet, no results and a failed read stay quiet. 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 message, the picture and the recovery:
577
631
 
578
- - `firstUse`: a collection with no records yet, whoever the viewer is and however the collection fills. Illustrated presentation takes its picture from the approved set in `@nadicodeai/design-system/assets/empty`. A title and one line saying what will live there, the picture when the product uses illustrated presentation, and, when this viewer can add the first record, the action that adds it.
632
+ - `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, `outline` while the title row keeps a primary of its own. 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.
579
633
  - `nothingYet`: a list whose kind has no picture of its own, such as invoices or history, or a table inside a page. One line, with no picture and nothing to press.
580
634
  - `noResults`: a search or filter that matches nothing. One line and Clear filters (or Clear search), with no picture.
581
635
  - `failed`: a read that failed where content would be. The warning mark, the message's title and body, and its controls, Retry among them.
@@ -586,7 +640,7 @@ Modal overlays use the scrim with no blur. Focus is not elevation: it is always
586
640
 
587
641
  ## Shapes
588
642
 
589
- A corner follows what the thing is, on one scale: buttons, inputs, badges, tabs and menu rows take `{rounded.sm}` (6 px); resting cards and tables also take `{rounded.sm}`; menus, popovers, dialogs, sheets and toasts take `{rounded.lg}` (12 px), and so does a drawer on each edge not flush with the window; tooltips take `{rounded.sm}` (6 px); a collection's glass toolbar takes `{rounded.lg}` (12 px); a conversation bubble takes `{rounded.2xl}` (20 px); Nadia's chat bar takes `{rounded.3xl}` (24 px). A checkbox mark is a square at `{rounded.xs}` (2 px), keeping its 16 px mark distinct from a circular radio. A surface never earns a larger corner by sitting higher. A corner nested inside another takes the outer radius minus the gap between them. Full-bleed sections take `{rounded.none}`. `{rounded.full}` is for avatars, switch and radio marks, slider thumbs, filter chips and tags. The other radii are for inner marks and geometry a component owns, such as a progress track.
643
+ A corner follows what the thing is, on one scale: buttons, inputs, badges, tabs and menu rows take `{rounded.sm}` (6 px), and a line tab takes it on its two top corners only, standing on its underline; resting cards and tables also take `{rounded.sm}`; menus, popovers, dialogs, sheets and toasts take `{rounded.lg}` (12 px), and so does a drawer on each edge not flush with the window; tooltips take `{rounded.sm}` (6 px); a collection's glass toolbar takes `{rounded.lg}` (12 px); a conversation bubble takes `{rounded.2xl}` (20 px); Nadia's chat bar takes `{rounded.3xl}` (24 px). A checkbox mark is a square at `{rounded.xs}` (2 px), keeping its 16 px mark distinct from a circular radio. A surface never earns a larger corner by sitting higher. A corner nested inside another takes the outer radius minus the gap between them. Full-bleed sections take `{rounded.none}`. `{rounded.full}` is for avatars, switch and radio marks, slider thumbs, filter chips, tags and the conversation's send button. The other radii are for inner marks and geometry a component owns, such as a progress track.
590
644
 
591
645
  ## Motion
592
646
 
@@ -594,7 +648,9 @@ UI text stays static: no shimmer, typing, scrambling, pulsing or animated dots.
594
648
 
595
649
  Only Agents move on their own. The interface answers a person's action, briefly, and stops: motion follows the action, grows from the element that was pressed, and settles, within 300 ms; sheets and drawers that follow the pointer take their own durations. Keyboard actions and anything used a hundred times a day are instant. While a person waits for something they asked for, one waiting line moves and nothing else ("System activity"). On the website three things move: Agents, the product at work, and the shader objects. A product scene runs the real components through their states in beats while it is half in view, rests on the finished work, loops, stops off-screen, and holds the finished work under reduced motion; a part of a scene arrives with `.nc-arrive`: it holds its place from the start and comes into focus there on the message-arrival timing, so a playing scene never shifts the page. The threads, the hive and the card's foil move at their own slow pace; each stops off-screen and holds one frame under reduced motion. Words never move, and there is no parallax, scroll reveal of text, marquee or shimmer. Ruled by the founder on 26 September 2026: a Stripe-grade product site, with shaders adjusted to the art direction and the products shown working.
596
650
 
597
- The homepage figures have one owner-requested exception: a slight lean in response to a fine pointer, with no autonomous loop and no movement under reduced motion. Architecture and words stay still. Other illustrative pictures remain still.
651
+ The homepage figures have one owner-requested exception: a slight lean in response to a fine pointer, with no autonomous loop and no movement under reduced motion. Architecture and words stay still.
652
+
653
+ In the products a picture stands only on a collection's first use and on a whole-page moment: the entry screens, the failure pages, the Downloads page and a product's first run ("Entry screens"). Every picture stays still, with one exception: a first use's picture arrives once, after it loads. It holds its place and comes into focus there, from transparent and an 8 px blur to sharp, over `arrive` on `emphasized`, and never moves again; it never loops, and under reduced motion it does not animate at all and simply appears. Ruled by the owner on 14 October 2026.
598
654
 
599
655
  The default transition in every product is `confirm`, 180 ms, `out-strong`. Under reduced motion, movement stops and paint continues: transforms and keyframed movement go to their end state, and opacity and colour transitions stay, because a fade or a colour change is the state change itself. A component never answers reduced motion by turning every transition off.
600
656
 
@@ -605,6 +661,7 @@ The default transition in every product is `confirm`, 180 ms, `out-strong`. Unde
605
661
  | `confirm` 180 ms | Confirmation, instant state flips, the default transition |
606
662
  | `msg-in` 240 ms | A message arriving in a conversation |
607
663
  | `transition` 240 ms | Property transitions on a working surface, and the Portal's one page transition, which carries an Agent's orb from a list row into its page |
664
+ | `arrive` 300 ms | A first use's picture coming into focus, once |
608
665
  | `drawer-swipe` 400 ms, `drawer` 450 ms | Sheets and drawers, pointer-driven and programmatic |
609
666
  | `wait` 1.6 s | Every waiting indicator: the waiting line, on every screen, a kept Hermes screen's included |
610
667
 
@@ -622,13 +679,15 @@ answer keeps its component, density, value format and action placement.
622
679
 
623
680
  | Information | Component and context |
624
681
  | --- | --- |
625
- | Records with the same comparable facts | `DataTable`: one identity per row, typed columns, visible headings by default, and a menu when secondary mutations exist. Use the compact heading exception only for a self-describing directory. |
626
- | A descriptive item with supporting prose | `ItemGroup` and `Item`: identity, description and trailing actions. Use this for a feed or a descriptive list rather than inventing table columns. |
627
- | The identity of a record in a collection | `RecordIdentity`: name with its kind's optional mark; the containing table owns navigation. Agent and model identities keep their specialized owners. |
628
- | Facts about one object | `ObjectFacts` or `SettingsFacts`: labelled values in one coherent section. A relation uses its same identity or `Tag`; it does not become a separate card. |
629
- | A coherent set of changes | `SettingsSection` and `SettingsField`: the section owns its fields, help, recovery and local Save. Native uncontrolled forms use `SettingsForm` and `SettingsSubmitButton`; controlled editors derive their changed state from their own committed values. |
682
+ | 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. |
683
+ | Records with the same comparable facts | `DataTable`: one identity per row, typed columns, visible headings, and a menu when secondary mutations exist. |
684
+ | A descriptive item with supporting prose | `ItemGroup` and `Item`: identity, description and trailing actions, rather than invented table columns. |
685
+ | What happened, in order | `ActivityFeed`: one row per event under its day ("Activity"). |
686
+ | The identity of a record in a collection | `RecordIdentity`: its name, one muted line and its `IdentityMark`; the containing list owns navigation. |
687
+ | 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. |
688
+ | A coherent set of changes | `SettingsSection` with its `save` and `SettingsField`: the section owns its fields, help, recovery and local Save, and compares its fields with what is saved. |
630
689
  | 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. |
631
- | Another view of the current scope | `TabsNav`; an object's ancestry remains in breadcrumbs. A tab does not introduce a new scope or a second collection owner. |
690
+ | 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. |
632
691
  | 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. |
633
692
 
634
693
  One section holds one complete task or answer. A status, explanatory sentence
@@ -637,88 +696,98 @@ earn a second section by itself.
637
696
 
638
697
  ### Component selection and usage
639
698
 
640
- This catalog maps all 72 entries in the [Geist component reference](https://vercel.com/geist/introduction) to our shared owners. **Component** means a public owner exists for the job; it does not promise every Geist prop. **Composition** means existing parts can provide the described presentation together; the row names any behavior still needed. **Available upstream composition** identifies a stock shadcn example not yet packaged in our library. 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`.
699
+ Every component has a usage page, as Geist documents each of its own: what it is, and five fields.
641
700
 
642
- The shadcn column identifies official primitives, examples and blocks that supply the pattern. Prefer these compositions before adding another registry or dependency. A dedicated name in Geist does not require another primitive in our library. The separate [shadcn.io Code Block](https://www.shadcn.io/components/code-block) supplies richer code display in the wider shadcn ecosystem; it is not the official `@shadcn` registry. Neither it nor AI Elements is required for plain code, snippets or copyable values.
701
+ | Field | Says |
702
+ | --- | --- |
703
+ | When | The job it is for, with the products' own examples. |
704
+ | Instead | What to use for the jobs next to it. |
705
+ | Behaviour | Its states, what it does on each input, and what it owns. |
706
+ | Words | Which strings the product words, and how. |
707
+ | Access | Its role, its name, its keyboard path and its focus. |
708
+
709
+ 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.
710
+
711
+ The table below maps all 72 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. **Available upstream composition** identifies a stock shadcn example not yet packaged in our library. 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.
643
712
 
644
- Read the relevant row before building a surface, then use the [complete compositions](#complete-compositions) before assembling primitives. Component mechanics and supported props live in [the shared UI source](../ui/src/components); product adapters supply data, routes, translations, permissions and persistence. A component's visual owner supplies its geometry, typography, colour, focus and surface zones. A caller supplies layout and content.
713
+ Use the [complete compositions](#complete-compositions) before assembling primitives. Component mechanics and supported props live in [the shared UI source](../ui/src/components); product adapters supply data, routes, translations, permissions and persistence. A component's visual owner supplies its geometry, typography, colour, focus and surface zones. A caller supplies layout and content.
645
714
 
646
715
  Every example and consuming surface covers its reachable populated, empty, loading, invalid, disabled, failed and compact states. A failed request is not empty data; a disabled control has a reason. Controls keep their visible names, keyboard behavior and focus return. Keep product wording in sentence case. A source reference establishes a pattern, while rendered examples establish what the implementation actually looks like.
647
716
 
648
- | Geist reference | Shared owner and coverage | Official shadcn pattern | Use and composition contract |
649
- | --- | --- | --- | --- |
650
- | [Avatar](https://vercel.com/geist/avatar) | Component: `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarGroup`, `AvatarGroupCount`, `AvatarBadge` in `avatar.tsx`; Agent identity remains `AgentOrb`. | [Avatar](https://ui.shadcn.com/docs/components/base/avatar) | One person/team gets one image with literal identity and initials fallback. Groups use the group owner and a group label; overflow count is explicit. Use the [avatar owner](../ui/src/components/avatar.tsx) for a compact identity; larger object identity uses the existing object composition. Image failure retains identity. Decorative repeated image avoids double announcement. |
651
- | [Badge](https://vercel.com/geist/badge) | Component: `Badge`; `StatusBadge` for state; `Tag` for a categorical fact. Choose a typed tone; the owner supplies label geometry. | [Badge](https://ui.shadcn.com/docs/components/base/badge) | Short metadata beside its subject. A status, category and filter have different semantics. Use a word with semantic color; never a click handler on a static label. Explain lifecycle restrictions through supplementary help. Do not nest badges or pack paragraphs inside one. |
652
- | [Banner](https://vercel.com/geist/banner) | Composition: `NoticeBanner` / `MessageNotice`; no announcement-specific owner. | [Alert](https://ui.shadcn.com/docs/components/base/alert) + action Button. | Container-wide important message at its scope. Persistent condition has a resolver; transient success is a toast. Choose meaning and announcement urgency through the [NoticeBanner owner](../ui/src/components/notice-banner.tsx); a field error stays with its field. Only announce on a meaningful change. |
653
- | [Book](https://vercel.com/geist/book) | Optional composition: `Card` / `PictureLabel` + `AspectRatio` and cover artwork. No dedicated book-frame export. | [Card](https://ui.shadcn.com/docs/components/base/card) + [Aspect Ratio](https://ui.shadcn.com/docs/components/base/aspect-ratio) + cover artwork; decorative composition, no stock book primitive. | Marketing/docs cover artwork only. Never required to represent product records or Library entries. If adopted, keep its heading semantics, honest link target and stable aspect ratio; no fake spine/control behavior. |
654
- | [Breadcrumbs](https://vercel.com/geist/breadcrumbs) | Component: `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis`. | [Breadcrumb](https://ui.shadcn.com/docs/components/base/breadcrumb) | Express hierarchy, not browsing history. Ancestors link; current location uses `BreadcrumbPage`. Separators are decorative. Long paths may disclose middle ancestors, but preserve parent and current item; never replace the path with a back button. |
655
- | [Browser](https://vercel.com/geist/browser) | Optional composition: `Card` + `AspectRatio` and decorative browser chrome. `DesktopShell` remains real product chrome. | [Card](https://ui.shadcn.com/docs/components/base/card) + [Aspect Ratio](https://ui.shadcn.com/docs/components/base/aspect-ratio) + decorative header; no stock browser frame. | Use only when an actual captured browser view needs a frame. Its controls are decorative/nonfocusable. Do not wrap our live product examples in fictional browser controls or treat `DesktopShell` as an equivalent prop API. |
656
- | [Button](https://vercel.com/geist/button) | Component: `Button`, `buttonVariants`, `PendingLabel`; no native `loading` prop. | [Button](https://ui.shadcn.com/docs/components/base/button) + [Spinner](https://ui.shadcn.com/docs/components/base/spinner) for upstream pending treatment. | State-changing command uses a button; navigation uses `render` with a real anchor/router link. Primary, supporting, tertiary and destructive roles use typed variants. Name the action/object. Preserve dimensions and busy announcement while working; use `PendingLabel`, `aria-busy` and the caller's duplicate-submit protection. Icon-only needs its own action label. |
657
- | [Calendar](https://vercel.com/geist/calendar) | Component: `Calendar`; `DateRangeControl` for metric windows. | [Calendar](https://ui.shadcn.com/docs/components/base/calendar); [Date Picker](https://ui.shadcn.com/docs/components/base/date-picker) combines Calendar and Popover. | Dates/ranges whose month/day context matters. Respect locale, timezone, available window and committed selection. Presets are real buttons. Pair trigger with selected range, not a forgotten placeholder. Retain DayPicker keyboard operation; announce completed range and return focus after closing. |
658
- | [Checkbox](https://vercel.com/geist/checkbox) | Component: `Checkbox` + `FieldSet`, `FieldLegend`, `FieldLabel`. | [Checkbox](https://ui.shadcn.com/docs/components/base/checkbox) + [Field](https://ui.shadcn.com/docs/components/base/field) | Independent options, acknowledgments and row selection. Group related options semantically; label each row selection by record name. Derive mixed state from child selection. Required acknowledgment validates on submit. Mixed selection uses a distinct mark, not the checked mark. |
659
- | [Choicebox](https://vercel.com/geist/choicebox) | Composition: `FieldLabel` containing `Field`, `FieldContent`, `FieldTitle`/`FieldDescription` plus `RadioGroupItem` or `Checkbox`; no `Choicebox` export. | [Field](https://ui.shadcn.com/docs/components/base/field) choice-card examples with Checkbox or Radio Group. | Rich choices with description/icon, usually 4–6 or fewer. One selection model per group. Whole tile activates its radio/checkbox; no nested link/button stealing selection. Selected mark accompanies border. Fieldset/legend names the set; disabled reasons remain available. Make this one documented recipe rather than custom card styles. |
660
- | [Clearable Input](https://vercel.com/geist/clearable-input) | Composition: `InputGroup`, `InputGroupInput`, `InputGroupAddon`, `InputGroupButton`; no clearable wrapper. | [Input Group](https://ui.shadcn.com/docs/components/base/input-group) + InputGroupButton reset action. | Editable single-line text with an explicit reset. A named clear button resets controlled state and restores input focus. Escape clears only while the field owns the interaction and has a value; do not swallow an overlay's dismissal otherwise. Preserve disabled, invalid and empty states. |
661
- | [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. | [Typography inline-code example](https://ui.shadcn.com/r/styles/new-york-v4/typography-inline-code.json); [Typeset](https://ui.shadcn.com/docs/typeset) for rendered prose. | Inline tokens and short syntax need mono, selectable text, sufficient contrast and no visual line numbers in copied value. Static documentation code and conversation rendering have different owners. Do not expose assistant internals as a general component contract. |
662
- | [Code Block](https://vercel.com/geist/code-block) | Component composition: `CodeBlock` owns `InputGroup`, read-only `InputGroupTextarea`, optional filename/language addons and `CopyButton`. Conversation `MarkdownText` and `FileDiff` retain their specialized jobs; plain display does not imply syntax highlighting. | [Input Group](https://ui.shadcn.com/docs/components/base/input-group) code-editor example: textarea, filename addon, copy button; use readOnly for display. | Multi-line code with honest filename/language, optional copy and line context. Preserve exact runnable text and whitespace. Overflow stays readable. Name the code region and translate copy feedback through the [CodeBlock owner](../ui/src/components/code-block.tsx). Its language label describes plain read-only text; syntax highlighting, highlighted lines, language selection and line numbers remain enhanced capabilities. |
663
- | [Collapse](https://vercel.com/geist/collapse) | Component: `Accordion`, `Collapsible`; complete `Disclosure` and `ExpandableBlock`. | [Collapsible](https://ui.shadcn.com/docs/components/base/collapsible) or [Accordion](https://ui.shadcn.com/docs/components/base/accordion) | Optional/advanced content, not the page's primary answer. Related independent sections may remain open together; mutually exclusive sections may use single-open mode. Trigger names the topic and exposes expansion. Preserve useful summary and field state. Do not bury essential warning or nest many disclosure levels. |
664
- | [Combobox](https://vercel.com/geist/combobox) | Component: `Combobox`, input/content/list/item/group/label/empty/chips parts. | [Combobox](https://ui.shadcn.com/docs/components/base/combobox) | Type to filter a known set. Short fixed choices use Select; free text uses Input. Keep async loading distinct from no matches; retain current selection while fetching. Visible label, scoped query hint, deterministic keyboard selection and no accidental parent-form submit. Use the [Combobox owner](../ui/src/components/combobox.tsx) for its clear control and selection behavior. |
665
- | [Command Menu](https://vercel.com/geist/command-menu) | Component: `CommandDialog`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandShortcut`. | [Command](https://ui.shadcn.com/docs/components/base/command) + [Dialog](https://ui.shadcn.com/docs/components/base/dialog) | Global resource/action finding, not a row menu. App binds platform shortcut once. Name scope and groups; show useful defaults, retain query through nested navigation if implemented. Trap/restore focus through dialog; announce results appropriately. Page stacks/recents are app behavior, not provided public props. |
666
- | [Context Card](https://vercel.com/geist/context-card) | Component: `HoverCard`, `HoverCardTrigger`, `HoverCardContent`. | [Hover Card](https://ui.shadcn.com/docs/components/base/hover-card) | Supplementary entity preview with a few facts. A short explanation is Tooltip; a persistent editor is Sheet/page. Preserve trigger name and a stable route to full detail. Never put the only destructive action or essential information behind hover. Interactive-preview keyboard behavior must be exercised. |
667
- | [Context Menu](https://vercel.com/geist/context-menu) | Component: `ContextMenu` family; same commands as `ActionMenu`/`DropdownMenu`. | [Context Menu](https://ui.shadcn.com/docs/components/base/context-menu) | Right-click convenience on a record/canvas target. Every command remains reachable from a visible trigger. Keep same permission and consequence. Confine interception to that target; support keyboard menu invocation, Escape and focus return. Destructive commands group last; avoid deep submenu trees. |
668
- | [Copy Button](https://vercel.com/geist/copy-button) | Component composition: `CopyButton` owns Button + existing clipboard behavior, pending state and an announced result. Portal `CopyableIdentifier` binds product identifiers to it. | [Button](https://ui.shadcn.com/docs/components/base/button) + Clipboard API; [Input Group](https://ui.shadcn.com/docs/components/base/input-group) has copy-action examples. | Copy exact underlying value and acknowledge only clipboard success; expose failure/retry. Keep control stable and label the target. Translate copy feedback through the [CopyButton owner](../ui/src/components/copy-button.tsx). |
669
- | [Description](https://vercel.com/geist/description) | Component: `ObjectFacts`, `SettingsFactsSection`, `TableValue`. | [Card](https://ui.shadcn.com/docs/components/base/card) title/description or [Item](https://ui.shadcn.com/docs/components/base/item); semantic description list for key/value facts. | Key/value object facts in a semantic definition list. Static metadata is not a two-column comparison table. Key names the fact; value retains exact identifiers, links or copy action. Unknown uses accessible dash, zero remains zero. Long prose may use a block fact. |
670
- | [Destructive Action Modal](https://vercel.com/geist/destructive-action-modal) | Component for ordinary confirmation: `ConfirmDialog`; gap for typed-name confirmation. | [Alert Dialog](https://ui.shadcn.com/docs/components/base/alert-dialog) + [Field](https://ui.shadcn.com/docs/components/base/field) and Input for typed verification. | Name object, irreversible effects and recovery before destructive command. Keep failed mutation open, preserve focus and prevent duplicate commit. Use the [ConfirmDialog owner](../ui/src/components/confirm-dialog.tsx) for ordinary confirmation. A typed-name gate for a high-stakes operation needs a shared extension before a surface promises it. |
671
- | [Dots Menu](https://vercel.com/geist/dots-menu) | Component: `ActionMenu` and `DropdownMenuCommand`. | [Dropdown Menu](https://ui.shadcn.com/docs/components/base/dropdown-menu) + named icon Button. | One discoverable overflow trigger for secondary record actions. Name whose actions the [ActionMenu owner](../ui/src/components/action-menu.tsx) opens and supply real menu commands. Keep the target easy to hit and the menu inside the viewport. |
672
- | [Drawer](https://vercel.com/geist/drawer) | Component: low-level `Drawer` family; `FormDrawer` owns the right-side drawer for a catalog or a setup in steps ([Menus and commands](#menus-and-commands)). | [Drawer](https://ui.shadcn.com/docs/components/base/drawer) pattern; keep our Base UI implementation. | Geist uses mobile bottom drawers; do not silently call our right-side form behavior identical. Choose one shared adaptive recipe if parity is adopted. Preserve dirty fields, visible footer actions, contained scroll, dismissal and focus return. Destructive decisions use a blocking confirmation. |
673
- | [Empty State](https://vercel.com/geist/empty-state) | Component: [EmptyState](../ui/src/components/empty-state.tsx), following the [absence contract](#elevation--depth). | [Empty](https://ui.shadcn.com/docs/components/base/empty) + appropriate action. | Absence, unmatched filters and failed fetch are different states. First-use action exists only with permission and a real creation path. Search offers clear/reset; failure offers actual recovery. Put outside an empty table body. Permission/upgrade/cleared variants are not exported and must not be faked as noResults. |
674
- | [Entity](https://vercel.com/geist/entity) | Component: `ItemGroup`, `Item`, `ItemMedia`, `ItemContent`, `ItemTitle`, `ItemDescription`, `ItemActions`; domain `CatalogEntry`, `AgentListRow`. | [Item](https://ui.shadcn.com/docs/components/base/item) with media, content and actions. | Descriptive row with identity, secondary detail and action, not sortable comparative columns. Use divided group for peers and one coherent hit target with independent nested controls. Skeleton matches row geometry. Primary direct action may remain visible; secondary commands use overflow. |
675
- | [Error](https://vercel.com/geist/error) | Component: `FieldError`, `MessageNotice`, `EmptyState kind="failed"`, `FeedbackState` for existing owned surfaces. | [Field](https://ui.shadcn.com/docs/components/base/field) error or [Alert](https://ui.shadcn.com/docs/components/base/alert) at the relevant scope. | Put field validation by field, action failure by action, failed read where content would be. State what failed, retained work and concrete recovery. Catalog-resolved messages own product wording. Diagnostic identifier only when available; never raw exception or fake error ID. Async failure is announced without stealing useful input. |
676
- | [Error Card](https://vercel.com/geist/error-card) | Composition: existing failed-state owner within `Card`/section; no ErrorCard export. | [Card](https://ui.shadcn.com/docs/components/base/card) + [Alert](https://ui.shadcn.com/docs/components/base/alert) or [Empty](https://ui.shadcn.com/docs/components/base/empty) | One failed region retains surrounding useful page and its geometry. Do not render an empty card or turn a whole page into an error because one card failed. Recovery remains inside its region. |
677
- | [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. | [Popover](https://ui.shadcn.com/docs/components/base/popover) + [Field](https://ui.shadcn.com/docs/components/base/field), Textarea, Radio Group and Button; optional Select. | Explicit feedback submission with optional sentiment, context and a real destination. Preserve text on error and acknowledge success. Emotion controls need text names; do not fabricate sending or collect unsolicited sensitive page content. Public Geist demos supply no detailed reusable behavior contract. |
678
- | [Fieldset](https://vercel.com/geist/fieldset) | Component: `SettingsSection`, `SettingsField`, `SettingsForm` and `SettingsSubmitButton`; `FieldSet`/`FieldLegend` for semantic subgroups. | [Card](https://ui.shadcn.com/docs/components/base/card) + [Field](https://ui.shadcn.com/docs/components/base/field) and footer action. | One independently saved setting group: heading/description, content, feedback, recessed footer with local save. Use `controlProps` from SettingsField to bind id/help/error/disabled/required. Gated state explains how to resolve it; a disabled wall is not yet a shared prop. Header and content share one body plane; the local save footer uses the recessed fill. |
679
- | [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. | [sidebar-11](https://ui.shadcn.com/blocks/sidebar#sidebar-11): Sidebar + Collapsible for nested file navigation. | Hierarchical files when structure is the task. Use semantic nested lists and Collapsible for expandable file navigation, following sidebar-11. A tree widget with `role="tree"` additionally needs roving keyboard focus, expand/collapse and selection semantics; do not claim those from the disclosure pattern alone. |
680
- | [Gauge](https://vercel.com/geist/gauge) | Available upstream composition: chart-radial-text uses Card + Chart + installed Recharts. Our existing `UsageMeter`/`Progress` cover linear readings; a radial wrapper is not yet exported. | [Radial Charts](https://ui.shadcn.com/charts/radial), especially chart-radial-text; existing Recharts dependency. | Ratio/allowance with a meaningful maximum and adjacent value/label; never fake a percentage for unknown work. The product owns thresholds; a source example cannot change them. Circular form is a visual choice requiring a shared owner if adopted; no radial API exists today. |
681
- | [Grid](https://vercel.com/geist/grid) | Composition: layout grid, `CardRow`, website `nc-page`; no ruled `Grid` API. | [Card](https://ui.shadcn.com/docs/components/base/card) in CSS grid; ruled presentation guides are decorative layout. | Ordinary card layout uses layout utilities/CardRow. Geist visible guides are for marketing/docs composition; guides are decorative, do not affect reading order, and are drawn once at joins. Do not install a grid mechanism merely for normal responsive columns. |
682
- | [Input](https://vercel.com/geist/input) | Component: `Input`, `InputGroup`, `SettingsField`/`Field`. | [Input](https://ui.shadcn.com/docs/components/base/input) + [Field](https://ui.shadcn.com/docs/components/base/field) | One free-form value. Correct type/autocomplete/inputMode, visible label, stable help and field error. Domain rules decide trimming; never trim passwords/secrets/code indiscriminately. Preserve entry through request failures. Size and state belong to owner; label/error strings are composed, not built-in Input props. |
683
- | [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. | [Input Group](https://ui.shadcn.com/docs/components/base/input-group) for static code; [Collapsible](https://ui.shadcn.com/docs/components/base/collapsible) for disclosure. Full JSON tree navigation/search is additional behavior. | Structured payload inspection warrants an accessible expandable tree with selectable/copyable values. Static documentation can use a code block. No `defaultExpandDepth`/`highlightPattern` API exists in our package; do not claim conversation output is parity. |
684
- | [Keyboard Input](https://vercel.com/geist/keyboard-input) | Component: `Kbd`, `KbdGroup`, `CommandShortcut`; platform mapping is caller-owned. | [Kbd](https://ui.shadcn.com/docs/components/base/kbd) and KbdGroup. | Shortcut hints near a command, not an interactive input. Keep each key meaningful, surrounding prose outside the key. Show OS-correct modifiers and only shortcuts actually bound. Our Kbd has no Geist modifier booleans or automatic OS mapping. |
685
- | [Label](https://vercel.com/geist/label) | Component: `Label`, `FieldLabel`, `FieldLegend`. | [Field](https://ui.shadcn.com/docs/components/base/field) label/legend or [Label](https://ui.shadcn.com/docs/components/base/label) | Visible name attached to the control or semantic group. Placeholder is example/hint, not label replacement. Mark requirement and help separately. Do not use another aria-label that overrides an already correct visible label. |
686
- | [Load More Button](https://vercel.com/geist/load-more-button) | Composition: `Button` + `PendingLabel` and app pagination; no complete owner. | [Button](https://ui.shadcn.com/docs/components/base/button) + pending feedback and app pagination. | Append records while preserving scroll, previous rows and focus. Expose pending/error/exhausted states; no duplicate fetch or disappearance while focused. A request for another page differs from exposing already-loaded hidden content. |
687
- | [Loading Dots](https://vercel.com/geist/loading-dots) | Personalization: `PendingLabel` / `WaitingLine`; no LoadingDots. | [Spinner](https://ui.shadcn.com/docs/components/base/spinner) supplies upstream indeterminate feedback; retain our waiting treatment. | Inline indeterminate wait names actual work, announces busy state once, and uses one indicator. Agent work uses the Agent's own state; system waiting follows the activity rules below. Never animate completed text. |
688
- | [Menu](https://vercel.com/geist/menu) | Component: `DropdownMenu` family, `DropdownMenuCommand`, `ActionMenu`. | [Dropdown Menu](https://ui.shadcn.com/docs/components/base/dropdown-menu) | Resource actions from a discoverable trigger. Choice lists use appropriate radio/checkbox menu items. Keep label first, target clear, related groups separated and destructive group last. Use library positioning, typeahead, keyboard selection, Escape and focus return. A locked command needs readable reason. |
689
- | [MiddleTruncate](https://vercel.com/geist/middle-truncate) | Composition for technical text: mono value + `Tooltip` + copy action. The width-aware middle-truncation behavior is not provided by those parts or a public shared owner. | [Tooltip](https://ui.shadcn.com/docs/components/base/tooltip) + code/text layout and copy. No stock middle-truncation algorithm. | Paths/URLs/IDs where prefix and suffix matter. Keep original text available and copy the original, not ellipsis. Prose uses wrapping or end truncation. Avoid jitter under resize, preserve accessible full value, and never make tooltip the only way to recover an essential action. |
690
- | [Modal](https://vercel.com/geist/modal) | Component: `Dialog` family, `FormDialog` for short forms, and `ConfirmDialog` for consequences. | [Dialog](https://ui.shadcn.com/docs/components/base/dialog) | Blocking decision or bounded focused job. Title, description when useful, scrollable body and footer; return focus predictably. Save failure retains data. Allow safe dismissal; explicitly protect unsaved/destructive context. Persistent context uses Sheet/page, not stacked dialogs. |
691
- | [Multi Select](https://vercel.com/geist/multi-select) | Composition: `Combobox multiple` with chips/list; `CollectionFilters` for query facets; checkbox menus for small sets. | [Combobox](https://ui.shadcn.com/docs/components/base/combobox) multiple mode and chips. | Multiple known values with visible selection/count, labelled remove controls and empty/search states. Match controlled state to URL/server. Keep keyboard movement/removal delegated to Base UI. No separate MultiSelect export or built-in select-only/all contract. |
692
- | [Note](https://vercel.com/geist/note) | Component: `Alert`, `NoticeBanner`, `MessageNotice`. | [Alert](https://ui.shadcn.com/docs/components/base/alert) | Persistent contextual explanation/constraint next to its subject. Choose semantic tone, one concept and one resolver. Success/violet/cyan extensions must be real typed variants before taught. Avoid dismissing an unresolved important condition, or using a toast as its only home. |
693
- | [Pagination](https://vercel.com/geist/pagination) | Component for numbered/data pager: `Pagination` family / `DataTable`; compose Geist's previous/next article rail from links. | [Pagination](https://ui.shadcn.com/docs/components/base/pagination); named links for an article rail. | Distinguish navigating sibling pages from paging rows. Data pager keeps count/range and disabled boundaries; article rail names the next destination and omits absent ends. Announce current page and route through the app. |
694
- | [Phone](https://vercel.com/geist/phone) | Optional composition: `AspectRatio` + decorative frame and captured content. No dedicated device-frame export. | [Aspect Ratio](https://ui.shadcn.com/docs/components/base/aspect-ratio) + decorative frame; no stock phone primitive. | Captured mobile showcase only; decorative chrome hidden from accessibility, content has meaningful alternative. Preserve aspect ratio and paused/reduced-motion behavior. Product parity does not require fake device surrounds. |
695
- | [Pill](https://vercel.com/geist/badge#pill) | Component for static category: `Tag`; filters: `CollectionFilters`; links/buttons keep their semantics. | [Badge](https://ui.shadcn.com/docs/components/base/badge) for metadata; [Toggle Group](https://ui.shadcn.com/docs/components/base/toggle-group) or buttons for interactive state. | A shape is not a new interaction. Static metadata stays static; active filter has remove/reset and state; navigation is a link. Avoid wrapping arbitrary clickable badge spans. |
696
- | [Progress](https://vercel.com/geist/progress) | Component: `Progress`, `ProgressTrack`, `ProgressIndicator`, `ProgressLabel`, `ProgressValue`; `UsageMeter`. | [Progress](https://ui.shadcn.com/docs/components/base/progress) | Known completed amount against real `max`; print work and units. Unknown wait uses waiting treatment. Semantic warnings come from product thresholds. Accessible value/name and sensible update frequency; completion becomes the actual finished state. Stops/labels are not a provided prop. |
697
- | [Project Banner](https://vercel.com/geist/project-banner) | Composition: scoped `MessageNotice`/`NoticeBanner`; prioritization belongs to the app. | [Alert](https://ui.shadcn.com/docs/components/base/alert) + action at the page scope. | One strongest unresolved object-wide condition above its content, with a resolving action. It remains until underlying state changes. App chooses urgency and authorization. Never stack duplicate section notices or dismiss away a blocking condition. |
698
- | [Radio](https://vercel.com/geist/radio) | Component: `RadioGroup`, `RadioGroupItem` with FieldSet/legend. | [Radio Group](https://ui.shadcn.com/docs/components/base/radio-group) + [Field](https://ui.shadcn.com/docs/components/base/field) | Small mutually exclusive choices; rich ones use choice tiles; large searchable ones use Combobox. Safe default when justified; require deliberate choice when consequential. Group label, named options, arrow selection, one tab stop and disabled reasons. |
699
- | [Relative Time Card](https://vercel.com/geist/relative-time-card) | Composition: `HoverCard` + semantic time + locale formatting. Portal already has `formatRelativeTime` and `portalRelativeTimeFormatter`; a shared wrapper can accept formatted strings from the product. | [Hover Card](https://ui.shadcn.com/docs/components/base/hover-card) + semantic time and locale formatting. | Recent time in dense rows with exact date/timezone on focus/hover. Centralize compact locale format and transition to absolute dates; preserve machine-readable timestamp. Missing/stale/loading differ. Do not claim Vercel's seven-day cutoff already exists. |
700
- | [Scroller](https://vercel.com/geist/scroller) | Component: `ScrollArea`, `ScrollBar`; `Carousel` for a controlled rail. | [Scroll Area](https://ui.shadcn.com/docs/components/base/scroll-area); [Carousel](https://ui.shadcn.com/docs/components/base/carousel) for a controlled rail. | One overflow axis where appropriate; preserve reading order, keyboard focus visibility and meaningful controls. Large datasets require a measured virtualization need and owner. Our ScrollArea has no Geist direct-child scroll-button API. |
701
- | [Search Input](https://vercel.com/geist/search-input) | Component: `CollectionSearch` in `CollectionToolbar`; richer reset/loading is a gap. | [Input Group](https://ui.shadcn.com/docs/components/base/input-group) search icon, input, reset button and pending state. | Scope-specific query, clear/reset, retained results while updating and no-results recovery. Use [CollectionSearch](../ui/src/components/collection-search.tsx) for the query control; compose a reset when the task requires one. Preserve the global command-menu shortcut. |
702
- | [Select](https://vercel.com/geist/select) | Component: `Select` family, `NativeSelect` family. | [Select](https://ui.shadcn.com/docs/components/base/select) or [Native Select](https://ui.shadcn.com/docs/components/base/native-select) | Short fixed known options; a few meaningful options may be radio/segmented; many searchable ones use Combobox. Label and validation are field composition. Keep placeholder distinct from an actual option, groups semantic and keyboard selection native to primitive. |
703
- | [Separator](https://vercel.com/geist/separator) | Component: `Separator`; `FieldSeparator` and menu/group separators for scoped roles. | [Separator](https://ui.shadcn.com/docs/components/base/separator) | Divide distinct groups, not every paragraph. Each join has one line. Preserve semantic orientation and decorative treatment. Frame inset is needed only where overlaid edge would double a line; do not add caller borders to an elevated owner. |
704
- | [Sheet](https://vercel.com/geist/sheet) | Component: `Sheet` family; actual modal/dismissal defaults differ from Geist. | [Sheet](https://ui.shadcn.com/docs/components/base/sheet) | Persistent associated context such as row inspection. Title, explicit close, contained scrolling and focus return to originating record. Decide nonmodal behavior deliberately; do not repeat Geist's contradictory “nonmodal but trap focus” guidance mechanically. Blocking destructive consequence uses confirmation. |
705
- | [Show more](https://vercel.com/geist/show-more) | Composition: `Disclosure`/`Collapsible`/`ExpandableBlock` depending content. | [Collapsible](https://ui.shadcn.com/docs/components/base/collapsible) + named trigger. | Reveal already-loaded supporting content with meaningful hidden count and stable context. Trigger exposes expansion and target; preserve reading position and focus. Do not call a network fetch merely “show more” without pending/error behavior. |
706
- | [Skeleton](https://vercel.com/geist/skeleton) | Component: `Skeleton`. | [Skeleton](https://ui.shadcn.com/docs/components/base/skeleton) | Known loading geometry only; match final shape and avoid layout shift. Loading region is busy, skeleton decorative/nonfocusable. It must resolve to data, absence or failure. Static is our current material choice; shimmer adoption must reconcile motion owner. |
707
- | [Slider](https://vercel.com/geist/slider) | Component: `Slider`; compose with `Input` for precision. | [Slider](https://ui.shadcn.com/docs/components/base/slider) + [Input](https://ui.shadcn.com/docs/components/base/input) for exact values. | Bounded range where position matters, with explicit minimum/maximum/step, current value/unit and accessible thumb labels. Exact numeric entry remains available when precision matters. Keyboard operation stays with primitive; no fabricated threshold colors. |
708
- | [Snippet](https://vercel.com/geist/snippet) | Composition: read-only mono `InputGroupInput` + addon `CopyButton`, or `CodeBlock` for multiple lines. A shell prompt is separate from the copied value; no separate selection or clipboard engine is needed. | [Input Group](https://ui.shadcn.com/docs/components/base/input-group) read-only mono value + copy button. | A short exact command or value intended for copying. Displayed shell prompt is not copied. One command, selectable code and honest copy success/failure. Secrets need explicit reveal policy and must not leak into logs/labels. Build on existing clipboard capability. |
709
- | [Spinner](https://vercel.com/geist/spinner) | Personalization: `PendingLabel`/`WaitingLine`, no Spinner export. | [Spinner](https://ui.shadcn.com/docs/components/base/spinner) is the upstream alternative; retain our waiting treatment. | Short indeterminate system action. The shared waiting recipe owns stable sizing, focus and announcement; never replace Agent identity with spinner. Respect reduced motion and avoid two indicators for one wait. |
710
- | [Split Button](https://vercel.com/geist/split-button) | Composition: `ButtonGroup`, `Button`, `DropdownMenu`; no unified SplitButton. | [Button Group](https://ui.shadcn.com/docs/components/base/button-group) split/dropdown examples. | Clear default with a few closely related alternatives. Menu trigger names the action family. Include default in menu, share pending/disabled state and keep destructive actions out of an ambiguous split primary. A reusable wrapper avoids caller seam/rounding mistakes. |
711
- | [Status Dot](https://vercel.com/geist/status-dot) | Component: [StatusDot](../ui/src/components/status-dot.tsx) and `StatusBadge`. | [Badge](https://ui.shadcn.com/docs/components/base/badge) + labelled status dot; existing StatusDot owns our visual. | Our dot is generic; Geist's is specifically deployment lifecycle. Do not import Vercel state vocabulary into Agents. Pair with readable state; decorative dot when text already names it. Motion/status meaning remains truthful to product state. |
712
- | [Switch](https://vercel.com/geist/switch) | Composition: `RadioGroup` for mutually exclusive segmented values; `Tabs` for panels; `ToggleGroup` where pressed-button semantics fit. | [Radio Group](https://ui.shadcn.com/docs/components/base/radio-group), [Toggle Group](https://ui.shadcn.com/docs/components/base/toggle-group) or [Tabs](https://ui.shadcn.com/docs/components/base/tabs) according to selection semantics. | Geist Switch means segmented selection, not our boolean `Switch`. Two/three short peer values, one selected state, stable widths and named options. Choose radio or tab semantics by job; do not mislabel button toggles as radios. |
713
- | [Table](https://vercel.com/geist/table) | Component: [Table](../ui/src/components/table.tsx) family + `DataTable` + `TableCard` + `TableValue`; density, striping and borders belong to the shared owner. Virtualization remains a separate capability. | [Table](https://ui.shadcn.com/docs/components/base/table) + [Data Table](https://ui.shadcn.com/docs/components/base/data-table) with TanStack Table. | Comparable shared columns use table; descriptive rows use Item; key/value facts use ObjectFacts. Visible column labels when comparing/sorting. Numeric columns tabular/right aligned. Shared toolbar, body, totals and footer own consistent geometry. Empty state sits outside body; rows keep stable IDs, nested controls and compact details. |
714
- | [Tabs](https://vercel.com/geist/tabs) | Component: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`; URL views use `TabsNav`/`TabsLink`. | [Tabs](https://ui.shadcn.com/docs/components/base/tabs) | Sibling views of the same object. App owns route/query persistence. Choose the shared `line` variant for an underline or the default variant for a segmented treatment, keeping siblings consistent; never draw either in route styles. Keep overflow navigable and selection/focus distinct; label tablist, preserve keyboard behavior and use nouns for views. |
715
- | [Text With Copy Button](https://vercel.com/geist/text-with-copy-button) | Composition: mono text + `CopyButton`; Portal `CopyableIdentifier` supplies the product value and translated labels. Copying and result feedback stay in the shared owner. | [Input Group](https://ui.shadcn.com/docs/components/base/input-group) or text + [Button](https://ui.shadcn.com/docs/components/base/button) and clipboard behavior. | IDs/addresses users actually need elsewhere. Keep exact value and target-specific copy label; acknowledge true success, handle failure and expose full value if truncated. Avoid turning ordinary prose into copyable chrome. |
716
- | [Textarea](https://vercel.com/geist/textarea) | Component: `Textarea` + Field/SettingsField. | [Textarea](https://ui.shadcn.com/docs/components/base/textarea) + [Field](https://ui.shadcn.com/docs/components/base/field) | Multiline description, notes or code. Useful rows/maximum growth, retained content, visible label/help/error and character limit only when real. Do not trim deliberate whitespace in code or secrets. Ensure footer actions stay reachable on compact screens. |
717
- | [Theme Switcher](https://vercel.com/geist/theme-switcher) | Component: `ThemeModeSwitcher`, `ThemeModeMenu`. | [Dark mode](https://ui.shadcn.com/docs/dark-mode) + [Dropdown Menu](https://ui.shadcn.com/docs/components/base/dropdown-menu) or radio choices; app theme provider owns persistence. | One canonical light/system/dark preference per shell/settings context, app provider is source of truth. Bind the [theme controls](../ui/src/components/theme-mode-switcher.tsx) to that provider, name the group and localize its choices. Respect forced mode in the product adapter. |
718
- | [Toast](https://vercel.com/geist/toast) | Component: `Toaster`; `showMessageToast` from `@nadicodeai/ui/components/toast`. | [Toast](https://ui.shadcn.com/docs/components/base/toast) for current Base UI source; our existing Toaster/message adapter remains the public owner. | One terminal acknowledgment per user action. Field validation and persistent urgent problems retain their local home. Product catalog supplies wording. Only show Undo when safe rollback exists; do not make transient toast the only route to recovery or essential navigation. |
719
- | [Toggle](https://vercel.com/geist/toggle) | Component: our `Switch` for boolean setting; our `Toggle` is a pressed button. | [Switch](https://ui.shadcn.com/docs/components/base/switch) for a boolean setting; [Toggle](https://ui.shadcn.com/docs/components/base/toggle) for a pressed action. | Name what ON means. Persist immediately only when the actual setting contract says so; otherwise belong to a saved setting group. Expose pending failure and recover to truthful state. Checkbox is for selection/acknowledgment, segmented control for exclusive values. |
720
- | [Tooltip](https://vercel.com/geist/tooltip) | Component: `TooltipProvider`, `Tooltip`, `TooltipTrigger`, `TooltipContent`. | [Tooltip](https://ui.shadcn.com/docs/components/base/tooltip) | Short supplementary constraint or meaning, hover and focus. Visible control already names itself. Never sole home for important action/error or substitute for icon button's accessible name. Rich entity preview uses HoverCard; forms help remains connected field text. |
721
- | [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. | [Aspect Ratio](https://ui.shadcn.com/docs/components/base/aspect-ratio) + native video controls, poster and caption tracks. | Real media with controls, captions/transcript where speech matters, deliberate autoplay/mute and reduced-motion poster. Preserve layout while loading and recover from unavailable media. Do not create dummy controls or claim a general video system from one marketing embedding. |
717
+ | Geist reference | Shared owner |
718
+ | --- | --- |
719
+ | [Avatar](https://vercel.com/geist/avatar) | Component: `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarGroup`, `AvatarGroupCount`, `AvatarBadge` in `avatar.tsx`; a record's mark, a person's included, is `IdentityMark`, and an Agent is her `AgentOrb`. |
720
+ | [Badge](https://vercel.com/geist/badge) | Component: `Badge`; `StatusBadge` for state; `Tag` for a categorical fact. Choose a typed tone; the owner supplies label geometry. |
721
+ | [Banner](https://vercel.com/geist/banner) | Composition: `NoticeBanner` / `MessageNotice`; no announcement-specific owner. |
722
+ | [Book](https://vercel.com/geist/book) | Optional composition: `Card` / `PictureLabel` + `AspectRatio` and cover artwork. No dedicated book-frame export. |
723
+ | [Breadcrumbs](https://vercel.com/geist/breadcrumbs) | Component: `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis`. |
724
+ | [Browser](https://vercel.com/geist/browser) | Optional composition: `Card` + `AspectRatio` and decorative browser chrome. `DesktopShell` remains real product chrome. |
725
+ | [Button](https://vercel.com/geist/button) | Component: `Button`, `buttonVariants`, `PendingLabel`; no native `loading` prop. |
726
+ | [Calendar](https://vercel.com/geist/calendar) | Component: `Calendar`; `DateRangeControl` for metric windows. |
727
+ | [Checkbox](https://vercel.com/geist/checkbox) | Component: `Checkbox` + `FieldSet`, `FieldLegend`, `FieldLabel`. |
728
+ | [Choicebox](https://vercel.com/geist/choicebox) | Composition: `FieldLabel` containing `Field`, `FieldContent`, `FieldTitle`/`FieldDescription` plus `RadioGroupItem` or `Checkbox`; no `Choicebox` export. |
729
+ | [Clearable Input](https://vercel.com/geist/clearable-input) | Composition: `InputGroup`, `InputGroupInput`, `InputGroupAddon`, `InputGroupButton`; no clearable wrapper. |
730
+ | [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. |
731
+ | [Code Block](https://vercel.com/geist/code-block) | Component composition: `CodeBlock` owns `InputGroup`, read-only `InputGroupTextarea`, optional filename/language addons and `CopyButton`. Conversation `MarkdownText` and `FileDiff` retain their specialized jobs; plain display does not imply syntax highlighting. |
732
+ | [Collapse](https://vercel.com/geist/collapse) | Component: `Accordion`, `Collapsible`; complete `Disclosure` and `ExpandableBlock`. |
733
+ | [Combobox](https://vercel.com/geist/combobox) | Component: `Combobox`, input/content/list/item/group/label/empty/chips parts. |
734
+ | [Command Menu](https://vercel.com/geist/command-menu) | Component: `CommandDialog`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandShortcut`. |
735
+ | [Context Card](https://vercel.com/geist/context-card) | Component: `HoverCard`, `HoverCardTrigger`, `HoverCardContent`. |
736
+ | [Context Menu](https://vercel.com/geist/context-menu) | Component: `ActionContextMenu`, the same `RecordMenu` as the row's `ActionMenu`, over the `ContextMenu` family. |
737
+ | [Copy Button](https://vercel.com/geist/copy-button) | Component composition: `CopyButton` owns Button + existing clipboard behavior, pending state and an announced result. Portal `CopyableIdentifier` binds product identifiers to it. |
738
+ | [Description](https://vercel.com/geist/description) | Component: `ObjectPage`'s `facts`, `ObjectFacts`, `SettingsFactsSection`, `TableValue`. |
739
+ | [Destructive Action Modal](https://vercel.com/geist/destructive-action-modal) | Component for ordinary confirmation: `ConfirmDialog`; gap for typed-name confirmation. |
740
+ | [Dots Menu](https://vercel.com/geist/dots-menu) | Component: `ActionMenu` for a record, `PageActions`' `menu` for a page. |
741
+ | [Drawer](https://vercel.com/geist/drawer) | Component: `CatalogDrawer` for a catalogue and `FormDrawer job="steps"` for a setup in steps, over the low-level `Drawer` family ([Menus and commands](#menus-and-commands)). |
742
+ | [Empty State](https://vercel.com/geist/empty-state) | Component: [EmptyState](../ui/src/components/empty-state.tsx), following the [absence contract](#elevation--depth). |
743
+ | [Entity](https://vercel.com/geist/entity) | Component: `RecordList` for records; `CatalogEntry` in `LibraryShelf` and `CatalogDrawer`; `AgentListRow`; `ItemGroup` and its parts for a descriptive list. |
744
+ | [Error](https://vercel.com/geist/error) | Component: `FieldError`, `MessageNotice`, `EmptyState kind="failed"`. |
745
+ | [Error Card](https://vercel.com/geist/error-card) | Composition: existing failed-state owner within `Card`/section; no ErrorCard export. |
746
+ | [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. |
747
+ | [Fieldset](https://vercel.com/geist/fieldset) | Component: `SettingsSection` with its `save`, `SettingsField`; `FieldSet`/`FieldLegend` for semantic subgroups. |
748
+ | [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. |
749
+ | [Gauge](https://vercel.com/geist/gauge) | Available upstream composition: chart-radial-text uses Card + Chart + installed Recharts. Our existing `UsageMeter`/`Progress` cover linear readings; a radial wrapper is not yet exported. |
750
+ | [Grid](https://vercel.com/geist/grid) | Composition: layout grid, `CardRow`, website `nc-page`; no ruled `Grid` API. |
751
+ | [Input](https://vercel.com/geist/input) | Component: `Input`, `InputGroup`, `SettingsField`/`Field`. |
752
+ | [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. |
753
+ | [Keyboard Input](https://vercel.com/geist/keyboard-input) | Component: `Kbd`, `KbdGroup`, `CommandShortcut`; platform mapping is caller-owned. |
754
+ | [Label](https://vercel.com/geist/label) | Component: `Label`, `FieldLabel`, `FieldLegend`. |
755
+ | [Load More Button](https://vercel.com/geist/load-more-button) | Component: `ActivityFeed`'s `older`; elsewhere `Button` + `PendingLabel` and app pagination. |
756
+ | [Loading Dots](https://vercel.com/geist/loading-dots) | Personalization: `PendingLabel` / `WaitingLine`; no LoadingDots. |
757
+ | [Menu](https://vercel.com/geist/menu) | Component: `ActionMenu`, `ActionContextMenu`, `PageActions`' `menu` and `AccountMenu`, which draw their rows with `DropdownMenuCommand`; the `DropdownMenu` family directly only for a choice menu such as a sort. |
758
+ | [MiddleTruncate](https://vercel.com/geist/middle-truncate) | Composition for technical text: mono value + `Tooltip` + copy action. The width-aware middle-truncation behavior is not provided by those parts or a public shared owner. |
759
+ | [Modal](https://vercel.com/geist/modal) | Component: `Dialog` family, `FormDialog` for short forms, and `ConfirmDialog` for consequences. |
760
+ | [Multi Select](https://vercel.com/geist/multi-select) | Composition: `Combobox multiple` with chips/list; `CollectionFilters` for query facets; checkbox menus for small sets. |
761
+ | [Note](https://vercel.com/geist/note) | Component: `Alert`, `NoticeBanner`, `MessageNotice`. |
762
+ | [Pagination](https://vercel.com/geist/pagination) | Component for numbered/data pager: `Pagination` family / `DataTable`; compose Geist's previous/next article rail from links. |
763
+ | [Phone](https://vercel.com/geist/phone) | Optional composition: `AspectRatio` + decorative frame and captured content. No dedicated device-frame export. |
764
+ | [Pill](https://vercel.com/geist/badge#pill) | Component for static category: `Tag`; filters: `CollectionFilters`; links/buttons keep their semantics. |
765
+ | [Progress](https://vercel.com/geist/progress) | Component: `Progress`, `ProgressTrack`, `ProgressIndicator`, `ProgressLabel`, `ProgressValue`; `UsageMeter`. |
766
+ | [Project Banner](https://vercel.com/geist/project-banner) | Composition: scoped `MessageNotice`/`NoticeBanner`; prioritization belongs to the app. |
767
+ | [Radio](https://vercel.com/geist/radio) | Component: `RadioGroup`, `RadioGroupItem` with FieldSet/legend. |
768
+ | [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. |
769
+ | [Scroller](https://vercel.com/geist/scroller) | Component: `ScrollArea`, `ScrollBar`; `Carousel` for a controlled rail. |
770
+ | [Search Input](https://vercel.com/geist/search-input) | Component: `CollectionSearch` in `CollectionToolbar`; richer reset/loading is a gap. |
771
+ | [Select](https://vercel.com/geist/select) | Component: `Select` family, `NativeSelect` family. |
772
+ | [Separator](https://vercel.com/geist/separator) | Component: `Separator`; `FieldSeparator` and menu/group separators for scoped roles. |
773
+ | [Sheet](https://vercel.com/geist/sheet) | Component: `RecordSheet` for one record looked at from a list, over the `Sheet` family. |
774
+ | [Show more](https://vercel.com/geist/show-more) | Composition: `Disclosure`/`Collapsible`/`ExpandableBlock` depending content. |
775
+ | [Skeleton](https://vercel.com/geist/skeleton) | Component: `Skeleton`. |
776
+ | [Slider](https://vercel.com/geist/slider) | Component: `Slider`; compose with `Input` for precision. |
777
+ | [Snippet](https://vercel.com/geist/snippet) | Composition: read-only mono `InputGroupInput` + addon `CopyButton`, or `CodeBlock` for multiple lines. A shell prompt is separate from the copied value; no separate selection or clipboard engine is needed. |
778
+ | [Spinner](https://vercel.com/geist/spinner) | Personalization: `PendingLabel`/`WaitingLine`, no Spinner export. |
779
+ | [Split Button](https://vercel.com/geist/split-button) | Composition: `ButtonGroup`, `Button`, `DropdownMenu`; no unified SplitButton. |
780
+ | [Status Dot](https://vercel.com/geist/status-dot) | Component: [StatusDot](../ui/src/components/status-dot.tsx) and `StatusBadge`. |
781
+ | [Switch](https://vercel.com/geist/switch) | Composition: `RadioGroup` for mutually exclusive segmented values; `Tabs` for panels; `ToggleGroup` where pressed-button semantics fit. |
782
+ | [Table](https://vercel.com/geist/table) | Component: `RecordList` for rows that name themselves; [Table](../ui/src/components/table.tsx) family + `DataTable` + `TableCard` + `TableValue` for a comparison. Virtualization remains a separate capability. |
783
+ | [Tabs](https://vercel.com/geist/tabs) | Component: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`; URL views use `TabsNav`/`TabsLink`. |
784
+ | [Text With Copy Button](https://vercel.com/geist/text-with-copy-button) | Composition: mono text + `CopyButton`; Portal `CopyableIdentifier` supplies the product value and translated labels. Copying and result feedback stay in the shared owner. |
785
+ | [Textarea](https://vercel.com/geist/textarea) | Component: `Textarea` + Field/SettingsField. |
786
+ | [Theme Switcher](https://vercel.com/geist/theme-switcher) | Component: `ThemeModeSwitcher`, `ThemeModeMenu`. |
787
+ | [Toast](https://vercel.com/geist/toast) | Component: `Toaster`; `showMessageToast` from `@nadicodeai/ui/components/toast`. |
788
+ | [Toggle](https://vercel.com/geist/toggle) | Component: our `Switch` for boolean setting; our `Toggle` is a pressed button. |
789
+ | [Tooltip](https://vercel.com/geist/tooltip) | Component: `TooltipProvider`, `Tooltip`, `TooltipTrigger`, `TooltipContent`. |
790
+ | [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. |
722
791
 
723
792
  ### Additional shared building blocks
724
793
 
@@ -726,7 +795,7 @@ These shared components also follow the same foundations, although Geist does no
726
795
 
727
796
  | Owner | Use and composition contract |
728
797
  | --- | --- |
729
- | `Sidebar`, `DashboardShell`, `DesktopShell`, `PortalSidebarFrame` | Persistent product navigation and workspace chrome. The product owns destinations and active state; the shell owns compact behavior, landmarks and layout. Never add a competing sidebar to a page. |
798
+ | `Sidebar`, `SidebarSections`, `UpdateCard`, `AccountMenu`, `DashboardShell`, `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, `UpdateCard` the offer to update above the account. Never add a competing sidebar to a page. |
730
799
  | `NavigationMenu` | A site's grouped navigation links, with genuine destinations and usable keyboard paths. Product object views use shared tabs; commands use a menu. |
731
800
  | `Menubar` | A persistent application command bar with related menus. Use only when the workspace has that command model; do not substitute it for website navigation or a row's actions. |
732
801
  | `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). |
@@ -747,27 +816,31 @@ Choose the owner for the whole task. Keep its slots and state handling together
747
816
 
748
817
  | Task | Shared composition | What the product adapter supplies |
749
818
  | --- | --- | --- |
750
- | Independently saved settings | `ReadingColumn` → `SettingsSection` → `SettingsField`; Portal selects the configuration width and form presentation through `SettingsPage` and its `SettingsSection` adapter. `SettingsForm` and `SettingsSubmitButton` own native form dirty/pending state. `footerHint` and `footer` stay in the recessed strip. | Values, validation, dirty/pending state, local Save, retained input and failure. Header and content share one body plane; the footer has one seam. |
751
- | Read-only 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. |
752
- | Entity collection | `CollectionPage`: the title's actions, search and sort, then rows that identify themselves (`RecordIdentity` with its `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, 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. |
753
- | Comparable collection | `PageHeader`/`PageActions`, `CollectionToolbar`, `DataTable` within `TableCard` when framed. | Stable record IDs, columns, visible headings, search/filter/sort, URL state, row commands, pagination and `empty`. Numeric columns align at the trailing edge; totals and paging use their own footer zone. |
754
- | Descriptive entity list | `ItemGroup divided` with `ItemMedia`, `ItemContent`, `ItemTitle`, `ItemDescription`, `ItemActions`; domain owners include `AgentListRow` and `CatalogEntry`. | Identity, a short description, categorical facts, one primary task and secondary menu. Long values wrap or truncate predictably; a row never gains a second nested hit target by accident. |
755
- | Overview figures | `CardRow` with `KpiCard` or the chosen chart card. | Measurement, unit, comparison period, truthful delta and stable categorical assignment. Related cards align; status colour and series colour retain different meanings. |
756
- | Object workspace | `ObjectOverview`, `ObjectIdentity`, `ObjectFacts`, `ObjectSection` and shared tabs. | The actual object, scope, hierarchy, sections, routes and commands. Agent identity retains its orb and the Agent-specific composition. |
757
- | 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 or a setup in steps uses `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. |
758
- | Destructive confirmation | `ConfirmDialog`; the app's confirm-action adapter. | Named target, real consequence, labels, pending state, failure and operation. Typed-name verification is not a supported prop; extend the owner when that capability is needed. |
759
- | Global commands or record actions | `CommandDialog` for cross-resource search; `ActionMenu` and `DropdownMenuCommand` for one record. | Real commands, grouping, permissions, disabled reasons and keyboard shortcuts. Choose by scope; keep the destructive group last. |
760
- | Absence or failed read | `EmptyState` through `DataTable.empty` or the affected content region; `MessageNotice` for persistent conditions. | First use, nothing yet, no matches or failed read, with the real recovery action. Empty state belongs outside the semantic table body. |
819
+ | Settings | `SettingsPage` → `SettingsSection` with its `save` → `SettingsField`. | The section's title and one line, its fields, what saving does (`save.action`), the save's words and its hint. The section owns the changed state, Save, "Saved" and a failure under its field. |
820
+ | 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. |
821
+ | 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. |
822
+ | 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. |
823
+ | 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; totals and paging use their own footer zone. |
824
+ | 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. |
825
+ | 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`. |
826
+ | A reusable thing to add | `LibraryShelf` with `CatalogEntry`s; `CatalogDrawer` where the choice opens from a page. | The shelf's definition line, the entries with their kind's tile, one line each, the add and its state. |
827
+ | 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. |
828
+ | 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. |
829
+ | 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. |
830
+ | 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. |
831
+ | 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. Typed-name verification is not a supported prop; extend the owner when that capability is needed. |
832
+ | 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. |
833
+ | 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 or failed read, with the real recovery action. Empty state belongs outside the semantic table body. |
761
834
  | Rich choices | `FieldSet`/`FieldLegend`, then `FieldLabel` containing `Field`, its content and a radio or checkbox. | Known options, selection model, labels, descriptions, validation and disabled reasons. The tile label activates its control; a nested link cannot steal the choice. |
762
835
  | Split action | `ButtonGroup` with the default `Button` and a `DropdownMenu` trigger. | One action family, default action, alternatives and shared pending/disabled state. Label the group and menu trigger. Preserve one seam and include the default in the menu. |
763
836
  | Copyable identifier or command | Mono text or read-only `InputGroupInput` plus `CopyButton`; put the optional shell prompt outside the copied value. | Exact source value, visible representation and translated `copyLabel`, `copiedLabel`, `copyFailedLabel`. Preserve the full source when the displayed representation is shortened. |
764
837
  | Plain code or static JSON | `CodeBlock` owns the read-only InputGroup composition and CopyButton. | Exact `code`, accessible `label`, optional filename/language and copy labels. Serialize static JSON in the adapter; use FileDiff for an actual before/after comparison. |
765
- | Relative timestamp preview | Semantic `time` with `HoverCard`; keep the exact timestamp available to assistive technology. | Machine-readable timestamp, formatted relative and absolute text, timezone, and the product's refresh policy. Portal keeps its existing locale formatter. |
838
+ | 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. |
766
839
  | 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. |
767
840
  | 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. |
768
841
  | 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. |
769
842
 
770
- Use `CopyButton` and `CodeBlock` for their complete compositions. Relative-time previews, feedback and expandable file navigation combine existing parts with product adapters. Enhanced code highlighting 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.
843
+ Use `CopyButton` and `CodeBlock` for their complete compositions. Feedback and expandable file navigation combine existing parts with product adapters. Enhanced code highlighting 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.
771
844
 
772
845
  ### Personalization boundaries
773
846
 
@@ -777,7 +850,7 @@ Keep Geist's hierarchy, control metrics, typography roles, neutral surface zones
777
850
 
778
851
  - **Buttons.** Every button is `{spacing.control-height}` high with a `{typography.body-sm}` label at 500 and `{rounded.sm}` corners ("Layout" says where the smaller sizes may go). `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.
779
852
  - **Dialog footers.** A dialog's actions sit at the footer's trailing edge, the primary last. A choice to stop being asked is a checkbox at the footer's leading edge, remembered by whichever action is taken, never a third button.
780
- - **Tabs** switch between sibling views of one object. Use `Tabs` with `TabsList`, `TabsTrigger` and `TabsContent` for panels; use `TabsNav` and `TabsLink` for URLs. Both page sections and local panels may use the shared `line` treatment for an underline or the default segmented treatment. Choose by the surrounding composition: existing shells retain their segmented tabs, and the inbox retains its local line tabs. Select the treatment through the component API, never a page border or colour override. Keep one treatment among siblings, label the group, and retain keyboard navigation and compact overflow. A choice that changes a value rather than a view uses radio or toggle semantics instead.
853
+ - **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. A set of different things, such as Nadia's company sections, is pages in the sidebar, never tabs. Every tab is 36 px high with 12 px sides, `{typography.body-sm}` at 500 and 4 px between tabs; label the group and keep keyboard navigation. A choice that changes a value rather than a view uses radio or toggle semantics instead.
781
854
  - **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.
782
855
  - **Fields** keep the `{colors.input}` edge, `{rounded.sm}`, no shadow and at least `{spacing.control-height}`. Help stays with its field; in configuration sections, the label and help sit above the control, and validation stays with it.
783
856
  - **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.
@@ -785,37 +858,52 @@ Keep Geist's hierarchy, control metrics, typography roles, neutral surface zones
785
858
 
786
859
  ### Cards and sections
787
860
 
788
- `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. 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 each card saves its own fields with its own Save in its footer, always visible and disabled until something in that card changes; nothing on a settings page applies on change. Only the preferences in the account menu, Theme and Language, apply at once. 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, such as the plan a feature needs or a link to learn more, 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.
861
+ `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. 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 each card saves its own 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; once saved, "Saved" with the ok check in the footer's left until the next change; a failure keeps every value and shows under the field it is about. An object's section that edits in place swaps to the same section with Cancel before Save. Nothing on a settings page applies on change. Only the preferences in the account menu, Theme and Language, apply at once. 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, such as the plan a feature needs or a link to learn more, 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.
789
862
 
790
863
  ### Menus and commands
791
864
 
792
- 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`, with destructive commands after a separator. A menu is not required when the row has no secondary mutations. Disabled commands retain an explanation. The three-dot trigger is a `{spacing.control-height}` ghost icon button. Menu rows put the label first and a decorative icon at the trailing edge; a destructive command never becomes the loudest repeated action.
865
+ 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 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 disabled command stays in place with its reason under its label. A destructive command never becomes the loudest repeated action: it is never a button on every row. Ruled by the owner on 14 October 2026.
793
866
 
794
- Choose the surface by where the thing being changed is shown and how big the job is. Each surface has one owner:
867
+ 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:
795
868
 
796
869
  | The person wants to | Surface | Owner | Example |
797
870
  | --- | --- | --- | --- |
798
- | Change a record shown in a list | Its row opens into the form, with Cancel and Save in the form's footer; the other rows stay where they are | `CollectionPage`'s `editor` with `RowEditForm` | Vercel Domains › Edit; a connected channel's Settings |
799
- | 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`; `SettingsSection` on a settings page | An Agent's name and description |
800
- | Add a record through a few fields, or change one or two values from a command | A centred dialog | `FormDialog`; the Portal's `ActionDialogForm` | Add password, Add MCP server |
801
- | Choose from a catalog, or set up in several steps with a wait | A drawer at the right edge, the page still behind it | `FormDrawer` with `job="catalog" \| "steps"` | Vercel Domains › Add; Add skill from the Library; Telegram's QR setup |
871
+ | 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 |
872
+ | 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 |
873
+ | 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 |
874
+ | Choose from a catalogue | A drawer at the right edge, the page still behind it: the search, then the entries, each with its Add | `CatalogDrawer` | Add a skill, in both products; Vercel Domains › Add |
875
+ | Set up in several steps with a wait | The same drawer, 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 |
876
+ | Look at one record from a list, then go back to the list | A sheet from the right, its title naming the record, its facts and its payload; Close gives focus back to the row | `RecordSheet` | One Stripe event in Billing |
802
877
  | Do a workflow a person could link to | Its own page | The page's composition | Vercel Add New › Project; New Agent |
803
878
  | Confirm a consequence | A confirmation | `ConfirmDialog` | Remove, Disconnect |
804
879
 
805
- A record already on screen is never edited in a dialog or a drawer, and a drawer is never chosen because an action takes input: `FormDrawer` requires a `job`, `"catalog"` or `"steps"`, and owns its one width, 560 px (the full width on a compact screen), which no caller sets. A settings page is the Configuration composition. These choices follow the live Vercel Domains, Projects and settings pages and the [Geist Modal](https://vercel.com/geist/modal) and [Fieldset](https://vercel.com/geist/fieldset) patterns.
880
+ A record already on screen is never edited in a dialog or a drawer, and a drawer is never chosen because an action takes input: a catalogue is `CatalogDrawer`, a setup in steps is `FormDrawer` with `job="steps"`, and the drawer owns its one width, 560 px (the full width on a compact screen), which no caller sets. 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) and [Fieldset](https://vercel.com/geist/fieldset) patterns.
806
881
 
807
882
  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.
808
883
 
809
884
 
810
885
  ### Tables and missing values
811
886
 
812
- A table is native table structure: 40 px rows for comparable facts and 48 px rows for entity collections. Visible headings use 14 px / 20 px at weight 500 in a 36 px row; numeric columns align to the trailing edge with tabular figures. A collection row is one line: the record's identity and at most about five facts, and anything more lives on the record's page. Column headings are visible by default and remain visible when people compare or sort values. A compact entity collection whose cells identify themselves may keep accessible headings visually hidden with `showColumnHeaders={false}`; choose this exception deliberately. `CollectionPage` (`@nadicodeai/ui/components/collection-page`) is the entity collection whole, and it makes that exception by drawing rows that identify themselves: the record's name with one muted line saying what it is or where it comes from (`RecordIdentity`'s `detail`, such as "Company skill · every Agent has it"); a status only when it tells the row apart from the others; a time in its verb's sentence (`RecordTime`: "Updated 3 hours ago", the date after a week, the exact moment in its title); one command that is not destructive as a button and otherwise the row's menu; an action that failed or was refused as its catalogue message inside its own row, with its next step, never a notice under the row; and an edit open inside its row. A screen gives it the records and what each one says, never its own toolbar, table, rows or menus. A simple descriptive list uses `ItemGroup` and `Item` instead of table semantics. 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: identity and actions keep their columns, and supporting facts move into a detail row inside the same record before the table gets cramped. The identity column claims up to a third of the table, gives supporting columns more room in a wide comparison, gives it back down to a floor, and holds its name on one line: a long name ends in an ellipsis and is whole in its title, as a dashboard row does. A fact holds one line at its own width; a status sentence in a figure column wraps at word boundaries while the figure keeps its numeric alignment. A column of words, such as a summary or a sentence, holds one line up to a cap of `16rem`, shrinks to a floor of its own in a narrow table, and past the cap 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 may break at any character. 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 in the 28 px tile.
887
+ A table is native table structure: 40 px rows for comparable facts, with visible headings at 14 px / 20 px and weight 500 in a 36 px row; numeric columns align to the trailing edge with tabular figures, and the headings stay visible because people compare and sort by them.
888
+
889
+ 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 56 px row whose slots stand left to right at fixed measures, so a person reads every list the same way:
890
+
891
+ | Slot | Measure | Holds |
892
+ | --- | --- | --- |
893
+ | Identity | At most a third of the row | The record's 32 px identity mark ("Identity marks"), 12 px, then its name on one line over one muted line saying what it is or where it comes from (`RecordIdentity`'s `detail`: "Company skill · every Agent has it") |
894
+ | Facts | The rest | A few facts on one line, truncating, never wrapping |
895
+ | Status | 150 px | Only the exception, the status that tells this row apart from the others; empty for the usual record |
896
+ | Time | 92 px, tabular | The time in its verb's sentence (`RecordTime`: "Updated 3 hours ago", the date after a week, the exact moment on hover) |
897
+ | The row's command | At least 112 px | One command that is not destructive, as an `outline` button |
898
+ | The three dots | 36 px | The row's `ActionMenu`, last; or a muted chevron when the row only opens |
899
+
900
+ 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 simple descriptive list uses `ItemGroup` and `Item` instead of table semantics. 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: identity and actions keep their columns, and supporting facts move into a detail row inside the same record before the table gets cramped. The identity column claims up to a third of the table, gives supporting columns more room in a wide comparison, gives it back down to a floor, and holds its name on one line: a long name ends in an ellipsis and is whole in its title, as a dashboard row does. A fact holds one line at its own width; a status sentence in a figure column wraps at word boundaries while the figure keeps its numeric alignment. A column of words, such as a summary or a sentence, holds one line up to a cap of `16rem`, shrinks to a floor of its own in a narrow table, and past the cap 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 may break at any character. 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.
813
901
 
814
902
  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.
815
903
 
816
904
  ### Status
817
905
 
818
- A status is one Badge: a mark and a word in one of five tones, so it reads without colour.
906
+ A status is one `StatusBadge`: a mark and a word in one of five tones, so it reads without colour.
819
907
 
820
908
  | Tone | Means | Pair |
821
909
  | --- | --- | --- |
@@ -827,6 +915,20 @@ A status is one Badge: a mark and a word in one of five tones, so it reads witho
827
915
 
828
916
  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.
829
917
 
918
+ 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").
919
+
920
+ ### Activity
921
+
922
+ 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.
923
+
924
+ - **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.
925
+ - **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.
926
+ - **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.
927
+ - **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.
928
+ - **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".
929
+
930
+ 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.
931
+
830
932
  ### System activity
831
933
 
832
934
  Who does the work decides how it looks. An Agent's work is that Agent's orb in its state and a sentence saying what it does. The system's own work never takes an orb; inside a conversation there is no system's work, only hers and the person's ("Agent orbs"):
@@ -835,7 +937,7 @@ Who does the work decides how it looks. An Agent's work is that Agent's orb in i
835
937
  | --- | --- |
836
938
  | Under about a second | The control's own label changes to its pending word; its place, size and icon stay. |
837
939
  | Past a second | One 2 px waiting line of ink beside the work, a 32% segment crossing it on `wait`, and nothing else moves. |
838
- | Measurable | A progress line with a written count. Never an invented percentage. |
940
+ | Measurable | A progress line with a written count, "Step 2 of 3" or "12 of 40 MB". Never a percentage. |
839
941
  | A page load | Still skeleton placeholders in the shape of the content, never pulsing and never paired with a waiting line. |
840
942
 
841
943
  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.
@@ -846,7 +948,7 @@ A page that cannot show what was asked for opens with a picture, the way a Porta
846
948
 
847
949
  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.
848
950
 
849
- Two registers share that composition:
951
+ The not-found page and the fault page are one composition, in that order: the picture with its label, the eyebrow, the title, one sentence, then the recovery first. Only a fault carries its code, and no orb turns on either. Two registers share it:
850
952
 
851
953
  - **An absence** is an address or a record that is not there. Its picture and label are the joke of the missing address. Nothing went wrong, so nothing takes a feedback role, no glyph stands in for the failure and no code is printed.
852
954
  - **A fault** is a request the product stopped. Its picture and label are calm and never a joke about lost work. Below the actions stands the evidence: the diagnostic code, the word that names it and one line saying what to do with it. The error pair marks the eyebrow.
@@ -860,6 +962,7 @@ An Agent is its orb, in every product; there is no other Agent picture. An orb i
860
962
  - **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.
861
963
  - **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`.
862
964
  - **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.
965
+ - **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.
863
966
  - **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.
864
967
  - **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.
865
968
  - **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. 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.
@@ -876,7 +979,19 @@ The sign-up page draws the account being made as the company's card: one object
876
979
 
877
980
  ### Identity marks
878
981
 
879
- A recognised app, platform or model maker is drawn with its genuine mark beside its readable name, in a tile that owns the box; an unknown one keeps its name and a text monogram, never a guessed logo. Where a mark names a row, the tile is 28 px for an app or a channel, in a collection's 48 px row, and 24 px for a model's maker, 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 Library skill uses the purple `CatalogTile` tone and a plugin uses its blue `info` tone, each with the catalog's line icon, at 40 px in a row and 64 px at the head of its page, so the tile's ground says which kind it is; the icon is one of the catalog'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.
982
+ 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.
983
+
984
+ | Kind | The mark |
985
+ | --- | --- |
986
+ | 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") |
987
+ | An app | Its own logo in the tile |
988
+ | A company | Its two initials in a ringed tile on the card ground |
989
+ | A person | Their picture, or their initials, round |
990
+ | 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 |
991
+
992
+ 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.
993
+
994
+ 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.
880
995
 
881
996
  ### Product views
882
997
 
@@ -900,6 +1015,20 @@ Every product draws Lucide's shapes, unedited, in the house render: a 1.5 px lin
900
1015
 
901
1016
  Three icons are the house's own, drawn on Lucide's grid in the same render: `arch` for the company and its doors, `approvals`, the arch with a check, for work waiting on a person, and `agent`, the orb seen face-on with the Agent as the dot on its path. They name those three things wherever an icon does, and no Lucide glyph stands in for them. An icon beside text is decorative; an icon-only control carries an accessible name.
902
1017
 
1018
+ An icon belongs to a control or a menu row, never to a title. A card's, a section's or a label's title and a KPI's label carry no icon (`no-decorative-icon`): an icon there repeats the words beside it and competes with the marks that matter. A menu row or a button takes one icon, leading its label. A mark goes where it says which thing you are looking at; a picture marks a moment. Where each goes:
1019
+
1020
+ | Place | Identity mark | Icon | Picture |
1021
+ | --- | --- | --- | --- |
1022
+ | A record's row | Yes, 32 px, first | Its kind's icon, as its mark, when it has no mark of its own | No |
1023
+ | An object's header | Yes, 64 px | No | No |
1024
+ | A card's or a section's title | Only when the card is one external system | No | No |
1025
+ | A KPI card | No | Only the trend in the change badge | No |
1026
+ | A first-use empty state | No | No | Yes, the section's picture, 240 × 176 on a page |
1027
+ | Nothing yet, no results | No | No | No |
1028
+ | A failed read | No | The warning mark | No |
1029
+ | A menu row, a button | No | One, leading | No |
1030
+ | Entry, a failure page, Downloads, a first run | The Agent's orb where she is the subject | No | Yes, one, whole |
1031
+
903
1032
  A benefit of nadicode, and each piece of office work Nadia arrives ready for, is drawn on the website by its object: one transparent object of the art direction, something old from Italy with one piece of today in it, in two identity hues, in the family of the Portal's email pictures (the key ring with its security key). The objects are a library (`brand/visual-design/images/website-objects/`, briefed in `brand/refs/briefs/website-benefits.json`), and the same object stands for the same benefit wherever the brand names it: the website, the docs, a deck, a post. On a page they stand at one size, above their title, and never move. Lucide glyphs stay the products' own icons and never stand for a benefit on the website.
904
1033
 
905
1034
  ## Brand media authority
@@ -929,20 +1058,23 @@ The clear space is one stroke width of the rendered logo on every side. The mini
929
1058
  - **Portal.** A product shell on the workspace ground, built from the page compositions and the package components, with `globals.css` giving every elevated surface its tone.
930
1059
  - **Nadia.** Nadia draws her own window through Hermes's `window.shell` area, inside Hermes's app, which owns Tailwind and its own surface layering. Nadia's theme paints Hermes's roles with the shared `semantic` roles and core radii, and imports `@nadicodeai/ui/components.css` with the design system's host stylesheet, so the package components render with the same recipes and glass. The window follows the operating system's material where the system composites it. Hermes answers reduced motion by cutting every transition and animation; Nadia's stylesheet gives her own parts, the package overlays, the entry screen and the waiting line, the reduced-motion policy in "Motion" back.
931
1060
  Her conversation is one centred column of `56rem` holding her replies, the person's messages and the chat bar: her reply is plain text across the column, only the person's message is a bubble, grey (`{colors.canvas-soft-2}`) at the `raised` height in both modes, at the column's trailing edge, and the chat bar floats over the messages as the lightest step, in glass. The selected Agent row carries the sidebar's selected fill, as the Agents list draws it.
932
- - **Alveare.** The desktop keeps Buzz's components and layout, styled through an adapter as Nadia styles Hermes: Buzz's colour roles read the shared `semantic` roles, every text takes a type role, and Buzz's shared components take the package's geometry, states, focus and motion, over the design system's host stylesheet and `@nadicodeai/ui/components.css`.
1061
+ - **Alveare.** The desktop keeps Buzz's components and layout, styled through an adapter as Nadia styles Hermes: Buzz's colour roles read the shared `semantic` roles, tokens only and never a copied value, every text takes a type role, and Buzz's shared components take the package's geometry, states, focus and motion, over the design system's host stylesheet and `@nadicodeai/ui/components.css`. For the four jobs the products share, Alveare mounts the package's own component in place of Buzz's: every empty state is `EmptyState`, the account menu is `AccountMenu`, a status is `StatusBadge`, and the three dots are `ActionMenu`. Agents appear as their orbs, and a person never reads "Buzz". Ruled by the owner on 14 October 2026.
933
1062
 
934
1063
  ## Do's and Don'ts
935
1064
 
936
- - Do keep the brand still: only Agents, the product at work and the website's shader objects move. Don't animate sections, pictures or scrolling, apart from the bounded homepage figure response in Motion.
1065
+ - Do keep the brand still: only Agents, the product at work and the website's shader objects move. Don't animate sections, pictures or scrolling, apart from the bounded homepage figure response and a first use's one arrival in Motion.
937
1066
  - Do use the identity hues at full strength in the places "Colour as stage" lists, with the text pole that clears AA. Don't tint them, grade them, or paint a control, chrome or a status with them.
938
1067
  - Do choose a surface's height by its physical role, and let its edge and faint shadow show it in both modes. Don't add a border, ring or second shadow to an elevated surface.
939
1068
  - Do put glass on floating chrome over content that scrolls or moves under it. Don't make content glass, or put glass over a plain ground.
940
1069
  - Do show authentic captures or real-component scenes as "Product views on the website" specifies, with provenance recorded in the repository. Don't use device frames or invented interfaces.
941
1070
  - Do give each thing the corner its role takes on the scale ("Shapes"). Don't give a higher surface a bigger corner.
942
1071
  - Do give every line one owner, and draw focus only with the focus outline.
943
- - Do say a status with a Badge tone, a mark and a word. Don't use colour alone or a bare dot.
1072
+ - Do say a status with a `StatusBadge` tone, a mark and a word. Don't use colour alone or a bare dot.
944
1073
  - 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.
945
1074
  - Do use the dotted field only on a canvas where things are arranged. Don't put it behind lists, dashboards, settings or website sections.
1075
+ - 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.
1076
+ - 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.
1077
+ - 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.
946
1078
  - Do add a rule here before a product needs it. Don't restate a rule in another document; link to it.
947
1079
 
948
1080
  ## Token extension
@@ -1944,6 +2076,12 @@ The YAML front matter holds the four groups the DESIGN.md format defines: colors
1944
2076
  "unit": "ms"
1945
2077
  }
1946
2078
  },
2079
+ "arrive": {
2080
+ "$value": {
2081
+ "value": 300,
2082
+ "unit": "ms"
2083
+ }
2084
+ },
1947
2085
  "drawer-swipe": {
1948
2086
  "$value": {
1949
2087
  "value": 400,