@nadicodeai/ui 4.0.5 → 5.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.
Files changed (42) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +4 -0
  3. package/dist/components/activity-orb.d.ts +21 -0
  4. package/dist/components/activity-orb.d.ts.map +1 -0
  5. package/dist/components/activity-orb.js +26 -0
  6. package/dist/components/threshold/canvas-budget.d.ts +10 -0
  7. package/dist/components/threshold/canvas-budget.d.ts.map +1 -0
  8. package/dist/components/threshold/canvas-budget.js +54 -0
  9. package/dist/components/threshold/index.d.ts +6 -0
  10. package/dist/components/threshold/index.d.ts.map +1 -0
  11. package/dist/components/threshold/index.js +5 -0
  12. package/dist/components/threshold/orb.d.ts +14 -0
  13. package/dist/components/threshold/orb.d.ts.map +1 -0
  14. package/dist/components/threshold/orb.js +17 -0
  15. package/dist/components/threshold/presence.d.ts +27 -0
  16. package/dist/components/threshold/presence.d.ts.map +1 -0
  17. package/dist/components/threshold/presence.js +159 -0
  18. package/dist/components/threshold/room.d.ts +33 -0
  19. package/dist/components/threshold/room.d.ts.map +1 -0
  20. package/dist/components/threshold/room.js +168 -0
  21. package/dist/components/threshold/threshold.css +228 -0
  22. package/dist/components/threshold/typed-line.d.ts +8 -0
  23. package/dist/components/threshold/typed-line.d.ts.map +1 -0
  24. package/dist/components/threshold/typed-line.js +43 -0
  25. package/dist/components/threshold/use-reduced-motion.d.ts +10 -0
  26. package/dist/components/threshold/use-reduced-motion.d.ts.map +1 -0
  27. package/dist/components/threshold/use-reduced-motion.js +32 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -0
  31. package/dist/internal/activity-orb.d.ts +27 -0
  32. package/dist/internal/activity-orb.d.ts.map +1 -0
  33. package/dist/internal/activity-orb.js +150 -0
  34. package/dist/internal/agent-visual/color.d.ts.map +1 -1
  35. package/dist/internal/agent-visual/color.js +13 -7
  36. package/dist/threshold.d.ts +2 -0
  37. package/dist/threshold.d.ts.map +1 -0
  38. package/dist/threshold.js +13 -0
  39. package/docs/activity.md +120 -0
  40. package/docs/contract.md +8 -2
  41. package/docs/threshold.md +221 -0
  42. package/package.json +8 -2
@@ -0,0 +1,120 @@
1
+ # System activity
2
+
3
+ The system activity family has three motions. They describe ongoing system
4
+ operations across the website, Portal and Nadia. They do not represent an
5
+ Agent's identity, attention or personality.
6
+
7
+ | Activity | Use when | Library motion |
8
+ | --- | --- | --- |
9
+ | `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. |
10
+ | `connecting` | Actively establishing or restoring communication with an account, service or device. | `connecting`: links form between nodes and carry signals. |
11
+ | `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. |
12
+
13
+ These are three NadicodeAI activity treatments, not a claim that every design
14
+ system defines these categories. The label names the specific operation;
15
+ people are never expected to decode particle movement alone.
16
+
17
+ ## Choose the feedback
18
+
19
+ An operation's purpose selects the orb; the endpoint, worker or request type
20
+ does not. Retrieving search results is `loading`. Establishing the connection
21
+ before that request is `connecting`. A working connection remains a settled
22
+ status while data loads. Synchronizing records after connection is
23
+ `processing`; reconnecting during sync temporarily uses `connecting`.
24
+
25
+ Saving is `processing`, not a fourth activity. Uploading, downloading,
26
+ installing and converting use the existing `Progress` component when the
27
+ completed amount can be measured. For an unmeasured preparation phase, use
28
+ `processing`. When progress will become measurable, reserve a progress
29
+ treatment from the start so the transition does not move the layout. Never
30
+ invent a percentage or surround an indeterminate orb with a simulated fill.
31
+
32
+ Queued, paused, offline, blocked, successful and failed conditions use the
33
+ existing status or action-feedback treatment. A request waiting for a person
34
+ does not keep an orb moving. A retry uses `connecting` only while an actual
35
+ connection attempt is in progress; its failure or recovery replaces activity
36
+ with the appropriate feedback. An account being active is not an activity.
37
+
38
+ Use the indicator beside the operation it describes. Keep the rest of the
39
+ interface available. One operation gets one indicator; separately progressing
40
+ operations can each have one. For content with a predictable layout, prefer
41
+ `Skeleton` or retained content over replacing the region with an orb. Do not
42
+ show both a skeleton and a central orb for the same wait.
43
+
44
+ ## Brand treatment
45
+
46
+ All three reuse `thinking-orbs` 0.3.1 (MIT). Their geometry, depth ordering and
47
+ size-specific density remain the library's. NadicodeAI adapts the palette and
48
+ clock, not the rendering engine.
49
+
50
+ The structure inherits the surrounding text's neutral ink. Foreground and
51
+ signal particles carry the existing cobalto `--nc-info` role, mixed with that
52
+ ink to preserve contrast in either appearance. Depth fades into the actual
53
+ surface through transparency. There are no activity-specific colours: cobalto
54
+ means work underway throughout the family. Green, amber and red retain their
55
+ settled feedback meanings. Controls and panel backgrounds remain neutral.
56
+
57
+ The selected clock multipliers are 0.92 for loading, 0.86 for connecting and
58
+ 0.92 for processing, applied to each original size-specific preset. The
59
+ connection animation is slightly quieter because its forming links add
60
+ movement as well as its travelling particles. The implementation owns these
61
+ values together; consumers cannot tune speed, density or particle colours.
62
+
63
+ Use 20 CSS pixels inline, including buttons and rows with room for that size;
64
+ 64 for a contained region; 88 for an application threshold. The 20 preset is
65
+ separately tuned, and must not be obtained by shrinking the 64 preset. The 88
66
+ extent uses the 64 preset. A 12–16 pixel badge slot keeps its existing compact
67
+ spinner until its surrounding component explicitly accommodates this family.
68
+
69
+ ## React
70
+
71
+ ```tsx
72
+ import { ActivityOrb } from "@nadicodeai/ui/components/activity-orb";
73
+
74
+ <ActivityOrb activity="loading" label="Caricamento progetti" size={64} />;
75
+
76
+ <div role="status">
77
+ <ActivityOrb activity="connecting" decorative />
78
+ <span>Connessione all’account…</span>
79
+ </div>;
80
+
81
+ <Button disabled={saving} aria-busy={saving}>
82
+ {saving ? <ActivityOrb activity="processing" decorative /> : null}
83
+ Salva modifiche
84
+ </Button>;
85
+ ```
86
+
87
+ `activity` is required. `size` defaults to 20. A meaningful standalone orb
88
+ requires `label`; `decorative` removes its accessible name when the adjacent
89
+ text or control already supplies the meaning. `paused` freezes the current
90
+ frame for a user-controlled motion pause or a design specimen. It does not
91
+ mean the underlying operation is paused. Product code unmounts the orb when
92
+ the operation ends or cannot proceed.
93
+
94
+ Import `@nadicodeai/ui/globals.css` as described in the
95
+ [consumer guide](consuming-cross-repo.md). The renderer inherits actual CSS
96
+ ink and reads canonical colour tokens. The existing `Orb` threshold adapter
97
+ keeps its public API: `working` maps to `processing`, `connecting` maps to
98
+ `connecting`, and `absent` mounts nothing. Its fixed dark appearance stays at
99
+ the threshold seam, outside the shared renderer.
100
+
101
+ ## Accessibility and runtime
102
+
103
+ Reduced motion holds one representative frame and makes no animation requests.
104
+ A preference change takes effect while the component is mounted. The
105
+ operation's readable label remains. Pausing and resuming preserves the phase.
106
+ Offscreen or hidden activity stops rendering. Removing a component removes
107
+ its animation request and observers. DPR is capped at two. The renderer uses
108
+ Canvas 2D and requires no WebGL; without a 2D context, the accessible meaning
109
+ remains and the surrounding operation stays usable.
110
+
111
+ ## Ownership and source
112
+
113
+ The component owns rendering and the closed activity choices. Applications
114
+ own requests, retries, progress measurements, copy, completion and recovery.
115
+ Product-design guidance links here for selection and usage. The foundation's
116
+ colour meanings remain in the design-system contract.
117
+
118
+ The source selection follows the [library's public engine and presets](https://libraries.dev/orbs).
119
+ The distinction between measured and unmeasured progress follows
120
+ [Apple's progress guidance](https://developer.apple.com/design/human-interface-guidelines/progress-indicators).
package/docs/contract.md CHANGED
@@ -6,7 +6,7 @@ The normative invariants for the `@nadicodeai/ui` package: what may live here, h
6
6
 
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
- - **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).
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). `@nadicodeai/ui/threshold.css` is the one stylesheet outside it, because the surface that needs it most loads no Tailwind layer at all: the Nadia desktop shell takes that file and the design system's token artifact and nothing else. A React caller imports it beside globals.css, never instead of it.
10
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").
11
11
 
12
12
  ### Material surfaces
@@ -92,6 +92,12 @@ gone, is [`packages/design-system/DESIGN.md`, "## Status Vocabulary"](../../desi
92
92
 
93
93
  ## Package Shape Invariants
94
94
 
95
+ System activity uses `ActivityOrb` for loading, connecting and processing;
96
+ [activity.md](activity.md) owns selection, rendering and accessibility. It
97
+ composes with status feedback and measured progress without adding outcome
98
+ states to the orb. The existing threshold `Orb` adapts its public states to
99
+ this same renderer.
100
+
95
101
  - **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.
96
102
  - **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.
97
103
  - **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.
@@ -101,7 +107,7 @@ gone, is [`packages/design-system/DESIGN.md`, "## Status Vocabulary"](../../desi
101
107
 
102
108
  ## Admission Rules
103
109
 
104
- Assistant conversation and runtime APIs enter through `@nadicodeai/ui/agents`; Agent identity enters through `@nadicodeai/ui/components/agent-card` and `@nadicodeai/ui/components/agent-visual`. 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, boot/install overlays, mascots, 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.
110
+ Assistant conversation and runtime APIs enter through `@nadicodeai/ui/agents`; Agent identity enters through `@nadicodeai/ui/components/agent-card` and `@nadicodeai/ui/components/agent-visual`. 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. `@nadicodeai/ui/threshold` is the plan-approved package surface for the room, Nadia's presence, the status orb and the typed line used by the Nadia desktop app. The screen, its copy and its state machine stay in the consuming app, and the entry ships no screen. Its contract is [`threshold.md`](threshold.md). A component left unconsumed after its target migration ships gets removed.
105
111
 
106
112
  ### Agent identity presentation
107
113
 
@@ -0,0 +1,221 @@
1
+ # The threshold primitives
2
+
3
+ `@nadicodeai/ui/threshold` holds the four primitives a threshold is drawn from. A
4
+ threshold is the moment before the product is usable: a first run, a sign-in, a
5
+ repair. The Nadia desktop app draws its room, presence, status orb and typed line
6
+ from here, so the brand logic has one home.
7
+
8
+ The entry ships no screen and no state machine. Which view is on, what the
9
+ words are, and when a threshold ends belong to the consuming app.
10
+
11
+ ```tsx
12
+ import { NadiaPresence, Orb, ThresholdRoom, TypedLine } from "@nadicodeai/ui/threshold";
13
+ import "@nadicodeai/ui/threshold.css";
14
+ ```
15
+
16
+ ## What each part is for
17
+
18
+ | Part | Its one job | Never |
19
+ | --- | --- | --- |
20
+ | `ThresholdRoom` | the place: a verde field on true black | inside the working app |
21
+ | `NadiaPresence` | the host: her arrival is the progress | a loader, a ring, a bar |
22
+ | `Orb` | status: we are working, or we wait on the Customer | on an error, or beside an actionable button |
23
+ | `TypedLine` | what Nadia is saying while you wait | a control or state machine |
24
+
25
+ The logo is not on this list. It belongs to window chrome, the tray and the
26
+ About row, never to a threshold.
27
+
28
+ ## The stylesheet
29
+
30
+ `@nadicodeai/ui/threshold.css` is a separate import rather than a part of
31
+ `globals.css`, because the surface that needs it most does not load Tailwind:
32
+ the Nadia desktop shell takes this file and the design-system token layer and
33
+ nothing else from this package. A Tailwind consumer imports it beside
34
+ `@nadicodeai/ui/globals.css`.
35
+
36
+ Both the stylesheet and the room need the design-system tokens above them, from
37
+ `@nadicodeai/ui/globals.css` or `@nadicodeai/design-system/css`. Without that
38
+ cascade there is no palette, and the room paints its still field rather than
39
+ guessing a colour.
40
+
41
+ ## `ThresholdRoom`
42
+
43
+ ```tsx
44
+ <div className="relative isolate overflow-hidden">
45
+ <ThresholdRoom energy={arriving ? 1 : 0} dimmed={repairing} ready={connected} />
46
+ {/* the presence and the words, above it */}
47
+ </div>
48
+ ```
49
+
50
+ | Prop | Type | Default | Meaning |
51
+ | --- | --- | --- | --- |
52
+ | `dimmed` | `boolean` | `false` | a repair or an error quiets the room to 22 percent |
53
+ | `energy` | `number` | `0` | how much of the room's pace runs, 0 to 1 |
54
+ | `ready` | `boolean` | `false` | success: the field takes verde vivo |
55
+ | `className` | `string` | — | added to the root |
56
+
57
+ The room paints absolutely inside its own relative, isolated parent and never
58
+ against the viewport, so a bounded surface can hold one without covering the
59
+ page. It is `aria-hidden`, takes no pointer events, and carries no words.
60
+
61
+ **The recipe.** Paper Shaders 0.0.78 `meshGradientFragmentShader`, its published
62
+ Ink preset adapted to two colours and slowed: `u_colors` are
63
+ `--nc-identity-verde` and `--nc-identity-ink`, `u_colorsCount` 2, `u_distortion`
64
+ 1, `u_swirl` 0.2, `u_rotation` 90, `u_grainMixer` and `u_grainOverlay` 0, fit
65
+ contain, scale 1. Mount speed is 0.22 at energy 1, starting frame 12000, minimum
66
+ pixel ratio 1, maximum pixel count 1,024,000, canvas opacity 0.65. The room is
67
+ masked with `radial-gradient(ellipse 65% 58% at 50% 35%, black 10%, transparent
68
+ 95%)`. The two colours are shader uniforms, never CSS.
69
+
70
+ `energy` is a scalar so a screen can decide. The captured recipe is energy 1;
71
+ Nadia's first run holds it there while she is still arriving and drops it to 0
72
+ when she has, which stills the field. Sign-in, success and every repair state
73
+ pass 0.
74
+
75
+ **One canvas per window.** While `NadiaPresence` is painting her arrival with
76
+ water, the room mounts nothing and shows this same still field; the moment the
77
+ water leaves, at reveal 1 or when it cannot paint, the room's canvas mounts.
78
+ The budget lives in the package, so a surface that composes both, as first run
79
+ does, keeps the register's one shader canvas without doing anything.
80
+
81
+ **No GPU, no tokens, or a lost context.** The room keeps its still field: a
82
+ token-built radial gradient that needs no WebGL. It carries `data-surface`,
83
+ `"shader"` while the canvas paints and `"still"` otherwise, and a mount that
84
+ fails leaves no dead canvas covering the fallback. A `webglcontextlost` event
85
+ returns it to the still field rather than to a black rectangle.
86
+
87
+ **Reduced motion.** Speed 0: the shader paints frame 12000 once and stops. The
88
+ room still has its colour and its shape; it just holds one frame.
89
+
90
+ ## `NadiaPresence`
91
+
92
+ ```tsx
93
+ <NadiaPresence label={t("install.progress")} reveal={fraction} src={portrait} state="arriving" />
94
+ ```
95
+
96
+ | Prop | Type | Meaning |
97
+ | --- | --- | --- |
98
+ | `label` | `string` | the accessible name of the arrival |
99
+ | `reveal` | `number` | 0 behind the field, 1 standing clear |
100
+ | `src` | `string` | the portrait master's URL |
101
+ | `state` | `"arriving" \| "present" \| "ready" \| "error"` | which threshold she is in |
102
+
103
+ `src` is a prop rather than an import because this package imports no binary
104
+ asset of its own; the consuming surface resolves
105
+ `@nadicodeai/design-system/assets/nadia-solid-surface` with its own bundler and
106
+ passes the URL. Keeping it out of the library is what lets a Node or a
107
+ bundler-free consumer load the module at all.
108
+
109
+ **The arrival.** Opacity is `0.025 + 0.975 × reveal^1.25`, exported as
110
+ `arrivalOpacity` so a screen can read the same curve. At the start she is a
111
+ trace, which gives the arrival somewhere to go. Her lower edge dissolves into
112
+ the room with `linear-gradient(black 55%, transparent 98%)` instead of ending on
113
+ a rectangle. The `error` state multiplies her by 0.62.
114
+
115
+ **The water.** While `state` is `arriving`, `reveal` is below 1 and motion is
116
+ allowed, the portrait is painted by Paper's `waterFragmentShader` at a
117
+ restrained intensity: `caustic`, `edges` and `waves` are `0.35`, `0.4` and `0.3`
118
+ scaled by how much of the arrival is left, with `layering` 0.4 and `highlights`
119
+ 0.12, `colorBack` fully transparent and `colorHighlight` on
120
+ `--nc-identity-verde`. Every amplitude reaches zero as she arrives, so her
121
+ geometry holds and the water is gone by the time she stands clear. At reveal 1,
122
+ and in every other state, this is the plain portrait.
123
+
124
+ While the water is wanted it is the window's one shader canvas: the presence
125
+ holds the canvas budget from the moment it asks for the water until the water
126
+ is torn down, and the room keeps its still field for exactly that long.
127
+
128
+ The water canvas is masked to her own silhouette. Water adds its highlight term
129
+ to the whole frame rather than only to the sampled image, so an unmasked canvas
130
+ paints a faintly lit rectangle over the room; the mask matches the shader's own
131
+ contain fit and centred origin.
132
+
133
+ **Reduced motion, no GPU, or an image that will not decode.** The plain
134
+ portrait, at the same reveal. The figure carries `data-surface`, `"water"` or
135
+ `"portrait"`.
136
+
137
+ **Success.** `state="ready"` runs one 360 ms verde vivo beat over her silhouette
138
+ on `--nc-ease-out-strong`, opacity 0 to 0.22 at 35 percent and back to 0, once.
139
+ Verde vivo is Nadia's presence accent and this is the only place a threshold
140
+ spends it, which is why the buttons beside it stay neutral. Reduced motion
141
+ suppresses the beat.
142
+
143
+ **Assistive technology.** A hidden `progressbar` carries `label` and the floored
144
+ percentage, so it reaches 100 only when the arrival is actually finished. The
145
+ portrait itself is decorative; the words are the typed line's.
146
+
147
+ ## `Orb`
148
+
149
+ ```tsx
150
+ <Orb label={t("install.working")} state="working" />
151
+ ```
152
+
153
+ | Prop | Type | Default | Meaning |
154
+ | --- | --- | --- | --- |
155
+ | `label` | `string` | — | what the orb means, in the Customer's language |
156
+ | `size` | `88 \| 64 \| 20` | `88` | CSS pixels: 88 at a threshold, 20 inside a running turn, per the living-elements register |
157
+ | `state` | `"working" \| "connecting" \| "absent"` | — | see below |
158
+
159
+ The threshold adapter preserves its public states: `working` selects the
160
+ shared `processing` activity, `connecting` selects `connecting`, and `absent`
161
+ renders nothing. The threshold defaults to fixed dark ink; `theme="auto"`
162
+ inherits the surrounding product's ink and `theme="light"` fixes light-surface
163
+ ink. There is one renderer, `ActivityOrb`, rather than a second threshold orb.
164
+
165
+ [System activity](activity.md) owns the motion recipes, colour treatment,
166
+ optical sizes, reduced-motion behaviour and connection-versus-person-waiting
167
+ rule. Applications retain their operation state machines and copy.
168
+
169
+ ## `TypedLine`
170
+
171
+ ```tsx
172
+ <TypedLine text={t("install.step.1")} />
173
+ ```
174
+
175
+ | Prop | Type | Default |
176
+ | --- | --- | --- |
177
+ | `cadenceMs` | `number` | `44` |
178
+ | `text` | `string` | — |
179
+ | `className` | `string` | — |
180
+
181
+ The line types itself at 44 ms per character, the cadence the rendered
182
+ comparisons selected for a first-run pause. Three things it does that a naive
183
+ typewriter does not:
184
+
185
+ - the count comes from elapsed time, not from how many callbacks arrived, so a
186
+ software renderer or a busy main thread cannot stretch a one second sentence
187
+ into five;
188
+ - a hidden copy of the whole sentence reserves its width, so the orb beside it
189
+ does not travel sideways with each character;
190
+ - the live region receives the complete sentence at once, so a screen reader is
191
+ never read one letter at a time.
192
+
193
+ A new `text` retypes from a cleared line. The caret is steady while typing and
194
+ hidden afterwards.
195
+
196
+ **Reduced motion.** The whole line at once, and no caret.
197
+
198
+ ## Tokens, and the one fixed presentation
199
+
200
+ Everything visual comes from design-system tokens. A threshold is a fixed dark
201
+ surface: it is true black in every theme, so the text ladder reads
202
+ `--nc-dark-ink`, `--nc-dark-body` and `--nc-dark-muted` directly instead of
203
+ branching on the app's mode. That is the fixed-presentation escape
204
+ [`contract.md`](contract.md) names, taken once and coherently for the whole
205
+ surface.
206
+
207
+ The room's two colours are the exception that proves it: they are shader
208
+ uniforms read off `--nc-identity-verde` and `--nc-identity-ink` at mount, not
209
+ CSS values.
210
+
211
+ ## Motion budget
212
+
213
+ The foundation's: `--nc-duration-open` for a room fading in or out,
214
+ `--nc-duration-confirm` for the presence, `--nc-ease-emphasized` and
215
+ `--nc-ease-out-strong`. The success beat is the one
216
+ duration written here rather than taken from a token, 360 ms, because it is an
217
+ envelope rather than a state change.
218
+
219
+ Under `prefers-reduced-motion: reduce` the room holds one frame, the water is
220
+ not mounted, the presence jumps to its value, the beat does not run, the orb
221
+ draws once and the line appears whole.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/ui",
3
- "version": "4.0.5",
3
+ "version": "5.1.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,6 +19,11 @@
19
19
  "types": "./dist/agents.d.ts",
20
20
  "import": "./dist/agents.js"
21
21
  },
22
+ "./threshold": {
23
+ "types": "./dist/threshold.d.ts",
24
+ "import": "./dist/threshold.js"
25
+ },
26
+ "./threshold.css": "./dist/components/threshold/threshold.css",
22
27
  "./hooks": {
23
28
  "types": "./dist/hooks/index.d.ts",
24
29
  "import": "./dist/hooks/index.js"
@@ -61,7 +66,7 @@
61
66
  "@assistant-ui/react": "0.15.1",
62
67
  "@assistant-ui/react-markdown": "0.14.8",
63
68
  "@base-ui/react": "^1.6.0",
64
- "@nadicodeai/design-system": "4.0.5",
69
+ "@nadicodeai/design-system": "5.1.0",
65
70
  "@paper-design/shaders": "0.0.78",
66
71
  "class-variance-authority": "^0.7.1",
67
72
  "clsx": "^2.1.1",
@@ -78,6 +83,7 @@
78
83
  "remark-gfm": "^4.0.1",
79
84
  "sonner": "^2.0.7",
80
85
  "tailwind-merge": "^3.0.0",
86
+ "thinking-orbs": "0.3.1",
81
87
  "tw-animate-css": "^1.4.0",
82
88
  "tw-shimmer": "0.4.12",
83
89
  "zustand": "^5.0.14"