@nadicodeai/ui 10.1.0 → 12.1.1

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.
Files changed (96) hide show
  1. package/AGENTS.md +39 -24
  2. package/README.md +58 -124
  3. package/dist/agent-orb.d.ts +1 -1
  4. package/dist/agent-orb.d.ts.map +1 -1
  5. package/dist/assets/brand/index.json +1 -1
  6. package/dist/components/action-menu.js +1 -1
  7. package/dist/components/agent-card.d.ts.map +1 -1
  8. package/dist/components/agent-card.js +2 -4
  9. package/dist/components/agent-orb.d.ts +11 -5
  10. package/dist/components/agent-orb.d.ts.map +1 -1
  11. package/dist/components/agent-orb.js +8 -6
  12. package/dist/components/alert.js +3 -3
  13. package/dist/components/badge.js +6 -6
  14. package/dist/components/brand-field.d.ts +2 -0
  15. package/dist/components/brand-field.d.ts.map +1 -1
  16. package/dist/components/brand-field.js +3 -1
  17. package/dist/components/button.d.ts +1 -1
  18. package/dist/components/button.d.ts.map +1 -1
  19. package/dist/components/button.js +4 -5
  20. package/dist/components/carousel.js +2 -2
  21. package/dist/components/chart-bar-horizontal.js +1 -1
  22. package/dist/components/chart.js +1 -1
  23. package/dist/components/command.js +1 -1
  24. package/dist/components/context-menu.js +1 -1
  25. package/dist/components/diff-count.js +1 -1
  26. package/dist/components/dropdown-menu.js +3 -3
  27. package/dist/components/footer.d.ts +4 -7
  28. package/dist/components/footer.d.ts.map +1 -1
  29. package/dist/components/footer.js +1 -4
  30. package/dist/components/item.js +1 -1
  31. package/dist/components/kpi-card.js +2 -2
  32. package/dist/components/label-system.d.ts +1 -5
  33. package/dist/components/label-system.d.ts.map +1 -1
  34. package/dist/components/label-system.js +2 -6
  35. package/dist/components/menu-choice-row.js +1 -1
  36. package/dist/components/menubar.js +1 -1
  37. package/dist/components/object-workspace.js +1 -1
  38. package/dist/components/progress.js +1 -1
  39. package/dist/components/scope-switcher.d.ts +24 -0
  40. package/dist/components/scope-switcher.d.ts.map +1 -0
  41. package/dist/components/scope-switcher.js +34 -0
  42. package/dist/components/settings-section.d.ts +2 -1
  43. package/dist/components/settings-section.d.ts.map +1 -1
  44. package/dist/components/settings-section.js +2 -2
  45. package/dist/components/status-dot.js +1 -1
  46. package/dist/components/status-row.js +1 -1
  47. package/dist/components/table.d.ts.map +1 -1
  48. package/dist/components/table.js +1 -1
  49. package/dist/components/tabs.d.ts +11 -5
  50. package/dist/components/tabs.d.ts.map +1 -1
  51. package/dist/components/tabs.js +37 -5
  52. package/dist/components/tooltip.js +1 -1
  53. package/dist/components/trend-badge.d.ts.map +1 -1
  54. package/dist/components/trend-badge.js +1 -2
  55. package/dist/eslint/index.js +4 -0
  56. package/dist/eslint/rules/jsx-class-utility.js +1 -1
  57. package/dist/eslint/rules/no-shadcn-appearance-override.js +3 -3
  58. package/dist/eslint/rules/one-control-height.js +94 -0
  59. package/dist/eslint/rules/settings-section-description.js +45 -0
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -1
  63. package/dist/internal/agent-orb-motion.d.ts +1 -0
  64. package/dist/internal/agent-orb-motion.d.ts.map +1 -1
  65. package/dist/internal/agent-orb-motion.js +6 -3
  66. package/dist/internal/agent-orb.d.ts +10 -2
  67. package/dist/internal/agent-orb.d.ts.map +1 -1
  68. package/dist/internal/agent-orb.js +19 -9
  69. package/dist/internal/assistant-ui/attachment.js +4 -4
  70. package/dist/internal/assistant-ui/follow-up-suggestions.js +1 -1
  71. package/dist/internal/assistant-ui/markdown-text.js +4 -4
  72. package/dist/internal/assistant-ui/reasoning.js +2 -2
  73. package/dist/internal/assistant-ui/thread.d.ts.map +1 -1
  74. package/dist/internal/assistant-ui/thread.js +11 -9
  75. package/dist/internal/assistant-ui/tool-group.js +2 -2
  76. package/dist/styles/components.css +9 -5
  77. package/dist/styles/globals.css +43 -0
  78. package/package.json +3 -4
  79. package/dist/components/handoff-banner.d.ts +0 -9
  80. package/dist/components/handoff-banner.d.ts.map +0 -1
  81. package/dist/components/handoff-banner.js +0 -6
  82. package/docs/activity.md +0 -113
  83. package/docs/agent-orb.md +0 -168
  84. package/docs/component-classification.md +0 -45
  85. package/docs/consuming-cross-repo.md +0 -116
  86. package/docs/contract.md +0 -388
  87. package/docs/icon-assets.md +0 -106
  88. package/docs/migration/1.0.0.md +0 -75
  89. package/docs/migration/10.0.0.md +0 -144
  90. package/docs/migration/2.0.0.md +0 -69
  91. package/docs/migration/6.0.0.md +0 -30
  92. package/docs/migration/9.0.0.md +0 -35
  93. package/docs/migration/base-ui-migration.md +0 -62
  94. package/docs/model-identity.md +0 -18
  95. package/docs/product-compositions.md +0 -96
  96. package/docs/product-sections.md +0 -200
package/docs/contract.md DELETED
@@ -1,388 +0,0 @@
1
- # @nadicodeai/ui Package Contract
2
-
3
- The normative invariants for the `@nadicodeai/ui` package: what may live here, how it is styled, and what consumers own. `../AGENTS.md` is the map; this file is the contract it routes to.
4
-
5
- ## Styling Invariants
6
-
7
- - **Token-only, no raw literals or shadow aliases.** Components style through semantic shadcn utilities (`bg-background`, `text-foreground`, `border-input`, and the rest), or a canonical generated design-system token when no standard role is precise enough. The UI package does not mint screen-, component-, or product-specific color aliases. No raw `#hex` / `rgb()` / `hsl()` may appear in `.tsx`; a new source value goes to `packages/design-system/DESIGN.md` first and flows through generated output. A narrow protocol adapter may translate third-party classes directly to canonical tokens in `globals.css`, but it must not create another token vocabulary. Enforced by the raw-colour `no-restricted-syntax` selectors in the library ESLint config, `packages/ui/tests/guards/dark-semantic-roles.test.ts`, and `packages/ui/tests/guards/token-reference-integrity.test.ts`.
8
- - **Dark-ready semantics, no component dark implementation.** Components use the same semantic role or canonical mode-aware token in every mode. They must not ship Tailwind `dark:` color utilities, component-local `.dark` blocks, OS dark-mode branching, or raw dark palette utilities such as `dark:bg-slate-950` and `dark:text-white`. An intentionally fixed-presentation technology surface, such as the dark terminal console, selects canonical fixed-mode tokens as one coherent component treatment rather than branching by app theme. Enforced by `packages/ui/tests/guards/no-dark.test.ts`, `packages/ui/tests/guards/dark-semantic-roles.test.ts`, and `packages/ui/tests/guards/token-reference-integrity.test.ts`. The generated mode and theme architecture lives only in [`packages/design-system/DESIGN.md`](../../design-system/DESIGN.md#css-architecture--token-pipeline).
9
- - **Components inherit one complete React style interface.** `@nadicodeai/ui/globals.css` composes every style dependency owned by this package, including the complete design-system CSS module. Components carry no theme of their own, and React callers do not compose design-system stylesheets or partials beside it. Installation and source-scanning setup live in the [consumer setup guide](consuming-cross-repo.md). The package ships two stylesheets, split by who owns the page. `@nadicodeai/ui/globals.css` is for an application this design system styles whole: it imports Tailwind, the complete design system with its reset and base layer, and the second file. `@nadicodeai/ui/components.css` is for a host that already runs Tailwind over base styles of its own, such as the Nadia desktop inside Hermes's shell: it carries the `@source` lines, the `nc-*` utilities and the component-owned rules, and nothing that lands on a page. That host imports `@nadicodeai/design-system/css/host` before it, which adds the Tailwind keys only this design system names and leaves the host its shadcn roles, its radius scale and its palette. The components render in both from one source, which is what keeps the products consistent. A surface that loads no Tailwind layer takes the design system's token artifact and writes its own rules against it.
10
- - **Design-system geometry remains the foundation.** React components compose the shipped `nc-*` layout and display CSS components; they do not redefine the website's content column, its gutters, its step between sections, token values, or raw CSS geometry. Use parent-paints-plus-gap for bordered cell grids and keep every visible seam single-owner (doctrine home: `packages/design-system/DESIGN.md` "## Layout").
11
-
12
- ### Persistent notices
13
-
14
- `NoticeBanner` composes Alert's icon, title, description and optional action.
15
- Use it for persistent guidance in the affected section or page container;
16
- transient action feedback uses Toast, and field validation stays with its field.
17
- The component owns wrapping, typography and the responsive action placement.
18
- Consumers supply the condition, localized impact and recovery action. Internal
19
- error codes stay in diagnostics, not notice content. Portal's operational,
20
- read-only and stale-data notices use this shared composition directly.
21
-
22
- ### Material surfaces
23
-
24
- `components.css`, which `globals.css` imports, declares the four elevation utilities, `nc-elevation-resting`,
25
- `nc-elevation-raised`, `nc-elevation-floating`, and `nc-elevation-modal`. Each
26
- sets `box-shadow` to the generated `--nc-elevation-<rung>` token, pinned by
27
- `packages/design-system/DESIGN.md` "core.elevation": one recipe per rung per
28
- mode, whose last layer is that rung's optical ring, black at 5% in light and
29
- white at 18% in dark. No package or consumer declares a second elevation
30
- vocabulary. Never pair an `nc-elevation-*` root with `border-*`, a separate
31
- `ring-*`, or another `box-shadow` (`nadicodeai/no-elevated-edge` enforces it).
32
-
33
- Physical role, not content importance, chooses depth:
34
-
35
- - `Card surface="resting"` (the default) uses `nc-elevation-resting`.
36
- - `Card surface="raised"` uses `nc-elevation-raised`; active or selected
37
- work chooses it explicitly.
38
- - `Card surface="plain"` is the explicit structural/auth/website escape and
39
- carries no elevation.
40
- - `Card surface="outlined"` is a flat panel with a single semantic border and
41
- compact corners; it carries no elevation ring or shadow. Bounded fact cells
42
- use it inside resting record sections. Portal's kit keeps the resting default.
43
- - Tooltip, menu, popover, select, combobox, navigation popup, hover card,
44
- chart tooltip, and toast roots own `nc-elevation-floating` internally.
45
- - Dialog, alert dialog, drawer, and sheet roots own `nc-elevation-modal`
46
- internally.
47
-
48
- All solid surfaces use the one semantic fill. Dark mode therefore stays true
49
- black; depth never comes from progressively greyer cards.
50
-
51
- Keyboard focus has one owner: the `globals.css` base layer draws the offset
52
- ink outline on every `:focus-visible` element, and an invalid control takes
53
- the same geometry in the destructive hue. A component never repaints focus
54
- with a box-shadow ring, a raw width, or its own hue
55
- (`nadicodeai/no-repainted-focus` enforces this). A component may only
56
- suppress the outline where a proxy target carries it (`InputGroup` shows the
57
- indicator for its inner control; the OTP active slot for its hidden input) or
58
- where a highlight ground replaces it (menu and command items). The proxy
59
- target draws it with `nc-focus-outline`, the one utility that carries the
60
- outline's style, width, and offset, plus a ring-role color utility. Nothing
61
- restates those three as separate utilities: an `outline-none` or
62
- `outline-hidden` on the same element leaves Tailwind's own outline utilities
63
- painting nothing.
64
-
65
- `nc-glass` is the one glass material for floating chrome: popover, select,
66
- combobox, and navigation-menu popups, the command palette, and `GlassControl`
67
- bars carry it over our own content. Command dropdowns use a solid popover fill
68
- so labels stay readable over dense rows; account dropdowns share that surface. Content surfaces stay
69
- solid: cards, KPIs, tables, inspectors, and dialog, sheet, and drawer bodies.
70
- Glass keeps exactly one separator, the elevation falloff's integrated edge
71
- ring. Reduced transparency falls back to the same solid floating surface.
72
- `GlassControl` remains the control-chrome form, using `shape="control"` for
73
- compact floating controls and `shape="bar"` for full-width header chrome.
74
-
75
- `Empty` makes containment explicit with `scope="page" | "region" | "card"`.
76
- Page and region states own one opaque dashed boundary. A Card-scoped Empty is
77
- transparent and unframed because the Card already owns containment.
78
-
79
- ### Tables
80
-
81
- `Table` owns the native table structure, solid semantic background, an overflow
82
- safety boundary, and one compact density: 36px header rows and 40px body rows. Consumers
83
- compose `TableHeader`, `TableBody`, `TableRow`, `TableHead`, and `TableCell`; they
84
- do not replace table layout with grid or flex, hide the header to create another
85
- presentation, or reconstruct the native semantics with ARIA. Portal collections
86
- must fit their available width without horizontal scrolling: preserve identity
87
- and actions in columns and place supporting facts in native detail rows before
88
- the full schema becomes cramped. Test the table container itself, not only the
89
- document width; retaining the overflow safety boundary must not conceal a
90
- layout defect. Body rows use the full `bg-accent` fill on hover, keyboard focus
91
- within, and while a descendant menu is expanded. Headers and footers do not gain
92
- the body-row hover treatment. Keep cell backgrounds transparent so they do not
93
- hide the shared highlight. The design system's interaction states own the
94
- cross-product table and action affordances.
95
-
96
- `CollectionScope` chooses the parent of one record collection. It places the
97
- scope selector and optional detail action above the collection toolbar. Empty
98
- parents stay in the selector. Apps bind authorized options and URL state; clearing
99
- record filters preserves the parent scope. Parent details belong on their detail
100
- page, not in repeated panels surrounding identical child tables.
101
-
102
- A ranked breakdown is not a kit component: the Portal renders it through its
103
- one table owner, `DataTable`, with a `Progress` cell. The kit composes no second
104
- table (`tests/chart-kpi-ownership.test.ts`).
105
-
106
- ### Record action menus
107
-
108
- `ActionMenu` is the shared three-dot composition of `Button` and `DropdownMenu`:
109
- a ghost icon trigger, horizontal ellipsis, and end-aligned popup. The shared
110
- stylesheet gives enabled buttons a pointer cursor, including this trigger. It inherits the
111
- menu's opaque surface, elevation, motion, keyboard behavior, and focus return. Supply
112
- `label` as a localized, record-specific accessible name; `triggerId` supports
113
- focus return from an app-owned confirmation, and `children` supplies menu items.
114
- Apps own action selection, permission, placement, and confirmation. Reuse this
115
- composition instead of drawing another trigger or popup.
116
-
117
- ## Status Vocabulary
118
-
119
- `Badge` is the product's only status carrier. It ships five semantic tone
120
- variants — `gray`, `green`, `red`, `amber`, `blue` — each one soft/deep pair
121
- from the generated tokens (`--nc-canvas-soft-2`/`--nc-body`,
122
- `--nc-link-bg-soft`/`--nc-link-deep`, `--nc-error-soft`/`--nc-error-deep`,
123
- `--nc-warning-soft`/`--nc-warning-deep`, `--nc-info-soft`/`--nc-info-deep`), so
124
- a status clears AA in both modes without a call site painting anything. A status
125
- badge carries a mark in the existing icon slot beside its word, because colour
126
- must never be the only thing that says what the status is; the mark is
127
- `aria-hidden`, and the word is the accessible status. The tones are variants,
128
- not utilities, precisely so `nadicodeai/no-shadcn-appearance-override` can keep
129
- them here.
130
-
131
- What this package does not own: which statuses exist, which tone and which mark
132
- each value takes, and the words. Those are the consuming app's domain — the
133
- Portal maps them in one kit module and nothing else picks a status colour. The
134
- design-system side of the decision, and why the retired CSS status grammars are
135
- gone, is [`packages/design-system/DESIGN.md`, "## Status Vocabulary"](../../design-system/DESIGN.md#status-vocabulary).
136
-
137
- ## Package Shape Invariants
138
-
139
- System activity is `PendingLabel` under a second, `WaitingLine` past one,
140
- `Progress` with a written count when the amount is known, and a still
141
- `Skeleton` for a page load; [activity.md](activity.md) owns selection,
142
- rendering and accessibility. An orb means an Agent, so no system wait takes
143
- one, and nothing here carries an outcome state.
144
-
145
- - **Source-authored, built package.** Components are authored as shadcn-style source in `src/`, then emitted to `dist/` for consumption by other apps. Package exports must point at built JS, declarations, and CSS, never raw `src/*.tsx`, and `npm pack --dry-run` must include only intentional consumer artifacts.
146
- - **Package surface is source-backed.** Public components stay flat under `src/components/`. Reviewed registry source under `src/internal/` is implementation, not a package namespace. Do not add top-level exports for recipes, blocks, agent, terminal, or vendor groupings unless a later package API decision adds concrete public source, built files, docs, and tests in the same change.
147
- - **Agents is the client-only conversation interface.** `@nadicodeai/ui/agents` re-exports the complete `@assistant-ui/react` runtime and primitive API plus the reviewed, branded registry composition held under `src/internal/assistant-ui/`. Its built entry retains `"use client"`. Consumers receive Thread, attachments, Markdown, reasoning, tools, suggestions, providers, runtimes, hooks, and types from that one domain namespace. Agent identity components use their direct component subpaths so server compositions do not import the conversation runtime. The vendor name is not a public nadicode subpath, and the package does not rename, wrap, or rebuild assistant-ui behavior.
148
- - **Base UI is the active primitive base.** `components.json` must resolve through a Base style, currently `base-nova`, and `npx shadcn@latest info --json` must report `base: "base"`. Do not reintroduce `radix-ui`, `@radix-ui/*`, or `vaul` to package source, dependencies, or app optimizer configuration. Registry-owned shadcn primitives stay aligned with current `shadcn@latest` Base UI output; use Base UI `render` composition instead of Radix `asChild`. `cmdk` is allowed only for the stock shadcn Command component while the current `base-nova` registry declares that dependency; do not use it elsewhere or replace it without a shadcn/compatibility decision and tests.
149
- - **Charts are stock shadcn charts.** Every chart and KPI here is a registry item, changed as little as possible: `chart.tsx` is shadcn's `chart` over Recharts; `chart-bar-stacked`, `chart-area-stacked`, `chart-bar-multiple`, and `chart-bar-horizontal` are the blocks of those names; `chart-sparkline` is `chart-line-linear` stripped of its card, axes, grid and tooltip; `kpi-card` is the `dashboard-01` block's section card; `chart-card` is their shared Card frame. Each pulled file names its registry item on line one. `base-nova` does not publish the chart gallery and the CLI search does not list it, so a chart the kit lacks is pulled from `https://ui.shadcn.com/r/styles/new-york-v4/<name>.json` (`files[0].content`) and added here. What deviates from stock, and why, is listed once in [`DESIGN.md`, "Chart source"](../../design-system/DESIGN.md#chart-source); a change to a pulled chart that is not on that list is drift. There is no hidden table twin, no mark inside a legend, and no second axis. Every chart component renders the whole card — `Card > CardHeader(CardTitle, CardDescription, CardAction) > CardContent(ChartContainer) > CardFooter?` — at a fixed plot height (`h-64`, `h-80` at `size="lg"`), legend below the plot, left-aligned and wrapping. An app composes these, never builds a chart card frame by hand, and never imports `recharts`, which this package alone declares. Which chart a reading question deserves is owned by [`skills/design/product-design/references/charts.md`](../../../skills/design/product-design/references/charts.md).
150
-
151
- - **Named shadcn blocks stay named.** When a user, plan, or issue selects a shadcn block/example such as `login-02`, `signup-02`, `dashboard-01`, or `sidebar-*`, use that registry item as the source of truth through shadcn CLI/MCP. Do not replace it with a package-authored abstraction unless the plan explicitly records the substitution, the reason, and the true consumer route that proves it.
152
- - **A change to what the package ships bumps the version.** Any change to exported components, `src/index.ts`, package `exports`, `globals.css`, `components.css`, theme behavior, package docs, or design-system token, CSS, or theme output this package consumes bumps both package versions in lockstep before handoff, and `npm pack --dry-run -w @nadicodeai/ui` shows exactly what consumers receive (procedure: `../../../docs/agents/publish.md`).
153
-
154
- ## Admission Rules
155
-
156
- Assistant conversation and runtime APIs enter through `@nadicodeai/ui/agents`; Agent identity enters through `@nadicodeai/ui/components/agent-orb`, its stored-appearance resolver `@nadicodeai/ui/components/agent-orb-appearance` (both owned by [`agent-orb.md`](agent-orb.md)), and `@nadicodeai/ui/components/agent-card`. `@nadicodeai/ui/agent-orb` and `@nadicodeai/ui/activity` are the two React-free entries: each exports the canvas renderer its component mounts, for a native startup screen that paints before React loads, and neither may import React. The consuming application still constructs the runtime and owns transport, persistence, permissions, tools, approvals, and failure policy. Shared non-Agent product components (a shell, a settings surface, a generic section, a diff, or a file preview) are admitted only with a named consuming surface landing in the same change. App-bespoke UI and behavior (window chrome, PTY/xterm embeds, queues, approvals, and persistence) stay in the consuming app; they never become package abstractions. A component left unconsumed after its target migration ships gets removed.
157
-
158
- ### Placement
159
-
160
- `@nadicodeai/ui` and `@nadicodeai/design-system` hold the Foundation and the
161
- components, assets and styles at least two products use; a component, asset or
162
- style one product alone needs lives in that product's own directory. Nadia's
163
- entry screen is the worked example: the room, the presence, the typed line and
164
- their stylesheet were a package surface no other product imported, and they now
165
- live in the desktop at `apps/nadia/hermes/apps/desktop/src/plugins/nadia/entry/`,
166
- where [`apps/nadia/docs/entry-screen.md`](../../../apps/nadia/docs/entry-screen.md)
167
- owns their contract. `packages/ui/tests/guards/product-placement.test.ts`
168
- refuses a product's name in either package.
169
-
170
- ### Agent identity
171
-
172
- `AgentOrb` is the only Agent identity the package ships, and
173
- [`agent-orb.md`](agent-orb.md) owns its rules. `AgentCard` composes the
174
- fundamental `Card` with explicit `visual`, `name`, `job`, `status`, `meta`, and
175
- `action` slots and owns presentation only; when it is the page identity, the
176
- `name` slot carries the page's `h1`. There is no `AgentGlyph`, `AgentRow`, or
177
- product-domain `agent` object prop in the shared package.
178
-
179
- - **Standard components, not app-owned product features.** This package ships primitives, standard UI components, assistant-ui compositions, brand adapters, React adapters, reusable sections, and presentational product components. It must not ship app-specific feature/use-case screens or names such as operator-login, portal activation, customer credential control, or customer management. Domain registries, command search providers, and chart dashboards bind app data and routes in the app; that binding is not a reason to copy reusable presentation. Portal's DataTable owns its Next.js query and registry protocol once, while consuming the shared UI foundation. Product-independent compositions belong here with a real consumer. The source-backed `chart-*` and `kpi-card` package surface is the plan-approved exception per issue #166; apps select charts and bind their titles, data, and navigation. A chart is named by the card title above it and navigated through Recharts' own accessibility layer; the kit ships no hidden table twin, because a component whose complete value is already available in visible text or `aria-valuetext` does not add a redundant table.
180
- - **Auth routes are consumer-owned shadcn block work.** Login, signup, OTP, two-factor, magic-link, and provider-button flows start from the selected shadcn block/example in the consuming app, with app-owned Better Auth behavior. Do not ship generic auth, signup, OTP, federated-login, or provider abstractions from this package unless a later explicit boundary decision replaces the selected shadcn block.
181
- - **Reusable sections and compositions live here after proof.** Media mounts, demo surfaces, section intros, CTA bands, proof slots, generic shells, and homepage/use-case sections belong in `@nadicodeai/ui` when a second concrete consumer, a plan-approved shared component, or a named validation slice proves the package boundary. Website, portal, and sibling Nadia routes pass data and media children into existing compositions instead of rebuilding layout locally.
182
- - **Brand renderers live here for React consumers.** Logo geometry, asset generation, and usage semantics are owned by [`packages/design-system/DESIGN.md`, "Brand media authority"](../../design-system/DESIGN.md#brand-media-authority). `BrandWordmark` names the company and `BrandMark` is the plain n, for placements where the name does not fit. There is no lockup adapter: the n is cut from the wordmark, so a placement carrying both would draw the letter twice, and `tests/brand-logo-usage.test.ts` fails on one. Both render the package's public neutral geometry seam, which emits no DOM id, with dynamic theme paint and accessible names. Consuming routes use these adapters; they never import raw logo assets, draw brand SVG inline, mask or recolor assets, or compose a new logo form.
183
- - **Third-party brand marks are generated, never hand-drawn and never hand-edited.** Other companies' marks live in `packages/ui/src/components/brand-icons/`, emitted from an explicit allow-list by `packages/ui/scripts/build-brand-icons.mjs` and exported through the `@nadicodeai/ui/components/brand-icons` subpath. They are identity images, not UI icons: a brand mark is exempt from the currentColor stroke system and from the raw-literal rule, it never stands in for a UI icon, and a UI icon never stands in for a brand. Marks drawn as one silhouette are stripped to `currentColor` by the generator; every other mark keeps its official colors verbatim. To add a brand, edit the generator's allow-list and run `npm run generate:brand-icons -w @nadicodeai/ui`; hand edits to the generated directory are lost and `packages/ui/tests/guards/brand-icons-codegen.test.ts` fails on them. Use is nominative only — naming a platform, an integration, or a provider — never adjacent to endorsement or partnership wording; that nominative posture is the licensing and trademark position, so a mark never appears where the copy implies endorsement.
184
- The one script reads platform and app marks from the Iconify `logos` set (CC0-1.0), model identities from the LobeHub icon set (MIT, `@lobehub/icons-static-svg`), and a small reviewed supplemental set from the official or OpenRouter-published source recorded beside each input. Raster supplements are embedded into generated SVG components, so a consumer never fetches a logo at runtime. The emitted components carry no icon-data runtime dependency. The artwork itself does ship, so every upstream travels with its applicable licence or provenance notice in `LICENSES/` and `NOTICE`, and `tests/guards/brand-icons-codegen.test.ts` holds the allow-list to that: a new source cannot ship unattributed.
185
- - **The house's own icons are Lucide components.** `@nadicodeai/ui/components/house-icons` exports `ArchIcon`, `ApprovalsIcon` and `AgentIcon`, built with lucide-react's `createLucideIcon` from the one geometry in `@nadicodeai/design-system/assets/house-icons`, so they take the `svg.lucide` house render and fit any `LucideIcon` slot. They name the company and its doors, work waiting on a person, and an Agent; a Lucide glyph never stands in for them. The render itself, a 1.5 px line held on screen and 1.25 px at 12 px, is `DESIGN.md` "Iconography".
186
- - **No fake proof APIs.** Components may support future testimonials, logos, stats, and proof slots, but they must render nothing public when the proof is absent. Do not ship placeholder customer names, metrics, or logos.
187
-
188
- ### Model identity
189
-
190
- `ModelMark` is a model's identity: its maker or model-family mark, plus the
191
- model's name and author when a caller supplies them. It takes `author` — the
192
- slug a usage read reports — and resolves it against the reviewed catalog
193
- mapping (rule `model-shown-with-logo`). OpenRouter aliases prefixed with `~`
194
- resolve to their canonical author's mark. For community author pages where
195
- OpenRouter publishes the generic Hugging Face image, `ModelMark` uses that same
196
- fallback and still prints the actual author. A future author outside the mapping
197
- gets a text monogram, not an invented logo or an empty tile. The tile owns the
198
- box and scales the artwork inside it; `size` states that box, and the author
199
- slug prints as reported.
200
-
201
- One component has two readings: with `name`, the row a table cell or ranked
202
- list reads; without one, the bare mark beside text that already names the model.
203
- The chart kit does not place marks: its legend and tooltip are the stock shadcn
204
- content, and a chart names a model through the label in its `ChartConfig`. A
205
- model's row in a table or a ranked list is where `ModelMark` belongs. A mark is
206
- decoration beside the label that already names it; one standing alone takes
207
- `label` (`role="img"` with an accessible name) or stays hidden.
208
- Named marks stay within their containing cell, shortening the visible text when
209
- space is tight; callers with long model IDs provide the full ID in `title`.
210
-
211
- The package owns the reviewed author-to-mark mapping and its generated artwork;
212
- the live catalog remains the source of which authors exist. The consuming app
213
- supplies the reported author slug, the model name, and the colors it pairs with
214
- them.
215
-
216
- ### App identity
217
-
218
- `AppMark` is an app an Agent connects to: Gmail, Google Calendar, Slack,
219
- GitHub, Notion and the rest of the generated set. It takes `app` — a
220
- connector's slug, registry id or display name, compared on letters and digits
221
- alone — and draws the generated mark in the same tile as `ModelMark`, or the
222
- app's initial when the set has no mark for it. It never fetches a favicon and
223
- never draws an invented logo. Every product that names a connected app draws it
224
- through `AppMark` or `appMarkFor`, so the same app looks the same in the Portal
225
- and in Nadia; a product keeps no folder of logo files of its own.
226
-
227
- ## Accessibility Invariant
228
-
229
- **Tier-1 accessibility is tested, browser-free.** Rendering component tests use the package `expectAccessible` helper to check the rendered DOM. The jsdom tier is structural: rendered contrast, landmark context, focus, and keyboard behavior belong to real-browser Playwright coverage. The shared library ESLint configuration is the executable authority for static JSX accessibility rules, and the repository quality doctrine owns their policy; this contract does not copy the rule inventory.
230
-
231
- ## Reusable Sections
232
-
233
- The source tree, barrels, and package exports own the current reusable section vocabulary. When a page needs a variation, extend the owning source component with a small typed prop or slot; do not fork the layout in a consumer or document a second component inventory here.
234
-
235
- `KpiCard` is one measured figure, in the shape of the `dashboard-01` section
236
- card: label, value, the change as a badge top right, and a footer carrying the
237
- comparison line and a note, both in the caller's language. There is no grouped or divided variant — a
238
- row of KPIs is a `CardRow` of `KpiCard`s, each its own card. Apps supply the
239
- figures and bind links through `render`. Charts expose `size` (`default` or
240
- `lg`), which selects a fixed plot height, so consumers never select internal
241
- chart elements. `ChartSummary` owns a chart's measured value, trend badge,
242
- status and comparison line inside the chart card; consumers provide typed
243
- content instead of rebuilding that presentation beside the plot.
244
-
245
- `CollectionToolbar` owns the collection-control layout: compact filters at the
246
- leading edge, a flexible search field, and trailing actions. Its slot order is
247
- also the reading and focus order, including when controls wrap. Consumers supply
248
- existing controls and own their query state; they do not set independent search
249
- widths or align filters to table columns. `CollectionFilters` owns the controlled
250
- field/value picker and applied-filter chips; callers supply options, localized
251
- copy, and callbacks. The component owns discovery, edit/remove, keyboard focus,
252
- and empty search results. It does not own routing or fetch state.
253
-
254
- `SettingsFactsSection` owns a named record or settings reading section: its
255
- heading and optional action, header separator, and bounded responsive fact
256
- cells in two equal columns, stacked on compact screens. Consumers supply labels,
257
- values, an optional contextual footer, and a Card surface; paragraph facts take a full row. The stable heading remains a programmatic focus target
258
- when an editor closes. Keep fact labels, order, and cells stable when data is
259
- missing: null and undefined values use the required localized
260
- `missingValueLabel`, while zero remains a value. Consumers do not filter out
261
- unknown facts or replace them with indefinite loading skeletons. Editable
262
- controls remain in `SettingsSection`.
263
-
264
- `ReadingColumn` owns the centered content column a settings region reads in.
265
- `width="reading"` bounds the measure at `max-w-3xl` for stacked forms and record
266
- sections; `width="full"` keeps the workspace width for a page whose table needs
267
- it. Both stay centered, so the column agrees with the centered page heading
268
- above it at either width. Consumers supply the sections inside it and do not set
269
- their own container width or centering around them.
270
-
271
- `CardRow` owns a row of sibling cards. The grid stretches its cells and every
272
- direct card child fills the one it lands in, so cards that share a row share one
273
- height instead of ending on a ragged edge (`rule/card-row-equal-height`).
274
- `columns` takes 2, 3, 4, or `auto` for an equal share however many cards there
275
- are, and `from` names the breakpoint the row starts at (`sm`, `md`, `lg`, `xl`);
276
- below it the row is one column, where each card is its own row. Consumers pass
277
- layout — the two props, a `gap-*` when the default rhythm is wrong, and `render`
278
- when the row is a `section` or a list — never appearance. `nadicodeai/card-row-owner`
279
- reports a multi-column grid or row flex of cards written without it.
280
-
281
- `SettingsSection` composes Card's named header, divider, FieldGroup body, and
282
- optional divided `footer` for local save actions and feedback. The caller owns
283
- the form transaction and controls; the section owns their shared geometry.
284
- Header `actions` are optional supporting commands, distinct from footer submission.
285
- `SettingsField` uses one render callback in server and client components. Spread
286
- its `controlProps` onto the control to retain the visible label, descriptions,
287
- error association, and disabled/required state; do not wire another copy by hand.
288
-
289
- `TableDetailRow` keeps supporting collection facts readable at compact widths
290
- inside a native full-width table cell. It shares the owning column labels and
291
- values rather than requiring another responsive cell implementation. Identity
292
- and status retain their column headers. Missing values retain their place.
293
- `Table compactDetails` and `TableDetailRow layout` share `tableCompactLayouts`:
294
- `standard` retains full columns at 768px of container width, `wide` at 1024px,
295
- and `spacious` at 1152px for schemas with additional operational columns.
296
- The same owner switches column visibility, detail visibility, and row boundaries.
297
- `TableRecordGroup` wraps the primary row and its compact detail row in one
298
- native row group, so hovering, focusing, expanding, or selecting either half
299
- highlights one record. A consumer does not render the two rows as independent
300
- siblings. Portal `DataTable` chooses a layout from its column count by default;
301
- a comparison whose columns remain readable longer may select one of these
302
- shared layouts instead of adding route-owned width rules.
303
- Supporting facts stack below 384px of table width, use two columns when they
304
- fit, and use three for additional facts at the larger container breakpoint.
305
- The cell renderer supplies the value, leaving the label to the detail row.
306
-
307
- ### Missing table values
308
-
309
- `TableValue` owns the [cross-product missing-value convention](../../design-system/PRODUCT_LANGUAGE.md#missing-values-in-collections).
310
- Pass the actual scalar or rendered value and a required localized `missingLabel`.
311
- Null, undefined and empty strings render a muted, visually fixed `—` plus
312
- screen-reader text; zero and present values pass through unchanged. No custom
313
- visible fallback is exposed. Do not prefill `value` with absence copy. Bounded
314
- object facts use the same owner and retain their labels and slots when sparse;
315
- the component's name does not restrict it to a table element.
316
- Loading, failure, permission and actionable states remain outside this component.
317
-
318
- ## Consumer Boundary
319
-
320
- Consumers own routing, auth, app data, server actions, runtime construction and adapters, permission decisions, analytics, and app-specific copy. The package owns reusable React presentation and the public Agent UI dependency boundary. Agent state and behavior come through the Agents interface; non-React and TUI contracts belong to their consuming products.
321
-
322
- App source composes layout, data, behavior, and semantic content around package components. It does not restyle an imported component root: appearance changes enter through the component's typed variant or are added to the owning package component. App `className` values on package components arrange and size the component root. Descendant `data-slot` selectors are not a public styling API; internal spacing, type, and sizing belong to the shared component, enforced by `nadicodeai/no-shadcn-appearance-override`. App source uses semantic roles such as `bg-background` and `text-foreground`; canonical `nc-*` color utilities stay behind package-owned primitives such as `BrandField`. Raw Tailwind palette colors are not available to anyone: the generated theme clears the `--color-*` namespace, so `bg-red-500` produces no CSS and `better-tailwindcss/no-unknown-classes` reports it. Visible controls come from `@nadicodeai/ui`, while hidden inputs remain valid form transport. The root ESLint rules `nadicodeai/no-shadcn-appearance-override` and `nadicodeai/no-native-visible-control` enforce the rest of this boundary; each takes a `packageOwnedPrimitives` option (`"exempt"` by default, `"lint"` to hold this package's own source to the same rule).
323
-
324
- When this package is read from an installed npm package, follow the [consumer setup guide](consuming-cross-repo.md) for setup and public import paths. The styling invariant above remains the authority for component implementation.
325
-
326
- The public strict ESLint config enforces only properties that can be decided
327
- from one source file and the installed generated tokens. It does not infer
328
- that two JSX trees represent the same product pattern. Repeated patterns stay
329
- governed by a named owning component, typed variants or slots, and explicit
330
- package or Portal-kit imports. A new repeated pattern is consolidated at its
331
- owner and pinned with a focused source or rendering test; generic JSX
332
- similarity warnings are outside this contract because they cannot distinguish
333
- valid domain composition from a fork without false positives.
334
-
335
- ### Explanations belong to the content they explain
336
-
337
- Compose a card or settings section around a real task, collection, or form. Its
338
- header names that content and can explain scope or consequences; its footer
339
- can carry secondary guidance or the save action. Field help belongs beside the
340
- field. Do not create a paragraph-only card to conceal an orphan introduction.
341
- Portal placement is owned by its [context contract](../../../apps/portal/docs/information-architecture.md#context-breadcrumbs-headings-and-labels);
342
- `DataTable.heading`, `CardHeader` and `SettingsSection` are its composition
343
- seams. Empty states and operational notices remain distinct message owners.
344
-
345
- ### Command popup composition
346
-
347
- `DropdownMenuContent` uses comfortable spacing by default. Its content-based
348
- width is bounded by Base UI's available width; collision padding keeps it off
349
- screen edges and available height allows vertical scrolling. A command popup
350
- uses solid `bg-popover` so underlying table text cannot compete with labels.
351
- Do not size command menus from an icon trigger. `DropdownMenuCommand` requires
352
- an `icon` element and owns the wrapping label and trailing, decorative icon slot.
353
- Use it for links, callbacks, native form buttons, and dialog triggers. It retains
354
- Menu.Item behavior; it is not a new positioning or focus implementation.
355
- `DropdownMenuItem iconOnly` remains available for the account-settings gear;
356
- checkbox and radio items retain their selection semantics.
357
-
358
- ### Account popup composition
359
-
360
- `DropdownMenuContent density="comfortable"` supplies roomier command rows
361
- through the shared owner. Account-menu consumers put the label first and the
362
- icon at the trailing edge. `MenuChoiceRow` owns a named inline preference row:
363
- the label reads at the leading edge and the exclusive choices sit together at
364
- the trailing edge as compact menu radio items, each carrying a glyph or a short
365
- code. `iconOnly` retains every radio's accessible name and checked state without
366
- a second checkmark, and selection keeps the menu open. `ThemeModeMenu` is the
367
- theme row built on it. The account menu shows two such rows, Theme and Language.
368
-
369
- The identity row names who is signed in and carries one `DropdownMenuItem
370
- iconOnly` gear at its trailing edge, linking to the account section; the menu
371
- does not repeat that section's pages as commands. Consumers bind preference
372
- persistence, routing, and localized labels.
373
-
374
- ## Object workspace compositions
375
-
376
- `ObjectIdentity`, `ObjectOverview`, `ObjectSection` and `ObjectFacts` compose the
377
- [shared IA patterns](../../design-system/PRODUCT_LANGUAGE.md#reusable-information-architecture)
378
- from existing Card and typography foundations. They own identity/action alignment,
379
- primary/supporting responsive columns, summary boundaries and semantic definition
380
- pairs. They take slots and values, never domain stores, URLs, permissions or save
381
- functions. Portal's agent workspace binds them to its scoped routes; Nadia may
382
- reuse the same presentation within its own shell. Do not ship a second complete
383
- Agent application inside this package.
384
-
385
- `ObjectIdentity.support` adds a small set of stable, labelled context facts to
386
- the identity header. The facts sit beside the identity when space allows and
387
- collapse behind the caller's localized disclosure label on compact screens.
388
- Operational warnings and changing report readings remain in the page body.
@@ -1,106 +0,0 @@
1
- # Icons as files
2
-
3
- `@nadicodeai/ui` draws icons as React components. The same icons also ship as
4
- plain SVG files, for a consumer that renders without React: a desktop shell, a
5
- CSS mask, a mail template, an `<img>` tag.
6
-
7
- Nothing in this set is drawn twice. Each brand file is its own committed
8
- component rendered once at build time, and the house icons come from the static
9
- set the design system generates, so a file and the component a page renders are
10
- the same shapes.
11
-
12
- ## Where they are
13
-
14
- | What | Path |
15
- | --- | --- |
16
- | Third-party brand marks | `@nadicodeai/ui/assets/brand/<slug>.svg` |
17
- | The index over them | `@nadicodeai/ui/assets/brand/index.json` |
18
- | The house's three icons | `@nadicodeai/design-system/icons/{arch,approvals,agent}.svg` |
19
- | Lucide, under the house render | `@nadicodeai/design-system/icons/<name>.svg` |
20
-
21
- `index.json` lists every brand mark, so the set's size is read from it rather
22
- than copied into prose.
23
-
24
- The house and Lucide icons live in the design system because that package owns
25
- the static icon set (DESIGN.md `## Iconography`). `@nadicodeai/ui` pins it
26
- exactly, so installing UI installs those files too. They are 24 px on Lucide's
27
- 24-unit grid, `fill="none"`, `stroke="currentColor"`, drawn with the house line
28
- weight held on screen by `vector-effect="non-scaling-stroke"`.
29
-
30
- A brand mark is an identity image, not a UI icon: it carries a third party's
31
- artwork and is exempt from the stroke system. Each file is 24 px square, keeps
32
- the brand's own colours, and names itself with `data-brand="<slug>"`. A mark the
33
- set draws as one silhouette paints with `currentColor` instead, so it stays
34
- visible on a dark surface; `index.json` says which marks those are. The files
35
- carry no `aria-*` attributes: whoever places a mark labels it, or leaves it
36
- decorative beside the text that names it.
37
-
38
- ## Resolving an app name
39
-
40
- `index.json` is the set as data:
41
-
42
- ```json
43
- {
44
- "marks": [
45
- {
46
- "slug": "google-calendar",
47
- "name": "Google Calendar",
48
- "set": "app",
49
- "mono": false,
50
- "file": "google-calendar.svg",
51
- "aliases": ["googlecalendar"]
52
- }
53
- ]
54
- }
55
- ```
56
-
57
- `set` says which allow-list the artwork was listed in: `platform`, `app`, or
58
- `model`. `mono` is the silhouette rule above. `aliases` are the connector
59
- spellings `appMarkFor` resolves to that mark, already squashed to lowercase
60
- letters and digits.
61
-
62
- To resolve a connector name the way `AppMark` does, squash the name the same
63
- way and take an exact alias match; failing that, take the longest alias the
64
- squashed name contains, so `GitHub MCP` is GitHub and never a shorter
65
- accidental match. A name that matches nothing has no mark: show the app's
66
- initial, never an invented logo and never a fetched favicon.
67
-
68
- ```js
69
- import index from "@nadicodeai/ui/assets/brand/index.json" with { type: "json" };
70
-
71
- const aliases = new Map(
72
- index.marks.flatMap((mark) => mark.aliases.map((alias) => [alias, mark])),
73
- );
74
- const squash = (value) => value.toLowerCase().replace(/[^a-z0-9]/g, "");
75
-
76
- export function markFor(app) {
77
- const target = squash(app);
78
- if (!target) return undefined;
79
- if (aliases.has(target)) return aliases.get(target);
80
-
81
- const alias = [...aliases.keys()]
82
- .filter((key) => target.includes(key))
83
- .sort((a, b) => b.length - a.length)[0];
84
- return alias ? aliases.get(alias) : undefined;
85
- }
86
- ```
87
-
88
- The table both sides read is `src/components/app-mark-aliases.ts`. Adding a
89
- connector spelling there puts it in the next release's index.
90
-
91
- ## Keeping them true
92
-
93
- `scripts/build-icon-assets.mjs` writes `dist/assets/brand/` as a build step, from
94
- the built components and that alias table. There is nothing to regenerate by
95
- hand and nothing committed to review: a new mark arrives by the ordinary route,
96
- the allow-list in `scripts/build-brand-icons.mjs` and
97
- `npm run generate:brand-icons -w @nadicodeai/ui`, and the files follow the
98
- components it emits. `tests/guards/icon-assets.test.ts` rebuilds the set and
99
- fails when the shipped files are not what the components render, when the index
100
- does not name exactly the files beside it, or when an alias it publishes does
101
- not resolve the way `appMarkFor` does.
102
-
103
- ## Nadia
104
-
105
- Nadia's desktop reads these files by moving its exact pin on `@nadicodeai/ui` to
106
- the release that carries them; nothing else in the package changes for it.
@@ -1,75 +0,0 @@
1
- # Migrating to `@nadicodeai/ui` 1.0.0
2
-
3
- This release makes material hierarchy and Agent identity package-owned. It
4
- also publishes the previously held status-vocabulary break.
5
-
6
- ## Status
7
-
8
- `StatusTag` and `StateTag` are removed. Use `Badge` with one of its five
9
- semantic tones. The consuming app owns the status word, icon, and tone mapping.
10
-
11
- ```tsx
12
- import { Badge } from "@nadicodeai/ui/components/badge";
13
-
14
- <Badge variant="green">Ready</Badge>;
15
- ```
16
-
17
- The retired `.nc-status-tag`, `.nc-state-tag`, and `.nc-state-row` CSS classes
18
- have no replacement CSS API.
19
-
20
- ## Material and Card
21
-
22
- Import `@nadicodeai/ui/globals.css` once. It now composes
23
- `shadow-plugin@1.2.7`; consumers do not install its classes or override its
24
- ring.
25
-
26
- `Card` defaults to `surface="resting"`. Use `surface="raised"` only for work
27
- that is physically raised or selected, and `surface="plain"` for the explicit
28
- structural/auth/website escape. `variant` continues to describe interaction.
29
- Replace the removed `variant="borderless"` with `surface="plain"`.
30
-
31
- ```tsx
32
- <Card>Resting content</Card>
33
- <Card surface="raised">Selected work</Card>
34
- <Card surface="plain">Structural content</Card>
35
- ```
36
-
37
- Overlay primitives now own floating or modal elevation internally. Remove
38
- caller `border-*`, `ring-*`, `shadow-*`, and `smooth-shadow-*` appearance
39
- classes from their content roots.
40
-
41
- ## Empty and glass control
42
-
43
- Set Empty containment explicitly when nesting matters:
44
-
45
- ```tsx
46
- <Empty scope="region">No agents</Empty>
47
- <Card><Empty scope="card">No runs</Empty></Card>
48
- ```
49
-
50
- Use `GlassControl shape="control"` for compact controls and `shape="bar"` for
51
- full-width header chrome. Both float over legible content. Cards and modal
52
- primitives continue to contain page content and dialog bodies.
53
-
54
- ## Agent identity
55
-
56
- Agent identity is available from direct component subpaths. The
57
- `@nadicodeai/ui/agents` entry remains the client-only assistant conversation
58
- and runtime interface.
59
-
60
- ```tsx
61
- import { AgentCard } from "@nadicodeai/ui/components/agent-card";
62
- import { AgentVisual } from "@nadicodeai/ui/components/agent-visual";
63
-
64
- <AgentCard
65
- name={agent.name}
66
- job={agent.job}
67
- status={<Badge variant="blue">Running</Badge>}
68
- visual={<AgentVisual recipe={agent.recipe} presentation="card" />}
69
- />;
70
- ```
71
-
72
- Recipe assignment is app-owned and explicit. Do not infer it from a free-form
73
- role, hash an identifier, or randomize it. `AgentVisual` accepts only the five
74
- closed recipes and three presentations; raw shader controls are intentionally
75
- not public. `AgentGlyph` and `AgentRow` do not exist in the 1.0.0 API.