@nadicodeai/ui 9.2.1 → 10.0.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.
Files changed (216) hide show
  1. package/AGENTS.md +1 -0
  2. package/README.md +5 -5
  3. package/dist/agent-orb.d.ts +3 -0
  4. package/dist/agent-orb.d.ts.map +1 -0
  5. package/dist/agent-orb.js +2 -0
  6. package/dist/assets/brand/aionlabs.svg +2 -0
  7. package/dist/assets/brand/airtable.svg +2 -0
  8. package/dist/assets/brand/apple.svg +2 -0
  9. package/dist/assets/brand/arcee.svg +2 -0
  10. package/dist/assets/brand/asana.svg +2 -0
  11. package/dist/assets/brand/atlassian.svg +2 -0
  12. package/dist/assets/brand/baidu.svg +2 -0
  13. package/dist/assets/brand/bytedance.svg +2 -0
  14. package/dist/assets/brand/claude.svg +2 -0
  15. package/dist/assets/brand/cohere.svg +2 -0
  16. package/dist/assets/brand/datadog.svg +2 -0
  17. package/dist/assets/brand/deepseek.svg +2 -0
  18. package/dist/assets/brand/discord.svg +2 -0
  19. package/dist/assets/brand/dolphin.svg +2 -0
  20. package/dist/assets/brand/dotsstudio.svg +2 -0
  21. package/dist/assets/brand/figma.svg +2 -0
  22. package/dist/assets/brand/gemini.svg +2 -0
  23. package/dist/assets/brand/github.svg +2 -0
  24. package/dist/assets/brand/gitlab.svg +2 -0
  25. package/dist/assets/brand/gmail.svg +2 -0
  26. package/dist/assets/brand/google-calendar.svg +2 -0
  27. package/dist/assets/brand/google-drive.svg +2 -0
  28. package/dist/assets/brand/grok.svg +2 -0
  29. package/dist/assets/brand/huggingface.svg +2 -0
  30. package/dist/assets/brand/ibm.svg +2 -0
  31. package/dist/assets/brand/inception.svg +2 -0
  32. package/dist/assets/brand/inclusionai.svg +2 -0
  33. package/dist/assets/brand/index.json +778 -0
  34. package/dist/assets/brand/inference.svg +2 -0
  35. package/dist/assets/brand/intercom.svg +2 -0
  36. package/dist/assets/brand/jira.svg +2 -0
  37. package/dist/assets/brand/kwaipilot.svg +2 -0
  38. package/dist/assets/brand/linear.svg +2 -0
  39. package/dist/assets/brand/linux.svg +2 -0
  40. package/dist/assets/brand/liquid.svg +2 -0
  41. package/dist/assets/brand/longcat.svg +2 -0
  42. package/dist/assets/brand/mattermost.svg +2 -0
  43. package/dist/assets/brand/meta.svg +2 -0
  44. package/dist/assets/brand/microsoft.svg +2 -0
  45. package/dist/assets/brand/minimax.svg +2 -0
  46. package/dist/assets/brand/mistral.svg +2 -0
  47. package/dist/assets/brand/moonshot.svg +2 -0
  48. package/dist/assets/brand/morph.svg +2 -0
  49. package/dist/assets/brand/n8n.svg +2 -0
  50. package/dist/assets/brand/netlify.svg +2 -0
  51. package/dist/assets/brand/nexagi.svg +2 -0
  52. package/dist/assets/brand/notion.svg +2 -0
  53. package/dist/assets/brand/nousresearch.svg +2 -0
  54. package/dist/assets/brand/nova.svg +2 -0
  55. package/dist/assets/brand/nvidia.svg +2 -0
  56. package/dist/assets/brand/openai.svg +2 -0
  57. package/dist/assets/brand/openrouter.svg +2 -0
  58. package/dist/assets/brand/paypal.svg +2 -0
  59. package/dist/assets/brand/perceptron.svg +2 -0
  60. package/dist/assets/brand/perplexity.svg +2 -0
  61. package/dist/assets/brand/poolside.svg +2 -0
  62. package/dist/assets/brand/postgresql.svg +2 -0
  63. package/dist/assets/brand/qwen.svg +2 -0
  64. package/dist/assets/brand/reka.svg +2 -0
  65. package/dist/assets/brand/relace.svg +2 -0
  66. package/dist/assets/brand/sakana.svg +2 -0
  67. package/dist/assets/brand/sentry.svg +2 -0
  68. package/dist/assets/brand/signal.svg +2 -0
  69. package/dist/assets/brand/slack.svg +2 -0
  70. package/dist/assets/brand/spotify.svg +2 -0
  71. package/dist/assets/brand/square.svg +2 -0
  72. package/dist/assets/brand/stepfun.svg +2 -0
  73. package/dist/assets/brand/stripe.svg +2 -0
  74. package/dist/assets/brand/supabase.svg +2 -0
  75. package/dist/assets/brand/telegram.svg +2 -0
  76. package/dist/assets/brand/tencent.svg +2 -0
  77. package/dist/assets/brand/the-drummer.svg +2 -0
  78. package/dist/assets/brand/thinkingmachines.svg +2 -0
  79. package/dist/assets/brand/todoist.svg +2 -0
  80. package/dist/assets/brand/unbiased.svg +2 -0
  81. package/dist/assets/brand/unreal-engine.svg +2 -0
  82. package/dist/assets/brand/upstage.svg +2 -0
  83. package/dist/assets/brand/vercel.svg +2 -0
  84. package/dist/assets/brand/webflow.svg +2 -0
  85. package/dist/assets/brand/whatsapp.svg +2 -0
  86. package/dist/assets/brand/windows.svg +2 -0
  87. package/dist/assets/brand/writer.svg +2 -0
  88. package/dist/assets/brand/xiaomimimo.svg +2 -0
  89. package/dist/assets/brand/youtube.svg +2 -0
  90. package/dist/assets/brand/yuanbao.svg +2 -0
  91. package/dist/assets/brand/zai.svg +2 -0
  92. package/dist/assets/brand/zapier.svg +2 -0
  93. package/dist/components/action-band.js +1 -1
  94. package/dist/components/action-status.d.ts +5 -0
  95. package/dist/components/action-status.d.ts.map +1 -1
  96. package/dist/components/action-status.js +6 -2
  97. package/dist/components/agent-orb-appearance.d.ts +16 -2
  98. package/dist/components/agent-orb-appearance.d.ts.map +1 -1
  99. package/dist/components/agent-orb-appearance.js +28 -5
  100. package/dist/components/agent-orb.d.ts +6 -3
  101. package/dist/components/agent-orb.d.ts.map +1 -1
  102. package/dist/components/agent-orb.js +20 -7
  103. package/dist/components/app-mark-aliases.d.ts +16 -0
  104. package/dist/components/app-mark-aliases.d.ts.map +1 -0
  105. package/dist/components/app-mark-aliases.js +84 -0
  106. package/dist/components/app-mark.d.ts +4 -0
  107. package/dist/components/app-mark.d.ts.map +1 -1
  108. package/dist/components/app-mark.js +14 -31
  109. package/dist/components/automation-section.d.ts.map +1 -1
  110. package/dist/components/automation-section.js +2 -2
  111. package/dist/components/badge.js +1 -1
  112. package/dist/components/brand.d.ts +3 -2
  113. package/dist/components/brand.d.ts.map +1 -1
  114. package/dist/components/brand.js +18 -28
  115. package/dist/components/button.js +1 -1
  116. package/dist/components/code-editor-mockup.d.ts +1 -1
  117. package/dist/components/code-editor-mockup.js +1 -1
  118. package/dist/components/collection-filters.js +2 -2
  119. package/dist/components/confirm-dialog.d.ts +3 -1
  120. package/dist/components/confirm-dialog.d.ts.map +1 -1
  121. package/dist/components/confirm-dialog.js +3 -3
  122. package/dist/components/drawer.js +5 -5
  123. package/dist/components/feature-grid.js +2 -2
  124. package/dist/components/feedback-state.d.ts.map +1 -1
  125. package/dist/components/feedback-state.js +6 -9
  126. package/dist/components/footer.js +2 -2
  127. package/dist/components/hero.js +1 -1
  128. package/dist/components/house-icons.d.ts.map +1 -1
  129. package/dist/components/house-icons.js +9 -1
  130. package/dist/components/input-otp.js +1 -1
  131. package/dist/components/installer-shell.d.ts.map +1 -1
  132. package/dist/components/installer-shell.js +5 -6
  133. package/dist/components/logo-band.js +1 -1
  134. package/dist/components/navigation-menu.js +2 -2
  135. package/dist/components/pending-label.d.ts +15 -0
  136. package/dist/components/pending-label.d.ts.map +1 -0
  137. package/dist/components/pending-label.js +14 -0
  138. package/dist/components/proof-slot.js +1 -1
  139. package/dist/components/section-intro.js +2 -2
  140. package/dist/components/security-grid.js +1 -1
  141. package/dist/components/sheet.js +1 -1
  142. package/dist/components/shift-band.js +1 -1
  143. package/dist/components/skeleton.d.ts +4 -0
  144. package/dist/components/skeleton.d.ts.map +1 -1
  145. package/dist/components/skeleton.js +5 -1
  146. package/dist/components/sonner.d.ts.map +1 -1
  147. package/dist/components/sonner.js +5 -2
  148. package/dist/components/statement.js +2 -2
  149. package/dist/components/steps.js +2 -2
  150. package/dist/components/table.js +1 -1
  151. package/dist/components/tabs.js +1 -1
  152. package/dist/components/texture-strip.js +1 -1
  153. package/dist/components/toggle.js +1 -1
  154. package/dist/components/waiting-line.d.ts +30 -0
  155. package/dist/components/waiting-line.d.ts.map +1 -0
  156. package/dist/components/waiting-line.js +33 -0
  157. package/dist/eslint/rules/no-invalid-token-utility.js +1 -1
  158. package/dist/index.d.ts +2 -8
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +2 -8
  161. package/dist/internal/agent-orb-motion.d.ts +9 -1
  162. package/dist/internal/agent-orb-motion.d.ts.map +1 -1
  163. package/dist/internal/agent-orb-motion.js +23 -11
  164. package/dist/internal/agent-orb.d.ts +14 -1
  165. package/dist/internal/agent-orb.d.ts.map +1 -1
  166. package/dist/internal/agent-orb.js +78 -22
  167. package/dist/internal/assistant-ui/attachment.d.ts.map +1 -1
  168. package/dist/internal/assistant-ui/attachment.js +3 -2
  169. package/dist/internal/assistant-ui/reasoning.d.ts.map +1 -1
  170. package/dist/internal/assistant-ui/reasoning.js +4 -4
  171. package/dist/internal/assistant-ui/tool-fallback.d.ts.map +1 -1
  172. package/dist/internal/assistant-ui/tool-fallback.js +5 -4
  173. package/dist/internal/assistant-ui/tool-group.d.ts.map +1 -1
  174. package/dist/internal/assistant-ui/tool-group.js +4 -3
  175. package/dist/lib/motion.d.ts +18 -4
  176. package/dist/lib/motion.d.ts.map +1 -1
  177. package/dist/lib/motion.js +18 -4
  178. package/dist/styles/globals.css +0 -1
  179. package/docs/activity.md +99 -119
  180. package/docs/agent-orb.md +71 -20
  181. package/docs/component-classification.md +4 -4
  182. package/docs/consuming-cross-repo.md +8 -10
  183. package/docs/contract.md +10 -9
  184. package/docs/icon-assets.md +106 -0
  185. package/docs/migration/10.0.0.md +144 -0
  186. package/package.json +8 -7
  187. package/dist/activity.d.ts +0 -2
  188. package/dist/activity.d.ts.map +0 -1
  189. package/dist/activity.js +0 -2
  190. package/dist/components/activity-orb.d.ts +0 -21
  191. package/dist/components/activity-orb.d.ts.map +0 -1
  192. package/dist/components/activity-orb.js +0 -26
  193. package/dist/components/benefits-bento.d.ts +0 -15
  194. package/dist/components/benefits-bento.d.ts.map +0 -1
  195. package/dist/components/benefits-bento.js +0 -6
  196. package/dist/components/bento.d.ts +0 -39
  197. package/dist/components/bento.d.ts.map +0 -1
  198. package/dist/components/bento.js +0 -70
  199. package/dist/components/glyph-spinner.d.ts +0 -10
  200. package/dist/components/glyph-spinner.d.ts.map +0 -1
  201. package/dist/components/glyph-spinner.js +0 -101
  202. package/dist/components/nav-bar.d.ts +0 -24
  203. package/dist/components/nav-bar.d.ts.map +0 -1
  204. package/dist/components/nav-bar.js +0 -35
  205. package/dist/components/page-frame.d.ts +0 -30
  206. package/dist/components/page-frame.d.ts.map +0 -1
  207. package/dist/components/page-frame.js +0 -30
  208. package/dist/components/spinner.d.ts +0 -3
  209. package/dist/components/spinner.d.ts.map +0 -1
  210. package/dist/components/spinner.js +0 -7
  211. package/dist/components/use-case-bento.d.ts +0 -13
  212. package/dist/components/use-case-bento.d.ts.map +0 -1
  213. package/dist/components/use-case-bento.js +0 -8
  214. package/dist/internal/activity-orb.d.ts +0 -27
  215. package/dist/internal/activity-orb.d.ts.map +0 -1
  216. package/dist/internal/activity-orb.js +0 -150
package/docs/activity.md CHANGED
@@ -1,133 +1,113 @@
1
1
  # System activity
2
2
 
3
- **Target state** ([the nadicode design system ADR](../../../docs/adr/2026-09-23-the-nadicode-design-system.md), #2261): this family retires. Who does the work decides how it looks: an Agent's work shows that Agent's orb and a sentence; Nadia connecting is her ribbon, working; system work under about a second is a still pending label on the control; beyond a second one 2 px ink waiting line moves linearly on the `wait` token; measurable work is a progress line with a written count; page loads show still placeholders. An orb means an Agent, so an orb for system work is the confusion this change removes. Until #2261, #2262 and #2263 land, this page describes what is built.
4
-
5
- The system activity family has three motions. They describe ongoing system
6
- operations across the website, Portal and Nadia. They do not represent an
7
- Agent's identity, attention or personality. The separate [AgentOrb](agent-orb.md)
8
- uses seven of the same upstream geometries for persistent identity, with one
9
- chosen pigment instead of the system activity palette.
10
-
11
- | Activity | Use when | Library motion |
12
- | --- | --- | --- |
13
- | `loading` | Retrieving or revealing content: opening a workspace, fetching records, refreshing a view, retrieving search results. | `composing`: flowing dotted bands wrap around a globe, shown as “Thinking…” in the library's large demo. |
14
- | `connecting` | Actively establishing or restoring communication with an account, service or device. | `connecting`: links form between nodes and carry signals. |
15
- | `processing` | Carrying out an operation: saving, submitting, applying changes, validating, importing, preparing an export, reconciling a sync, or other unmeasured work. | `working`: particles travel on tilted orbits. |
16
-
17
- These are three NadicodeAI activity treatments, not a claim that every design
18
- system defines these categories. The label names the specific operation;
19
- people are never expected to decode particle movement alone.
20
-
21
- ## Choose the feedback
22
-
23
- An operation's purpose selects the orb; the endpoint, worker or request type
24
- does not. Retrieving search results is `loading`. Establishing the connection
25
- before that request is `connecting`. A working connection remains a settled
26
- status while data loads. Synchronizing records after connection is
27
- `processing`; reconnecting during sync temporarily uses `connecting`.
28
-
29
- Saving is `processing`, not a fourth activity. Uploading, downloading,
30
- installing and converting use the existing `Progress` component when the
31
- completed amount can be measured. For an unmeasured preparation phase, use
32
- `processing`. When progress will become measurable, reserve a progress
33
- treatment from the start so the transition does not move the layout. Never
34
- invent a percentage or surround an indeterminate orb with a simulated fill.
35
-
36
- Queued, paused, offline, blocked, successful and failed conditions use the
37
- existing status or action-feedback treatment. A request waiting for a person
38
- does not keep an orb moving. A retry uses `connecting` only while an actual
39
- connection attempt is in progress; its failure or recovery replaces activity
40
- with the appropriate feedback. An account being active is not an activity.
41
-
42
- Use the indicator beside the operation it describes. Keep the rest of the
43
- interface available. One operation gets one indicator; separately progressing
44
- operations can each have one. For content with a predictable layout, prefer
45
- `Skeleton` or retained content over replacing the region with an orb. Do not
46
- show both a skeleton and a central orb for the same wait.
47
-
48
- ## Brand treatment
49
-
50
- All three reuse `thinking-orbs` 0.3.1 (MIT). Their geometry, depth ordering and
51
- size-specific density remain the library's. NadicodeAI adapts the palette and
52
- clock, not the rendering engine.
53
-
54
- The structure inherits the surrounding text's neutral ink. Foreground and
55
- signal particles carry the existing cobalto `--nc-info` role, mixed with that
56
- ink to preserve contrast in either appearance. Depth fades into the actual
57
- surface through transparency. There are no activity-specific colours: cobalto
58
- means work underway throughout the family. Green, amber and red retain their
59
- settled feedback meanings. Controls and panel backgrounds remain neutral.
60
-
61
- The selected clock multipliers are 0.92 for loading, 0.86 for connecting and
62
- 0.92 for processing, applied to each original size-specific preset. The
63
- connection animation is slightly quieter because its forming links add
64
- movement as well as its travelling particles. The implementation owns these
65
- values together; consumers cannot tune speed, density or particle colours.
66
-
67
- Use 20 CSS pixels inline, including buttons and rows with room for that size;
68
- 64 for a contained region; 88 on an application's entry screen. The 20 preset is
69
- separately tuned, and must not be obtained by shrinking the 64 preset. The 88
70
- extent uses the 64 preset. A 12–16 pixel badge slot keeps its existing compact
71
- spinner until its surrounding component explicitly accommodates this family.
72
- Nadia's desktop accommodates it once, in the spinner itself: the spinner's box
73
- stays one text line tall and at least 20 pixels wide, and the 20 pixel orb sits
74
- centred in it and overhangs it above and below, so a row keeps its height and
75
- the orb never meets the text beside it.
76
-
77
- ## React
3
+ Who does the work decides how it looks.
78
4
 
79
- ```tsx
80
- import { ActivityOrb } from "@nadicodeai/ui/components/activity-orb";
5
+ An Agent's work shows that Agent's orb in its state and a sentence saying what
6
+ it is doing; [AgentOrb](agent-orb.md) owns it, and Nadia connecting is her
7
+ ribbon, working. Everything on this page is the *system's* own work: a save, a
8
+ sign-in, a refresh, a page load. An orb means an Agent, so system work never
9
+ takes one.
81
10
 
82
- <ActivityOrb activity="loading" label="Caricamento progetti" size={64} />;
11
+ | The wait | What it shows |
12
+ | --- | --- |
13
+ | Under about a second | The control's own label changes to its pending word. Nothing else. |
14
+ | Past a second | One `WaitingLine` beside the work, and nothing else moves. |
15
+ | Measurable | `Progress` with a written count ("120 di 240 fatture"). |
16
+ | A page load | `Skeleton` placeholders in the shape of the content, still. |
83
17
 
84
- <div role="status">
85
- <ActivityOrb activity="connecting" decorative />
86
- <span>Connessione all’account…</span>
87
- </div>;
18
+ One wait gets one indicator. Separately progressing operations may each have
19
+ one. A request waiting for a person is not a wait: queued, paused, offline,
20
+ blocked, successful and failed conditions take the status or feedback
21
+ treatment. An account being active is not an activity.
22
+
23
+ ## The pending label
24
+
25
+ The control keeps its place, its size and its icon; only its word changes, and
26
+ the caller marks the control `disabled` and `aria-busy` so the change is
27
+ announced as well as read.
28
+
29
+ ```tsx
30
+ import { PendingLabel } from "@nadicodeai/ui/components/pending-label";
88
31
 
89
- <Button disabled={saving} aria-busy={saving}>
90
- {saving ? <ActivityOrb activity="processing" decorative /> : null}
91
- Salva modifiche
32
+ <Button aria-busy={saving || undefined} disabled={saving} type="submit">
33
+ <PendingLabel pending={saving} pendingLabel="Salvataggio…">
34
+ Salva modifiche
35
+ </PendingLabel>
92
36
  </Button>;
93
37
  ```
94
38
 
95
- `activity` is required. `size` defaults to 20. A meaningful standalone orb
96
- requires `label`; `decorative` removes its accessible name when the adjacent
97
- text or control already supplies the meaning. `paused` freezes the current
98
- frame for a user-controlled motion pause or a design specimen. It does not
99
- mean the underlying operation is paused. Product code unmounts the orb when
100
- the operation ends or cannot proceed.
101
-
102
- Nadia's initial HTML can use `mountActivityOrb(canvas, activity, size)` from
103
- `@nadicodeai/ui/activity` before React loads. It is the renderer used by
104
- `ActivityOrb`, with the same motion, palette and accessibility requirements.
105
- The caller supplies the canvas and adjacent label, and calls `destroy()` when
106
- the operation fails or React replaces the startup surface.
107
-
108
- Import `@nadicodeai/ui/globals.css` as described in the
109
- [consumer guide](consuming-cross-repo.md). The renderer inherits actual CSS
110
- ink and reads canonical colour tokens. Nadia's entry screen wraps this renderer
111
- in its own `Orb`, which fixes a dark appearance the shared renderer knows
112
- nothing about; that wrapper lives in the desktop, not here.
113
-
114
- ## Accessibility and runtime
115
-
116
- Reduced motion holds one representative frame and makes no animation requests.
117
- A preference change takes effect while the component is mounted. The
118
- operation's readable label remains. Pausing and resuming preserves the phase.
119
- Offscreen or hidden activity stops rendering. Removing a component removes
120
- its animation request and observers. DPR is capped at two. The renderer uses
121
- Canvas 2D and requires no WebGL; without a 2D context, the accessible meaning
122
- remains and the surrounding operation stays usable.
39
+ `pending` and `pendingLabel` are required; the children are the idle label. It
40
+ renders a `span` carrying `data-slot="pending-label"` and, while pending,
41
+ `data-pending="true"`. It adds no mark of its own: a control that had an icon
42
+ keeps that icon, unchanged, throughout the wait.
123
43
 
124
- ## Ownership and source
44
+ ## The waiting line
45
+
46
+ A 2 px track of ink with a 32% segment crossing it linearly on the `wait`
47
+ token, after one second of waiting. Before that second the track is there and
48
+ empty, unannounced, so the line arriving moves nothing around it; a wait the
49
+ person barely notices does not deserve movement, and a control that flashed an
50
+ indicator for 200 ms reads as a glitch.
51
+
52
+ ```tsx
53
+ import { WaitingLine } from "@nadicodeai/ui/components/waiting-line";
54
+
55
+ <WaitingLine label="Caricamento delle fatture" />;
56
+
57
+ <div>
58
+ <p>Stiamo preparando il documento.</p>
59
+ <WaitingLine decorative />
60
+ </div>;
61
+ ```
62
+
63
+ A meaningful standalone line takes `label` and announces itself as a status;
64
+ `decorative` removes its accessible name when the text beside it already says
65
+ what is happening. Width comes from the caller (`className="w-24"`, or the
66
+ full width of the region it belongs to); height, ink, segment and timing are
67
+ the component's and are not tunable. The one-second delay is
68
+ `WAITING_LINE_DELAY_MS`, exported so a test can name it rather than guess it.
69
+
70
+ Reduced motion drops the travel and breathes the full line in opacity, so a
71
+ wait still reads as a wait. The rule lives in
72
+ `packages/design-system/src/css/motion.css` as `.nc-anim-wait`, with the rest
73
+ of the motion vocabulary.
125
74
 
126
- The component owns rendering and the closed activity choices. Applications
127
- own requests, retries, progress measurements, copy, completion and recovery.
128
- Product-design guidance links here for selection and usage. The foundation's
129
- colour meanings remain in the design-system contract.
75
+ Product code unmounts the line when the operation ends or cannot proceed.
76
+
77
+ ## Measurable and still
78
+
79
+ `Progress` carries work whose completed amount is known, always beside a
80
+ written count. When work will become measurable, reserve the progress
81
+ treatment from the start so the transition does not move the layout. Never
82
+ invent a percentage.
83
+
84
+ `Skeleton` is a still placeholder shaped like the content behind it. It does
85
+ not pulse. Do not show a skeleton and a waiting line for the same wait: the
86
+ placeholders already say the page is arriving.
87
+
88
+ ## What retired
89
+
90
+ `ActivityOrb` and `mountActivityOrb`, `Spinner`, `GlyphSpinner`, Sonner's
91
+ spinning loading icon, the raw `LoaderCircle` spinners in `feedback-state`,
92
+ `installer-shell`, `automation-section` and `confirm-dialog`, the assistant-ui
93
+ attachment, tool-fallback and tool-group spinners, and the shimmer text on the
94
+ reasoning, tool-fallback and tool-group triggers. With them went the
95
+ `thinking-orbs` system-activity palette, the `tw-shimmer` dependency and the
96
+ duplicate `@utility shimmer`, and the unused `nc-anim-typing` and
97
+ `nc-anim-shimmer` hooks.
98
+
99
+ Where one of those marked a running state inside a component, the state is now
100
+ said in words and marked with a still glyph: an installer step and an
101
+ automation row take `CircleDotIcon`, and a running tool call says "Running
102
+ tool". A status badge never moves; its word and its mark carry the state.
103
+
104
+ ## Ownership and source
130
105
 
131
- The source selection follows the [library's public engine and presets](https://libraries.dev/orbs).
106
+ The components own the delay, the geometry, the ink and the motion. The
107
+ application owns the request, the retry, the copy, the completion and the
108
+ recovery. The decision behind this page is
109
+ [the nadicode design system ADR](../../../docs/adr/2026-09-23-the-nadicode-design-system.md);
110
+ the design-system side of it is
111
+ [`DESIGN.md`, "## System activity"](../../design-system/DESIGN.md#system-activity).
132
112
  The distinction between measured and unmeasured progress follows
133
113
  [Apple's progress guidance](https://developer.apple.com/design/human-interface-guidelines/progress-indicators).
package/docs/agent-orb.md CHANGED
@@ -16,7 +16,6 @@ uploads in Nadia is theirs, not an identity this package draws.
16
16
  | `braid` | Interwoven strands | `weaving` |
17
17
  | `morph` | Changing geometric contour | `shaping` |
18
18
 
19
- **Target state** ([the nadicode design system ADR](../../../docs/adr/2026-09-23-the-nadicode-design-system.md)): an idle orb holds still and every surface passes the Agent's real state, so only a working Agent moves (#2260); under reduced motion a thinking or working orb breathes in opacity instead of freezing (#2260); the colours a person may choose are cobalto, verde and arancio beside the inherited default, and a stored rosso draws arancio, because rosso equals the error red (#2269). Until those land, the rest of this page describes what is built.
20
19
 
21
20
  ## Nadia
22
21
 
@@ -38,16 +37,30 @@ strings, server-safe so a server can resolve a row before it renders:
38
37
  | Field | Stored as |
39
38
  | --- | --- |
40
39
  | `shape` | `orb:orbit`, `orb:ribbon`, `orb:lattice`, `orb:globe`, `orb:network`, `orb:braid`, `orb:morph` |
41
- | `color` | The design system's identity hex of cobalto, verde, rosso or arancio, or null |
40
+ | `color` | The design system's identity hex of cobalto, verde or arancio, or null; or a rosso an Agent chose before rosso left the picker |
42
41
 
43
42
  `resolveAgentOrb(stored, { root })` returns the form and colour to draw. The
44
43
  root Agent is Nadia and draws `nadia` whatever is stored. An unknown or empty
45
44
  shape draws `orbit`; an unknown or empty colour draws the inherited text
46
45
  colour, which is also the default a picker's reset restores. Paint a colour
47
- with `agentOrbColorTokens[color]`. The four colours clear 3:1 against both the
48
- white and the black canvas; giallo does not and is not offered. Each retired
49
- `forma:` spelling resolves to its own orb, so an Agent keeps a distinct look
50
- until someone changes it.
46
+ with `agentOrbColorTokens[color]`. Each retired `forma:` spelling resolves to
47
+ its own orb, so an Agent keeps a distinct look until someone changes it.
48
+
49
+ ## The three colours
50
+
51
+ A person chooses cobalto, verde or arancio, or nothing, which leaves the orb
52
+ the inherited text colour. All three clear 3:1 against both the white and the
53
+ black canvas, so a chosen orb stays visible in either theme; giallo does not
54
+ (1.6:1 on white) and is not offered.
55
+
56
+ Rosso is not offered either. It is exactly the error red, so a red Agent beside
57
+ a red "Bloccato" badge read as a broken Agent rather than a chosen colour.
58
+ Nadia keeps rosso in her own stripes, where it is one of four and says nothing
59
+ on its own. An Agent that chose rosso before it left the picker keeps a colour:
60
+ `resolveAgentOrb` maps a stored `rosso`, by name or by the `#d3302f` hex, to
61
+ arancio, the nearest hue that is not a status. That is a mapping on the way in,
62
+ not an offer on the way out — `agentOrbColors` is what a picker shows, and it
63
+ has three entries.
51
64
 
52
65
  The appearance is a person's choice, never derived: do not infer it from a name,
53
66
  an id hash or a role. A machine check-in is not Agent activity, so a surface that
@@ -62,6 +75,7 @@ import { AgentOrb } from "@nadicodeai/ui/components/agent-orb";
62
75
 
63
76
  <AgentOrb form="ribbon" color="var(--nc-identity-cobalto)" size={32} label="Research" />;
64
77
  <AgentOrb form="nadia" size={32} label="Nadia" />;
78
+ <AgentOrb form="globe" presence="over-picture" size={96} state="working" label="Ricerca" />;
65
79
  ```
66
80
 
67
81
  `form` is required. `color` accepts one CSS colour, including a customer-chosen
@@ -75,6 +89,15 @@ that single-colour identity, using the existing chart and warning tokens. Choose
75
89
  colour visible on the intended background. A deliberately black pigment on a
76
90
  black surface is not automatically recoloured.
77
91
 
92
+ A native startup screen paints an orb before React loads through
93
+ `mountAgentOrb(canvas, { form, size, presence, state, paused })` from
94
+ `@nadicodeai/ui/agent-orb`, the React-free entry beside
95
+ `@nadicodeai/ui/activity`. It is the renderer `AgentOrb` itself mounts, with
96
+ the same motion, states and presence; the caller supplies the canvas and the
97
+ adjacent name, and calls `destroy()` when React replaces the startup surface.
98
+ Import `@nadicodeai/ui/globals.css` with it: the renderer inherits the CSS ink
99
+ and reads the colour tokens from the element.
100
+
78
101
  Sizes are 20, 24, 32, 40, 64, 96 and 160 CSS pixels. Menu sizes through 32 use
79
102
  the upstream compact density; larger sizes use its detailed profile. `paused`
80
103
  holds the current frame and resumes without resetting the appearance. An
@@ -84,16 +107,42 @@ API because the stock React component only offers monochrome light/dark ink,
84
107
  not a configurable pigment.
85
108
 
86
109
  Motion is limited to 30 painted frames per second. Offscreen and hidden orbs
87
- stop scheduling animation. Reduced motion freezes the current frame and is
88
- observed live. Unmount removes the frame request and observers. DPR is capped
89
- at two. Without Canvas 2D, the accessible name remains available.
90
-
91
- `state` accepts idle, working, waiting, blocked, thinking and done. Geometry
92
- and state gestures have separate clocks: slowing a gesture must not make the
93
- idle form appear frozen. Each form has its own geometry pace, shared across
94
- avatar sizes; Morph holds a contour for about 1.6 seconds and changes it over
95
- one second at idle. Working and Thinking keep the form's base pace.
96
- Waiting holds its pose; Blocked makes one gentle interrupted effort and stops.
110
+ stop scheduling animation. Unmount removes the frame request and observers.
111
+ DPR is capped at two. Without Canvas 2D, the accessible name remains
112
+ available.
113
+
114
+ The engine paints the box the canvas occupies, not the `size` it was given: a
115
+ `ResizeObserver` follows the element, and the backing store and the geometry's
116
+ density profile both come from the measured CSS pixels. `size` sets that box
117
+ through `--nc-agent-orb-size` in a class, so a caller whose layout decides the
118
+ size overrides it with its own sizing class and the orb stays sharp at
119
+ whatever width it lands on. The declared size still stands in before layout
120
+ has measured anything, on a server render and in a test environment without
121
+ layout.
122
+
123
+ `presence` decides how loudly one orb reads on its ground, per mount, never
124
+ as a module setting: two orbs on one page can want different weights.
125
+ `plain`, the default, is the interface's own ink. `over-picture` multiplies
126
+ ink by 2.3 and every dot and line radius by 1.9, for an orb drawn on a
127
+ photograph, where the plain weights sink into the image. Nadia on open sky
128
+ keeps `plain`.
129
+
130
+ Reduced motion turns movement off and keeps opacity and colour. A thinking or
131
+ working orb breathes between full ink and 0.45 over 1.6 s, ease-in-out, so
132
+ busy still reads as busy; its geometry does not turn, and the state cue
133
+ breathes with the body. Every other state, idle, waiting, blocked and done
134
+ included, holds one still frame. The preference is observed live.
135
+
136
+ `state` accepts idle, working, waiting, blocked, thinking and done, and every
137
+ surface passes the Agent's real one: only Agents move on their own, so motion
138
+ on a screen means an Agent is working and a still orb means it is not. Idle
139
+ holds one still pose and schedules no frame after its first paint; a surface
140
+ that knows only check-ins draws idle, because a machine check-in is not Agent
141
+ activity. Geometry and state gestures have separate clocks. Each form has its
142
+ own geometry pace, shared across avatar sizes, which Working and Thinking
143
+ keep; Morph holds a contour for about 1.6 seconds and changes it over one
144
+ second. Waiting holds its pose; Blocked makes one gentle interrupted effort
145
+ and stops.
97
146
  V7 keeps the material free-standing and intact:
98
147
  no gear, thought bubble, pause, exclamation mark or check is cut into the form.
99
148
  Working and Thinking preserve the native geometry: do not stretch, flatten,
@@ -104,14 +153,16 @@ with sequential opacity and slight size pulses on a 3.6-second loop.
104
153
  The distinct symbols remain visible throughout both cycles. Done repeats
105
154
  the ribbon in three celebratory sweeps and then rests. Changing `motionKey`
106
155
  replays it. These exterior cues use the celebration palette; the body keeps
107
- its configured pigment. Reduced motion holds both cues still.
156
+ its configured pigment.
108
157
  No state has an enclosing circle. Keep a nearby status label where exact
109
158
  state identification matters, including reduced motion and compact sizes.
110
- Reduced motion holds the state pose and skips the celebration.
111
159
 
112
160
  In the Portal, [`agent-identity.tsx`](../../../apps/portal/src/components/agent-identity.tsx)
113
161
  draws every Agent, in its lists, Agent workspace and the sign-in, sign-up and
114
- Get Nadia pages. Inspect them in the running local Portal for compact and
115
- full-size appearances.
162
+ Get Nadia pages, and `src/lib/agent-orb.ts` maps what a machine reported to
163
+ the state drawn. It also gives an orb a `travelId`, which wraps it in React's
164
+ `<ViewTransition>` so the same orb travels from a list row into the Agent's
165
+ page; `apps/portal/docs/design-doctrine.md` owns that rule. Inspect them in
166
+ the running local Portal for compact and full-size appearances.
116
167
 
117
168
  [Upstream library and presets](https://libraries.dev/orbs), MIT licensed.
@@ -3,7 +3,7 @@
3
3
  This file records the ownership class for U4 product compositions. The package may render standard product UI, but consuming apps own app behavior, data, persistence, and product-specific promises.
4
4
 
5
5
  If first-party shadcn or assistant-ui registry source already covers the same
6
- generic product UI, do not add a parallel NadicodeAI package API. Use the
6
+ generic product UI, do not add a parallel nadicode package API. Use the
7
7
  registry item in the consuming app and adapt it there with app-owned data,
8
8
  copy, and behavior.
9
9
 
@@ -20,8 +20,8 @@ copy, and behavior.
20
20
  | Component | Classification | Built on | Required states | Package-owned accessibility | Fixture |
21
21
  | --- | --- | --- | --- | --- | --- |
22
22
  | `ActionMenu` | Canonical record-action composition. | shadcn `Button` and `DropdownMenu`. | closed, open, keyboard selection, focus return. | Required accessible trigger name; inherited menu mechanics. | `../src/components/action-menu.test.tsx` |
23
- | `SettingsSection` | NadicodeAI product composition. | shadcn Card slots, Separator, and FieldGroup. | header, field group, and divided action footer. | Section structure; consumer owns the form transaction. | `../src/components/settings-section.test.tsx` |
24
- | `SettingsField` | NadicodeAI product composition. | shadcn Field, FieldLabel, FieldDescription, and FieldError. | valid, invalid, required, and disabled field rows. | Render callback supplies the control's label and message associations in server and client components. | `../src/components/settings-section.test.tsx` |
23
+ | `SettingsSection` | nadicode product composition. | shadcn Card slots, Separator, and FieldGroup. | header, field group, and divided action footer. | Section structure; consumer owns the form transaction. | `../src/components/settings-section.test.tsx` |
24
+ | `SettingsField` | nadicode product composition. | shadcn Field, FieldLabel, FieldDescription, and FieldError. | valid, invalid, required, and disabled field rows. | Render callback supplies the control's label and message associations in server and client components. | `../src/components/settings-section.test.tsx` |
25
25
  | `FeedbackState` | Net-new central product state composition adapted from shadcn empty-state patterns. | shadcn `Empty` and lucide status icons. | `empty`, `loading`, `saving`, `error`, `permission`, `offline`, `no-results`, `fatal`. | `role="status"` for loading/saving; `role="alert"` for error, permission, offline, and fatal. | `../tests/guards/product-compositions.test.ts` |
26
26
 
27
27
  ## App-Owned Boundaries
@@ -36,7 +36,7 @@ Keep these out of `@nadicodeai/ui`:
36
36
 
37
37
  ## Fixture Guidance
38
38
 
39
- U4 fixtures should use NadicodeAI-relevant work objects:
39
+ U4 fixtures should use nadicode-relevant work objects:
40
40
 
41
41
  - briefing agents, review agents, approval queues, sessions, models, workspace defaults, files, pairing codes, and weekly approvals;
42
42
  - controlled state examples for sorting, selection, visibility, validation, loading, saving, offline, permission, and fatal states;
@@ -82,22 +82,20 @@ the same value. Consumers may compose domain-specific components from the
82
82
  public UI package; the rules govern package boundaries and design values, not
83
83
  the shape of a consumer's JSX tree.
84
84
 
85
- ## NadicodeAI logo
85
+ ## nadicode logo
86
86
 
87
- React surfaces that need both the mark and readable company name use the
88
- horizontal lockup from the UI package:
87
+ React surfaces name the company with the wordmark from the UI package:
89
88
 
90
89
  ```tsx
91
- import { BrandLockup } from "@nadicodeai/ui/components/brand";
90
+ import { BrandWordmark } from "@nadicodeai/ui/components/brand";
92
91
 
93
- <BrandLockup alt="NadicodeAI" />;
92
+ <BrandWordmark alt="nadicode" />;
94
93
  ```
95
94
 
96
- Use `BrandMark` where space is tight and `BrandWordmark` where the name must read
97
- by itself. Do not assemble a new lockup from those two components. Static
98
- consumers that need files instead of React declare the design-system dependency
99
- directly and use the documented SVG or PNG package routes in
100
- `packages/design-system/README.md`.
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`.
101
99
 
102
100
  ## Base UI API breaks (migrating from Radix-era components)
103
101
 
package/docs/contract.md CHANGED
@@ -7,7 +7,7 @@ The normative invariants for the `@nadicodeai/ui` package: what may live here, h
7
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
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
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 kept `nc-*` page grammar and display CSS components; they do not redefine frame math, marker placement, 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").
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
11
 
12
12
  ### Persistent notices
13
13
 
@@ -136,14 +136,15 @@ gone, is [`packages/design-system/DESIGN.md`, "## Status Vocabulary"](../../desi
136
136
 
137
137
  ## Package Shape Invariants
138
138
 
139
- System activity uses `ActivityOrb` for loading, connecting and processing;
140
- [activity.md](activity.md) owns selection, rendering and accessibility. It
141
- composes with status feedback and measured progress without adding outcome
142
- states to the orb.
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.
143
144
 
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.
145
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.
146
- - **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 NadicodeAI subpath, and the package does not rename, wrap, or rebuild assistant-ui behavior.
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.
147
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.
148
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).
149
150
 
@@ -152,7 +153,7 @@ states to the orb.
152
153
 
153
154
  ## Admission Rules
154
155
 
155
- 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`. 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.
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.
156
157
 
157
158
  ### Placement
158
159
 
@@ -177,8 +178,8 @@ product-domain `agent` object prop in the shared package.
177
178
 
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.
179
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.
180
- - **Reusable sections and compositions live here after proof.** Bento grids, media mounts, demo surfaces, page frames, section intros, CTA bands, proof slots, benefits grids, 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.
181
- - **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). `BrandLockup` supplies the approved horizontal lockup. `BrandMark` serves tight spaces and `BrandWordmark` serves placements where the name must read by itself. All three render the package's public neutral geometry seam with React-scoped SVG ids, 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 create another lockup rule.
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.
182
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.
183
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.
184
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".
@@ -0,0 +1,106 @@
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.