@nomosui/react 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +185 -192
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -1,171 +1,166 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nomos
|
|
3
|
-
description:
|
|
3
|
+
description: Pick the right Nomos design-system brick for a given use — atoms (Badge, Chip, Meter, Freshness, Table, Button, Input, field, form blocks), table shell, tone vocabulary, tokens and density — and query the catalogue through the local MCP server, including `ui://` views rendered in conversation.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
###
|
|
15
|
-
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
- **
|
|
19
|
-
|
|
20
|
-
- **
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
- **
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
`onValueChange`,
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- **
|
|
33
|
-
`TooltipProvider`
|
|
34
|
-
|
|
35
|
-
- **
|
|
36
|
-
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
###
|
|
41
|
-
|
|
42
|
-
- **
|
|
43
|
-
|
|
44
|
-
- **
|
|
45
|
-
|
|
46
|
-
- **
|
|
47
|
-
|
|
48
|
-
- **
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
- **
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- **
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- **Séparer deux contenus** → `Separator` ; **tenir la place d'un contenu qui charge** →
|
|
6
|
+
# The Nomos design system
|
|
7
|
+
|
|
8
|
+
An **app-agnostic core** (the root of this repo): it knows neither an app's state, nor its
|
|
9
|
+
i18n, nor its router, nor its query params. As soon as a product word is needed to explain a
|
|
10
|
+
brick, it belongs to the app, not the core.
|
|
11
|
+
|
|
12
|
+
## Which brick to pick
|
|
13
|
+
|
|
14
|
+
### Qualify, situate
|
|
15
|
+
|
|
16
|
+
- **Qualify with a short word** (a status, a domain) → `Badge`. The tone comes from a
|
|
17
|
+
`toneClasses` class, never from a product word.
|
|
18
|
+
- **Qualify removably** (an active filter) → `Chip`, with `onRemove`; with no action, a bare
|
|
19
|
+
`Chip`. A status you can't remove stays a `Badge`.
|
|
20
|
+
- **Place a value on a scale**, with a threshold → `Meter` (wide bar) or `CompactMeter`
|
|
21
|
+
(dense cell). The threshold text is injected by the caller.
|
|
22
|
+
- **State the age of a datum** → `Freshness`; the age is already formatted, `stale` is
|
|
23
|
+
decided by the caller.
|
|
24
|
+
- **Show a task's progress** (a total, no threshold) → `ProgressBar`, `progressbar`
|
|
25
|
+
semantics.
|
|
26
|
+
- **Highlight a number** → `Counter` (suffix and label provided by the app).
|
|
27
|
+
- **Collect or show a rating** → `Rating`: interactive only if the app provides
|
|
28
|
+
`onValueChange`, otherwise a display.
|
|
29
|
+
- **List dated events** → `Timeline` (a rail, presentation only; the date format comes from
|
|
30
|
+
the app). A point carries a state — `done` (default) or `past` (greyed) — and a `past`
|
|
31
|
+
requires a `stateLabel` provided by the app: colour alone is not enough.
|
|
32
|
+
- **Name an icon control, tuck away a short hint** → `Tooltip` as a family (a
|
|
33
|
+
`TooltipProvider` around several, `Tooltip`, `TooltipTrigger`, `TooltipContent`). Never for
|
|
34
|
+
essential information: it is neither keyboard-only nor touch accessible.
|
|
35
|
+
- **Represent a person by their image** → `Avatar` (alt text and fallback come from the app;
|
|
36
|
+
native `<img>` + `onError`, no dependency).
|
|
37
|
+
- **Cap a title with a short hook** → `Kicker` (an uppercase label, an optional tone dot via
|
|
38
|
+
`dot`; the text is injected by the caller).
|
|
39
|
+
|
|
40
|
+
### Act, input
|
|
41
|
+
|
|
42
|
+
- **Act** → `Button` (`variant` = the tone, `size` = the density; `asChild` to put the style
|
|
43
|
+
on a link).
|
|
44
|
+
- **Navigate to a URL** → `Link` (an `<a>`; `href` and label injected, the core carries no
|
|
45
|
+
routing; `asChild` to put the style on an app routing component).
|
|
46
|
+
- **Copy a text** → `CopyButton` (`value` to copy, `label`/`copiedLabel` and `icon`
|
|
47
|
+
injected; the transient label returns on its own after ~2 s).
|
|
48
|
+
- **Input one line** → `Input`; **a bounded number** → `NumberField` (`step`, `min`, `max`
|
|
49
|
+
from the native element); **search with an icon and a clear button** → `SearchField`;
|
|
50
|
+
**several lines** → `Textarea`.
|
|
51
|
+
- **Check** → `Checkbox` (independent option); **toggle right away** → `Switch`; **choose a
|
|
52
|
+
single option among a few** → `RadioGroup`; **a one-off toggle** (mode, filter) → `Toggle`,
|
|
53
|
+
**a segment** → `ToggleGroup`. All are controlled by props: the state stays in the app.
|
|
54
|
+
- **Choose a single value from a list of options** → `Select` as parts (`SelectTrigger`,
|
|
55
|
+
`SelectValue`, `SelectContent`, `SelectItem`; labels and values come from the app). For
|
|
56
|
+
actions, it's a `DropdownMenu`.
|
|
57
|
+
|
|
58
|
+
### Structure
|
|
59
|
+
|
|
60
|
+
- **A surface** (title, body, footer) → the parts of `Card`.
|
|
61
|
+
- **A site header** (brand, navigation, actions) → `Navbar`: a sticky bar at the top, a
|
|
62
|
+
bottom border, the core's background. The `brand`, `nav` and `actions` slots are injected
|
|
63
|
+
by the app — no `href` and no product word in the core (ADR 0030).
|
|
64
|
+
- **An empty state, or a failure to name** → `EmptyState` (the centred card; the raw `detail`
|
|
65
|
+
names the real failure, rather than a silent zero).
|
|
66
|
+
- **A footer** (brand, link row, legal line) → `Footer`: injected slots (`brand`, `links`,
|
|
67
|
+
`legal`), with no routing or core-specific label.
|
|
68
|
+
- **Inline information** that asks for attention without blocking → `Alert` (the tone comes
|
|
69
|
+
from the core; `onClose` goes with `closeLabel`).
|
|
70
|
+
- **Separate two contents** → `Separator`; **hold the place of loading content** →
|
|
72
71
|
`Skeleton`.
|
|
73
|
-
- **
|
|
74
|
-
(`PopoverTrigger`, `PopoverContent`, `PopoverAnchor`
|
|
75
|
-
|
|
76
|
-
- **
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- **
|
|
80
|
-
**
|
|
81
|
-
|
|
82
|
-
- **
|
|
83
|
-
|
|
84
|
-
- **
|
|
85
|
-
(`DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`aria-describedby`) pour les contrôles du cœur : `Input`, `Textarea`, `NumberField`,
|
|
122
|
-
`SearchField` et le déclencheur de `Select`.
|
|
72
|
+
- **Rich or interactive content in a floating surface on click** → `Popover`
|
|
73
|
+
(`PopoverTrigger`, `PopoverContent`, `PopoverAnchor` to anchor elsewhere). For a short text
|
|
74
|
+
on hover, it's a `Tooltip`.
|
|
75
|
+
- **Reveal a detail on demand** → `Collapsible` (`CollapsibleTrigger`, `CollapsibleContent`);
|
|
76
|
+
**collapsible sections, one at a time** → `Accordion` (`AccordionItem`, `AccordionTrigger`,
|
|
77
|
+
`AccordionContent`, `type` single/multiple).
|
|
78
|
+
- **Switch between sibling views** → `Tabs` (`TabsList`, `TabsTrigger`, `TabsContent`);
|
|
79
|
+
**bound a dense sub-view to a fixed height** → `ScrollArea` (purely cosmetic, scrolling
|
|
80
|
+
stays native).
|
|
81
|
+
- **Warn without blocking** → `Toast` via `useToast().show({ message })` (the queue and
|
|
82
|
+
auto-dismiss live in `ToastProvider`).
|
|
83
|
+
- **Group actions behind a compact trigger** → `DropdownMenu` as parts
|
|
84
|
+
(`DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, checkable, radio,
|
|
85
|
+
submenu; labels and actions come from the app). To choose a form value, it's a `Select`.
|
|
86
|
+
- **Ask for a decision in a centred modal** → `Dialog` (trigger, title, description, body,
|
|
87
|
+
footer; the close label comes from the app). Stacking, overlay and motion come from the
|
|
88
|
+
tokens, never from a hard-coded value.
|
|
89
|
+
- **Confirm a destructive action** → `AlertDialog` (`AlertDialogTrigger`,
|
|
90
|
+
`AlertDialogContent`, `AlertDialogAction`, `AlertDialogCancel`; two explicit outcomes). It
|
|
91
|
+
does not close on an outside click.
|
|
92
|
+
- **Show content anchored to an edge** → `Sheet` as parts (`SheetTrigger`, `SheetContent`
|
|
93
|
+
with `side`, `SheetHeader`, `SheetFooter`); `side="bottom"` is the **drawer** — the same
|
|
94
|
+
panel, a different side, not a separate atom.
|
|
95
|
+
- **Browse a hierarchy** → `Tree` (navigation, one active node) or `SelectionTree`
|
|
96
|
+
(multi-selection); opening and selection are controlled by props, keyboard focus (`tree`
|
|
97
|
+
role, arrows) is internal.
|
|
98
|
+
|
|
99
|
+
### Write
|
|
100
|
+
|
|
101
|
+
- **Title a section** → `Heading`: `level` chooses the `h1`..`h6` tag **and** the semantic
|
|
102
|
+
size (display, title, lead, body, caption, micro). Document hierarchy is decided by the
|
|
103
|
+
level, never by the size.
|
|
104
|
+
- **Write body text** → `Text`: `size` reads the semantic scale (`lead`, `body`, `caption`,
|
|
105
|
+
`micro`), `as` chooses `p` (default) or `span` for inline text. Sizes come from the tokens,
|
|
106
|
+
never from an ad-hoc utility.
|
|
107
|
+
- **Show a code excerpt** → `Code` (inline, in a sentence) or `CodeBlock` (scrollable block
|
|
108
|
+
with a copy button; `code` is the copied text, `children` the rendering, labels are
|
|
109
|
+
injected). Syntax highlighting stays with the app.
|
|
110
|
+
|
|
111
|
+
### Form
|
|
112
|
+
|
|
113
|
+
- **Lay out** → `Form` (grid of fields + action area, `columns` for two columns). No
|
|
114
|
+
validation, no state, no text.
|
|
115
|
+
- **A field slot** (label, control, hint or error) → `Field`; **name a control alone** →
|
|
116
|
+
`Label` (`htmlFor`); **group related fields** → `Fieldset` (`legend` provided by the app).
|
|
117
|
+
The error message is injected, never computed by the core. `Field` itself associates the
|
|
118
|
+
control with its message (`aria-invalid`, `aria-describedby`) for the core controls:
|
|
119
|
+
`Input`, `Textarea`, `NumberField`, `SearchField` and the `Select` trigger.
|
|
123
120
|
|
|
124
121
|
### Tables
|
|
125
122
|
|
|
126
|
-
- **
|
|
127
|
-
`
|
|
128
|
-
- **
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
- **
|
|
132
|
-
|
|
133
|
-
(
|
|
123
|
+
- **A dense grid of rows** → the parts of `Table` (`Table`, `TableHeader`, `TableBody`,
|
|
124
|
+
`TableRow`, `TableHead`, `TableCell`), importable separately.
|
|
125
|
+
- **Filter by a dimension** → `FacetFilter` (options, selection and the "clear" label
|
|
126
|
+
injected); **search, count, manage columns** → `DataTableToolbar`; **navigate a page** →
|
|
127
|
+
`DataTablePagination` (independent of the table).
|
|
128
|
+
- **A driven table** (columns, facets, sorting, pagination, row detail) → the
|
|
129
|
+
`FacetedDataTable` feature: it owns the behaviour and the **placement** of the detail
|
|
130
|
+
(`overlay` layer or `inline` expansion), the app provides the content and the `labels`.
|
|
134
131
|
|
|
135
132
|
### Conversation
|
|
136
133
|
|
|
137
|
-
- **
|
|
138
|
-
|
|
139
|
-
`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- **
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
###
|
|
153
|
-
|
|
154
|
-
- **
|
|
155
|
-
`
|
|
156
|
-
- **
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
<!-- inventaire : début — tenu par src/mcp/skill.test.ts -->
|
|
134
|
+
- **Wire up a conversation** → the `useChatThread({ transport, initialMessages })` feature:
|
|
135
|
+
it owns the **state machine** — optimistic turn append, delta assembly, `send`, `stop`,
|
|
136
|
+
`retry`/`regenerate`, `replace`, error — and **nothing of the transport** (ADR 0032). The
|
|
137
|
+
app injects a `ChatTransport` (a `send` function): the core calls no model, knows no
|
|
138
|
+
endpoint, shows no network error (ADR 0018). A message carries a role and **parts**
|
|
139
|
+
(`text`, `reasoning`, `tool`, `data`): a structured signal is a first-class part, never a
|
|
140
|
+
sentinel fished out of the prose.
|
|
141
|
+
- **The thread window** → `Conversation`: a live `log` region anchored at the bottom, the
|
|
142
|
+
scrolling held by the core, the height by the app. **A message** → `Message`: the placement
|
|
143
|
+
and tone of the role, the content in `parts`, and `renderPart` so the app keeps markdown,
|
|
144
|
+
highlighting and business artefacts. **The input** → `Composer`: controlled by props
|
|
145
|
+
(`value` + `onChange`), `Enter` sends, `Shift+Enter` breaks the line, IME guard; `busy` +
|
|
146
|
+
`onStop` to interrupt. **Wait for the reply** → `TypingIndicator` (label injected, `status`
|
|
147
|
+
region).
|
|
148
|
+
|
|
149
|
+
### Tones and tokens
|
|
150
|
+
|
|
151
|
+
- **A status tone** → `toneClasses` (`neutral`, `info`, `progress`, `attention`, `warning`,
|
|
152
|
+
`danger`, `success`). It is an **intention**, not a colour.
|
|
153
|
+
- **Colours, spacing, typography** → the tokens (`tokens.json`, single source). The theme and
|
|
154
|
+
density are set on the **render root** (`data-theme`, `data-density`), never on `:root`.
|
|
155
|
+
|
|
156
|
+
The default sort is carried by the column (`meta.defaultSort`), never by the core.
|
|
157
|
+
|
|
158
|
+
## The inventory
|
|
159
|
+
|
|
160
|
+
The exact list of catalogue bricks, held by test (`src/mcp/skill.test.ts`): a brick added to
|
|
161
|
+
the catalogue without being here turns red, and a line that no longer exists does too.
|
|
162
|
+
|
|
163
|
+
<!-- inventory: start — kept by src/mcp/skill.test.ts -->
|
|
169
164
|
accordion
|
|
170
165
|
alert
|
|
171
166
|
alert-dialog
|
|
@@ -224,44 +219,42 @@ toggle-group
|
|
|
224
219
|
tooltip
|
|
225
220
|
tree
|
|
226
221
|
typing-indicator
|
|
227
|
-
<!--
|
|
222
|
+
<!-- inventory: end -->
|
|
228
223
|
|
|
229
|
-
##
|
|
224
|
+
## Querying the catalogue
|
|
230
225
|
|
|
231
|
-
|
|
226
|
+
The local MCP server serves the same inventory as the style page, read-only:
|
|
232
227
|
|
|
233
228
|
```sh
|
|
234
|
-
npm run mcp # stdio,
|
|
229
|
+
npm run mcp # stdio, no token
|
|
235
230
|
```
|
|
236
231
|
|
|
237
|
-
- `list_components { query? }` —
|
|
238
|
-
- `get_component { name }` —
|
|
239
|
-
- `preview_component { name }` —
|
|
240
|
-
- `list_scenes` —
|
|
241
|
-
- `render_<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
##
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
document
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
(`src/catalogue/coherence.test.ts`) ; une brique absente de l'inventaire fait rougir
|
|
267
|
-
`src/mcp/skill.test.ts`.
|
|
232
|
+
- `list_components { query? }` — which bricks exist, and which one matches a word.
|
|
233
|
+
- `get_component { name }` — the full contract of a brick (props, variants, usages).
|
|
234
|
+
- `preview_component { name }` — its rendering recipe (example props, variants, usages).
|
|
235
|
+
- `list_scenes` — the available **composite scenes**.
|
|
236
|
+
- `render_<name>` / `render_scene_<name>` — return the recipe, **carry the data** (`props`,
|
|
237
|
+
optional) and **reference the view** via `_meta.ui.resourceUri`.
|
|
238
|
+
|
|
239
|
+
Resources carry the same data as the tools: `nomos://tokens` (the inventory flattened by
|
|
240
|
+
mode) and `nomos://component/<name>` (the manifest).
|
|
241
|
+
|
|
242
|
+
## Rendering in conversation
|
|
243
|
+
|
|
244
|
+
Beyond the manifest, each brick has a **view** served at `ui://nomos/<name>` — a
|
|
245
|
+
self-contained document (`text/html;profile=mcp-app`) the host renders in a sandboxed iframe.
|
|
246
|
+
A **composite scene** (`ui://nomos/composite/<name>`) assembles several bricks into a screen
|
|
247
|
+
that makes sense (a form, a status card). Scenes are also exported on the public JS surface
|
|
248
|
+
(`composites`, `compositeNames`, `findComposite`, ADR 0031): the MCP server and the site
|
|
249
|
+
render the **same** source, and their default copy is neutral — it is injected via props.
|
|
250
|
+
|
|
251
|
+
The view **emits intentions** (`ready`, `select`, `change`, `error`) and never mutates state:
|
|
252
|
+
the host decides. It pushes it the appearance (`set-view`: theme, density) and the **data**
|
|
253
|
+
(`set-data`, ADR 0023) — the `props` the render tool carried.
|
|
254
|
+
|
|
255
|
+
## If you change the design system
|
|
256
|
+
|
|
257
|
+
A change that changes a brick's **usage** updates this skill in the same change: the inventory
|
|
258
|
+
list and the prose. A component added to the catalogue without its manifest (props, variants,
|
|
259
|
+
usages) turns the coherence test red (`src/catalogue/coherence.test.ts`); a brick missing from
|
|
260
|
+
the inventory turns `src/mcp/skill.test.ts` red.
|
package/package.json
CHANGED