@terreno/mcp 57.12.0 → 57.13.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.
- package/dist/docs/upgrades/57.13.0.md +75 -0
- package/dist/docs/versioned/next/explanation/README.md +1 -1
- package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
- package/dist/docs/versioned/next/explanation/example-coverage.md +3 -3
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
- package/dist/docs/versioned/next/how-to/README.md +2 -0
- package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
- package/dist/docs/versioned/next/how-to/circleci.md +2 -0
- package/dist/docs/versioned/next/how-to/preview-demo.md +5 -5
- package/dist/docs/versioned/next/how-to/rate-limiting.md +17 -0
- package/dist/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
- package/dist/docs/versioned/next/reference/README.md +1 -1
- package/dist/docs/versioned/next/reference/admin-backend.md +26 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +15 -0
- package/dist/docs/versioned/next/reference/ai.md +55 -3
- package/dist/docs/versioned/next/reference/api.md +4 -0
- package/dist/docs/versioned/next/reference/blocks.md +243 -11
- package/dist/docs/versioned/next/reference/environment-variables.md +3 -0
- package/dist/docs/versioned/next/reference/ui.md +212 -10
- package/dist/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
- package/package.json +3 -3
- package/src/docs/upgrades/57.13.0.md +75 -0
- package/src/docs/versioned/next/explanation/README.md +1 -1
- package/src/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
- package/src/docs/versioned/next/explanation/example-coverage.md +3 -3
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
- package/src/docs/versioned/next/how-to/README.md +2 -0
- package/src/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
- package/src/docs/versioned/next/how-to/circleci.md +2 -0
- package/src/docs/versioned/next/how-to/preview-demo.md +5 -5
- package/src/docs/versioned/next/how-to/rate-limiting.md +17 -0
- package/src/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
- package/src/docs/versioned/next/reference/README.md +1 -1
- package/src/docs/versioned/next/reference/admin-backend.md +26 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +15 -0
- package/src/docs/versioned/next/reference/ai.md +55 -3
- package/src/docs/versioned/next/reference/api.md +4 -0
- package/src/docs/versioned/next/reference/blocks.md +243 -11
- package/src/docs/versioned/next/reference/environment-variables.md +3 -0
- package/src/docs/versioned/next/reference/ui.md +212 -10
- package/src/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
|
@@ -54,7 +54,7 @@ A dataset name matches `^[a-z][a-z0-9_]{0,63}$`. A document has at most 8 datase
|
|
|
54
54
|
| `image` | `alt`, `src` | `id` |
|
|
55
55
|
| `details` | `title`, `text` | `id` |
|
|
56
56
|
|
|
57
|
-
`src` is a `data:image` URL, a `file:` ref (`file:` plus an id), or an `https` URL. `https` is allowed only when the hostname is in `validateBlocks` `imageHosts` (the server option is `uiBlocks.imageHosts`). Anything else is `IMAGE_HOST_NOT_ALLOWED`. An empty list rejects every `https` image. A ref dataset `id` uses 1–80 letters, digits, underscores, or hyphens. A block or action-element `id` stays lowercase and starts with a letter. `limit` of `0` is `INVALID_TYPE` (an integer of at least 1), not `TOO_SHORT`.
|
|
57
|
+
`src` (on an `image` block, on each `gallery` tile, and on a `list` item's `image`) is a `data:image` URL, a `file:` ref (`file:` plus an id), or an `https` URL. `https` is allowed only when the hostname is in `validateBlocks` `imageHosts` (the server option is `uiBlocks.imageHosts`). Anything else is `IMAGE_HOST_NOT_ALLOWED`. An empty list rejects every `https` image. A ref dataset `id` uses 1–80 letters, digits, underscores, or hyphens. A block or action-element `id` stays lowercase and starts with a letter. `limit` of `0` is `INVALID_TYPE` (an integer of at least 1), not `TOO_SHORT`.
|
|
58
58
|
|
|
59
59
|
Warnings do not block rendering:
|
|
60
60
|
|
|
@@ -74,15 +74,153 @@ Warnings do not block rendering:
|
|
|
74
74
|
| `badge` | `text` (1–80) | `status`: `info`, `error`, `warning`, `success`, `neutral`, `active`; `id` | `Badge` |
|
|
75
75
|
| `divider` | — | `id` | `SectionDivider` |
|
|
76
76
|
| `context` | `text` (1–280) | `id` | `Text` |
|
|
77
|
+
| `stepper` | see [Stepper](#stepper) | | `IconButton` − and +, `Text`, `Heading` |
|
|
78
|
+
| `checklist` | see [Checklist](#checklist) | | `CheckBox` rows, `Text`, `Heading` |
|
|
79
|
+
| `gallery` | see [Gallery](#gallery) | | `Image` tiles in a row or grid, `Text` captions |
|
|
80
|
+
| `list` | see [List](#list) | | `Image` thumbnails beside `Text` title, text, and meta |
|
|
77
81
|
| `columns` | `children`: 2–4 blocks | `id` | `Box` row |
|
|
78
|
-
| `card` | `children`: at least 1 block | `title` (1–120), `id` | `Card` |
|
|
82
|
+
| `card` | `children`: at least 1 block | `title` (1–120), `eyebrow` (1–60), `id` | `Card` |
|
|
79
83
|
|
|
80
84
|
`columns` and `card` sit at the top level. Their children are leaf blocks. A layout block
|
|
81
85
|
inside another layout block is `DEPTH_EXCEEDED`. Every block counts toward the 50-block
|
|
82
86
|
cap, including the `card` or `columns` block itself, so a card cannot hold 50 children.
|
|
83
87
|
|
|
88
|
+
A card's `eyebrow` is a short label that sits small and muted above the `title` (or above the
|
|
89
|
+
children when there is no title). `BlocksView` draws it as a `Text` in the same small, muted style
|
|
90
|
+
as the stepper label, with test ID `<card path>-eyebrow`.
|
|
91
|
+
|
|
84
92
|
`id` matches `^[a-z][a-z0-9_]{0,63}$`.
|
|
85
93
|
|
|
94
|
+
## Stepper
|
|
95
|
+
|
|
96
|
+
A `stepper` is a − value + control. Each tap calls a host callback, and the host returns the
|
|
97
|
+
replacement stepper (see `scaleStepperHostAction` in [the AI reference](ai.md)).
|
|
98
|
+
|
|
99
|
+
| Field | Required | Rule |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `id` | yes | Block id pattern, at most 54 characters |
|
|
102
|
+
| `label` | yes | 1–80 |
|
|
103
|
+
| `value`, `min`, `max` | yes | Numbers. `min` < `max` and `min` ≤ `value` ≤ `max`, or `OUT_OF_RANGE` |
|
|
104
|
+
| `step` | no | Above 0 (`OUT_OF_RANGE`). Default 1 |
|
|
105
|
+
| `unit` | no | 1–40, shown under the value ("People") |
|
|
106
|
+
| `callback` | yes | `{name, payload?}`. `name` is checked by `UNKNOWN_HOST_ACTION` against `stepperActions` when that is passed, otherwise against `hostActions` |
|
|
107
|
+
| `itemsTitle` | no | 1–80, a small heading above the items |
|
|
108
|
+
| `items` | no | 0–12 of `{label (1–80), amount, unit? (1–20), decimals? (0–3, default 0), round?: nearest or up (default nearest)}`. `decimals` below 0 or above 3 is `OUT_OF_RANGE` |
|
|
109
|
+
| `note` | no | 1–280, muted under the items |
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
- type: stepper
|
|
113
|
+
id: guests
|
|
114
|
+
label: Number of people
|
|
115
|
+
unit: People
|
|
116
|
+
value: 5
|
|
117
|
+
min: 1
|
|
118
|
+
max: 20
|
|
119
|
+
callback: {name: scaleStepper}
|
|
120
|
+
items:
|
|
121
|
+
- {label: Bone-in leg of lamb, amount: 2, unit: kg, decimals: 1}
|
|
122
|
+
- {label: Carrots, amount: 8, round: up}
|
|
123
|
+
note: Generous portions, with a little extra.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The renderer uses the element ids `<id>_decrease` and `<id>_increase` for the − and + buttons
|
|
127
|
+
(`stepperElementIds(id)`). Another block or action element with either id is `DUPLICATE_ID`.
|
|
128
|
+
A tap sends `{kind: callback, name, payload: {...payload, value: value ± step}}` with
|
|
129
|
+
`blockId: <id>`.
|
|
130
|
+
|
|
131
|
+
## Checklist
|
|
132
|
+
|
|
133
|
+
A `checklist` is a list of tickable items with an "n of m" counter. `BlocksView` draws the title
|
|
134
|
+
and counter, then one `CheckBox` row per item: the bold `text`, then the small muted `meta` (such
|
|
135
|
+
as a time), then the muted `detail`. With a `callback` whose name is in `hostActions` (or with `hostActions` omitted), a
|
|
136
|
+
tick calls `onAction` with `blockId: <id>`, `elementId: <id>_<item id>`, and
|
|
137
|
+
`{kind: "callback", name, payload: {...payload, itemId, checked, state}}`. `state` maps every
|
|
138
|
+
item id to its tick after this one. `BlocksView` disables the checklist while a tick is
|
|
139
|
+
pending, and the tick shows only when the host's returned checklist arrives as an override.
|
|
140
|
+
Without a `callback`, or when its name is not in `hostActions`, ticks stay on the device. On the
|
|
141
|
+
server, `toggleChecklistHostAction` from `@terreno/ai` handles the callback and returns the stored
|
|
142
|
+
checklist with `checked` from `state` (see [Host actions](ai.md#host-actions)).
|
|
143
|
+
|
|
144
|
+
| Field | Required | Rule |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `id` | yes | Block id pattern, at most 31 characters |
|
|
147
|
+
| `title` | no | 1–120 |
|
|
148
|
+
| `callback` | no | `{name, payload?}`. `name` is checked by `UNKNOWN_HOST_ACTION` against `checklistActions` when that is passed, otherwise against `hostActions` |
|
|
149
|
+
| `items` | yes | 1–30 of `{id, text (1–120), detail? (1–280), meta? (1–40), checked?: boolean}`. An item `id` uses the block id pattern, at most 32 characters, and is unique in the checklist (`DUPLICATE_ID`) |
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
- type: checklist
|
|
153
|
+
id: cooking
|
|
154
|
+
title: Cooking checklist
|
|
155
|
+
callback: {name: toggleChecklist}
|
|
156
|
+
items:
|
|
157
|
+
- {id: oven, meta: "1:00 pm", text: Preheat the oven, detail: "220 C, fan off.", checked: true}
|
|
158
|
+
- {id: lamb_in, meta: "1:30 pm", text: Put the lamb in}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Each item's tick has the reserved element id `<id>_<item id>` (`checklistElementId(id, itemId)`).
|
|
162
|
+
Another block or action element with that id, or a stepper or checklist that derives the same
|
|
163
|
+
element id, is `DUPLICATE_ID`.
|
|
164
|
+
|
|
165
|
+
## Gallery
|
|
166
|
+
|
|
167
|
+
A `gallery` is a set of 2–6 photos shown together. `BlocksView` draws up to three in one row of
|
|
168
|
+
equal 4:3 tiles and wraps more into a three-column grid. On a narrow screen the tiles keep a
|
|
169
|
+
minimum width and the row scrolls sideways (see [BlocksView](ui.md#blocksview)).
|
|
170
|
+
|
|
171
|
+
| Field | Required | Rule |
|
|
172
|
+
| --- | --- | --- |
|
|
173
|
+
| `id` | no | Block id pattern |
|
|
174
|
+
| `images` | yes | 2–6 of `{src, alt (1–200), caption? (1–120)}`. Fewer is `TOO_FEW`, more is `TOO_MANY` |
|
|
175
|
+
|
|
176
|
+
Each tile's `src` follows the `image` rules above. A tile that breaks them is
|
|
177
|
+
`IMAGE_HOST_NOT_ALLOWED` at that tile's path, such as `blocks[0].images[2].src`.
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
- type: gallery
|
|
181
|
+
id: roast_photos
|
|
182
|
+
images:
|
|
183
|
+
- {src: "file:roast-lamb", alt: Roast leg of lamb on a carving board, caption: Roast lamb}
|
|
184
|
+
- {src: "file:roast-potatoes", alt: Crisp roast potatoes in a tray}
|
|
185
|
+
- {src: "https://images.example.com/carrots.jpg", alt: Glazed carrots, caption: "Honey, thyme, and butter"}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The third tile validates only when `imageHosts` includes `images.example.com`.
|
|
189
|
+
|
|
190
|
+
## List
|
|
191
|
+
|
|
192
|
+
A `list` is a stack of 1–12 entries, each with a title and an optional thumbnail. `BlocksView`
|
|
193
|
+
draws each item as a row: a 3:4 portrait thumbnail on the left, then the small muted `meta`, the
|
|
194
|
+
bold `title`, and the muted `text` (see [BlocksView](ui.md#blocksview)).
|
|
195
|
+
|
|
196
|
+
| Field | Required | Rule |
|
|
197
|
+
| --- | --- | --- |
|
|
198
|
+
| `id` | no | Block id pattern |
|
|
199
|
+
| `items` | yes | 1–12 items. Fewer is `TOO_FEW`, more is `TOO_MANY` |
|
|
200
|
+
| `items[].title` | yes | 1–120 characters |
|
|
201
|
+
| `items[].text` | no | 1–500 characters of plain text, not markdown |
|
|
202
|
+
| `items[].meta` | no | 1–40 characters, a short label such as a time or a price |
|
|
203
|
+
| `items[].image` | no | `{src, alt}`. `alt` is 1–200 characters, the same cap as an `image` block's `alt` (`BLOCK_LIMITS.headingTextMaxLength`) |
|
|
204
|
+
|
|
205
|
+
An item image's `src` follows the `image` rules above. One that breaks them is
|
|
206
|
+
`IMAGE_HOST_NOT_ALLOWED` at that item's path, such as `blocks[0].items[1].image.src`.
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
- type: list
|
|
210
|
+
id: menu
|
|
211
|
+
items:
|
|
212
|
+
- title: Roast leg of lamb
|
|
213
|
+
text: Rubbed with garlic and rosemary, then rested for 20 minutes.
|
|
214
|
+
meta: Main
|
|
215
|
+
image: {src: "file:roast-lamb", alt: Roast leg of lamb on a carving board}
|
|
216
|
+
- title: Honey carrots
|
|
217
|
+
text: "Glazed with honey, thyme, and butter."
|
|
218
|
+
image: {src: "https://images.example.com/carrots.jpg", alt: Glazed carrots}
|
|
219
|
+
- title: Mint sauce
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The second item validates only when `imageHosts` includes `images.example.com`.
|
|
223
|
+
|
|
86
224
|
## Actions
|
|
87
225
|
|
|
88
226
|
An `actions` block requires `id` and `elements` (1–25). An element is a `button` or a
|
|
@@ -94,10 +232,76 @@ An `actions` block requires `id` and `elements` (1–25). An element is a `butto
|
|
|
94
232
|
| `segmented` | `id`, `target` (a chart or table `id`), `options` (2–8 of `{label, data}`) | — |
|
|
95
233
|
|
|
96
234
|
`action.kind` is `reply` (`text`), `open` (`url` or `route`, exactly one), `select`
|
|
97
|
-
(`target` plus `data`),
|
|
98
|
-
that is not a chart or table `id`
|
|
235
|
+
(`target` plus `data`), `callback` (`name` plus optional `payload`), or `copy` (`text` or
|
|
236
|
+
`target`, exactly one; see [Copy](#copy)). A `select` target that is not a chart or table `id`
|
|
237
|
+
is `SELECT_TARGET_INVALID`. When `validateBlocks` is
|
|
99
238
|
called with `hostActions`, a callback `name` outside that list is `UNKNOWN_HOST_ACTION`.
|
|
100
|
-
Omitting `hostActions` skips that check.
|
|
239
|
+
Omitting `hostActions` skips that check. A stepper `callback.name` is checked against
|
|
240
|
+
`stepperActions` instead when that list is passed, so a stepper that names a registered action
|
|
241
|
+
that does not handle steppers is `UNKNOWN_HOST_ACTION`. `checklistActions` does the same for
|
|
242
|
+
checklist callbacks.
|
|
243
|
+
|
|
244
|
+
## Copy
|
|
245
|
+
|
|
246
|
+
A `copy` action puts text on the device clipboard. It never reaches the host, so `hostActions`
|
|
247
|
+
does not apply to it. It is a `button` element's `action`:
|
|
248
|
+
|
|
249
|
+
| Field | Rule |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `text` | 1–4,000 characters (`BLOCK_LIMITS.copyTextMaxLength`), copied as written |
|
|
252
|
+
| `target` | The `id` of a `stepper`, `checklist`, `list`, `table`, or `text` block in this document (`COPY_TARGET_TYPES`). Any other block, or an id no block has, is `COPY_TARGET_INVALID` |
|
|
253
|
+
|
|
254
|
+
Set exactly one of the two. Neither or both is `MISSING_REQUIRED` at the action's path, as for
|
|
255
|
+
`open`. The target may sit anywhere in the document, before or after the button, inside a
|
|
256
|
+
`card` or `columns`.
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
v: 1
|
|
260
|
+
blocks:
|
|
261
|
+
- type: stepper
|
|
262
|
+
id: guests
|
|
263
|
+
label: Number of people
|
|
264
|
+
unit: People
|
|
265
|
+
value: 5
|
|
266
|
+
min: 1
|
|
267
|
+
max: 20
|
|
268
|
+
callback: {name: scaleStepper}
|
|
269
|
+
items:
|
|
270
|
+
- {label: Bone-in leg of lamb, amount: 2, unit: kg, decimals: 1}
|
|
271
|
+
- {label: Carrots, amount: 8}
|
|
272
|
+
- type: actions
|
|
273
|
+
id: shopping
|
|
274
|
+
elements:
|
|
275
|
+
- type: button
|
|
276
|
+
id: copy_list
|
|
277
|
+
text: Copy shopping list
|
|
278
|
+
variant: outline
|
|
279
|
+
action: {kind: copy, target: guests}
|
|
280
|
+
- type: button
|
|
281
|
+
id: copy_note
|
|
282
|
+
text: Copy note
|
|
283
|
+
action: {kind: copy, text: "Lamb 2 kg, carrots 8, mint sauce."}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`blockPlainText(block, {datasets?, checked?})` turns a target block into the copied text. Lines
|
|
287
|
+
are joined with `\n`, with no trailing newline.
|
|
288
|
+
|
|
289
|
+
| Block | Text |
|
|
290
|
+
| --- | --- |
|
|
291
|
+
| `stepper` | `<label>: <value> <unit>`, then one `<item label>: <amount> <unit>` line per item. Amounts use the item's `decimals` (2 at 1 decimal is `2.0`). A missing unit leaves no trailing space. For the example above: `Number of people: 5 People`, `Bone-in leg of lamb: 2.0 kg`, `Carrots: 8` |
|
|
292
|
+
| `checklist` | One `[x] <text>` or `[ ] <text>` line per item. `checked` maps item ids to the current ticks and overrides each item's `checked`; ids the checklist lacks are ignored |
|
|
293
|
+
| `list` | One `- <title>: <text>` line per item, or `- <title>` when the item has no text |
|
|
294
|
+
| `table` | A header row of column names (the table's `columns` in order, or every dataset column) with each `_` shown as a space, as the table on screen shows them (`lamb_kg` is `lamb kg`), then one row per dataset row, tab-separated. A `null` cell is empty. The table's dataset must be inline in `datasets`; a missing dataset or an unresolved `ref` returns an empty string, so resolve refs first |
|
|
295
|
+
| `text` | The `markdown` as written |
|
|
296
|
+
|
|
297
|
+
Any other block returns an empty string. Pass the block as it is shown, with a host's
|
|
298
|
+
replacement block applied, so a stepper at 6 copies the scaled amounts.
|
|
299
|
+
|
|
300
|
+
`BlocksView` performs the copy on the device. A press builds the text with `blockPlainText`
|
|
301
|
+
from the target as it is shown now (the host's override, the device's checklist ticks, and the
|
|
302
|
+
table's selected dataset once it has rows), writes it with `expo-clipboard`, and shows "Copied"
|
|
303
|
+
next to the button for 2 seconds. It never calls `onAction`. See
|
|
304
|
+
[BlocksView](./ui.md#blocksview).
|
|
101
305
|
|
|
102
306
|
## Partial parsing
|
|
103
307
|
|
|
@@ -122,6 +326,21 @@ Unknown fields fail with `UNKNOWN_KEY`. `v` must be `1` (`UNSUPPORTED_VERSION`).
|
|
|
122
326
|
| `columns` children | 2–4 |
|
|
123
327
|
| Elements in one `actions` block | 25 |
|
|
124
328
|
| Text in one `text` block | 4,000 characters |
|
|
329
|
+
| Items in one `stepper` | 12 |
|
|
330
|
+
| `stepper` id | 54 characters |
|
|
331
|
+
| Items in one `checklist` | 1–30 |
|
|
332
|
+
| `checklist` id | 31 characters |
|
|
333
|
+
| `checklist` item id | 32 characters |
|
|
334
|
+
| Images in one `gallery` | 2–6 |
|
|
335
|
+
| `gallery` image `alt` | 200 characters |
|
|
336
|
+
| `gallery` image `caption` | 120 characters |
|
|
337
|
+
| Items in one `list` | 1–12 |
|
|
338
|
+
| `list` item `title` | 120 characters |
|
|
339
|
+
| `list` item `text` | 500 characters |
|
|
340
|
+
| `list` item `meta` | 40 characters |
|
|
341
|
+
| `list` item image `alt` | 200 characters |
|
|
342
|
+
| `card` `eyebrow` | 60 characters |
|
|
343
|
+
| `copy` action `text` | 4,000 characters |
|
|
125
344
|
| Document version | 1 |
|
|
126
345
|
|
|
127
346
|
## Errors
|
|
@@ -134,19 +353,21 @@ heuristics and does not fail `ok`.
|
|
|
134
353
|
| --- | --- |
|
|
135
354
|
| `COLUMN_NOT_FOUND` | A chart or table names a column the dataset does not have. |
|
|
136
355
|
| `COLUMN_TYPE_MISMATCH` | A column value, or a chart axis, does not match the column type. |
|
|
356
|
+
| `COPY_TARGET_INVALID` | A copy action's `target` names a block that is not a `stepper`, `checklist`, `list`, `table`, or `text` block, or an id no block in the document has. |
|
|
137
357
|
| `DATASET_NOT_FOUND` | A chart or table names a dataset the document does not define. |
|
|
138
358
|
| `DATASET_TOO_LARGE` | A dataset has more than 500 rows or 12 columns. |
|
|
139
359
|
| `DEPTH_EXCEEDED` | A `columns` or `card` block is nested inside another layout block. |
|
|
140
|
-
| `DUPLICATE_ID` | A block id, an action-element id, or a column name is used more than once. |
|
|
360
|
+
| `DUPLICATE_ID` | A block id, an action-element id, or a column name is used more than once, an id takes a stepper's `<id>_decrease` or `<id>_increase` or a checklist's `<id>_<item id>`, or a checklist repeats an item id. |
|
|
141
361
|
| `HTML_DISABLED` | An html block is present and this host has not turned HTML on. |
|
|
142
362
|
| `HTML_TOO_LARGE` | An html block is larger than 100,000 bytes. |
|
|
143
|
-
| `IMAGE_HOST_NOT_ALLOWED` | An image URL is not a `data:image` URL, a `file:` ref, or an `https` URL on an allowed host. |
|
|
363
|
+
| `IMAGE_HOST_NOT_ALLOWED` | An `image`, `gallery` tile, or `list` item image URL is not a `data:image` URL, a `file:` ref, or an `https` URL on an allowed host. A gallery error names the tile (`blocks[0].images[2].src`); a list error names the item (`blocks[0].items[1].image.src`). |
|
|
144
364
|
| `INVALID_ENUM` | A value is not one of the allowed values. |
|
|
145
365
|
| `INVALID_FORMAT` | A string does not match its required format. |
|
|
146
|
-
| `INVALID_TYPE` | A value has the wrong type, including a
|
|
366
|
+
| `INVALID_TYPE` | A value has the wrong type, including a ref `limit` below 1. |
|
|
147
367
|
| `KEY_ORDER` | Top-level keys are not in the order `v`, `datasets`, `blocks`. |
|
|
148
|
-
| `MISSING_REQUIRED` | A required field is missing. |
|
|
368
|
+
| `MISSING_REQUIRED` | A required field is missing, or an `open` or `copy` action sets neither or both of its two fields. |
|
|
149
369
|
| `NOT_A_DOCUMENT` | The reply is not one YAML or JSON mapping with a `v` field. |
|
|
370
|
+
| `OUT_OF_RANGE` | A number is outside its allowed range: a stepper `value` outside `min` and `max`, `min` not below `max`, `step` at or below 0, or `decimals` below 0 or above 3. |
|
|
150
371
|
| `SELECT_TARGET_INVALID` | A select action names a block that is not a chart or table. |
|
|
151
372
|
| `ROW_ARITY_MISMATCH` | A dataset row does not have one value per column. |
|
|
152
373
|
| `TABLE_TOO_WIDE` | A table lists more than 12 columns. |
|
|
@@ -167,12 +388,23 @@ heuristics and does not fail `ok`.
|
|
|
167
388
|
| --- | --- |
|
|
168
389
|
| `parseBlocks(text)` | Fence strip, YAML or JSON parse |
|
|
169
390
|
| `parseBlocksPartial(text)` | Completed top-level blocks while a reply is still streaming |
|
|
170
|
-
| `validateBlocks(doc, options?)` | Structure, then dataset, chart, table, action, and html lint. `options.knownDatasets` checks `ref` columns. `options.hostActions` checks callback names. `options.allowHtml` allows `html` blocks. |
|
|
391
|
+
| `validateBlocks(doc, options?)` | Structure, then dataset, chart, table, action, stepper, checklist, image, gallery, and list image src, and html lint. `options.knownDatasets` checks `ref` columns. `options.hostActions` checks callback names. `options.stepperActions` and `options.checklistActions`, when set, check stepper and checklist `callback.name` instead (`UNKNOWN_HOST_ACTION`). `options.allowHtml` allows `html` blocks. |
|
|
171
392
|
| `wrapAsTextDocument(text)` | Display fallback for a non-document |
|
|
172
393
|
| `blocksSchema` | Zod schema |
|
|
173
394
|
| `blocksJsonSchema` | JSON Schema for the same structure |
|
|
174
|
-
| `blocksPromptSection({hostActions, allowHtml})` | System-prompt section. Limits come from `BLOCK_LIMITS`. `allowHtml` adds the `html` block. |
|
|
395
|
+
| `blocksPromptSection({hostActions, allowHtml, imageHosts, richBlocks, stepperActions, checklistActions})` | System-prompt section. Limits come from `BLOCK_LIMITS`. `allowHtml` adds the `html` block. `richBlocks` (default `true`) offers the rich blocks; `false` returns the prompt as it was before them, whatever the other lists hold. With `richBlocks` on and a non-empty `stepperActions`, the prompt adds `stepper`, its rules and limits, and names those actions as its callbacks; without one it never mentions `stepper`. With `richBlocks` on, the prompt adds `checklist`, `gallery`, and `list` with their limits, plus the `card` `eyebrow` and its limit; gallery tile and list item image srcs follow the same image src line as `image`, so `https` is offered only with `imageHosts`. The word `list` appears in the prompt only as this block's name. A non-empty `checklistActions` is named as the callback to set; without one the prompt says to leave `callback` out, so ticks stay local. With `richBlocks` on, one line offers the `copy` action with its text limit and its target block types, naming `stepper` only when the stepper is offered. |
|
|
175
396
|
| `BLOCK_LIMITS` | The numbers in the table above |
|
|
176
397
|
| `BLOCK_ERROR_CODES` | The codes in the table above |
|
|
177
398
|
| `BLOCK_WARNING_CODES` | `BAR_TOO_MANY_CATEGORIES`, `DONUT_TOO_MANY_SLICES`, `LINE_SINGLE_POINT` |
|
|
399
|
+
| `stepperElementIds(id)` | `{decrease, increase}`: the element ids of a stepper's buttons |
|
|
400
|
+
| `checklistElementId(id, itemId)` | `<id>_<item id>`: the element id of a checklist item's tick. `ChecklistBlock`, `ChecklistItem`, and `ChecklistCallback` are type exports. |
|
|
401
|
+
| `scaleStepperBlock(original, value)` | The stepper at `value` with each item's `amount` scaled linearly from `original` (`amount × value / original value`), rounded to its `decimals` (`round: up` rounds up). An `original` with `value: 0` keeps its amounts. Always pass the block the agent wrote, so rounding does not drift. `scaleStepperHostAction` and the demo playground use it |
|
|
402
|
+
| `isStepperValueAllowed(stepper, value)` | `true` when `value` is finite, within `min` and `max`, and a whole number of `step`s from the stepper's `value` |
|
|
403
|
+
| `applyChecklistState(checklist, {itemId, checked, state})` | The checklist with each item's `checked` from `state`; an item missing from `state` is unchecked and `itemId` takes `checked`. `toggleChecklistHostAction` and the demo playground use it. `ChecklistTick` is a type export |
|
|
404
|
+
| `unknownChecklistItemIds(checklist, tick)` | The ids in a tick's `itemId` and `state` that the checklist has no item for, each once |
|
|
405
|
+
| `blockPlainText(block, {datasets?, checked?})` | The text a copy action copies for its target block (see [Copy](#copy)). `BlockPlainTextOptions` is a type export |
|
|
406
|
+
| `COPY_TARGET_TYPES` | `stepper`, `checklist`, `list`, `table`, `text`: the blocks a copy `target` may name. `CopyAction` is a type export |
|
|
407
|
+
| `GalleryBlock`, `GalleryImage` | Type exports for the `gallery` block and its tiles |
|
|
408
|
+
| `ListBlock`, `ListItem`, `ListItemImage` | Type exports for the `list` block, its items, and their thumbnails |
|
|
409
|
+
| `STEPPER_ROUNDING` | `nearest`, `up`. `StepperBlock`, `StepperItem`, and `StepperCallback` are type exports. |
|
|
178
410
|
| `HTML_HEIGHTS`, `CALLOUT_STATUSES` | Allowed `html` heights and `callout` statuses. `HtmlBlock`, `CalloutBlock`, `ImageBlock`, and `DetailsBlock` are type exports. |
|
|
@@ -136,9 +136,12 @@ Resolution order for API base URL (`rtk/src/constants.ts`):
|
|
|
136
136
|
| Variable | Read by | Required | Default | Secret | Scope |
|
|
137
137
|
|----------|---------|----------|---------|--------|-------|
|
|
138
138
|
| `GCS_BUCKET` | example-backend, admin | ❌ | — | No | server |
|
|
139
|
+
| `GCS_FOLDER_PREFIX` | example-backend `DocumentStorageApp` (`folderPrefix`) | ❌ | — | No | server |
|
|
139
140
|
| `GCS_PROJECT_ID` | GCS clients | ❌ | — | No | server |
|
|
140
141
|
| `GCS_SERVICE_ACCOUNT_KEY` | GCS clients | ❌ | — | Yes | server |
|
|
141
142
|
|
|
143
|
+
Example-backend Cloud Run deploys set `GCS_BUCKET` (default `flourish-terreno-example-documents`). Preview deploys also set `GCS_FOLDER_PREFIX=pr-<number>/`.
|
|
144
|
+
|
|
142
145
|
## Feature flags
|
|
143
146
|
|
|
144
147
|
| Variable | Read by | Required | Default | Secret | Scope |
|
|
@@ -61,6 +61,55 @@ const customStyle: StyleProp<ViewStyle> = {
|
|
|
61
61
|
- Ensures type compatibility when passing styles to @terreno/ui components
|
|
62
62
|
- Simplifies imports (one package instead of two)
|
|
63
63
|
|
|
64
|
+
## Theme modes
|
|
65
|
+
|
|
66
|
+
`TerrenoProvider` defaults to the Flourish Health light mode exported from
|
|
67
|
+
Figma. Select the matching Figma dark mode explicitly or follow the operating
|
|
68
|
+
system:
|
|
69
|
+
|
|
70
|
+
``````tsx
|
|
71
|
+
import {TerrenoProvider} from "@terreno/ui";
|
|
72
|
+
|
|
73
|
+
<TerrenoProvider colorScheme="system">
|
|
74
|
+
<App />
|
|
75
|
+
</TerrenoProvider>
|
|
76
|
+
``````
|
|
77
|
+
|
|
78
|
+
`colorScheme` accepts `"light"`, `"dark"`, or `"system"` and defaults to
|
|
79
|
+
`"light"` for deterministic server rendering. `"system"` follows the operating
|
|
80
|
+
system. Change the choice after startup with `setColorScheme`:
|
|
81
|
+
|
|
82
|
+
``````tsx
|
|
83
|
+
import {SelectField, themeColorSchemeOptions, useTheme, type ThemeColorScheme} from "@terreno/ui";
|
|
84
|
+
|
|
85
|
+
const AppearanceSetting = () => {
|
|
86
|
+
const {colorSchemeSetting, setColorScheme} = useTheme();
|
|
87
|
+
return (
|
|
88
|
+
<SelectField
|
|
89
|
+
onChange={(value) => {
|
|
90
|
+
setColorScheme(value as ThemeColorScheme);
|
|
91
|
+
}}
|
|
92
|
+
options={themeColorSchemeOptions}
|
|
93
|
+
title="Appearance"
|
|
94
|
+
value={colorSchemeSetting}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
``````
|
|
99
|
+
|
|
100
|
+
Dark `surface.warningLight` uses `warning200`. The lighter warning orange, `warning100`, is 2.52:1 against warning text and misses WCAG AA. Highlighted `DataTable` cells switch to `text.inverted` when `text.primary` is below 4.5:1 on that fill. Inverted `MarkdownView` code uses `surface.neutralDark` when `surface.neutralLight` is below 4.5:1 against that text, and blockquotes do the same with `surface.baseAlternate`. Primary `Button` labels use `text.secondaryDark` when that pair meets 4.5:1 on `surface.primary`, and `text.inverted` otherwise, so `setTheme(darkThemeConfig)` stays readable without changing `colorScheme`. The demo palette generator emits status `050` steps so custom palettes update those dark error, success, and warning surfaces.
|
|
101
|
+
|
|
102
|
+
`themeColorSchemeOptions` labels those values Light, Dark, and Follow system.
|
|
103
|
+
`useTheme()` also returns the resolved `colorScheme` (`"light"` or `"dark"`)
|
|
104
|
+
with `theme`, `setTheme`, `setPrimitives`, and `resetTheme`. An explicit
|
|
105
|
+
`colorScheme` prop replaces the current choice whenever the parent changes it.
|
|
106
|
+
|
|
107
|
+
The public `lightThemeConfig`, `darkThemeConfig`, and
|
|
108
|
+
`defaultThemePrimitives` exports are the canonical token maps. They include the
|
|
109
|
+
Figma AI surface/border roles and the dark status ramps. Custom overrides set
|
|
110
|
+
through `useTheme()` are layered over the active mode; `resetTheme()` removes
|
|
111
|
+
those overrides without changing the provider's selected mode.
|
|
112
|
+
|
|
64
113
|
### DashboardGrid and DashboardGridItem
|
|
65
114
|
|
|
66
115
|
Eager layout-only wrapping grid. Default columns `{sm: 1, md: 2, lg: 3}`. Children stay caller-supplied `Card`s. Cell width is `(rowWidth - gap × (columns - 1)) / columns` so flex `gap` does not wrap extra columns.
|
|
@@ -473,10 +522,17 @@ rows, answers, and errors. Steps: [Add agent asks to a chat](../how-to/agent-ui-
|
|
|
473
522
|
`BlocksView` paints a whole-reply document from `@terreno/blocks`. Pass the assistant text
|
|
474
523
|
as `document`. Leaf blocks in this slice are `heading`, `text`, `metric`, `badge`,
|
|
475
524
|
`divider`, and `context`. `columns` is a row from the `md` breakpoint and a stack on `sm`.
|
|
476
|
-
`card` groups children under an optional title. A `callout` draws `Banner` and is not dismissible. An `image` draws `Image` and requires `alt`; pass `imageHosts` to allow `https` hosts. A `details` block draws `Accordion`. A `chart` draws `LineChart`, `BarChart`,
|
|
525
|
+
`card` groups children under an optional title, with an optional small muted `eyebrow` above it (test ID `<card path>-eyebrow`). A `callout` draws `Banner` and is not dismissible. An `image` draws `Image` and requires `alt`; pass `imageHosts` to allow `https` hosts. A `details` block draws `Accordion`. A `chart` draws `LineChart`, `BarChart`,
|
|
477
526
|
`AreaChart`, or `DonutChart` from an inline dataset or from `points`. The series is only
|
|
478
527
|
`{label, value}` — a point color in the document is not passed through. A `table` draws
|
|
479
|
-
`DataTable
|
|
528
|
+
`DataTable` and maps each dataset column type to a cell: `number` to a right-aligned
|
|
529
|
+
`number` cell, `date` to a `date` cell (Luxon `DATE_MED`, such as "Mar 14, 2026"), and
|
|
530
|
+
`string` to `text`. A listed column that the dataset does not have yet is `text`. Each header
|
|
531
|
+
is the column name with underscores shown as spaces (`bone_in_lamb` reads "bone in lamb"). The table
|
|
532
|
+
measures its container with `onLayout` and splits the width evenly across its columns, with
|
|
533
|
+
at least 96 per column; past that it scrolls sideways. Before the first layout each column
|
|
534
|
+
is 120 wide. Its height fits the header plus its rows (54 each), up to 10 rows; a longer table
|
|
535
|
+
scrolls inside that height, so it never spills over the next block. A `ref` dataset stays empty until `resolveDataset` returns its rows, and the
|
|
480
536
|
chart shows `loading` while that promise is in flight. A rejected fetch clears that
|
|
481
537
|
loading state. A segmented control highlights the option whose `data` matches the target
|
|
482
538
|
chart or table. A `select` action switches a table the same way it switches a chart.
|
|
@@ -494,6 +550,83 @@ action also stores the chosen dataset on the target chart for this view. Pass
|
|
|
494
550
|
`hostActions` and every callback stays enabled. `pendingElementIds` shows those
|
|
495
551
|
buttons as loading. `overrides` replaces a block by id.
|
|
496
552
|
|
|
553
|
+
A `stepper` draws its `label` as a small muted line, then `IconButton` − and + around the
|
|
554
|
+
value and its `unit`, then `itemsTitle`, a two-column grid of item labels and amounts
|
|
555
|
+
(formatted with `decimals`, so `{amount: 2, unit: kg, decimals: 1}` reads "2.0 kg"), and the
|
|
556
|
+
muted `note`. − is disabled when `value - step` would fall below `min`, and + when `value + step`
|
|
557
|
+
would pass `max`, so with `max: 20` and `step: 2` + is disabled at 19. Both are disabled while either button's
|
|
558
|
+
element id is in `pendingElementIds`, and when `hostActions` is passed without the stepper's
|
|
559
|
+
`callback.name`. The buttons are labelled "Decrease <label>" and "Increase <label>". A tap
|
|
560
|
+
calls `onAction` with `blockId: <id>`, `elementId: <id>_decrease` or `<id>_increase`, and
|
|
561
|
+
`{kind: "callback", name, payload: {...payload, value: value ± step}}`. A host returns the
|
|
562
|
+
new stepper, and `overrides[<id>]` paints it.
|
|
563
|
+
|
|
564
|
+
A `checklist` draws its `title` on the left and an "n of m" counter on the right, then one
|
|
565
|
+
`CheckBox` row per item: the bold `text` first, then the small muted `meta` (such as a time),
|
|
566
|
+
then the muted `detail`. Each row is a pressable `Box` with the `checkbox` role, labelled with the item
|
|
567
|
+
text, that reports `accessibilityState` `{checked, disabled}` (`aria-checked` and
|
|
568
|
+
`aria-disabled` on web); Space toggles a focused row on web. A locked row keeps its role and
|
|
569
|
+
label and reports `disabled: true`. Test ids are stable across states: the item wrapper is
|
|
570
|
+
`<path>-<id>_<item id>`, the pressable row `<path>-<id>_<item id>-row-clickable`, its
|
|
571
|
+
`CheckBox` `<path>-<id>_<item id>-checkbox`, its lines `<path>-<id>_<item id>-text`, `-meta`,
|
|
572
|
+
and `-detail`, and the counter `<path>-counter`. A tick goes to the host when the checklist has a `callback`,
|
|
573
|
+
`onAction` is passed, and `hostActions` is omitted (as for the stepper) or lists
|
|
574
|
+
`callback.name`. Then a tap calls `onAction` with `blockId: <id>`,
|
|
575
|
+
`elementId: <id>_<item id>`, and `{kind: "callback", name, payload: {...payload, itemId,
|
|
576
|
+
checked, state}}`, where `state` maps every item id to its tick after this one. The tick is
|
|
577
|
+
not drawn ahead of the reply. `BlocksView` disables the checklist while a tick is pending:
|
|
578
|
+
while any of its `<id>_<item id>` element ids is in `pendingElementIds`, no row is pressable,
|
|
579
|
+
so a second tick cannot send a `state` that misses the first. The counter changes when
|
|
580
|
+
`overrides[<id>]` brings the returned checklist. Otherwise a tick stays in this `BlocksView` only and updates the counter at once.
|
|
581
|
+
When an override for the checklist arrives or changes, earlier local ticks on it are dropped
|
|
582
|
+
and the override's `checked` values show.
|
|
583
|
+
On the server, `toggleChecklistHostAction` from `@terreno/ai` handles the callback and returns
|
|
584
|
+
the stored checklist with the ticks from `state` (see [Host actions](ai.md#host-actions)). A host
|
|
585
|
+
that records progress registers its own `handles: "checklist"` action.
|
|
586
|
+
|
|
587
|
+
A `gallery` draws each image as a 4:3 `Image` tile (`fit` cover), with `alt` as the tile's
|
|
588
|
+
accessible label and the optional `caption` as small muted text under it. Tiles are 8 apart
|
|
589
|
+
(the `gap={2}` spacing step). The gallery measures its container with `onLayout`: up to three
|
|
590
|
+
images share one row at equal widths, and more wrap into a three-column grid whose last row
|
|
591
|
+
keeps the same tile width. When that width would fall under 160, every tile stays 160 wide in
|
|
592
|
+
one row that scrolls sideways. A measured width decides this rather than the `sm` breakpoint, so
|
|
593
|
+
a gallery inside a narrow `columns` child or card also scrolls. Before the first layout each tile
|
|
594
|
+
is 160 wide. A `file:` tile loads through `resolveImage` like an `image` block. Until it has a
|
|
595
|
+
URL, or when the lookup fails, the tile is a muted 4:3 placeholder that shows the alt text and
|
|
596
|
+
keeps it as the label. Test ids: the gallery `<path>`, each grid row `<path>-row-<n>`, the
|
|
597
|
+
scrolling row `<path>-scroll`, each tile `<path>-image-<index>`, the frame around a loaded
|
|
598
|
+
image `<path>-image-<index>-image`, its caption `<path>-image-<index>-caption`, and a
|
|
599
|
+
placeholder `<path>-image-<index>-placeholder`.
|
|
600
|
+
`Image` passes `alt` to the native image as its `accessibilityLabel`.
|
|
601
|
+
|
|
602
|
+
A `list` draws one row per item, 12 apart (`gap={3}`). The row starts with a 3:4 portrait
|
|
603
|
+
`Image` thumbnail (`fit` cover), always 112 wide and 149 tall, with `alt` as its accessible label.
|
|
604
|
+
Beside it, the optional `meta` sits small and muted above the bold `title`, and the optional
|
|
605
|
+
`text` follows in muted body text. `text` is plain text, not markdown. When any item in the list
|
|
606
|
+
has a thumbnail, an item without one keeps an empty 112-wide gutter so every title starts at the
|
|
607
|
+
same place; a list with no thumbnails draws text only, with no gutter. A `file:` thumbnail loads
|
|
608
|
+
through `resolveImage`, including in a list inside a card. Until it has a URL, or when the lookup
|
|
609
|
+
fails, it is a muted 112 by 149 placeholder that shows the alt text and keeps it as the label.
|
|
610
|
+
Test ids: the list `<path>`, each row `<path>-item-<index>`, its thumbnail
|
|
611
|
+
`<path>-item-<index>-image`, placeholder `<path>-item-<index>-placeholder`, gutter
|
|
612
|
+
`<path>-item-<index>-gutter`, text column `<path>-item-<index>-body`, and the
|
|
613
|
+
`<path>-item-<index>-meta`, `-title`, and `-text` lines.
|
|
614
|
+
|
|
615
|
+
A `copy` button writes the clipboard on the device and never calls `onAction`, so
|
|
616
|
+
`hostActions` does not disable it. A press builds the text with `blockPlainText` from
|
|
617
|
+
`@terreno/blocks`: a literal `text` as written, or the `target` block as it is shown now. That
|
|
618
|
+
means the `overrides` replacement (a stepper at 6 copies the scaled amounts), the checklist's
|
|
619
|
+
ticks made on the device, and a table's selected dataset, inline or a `ref` that
|
|
620
|
+
`resolveDataset` has returned. `BlocksView` writes it with `expo-clipboard` `setStringAsync`
|
|
621
|
+
(web and native), then shows "Copied" in small muted text beside the button for 2 seconds. A
|
|
622
|
+
failed write, or a target with no text yet (such as a `ref` table still loading), shows
|
|
623
|
+
"Couldn't copy" in error text for 2 seconds instead, logs `console.warn`, and does not throw.
|
|
624
|
+
The status sits in a `Box` with `accessibilityLiveRegion="polite"`, which stays mounted while
|
|
625
|
+
empty, so screen readers on web and Android announce the status when it appears (`Text` has
|
|
626
|
+
no live-region prop, and VoiceOver on iOS does not announce React Native live regions).
|
|
627
|
+
Test ids: the button `<path>-<element id>`, the live region `<path>-<element id>-status`, and
|
|
628
|
+
the status text `<path>-<element id>-status-text`.
|
|
629
|
+
|
|
497
630
|
```tsx
|
|
498
631
|
<BlocksView
|
|
499
632
|
document={reply}
|
|
@@ -505,6 +638,23 @@ buttons as loading. `overrides` replaces a block by id.
|
|
|
505
638
|
|
|
506
639
|
Demo story: `BlocksView`.
|
|
507
640
|
|
|
641
|
+
### MarkdownView
|
|
642
|
+
|
|
643
|
+
`MarkdownView` renders markdown with `react-native-markdown-display` and the current theme.
|
|
644
|
+
GPTChat and the `text` block render through it.
|
|
645
|
+
|
|
646
|
+
GFM tables use theme colors, never fixed hex values:
|
|
647
|
+
|
|
648
|
+
- Cell borders draw a grid in `theme.border.default`.
|
|
649
|
+
- Header cells sit on `theme.surface.secondaryLight` and use the bold text font (`text-bold`).
|
|
650
|
+
Body cells use `text-regular`.
|
|
651
|
+
- Every column gets an equal share of the width, at least 96. A narrow table fills its
|
|
652
|
+
container. A table wider than its container scrolls sideways in a horizontal `ScrollView`.
|
|
653
|
+
|
|
654
|
+
Test ids: `markdown-table-scroll` (the scroll view), `markdown-table`,
|
|
655
|
+
`markdown-table-header-<column>`, and `markdown-table-cell-<row>-<column>` (body rows count
|
|
656
|
+
from 0).
|
|
657
|
+
|
|
508
658
|
### HtmlFrame
|
|
509
659
|
|
|
510
660
|
`HtmlFrame` paints one `html` block. On web it is an `<iframe sandbox="" referrerpolicy="no-referrer">` whose `srcdoc` starts with a Content-Security-Policy meta tag (`default-src 'none'`). On native it is a WebView with JavaScript off, and navigation after the first load is rejected. `BlocksView` renders that frame only when `allowHtml` is true and `streaming` is false. Otherwise the block is a card that says the preview is off or still arriving. `GPTChat` passes `allowHtml` through.
|
|
@@ -738,7 +888,8 @@ top spacing, the navigation row, bottom spacing, and `bottomNavBarHeight`.
|
|
|
738
888
|
opts into a labeled full-width pager on the narrow viewport. `narrowBelowWidth` uses that
|
|
739
889
|
viewport when the window is at or below the given pixel width; when omitted, the narrow
|
|
740
890
|
viewport follows `isNarrowViewport()`. These props are web only; the native `SplitPage`
|
|
741
|
-
ignores them. See `SplitPageProps` for when each prop applies and what is ignored.
|
|
891
|
+
ignores them. See `SplitPageProps` for when each prop applies and what is ignored. An unset
|
|
892
|
+
`color` is `baseAlternate` on web and native.
|
|
742
893
|
`IconButton`'s `backgroundOpacity` tints only that button's background.
|
|
743
894
|
|
|
744
895
|
```typescript
|
|
@@ -772,12 +923,25 @@ Buttons use a scale animation by default. Set `pressAnimation="opacity"` for an
|
|
|
772
923
|
|
|
773
924
|
Disabled and loading buttons use a non-interactive pressable regardless of the selected animation.
|
|
774
925
|
|
|
926
|
+
### Tooltip
|
|
927
|
+
|
|
928
|
+
`Tooltip` (and `tooltipText` on `Button` / `IconButton`) renders its bubble outside the trigger's clipping and stacking context:
|
|
929
|
+
|
|
930
|
+
| Platform | Mount point | Positioning |
|
|
931
|
+
| --- | --- | --- |
|
|
932
|
+
| Web | `document.body` | `position: fixed`, `z-index: 9999`, so it shows above an open `Modal` and nothing else covers the page |
|
|
933
|
+
| iOS / Android | `TerrenoProvider` portal host | `position: absolute`, `z-index: 999` |
|
|
934
|
+
|
|
935
|
+
The bubble stays off screen at `opacity: 0` until the trigger is measured, then `getTooltipPosition` places it at `idealPosition` or the first side that fits on screen.
|
|
936
|
+
|
|
775
937
|
### Toast
|
|
776
938
|
|
|
777
939
|
`TerrenoProvider` mounts the toast container. Call `useToast()` for `success`, `info`, `warn`, `error`, `show`, `hide`, and `catch`.
|
|
778
940
|
|
|
779
941
|
On web, that container is portaled to `document.body` (`position: fixed`, `z-index: 999999`, `pointerEvents: "box-none"`), so a toast stays above an open `Modal` and its backdrop. The web container is sized with `100%` rather than the measured window, so statically exported pages (where `Dimensions` reports 0×0 at build time) still center the toast on screen. Native iOS and Android keep the in-tree absolute container sized to the window.
|
|
780
942
|
|
|
943
|
+
Only the visible toast takes clicks. Every full-width layer around it (the container, its safe area, and the `Toast` layout wrapper) uses `pointerEvents: "box-none"`, so a button at the same height as an open toast stays clickable. Declare that style through `StyleSheet.create`; react-native-web drops `pointerEvents` from inline styles.
|
|
944
|
+
|
|
781
945
|
## Authentication Components
|
|
782
946
|
|
|
783
947
|
### SocialLoginButton
|
|
@@ -1114,6 +1278,32 @@ Responsive `Box` direction props update automatically when the window resizes or
|
|
|
1114
1278
|
All responsive Boxes share one dimension listener; non-responsive Boxes do not subscribe.
|
|
1115
1279
|
When multiple direction props match, the largest active breakpoint wins (`xl` over `lg` over `md` over `sm`).
|
|
1116
1280
|
|
|
1281
|
+
`Box` takes an optional `accessibilityState` of `{checked?, disabled?}`. It is sent to screen
|
|
1282
|
+
readers as `accessibilityState` and as `aria-checked` / `aria-disabled`, on both a plain and a
|
|
1283
|
+
clickable Box. On a clickable Box, `disabled: true` also stops presses while the role and
|
|
1284
|
+
label stay. A clickable Box with `accessibilityRole` `checkbox` or `switch` also toggles on
|
|
1285
|
+
Space on web, as ARIA expects.
|
|
1286
|
+
|
|
1287
|
+
```tsx
|
|
1288
|
+
<Box
|
|
1289
|
+
accessibilityHint="Marks this item as done"
|
|
1290
|
+
accessibilityLabel="Preheat the oven"
|
|
1291
|
+
accessibilityRole="checkbox"
|
|
1292
|
+
accessibilityState={{checked: false, disabled: isSaving}}
|
|
1293
|
+
onClick={toggle}
|
|
1294
|
+
/>
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
`Box` also takes `accessibilityLiveRegion` (`polite` or `assertive`) on a Box without
|
|
1298
|
+
`onClick`. It is sent as `aria-live`, so screen readers announce changes to the Box's contents
|
|
1299
|
+
on web and Android. VoiceOver on iOS does not announce React Native live regions.
|
|
1300
|
+
|
|
1301
|
+
```tsx
|
|
1302
|
+
<Box accessibilityLiveRegion="polite" testID="copy-status">
|
|
1303
|
+
{status ? <Text size="sm">{status}</Text> : null}
|
|
1304
|
+
</Box>
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1117
1307
|
## Icons
|
|
1118
1308
|
|
|
1119
1309
|
Terreno uses **FontAwesome 6** by default. Pass icon names via `iconName` on `Icon`, `Button`, `IconButton`, form fields, `Badge`, and other icon-aware components.
|
|
@@ -1233,8 +1423,8 @@ const buttonStyles = toggle(isPressed, pressedStyles, defaultStyles);
|
|
|
1233
1423
|
|
|
1234
1424
|
@terreno/ui components do not require environment variables. All configuration is done at runtime via:
|
|
1235
1425
|
|
|
1236
|
-
- **TerrenoProvider props** —
|
|
1237
|
-
- **Theme hooks** — `useTheme()`, `setTheme()`, `setPrimitives()`
|
|
1426
|
+
- **TerrenoProvider props** — `colorScheme`, `initialPrimitives`, custom icons, and OpenAPI spec URL
|
|
1427
|
+
- **Theme hooks** — `useTheme()`, `setColorScheme()`, `setTheme()`, `setPrimitives()`
|
|
1238
1428
|
- **Component props** — Direct prop overrides for individual components
|
|
1239
1429
|
|
|
1240
1430
|
**Example configuration:**
|
|
@@ -1243,16 +1433,28 @@ const buttonStyles = toggle(isPressed, pressedStyles, defaultStyles);
|
|
|
1243
1433
|
import {TerrenoProvider} from "@terreno/ui";
|
|
1244
1434
|
|
|
1245
1435
|
<TerrenoProvider
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
surface: {primary: "secondary500"},
|
|
1249
|
-
}}
|
|
1250
|
-
onError={(error) => console.error(error)}
|
|
1436
|
+
colorScheme="system"
|
|
1437
|
+
openAPISpecUrl="https://api.example.com/openapi.json"
|
|
1251
1438
|
>
|
|
1252
1439
|
{children}
|
|
1253
1440
|
</TerrenoProvider>
|
|
1254
1441
|
``````
|
|
1255
1442
|
|
|
1443
|
+
## DataTable column types
|
|
1444
|
+
|
|
1445
|
+
`columnType` picks the cell a column draws, unless `customColumnComponentMap` has an entry
|
|
1446
|
+
for that type:
|
|
1447
|
+
|
|
1448
|
+
| `columnType` | Cell |
|
|
1449
|
+
| --- | --- |
|
|
1450
|
+
| `text` (and any unmapped type) | The value as a string |
|
|
1451
|
+
| `number` | The value as a string, right-aligned, with the header right-aligned to match. A number or a numeric string both work |
|
|
1452
|
+
| `date` | An ISO string or a `Date`, formatted with Luxon `DATE_MED` ("Mar 14, 2026") in the local zone. A value Luxon cannot parse is shown as written, and an empty value is blank |
|
|
1453
|
+
| `boolean` | A check or a cross icon |
|
|
1454
|
+
|
|
1455
|
+
A value that is already formatted, such as "Oct 8, 2026, 3:00 PM", is not ISO, so a `date`
|
|
1456
|
+
column shows it unchanged.
|
|
1457
|
+
|
|
1256
1458
|
## DataTable server-side filtering
|
|
1257
1459
|
|
|
1258
1460
|
`DataTable` is data-layer agnostic. Optional filter and search props emit a
|