@nadicodeai/ui 6.6.1 → 7.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 (37) hide show
  1. package/AGENTS.md +2 -3
  2. package/dist/components/activity-orb.d.ts +1 -1
  3. package/dist/components/activity-orb.d.ts.map +1 -1
  4. package/dist/components/dropdown-menu.d.ts +4 -1
  5. package/dist/components/dropdown-menu.d.ts.map +1 -1
  6. package/dist/components/dropdown-menu.js +6 -3
  7. package/dist/eslint/index.js +2 -0
  8. package/dist/eslint/rules/menu-command-owner.js +45 -0
  9. package/docs/activity.md +4 -5
  10. package/docs/contract.md +33 -8
  11. package/package.json +2 -7
  12. package/dist/components/threshold/canvas-budget.d.ts +0 -10
  13. package/dist/components/threshold/canvas-budget.d.ts.map +0 -1
  14. package/dist/components/threshold/canvas-budget.js +0 -54
  15. package/dist/components/threshold/index.d.ts +0 -6
  16. package/dist/components/threshold/index.d.ts.map +0 -1
  17. package/dist/components/threshold/index.js +0 -5
  18. package/dist/components/threshold/orb.d.ts +0 -14
  19. package/dist/components/threshold/orb.d.ts.map +0 -1
  20. package/dist/components/threshold/orb.js +0 -17
  21. package/dist/components/threshold/presence.d.ts +0 -27
  22. package/dist/components/threshold/presence.d.ts.map +0 -1
  23. package/dist/components/threshold/presence.js +0 -159
  24. package/dist/components/threshold/room.d.ts +0 -33
  25. package/dist/components/threshold/room.d.ts.map +0 -1
  26. package/dist/components/threshold/room.js +0 -168
  27. package/dist/components/threshold/threshold.css +0 -228
  28. package/dist/components/threshold/typed-line.d.ts +0 -8
  29. package/dist/components/threshold/typed-line.d.ts.map +0 -1
  30. package/dist/components/threshold/typed-line.js +0 -43
  31. package/dist/components/threshold/use-reduced-motion.d.ts +0 -10
  32. package/dist/components/threshold/use-reduced-motion.d.ts.map +0 -1
  33. package/dist/components/threshold/use-reduced-motion.js +0 -32
  34. package/dist/threshold.d.ts +0 -2
  35. package/dist/threshold.d.ts.map +0 -1
  36. package/dist/threshold.js +0 -13
  37. package/docs/threshold.md +0 -221
package/docs/threshold.md DELETED
@@ -1,221 +0,0 @@
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` | system work underway, through the shared activity renderer | reporting a failed operation or waiting on a person as ongoing work |
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: threshold, region, or inline operation; see `activity.md` |
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.