@nurix/etna 0.20.37 → 0.20.39

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 (60) hide show
  1. package/data/catalog/rules/frontend.md +5 -5
  2. package/data/catalog/rules/pnpm.md +2 -2
  3. package/data/harness-index.json +1 -1
  4. package/data/harnesses/code/agents/brainstorm.md +5 -3
  5. package/data/harnesses/code/codex/agents/brainstorm.toml +5 -3
  6. package/data/harnesses/code/harness.json +2 -2
  7. package/data/harnesses/code/hooks/approved-packages.md +1 -2
  8. package/data/harnesses/code/rules/frontend.md +5 -5
  9. package/data/harnesses/code/rules/pnpm.md +2 -2
  10. package/data/harnesses/code/skills/boundary/references/packs/build-config-runtime.md +1 -1
  11. package/data/harnesses/code/skills/brainstorm/README.md +18 -45
  12. package/data/harnesses/code/skills/brainstorm/SKILL.md +105 -173
  13. package/data/harnesses/code/skills/brainstorm/references/doctrine.md +70 -141
  14. package/data/harnesses/code/skills/brainstorm/references/entry.md +70 -0
  15. package/data/harnesses/code/skills/brainstorm/references/harvest.md +85 -137
  16. package/data/harnesses/code/skills/brainstorm/references/session-schema.md +98 -135
  17. package/data/harnesses/code/skills/design/components/charts.md +121 -43
  18. package/data/harnesses/code/skills/design/components/collections.md +209 -41
  19. package/data/harnesses/code/skills/design/components/feedback.md +86 -25
  20. package/data/harnesses/code/skills/design/components/hooks.md +198 -12
  21. package/data/harnesses/code/skills/design/components/index.md +93 -71
  22. package/data/harnesses/code/skills/design/components/inputs.md +249 -157
  23. package/data/harnesses/code/skills/design/components/layout.md +98 -38
  24. package/data/harnesses/code/skills/design/components/media.md +46 -25
  25. package/data/harnesses/code/skills/design/components/motion.md +56 -46
  26. package/data/harnesses/code/skills/design/components/navigation.md +54 -15
  27. package/data/harnesses/code/skills/design/components/overlays.md +51 -41
  28. package/data/harnesses/code/skills/design/components/setup.md +84 -5
  29. package/data/harnesses/code/skills/design/components/typography.md +72 -22
  30. package/data/harnesses/code/skills/design/components/utils.md +47 -13
  31. package/data/harnesses/code/skills/design/data/chrome/board-column.md +12 -12
  32. package/data/harnesses/code/skills/design/data/chrome/data-table.md +6 -6
  33. package/data/harnesses/code/skills/design/data/chrome/side-panel.md +15 -15
  34. package/data/harnesses/code/skills/design/data/chrome/sidebar.md +25 -25
  35. package/data/harnesses/code/skills/design/data/chrome/stat-cards.md +22 -22
  36. package/data/harnesses/code/skills/design/data/chrome/topbar.md +12 -12
  37. package/data/harnesses/code/skills/design/references/emission.md +6 -6
  38. package/data/harnesses/code/skills/design/scripts/README-validator.md +20 -8
  39. package/data/harnesses/code/skills/design/scripts/validate-design-md.mjs +15 -10
  40. package/data/harnesses/delivery/agents/brainstorm.md +5 -3
  41. package/data/harnesses/delivery/codex/agents/brainstorm.toml +5 -3
  42. package/data/harnesses/delivery/harness.json +2 -2
  43. package/data/harnesses/delivery/skills/brainstorm/README.md +18 -45
  44. package/data/harnesses/delivery/skills/brainstorm/SKILL.md +105 -173
  45. package/data/harnesses/delivery/skills/brainstorm/references/doctrine.md +70 -141
  46. package/data/harnesses/delivery/skills/brainstorm/references/entry.md +70 -0
  47. package/data/harnesses/delivery/skills/brainstorm/references/harvest.md +85 -137
  48. package/data/harnesses/delivery/skills/brainstorm/references/session-schema.md +98 -135
  49. package/data/harnesses/etna/agents/brainstorm.md +5 -3
  50. package/data/harnesses/etna/codex/agents/brainstorm.toml +5 -3
  51. package/data/harnesses/etna/harness.json +2 -2
  52. package/data/harnesses/etna/skills/boundary/references/packs/build-config-runtime.md +1 -1
  53. package/data/harnesses/etna/skills/brainstorm/README.md +18 -45
  54. package/data/harnesses/etna/skills/brainstorm/SKILL.md +105 -173
  55. package/data/harnesses/etna/skills/brainstorm/references/doctrine.md +70 -141
  56. package/data/harnesses/etna/skills/brainstorm/references/entry.md +70 -0
  57. package/data/harnesses/etna/skills/brainstorm/references/harvest.md +85 -137
  58. package/data/harnesses/etna/skills/brainstorm/references/session-schema.md +98 -135
  59. package/data/harnesses/hr/harness.json +1 -1
  60. package/package.json +2 -2
@@ -2,52 +2,59 @@
2
2
 
3
3
  > Generated reference (regen-owned; see [index.md](index.md)). Binds only under `primitives: nurix`.
4
4
 
5
+ Components: Table, UiTable, List, Tree, FieldGrid, AnchoredScroller, Composer, MicInput, StreamingCursor, Marker
6
+
7
+ Each imports from its own subpath as one namespace, with its parts as dotted members, e.g. `import { UiTable } from "@nurix/components/block/ui-table"` then `UiTable.Row`, `UiTable.Cell`. `Table` is the exception: a single generic root whose cell renderers stay flat named exports.
8
+
5
9
  ## Table
6
10
 
7
- Data table with sorting, pagination, and row selection. Zero-config `<Table data columns />` renders bare; selection, sorting, pagination, and resize are opt-in via `tableConfig`.
11
+ Server-driven data table on a TanStack engine (`block/table`). Zero-config `<Table data columns />` renders bare; selection, sorting, pagination, resize and the grid keyboard model are opt-in via `tableConfig`. Install its two optional peer families first — `table` (`@tanstack/react-table`) and `spark` (`@mantine/charts @mantine/core recharts`, for the lazily loaded sparkline cell); a missing peer fails at the bundler with the package named.
8
12
 
9
13
  **Props:**
10
14
 
11
- - `data` - Array of row objects (must have `id` field)
15
+ - `data` - Array of row objects (each must have an `id` string)
12
16
  - `columns` - Array of `ColumnDefinition` objects
13
- - `columnOrder` - Optional column order (defaults to column definition order)
14
- - `pagination` - `{ current, size, total, onChange }`
15
- - `sorting` - `{ columns, direction, onChange }` (max 3 sort columns)
17
+ - `columnOrder` - Composite column ids in display order (defaults to column definition order)
18
+ - `pagination` - `{ current, size, total, onChange }` — controlled and 1-indexed; slice `data` to the current page yourself
19
+ - `sorting` - `{ columns, direction, onChange }` (max 3 sort columns) — `columns` takes composite ids
16
20
  - `onSelect` - Callback with selected row IDs
17
- - `onCellChange` - Write-back from `editable` cells: `({ rowId, columnId, slug, value }) => void` (data stays consumer-controlled)
21
+ - `onCellChange` - Write-back from `editable` cells: `({ rowId, columnId, slug, value }) => void`; fires only on a real commit (a blur that typed nothing emits nothing), and the table never mutates `data`
18
22
  - `loading` - Show loading skeleton
19
- - `tableConfig` - Optional; `{ preset, rowSelection, sort, pagination, header, density, columnBorders, tableBorders, resize, autoHeight }`
23
+ - `error` - `{ message, onRetry? }` — a failed fetch renders in place of the rows; wins over `loading`, which wins over the empty body
24
+ - `tableConfig` - Optional; `{ preset, rowSelection, sort, pagination, header, density, columnBorders, tableBorders, resize, autoHeight, gridAria }`
25
+
26
+ `density` is [compact|comfortable|spacious]; `columnBorders`/`tableBorders` are [none|all]. `preset: "classic"` sets a tinted short header row, comfortable density, the pagination footer and `tableBorders: "all"` in one key, and explicit keys override it. The pagination footer is opt-in (`pagination: true`); `gridAria` adds `role="grid"` with roving-tabindex arrow navigation.
20
27
 
21
- `preset: "classic"` restores the legacy look in one key; `resize` gates column drag handles; the pagination footer is opt-in (`pagination: true`).
28
+ **Composite ids.** `sorting.columns` and `columnOrder` take `getColumnId()`'s `${slug}_${component}` id — `name_text` for a `slug: "name"`, `type: "text"` column. A bare `"name"` silently matches no column.
22
29
 
23
30
  ```tsx
24
31
  <Table
25
- data={users}
32
+ data={pageRows}
26
33
  columns={columns}
27
34
  pagination={{ current: 1, size: 10, total: 100, onChange: setPagination }}
28
- sorting={{ columns: ["name"], direction: "asc", onChange: setSorting }}
35
+ sorting={{ columns: ["name_text"], direction: "asc", onChange: setSorting }}
29
36
  onSelect={setSelectedIds}
30
- tableConfig={{ rowSelection: true, resize: true }}
37
+ tableConfig={{ rowSelection: true, pagination: true, resize: true }}
31
38
  />
32
39
  ```
33
40
 
34
41
  ## ColumnDefinition
35
42
 
36
- Define table columns using the `defineColumn()` helper.
43
+ Define table columns with the `defineColumn()` helper, which fills `component` from `type`, `hidden: false`, and empty `metadata`/`display`.
37
44
 
38
45
  **Fields:**
39
46
 
40
- - `slug` - Field key in data object
47
+ - `slug` - Field key in the row object
41
48
  - `label` - Column header text
42
49
  - `type` - Data type: `text`, `number`, `boolean`, `date`, `enum`, `user`
43
- - `component` - Renderer (defaults from `type`): `text`, `number`, `boolean`, `date`, `select`, `multiselect`, `slider`, `rating`, `status`, `priority`, `assignee`, `sparkline`, `date-range`
50
+ - `component` - Renderer (defaults from `type`: `enum` → `select`, `user` → `assignee`, otherwise the type string): `text`, `number`, `boolean`, `date`, `date-range`, `select`, `multiselect`, `slider`, `rating`, `status`, `priority`, `assignee`, `sparkline`
44
51
  - `hidden` - Hide column
45
52
  - `editable` - Activate the interactive widget; columns render the read-only display variant by default
46
53
  - `metadata` - Component-specific options (see table below)
47
- - `display` - `{ width, sortable }` (width optional — columns are fluid when unset)
48
- - `cell` - Custom render function `(row) => ReactNode`
54
+ - `display` - `{ width, sortable }` (width optional — columns are fluid when unset; `sortable` defaults to true except for `boolean` and `user`)
55
+ - `cell` - Custom render function `(row) => ReactNode`; wins over `component`
49
56
 
50
- For full control, pass your own `cell()` or import a renderer from `@nurix/components/block/table` (`StatusPill`, `SelectCell`, etc.) as an escape hatch.
57
+ For full control, pass your own `cell()` and compose a renderer imported from `@nurix/components/block/table` — `EditableText`, `EditableNumber`, `EditableDate`, `EditableDateRange`, `BooleanCell`, `SliderCell`, `RatingCell`, `SelectCell`, `StatusCell`, `PriorityCell`, `AssigneeChip`, `MultiSelectCell`, `TableSparkline`. Each is a flat export so a consumer pays only for the renderers it imports.
51
58
 
52
59
  ### Metadata options by component
53
60
 
@@ -67,13 +74,13 @@ For full control, pass your own `cell()` or import a renderer from `@nurix/compo
67
74
  import { defineColumn } from "@nurix/components/block/table";
68
75
 
69
76
  const columns = [
70
- defineColumn({
77
+ defineColumn<Row>({
71
78
  slug: "name",
72
79
  label: "Name",
73
80
  type: "text",
74
81
  editable: true,
75
82
  }),
76
- defineColumn({
83
+ defineColumn<Row>({
77
84
  slug: "status",
78
85
  label: "Status",
79
86
  type: "enum",
@@ -82,14 +89,14 @@ const columns = [
82
89
  options: [{ value: "active", label: "Active", color: "#22c55e" }],
83
90
  },
84
91
  }),
85
- defineColumn({
92
+ defineColumn<Row>({
86
93
  slug: "progress",
87
94
  label: "Progress",
88
95
  type: "number",
89
96
  component: "slider",
90
97
  metadata: { min: 0, max: 100 },
91
98
  }),
92
- defineColumn({
99
+ defineColumn<Row>({
93
100
  slug: "createdAt",
94
101
  label: "Created",
95
102
  type: "date",
@@ -98,35 +105,196 @@ const columns = [
98
105
  ];
99
106
  ```
100
107
 
101
- ## PageHeader
108
+ ## UiTable
109
+
110
+ Bare native-table parts (`block/ui-table`) for markup you write yourself — no data model, no sort, no edit machinery. Reach for `Table` the moment the data needs sorting, editable cells or a `defineColumn` column set.
111
+
112
+ **Sub-components:** `UiTable.Header`, `UiTable.Body`, `UiTable.Footer`, `UiTable.Head`, `UiTable.Row`, `UiTable.Cell`, `UiTable.Caption`
113
+
114
+ - The root renders a `data-slot="table-container"` scroll wrapper around the `<table>`; `className` on `UiTable` styles the `<table>`, not the wrapper.
115
+ - Mark a selected row with `data-selected` on `UiTable.Row` (paints on the accent; `data-state="selected"` paints nothing). Body rows take the hover wash; header and footer rows stay still.
116
+ - No built-in loading, empty or error row — it renders exactly the rows it is given.
117
+
118
+ ```tsx
119
+ <UiTable>
120
+ <UiTable.Header>
121
+ <UiTable.Row><UiTable.Head>Invoice</UiTable.Head></UiTable.Row>
122
+ </UiTable.Header>
123
+ <UiTable.Body>
124
+ <UiTable.Row><UiTable.Cell>INV001</UiTable.Cell></UiTable.Row>
125
+ </UiTable.Body>
126
+ </UiTable>
127
+ ```
128
+
129
+ ## List
130
+
131
+ Keyed collection whose rows you draw (`block/list`) — `item.id` is the React key, the selection key, the expand key and what row motion tracks. Rows with typed columns are `Table`; a chat transcript is `AnchoredScroller`. Activity feeds, record history, notification inboxes and card grids are `List` with a recipe.
132
+
133
+ **Sub-components:** `List.Toolbar`, `List.Pagination` (the parts List renders from `toolbar` and `pagination`), `List.ItemErrorBoundary` (wraps every row: a render throw becomes a compact error state while the other rows stand)
134
+
135
+ **Props:** `data`, `renderItem(item, index, { isExpanded, onToggle, view })`, `toolbar` (`{ search: { placeholder, keys }, filters: [{ key, label, options, multiple }], showExpandControls, actions }`), `pagination` (`{ current, size, total, onChange }` — when present, the parent has already filtered, sorted and paged), `expandedIds`/`defaultExpandedIds`/`onExpandedIdsChange` (`string[] | "all"`), `searchValue`/`defaultSearchValue`/`onSearchValueChange`, `filters`/`defaultFilters`/`onFiltersChange`, `onSelect`, `groupBy(item) → { key, label }` (a `Marker` separator before each run), `live` (holds rows arriving above the viewport behind an "N new" pill — keep `data` newest-first), `onLoadMore` + `hasMore` + `loadingMore`, `view` [list|card], `onItemActivate(id)`, `onItemError(error, id)`, `loading`, `error` (`{ message, onRetry? }`), `emptyState`, `labels`, `config` (`{ selection, density, dividers, animation: { type: slide|fade|scale|none }, expandable, pageSizeOptions, cardColumns }`)
136
+
137
+ - No virtualization: every item in `data` renders, so page an unbounded collection with `onLoadMore` or `pagination`.
138
+ - `expandedIds`, `searchValue` and `filters` go controlled the moment the prop is passed — `expandedIds={[]}` locks expansion closed unless `onExpandedIdsChange` drives it.
139
+
140
+ ```tsx
141
+ <List
142
+ data={events}
143
+ renderItem={(event) => <FeedRow event={event} />}
144
+ groupBy={(event) => ({ key: dayKey(event), label: dayLabel(event) })}
145
+ toolbar={{ search: { keys: ["title"] }, filters: [{ key: "type", label: "Type", options }] }}
146
+ onLoadMore={loadOlder}
147
+ hasMore={hasMore}
148
+ loadingMore={isLoading}
149
+ />
150
+ ```
151
+
152
+ ## Tree
153
+
154
+ Hierarchy built from flat records that name their parent (`block/tree`); `useTree` is exported beside it for fully custom markup.
102
155
 
103
- Config-driven page masthead with an optional stat ledger — router-agnostic (the consumer owns navigation). Import from `@nurix/components/block/page-header`.
156
+ **Sub-components:** `Tree.Item`, `Tree.ItemLabel` (returned from `renderItem` when a row needs its own props or `render` element).
104
157
 
105
- **Props:** `title`, `eyebrow` (breadcrumb segments — strings or linked `Crumb`s; last is the current page), `description`, `statusSlot`, `actions`, `stats` (`Stat[]` ledger), `back` (`BackLink`), `onBack`, `onNavigate` (fired with a crumb's/stat's `href`), `statsAlign` [left|right], `tall`
158
+ **Props:** `data`, `getParentId` (`null` for a top-level record), `isItemFolder`, `isItemDisabled`, `renderItem`, `selectionMode` [none|single|multiple], `selectedIds`/`defaultSelectedIds`/`onSelectedIdsChange`, `expandedIds`/`defaultExpandedIds` (an id list or `"all"`; default `[]`)/`onExpandedIdsChange`, `onItemActivate`
159
+
160
+ - Keep the accessor functions stable — the rows re-flatten whenever `data` or an accessor changes identity. Records whose parent id names no record never render.
106
161
 
107
162
  ```tsx
108
- <PageHeader
109
- title="Q3 Pipeline"
110
- eyebrow={["Sales", "Pipelines"]}
111
- actions={<Button size="sm">Export</Button>}
112
- stats={[{ label: "Open deals", value: "142" }, { label: "Won", value: "$1.2M" }]}
163
+ <Tree
164
+ aria-label="Files"
165
+ data={records}
166
+ getParentId={getParentId}
167
+ selectionMode="single"
168
+ onItemActivate={openFile}
169
+ renderItem={(item) => item.getItemData().name}
113
170
  />
114
171
  ```
115
172
 
173
+ ## FieldGrid
174
+
175
+ One record's fields, column-driven and editable in place (`compound/field-grid`) — the description list. Skip it for one labelled form field (`compound/cell`) and for many records (`Table`).
176
+
177
+ **Props:** `columns` (`FieldColumn[]`), `data` (the one record), `onDataChange(slug, value)`, `arrangement` [grid|strip] (default `grid`, the responsive labelled `<dl>`; `strip` is the compact icon-and-value band), `columnsPerRow` (grid only, 1–9, default 6), `labels` (`{ empty, on, off }`)
178
+
179
+ **FieldColumn:** `id`, `slug`, `label`, `type` [text|number|boolean|date|slider|rating|enum|status|priority|assignee|avatar|avatarGroup|chip|timer], `options` (`[{ value, label, color, icon }]`), `readOnly`, `placeholder`, `min`/`max`/`step`, `format` (Intl number options), `colSpan`, `icon` (a Lucide icon, or `true` to pick one from `type` then `slug`), `validate(value)` (a returned message refuses the commit), `cell(value, record)` (wins over `type`)
180
+
181
+ - Editing is opt-in twice: a column is read-only unless it sets `readOnly: false` **and** the grid received `onDataChange`. Each editor commits once (Enter or blur for text, the pick for options, the release for a slider).
182
+ - The grid is naked — the strip's band chrome goes on the call site.
183
+
184
+ ```tsx
185
+ <FieldGrid
186
+ arrangement="strip"
187
+ data={deal}
188
+ onDataChange={(slug, value) => updateDeal(deal.id, { [slug]: value })}
189
+ className="border-y bg-muted/30 px-5 py-3"
190
+ columns={[
191
+ { id: "stage", slug: "stage", label: "Stage", type: "status", options: stages, readOnly: false, icon: true },
192
+ { id: "amount", slug: "amount", label: "Amount", type: "number", format: { style: "currency", currency: "USD" } },
193
+ ]}
194
+ />
195
+ ```
196
+
197
+ ## Page and record headers
198
+
199
+ No page-header block ships: compose the masthead at the call site. A title row and actions sit on a `Row`; a KPI ledger is several `Stat` (`element/stat`) on a `Row` beneath it, documented in [typography](typography.md); a record's labelled fields are `FieldGrid` with `arrangement="strip"`.
200
+
116
201
  ## Conversation surfaces
117
202
 
118
- Chat/AI message chrome; import each from its kebab-case subpath.
203
+ Chat and message-thread chrome; import each from its own subpath.
204
+
205
+ ### AnchoredScroller
206
+
207
+ Scroll region that sticks to the newest message and offers a jump control once the reader scrolls away (`block/anchored-scroller`). Install its optional peer family `anchored-scroller` (`@shadcn/react`) first. A plain scrollable region with no stick-to-bottom behavior is `element/scroll-area`.
208
+
209
+ **Sub-components:** `AnchoredScroller.Provider`, `AnchoredScroller.Viewport`, `AnchoredScroller.Content`, `AnchoredScroller.Item`, `AnchoredScroller.Button`; hooks `useAnchoredScroller`, `useAnchoredScrollerScrollable`, `useAnchoredScrollerVisibility`
210
+
211
+ **Props:** Provider — `shouldAutoScroll`, `defaultScrollPosition`; root — `labels` (`{ region, scrollToEnd, scrollToStart }`); Viewport — `shouldPreserveScrollOnPrepend` (default `true`); Item — `itemId`, `isScrollAnchor`; Button — `direction` [start|end] (default `end`), plus `Button`'s `variant`, `size`, `isIconOnly`
212
+
213
+ - The hooks read the scroll store only from components rendered under `AnchoredScroller.Provider`.
214
+ - Give every Item an `itemId` — `scrollToItem`, `visibleItemIds` and `currentAnchorId` key off it.
215
+
216
+ ```tsx
217
+ <AnchoredScroller.Provider shouldAutoScroll defaultScrollPosition="end">
218
+ <AnchoredScroller labels={{ region: "Support thread" }}>
219
+ <AnchoredScroller.Viewport>
220
+ <AnchoredScroller.Content>
221
+ {messages.map((m) => (
222
+ <AnchoredScroller.Item key={m.id} itemId={m.id} isScrollAnchor={m.isTurnStart}>{renderMessage(m)}</AnchoredScroller.Item>
223
+ ))}
224
+ </AnchoredScroller.Content>
225
+ </AnchoredScroller.Viewport>
226
+ <AnchoredScroller.Button direction="end" />
227
+ </AnchoredScroller>
228
+ </AnchoredScroller.Provider>
229
+ ```
230
+
231
+ ### Composer
232
+
233
+ Chat message composer: textarea, attachments, a footer of controls and a send (`@nurix/components/block/composer`, plus `useStagedFiles`, `composerNoticeVariants`).
234
+
235
+ **Props:** `onSubmit`, `onFilesDropped`, `reason` (blocks every send path and fills the Notice), `loading`, `accept`, `labels`
236
+
237
+ **Sub-components:** `Composer.Header`, `Composer.Attachments`, `Composer.Textarea` (`onEnterSubmit`, `maxRows`), `Composer.Footer` (`start`, `end`), `Composer.Attach` (`onSelect`, `multiple`), `Composer.Send`, `Composer.Notice` (`intent` [neutral|danger])
238
+
239
+ Enter submits (Shift+Enter is a newline) except while `reason` or `loading` holds the send. Render `Composer.Notice` unconditionally so a later reason announces. `useStagedFiles({ maxFiles })` returns `{ files, stage, remove, clear }`.
240
+
241
+ ```tsx
242
+ <Composer onSubmit={send} onFilesDropped={stage} reason={closed ? "The window is closed." : undefined}>
243
+ <Composer.Attachments>{files.map(renderChip)}</Composer.Attachments>
244
+ <Composer.Textarea value={draft} onValueChange={setDraft} />
245
+ <Composer.Footer start={<Composer.Attach onSelect={stage} />} end={<Composer.Send disabled={!draft.trim()} />} />
246
+ <Composer.Notice />
247
+ </Composer>
248
+ ```
249
+
250
+ ### MicInput
251
+
252
+ Microphone toggle with a live level meter, as a latch over the caller's engine or over a built-in recorder (`@nurix/components/compound/mic-input`, plus `useMicInputState`, `useMicRecorder`; recorder mode needs the optional peer `react-voice-visualizer`).
253
+
254
+ **Props:** `pressed`, `onPressedChange`, `onValueCommitted` (latch mode) or `recorder` (from `useMicRecorder()`), `status` [idle|listening|processing|error], `waveform` [true|false|"always"], `onError`, `labels`
255
+
256
+ **Sub-components:** `MicInput.Trigger` (`size` [sm|default|lg]), `MicInput.Meter` (`variant` [static|history], `deviceId`)
257
+
258
+ A denied microphone surfaces only through `onError` (meter) or `recorder.error`. A composer's voice button is a `Button` in `Composer.Footer`.
259
+
260
+ ```tsx
261
+ <MicInput pressed={isRecording} onPressedChange={toggle} status={status}>
262
+ <MicInput.Meter variant="history" />
263
+ <MicInput.Trigger size="sm" />
264
+ </MicInput>
265
+ ```
266
+
267
+ ### StreamingCursor
268
+
269
+ The blinking caret at the tail of a live stream (`effect/streaming-cursor`); inline, `h-[1lh]`, `aria-hidden`, blink shipped in the library stylesheet. Change the element with `render` (no `as` or `asChild`). Before the first token, show `element/loader`; inside a code block, set `compound/code-block`'s `isStreaming` prop instead.
270
+
271
+ ```tsx
272
+ <p>{streamedText}{isStreaming && <StreamingCursor className="ml-0.5" />}</p>
273
+ ```
274
+
275
+ ### Marker
276
+
277
+ Muted full-width row for out-of-band thread content — a date divider or a system event (`element/marker`). It is not a notification badge; an unread dot on an avatar is `Avatar.Badge`.
278
+
279
+ **Sub-components:** `Marker.Icon` (`aria-hidden` — put any meaning in the content text), `Marker.Content`; also `markerVariants`
280
+
281
+ **Props:** `variant` [default|separator|border] (`separator` flanks centered content with hairlines; `border` underlines the row), `render` (non-interactive elements only — an action goes inside `Marker.Content` as a `Link` or `Button`)
282
+
283
+ ```tsx
284
+ <Marker variant="separator">
285
+ <Marker.Content>Today</Marker.Content>
286
+ </Marker>
287
+ ```
119
288
 
120
- - `message` — docs-style message rows: `MessageGroup`, `Message`, `MessageAvatar`, `MessageHeader`, `MessageContent`, `MessageFooter`, `MessageStatus` (`status` [sending|sent|failed…] with icon + `data-status`)
121
- - `message-scroller` — pinned-to-bottom scroll container for feeds: `MessageScrollerProvider`, `MessageScroller`, `MessageScrollerViewport`, `MessageScrollerContent`, `MessageScrollerItem`, `MessageScrollerButton` (jump-to-latest); hooks `useMessageScroller`, `useMessageScrollerScrollable`, `useMessageScrollerVisibility`
122
- - `bubble` — chat-bubble rows: `BubbleGroup`, `Bubble`, `BubbleContent`, `BubbleReactions`
123
- - `attachment` — file chips for composer/thread: `Attachment` (+ `AttachmentGroup`, `AttachmentMedia`, `AttachmentContent`, `AttachmentTitle`, `AttachmentDescription`, `AttachmentActions`, `AttachmentAction`, `AttachmentTrigger`); `size` [default|sm], dashed border in idle state, `data-[state=error]` styling
124
- - `marker` — inline annotation row in a stream: `Marker`, `MarkerIcon`, `MarkerContent`; `variant` [default|separator|border]
289
+ ## Fallback-only concepts
125
290
 
126
- ## Other Collections
291
+ No nurix primitive — build each from its plain-spec fallback in [index.md](index.md):
127
292
 
128
- - `activity-timeline` — `GanttChart`: D3-based activity timeline (`Task`, `TaskStatus`, `ViewMode`, `GanttChartProps` types; `DEFAULT_STATUS_COLORS`, `getTaskDuration` helpers)
129
- - `changes-list` — `ChangesList`: grouped change-history feed (`ChangeItem`, `EntityConfig`, `OperationConfig`, `GroupBy` types)
130
- - `header` — `Header`: config-driven page header taking a single `config: HeaderConfig` (breadcrumbs, profile, status bar, grid, actions sections); sub-components `HeaderBreadcrumbs`, `HeaderActions`, `HeaderSection`, `HeaderCell`
131
- - `list` — `List`: data list with `ListToolbar`, `ListPagination`, `ListItemErrorBoundary`
132
- - `ui-table` — `UiTable`, `UiTableHeader`, `UiTableBody`, `UiTableFooter`, `UiTableHead`, `UiTableRow`, `UiTableCell`, `UiTableCaption`: bare HTML-table primitives; prefer `Table`
293
+ - `gantt-timeline`
294
+ - `chat-bubble`
295
+ - `json-viewer`
296
+ - `virtualized-list`
297
+ - `kanban-board`
298
+ - `event-calendar`
299
+ - `filter-toolbar`
300
+ - `vertical-timeline`
@@ -2,46 +2,107 @@
2
2
 
3
3
  > Generated reference (regen-owned; see [index.md](index.md)). Binds only under `primitives: nurix`.
4
4
 
5
- ## EmptyState
5
+ Components: Alert, Empty, ErrorBoundary, ErrorState, IssueReporter, Loader, NotificationBadge, Progress, Skeleton, SystemBanner, Toaster + toast
6
6
 
7
- Placeholder for empty content areas.
7
+ Each imports from its own subpath; a compound is one namespace with its parts as dotted members, e.g. `import { Empty } from "@nurix/components/compound/empty-state"` then `Empty.Header`, `Empty.Title`. Status colour is always `intent` (`neutral` | `info` | `success` | `warning` | `danger`, on the `--intent-*` tokens); a `danger` notice is `role="alert"`, every other intent `role="status"`, and a `role` you pass wins.
8
8
 
9
- **Props:** `icon`, `title`, `description`, `action`
9
+ ## Empty
10
+
11
+ Placeholder for an empty table body, panel, card or page (`compound/empty-state`).
12
+
13
+ **Sub-components:** `Empty.Header`, `Empty.Media` (`variant` [default|icon]), `Empty.Title`, `Empty.Description`, `Empty.Content`
14
+
15
+ - The root is unframed and fills a flex parent (`flex-1`); add `border` for the dashed frame, and a `min-h-*` or `h-full` inside a block-flow parent.
16
+ - `variant="icon"` draws the muted `size-10` tile, sizes an unsized `svg` to `size-6`, and is `aria-hidden`; the bare default stays exposed so an illustration `img` keeps its `alt`.
17
+ - Every part takes `render`; a root given a `role` is labelled by its Title. A 404 page is this composition — `min-h-[60vh]`, `<Empty.Title render={<h1 />}>`, a way back in `Empty.Content`; there is no `NotFound` export.
18
+ - Not for data in flight (`Skeleton`, `Loader`) or a failed load (`ErrorState`); `block/list` renders its own through its `emptyState` prop.
10
19
 
11
20
  ```tsx
12
- <EmptyState
13
- icon={<InboxIcon className="h-12 w-12 text-muted-foreground" />}
14
- title="No messages"
15
- description="Start a conversation to see messages here"
16
- action={<Button>New Message</Button>}
17
- />
21
+ <Empty className="border">
22
+ <Empty.Header>
23
+ <Empty.Media variant="icon"><Inbox /></Empty.Media>
24
+ <Empty.Title>No messages</Empty.Title>
25
+ <Empty.Description>Start a conversation to see messages here.</Empty.Description>
26
+ </Empty.Header>
27
+ <Empty.Content><Button>New message</Button></Empty.Content>
28
+ </Empty>
18
29
  ```
19
30
 
20
31
  ## Alert
21
32
 
22
- Dismissible status block; `Alert` is the container, `AlertTitle`/`AlertDescription` lay out the icon-led grid.
33
+ Inline notice that stays on the page until the user acts or navigates away (`compound/alert`).
23
34
 
24
- **Props:** `variant` [default|destructive], `dismissible`, `onDismiss`, `dismissButton` (custom node, overrides the default X button)
35
+ **Props:** `intent` [neutral|info|success|warning|danger] (default `neutral`, the card surface), `icon` (replaces the intent's glyph; `null` draws none), `onDismiss`, `labels` (`dismiss`), `render`
36
+ **Sub-components:** `Alert.Title`, `Alert.Description`
37
+
38
+ - Each status intent draws its own glyph; `neutral` draws none. Never place an `<svg>` child for the icon — pass `icon`.
39
+ - `onDismiss` draws the X in its own trailing column and never hides the alert — visibility stays in the parent (`{isVisible && <Alert … />}`).
40
+ - `<Alert.Title render={<h3 />}>` makes the title a real heading. A message that should clear itself is a toast.
25
41
 
26
42
  ```tsx
27
- <Alert variant="destructive" dismissible onDismiss={handleDismiss}>
28
- <TriangleAlert />
29
- <AlertTitle>Sync failed</AlertTitle>
30
- <AlertDescription>Retry in a few minutes.</AlertDescription>
43
+ <Alert intent="danger" onDismiss={() => setIsVisible(false)}>
44
+ <Alert.Title>Sync failed</Alert.Title>
45
+ <Alert.Description>Retry in a few minutes.</Alert.Description>
31
46
  </Alert>
32
47
  ```
33
48
 
34
- ## Other Components
49
+ ## Toaster + toast
50
+
51
+ Self-clearing notifications on Base UI Toast (`compound/toast`): mount `<Toaster />` once at the app root, call `toast()` from anywhere, inside or outside React.
52
+
53
+ **`toast` options:** `title`, `description`, `intent` (the only colour route, draws the glyph), `icon` (`null` draws none), `timeout` (ms; defaults to the dwell token, `Infinity` holds), `priority` [high|low] (default assertive for `danger`, polite otherwise), `action` (`{ label, onClick }`), `onDismiss`, `id` (reusing one updates in place)
54
+ **Methods:** `toast.promise(work, { pending, success, error })` (`error` always takes `danger`; a phase may be a function of the result), `toast.update(id, options)` (changes only the named fields), `toast.close(id?)`
55
+ **Toaster props:** `side` [top|bottom] (default `bottom`), `align` [start|center|end] (default `end`, logical), `hasCloseButton` (default `true`), `labels` (`region`, `dismiss`); parts `Toaster.Root`, `.Content`, `.Title`, `.Description`, `.Action`, `.Close`
56
+
57
+ - There are no `toast.success` / `toast.error` shortcuts — pass `intent`. Never mount a second `Toaster`: every one renders the same queue.
58
+
59
+ ```tsx
60
+ toast({ title: "Saved", description: "Your changes are live.", intent: "success" });
61
+ toast.promise(save(), { pending: "Saving…", success: "Saved", error: "Save failed" });
62
+ ```
63
+
64
+ ## SystemBanner
35
65
 
36
- - `loader` — `Loader`: five-dot pulse indicator; `size`, `gap`, `colors` (array, cycles if fewer than `numberOfDots`), `numberOfDots`, `height`
37
- - `progress` — `Progress`: Radix `Progress.Root`/`Indicator` pair; `value` (0–100)
38
- - `skeleton` — `Skeleton`: pulsing placeholder `div`; size it with `className` to match the final layout
39
- - `typing-indicator` — `TypingIndicator`: `users` (`string[]` or `{ id, name, avatar }[]`), `maxVisible` — stacked avatars plus "X and N others are typing…"
40
- - `error-boundary` — `ErrorBoundary`: class component catching render errors; `fallback`, `onError`, `onRetry` (default UI renders a retry link)
41
- - `not-found` — `NotFound`: built on `EmptyState`; `title`, `description`, `showHomeButton`, `onGoBack`
42
- - `sonner` — `Toaster`: theme-synced wrapper over `sonner`'s `Toaster`, watches `document.documentElement`'s `dark` class via a `MutationObserver`
43
- - `braille` — `Braille`: animated unicode-braille spinner; `spinner` (18 named patterns — `braille`, `braillewave`, `dna`, `scan`, `rain`, `scanline`, `pulse`, `snake`, `sparkle`, `cascade`, `columns`, `orbit`, `breathe`, `waverows`, `checkerboard`, `helix`, `fillsweep`, `diagswipe`), `interval` (ms, overrides the spinner's default), `label` (screen-reader text). The `inline-spinner` subpath exports the underlying frame registry and grid utilities: `spinners`, `gridToBraille`, `makeGrid`.
66
+ Shell-chrome banner above the app for environment, impersonation or outage states (`element/system-banner`); also exports `systemBannerVariants`.
67
+
68
+ **Props:** `title` (required), `description`, `intent` [neutral|info|success|warning|danger] (default `neutral`), `icon`, `action`, `onDismiss`, `labels` (`dismiss`, `actions`)
69
+ **Sub-components:** `SystemBanner.Stack` (orders and divides banners; placing it is yours)
70
+
71
+ - The banner stores no visibility: `onDismiss` renders the dismiss button and your callback unmounts it; omit it for a banner that must not be dismissed.
72
+ - `description` wraps on a narrow viewport. A message in content flow is `Alert`.
44
73
 
45
74
  ```tsx
46
- <Braille spinner="dna" label="Loading" />
75
+ <SystemBanner.Stack>
76
+ <SystemBanner intent="warning" title="Sandbox environment" description="Data resets nightly" />
77
+ <SystemBanner title="Viewing as Dana" action={<Button size="sm">Exit view-as</Button>} />
78
+ </SystemBanner.Stack>
47
79
  ```
80
+
81
+ ## IssueReporter
82
+
83
+ Report-a-problem dialog with screenshot capture and an environment snapshot (`block/issue-reporter`). Requires the optional peer `html2canvas-pro` — without it the DOM raster fails on every capture and each one falls back to a `getDisplayMedia` permission prompt.
84
+
85
+ **Props:** `onSubmit(report)` (required; a rejection keeps the dialog open, a resolution closes and resets), `open` / `defaultOpen` / `onOpenChange`, `shortcut` (e.g. `"mod+shift+i"`; default `null`), `shouldCaptureOnOpen` (default `true`), `fields` (default one `textarea` named `text`; types text|textarea|number|select|radio|checkbox), `defaultValue`, `metadata` (value or function, resolved at submit), `maxScreenshots` (default 3), `diagnosticsSections` (default all; `[]` turns collection off), `renderDiagnostics`, `captureOptions` (`ignoreElement`, `maxWidth`, `imageType`…), `onCaptureError`, `labels`
86
+ **Also exports:** `useScreenCapture(options)` → `[state, actions]`, `useDiagnostics({ sections, collectOnMount })` → `{ data, isLoading, error, refresh }`, `DIAGNOSTICS_SECTIONS` (`system`, `capabilities`, `webVitals`, `storage`, `serviceWorkers`), `CaptureError`
87
+
88
+ - `IssueReport` carries `fields`, `screenshots`, `diagnostics`, `metadata`. Submit stays disabled until every `required` field is set **and** a field holds a value or a screenshot is attached.
89
+ - A report needing no screenshot or diagnostics is a plain form over `compound/dialog`, not this.
90
+
91
+ ```tsx
92
+ <IssueReporter
93
+ shortcut="mod+shift+i"
94
+ fields={[{ name: "text", type: "textarea", label: "What went wrong?", required: true }]}
95
+ metadata={() => ({ route: location.pathname })}
96
+ onSubmit={async (report) => sendToTracker(report)}
97
+ />
98
+ ```
99
+
100
+ ## Other Components
101
+
102
+ - `loader` — `Loader` (`element/loader`): em-sized lattice in `currentColor` for open-ended work; `status` [pending|success|error] (default `pending`) settles the same glyph into a check or a cross, `label`, `successLabel`, `errorLabel`, `hasTimer`, `labels`. A "Thinking…" or typing row is `<Loader label="Typing" />` — there is no dots variant.
103
+ - `progress` — `Progress` (`element/progress`): `value` (0–100, clamped; `null` is indeterminate), `label` (the accessible name), `variant` [linear|circular|fill] (default `linear`), `size` [sm|default|lg] (not on `fill`), `format` / `getAriaValueText` to reword the announcement.
104
+ - `skeleton` — `Skeleton` (`element/skeleton`): pulsing `aria-hidden` `div` with no intrinsic size — size it with `className`, mark the container `aria-busy`, and pass `render={<span />}` inside phrasing content.
105
+ - `notification-badge` — `NotificationBadge` (`element/notification-badge`): `value` (pass the real count; renders nothing at zero), `max` (default 99, shows `99+`), `variant` [count|dot], `intent`, `labels` (`dot`); em-sized from the text beside it, positioned by you on a `relative` trigger.
106
+ - `error-state` — `ErrorState` (`compound/error-state`): failed-load block on Empty's geometry; `description` (required), `title`, `icon`, `onRetry` (renders Retry), `labels` (`retry`). Carries no live role; `block/list`, `block/table` and `block/tree` take `error={{ message, onRetry }}` instead.
107
+ - `error-boundary` — `ErrorBoundary` (`lib/error-boundary`): catches render errors in the subtree it wraps; `onError` (its only way out — it logs nothing), `onRetry` (refetch here, or the subtree throws again), `fallback` (replaces the whole card, retry included).
108
+ - No nurix primitive: `text-spinner` — build from the `index.md` fallback spec.