@nadicodeai/ui 10.0.2 → 12.1.0
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 +39 -24
- package/README.md +58 -124
- package/dist/agent-orb.d.ts +1 -1
- package/dist/agent-orb.d.ts.map +1 -1
- package/dist/assets/brand/index.json +1 -1
- package/dist/components/action-menu.js +1 -1
- package/dist/components/agent-card.d.ts.map +1 -1
- package/dist/components/agent-card.js +2 -4
- package/dist/components/agent-orb.d.ts +11 -5
- package/dist/components/agent-orb.d.ts.map +1 -1
- package/dist/components/agent-orb.js +8 -6
- package/dist/components/alert.js +3 -3
- package/dist/components/badge.js +6 -6
- package/dist/components/brand-field.d.ts +2 -0
- package/dist/components/brand-field.d.ts.map +1 -1
- package/dist/components/brand-field.js +3 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/button.d.ts.map +1 -1
- package/dist/components/button.js +4 -5
- package/dist/components/carousel.js +2 -2
- package/dist/components/chart-bar-horizontal.js +1 -1
- package/dist/components/chart.js +1 -1
- package/dist/components/command.js +1 -1
- package/dist/components/context-menu.js +1 -1
- package/dist/components/diff-count.js +1 -1
- package/dist/components/dropdown-menu.js +3 -3
- package/dist/components/footer.d.ts +15 -9
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +6 -27
- package/dist/components/item.js +1 -1
- package/dist/components/kpi-card.js +2 -2
- package/dist/components/label-system.d.ts +1 -5
- package/dist/components/label-system.d.ts.map +1 -1
- package/dist/components/label-system.js +2 -6
- package/dist/components/menu-choice-row.js +1 -1
- package/dist/components/menubar.js +1 -1
- package/dist/components/object-workspace.js +1 -1
- package/dist/components/progress.js +1 -1
- package/dist/components/scope-switcher.d.ts +24 -0
- package/dist/components/scope-switcher.d.ts.map +1 -0
- package/dist/components/scope-switcher.js +34 -0
- package/dist/components/settings-section.d.ts +2 -1
- package/dist/components/settings-section.d.ts.map +1 -1
- package/dist/components/settings-section.js +2 -2
- package/dist/components/status-dot.js +1 -1
- package/dist/components/status-row.js +1 -1
- package/dist/components/table.d.ts.map +1 -1
- package/dist/components/table.js +1 -1
- package/dist/components/tabs.d.ts +11 -5
- package/dist/components/tabs.d.ts.map +1 -1
- package/dist/components/tabs.js +37 -5
- package/dist/components/tooltip.js +1 -1
- package/dist/components/trend-badge.d.ts.map +1 -1
- package/dist/components/trend-badge.js +1 -2
- package/dist/eslint/index.js +4 -0
- package/dist/eslint/rules/jsx-class-utility.js +1 -1
- package/dist/eslint/rules/no-shadcn-appearance-override.js +3 -3
- package/dist/eslint/rules/one-control-height.js +94 -0
- package/dist/eslint/rules/settings-section-description.js +45 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/agent-orb-motion.d.ts +1 -0
- package/dist/internal/agent-orb-motion.d.ts.map +1 -1
- package/dist/internal/agent-orb-motion.js +6 -3
- package/dist/internal/agent-orb.d.ts +10 -2
- package/dist/internal/agent-orb.d.ts.map +1 -1
- package/dist/internal/agent-orb.js +19 -9
- package/dist/internal/assistant-ui/attachment.js +4 -4
- package/dist/internal/assistant-ui/follow-up-suggestions.js +1 -1
- package/dist/internal/assistant-ui/markdown-text.js +4 -4
- package/dist/internal/assistant-ui/reasoning.js +2 -2
- package/dist/internal/assistant-ui/thread.d.ts.map +1 -1
- package/dist/internal/assistant-ui/thread.js +11 -9
- package/dist/internal/assistant-ui/tool-group.js +2 -2
- package/dist/styles/components.css +9 -5
- package/dist/styles/globals.css +43 -0
- package/package.json +3 -4
- package/dist/components/handoff-banner.d.ts +0 -9
- package/dist/components/handoff-banner.d.ts.map +0 -1
- package/dist/components/handoff-banner.js +0 -6
- package/docs/activity.md +0 -113
- package/docs/agent-orb.md +0 -168
- package/docs/component-classification.md +0 -45
- package/docs/consuming-cross-repo.md +0 -116
- package/docs/contract.md +0 -388
- package/docs/icon-assets.md +0 -106
- package/docs/migration/1.0.0.md +0 -75
- package/docs/migration/10.0.0.md +0 -144
- package/docs/migration/2.0.0.md +0 -69
- package/docs/migration/6.0.0.md +0 -30
- package/docs/migration/9.0.0.md +0 -35
- package/docs/migration/base-ui-migration.md +0 -62
- package/docs/model-identity.md +0 -18
- package/docs/product-compositions.md +0 -96
- package/docs/product-sections.md +0 -200
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
# Consuming `@nadicodeai/ui` Cross-Repo
|
|
2
|
-
|
|
3
|
-
This is the contract for an external repo — the Nadia repo, sibling Nadia web,
|
|
4
|
-
desktop, or any consumer outside this monorepo — installing `@nadicodeai/ui`
|
|
5
|
-
from published npm rather than the workspace source.
|
|
6
|
-
|
|
7
|
-
## Version pinning
|
|
8
|
-
|
|
9
|
-
Pin the UI package to an exact version, never `latest` or a caret/tilde range:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm view @nadicodeai/ui version
|
|
13
|
-
npm install @nadicodeai/ui@<exact-version> react@^19 react-dom@^19 tailwindcss@^4 @tailwindcss/postcss@^4
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
`@nadicodeai/ui` pins and installs its `@nadicodeai/design-system` runtime
|
|
17
|
-
dependency exactly. A React consumer does not declare that dependency directly
|
|
18
|
-
just to use UI components or the UI stylesheet.
|
|
19
|
-
|
|
20
|
-
The package installs its Agent UI implementation exactly. Consumers import
|
|
21
|
-
presentation, runtime primitives, providers, hooks, and types through
|
|
22
|
-
`@nadicodeai/ui/agents`; they do not install or import assistant-ui packages
|
|
23
|
-
directly. A protocol-specific runtime adapter remains a direct consumer
|
|
24
|
-
dependency because the application owns transport and message state.
|
|
25
|
-
|
|
26
|
-
Declare `@nadicodeai/design-system` directly only when the app intentionally
|
|
27
|
-
uses a separate public design-system API, such as a font/logo asset or the
|
|
28
|
-
static CSS interface for a separate non-React surface. Pin that direct
|
|
29
|
-
dependency to the version required by the installed
|
|
30
|
-
UI release:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npm view @nadicodeai/ui@<exact-version> dependencies.@nadicodeai/design-system
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## CSS interface
|
|
37
|
-
|
|
38
|
-
Import one stylesheet once at the React app root:
|
|
39
|
-
|
|
40
|
-
```ts
|
|
41
|
-
import "@nadicodeai/ui/globals.css";
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
`@nadicodeai/ui/globals.css` is the complete React style interface. It composes
|
|
45
|
-
Tailwind v4, shadcn, and the complete design-system CSS module, and it declares
|
|
46
|
-
the four `nc-elevation-*` utilities over the generated elevation tokens.
|
|
47
|
-
Consumers do not add a second border or ring to package-owned elevated roots.
|
|
48
|
-
Do not import `@nadicodeai/design-system/css`, generated theme files, or CSS
|
|
49
|
-
partials beside it. The package declares its own built component sources; Tailwind's
|
|
50
|
-
normal automatic detection covers the consumer application. The app still owns
|
|
51
|
-
its PostCSS configuration, theme provider, and persisted mode preference.
|
|
52
|
-
|
|
53
|
-
That stylesheet also delivers the brand typeface. The design-system CSS module
|
|
54
|
-
declares the `@font-face` rules for Geist and Geist Mono and loads the packaged
|
|
55
|
-
woff2 files, so a consuming app must not add its own font loader for those two
|
|
56
|
-
families. A framework loader on top registers a second, differently sourced face
|
|
57
|
-
for the same family names, which downloads the fonts twice and leaves no defined
|
|
58
|
-
winner. Import files under `@nadicodeai/design-system/assets/fonts/*` only when
|
|
59
|
-
the consumer wants the faces without the CSS module; that is one of the
|
|
60
|
-
intentional direct-API cases described above.
|
|
61
|
-
|
|
62
|
-
## Strict ESLint interface
|
|
63
|
-
|
|
64
|
-
The package exports the same design-system rules used by this repository. Add
|
|
65
|
-
the flat config after the consumer's framework and TypeScript configs:
|
|
66
|
-
|
|
67
|
-
```js
|
|
68
|
-
import nadicodeai from "@nadicodeai/ui/eslint";
|
|
69
|
-
|
|
70
|
-
export default [
|
|
71
|
-
// Framework, parser, and TypeScript configs first.
|
|
72
|
-
...nadicodeai.configs["flat/strict"],
|
|
73
|
-
];
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Every rule in this config is an error. The config rejects generated `nc-*`
|
|
77
|
-
utilities that do not exist in the installed design-system release, arbitrary
|
|
78
|
-
radius values that exactly duplicate a generated token, source deep imports,
|
|
79
|
-
raw design values, native visible controls, and appearance overrides on shared
|
|
80
|
-
UI primitives. An arbitrary radius remains valid when no generated radius has
|
|
81
|
-
the same value. Consumers may compose domain-specific components from the
|
|
82
|
-
public UI package; the rules govern package boundaries and design values, not
|
|
83
|
-
the shape of a consumer's JSX tree.
|
|
84
|
-
|
|
85
|
-
## nadicode logo
|
|
86
|
-
|
|
87
|
-
React surfaces name the company with the wordmark from the UI package:
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
import { BrandWordmark } from "@nadicodeai/ui/components/brand";
|
|
91
|
-
|
|
92
|
-
<BrandWordmark alt="nadicode" />;
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Use `BrandMark`, the plain n, where the name does not fit. The n is cut from the
|
|
96
|
-
wordmark, so never place the two together. Static consumers that need files
|
|
97
|
-
instead of React declare the design-system dependency directly and use the
|
|
98
|
-
documented SVG or PNG package routes in `packages/design-system/README.md`.
|
|
99
|
-
|
|
100
|
-
## Base UI API breaks (migrating from Radix-era components)
|
|
101
|
-
|
|
102
|
-
`@nadicodeai/ui@0.5.0+` is Base UI only; the Radix-era compatibility layer is
|
|
103
|
-
gone. Full catalog and rationale: `docs/migration/base-ui-migration.md`. What
|
|
104
|
-
a consumer actually hits when porting call sites:
|
|
105
|
-
|
|
106
|
-
- No `asChild`. Use Base UI's `render` prop; when the rendered element isn't
|
|
107
|
-
a native `<button>`, also pass `nativeButton={false}`.
|
|
108
|
-
- Menu items (`DropdownMenuItem`, `ContextMenuItem`, `MenubarItem`) use
|
|
109
|
-
`onClick`, not `onSelect`.
|
|
110
|
-
- State attributes are `data-checked`/`data-unchecked` and
|
|
111
|
-
`data-open`/`data-closed`, not a single `data-state` value.
|
|
112
|
-
- `Accordion` and `ToggleGroup` values are always arrays — pass `multiple`
|
|
113
|
-
for multi-select instead of `type="single" | "multiple"`.
|
|
114
|
-
- `TooltipProvider` takes `delay`, not `delayDuration`.
|
|
115
|
-
- Disclosure panels (`CollapsibleContent`, etc.) take `keepMounted`, not
|
|
116
|
-
`forceMount`.
|
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.
|
package/docs/icon-assets.md
DELETED
|
@@ -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.
|