@nadicodeai/design-system 12.1.196 → 12.1.198

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/AGENTS.md CHANGED
@@ -7,12 +7,15 @@ the usage page in its source; `examples/` contains rendered specimens.
7
7
 
8
8
  `DESIGN.md` is nadicode's visual identity in
9
9
  [Google's DESIGN.md format](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md):
10
- the four token groups in its front matter, and prose saying how those values
11
- look applied, in the format's sections (Overview, Colors, Typography, Layout,
10
+ four of the format's five token groups in its front matter (colors, typography,
11
+ rounded, spacing), and prose saying how those values look applied, in the
12
+ format's sections (Overview, Colors, Typography, Layout,
12
13
  Elevation & Depth, Shapes, Components, Do's and Don'ts) and the ones it adds for
13
14
  the same purpose (Data Visualization, Motion, Iconography, Brand media
14
15
  authority, Registers). Its Components section says how components look: the
15
- tokens, materials and states their appearance takes.
16
+ tokens, materials and states their appearance takes. The format's fifth group,
17
+ `components`, stays empty: each component's look is drawn by its code in
18
+ `@nadicodeai/ui` from these tokens, and a second copy in YAML would drift.
16
19
 
17
20
  It never says how a component, a composition or a page is built, filled or
18
21
  placed, such as what a card's header holds, which composition a page renders or
@@ -31,10 +34,7 @@ rule goes to its owner:
31
34
  - A product's pages and scope: that app's docs.
32
35
 
33
36
  Edit `DESIGN.md` only when the look changes: a token, a material, the type
34
- scale, a colour's meaning, a radius, a motion value. Its sections that still
35
- hold rules of the second kind move to their owners in
36
- [#5187](https://github.com/nadicodeai/nadicodeai/issues/5187); edit them only to
37
- move a rule out.
37
+ scale, a colour's meaning, a radius, a motion value.
38
38
 
39
39
  ## Working here
40
40
 
package/DESIGN.md CHANGED
@@ -524,7 +524,7 @@ spacing:
524
524
 
525
525
  ## Overview
526
526
 
527
- This file is nadicode's visual identity: the tokens above are the values, and the sections below say how they look applied. Every component in `@nadicodeai/ui`, every website section, and every screen in the Portal, Nadia and Alveare builds on it. What a component, a composition or a page does, takes or holds is never written here: [`AGENTS.md`](AGENTS.md#what-goes-in-designmd) says what this file holds and where every other design rule goes.
527
+ This file is nadicode's visual identity: the tokens above are the values, and the sections below say how they look applied ([what it holds](AGENTS.md#what-goes-in-designmd)). Every component in `@nadicodeai/ui`, every website section, and every screen in the Portal, Nadia and Alveare builds on it. How a component, a composition or a page is built is never written here: what a component does, takes and holds is its usage page, in its source in `@nadicodeai/ui`, and [the product-design skill](../../skills/design/product-design/SKILL.md#where-each-decision-lives) says where every other design decision lives.
528
528
 
529
529
  nadicode puts AI Agents to work inside small and mid-sized companies. Its sites and products read as a technology company with a real product: the interface is neutral and exact, the product is shown working, and colour and depth give the page its energy. Three ideas carry every screen:
530
530
 
@@ -532,13 +532,7 @@ nadicode puts AI Agents to work inside small and mid-sized companies. Its sites
532
532
  - **Colour as stage.** The five identity hues appear at full strength only in the places "Colour as stage" lists, the colour of Agents among them, and never as a section's ground. They carry the brand; they never decorate a control. Semantic colour identifies statuses, categories and named actions through the component palette below.
533
533
  - **Real depth.** Surfaces sit at four heights. Content lies on the page with a 1 px edge and no grey fill; what floats takes its depth from light: deeper shadows in light, and in dark a line of light along its top, a brighter edge and a deep black shadow. Glass goes only where something moves under it.
534
534
 
535
- 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").
536
-
537
- The products are assembled in three layers, each with one owner and one check, so the same job looks the same on every page:
538
-
539
- - **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").
540
- - **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").
541
- - **Checks.** Three lint rules fail the build in the Portal, Nadia's window and Alveare HQ: a page renders a composition (`page-composition`), an absence is only `EmptyState` (`empty-state-owner`), and a title or a label carries no icon (`no-decorative-icon`).
535
+ The products are still: only Agents move on their own, apart from the company's card, which turns in the light as it appears and as it is issued. The website moves with purpose: the product at work, Agents and its few shader objects ("Motion").
542
536
 
543
537
  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`.
544
538
 
@@ -558,9 +552,9 @@ The neutral spine carries grounds, chrome, text and every control: `{colors.ink}
558
552
 
559
553
  ### Colour as stage
560
554
 
561
- The identity palette is the brand's colour: cobalto `{colors.identity-cobalto}`, verde `{colors.identity-verde}`, arancio `{colors.identity-arancio}`, giallo `{colors.identity-giallo}` and rosso `{colors.identity-rosso}`, with `{colors.identity-ink}` and `{colors.identity-white}` as the poles type sits on. Azzurro `{colors.identity-azzurro}` has three jobs: the first stripe of Nadia's ribbon, the last cell of Alveare's mark ("Brand media authority"), and a band of a company's card foil ("Company card").
555
+ The identity palette is the brand's colour: cobalto `{colors.identity-cobalto}`, verde `{colors.identity-verde}`, arancio `{colors.identity-arancio}`, giallo `{colors.identity-giallo}` and rosso `{colors.identity-rosso}`, with `{colors.identity-ink}` and `{colors.identity-white}` as the poles type sits on. Azzurro `{colors.identity-azzurro}` has three jobs: the first stripe of Nadia's ribbon, the last cell of Alveare's mark and the last band of Alveare Server's ("Brand media authority"), and a band of a company's card foil (`CompanyCard`).
562
556
 
563
- The hues appear at full strength and flat, with no tints, steps, gradients or hover variants, in these places. The one exception is the website's shader objects, each the material of one object and never a section's ground: the threads she holds, a fan of lines in her stripes from her orb to her colleagues, on black; Alveare's hive, cells that light in its hues as the work passes; and the company card's holographic foil ("Company card"). Stripes and cells stay pure bands of their hues, never a colour between two of them. Ruled by the founder: "shaders are not backgrounds all over the place".
557
+ The hues appear at full strength and flat, with no tints, steps, gradients or hover variants, in these places. The one exception is the website's shader objects, each the material of one object and never a section's ground: the threads she holds, a fan of lines in her stripes from her orb to her colleagues, on black; Alveare's hive, cells that light in its hues as the work passes; and the company card's holographic foil (`CompanyCard`). Stripes and cells stay pure bands of their hues, never a colour between two of them. Ruled by the founder: "shaders are not backgrounds all over the place".
564
558
 
565
559
  - **Section grounds on the website.** A section's ground is white, soft or black, or a band in cobalto or giallo at full strength where the page gives one section its moment, its type in the hue's pole (ruled by the founder: "how stripe and others introduce the solid filled sections, like in brand colours. I want that visual style"). Two neighbouring sections never share a ground and two bands never touch; otherwise colour lives in the pictures, the objects, the Agents and the shader objects.
566
560
  - **Glass.** The colour a glass surface shows through ("Elevation & Depth").
@@ -568,21 +562,21 @@ The hues appear at full strength and flat, with no tints, steps, gradients or ho
568
562
  - **Agents.** The orbs ("Agent orbs").
569
563
  - **Alveare's channels.** Each channel's mark sits on a tile of one hue, in the Mac's sidebar and the iPhone's channel list; the channel's id picks the hue, so a channel is the same colour on every device, and a direct conversation shows its people instead. On the iPhone the person's own messages are cobalto bubbles with white text. Ruled by the owner.
570
564
  - **Objects.** The saturated pieces of the art direction's objects, one per benefit ("Iconography").
571
- - **The logo.** The dot of the wordmark, the Portal's name after it in verde, the icon tile and Alveare's mark ("Brand media authority"), and the compact company identity marks ("Identity marks").
565
+ - **The logo.** The dot of the wordmark, the Portal's name after it in verde, the icon tile and Alveare's and Alveare Server's marks ("Brand media authority"), and the compact company identity marks ("Identity marks").
572
566
  - **Imagery.** Art direction reads these tokens and keeps no private palette.
573
567
 
574
- Text on a colour ground is the pole that clears AA on that hue: `{colors.identity-white}` on cobalto and rosso; `{colors.identity-ink}` on verde, arancio and giallo. A control on a colour ground uses the button's on-colour variants ("Components"). Product chrome in the Portal and Nadia stays neutral: grounds, text, lines and navigation, a selected row included, whatever it opens. Colour inside that structure is the identity palette at full strength in both themes: the primary control and the on switch in cobalto, feedback and state in cobalto, verde, giallo and rosso with their pole for text, and links in cobalto, the dark theme's links and coloured text one light step of the same hue. It also identifies Agents, providers and categories; an Agent's own colour is her orb, so her row in a list never takes a hue. Choose a typed component tone from the semantic palette; never paint a control with an arbitrary identity hex.
568
+ Text on a colour ground is the pole that clears AA on that hue: `{colors.identity-white}` on cobalto and rosso; `{colors.identity-ink}` on verde, arancio and giallo. A control on a colour ground uses the button's on-colour variants ([`Button`](../ui/src/components/button.tsx)). Product chrome in the Portal and Nadia stays neutral: grounds, text, lines and navigation, a selected row included, whatever it opens. Colour inside that structure is the identity palette at full strength in both themes: the primary control and the on switch in cobalto, feedback and state in cobalto, verde, giallo and rosso with their pole for text, and links in cobalto, the dark theme's links and coloured text one light step of the same hue. It also identifies Agents, providers and categories; an Agent's own colour is her orb, so her row in a list never takes a hue. Choose a typed component tone from the semantic palette; never paint a control with an arbitrary identity hex.
575
569
 
576
570
  ### Meaning
577
571
 
578
572
  - **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}`.
579
573
  - **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.
580
574
  - **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 is `StatusBadge`'s, in the foreground colour; never put a raw meter-fill token on text.
581
- - **Status** is always a `StatusBadge` tone with its shape and a word ("Components").
575
+ - **Status** is always a `StatusBadge` tone with its shape and a word ("Status").
582
576
 
583
577
  ### Category colour
584
578
 
585
- Labels and their icons carry the palette's eight hues to distinguish named categories, as Vercel's labels do: blue, teal, purple, pink, green, orange, yellow and red, each the hue's 200 step as the ground and its 1000 step as the ink in both themes (`category-<tone>` and `category-<tone>-foreground`). A category label (`CategoryTag`) is vivid instead: the hue itself, its 700 step (cobalto, acqua, viola, rosa, verde, arancio, giallo, rosso), as the ground in both themes, and its pole that clears AA as the ink, identity-white on cobalto, viola and rosso and identity-ink on the others (`label-<tone>` and `label-<tone>-foreground`). `Tag` owns these 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: a status is `StatusBadge`'s shape and word, never a pill, so a green or red label reads as a category and never as done or failed. 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.
579
+ Labels and their icons carry the palette's eight hues to distinguish named categories, as Vercel's labels do: blue, teal, purple, pink, green, orange, yellow and red, each the hue's 200 step as the ground and its 1000 step as the ink in both themes (`category-<tone>` and `category-<tone>-foreground`). A category label (`CategoryTag`) is vivid instead: the hue itself, its 700 step (cobalto, acqua, viola, rosa, verde, arancio, giallo, rosso), as the ground in both themes, and its pole that clears AA as the ink, identity-white on cobalto, viola and rosso and identity-ink on the others (`label-<tone>` and `label-<tone>-foreground`). The blue category never uses the status roles `info` or `info-soft`. The same category keeps the same tone across a surface and after sorting. A word remains visible, and a colour never invents status: a status is `StatusBadge`'s shape and word, never a pill, so a green or red label reads as a category and never as done or failed. 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.
586
580
 
587
581
  ### Scales
588
582
 
@@ -609,17 +603,16 @@ The shared colour roles are authored once, in the token extension's `semantic` g
609
603
 
610
604
  ### Chart source
611
605
 
612
- Every chart and KPI card is a stock shadcn registry component owned by `@nadicodeai/ui`; a page composes them and never assembles a chart card or reaches into a chart's internals. The package keeps a short, tested list of deliberate deviations from the registry. The visual ones are rules:
606
+ Every chart and KPI card is drawn as the stock shadcn registry draws it, with a short, tested list of deliberate deviations; the visual ones are these:
613
607
 
614
608
  - The tooltip prints the exact figure in the surface's units; the axis prints a scale (`1.6B`, `800M`).
615
609
  - The value axis is always drawn, with five ticks on a 1, 2, 2.5, 5 or 10 step.
616
610
  - Bars are capped in width, the grid is dashed in both directions, and plot heights are fixed.
617
611
  - A multi-series chart has a legend below the plot, left-aligned and wrapping: a round dot and the entity name, in series order. A usage summary instead identifies its series through the adjacent ranking's identity labels and coloured share bars, without dots.
618
612
  - 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.
619
- - A chart is a named section: its card title is the heading that names the plot.
620
- - Every chart card has one header and footer: `ChartCard`, whose usage page holds them. A usage summary keeps its period and metric selectors beside its title; other period choices sit on the page's title row where the page has one. A daily axis labels every second day from the first, fewer in a half-width card. `ChartRanking` owns a usage summary's ranking and complete-list disclosure; other compact rankings remain one line per entity. A category axis is as wide as its longest name up to 128 px, a longer name wrapping to a second line where its row holds two lines and cut with an ellipsis where it does not; a short window's activity calendar draws larger cells and shares its row with a sibling card, never a lone narrow card.
613
+ - A daily axis labels every second day from the first, fewer in a half-width card. A category axis is as wide as its longest name up to 128 px, a longer name wrapping to a second line where its row holds two lines and cut with an ellipsis where it does not.
621
614
 
622
- No pie, donut, radar or dual-axis chart, and no mark or logo inside a legend. The one ring is `Gauge`, a control for a single ratio out of 100 against a fixed maximum, not a chart.
615
+ No pie, donut, radar or dual-axis chart, and no mark or logo inside a legend. The one ring is `Gauge`, which is not a chart.
623
616
 
624
617
  ### Palette and assignment
625
618
 
@@ -637,9 +630,8 @@ A hue follows its entity, never its rank or position: one assignment per surface
637
630
 
638
631
  ### Meters, deltas and labels
639
632
 
640
- - **Meter** and **Gauge**: `Progress` for consumption against a cap and `Gauge` for one ratio out of 100 where the comparison is the point; their usage pages hold their tones, stops, overage and figure. Neither has an indeterminate form: an unknown wait is the waiting line.
641
- - **Delta**: an outline Badge with a trend icon (`TrendingUp`, `TrendingDown`, `Minus`) and the signed value, followed by a muted "vs {period}". Direction crossed with whether up is good sets the tone; a neutral direction stays neutral. Never bare signed text, colour alone, or a ▲▼ glyph. A figure with no comparison shows no badge.
642
- - **Labels**: legend, axis and label text are ink roles and never wear the series colour. A single series is titled, not legended. Tooltips enhance a chart and never hold the only copy of a value; where a page needs the numbers as text, it shows a real table built from the same data.
633
+ - **Delta**: an outline Badge with a trend icon (`TrendingUp`, `TrendingDown`, `Minus`) and the signed value, followed by a muted "vs {period}". Direction crossed with whether up is good sets the tone; a neutral direction stays neutral. Never bare signed text, colour alone, or a ▲▼ glyph. `KpiCard`'s usage page holds where the badge stands.
634
+ - **Labels**: legend, axis and label text are ink roles and never wear the series colour. Tooltips enhance a chart and never hold the only copy of a value.
643
635
 
644
636
  ## Typography
645
637
 
@@ -681,7 +673,7 @@ A token has one size at every width; nothing is sized in `vw` or `vh`. A role in
681
673
 
682
674
  ### The website's text
683
675
 
684
- The website keeps one token per role on every page: a hero's title `{typography.display-lg}` (`{typography.display-md}` under 640 px), a section's title `{typography.display-md}` (`{typography.heading-lg}` under 768 px), a lead `{typography.body-lg}` at a measure of 60 characters under a section's title, 52 in the closing band, whose row it shares with the band's object, and `40rem` in a hero, a card's or a tour stop's title `{typography.heading-md}`, an item's title `{typography.heading-sm}` and its words `{typography.body-md}`, and a product view's caption `{typography.label-mono}`. A label over a title, naming what the section or the card is, is `{typography.label-mono}` in capitals and the ground's text colour, never a hue. A section's words start at the column's edge; only a page's hero and its showcase centre them. A page's calls to action are the large button, in its hero and its closing band; every other way onward is a link with an arrow.
676
+ The website keeps one token per role on every page: a hero's title `{typography.display-lg}` (`{typography.display-md}` under 640 px), a section's title `{typography.display-md}` (`{typography.heading-lg}` under 768 px), a lead `{typography.body-lg}` at a measure of 60 characters under a section's title, 52 in the closing band, whose row it shares with the band's object, and `40rem` in a hero, a card's or a tour stop's title `{typography.heading-md}`, an item's title `{typography.heading-sm}` and its words `{typography.body-md}`, and a product view's caption `{typography.label-mono}`. A label over a title, naming what the section or the card is, is `{typography.label-mono}` in capitals and the ground's text colour, never a hue. A section's words start at the column's edge; only a page's hero and its showcase centre them. A call to action is the large button; every other way onward is a link with an arrow.
685
677
 
686
678
  ### Entry screens
687
679
 
@@ -692,46 +684,8 @@ instruction never becomes a heading merely because the screen has little copy.
692
684
  Actions and stage lists use `{typography.body-sm}`, and progress counts use
693
685
  `{typography.label-mono}`. These roles retain their token sizes at every width.
694
686
 
695
- An entry page is a whole page with one job and no product chrome: no sidebar,
696
- no breadcrumb, no footer. Each product has one entry frame, and every entry
697
- page renders it: the Portal's `PortalFunnelFrame` with `AccountEntryHeader`
698
- (sign-in, sign-up, an invitation, and the pages that hand a person to Nadia,
699
- which stand on the dark plane with her ambient orb at 96 px over the card),
700
- Nadia's `EntryScene` (her entry, update and installer screens, her orb at
701
- 160 px, 96 px under 640 px wide or 600 px tall), Alveare HQ's
702
- `HiveScene`, and the website's invite page, where an invite link opens: the
703
- centred `InviteCard` (the app's mark, the words, the open action, the
704
- installers one row per computer, and the QR code that opens the same link on a
705
- phone). A picture beside an entry page's centred column is `EntryPicture`, in
706
- every product.
707
-
708
- Every pairing in Nadia, a phone linking to her or to one of her Agents
709
- (WhatsApp, Telegram, WhatsApp calls), is one step: a form drawer whose body
710
- opens with her `EntryScene` contained at 16:9, saying only its short status
711
- ("Connecting Telegram", "Linked", "Code expired"), then the code or QR the phone
712
- takes with its expiry, then the steps on the phone. A longer sentence stands
713
- under the scene, never in it. The full-window scene stays for sign-in, start and
714
- update.
715
-
716
- A single-task entry screen centres the visible identity, message and actions as
717
- one group in the usable window, horizontally and vertically. Hidden content and
718
- reserved empty rows do not contribute to that group's height. A progress screen
719
- may reserve the lines its status needs to prevent movement between stages, and
720
- its measured progress is a written count in `{typography.label-mono}`, "Step 2
721
- of 3", never a percentage. A picture stands clear of the task and whole, its
722
- feet on the window's floor, inert and hidden from assistive technology, and
723
- only from `lg`: below `lg` the picture leaves and the task keeps the screen. An
724
- image edge must not masquerade as a finished object edge. Compact layouts
725
- preserve readable controls and scroll when content cannot fit.
726
-
727
- Where a picture may stand:
728
-
729
- | Place | Picture |
730
- | --- | --- |
731
- | Entry: sign-in, sign-up and welcome, an invitation, handing a person to Nadia, Nadia's entry and update screens, the installer | Yes |
732
- | A moment: a failure page, each app's card on Downloads, a product's first run, a collection's first use, Nadia's pairing of a phone | Yes |
733
- | A funnel task: two-factor, an interrupted setup, a legal page, impersonation, a connection's done page | No: the task is enough |
734
- | 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") |
687
+ An image edge must not masquerade as a finished object edge. Compact layouts preserve
688
+ readable controls and scroll when content cannot fit.
735
689
 
736
690
  ## Layout
737
691
 
@@ -759,52 +713,7 @@ These values are the `register.website.layout` tokens; the breakpoints live in `
759
713
 
760
714
  ### Product shells
761
715
 
762
- 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:
763
-
764
- | Kind | Its one place | Portal | Nadia | Alveare |
765
- | --- | --- | --- | --- | --- |
766
- | Where I can go | The sidebar | Groups by viewer, one selected row | Find, Assignments, Scheduled tasks, the company's Wiki and Skills, then the Agents | Inbox, Channels, Direct messages |
767
- | An object's sections | The product’s documented section navigation | Customer, Partner and Agent tabs below the top bar; Installation sections in the sidebar | Two swaps only: ⋯ → Edit and Settings | The channel's own panels |
768
- | The path | The top bar | Customer or Partner switcher on the left and the current section centred; other paths centred | The window header: the Agent's orb and name, then the chat switcher; on a nested page, a back arrow to the place above, then the path | The channel header names the channel |
769
- | Find, ⌘K | The top of the sidebar | An outlined field | At the top of the list | Buzz's search, in its sidebar |
770
- | The account | The sidebar's foot | With the bell | Under the update card | The package's `AccountMenu` |
771
- | Getting the app | Above the account | A card naming the apps, with their icons | The update card, only when there is one | The update card |
772
- | 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 |
773
-
774
- - **The workspace stays in view while working on a company.** In the Portal, Customers and Partners keep the workspace sidebar and use the `TabsNav` band below the top bar. A directory or search result opens the first tab, a Customer's Company or a Partner's Overview; switching companies keeps the current section when available and otherwise opens that first tab. Customer tabs are Company, Agents, Subscriptions, Members and Activity, reduced to the viewer’s authority; a Customer's own settings are `ObjectPage` entries of its Company tab that open in place, as Vercel's deployment page opens its steps, never a Settings tab or tabs inside a tab. Partner tabs are Overview, Customers, Finances, Members, Activity and Settings. Related views, such as Finances’ Commissions and Bulk pool, use line tabs inside their section. Agents also keep the workspace sidebar with their six local tabs. Installations retain `SidebarSections`; no object adds a second sidebar. A company's first tab starts with one `ObjectPage` identity card, its subscription state and Agent counts, then, for a Customer, its parts as entries that open in place, then its scoped charts and actionable issues. Subscription status summarizes member-account subscriptions; Partner company figures, assigned Customer figures, commissions and bulk-pool credit stay distinct.
775
- - **An area whose pages are siblings opens as a sub-menu, as Vercel's sidebar does.** Vercel marks such a row with a › at its end ("Logs", "Observability", "Firewall", "Settings"), and its press replaces the places with one row, a ‹ at its start and the area's name centred, above the area's pages as words alone, the open one filled like a selected row; a row that only opens a page has no ›. In the Portal these areas are Settings (nadicode), Company (whoever manages a company) and Library (everyone who sees it: Agent templates, Skills, Plugins), each with its › row, and Account, opened from the account menu; their pages are never tabs (`SidebarSections` without an identity, its row ending in `SidebarMenuChevron`). “Tabs imply the views share scope, URL parent, and data model” (https://vercel.com/geist/tabs): Library's pages hold different objects with different lists and different ways to add, so they are a sub-menu, as Vercel's AI Gateway is. A destination whose pages are views of one reading, Analytics, keeps its tabs, as Vercel's Analytics and OpenRouter's Activity do. The ‹ returns to where the person opened the area from.
776
- - **The top bar holds navigation, never the page's actions.** Customer and Partner collections and their object sections use `ScopeSwitcher`'s navigation variant on the left: the company's mark and name, a searchable list with the current company checked, and a close link back to the originating collection. The current section is centred, below the switcher on phones so the company name and status remain visible. Other pages keep the centred path, ending at the place or the object. An object's own status sits beside its name in the path and nowhere else on the page.
777
- - **Find goes to places.** It opens from the outlined field at the top of the sidebar and on ⌘K, and names every sidebar place, chat and settings page; it never runs a command. Its field names what it searches, never a bare "Search"; ⌘K or Ctrl+K opens it and no other field takes that binding; a shortcut on a row is a `CommandShortcut`; and a polite live region reads how many results remain as the person types.
778
- - **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.
779
- - **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.
780
- - **Compact screens keep the same relationships.** Tabs scroll horizontally, primary content stacks before supporting facts, and no second sidebar appears.
781
- Nadia's window, its list, its Settings and its side panel are Nadia's register ([Nadia's design doctrine](../../apps/nadia/docs/design-doctrine.md#the-window)).
782
-
783
- **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.
784
-
785
- ### Page compositions
786
-
787
- 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:
788
-
789
- | Composition | Owner | First answer | Structure |
790
- | --- | --- | --- | --- |
791
- | Overview | `OverviewPage` | What needs attention? | The period, the figures, the chart cards, then the exceptions that need a person. Never a stack of previews of every section. |
792
- | Collection | `CollectionPage` | Which record? | The title and the page's actions, the sibling collections as line tabs, one toolbar, then one line per record ("Tables and missing values"); one row is one hover and one open target. Actionable attention stands above the toolbar when present; business summaries belong to Dashboard and Analytics. A `rail` carries context intrinsic to the collection, such as the selected Library hub. The Portal's Agents are its exception, a `DataTable` of entity rows, as Vercel's Deployments list. |
793
- | Object | `ObjectPage` | What is this, what can I do? | The parent in the path and its product’s section navigation, then the object card, then its sections, each a card only where it owns a decision. In the Portal an Agent is an `ObjectPage` whose first tab, Agent, holds her settings as rows; in Nadia's window her Edit page opens on Profile with her `AgentProfileCard`. |
794
- | Settings | `SettingsPage` | What can I change? | Section cards, each saving its own fields; the danger zone closes the page. |
795
- | Library | `LibraryShelf` for Agent templates; `CollectionPage` for Skills and Plugins | What reusable thing can I use? | Agent templates as Vercel's Templates gallery; Skills and Plugins as an entity collection, with staff's hub as its rail. |
796
-
797
- Each composition's usage page holds its slots, geometry and states.
798
-
799
- 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").
800
-
801
- Every setting is that Settings composition, whether it belongs to the person, to the platform or to one record's Settings tab, so the column keeps its width and its field layout from one entry of a settings menu to the next, and each section saves its own fields from its own footer ("Cards and sections"). A card's header and fields share one uninterrupted body, and only its save footer has a divider. `SettingsPage`'s, `SettingsSection`'s and `SettingsField`'s usage pages hold the column, the cards and the fields; Nadia keeps its settings column at most 42rem. A structured table keeps visible column names while its container can hold the data, and a matrix whose every column must stay, such as notification audiences by group, keeps them all and lets its names wrap.
802
-
803
- Every page opens with the same header, `PageHeader`: collections, working pages and settings pages alike, in the page's own column, so the title and the cards share one edge, but for the plain list `CollectionPage`'s usage page names. The title is the page's name as its navigation writes it, the page's actions sit on the title's row through `PageActions`, whose usage pages hold their order, variants and wrapping, and under the title is one sentence saying what the page's concept is, as every Vercel page does (*Passport*: what it does and where it applies), with a link to learn more when there is one. An action always sits on the title of the thing it acts on, never on a tab's, and one command is a button, never a menu of one. Navigation remains a visible link or part of the navigation, never an overflow command. That sentence is the page's only explanation: nothing else introduces, explains or counts the page, and no line of text stands loose under a card or a list; what a card's reader needs is the card's own description or footer line, and a lasting condition is a notice above what it concerns. Inside an object worked from a section sidebar, the title is the section's name ("Apps"); a page that opens with the object's own header or card, such as an Agent's Profile, has it as its title and no title row above it.
804
-
805
- 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.
806
-
807
- 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.
716
+ 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.
808
717
 
809
718
  ## Elevation & Depth
810
719
 
@@ -839,9 +748,7 @@ The owner approved the materials: Vercel's surfaces with a little more depth on
839
748
 
840
749
  **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 capture keeps what the product draws as it draws it, ruled by the founder: a label the product has not translated yet, or the marks its Browser puts on the controls a colleague works, stays. 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.
841
750
 
842
- Absence follows its container, and only a first use has a picture. In every product, the Portal included, a collection's first use shows its section's picture, and every other kind of absence goes without one. A picture sits on a working screen only there: never in a populated collection, a row or any other card; whole-page moments keep theirs ("Entry screens"). One exception, the owner's: Nadia's Settings, Memory keeps its section's picture beside the summary of what a memory knows, above its memories. A page or region absence is a resting card with its own edge, never a dashed boundary; an absence inside a card is unframed, because the card already owns the edge, and `CollectionPage` puts every absence it draws in its card. `EmptyState` is the only owner of an absence: nothing else draws one, its `scope` (`page`, `region` or `card`) sets that frame, and it has no presentation option, so every product draws each kind the same way. A composition takes its absences as props (`empty`, `noMatch`, `failed`), as a `DataTable` takes `empty`. Its kind names the cause, and `EmptyState`'s usage page holds what each kind draws: `firstUse` (a collection with no records yet: its section's picture from the approved set in `@nadicodeai/design-system/assets/empty`, which [the art direction](../../skills/design/art-direction/references/surfaces.md#the-empty-state-set) keeps, and the one action that adds the first record, which the page's title row also keeps as its filled primary; a create action carries the leading +), `nothingYet`, `noResults`, `cleared`, `denied` and `failed`; a card-sized failure is `failed` with `scope="card"`, and there is no `ErrorCard`.
843
-
844
- A persistent notice sits in the section it concerns, action feedback is a toast, and field validation stays with its field. A notice has no close button: it stays until the state changes, and one note stands for one concept. A section notice is `MessageNotice`; a condition of the page's own object that the page itself resolves, such as a past-due subscription on its billing page, is a `NoticeBanner` carrying that fix; a failure the person must handle is never a toast alone but a very short toast beside a persistent notice. A condition of the system, the setup or the staff (a connection, a stored secret, a failing job, anything a staff member fixes in settings) is a notification in the product's inbox (in the Portal, the bell beside the account at the sidebar's foot), as Vercel keeps certificate, domain and integration alerts in its dashboard's notifications popover ([Vercel's notifications](https://vercel.com/docs/notifications)). It is never a banner on the pages, the Dashboard or the frame they share; the page that fixes it shows the state in its own section. The frame's one strip marks the Portal's Impersonation, because everything done during it is recorded as the Customer's member. A notice, a failed read, a confirmation's error and a toast each show a message its product's catalogue worded, never a sentence written where it shows ([the decision](../../docs/adr/2026-10-08-every-message-a-customer-reads-is-a-code-its-products-catalogue-words.md)). Each wraps a long word, never truncates, closes its title with a full stop before a body that follows on the same line, and shows a body only when it says more than the title; their usage pages hold how each draws it.
751
+ An absence on a page or in a region (`EmptyState`) 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.
845
752
 
846
753
  Dialogs, sheets, drawers and the command palette sit over the scrim ("Colors"); popovers, selects and toasts float without one. Focus is not elevation: it is always the focus outline.
847
754
 
@@ -853,11 +760,11 @@ A corner follows what the thing is, on one scale: buttons, inputs, badges, tabs
853
760
 
854
761
  UI text stays static: no shimmer, typing, scrambling, pulsing or animated dots. Status labels use sentence case without trailing ellipses; complete instructional sentences keep their punctuation.
855
762
 
856
- 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 film of ours plays only when the visitor presses play. 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: a Stripe-grade product site, with shaders adjusted to the art direction and the products shown working.
763
+ Only Agents move on their own, with one exception: the company's card (`CompanyCard`) turns in the light for 2.2 s when it is first seen and again when it is issued, then stays still, and under reduced motion it does not turn. 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 waits](../../skills/design/product-design/references/living-elements.md#system-waits)). On the website three things move: Agents, the product at work, and the shader objects; a film of ours plays only when the visitor presses play. 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: a Stripe-grade product site, with shaders adjusted to the art direction and the products shown working.
857
764
 
858
765
  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.
859
766
 
860
- 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.
767
+ Every picture in the products stays still ("Iconography" says where one stands), 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.
861
768
 
862
769
  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.
863
770
 
@@ -876,308 +783,42 @@ The easings are `emphasized`, `out-strong`, `in-out-strong` and `drawer`; the wa
876
783
 
877
784
  ## Components
878
785
 
879
- Every product builds from `@nadicodeai/ui`: stock shadcn components on Base UI, the chart kit, the assistant-ui conversation interface, and the compositions below. A component styles itself only through the roles and tokens in this file; it carries no raw colour, no `dark:` utility and no theme of its own. A page arranges and sizes a component and never restyles it: a new appearance is a typed variant or slot added to the component, and a missing reusable composition is added to the shared package, even while one product alone needs it. Product-specific data and persistence stay in the app adapter. Visible controls always come from the package.
880
-
881
- ### Information shapes
882
-
883
- Choose the presentation from the answer the data gives. A change of page or
884
- viewer scope changes the records and available actions, while the same kind of
885
- answer keeps its component, density, value format and action placement.
886
-
887
- | Information | Component and context |
888
- | --- | --- |
889
- | Records a person finds one of and acts on | `RecordList` inside `CollectionPage`, on a page of its own. |
890
- | Records with the same comparable facts | `DataTable`. |
891
- | A descriptive item with supporting prose | `ItemGroup` and `Item`, rather than invented table columns. |
892
- | What happened, in order | `ActivityFeed` ("Activity"). |
893
- | The identity of a record in a collection | `RecordIdentity`; the containing list owns navigation. |
894
- | Facts about one object | `ObjectPage`'s facts, `ObjectFacts` or `SettingsFactsSection`, in one coherent section. A relation uses its same identity or `Tag`; it does not become a separate card. |
895
- | A coherent set of changes | `SettingsSection` with its `save` and `SettingsField`. |
896
- | A classification, lifecycle or operation | `Badge`, `StatusBadge` or `ActionStatus`. A missing value, an empty list and a failed read remain distinct. |
897
- | 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. |
898
- | A measurement over time or across objects | The KPI or chart composition for the reading ("Data Visualization"); a named comparison is a `DataTable` with units and visible headings. |
899
-
900
- One section holds one complete task or answer. A status, explanatory sentence
901
- or Change button belongs beside the facts or fields it qualifies; it does not
902
- earn a second section by itself.
903
-
904
- ### Component selection and usage
905
-
906
- Every component has a usage page, as Geist documents each of its own: what it is, and five fields.
907
-
908
- | Field | Says |
909
- | --- | --- |
910
- | When | The job it is for, with the products' own examples. |
911
- | Instead | What to use for the jobs next to it. |
912
- | Behaviour | Its states, what it does on each input, and what it owns. |
913
- | Words | Which strings the product words, and how. |
914
- | Access | Its role, its name, its keyboard path and its focus. |
915
-
916
- The page is written in the component's own source, as the TSDoc of its main export (`@family`, `@when`, `@instead`, `@behaviour`, `@words`, `@access`), so an editor shows it on hover in every product; `@nadicodeai/ui`'s showcase renders every page, family by family, with the component drawn in its states. A component without its usage page, or a page without its specimen, fails the package's checks. A usage page holds what its component does, takes and holds, applies the look in this file and never restates it.
917
-
918
- The table below maps all 73 entries in the [Geist component reference](https://vercel.com/geist/introduction) to our shared owners; how each owner is used is on its usage page. **Component** means a public owner exists for the job; it does not promise every Geist prop. **Composition** means existing parts provide the presentation together. An **enhanced capability gap** names specific behavior, not the absence of a matching component name. Use our exported APIs and names, not Geist's prop names: for example, our Button appearance is `variant`, while HTML submission behavior is `type`. A dedicated name in Geist does not require another primitive in our library; prefer existing compositions before adding another registry or dependency.
919
-
920
- 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.
921
-
922
- 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.
923
-
924
- | Geist reference | Shared owner |
925
- | --- | --- |
926
- | [Address Input](https://vercel.com/geist/address-input) | `Field` parts in a `FieldGroup` 16 px apart: a `NativeSelect` country first, a street `Combobox` (or `Input` when there is no lookup), Address line 2 with (optional) written in its label, then pairs such as State and ZIP or Postal code and City two-up; a failed lookup ends in the manual fields. No dedicated export: it needs a provider only a product has |
927
- | [Avatar](https://vercel.com/geist/avatar) | The `Avatar` family; a record's mark is `IdentityMark`, and an Agent is her `AgentOrb` |
928
- | [Badge](https://vercel.com/geist/badge) | `Badge`; `StatusBadge` for state, `Tag` for a categorical fact |
929
- | [Banner](https://vercel.com/geist/banner) | `NoticeBanner` |
930
- | [Book](https://vercel.com/geist/book) | `Card` + `AspectRatio` and cover artwork; no book-frame export |
931
- | [Breadcrumbs](https://vercel.com/geist/breadcrumbs) | The `Breadcrumb` family |
932
- | [Browser](https://vercel.com/geist/browser) | `Card` + `AspectRatio` and decorative browser chrome; `DesktopShell` stays real product chrome |
933
- | [Button](https://vercel.com/geist/button) | `Button` and `ButtonLink` |
934
- | [Calendar](https://vercel.com/geist/calendar) | `Calendar`; `DateRangeControl` for a metric's window |
935
- | [Checkbox](https://vercel.com/geist/checkbox) | `Checkbox` with the Field parts |
936
- | [Choicebox](https://vercel.com/geist/choicebox) | `ChoiceboxGroup` |
937
- | [Clearable Input](https://vercel.com/geist/clearable-input) | `ClearableInput` |
938
- | [Code](https://vercel.com/geist/code) | Semantic `code` with the shared mono typography roles |
939
- | [Code Block](https://vercel.com/geist/code-block) | `CodeBlock` |
940
- | [Collapse](https://vercel.com/geist/collapse) | `Accordion`, `Collapsible` and the complete `Disclosure`; a collapse never nests in another |
941
- | [Combobox](https://vercel.com/geist/combobox) | `Combobox` |
942
- | [Command Menu](https://vercel.com/geist/command-menu) | `CommandDialog` |
943
- | [Context Card](https://vercel.com/geist/context-card) | `HoverCard` with `HoverCardSummary` |
944
- | [Context Menu](https://vercel.com/geist/context-menu) | `ActionContextMenu` |
945
- | [Copy Button](https://vercel.com/geist/copy-button) | `CopyButton`; `TextWithCopyButton` binds a shown value to it |
946
- | [Description](https://vercel.com/geist/description) | `ObjectPage`'s `facts`, `ObjectFacts`, `SettingsFactsSection`, `TableValue` |
947
- | [Destructive Action Modal](https://vercel.com/geist/destructive-action-modal) | `ConfirmDialog` |
948
- | [Dots Menu](https://vercel.com/geist/dots-menu) | `ActionMenu` for a record, `PageActions`' `menu` for a page |
949
- | [Drawer](https://vercel.com/geist/drawer) | `Drawer`, the phone's bottom sheet for a short job |
950
- | [Empty State](https://vercel.com/geist/empty-state) | `EmptyState`, following the [absence contract](#elevation--depth) |
951
- | [Entity](https://vercel.com/geist/entity) | `RecordList` for records; `Item`, `ItemGroup` and `ItemSelect` for a descriptive list; `CatalogEntry` in `LibraryShelf` and `CatalogDrawer`; `AgentListRow` |
952
- | [Error](https://vercel.com/geist/error) | `FieldError` (a field), `MessageNotice` (a control or section), `EmptyState kind="failed"` with its reference (a block) |
953
- | [Error Card](https://vercel.com/geist/error-card) | `EmptyState kind="failed" scope="card"`; no ErrorCard export |
954
- | [Feedback](https://vercel.com/geist/feedback) | `Popover`, `Field`, `Textarea`, `RadioGroup`, optional `Select`, and `Button` |
955
- | [Fieldset](https://vercel.com/geist/fieldset) | `SettingsSection` with `SettingsField`; `FieldSet` and `FieldLegend` for semantic subgroups |
956
- | [File Tree](https://vercel.com/geist/file-tree) | `SidebarMenu` + `Collapsible` + `FileTypeIcon` can follow sidebar-11 for nested files |
957
- | [Gauge](https://vercel.com/geist/gauge) | `Gauge` |
958
- | [Grid](https://vercel.com/geist/grid) | Layout grid, `CardRow`, website `nc-page`; no ruled `Grid` API |
959
- | [Input](https://vercel.com/geist/input) | `Input`, with `prefix` and `suffix` for parts inside the box (`InputGroup` stays inside `@nadicodeai/ui`); `Field` or `SettingsField` for the card around it |
960
- | [JSON View](https://vercel.com/geist/json-view) | Composition for static JSON: code display with exact serialized text |
961
- | [Keyboard Input](https://vercel.com/geist/keyboard-input) | `Kbd` |
962
- | [Label](https://vercel.com/geist/label) | `Label`, `FieldLabel` and `FieldLegend` |
963
- | [Load More Button](https://vercel.com/geist/load-more-button) | `LoadMoreButton` |
964
- | [Loading Dots](https://vercel.com/geist/loading-dots) | `Button`'s `loading` and `WaitingLine`; no LoadingDots |
965
- | [Menu](https://vercel.com/geist/menu) | `ActionMenu`, `ActionContextMenu`, `PageActions`' `menu`, `AccountMenu` and the website's `SiteMenu`; the `DropdownMenu` family stays inside `@nadicodeai/ui`, which draws a choice menu such as a sort with it |
966
- | [MiddleTruncate](https://vercel.com/geist/middle-truncate) | `MiddleTruncate` |
967
- | [Modal](https://vercel.com/geist/modal) | The `Dialog` family, `FormDialog` for a short form and `ConfirmDialog` for a consequence |
968
- | [Multi Select](https://vercel.com/geist/multi-select) | `MultiSelect`; `Combobox multiple` when typing to find matters more; `CollectionFilters` for query facets |
969
- | [Note](https://vercel.com/geist/note) | `MessageNotice placement="section"` |
970
- | [Pagination](https://vercel.com/geist/pagination) | Not built until a page needs it: the pages of a table of records are `DataTable`'s own Previous and Next under its card, and `RecordList`'s footer paging |
971
- | [Phone](https://vercel.com/geist/phone) | `AspectRatio` + decorative frame and captured content; no device-frame export |
972
- | [Pill](https://vercel.com/geist/badge#pill) | `Badge` through `render` for a link-like badge; `Tag` for a static category; `CollectionFilters` for filters |
973
- | [Progress](https://vercel.com/geist/progress) | The `Progress` family; `UsageMeter` |
974
- | [Project Banner](https://vercel.com/geist/project-banner) | `NoticeBanner` |
975
- | [Radio](https://vercel.com/geist/radio) | `RadioGroup` |
976
- | [Relative Time Card](https://vercel.com/geist/relative-time-card) | `RecordTime` |
977
- | [Scroller](https://vercel.com/geist/scroller) | `ScrollArea` and `ScrollBar` |
978
- | [Search Input](https://vercel.com/geist/search-input) | `CollectionSearch` in `CollectionToolbar` |
979
- | [Select](https://vercel.com/geist/select) | The `Select` and `NativeSelect` families |
980
- | [Separator](https://vercel.com/geist/separator) | `Separator` |
981
- | [Sheet](https://vercel.com/geist/sheet) | `Sheet`; `RecordSheet` for one record looked at from a list; `FormDrawer` and `CatalogDrawer` for its steps and catalogue forms |
982
- | [Show more](https://vercel.com/geist/show-more) | Not built until a page needs it: a list that grows from the server takes `LoadMoreButton` |
983
- | [Skeleton](https://vercel.com/geist/skeleton) | `Skeleton` and `SkeletonRegion` |
984
- | [Slider](https://vercel.com/geist/slider) | `Slider` |
985
- | [Snippet](https://vercel.com/geist/snippet) | `Snippet` |
986
- | [Spinner](https://vercel.com/geist/spinner) | `Button`'s `loading` and `WaitingLine`, no Spinner export |
987
- | [Split Button](https://vercel.com/geist/split-button) | Not built until a page needs it |
988
- | [Status Dot](https://vercel.com/geist/status-dot) | `StatusBadge`, whose tone is a shape of dots, never a single dot |
989
- | [Switch](https://vercel.com/geist/switch) | `SegmentedControl` for two or three exclusive views of one surface; `Tabs` for panels; `ToggleGroup` for pressed buttons; `ThemeModeSwitcher` for the theme |
990
- | [Table](https://vercel.com/geist/table) | `Table`; `DataTable` for records; `RecordList` for rows that name themselves; `TableCard` and `TableValue` |
991
- | [Tabs](https://vercel.com/geist/tabs) | `Tabs`, and `TabsNav` with `TabsLink` for URL views |
992
- | [Text With Copy Button](https://vercel.com/geist/text-with-copy-button) | `TextWithCopyButton` |
993
- | [Textarea](https://vercel.com/geist/textarea) | `Textarea` with `Field` or `SettingsField` |
994
- | [Theme Switcher](https://vercel.com/geist/theme-switcher) | `ThemeModeSwitcher`, `ThemeModeMenu` and `ThemeModeSegments` |
995
- | [Toast](https://vercel.com/geist/toast) | `Toaster` and `showMessageToast` |
996
- | [Toggle](https://vercel.com/geist/toggle) | `Switch` for a boolean setting; our `Toggle` is a pressed button |
997
- | [Tooltip](https://vercel.com/geist/tooltip) | The `Tooltip` family |
998
- | [Video](https://vercel.com/geist/video) | `AspectRatio` + native video, controls, poster and caption tracks |
999
-
1000
- ### Additional shared building blocks
1001
-
1002
- These shared components also follow the same foundations, although Geist does not list each as a separate component. Use the existing product composition before choosing one directly.
1003
-
1004
- | Owner | Use and composition contract |
1005
- | --- | --- |
1006
- | `Sidebar`, `SidebarSections`, `UpdateCard`, `AccountMenu`, `DesktopShell`; the Portal's `PortalShellFrame` | Persistent product navigation and workspace chrome ("Product shells"). The product owns destinations and active state; the shell owns compact behavior, landmarks and layout. `SidebarSections` is the sidebar's swap into an object's sections or an area's sub-menu, `UpdateCard` the offer to update above the account. Never add a competing sidebar to a page. |
1007
- | Chart kit and `KpiCard` | Choose the reading through [Data Visualization](#data-visualization), then use its shared chart owner with units, period, legend and accessible data. Geometry never decides business thresholds. |
1008
-
1009
- `Popover`, `InputOTP`, the resizable panels, `AspectRatio`, the Field parts, `PairingCode`, `QrCode` and `FileDiff` each hold their contract on their usage page. Which form surface a job takes is decided in [Menus and commands](#menus-and-commands).
1010
-
1011
- ### Complete compositions
1012
-
1013
- Choose the owner for the whole task. Keep its slots and state handling together so pages supply content without recreating card zones, toolbar geometry or overlay behavior.
1014
-
1015
- | Task | Shared composition |
1016
- | --- | --- |
1017
- | Settings | `SettingsPage` → `SettingsSection` with its `save` → `SettingsField`. |
1018
- | Read-only facts | `ObjectPage`'s `facts`, `SettingsFactsSection` or `ObjectFacts`, with `TableValue` for missing facts. |
1019
- | Entity collection | `CollectionPage` on `RecordList`, with `RowEditForm` for the edit inside a row. The page supplies its records and their commands as data, never its own toolbar, table, rows or menus. |
1020
- | A list inside another page | `RecordList` with its `heading`: an overview's exceptions, an object's "Needs attention". |
1021
- | Comparable collection | `DataTable` within `TableCard`, under `PageActions` and `CollectionToolbar`. |
1022
- | Overview | `OverviewPage` with its `KpiCard` figures, its `ChartCard`s and its exceptions; the product supplies the reading time and the way to each figure's owner. |
1023
- | Object | `ObjectPage` with `ObjectSection`, `ObjectFacts`, a `RecordList` with its heading, a `SettingsSection` or an `ActivityFeed` as its sections. |
1024
- | A reusable thing to add | `LibraryShelf` with `AgentCard`s for a gallery of templates; `CollectionPage` for a catalog a person finds, opens and adds from (the Portal's Skills and Plugins); `CatalogDrawer` with `CatalogEntry`s where the choice opens from a page. |
1025
- | What happened | `ActivityFeed`; one run's steps are `RunTimeline` ("Activity"). |
1026
- | Getting something set up | `NumberedSteps` for the steps on a page; `FormDrawer job="steps"` for a setup with a wait. |
1027
- | 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)). |
1028
- | One record looked at from a list | `RecordSheet`. |
1029
- | Destructive confirmation | `ConfirmDialog`; the app's confirm-action adapter. |
1030
- | Global commands or record actions | `CommandDialog` for Find; `ActionMenu` and `ActionContextMenu` for one record; `PageActions` for a page. |
1031
- | Absence or failed read | `EmptyState`, through the composition's `empty`, `noMatch` and `failed` or `DataTable.empty`; `MessageNotice` for persistent conditions. |
1032
- | Rich choices | `ChoiceboxGroup` with `ChoiceboxGroupItem`, in the group's own `FieldSet` and legend. |
1033
- | Copyable identifier or command | `TextWithCopyButton` for a value, `Snippet` for a command; the prompt is the component's and is never copied. |
1034
- | Plain code or static JSON | `CodeBlock`. |
1035
- | A record's time | `RecordTime`: the time in its verb's sentence, or the clock time in a feed, with UTC and the local zone in its hover card; the product supplies its refresh policy. |
1036
- | Expandable file navigation | `SidebarMenu` and `Collapsible`, following shadcn sidebar-11, with file icons and real file links or commands. Keep the nested lists semantic; selection and roving arrow-key navigation, a full tree widget, need a separately verified keyboard contract. |
1037
- | Feedback collection | `Popover` with Field, Textarea, optional RadioGroup/Select and Button; inline rendering uses the same form parts. The product supplies the prompt, the topic choices, the permitted context and the destination; no collection backend is implied. |
1038
- | Native media playback | `AspectRatio` around a native video with its controls, poster and caption tracks. The product supplies the source, the captions, a transcript where needed, the playback policy and the recovery when the media is unavailable; a richer player needs a concrete media requirement. |
1039
-
1040
- Use `CopyButton`, `TextWithCopyButton`, `Snippet` and `CodeBlock` for their complete compositions. Feedback and expandable file navigation combine existing parts with product adapters. Syntax colour, full JSON or tree-widget navigation, and a JSON view's search highlighting and depth controls remain behaviour gaps: do not invent undocumented props or copy a local implementation into every caller.
786
+ Every component in `@nadicodeai/ui` takes its look only from the roles and tokens in this file: no raw colour, no `dark:` utility and no theme of its own. The sections below say how components look; what each component does, takes and holds is its usage page, in the component's source.
1041
787
 
1042
788
  ### Personalization boundaries
1043
789
 
1044
- The owner's line: follow Geist for everything that makes components and compositions coherent (the spacing scale, paddings and gaps, control, row and icon sizes, the type scale and hierarchy, layout, density, alignment, corner radii, how components are built and composed, and their behaviour and rules), and keep nadicode's colour, glass and material (surfaces, depth, shadows, blur), Agent orbs, genuine provider marks, categorical Library colours and sentence-case product words. Every part, option and rule comes from Geist's page or is already ours; nothing is invented. These differences are tokenized or owned by a typed component variant. Business status, permissions, transaction boundaries and product copy remain the product's truth; a visual reference cannot change them.
1045
-
1046
- The owner's decisions where Geist and ours differ:
1047
-
1048
- - Words stay in sentence case, never Geist's Title Case: tab titles, collapse headings, reasons and a date range's presets ("Past 7 days") included.
1049
- - Our component names stay; a Geist part we lack is added under a name that says what it is.
1050
- - A settings toggle, the theme included, is a draft that its section's Save applies, where Geist applies a toggle on change, as Vercel's own settings cards do with their footer Save.
1051
- - Find goes to places only: it runs no command and has no page stack.
1052
- - A wait stays still, with no spinner, loading dots or shimmer; a `Button` takes Geist's `loading` behaviour with its pending word in place of the spinner.
1053
- - `ChoiceboxGroup`, `ClearableInput`, `Snippet` and `LoadMoreButton` are components, never recipes a page assembles.
1054
- - The three-dot trigger stays 36 px, ruled by the owner.
790
+ Geometry follows Geist: the spacing scale, paddings and gaps, control, row and icon sizes, the type scale and hierarchy, layout, density, alignment and corner radii. Colour, glass and material (surfaces, depth, shadows, blur), the Agent orbs, genuine provider marks and the categorical Library colours are nadicode's own. Each difference from Geist is a token or a typed component variant.
1055
791
 
1056
792
  ### Controls
1057
793
 
1058
- `Button`'s usage page holds its sizes, variants and loading; `textLinkVariants`' holds the link treatments; `Checkbox`'s and `RadioGroup`'s hold their checked marks.
794
+ Input, Textarea, Select, NativeSelect, Combobox, MultiSelect and InputGroup share one box in three heights, `Input`'s.
1059
795
 
1060
- - **Dialog footers.** A dialog's Cancel sits at the footer's leading edge and its primary at the trailing edge; a dialog with one button fills the footer with it. A step back (Previous) is grouped with Cancel in one wrapper at the leading edge, so the footer keeps two ends. A choice to stop being asked is a checkbox at the end of the body, remembered by whichever action is taken, never a third button. On a phone the dialog and the confirmation are bottom sheets whose footer keeps its one row, Cancel leading and the primary trailing.
1061
- - **Tabs** switch between sibling collections or views of one scope, such as a company's Members, Invitations and Teams, or Billing's Subscriptions, Approvals and Stripe events. One shows at a time; the choice lives in the URL, as a route or `?tab=` with the first tab left out, so it survives a reload, Back and a shared link; and switching never changes whose things you are looking at. A tab never introduces a new scope: another company or Agent is another place, reached from the sidebar or a scope selector, and a choice of whose things are listed is a filter in the toolbar. A page's collections take the line tabs (`Tabs` with `TabsList variant="line"`), under the title row and above the toolbar. The page's actions stay on its title row whichever tab is shown, the toolbar belongs to the tab shown and switching clears its search and page, and two lists are never stacked with a tab row between them. A set of different things, such as Nadia's company sections, is pages in the sidebar, never tabs. A choice that changes a value rather than a view uses radio or toggle semantics instead. `Tabs`' usage page holds the tabs' geometry and words.
1062
- - **Fields.** Input, Textarea, Select, NativeSelect, Combobox, MultiSelect and InputGroup share one box in three heights, `Input`'s, and `Field`'s label and message: their usage pages hold the geometry, the states and the validation. Help is the one-line description of the card that holds the field, one setting to a card, never a line under each field; a field that may stay empty says so in its label, never in its placeholder.
1063
796
  - **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.
1064
797
  - Enabled buttons show a pointer; a disabled control advertises nothing. On coarse or non-hover input every target is at least `{spacing.touch-target}`, while a checkbox or radio mark stays compact and its label supplies the target. An unchecked checkbox or radio mark is its `{colors.input}` edge with no fill of its own, so it shows the surface it sits on, and greys on hover unless disabled.
1065
798
 
1066
- ### Cards and sections
1067
-
1068
- A card or settings section is composed around a real task, collection or form; its header names the content and may explain scope or consequence, and it is never a card holding only an introduction. A card's or a section's title carries no icon, unless the card stands for one external system, whose mark it then carries. On a settings page the shape follows the data. Settings that one card's description covers share that card, whatever their controls, as Vercel's Root Directory card holds its switches, and `SettingsRows`' usage page holds their rows; a typed value, a name or an address, keeps a card of its own. Every choice on a settings card, a switch, a theme, a language or a pick from a short list among them, is a draft until the card's footer Save applies it, and the product's toast confirms it; a save that fails says so under the card and keeps the draft. A collection is a table whose Add opens a form and whose every row carries **⋯**, and a destructive action there confirms first. A row of five figures sits in one row from 1280 px, three over two below that, two by two with the fifth across on a tablet, and one column on a phone; it never leaves one figure alone beside a hole.
1069
-
1070
- `Card`, `CardRow`, `SettingsSection`, `SettingsFactsSection` and `KpiCard` hold their own shapes on their usage pages.
1071
-
1072
- ### Menus and commands
1073
-
1074
- 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. A menu names the record it acts on, keeps a command that cannot run in place with its reason, and puts the destructive command last after a divider; `ActionMenu`'s usage page holds how it draws them, and `AccountMenu`'s the account menu's one exception. A destructive command never becomes the loudest repeated action: it is never a button on every row.
799
+ ### Line breaks
1075
800
 
1076
- 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:
1077
-
1078
- | The person wants to | Surface | Owner | Example |
1079
- | --- | --- | --- | --- |
1080
- | Change a record shown in a list | Its row opens into the form | `RowEditForm` in `CollectionPage`'s `editor` | Vercel Domains › Edit; a task, a mailbox, an MCP server, a channel |
1081
- | 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 |
1082
- | 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 |
1083
- | Choose from a catalogue | A modal sheet at the right edge | `CatalogDrawer` | Add a skill, in both products; Vercel Domains › Add |
1084
- | Add one of a few kinds of record | A menu from the title row's primary button; a kind that needs its own fields opens its dialog | `PageCommand`'s `choices` | Vercel Add New…; the Wiki's Add source |
1085
- | Set up in several steps with a wait | The same sheet, with its progress | `FormDrawer job="steps"` | Connect a messaging channel; Telegram's QR setup |
1086
- | Look at one record from a list, then go back to the list | A sheet from the right that leaves the page usable | `RecordSheet` | One Stripe event in Billing |
1087
- | Do a workflow a person could link to | Its own page | The page's composition | Vercel Add New › Project; New Agent |
1088
- | Confirm a consequence | A confirmation | `ConfirmDialog` | Remove, Disconnect |
1089
-
1090
- A record already on screen is never edited in a dialog or a sheet, and a sheet is never chosen because an action takes input: a catalogue is `CatalogDrawer` and a setup in steps is `FormDrawer` with `job="steps"`. A `Drawer` is only the phone's bottom sheet for a short job, never a desktop surface and never a confirmation. A settings page is the Settings composition. These choices follow the live Vercel Domains, Projects and settings pages and the [Geist Modal](https://vercel.com/geist/modal), [Sheet](https://vercel.com/geist/sheet) and [Fieldset](https://vercel.com/geist/fieldset) patterns.
1091
-
1092
- 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.
1093
-
1094
- ### Tables and missing values
1095
-
1096
- `Table`, `DataTable`, `TableCard`, `RecordList`, `Item` and `Tag` hold their geometry, columns and rows on their usage pages; a `DataTable` has column headings. No heading sorts the rows, so no heading is a button: the page holds the order, through a `CollectionSort` above the list, and `aria-sort` marks the column the rows follow.
1097
-
1098
- A collection fits its container without sideways scrolling, and on a phone a row keeps its name above supporting facts that wrap in full, never a stack of labelled values, where a status keeps its own mark; `DataTable`'s and `RecordList`'s usage pages hold how their columns give way. Text anywhere in the product breaks between words, and inside a word only when that word alone cannot fit a line; a machine string such as a hash or a key breaks at any character in wrapped text, or is cut in the middle with `MiddleTruncate` on one line.
1099
-
1100
- An unknown or not-applicable value in a table, a comparison list, a record list's facts or a set of facts is a muted `—` with screen-reader text, in the same slot as a present value, and its column stays; 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.
801
+ Text anywhere in the product breaks between words, and inside a word only when that word alone cannot fit a line; a machine string such as a hash or a key breaks at any character in wrapped text, or is cut in the middle with `MiddleTruncate` on one line.
1101
802
 
1102
803
  ### Status
1103
804
 
1104
- A status is one `StatusBadge`: a shape and a word in one of five tones, with no pill around them, as Vercel sets a deployment's state beside its time. Each tone has its own shape, filled with dots on a honeycomb grid, the material of the Agent orbs: green a circle, blue a diamond, amber a triangle, red a square, and gray an empty ring, because gray is every state with nothing to act on, Ready and Archived as much as Paused. The shape says the tone, so a status reads without colour, and the word says the exact state; a new state picks a tone, never a new shape. Ruled by the owner: the stock tinted pill read as generic, and the status takes the Agents' own dots. `StatusBadge`'s usage page holds the tones; `ProgressChecklist`'s holds how far a record is through the steps that make it ready.
1105
-
1106
- The Portal, Nadia and Alveare draw every status with the one `StatusBadge` in `@nadicodeai/ui`, which owns the tones and their shapes; each app keeps only its value-to-tone map in one module, and nothing else chooses a status colour. A status never moves. A single coloured dot is never a status, including beside a visible label and inside an Agent row or filter; the shared package exports no status-dot component. An Agent's orb remains her identity and activity indicator.
1107
-
1108
- 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").
1109
-
1110
- An Agent fleet's operational-state column and a run history's outcome are facts being compared, so each entry keeps its named `StatusBadge`, including Ready and Completed. This exception never permits a single coloured dot.
1111
-
1112
- ### Activity
1113
-
1114
- What happened has one shape in every product, `ActivityFeed`, whose usage page holds its row and frame; one run's steps are `RunTimeline`'s.
1115
-
1116
- ### System activity
1117
-
1118
- 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"):
1119
-
1120
- | The wait | What shows |
1121
- | --- | --- |
1122
- | Under about a second | The control's own label changes to its pending word, a button's through its `loading` and `loadingLabel`; its place, size and icon stay. |
1123
- | Past a second | One 2 px waiting line of ink beside the work, a 32% segment crossing it on `wait`, and nothing else moves. |
1124
- | Measurable | A progress line with a written count, "Step 2 of 3" or "12 of 40 MB". Never a percentage. A ratio of a fixed maximum is a `Gauge`, which prints the bare number and leaves the unit to its label. |
1125
- | A page load | Still skeleton placeholders in the shape of the content, inside one `SkeletonRegion`, never pulsing and never paired with a waiting line. |
1126
-
1127
- 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.
1128
-
1129
- A wait shows where the thing lives. A drawer or dialog stays open only for the person's part: what they still have to do in it (scan a code, finish in the browser, type) or a check that can still refuse what they entered, so they fix it there. Once that is accepted it closes, and what the system still has to do (a restart, an install, a sync) shows on the record's own row or section: its waiting line with the work named ("Connecting Telegram"), then its state, or its failure with the next action. An action that is itself the wait, such as sending an invitation or a payment, keeps its dialog until it answers. Ruled by the owner: Telegram's connect drawer stayed locked on "Connecting" while messaging restarted, after the person's part was done.
1130
-
1131
- In a row the wait is the row's detail line naming the work, with one waiting line under it and no status badge for the same wait. A waiting line beside a visible sentence or label that names the work is `decorative`; a line standing alone takes a `label`.
805
+ A status is a shape and a word, with no pill around them, as Vercel sets a deployment's state beside its time. Each of the five tones has its own shape, filled with dots on a honeycomb grid, the material of the Agent orbs: green a circle, blue a diamond, amber a triangle, red a square, and gray an empty ring. The shape says the tone, so a status reads without colour, and the word says the exact state. Ruled by the owner: the stock tinted pill read as generic, and the status takes the Agents' own dots. `StatusBadge` draws every status, and its usage page holds the tones and when a status shows.
1132
806
 
1133
807
  ### Failure surfaces
1134
808
 
1135
- A page that cannot show what was asked for opens with a picture, the way a Portal email does: one whole cut-out in art direction's editorial register, centred above the words, whose idea says what happened. Decorative artwork has no gallery caption, title, materials or year. Below the picture, on the same centre line, stand a mono eyebrow naming the situation, the page's one heading, one plain sentence, and the recovery action, which leads. The cut-out's transparent ground takes the page's own, so one file serves light and dark, and it stays whole at every width: it scales down with the column and is never cropped by the viewport. No orb turns on a failure page.
1136
-
1137
- 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.
1138
-
1139
- The not-found page and the fault page are one composition, in that order: the picture, 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:
1140
-
1141
- - **An absence** is an address or a record that is not there. Its picture carries 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.
1142
- - **A fault** is a request the product stopped. Its picture is 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.
1143
-
1144
- An absence inside a product's own chrome, such as an address or a record the Portal cannot open, keeps its picture, centred in the content region with the chrome around it. A fault inside the chrome keeps the words alone. Identity hue reaches a product failure surface only through the picture. The words never apologize and never name the mechanism ("Product messages" in `product-design`). On the website a failure surface is an ordinary full-bleed section and takes a section ground like any other.
809
+ A page that cannot show what was asked for opens with a picture, the way a Portal email does: one whole cut-out in art direction's editorial register, centred above the words, with no gallery caption, title, materials or year. The words stand under it on the same centre line, led by a mono eyebrow. The cut-out's transparent ground takes the page's own, so one file serves light and dark, and it stays whole at every width: it scales down with the column and is never cropped by the viewport. An absence, an address or a record that is not there, has a picture that carries the joke of the missing address, and nothing on it takes a feedback role. A fault, a request the product stopped, has a calm picture that is never a joke about lost work, and the error pair marks its eyebrow. Identity hue reaches a product failure surface only through the picture, and no orb turns on a failure page. On the website a failure surface is an ordinary full-bleed section and takes a section ground like any other.
1145
810
 
1146
811
  ### Agent orbs
1147
812
 
1148
- An Agent is its orb, in every product; there is no other Agent picture. `AgentOrb`'s usage page holds its forms, colours, sizes, states and reduced motion, and `AgentListRow`'s holds an Agent's row in Nadia's list. A surface that only knows a machine checked in draws idle.
1149
-
1150
- - **In a conversation.** Her states are the conversation's own: the orb in its header follows them, and every mark of her activity in the thread is her own orb in her real state, never another Agent's figure, a spinner, a ticker or a pulse. Her steps and tool calls hold still and say in words what they do or did, with a tool's kind as a decorative icon beside the words and no state of its own; the outcome of a step or a request is the `StatusBadge`, and a failed turn is the `MessageNotice`. A conversation has two actors, her and the person, and every wait in it belongs to one of them. Everything she does, including what follows the person's approval or answer, is her orb in its state: working once Run is pressed, thinking once a question is answered, waiting while an approval or a question waits for the person, blocked when a turn fails, done once when she finishes. The person's own action in progress shows on what they made: a file stays veiled on its own chip until it is uploaded, and fills only where its upload reports progress; the microphone shows it records. A pressed control keeps its place and changes only its word, a button through its `loading`. The waiting line is for the window's own loading, outside the conversation. Ruled by the owner: the Agent's states are the chat's default states, not random icons, and the person never meets "the app" in a conversation, only the Agent.
1151
-
1152
- ### Agent card
1153
-
1154
- An Agent shown as an object, in the Portal's Library and Nadia's gallery alike, is one `AgentCard`, whose usage page holds its shape.
1155
-
1156
- ### Company card
1157
-
1158
- The company's card is `CompanyCard`, whose usage page holds where it appears (sign-up, the account's Profile, a Sign-up invitation, a colleague's invitation), its face, foil and motion.
813
+ An Agent is her orb, in every product; there is no other Agent picture. `AgentOrb`'s usage page holds its forms, colours, sizes, states and reduced motion.
1159
814
 
1160
815
  ### Identity marks
1161
816
 
1162
- A company detail leads with its own published logo beside its name and description, never a deployment preview or an empty media panel. Every company logo keeps its original colours and proportions, contained whole on the company's dark ringed tile, which also backs transparent artwork; it is never converted to a silhouette or clipped to a shape. Until a logo is found, or when it does not load, the same tile carries the company's initials in white, so a found logo replaces only the letters. A company's mark never shows its standing and is the same in either directory: a Partner is said in words where its standing matters, never by a colour. `IdentityMark` owns the rendering and fallback; `ObjectPage` owns its placement.
1163
-
1164
- An identity mark says which thing a record is, and `IdentityMark` is its one owner: a row's, an object's, a feed's and a sidebar's mark is one of its kinds, never a decorative icon. A list gets its richness from real identity marks, never from decoration. `IdentityMark`'s usage page holds its kinds and sizes.
1165
-
1166
- 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.
1167
-
1168
- ### Product views
1169
-
1170
- Website product views follow "Product views on the website" in [Elevation & Depth](#elevation--depth): authentic captures or real-component scenes, with provenance recorded in the repository. Preserve the product's register and interface. Crop to the detail being explained; retain an original-image link for captures and never frame a view in a device.
817
+ A recognised app, platform or model maker is drawn with its genuine mark in its own colours; an unknown one is a text monogram, never a guessed logo. Every company logo keeps its original colours and proportions, contained whole on the company's dark ringed tile, which also backs transparent artwork; it is never converted to a silhouette or clipped to a shape. Until a logo is found, or when it does not load, the same tile carries the company's initials in white. A mark's colour identifies its maker, never a connection's health. `IdentityMark` draws a record's mark, and its usage page holds the kinds, sizes and fallbacks.
1171
818
 
1172
819
  ### CSS components
1173
820
 
1174
- These three ship as framework-neutral CSS; `@nadicodeai/ui` wraps the last two for React.
1175
-
1176
- **`dotted-field`** — the ground of a canvas where a person or an Agent places and arranges objects: a diagram of an Agent's work, a board of its outputs. The dots say that things here can be moved, so the field never sits behind a dashboard, a list, settings or a website section, and it never stands in for elevation.
1177
-
1178
- **`usage-meter`** — consumption against its allowance; `UsageMeter`'s usage page holds the rest.
1179
-
1180
- **`run-timeline`** — the thread of an Agent's run, one step per row; `RunTimeline`'s usage page holds the rest.
821
+ Three components ship as framework-neutral CSS. `dotted-field` is the ground of a canvas where a person or an Agent places and arranges objects, such as a diagram of an Agent's work or a board of its outputs. The dots say that things here can be moved, so the field never sits behind a dashboard, a list, settings or a website section, and it never stands in for elevation. `usage-meter` and `run-timeline` are the stylesheets of `UsageMeter` and `RunTimeline`, whose usage pages hold the rest.
1181
822
 
1182
823
  ## Iconography
1183
824
 
@@ -1185,24 +826,28 @@ Every product draws Lucide's shapes, unedited, in the house render: a 1.5 px lin
1185
826
 
1186
827
  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.
1187
828
 
1188
- `ObjectFactValue` gives Agent, Installation and Customer counts their fixed semantic mark and category colour inside the value: cobalto, acqua and viola respectively. A linked value keeps the same pairing. Its label stays plain, and a status remains `StatusBadge`; category colour never asserts health.
829
+ 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: an icon there repeats the words beside it and competes with the marks that matter. A menu row takes one icon, leading its label; a button takes one, leading or trailing it (`prefix` or `suffix`), and a create action carries the leading +. A mark goes where it says which thing you are looking at. Where each goes:
1189
830
 
1190
- The blue category uses the cobalto 200 ground and cobalto 1000 ink through `category-blue` and `category-blue-foreground`, with the same ramp steps in each theme. It never uses the status roles `info` or `info-soft`.
1191
-
1192
- 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: an icon there repeats the words beside it and competes with the marks that matter. A menu row takes one icon, leading its label; a button takes one, leading or trailing it (`prefix` or `suffix`). A mark goes where it says which thing you are looking at; a picture marks a moment. Where each goes:
831
+ | Place | Identity mark | Icon |
832
+ | --- | --- | --- |
833
+ | A record's row | Yes, 32 px, first | Its kind's icon, as its mark, when it has no mark of its own |
834
+ | An object's header | Yes, 64 px | No |
835
+ | A card's or a section's title | Only when the card is one external system | No |
836
+ | A KPI card | No | Only the trend in the change badge |
837
+ | Nothing yet, no results | No | No |
838
+ | Finished work, a denial | No | The success mark; the lock mark |
839
+ | A failed read | No | The warning mark |
840
+ | A menu row, a button | No | One, leading; a button's may trail |
841
+ | Entry, a failure page, Downloads, a first run | The Agent's orb where she is the subject | No |
842
+
843
+ A picture marks a moment, one whole cut-out of the art direction. Where a picture may stand:
1193
844
 
1194
- | Place | Identity mark | Icon | Picture |
1195
- | --- | --- | --- | --- |
1196
- | A record's row | Yes, 32 px, first | Its kind's icon, as its mark, when it has no mark of its own | No |
1197
- | An object's header | Yes, 64 px | No | No |
1198
- | A card's or a section's title | Only when the card is one external system | No | No |
1199
- | A KPI card | No | Only the trend in the change badge | No |
1200
- | A first-use empty state | No | No | Yes, the section's picture, 240 × 176 on a page |
1201
- | Nothing yet, no results | No | No | No |
1202
- | Finished work, a denial | No | The success mark; the lock mark | No |
1203
- | A failed read | No | The warning mark | No |
1204
- | A menu row, a button | No | One, leading; a button's may trail | No |
1205
- | Entry, a failure page, Downloads, a first run | The Agent's orb where she is the subject | No | Yes, one, whole; Downloads one per app, in its card |
845
+ | Place | Picture |
846
+ | --- | --- |
847
+ | Entry: sign-in, sign-up and welcome, an invitation, handing a person to Nadia, Nadia's entry and update screens, the installer | Yes |
848
+ | A moment: a failure page, each app's card on Downloads, a product's first run, a collection's first use, Nadia's pairing of a phone | Yes |
849
+ | A funnel task: two-factor, an interrupted setup, a legal page, impersonation, a connection's done page | No: the task is enough |
850
+ | Any other page inside a product's chrome, and every other absence | No, except an address that is not there, whose picture stands centred in the content ("Failure surfaces"), and Nadia's Settings, Memory, which keeps its section's picture beside the summary of what a memory knows, above its memories (ruled by the owner) |
1206
851
 
1207
852
  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.
1208
853
 
@@ -1215,7 +860,7 @@ The company is **nadicode**, lowercase. Its logo is the n, and the name drawn in
1215
860
  3. **Icon.** The mark on a tile with rounded corners, in one of three fields. The brand tile, the white n on `{colors.identity-cobalto}`, is for favicons, avatars and the website's app icon, and is the one place the brand colour fills a logo's field. Where the platform crops the picture itself, a profile picture cut to a circle or a square and a phone's home screen, the tile has square corners and the platform draws its shape: the avatar is the brand tile, or the light or dark tile where a profile wants white or black, with the n across 46% of the side, so a circle cut keeps it whole with its clear space. The mark is never a profile picture: it has no field, and its n runs to the edges a crop cuts. The light tile, the `{colors.ink}` n on white, is a desktop app's icon, which keeps it in the system's dark appearance. The dark tile, the white n on `{colors.identity-ink}`, is the icon wherever else it sits on a dark ground: a dark slide, an app's own dark screen.
1216
861
  4. **Signature form.** The light tile with its construction guides in `{colors.logo-guide}`: a dotted circle around the arch whose radius is the golden section of the n's width, the two centre axes, and the n's width and height, each measured 1.000. It is drawn from 128 px up, on light grounds: Nadia's desktop icon, a cover or closing slide, a poster, print; on a dark ground the dark tile stands in for it. Below 128 px a guide is a fraction of a pixel, so the icon drops them; drawn on a screen as a logo, such as at the top of an entry scene, it drops them at any size.
1217
862
 
1218
- Alveare's mark is its own, chosen by the founder: six flat honeycomb cells, pointed at the top, stacked one, two and three into a pyramid, in the identity colours row by row from the top: cobalto in its dark step (`{colors.dark-logo-dot}`), arancio, verde, rosso, giallo, azzurro. Six cells of one comb are the hive. It sits on the dark tile everywhere, the app icon included, since Alveare opens onto black, and on a black screen it stands alone without its tile. Alveare HQ carries the same mark and the same app icon, since it is the company's headquarters for its Alveare. The mark is drawn from one geometry source, `@nadicodeai/design-system/assets/alveare-geometry`: the app icons and marks and `PlatformMark` render it, and nothing copies its paths.
863
+ Alveare's mark is its own, chosen by the founder: six flat honeycomb cells, pointed at the top, stacked one, two and three into a pyramid, in the identity colours row by row from the top: cobalto in its dark step (`{colors.dark-logo-dot}`), arancio, verde, rosso, giallo, azzurro. Six cells of one comb are the hive. It sits on the dark tile everywhere, the app icon included, since Alveare opens onto black, and on a black screen it stands alone without its tile. Alveare Server's mark is a server rack of six bands, each with its light, in the same colours in the same order from the top, drawn a tenth smaller than the cells so the solid rack weighs what the open cells do beside it. Both are drawn from one geometry source, `@nadicodeai/design-system/assets/alveare-geometry`: Alveare's app icons and marks and `PlatformMark` render it, and nothing copies its paths.
1219
864
 
1220
865
  The mark and the wordmark never stand side by side: the wordmark already begins with the n, so together they read "n nadicode". Each place takes one of them.
1221
866
 
@@ -1235,14 +880,14 @@ The clear space is one stroke width of the rendered logo on every side. The mini
1235
880
  | Alveare | `apps/alveare/docs/design-doctrine.md` | None | System, light or dark, on Buzz's own theme pair |
1236
881
 
1237
882
  - **Website.** The page grammar is "The website column": full-bleed sections on white, soft and black grounds and the bands "Colour as stage" allows, alternating, with product views on them. Its cards are outlined; its product views take their real heights.
1238
- - **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.
883
+ - **Portal.** A product shell on the workspace ground, with `globals.css` giving every elevated surface its tone.
1239
884
  - **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.
1240
885
  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 in glass ("Elevation & Depth"). The selected Agent row carries the sidebar's selected fill, as the Agents list draws it.
1241
- - **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 jobs the products share, Alveare mounts the package's own component, or the look the package exports for it, in place of Buzz's: every empty state is `EmptyState`, the account menu is `AccountMenu`, a status is `StatusBadge`, the three dots are `ActionMenu`, Find is `SidebarFind`'s outlined row with its `Kbd`, a count at a sidebar row's end is `SidebarMenuBadge`, the composer is the chat bar (`nc-chat-bar`), and a wait is drawn as "System activity" says, by the same components Nadia's are. Alveare's own screens are checked by the same design-system lint as Nadia's and the Portal's. Buzz's colour roles are generated from the `semantic` map of each mode, so the sidebar and the page sit on the canvas in dark as Nadia's do. Agents appear as their orbs, and a person never reads "Buzz". Ruled by the owner.
886
+ - **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`. A job the products share takes the package's own component, or the look the package exports for it, in place of Buzz's ([Alveare's screens](../../apps/alveare/docs/design-doctrine.md#screens)). Buzz's colour roles are generated from the `semantic` map of each mode, so the sidebar and the page sit on the canvas in dark as Nadia's do. Agents appear as their orbs, and a person never reads "Buzz". Ruled by the owner.
1242
887
 
1243
888
  ## Do's and Don'ts
1244
889
 
1245
- - 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.
890
+ - 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, a first use's one arrival and the company card's turn in Motion.
1246
891
  - 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.
1247
892
  - Do choose a surface's height by its physical role, and let its recipe show it. Don't add a border, ring, second shadow or grey fill to an elevated surface.
1248
893
  - Do put glass only where content scrolls or moves under it. Don't make content, a menu, a popover, a toast or a dialog glass, or put glass over a plain page.
@@ -1250,12 +895,10 @@ The clear space is one stroke width of the rendered logo on every side. The mini
1250
895
  - Do give each thing the corner its role takes on the scale ("Shapes"). Don't choose a corner at the call site: a raised card, like any medium or large surface, takes the larger corner with its material.
1251
896
  - Do give every line one owner, and draw focus only with the focus outline.
1252
897
  - Do say a status with a `StatusBadge` tone, its shape and a word. Don't use colour alone or a bare dot.
1253
- - Do open a failure page as [Failure surfaces](#failure-surfaces) says, with the diagnostic code only for a request that stopped. Don't apologise or joke about lost work.
898
+ - Do open a failure page with its picture, as [Failure surfaces](#failure-surfaces) says. Don't let its picture joke about lost work.
1254
899
  - Do use the dotted field only on a canvas where things are arranged. Don't put it behind lists, dashboards, settings or website sections.
1255
- - Do build a page from its composition. Don't assemble a page's header, toolbar, rows or menus by hand.
1256
- - Do put navigation 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.
1257
900
  - Do change this file when the look changes, and put how a component or a page is built with its owner. Don't restate this file in another document; link to it.
1258
901
 
1259
902
  ## Token extension
1260
903
 
1261
- The YAML front matter holds the four groups the DESIGN.md format defines: colors, typography, rounded and spacing. Every other value is authored in `src/tokens/extension.tokens.json`, in the W3C design-tokens format: `core.material` (the scrim and glass values, a `dark-` value beside one that differs in dark), `core.elevation` (the four heights and the glass edge, per mode), `core.motion` (durations and easings), `semantic` (the shared colour roles and radius) and `register.website` (the website column). An alias is written `{core.color.canvas}`. In CSS a token is `--nc-<name>`, an elevation is reached only through `nc-elevation-<height>`, and Tailwind names the values through the `nc-` and `nadicode-` namespaces. The build composes the two into the package's token files and CSS, as the package README describes.
904
+ The YAML front matter holds four of the five token groups the DESIGN.md format defines: colors, typography, rounded and spacing. The fifth, `components`, stays empty, because each component's look is drawn by its code in `@nadicodeai/ui` from these tokens. Every other value is authored in `src/tokens/extension.tokens.json`, in the W3C design-tokens format: `core.material` (the scrim and glass values, a `dark-` value beside one that differs in dark), `core.elevation` (the four heights and the glass edge, per mode), `core.motion` (durations and easings), `semantic` (the shared colour roles and radius) and `register.website` (the website column). An alias is written `{core.color.canvas}`. In CSS a token is `--nc-<name>`, an elevation is reached only through `nc-elevation-<height>`, and Tailwind names the values through the `nc-` and `nadicode-` namespaces. The build composes the two into the package's token files and CSS, as the package README describes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/design-system",
3
- "version": "12.1.196",
3
+ "version": "12.1.198",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,3 +19,4 @@ export type AlvearePaint = (token: AlveareToken) => string;
19
19
 
20
20
  export declare const ALVEARE_DRAWING: number;
21
21
  export declare function alveareMarkGeometry(paint: AlvearePaint): AlveareGeometry;
22
+ export declare function alveareServerMarkGeometry(paint: AlvearePaint): AlveareGeometry;
@@ -1,17 +1,22 @@
1
1
  /**
2
- * Framework-neutral SVG geometry for Alveare's mark, which Alveare HQ carries too.
2
+ * Framework-neutral SVG geometry for Alveare's and Alveare Server's marks.
3
3
  *
4
- * This module is the only drawing of the mark. Alveare's and Alveare HQ's app
5
- * icons, the marks their scenes and the website show, and the shared
6
- * PlatformMark render this descriptor instead of copying paths.
4
+ * This module is the only drawing of either mark. Alveare's app icons, the marks
5
+ * its scenes and the website show, and the shared PlatformMark render these
6
+ * descriptors instead of copying paths.
7
7
  *
8
- * The mark is drawn in a square of ALVEARE_DRAWING units, centred in it, and
9
- * its view box crops to its shapes. A consumer paints each shape by the design
10
- * token it takes, which `paint` turns into a colour: a CSS variable in a page,
11
- * a hex value in a file. The six tokens run from the top.
8
+ * Both marks are drawn in one square of ALVEARE_DRAWING units, centred in it,
9
+ * so an icon places them alike; each view box crops to its own shapes. A
10
+ * consumer paints each shape by the design token it takes, which `paint`
11
+ * turns into a colour: a CSS variable in a page, a hex value in a file. The
12
+ * six tokens run from the top in the same order on both marks.
12
13
  *
13
- * The mark is six pointy-top cells of one comb, stacked one, two and three: on
14
- * a grid of radius 31, each cell is 5 units from its neighbours.
14
+ * Alveare's mark is six pointy-top cells of one comb, stacked one, two and
15
+ * three: on a grid of radius 31, each cell is 5 units from its neighbours.
16
+ * Alveare Server's is a rack 160 units square of six bands 6 apart, its outer
17
+ * corners of radius 18, each band with a light of radius 4.4 cut out 50 units
18
+ * right of centre; it is drawn at nine tenths, so the solid rack weighs what
19
+ * the open cells do beside it.
15
20
  */
16
21
 
17
22
  const freeze = (geometry) => {
@@ -81,3 +86,42 @@ export const alveareMarkGeometry = (paint) => {
81
86
  })),
82
87
  });
83
88
  };
89
+
90
+ const RACK_BALANCE = 0.9;
91
+ const RACK_SIDE = 160;
92
+ const BAND_GAP = 6;
93
+ const CORNER = 18;
94
+ const LIGHT = 4.4;
95
+ const LIGHT_RIGHT = 50;
96
+
97
+ /** Alveare Server's mark: the rack's six bands, painted from the top, each with its light. */
98
+ export const alveareServerMarkGeometry = (paint) => {
99
+ const at = (unit) => Number(round2(CENTRE + RACK_BALANCE * (unit - CENTRE)));
100
+ const size = (unit) => round2(RACK_BALANCE * unit);
101
+ const left = CENTRE - RACK_SIDE / 2;
102
+ const right = CENTRE + RACK_SIDE / 2;
103
+ const band = (RACK_SIDE - 5 * BAND_GAP) / 6;
104
+ const r = size(CORNER);
105
+ const corner = `A${r} ${r} 0 0 1`;
106
+ const light = size(LIGHT);
107
+ const lightX = CENTRE + LIGHT_RIGHT;
108
+ return freeze({
109
+ viewBox: viewBox(at(left), at(left), at(right), at(right)),
110
+ nodes: TOKENS.map((token, index) => {
111
+ const top = left + index * (band + BAND_GAP);
112
+ const bottom = top + band;
113
+ const middle = at(top + band / 2);
114
+ let body = `M${at(left)} ${at(top)}H${at(right)}V${at(bottom)}H${at(left)}Z`;
115
+ if (index === 0) {
116
+ body = `M${at(left)} ${at(bottom)}V${at(top + CORNER)}${corner} ${at(left + CORNER)} ${at(top)}H${at(right - CORNER)}${corner} ${at(right)} ${at(top + CORNER)}V${at(bottom)}Z`;
117
+ } else if (index === TOKENS.length - 1) {
118
+ body = `M${at(left)} ${at(top)}H${at(right)}V${at(bottom - CORNER)}${corner} ${at(right - CORNER)} ${at(bottom)}H${at(left + CORNER)}${corner} ${at(left)} ${at(bottom - CORNER)}Z`;
119
+ }
120
+ const hole = `M${at(lightX + LIGHT)} ${middle}A${light} ${light} 0 1 0 ${at(lightX - LIGHT)} ${middle}A${light} ${light} 0 1 0 ${at(lightX + LIGHT)} ${middle}Z`;
121
+ return {
122
+ element: "path",
123
+ attributes: { fill: paint(token), "fill-rule": "evenodd", d: `${body} ${hole}` },
124
+ };
125
+ }),
126
+ });
127
+ };