@terreno/mcp 57.9.0 → 57.10.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/dist/docs/upgrades/57.10.1.md +54 -0
- package/dist/docs/versioned/next/README.md +1 -0
- package/dist/docs/versioned/next/explanation/README.md +4 -0
- package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
- package/dist/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
- package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
- package/dist/docs/versioned/next/explanation/ai-observability.md +101 -0
- package/dist/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +3 -1
- package/dist/docs/versioned/next/explanation/loop-engineering.md +1 -1
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +29 -1
- package/dist/docs/versioned/next/explanation/typescript-configuration.md +1 -1
- package/dist/docs/versioned/next/explanation/versioning-policy.md +1 -1
- package/dist/docs/versioned/next/how-to/README.md +6 -0
- package/dist/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
- package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
- package/dist/docs/versioned/next/how-to/ai-feature-development.md +156 -0
- package/dist/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
- package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +2 -0
- package/dist/docs/versioned/next/how-to/circleci.md +5 -3
- package/dist/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -1
- package/dist/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
- package/dist/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
- package/dist/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
- package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
- package/dist/docs/versioned/next/reference/README.md +3 -0
- package/dist/docs/versioned/next/reference/admin-config.md +2 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +216 -0
- package/dist/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
- package/dist/docs/versioned/next/reference/ai-harness.md +1881 -0
- package/dist/docs/versioned/next/reference/ai.md +251 -25
- package/dist/docs/versioned/next/reference/api.md +46 -0
- package/dist/docs/versioned/next/reference/blocks.md +178 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +1 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +17 -0
- package/dist/docs/versioned/next/reference/test.md +11 -2
- package/dist/docs/versioned/next/reference/ui.md +325 -1
- package/dist/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
- package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
- package/dist/docs/versioned/next/tasks/ai-observability.md +233 -104
- package/dist/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +37 -0
- package/dist/tools.js.map +1 -1
- package/package.json +3 -2
- package/src/docs/upgrades/57.10.1.md +54 -0
- package/src/docs/versioned/next/README.md +1 -0
- package/src/docs/versioned/next/explanation/README.md +4 -0
- package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
- package/src/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
- package/src/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
- package/src/docs/versioned/next/explanation/ai-observability.md +101 -0
- package/src/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +3 -1
- package/src/docs/versioned/next/explanation/loop-engineering.md +1 -1
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +29 -1
- package/src/docs/versioned/next/explanation/typescript-configuration.md +1 -1
- package/src/docs/versioned/next/explanation/versioning-policy.md +1 -1
- package/src/docs/versioned/next/how-to/README.md +6 -0
- package/src/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
- package/src/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
- package/src/docs/versioned/next/how-to/ai-feature-development.md +156 -0
- package/src/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
- package/src/docs/versioned/next/how-to/charts-and-dashboards.md +2 -0
- package/src/docs/versioned/next/how-to/circleci.md +5 -3
- package/src/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -1
- package/src/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
- package/src/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
- package/src/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
- package/src/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
- package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
- package/src/docs/versioned/next/reference/README.md +3 -0
- package/src/docs/versioned/next/reference/admin-config.md +2 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +216 -0
- package/src/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
- package/src/docs/versioned/next/reference/ai-harness.md +1881 -0
- package/src/docs/versioned/next/reference/ai.md +251 -25
- package/src/docs/versioned/next/reference/api.md +46 -0
- package/src/docs/versioned/next/reference/blocks.md +178 -0
- package/src/docs/versioned/next/reference/environment-variables.md +1 -0
- package/src/docs/versioned/next/reference/mcp-server.md +17 -0
- package/src/docs/versioned/next/reference/test.md +11 -2
- package/src/docs/versioned/next/reference/ui.md +325 -1
- package/src/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
- package/src/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
- package/src/docs/versioned/next/tasks/ai-observability.md +233 -104
- package/src/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# UI blocks
|
|
2
|
+
|
|
3
|
+
`@terreno/blocks` checks the YAML (or JSON) document an agent uses when a chat reply is
|
|
4
|
+
components instead of prose. Ask schemas are in [Agent UI Asks](agent-ui-asks.md). Validate a
|
|
5
|
+
file locally with [the blocks how-to](../how-to/agent-ui-blocks.md).
|
|
6
|
+
|
|
7
|
+
## Document
|
|
8
|
+
|
|
9
|
+
A reply is one mapping. Keys, when present, are in this order: `v`, `datasets`, `blocks`.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
v: 1
|
|
13
|
+
blocks:
|
|
14
|
+
- type: text
|
|
15
|
+
markdown: I could not find any signups for that range.
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`parseBlocks` strips one surrounding ` ```yaml ` fence, parses YAML 1.2 core schema or JSON,
|
|
19
|
+
and rejects anchors, aliases, and explicit tags (`YAML_FEATURE_DISALLOWED`). A value that is
|
|
20
|
+
not a mapping with `v` is `NOT_A_DOCUMENT`. Callers display that reply with
|
|
21
|
+
`wrapAsTextDocument`, which builds `{v: 1, blocks: [{type: text, markdown}]}`.
|
|
22
|
+
|
|
23
|
+
## Datasets
|
|
24
|
+
|
|
25
|
+
A dataset name matches `^[a-z][a-z0-9_]{0,63}$`. A document has at most 8 datasets.
|
|
26
|
+
|
|
27
|
+
| Source | Fields | Rule |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| inline | `columns`: `{name, type}` where `type` is `string`, `number`, or `date`; `rows`: arrays | At most 500 rows and 12 columns. Each row has one value per column. `source: inline` is optional. |
|
|
30
|
+
| ref | `source: ref`, `id`, optional `grain` (`hour`, `day`, `week`, `month`), optional `limit` | `limit` above 1,000 is `TOO_MANY_POINTS`. Set `grain` to a coarser bucket. Column checks run when `knownDatasets` is passed. |
|
|
31
|
+
|
|
32
|
+
## Charts and tables
|
|
33
|
+
|
|
34
|
+
| Block | Required | Optional |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `chart` | `kind`: `line`, `bar`, `area`, `donut`, and either `data` + `x` + `y` or `points` (`{label, value}`) | `id`, `title`, `legend`, `emptyText`, `height`: `sm`, `md`, `lg` |
|
|
37
|
+
| `table` | `data` (dataset name) | `id`, `columns` (names), `title` |
|
|
38
|
+
|
|
39
|
+
`x` is a string or date column. `y` is a number column. A table lists at most 12 columns.
|
|
40
|
+
|
|
41
|
+
## HTML
|
|
42
|
+
|
|
43
|
+
| Block | Required | Optional |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `html` | `html` (at most 100,000 bytes) | `id`, `title`, `height`: `sm`, `md`, `lg` |
|
|
46
|
+
|
|
47
|
+
`validateBlocks` returns `HTML_DISABLED` unless `options.allowHtml` is true. A host turns that on with `uiBlocks.html`. The server sanitizes the HTML before it is stored. The client renders it only when `allowHtml` is set.
|
|
48
|
+
|
|
49
|
+
## Callout, image, and details
|
|
50
|
+
|
|
51
|
+
| Block | Required | Optional |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `callout` | `text` | `id`, `status`: `info`, `warning`, `alert` |
|
|
54
|
+
| `image` | `alt`, `src` | `id` |
|
|
55
|
+
| `details` | `title`, `text` | `id` |
|
|
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`.
|
|
58
|
+
|
|
59
|
+
Warnings do not block rendering:
|
|
60
|
+
|
|
61
|
+
| Code | When |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `BAR_TOO_MANY_CATEGORIES` | A bar chart would draw more than 60 categories. |
|
|
64
|
+
| `DONUT_TOO_MANY_SLICES` | A donut chart would draw more than 8 slices. |
|
|
65
|
+
| `LINE_SINGLE_POINT` | A line chart has one point. |
|
|
66
|
+
|
|
67
|
+
## Blocks
|
|
68
|
+
|
|
69
|
+
| Block | Required | Optional | Renders as |
|
|
70
|
+
| --- | --- | --- | --- |
|
|
71
|
+
| `heading` | `text` (1–200) | `size`: `sm`, `md`, `lg`, `xl`, `2xl`; `id` | `Heading` |
|
|
72
|
+
| `text` | `markdown` (1–4,000) | `id` | `MarkdownView` |
|
|
73
|
+
| `metric` | `label` (1–80), `value` (1–80) | `delta` (1–40), `trend`: `up`, `down`, `flat`, `helper` (1–120), `id` | `Card` |
|
|
74
|
+
| `badge` | `text` (1–80) | `status`: `info`, `error`, `warning`, `success`, `neutral`, `active`; `id` | `Badge` |
|
|
75
|
+
| `divider` | — | `id` | `SectionDivider` |
|
|
76
|
+
| `context` | `text` (1–280) | `id` | `Text` |
|
|
77
|
+
| `columns` | `children`: 2–4 blocks | `id` | `Box` row |
|
|
78
|
+
| `card` | `children`: at least 1 block | `title` (1–120), `id` | `Card` |
|
|
79
|
+
|
|
80
|
+
`columns` and `card` sit at the top level. Their children are leaf blocks. A layout block
|
|
81
|
+
inside another layout block is `DEPTH_EXCEEDED`. Every block counts toward the 50-block
|
|
82
|
+
cap, including the `card` or `columns` block itself, so a card cannot hold 50 children.
|
|
83
|
+
|
|
84
|
+
`id` matches `^[a-z][a-z0-9_]{0,63}$`.
|
|
85
|
+
|
|
86
|
+
## Actions
|
|
87
|
+
|
|
88
|
+
An `actions` block requires `id` and `elements` (1–25). An element is a `button` or a
|
|
89
|
+
`segmented` control.
|
|
90
|
+
|
|
91
|
+
| Element | Required | Optional |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `button` | `id`, `text` (1–80), `action` | `variant`: `primary`, `secondary`, `outline`, `ghost`, `destructive`; `iconName` |
|
|
94
|
+
| `segmented` | `id`, `target` (a chart or table `id`), `options` (2–8 of `{label, data}`) | — |
|
|
95
|
+
|
|
96
|
+
`action.kind` is `reply` (`text`), `open` (`url` or `route`, exactly one), `select`
|
|
97
|
+
(`target` plus `data`), or `callback` (`name` plus optional `payload`). A `select` target
|
|
98
|
+
that is not a chart or table `id` is `SELECT_TARGET_INVALID`. When `validateBlocks` is
|
|
99
|
+
called with `hostActions`, a callback `name` outside that list is `UNKNOWN_HOST_ACTION`.
|
|
100
|
+
Omitting `hostActions` skips that check.
|
|
101
|
+
|
|
102
|
+
## Partial parsing
|
|
103
|
+
|
|
104
|
+
`parseBlocksPartial(text)` never throws. A document that `validateBlocks` accepts returns
|
|
105
|
+
`{datasets, blocks, pending: false}`. A cut reply returns only the top-level blocks whose
|
|
106
|
+
next `- ` already arrived, drops the unfinished tail, and sets `pending: true`. `datasets`
|
|
107
|
+
is present only after the `blocks:` key has started and the prefix parses.
|
|
108
|
+
|
|
109
|
+
## CLI
|
|
110
|
+
|
|
111
|
+
`terreno-blocks validate <file | ->` prints each problem as `path CODE message — fix` and
|
|
112
|
+
exits 1. A valid document exits 0. Warnings are printed and still exit 0.
|
|
113
|
+
|
|
114
|
+
Unknown fields fail with `UNKNOWN_KEY`. `v` must be `1` (`UNSUPPORTED_VERSION`).
|
|
115
|
+
|
|
116
|
+
## Limits
|
|
117
|
+
|
|
118
|
+
| Limit | Value |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| Blocks in one document, including nested blocks | 50 |
|
|
121
|
+
| Nesting | 2 (`blocks` → `columns` or `card` → leaf) |
|
|
122
|
+
| `columns` children | 2–4 |
|
|
123
|
+
| Elements in one `actions` block | 25 |
|
|
124
|
+
| Text in one `text` block | 4,000 characters |
|
|
125
|
+
| Document version | 1 |
|
|
126
|
+
|
|
127
|
+
## Errors
|
|
128
|
+
|
|
129
|
+
`validateBlocks` returns `{ok: true, doc, warnings}` or `{ok: false, errors, warnings}`.
|
|
130
|
+
Each error is `{path, code, message, fix}`, sorted by path. `warnings` lists chart
|
|
131
|
+
heuristics and does not fail `ok`.
|
|
132
|
+
|
|
133
|
+
| Code | Meaning |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| `COLUMN_NOT_FOUND` | A chart or table names a column the dataset does not have. |
|
|
136
|
+
| `COLUMN_TYPE_MISMATCH` | A column value, or a chart axis, does not match the column type. |
|
|
137
|
+
| `DATASET_NOT_FOUND` | A chart or table names a dataset the document does not define. |
|
|
138
|
+
| `DATASET_TOO_LARGE` | A dataset has more than 500 rows or 12 columns. |
|
|
139
|
+
| `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. |
|
|
141
|
+
| `HTML_DISABLED` | An html block is present and this host has not turned HTML on. |
|
|
142
|
+
| `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. |
|
|
144
|
+
| `INVALID_ENUM` | A value is not one of the allowed values. |
|
|
145
|
+
| `INVALID_FORMAT` | A string does not match its required format. |
|
|
146
|
+
| `INVALID_TYPE` | A value has the wrong type, including a numeric field below its minimum. |
|
|
147
|
+
| `KEY_ORDER` | Top-level keys are not in the order `v`, `datasets`, `blocks`. |
|
|
148
|
+
| `MISSING_REQUIRED` | A required field is missing. |
|
|
149
|
+
| `NOT_A_DOCUMENT` | The reply is not one YAML or JSON mapping with a `v` field. |
|
|
150
|
+
| `SELECT_TARGET_INVALID` | A select action names a block that is not a chart or table. |
|
|
151
|
+
| `ROW_ARITY_MISMATCH` | A dataset row does not have one value per column. |
|
|
152
|
+
| `TABLE_TOO_WIDE` | A table lists more than 12 columns. |
|
|
153
|
+
| `TOO_FEW` | A list has fewer items than allowed. |
|
|
154
|
+
| `TOO_LONG` | A string is longer than allowed. |
|
|
155
|
+
| `TOO_MANY` | A list has more items than allowed. |
|
|
156
|
+
| `TOO_MANY_BLOCKS` | The document has more than 50 blocks. |
|
|
157
|
+
| `TOO_MANY_POINTS` | A ref dataset `limit` is above 1,000. |
|
|
158
|
+
| `TOO_SHORT` | A string is empty or only whitespace. |
|
|
159
|
+
| `UNKNOWN_HOST_ACTION` | A callback names a host action that is not registered. |
|
|
160
|
+
| `UNKNOWN_KEY` | An object has a field that its schema does not define. |
|
|
161
|
+
| `UNSUPPORTED_VERSION` | `v` is not 1. |
|
|
162
|
+
| `YAML_FEATURE_DISALLOWED` | The document uses a YAML anchor, alias, or explicit tag. |
|
|
163
|
+
|
|
164
|
+
## Exports
|
|
165
|
+
|
|
166
|
+
| Export | Role |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `parseBlocks(text)` | Fence strip, YAML or JSON parse |
|
|
169
|
+
| `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. |
|
|
171
|
+
| `wrapAsTextDocument(text)` | Display fallback for a non-document |
|
|
172
|
+
| `blocksSchema` | Zod schema |
|
|
173
|
+
| `blocksJsonSchema` | JSON Schema for the same structure |
|
|
174
|
+
| `blocksPromptSection({hostActions, allowHtml})` | System-prompt section. Limits come from `BLOCK_LIMITS`. `allowHtml` adds the `html` block. |
|
|
175
|
+
| `BLOCK_LIMITS` | The numbers in the table above |
|
|
176
|
+
| `BLOCK_ERROR_CODES` | The codes in the table above |
|
|
177
|
+
| `BLOCK_WARNING_CODES` | `BAR_TOO_MANY_CATEGORIES`, `DONUT_TOO_MANY_SLICES`, `LINE_SINGLE_POINT` |
|
|
178
|
+
| `HTML_HEIGHTS`, `CALLOUT_STATUSES` | Allowed `html` heights and `callout` statuses. `HtmlBlock`, `CalloutBlock`, `ImageBlock`, and `DetailsBlock` are type exports. |
|
|
@@ -23,6 +23,7 @@ Comprehensive guide to environment variables used across Terreno packages and ex
|
|
|
23
23
|
| `TERRENO_TEST_MONGODB_URI` | `@terreno/test` | ❌ | auto | Yes | tooling |
|
|
24
24
|
| `TERRENO_TEST_USE_MEMORY_MONGO` | `@terreno/test` | ❌ | — | No | tooling |
|
|
25
25
|
| `TERRENO_TEST_USE_REPLSET` | `@terreno/test` | ❌ | — | No | tooling |
|
|
26
|
+
| `TERRENO_TEST_MONGO_LAUNCH_TIMEOUT_MS` | `@terreno/test` | ❌ | `60000` | No | tooling |
|
|
26
27
|
| `TERRENO_TEST_USE_FIXTURE_CACHE` | `@terreno/test` | ❌ | — | No | tooling |
|
|
27
28
|
| `TERRENO_TEST_CACHE_DIR` | `@terreno/test` | ❌ | — | No | tooling |
|
|
28
29
|
| `BUN_TEST_DISABLE_DB` | test harness | ❌ | — | No | tooling |
|
|
@@ -391,6 +391,23 @@ Validate a Mongoose schema against Terreno conventions.
|
|
|
391
391
|
- Recommendations for fixes
|
|
392
392
|
- Severity levels (error, warning, info)
|
|
393
393
|
|
|
394
|
+
### terreno_validate_ui_blocks
|
|
395
|
+
|
|
396
|
+
Check one whole-reply YAML or JSON document. The text is the same report `terreno-blocks validate` prints: each problem is `path CODE message — fix`. A valid document with no warnings returns an empty string.
|
|
397
|
+
|
|
398
|
+
**Parameters:**
|
|
399
|
+
|
|
400
|
+
``````typescript
|
|
401
|
+
{
|
|
402
|
+
document: string; // Required — the YAML or JSON document
|
|
403
|
+
}
|
|
404
|
+
``````
|
|
405
|
+
|
|
406
|
+
**Returns:**
|
|
407
|
+
|
|
408
|
+
- The CLI report, including a trailing newline when there is at least one line
|
|
409
|
+
- An empty string when the document is valid and has no warnings
|
|
410
|
+
|
|
394
411
|
### terreno_bootstrap_ai_rules
|
|
395
412
|
|
|
396
413
|
Scaffold AI coding assistant rules (AGENTS.md, Cursor/Windsurf rules, Copilot instructions, rulesync config).
|
|
@@ -79,10 +79,14 @@ Defaults: `TOKEN_SECRET`, `TOKEN_ISSUER`, `REFRESH_TOKEN_SECRET`, `SESSION_SECRE
|
|
|
79
79
|
|----------|--------|
|
|
80
80
|
| `TERRENO_TEST_USE_MEMORY_MONGO` | When `"true"`, `registerSimpleMongoPreload` starts `mongodb-memory-server` if `TERRENO_TEST_MONGODB_URI` is unset. |
|
|
81
81
|
| `TERRENO_TEST_MONGODB_URI` | External MongoDB URI. Takes priority over in-memory server in `startMongoServer` and `registerSimpleMongoPreload`. |
|
|
82
|
+
| `TERRENO_TEST_MONGO_LAUNCH_TIMEOUT_MS` | Per-mongod start timeout for in-memory servers started by `startMongoServer`. Default `60000` (mongodb-memory-server's own default is 10s). Startup is tried twice. |
|
|
82
83
|
| `BUN_TEST_DISABLE_DB` | When `"true"`, `registerBackendPreload` skips all Mongo `beforeAll`/`afterAll` hooks (no DB startup). |
|
|
83
84
|
|
|
84
85
|
`startMongoServer` also publishes the resolved URI to `TERRENO_TEST_MONGO_URI` and honors `TERRENO_TEST_USE_REPLSET=true` for replica-set memory servers.
|
|
85
86
|
|
|
87
|
+
If `registerBackendPreload` cannot start Mongo, every later test fails at once with
|
|
88
|
+
`MongoDB test server failed to start` instead of waiting on mongoose command buffering.
|
|
89
|
+
|
|
86
90
|
## Exported helpers
|
|
87
91
|
|
|
88
92
|
| Export | Description |
|
|
@@ -92,6 +96,7 @@ Defaults: `TOKEN_SECRET`, `TOKEN_ISSUER`, `REFRESH_TOKEN_SECRET`, `SESSION_SECRE
|
|
|
92
96
|
| `setTerrenoTestEnv` | Apply and validate auth test env vars. |
|
|
93
97
|
| `startMongoServer` / `stopMongoServer` | Start or stop shared in-memory Mongo and connect mongoose. |
|
|
94
98
|
| `getMongoServerUri` | Resolved URI after `startMongoServer`. |
|
|
99
|
+
| `resolveMongoLaunchTimeoutMs` | In-memory mongod launch timeout from `TERRENO_TEST_MONGO_LAUNCH_TIMEOUT_MS` (integer ≥ 1000), default 60000. |
|
|
95
100
|
| `getBaseServer` | Build an Express app for supertest from route registrars. |
|
|
96
101
|
| `authAsUser` | Login helper returning auth headers for supertest. |
|
|
97
102
|
| `createMongoTestCache` | Fixture-cache controller for fast `@terreno/api` tests. |
|
|
@@ -112,8 +117,12 @@ Defaults: `TOKEN_SECRET`, `TOKEN_ISSUER`, `REFRESH_TOKEN_SECRET`, `SESSION_SECRE
|
|
|
112
117
|
Package CI uses `scripts/check-coverage.ts` (`bun run test:coverage`, default 95%
|
|
113
118
|
functions and lines) as the live gate. Bun 1.4.2+ may exit 1 on bunfig
|
|
114
119
|
`coverageThreshold` before isolated LCOV merges; the script continues when tests
|
|
115
|
-
reported `0 fail` and then enforces 95% on the merged report.
|
|
116
|
-
|
|
120
|
+
reported `0 fail` and then enforces 95% on the merged report. The blocks
|
|
121
|
+
coverage pass sets `--max-concurrency=1`, because Bun's default 20-way run
|
|
122
|
+
exits 0 on a 2-vCPU worker before it prints the `All files` row. When that
|
|
123
|
+
row is still missing after one retry, the script uses `coverage/lcov.info`
|
|
124
|
+
only if it lists every non-test source file under `src/`. Dedicated CircleCI
|
|
125
|
+
jobs (`api-ci`, `ai-ci`, `blocks-ci`,
|
|
117
126
|
`rtk-ci`, `ui-ci`, `syncdb-ci`, `comms-ci`, `mcp-server-ci`, `admin-spa-ci`) run
|
|
118
127
|
that script. Published packages without a dedicated workflow
|
|
119
128
|
(`admin-backend`, `admin-frontend`, `api-health`, `feature-flags`, `@terreno/test`)
|
|
@@ -10,6 +10,7 @@ React Native UI component library (a large component library). Layout (Box, Page
|
|
|
10
10
|
- Actions: `Button`, `IconButton`, `Link`
|
|
11
11
|
- Feedback: `Spinner`, `Modal`, `Toast`
|
|
12
12
|
- Notifications: `NotificationBell`, `NotificationInbox`, `NotificationPreferences`
|
|
13
|
+
- AI chat: `GPTChat`, `AskCard` (agent asks in the transcript), `SimpleAskCard` (an ask's simple card for narrow layouts), `BlocksView` (a whole-reply YAML document)
|
|
13
14
|
- Authentication: `SocialLoginButton`, `LoginScreen`, `SignUpScreen`
|
|
14
15
|
- Theming: `TerrenoProvider`, `useTheme`, custom icon registry (`icons` prop)
|
|
15
16
|
- **Type re-exports:** `StyleProp`, `ViewStyle` (re-exported from react-native to avoid version conflicts)
|
|
@@ -32,7 +33,7 @@ supported and is convenient when startup cost is not material:
|
|
|
32
33
|
import {Box, DataTable, Icon} from "@terreno/ui";
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
Heavy optional widgets (`GPTChat`, `EmojiSelector`, `MarkdownEditor`, consent flows, `ChartCard`, `Scorecard`, `SparklineChart`, `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, and related admin tools) are
|
|
36
|
+
Heavy optional widgets (`GPTChat`, `AskCard`, `BlocksView`, `EmojiSelector`, `MarkdownEditor`, consent flows, `ChartCard`, `Scorecard`, `SparklineChart`, `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, and related admin tools) are
|
|
36
37
|
re-exported from the root entry through lazy boundaries. Importing them from `@terreno/ui` stays type-compatible, but
|
|
37
38
|
their implementation modules load on first render instead of during the initial root import. `DashboardGrid` stays eager.
|
|
38
39
|
`MarkdownView` and `DataTable` header info defer `react-native-markdown-display`;
|
|
@@ -250,6 +251,18 @@ Buttons automatically size to their content unless `fullWidth` is specified:
|
|
|
250
251
|
|
|
251
252
|
Internally, Button sets `alignSelf: 'flex-start'` when `fullWidth={false}` to prevent stretching in column layouts.
|
|
252
253
|
|
|
254
|
+
A label stays on one line by default, so a long label can make the button wider than its container.
|
|
255
|
+
Set `wrapText` to keep the button inside its container instead: the label wraps onto centered lines
|
|
256
|
+
and the button grows taller. A `size="sm"` button grows from its 28px height instead of clipping the
|
|
257
|
+
second line. `SimpleAskCard` and `AskCard` set `wrapText` on their answer buttons, because their
|
|
258
|
+
labels come from the agent.
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
<Box direction="row" width={160}>
|
|
262
|
+
<Button onClick={handleSave} text="Save all changes now" wrapText />
|
|
263
|
+
</Box>
|
|
264
|
+
```
|
|
265
|
+
|
|
253
266
|
### TextField grow and maxHeight
|
|
254
267
|
|
|
255
268
|
`grow` makes a multiline field expand with its content. Pair it with `maxHeight` (pixels) to cap the expansion; past the cap the content scrolls.
|
|
@@ -390,10 +403,321 @@ The attach control (`FilePickerButton`) opens an anchored dropdown with **Photo
|
|
|
390
403
|
/>
|
|
391
404
|
```
|
|
392
405
|
|
|
406
|
+
Pass `uiBlocks` to render each assistant message through `BlocksView`. A streaming message
|
|
407
|
+
shows each finished top-level block and a spinner for the block still arriving. `reply`
|
|
408
|
+
calls `onSubmit` with the button text. `open` and `select` call `onBlockAction`. `callback`
|
|
409
|
+
calls `onBlockCallback` and the button stays loading until that promise settles. Return
|
|
410
|
+
`{replace: "block", blocks}` to swap that block, or `{text}` to place an assistant message
|
|
411
|
+
immediately after the message that started the callback. A result that arrives after
|
|
412
|
+
`currentHistoryId` changes is dropped. `hostActions` disables callback names the host did
|
|
413
|
+
not list. `resolveDataset` loads `ref` datasets. `resolveImage` turns a `file:` image id
|
|
414
|
+
into a URL.
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
<GPTChat
|
|
418
|
+
currentMessages={messages}
|
|
419
|
+
histories={histories}
|
|
420
|
+
hostActions={["export_csv"]}
|
|
421
|
+
onBlockAction={handleBlockAction}
|
|
422
|
+
onBlockCallback={handleBlockCallback}
|
|
423
|
+
onCreateHistory={onCreateHistory}
|
|
424
|
+
onDeleteHistory={onDeleteHistory}
|
|
425
|
+
onSelectHistory={onSelectHistory}
|
|
426
|
+
onSubmit={onSubmit}
|
|
427
|
+
uiBlocks
|
|
428
|
+
/>
|
|
429
|
+
```
|
|
430
|
+
|
|
393
431
|
Operator steps: [Add a GPT chat mascot](../how-to/add-gpt-chat-mascot.md). Demo story: `GPTChat` → `Mascot`.
|
|
432
|
+
Blocks playground: `BlocksPlayground`.
|
|
394
433
|
The example AI screen demonstrates a consumer selecting one of four bundled mascot
|
|
395
434
|
images once per mount.
|
|
396
435
|
|
|
436
|
+
#### Asks
|
|
437
|
+
|
|
438
|
+
`GPTChat` shows an [agent ask](agent-ui-asks.md) as an `AskCard` in the transcript. Set `ask` on
|
|
439
|
+
the ask's `tool-call` message and pass `onAskSubmit`:
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
<GPTChat
|
|
443
|
+
askErrors={askErrors}
|
|
444
|
+
currentMessages={currentMessages}
|
|
445
|
+
histories={histories}
|
|
446
|
+
onAskSubmit={handleAskSubmit}
|
|
447
|
+
onCreateHistory={onCreateHistory}
|
|
448
|
+
onDeleteHistory={onDeleteHistory}
|
|
449
|
+
onSelectHistory={onSelectHistory}
|
|
450
|
+
onSubmit={onSubmit}
|
|
451
|
+
/>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
| Prop or field | Type | Description |
|
|
455
|
+
| --- | --- | --- |
|
|
456
|
+
| `GPTChatMessage.ask` | `ChatAsk` | On a `tool-call` message: the ask (`kind`, `input`), its `toolCallId`, `status` (`pending`, `answered`, or `cancelled`), and optional `response` and `simple` card. The chat shows an `AskCard` instead of the tool call. |
|
|
457
|
+
| `onAskSubmit` | `(submission: {toolCallId, response}) => void \| Promise<void>` | Called when the user answers a pending ask. `response` is the answer envelope. The pressed control shows a loading state until the promise settles. Without it, asks show but cannot be answered. |
|
|
458
|
+
| `askErrors` | `Record<string, AskValidationError[]>` | Errors for the last answer to each ask, keyed by tool call id, such as the `fields` of a 400 `Invalid askResponse`. Shown inside the card. |
|
|
459
|
+
| `resolveAskFiles` | `AskFilesResolver` | Turns the files picked for a `files` ask into the refs its answer sends. Defaults to `resolveAskFilesAsDataUrls`. Pass an uploader to send `{fileId}` refs; see [Accept uploads with or without GCS](../how-to/agent-ui-asks.md#accept-uploads-with-or-without-gcs). |
|
|
460
|
+
|
|
461
|
+
- The ask's `tool-result` message stays in `currentMessages` but is not shown. Keep it: message
|
|
462
|
+
indexes must match the stored `prompts` rows that ratings use. When `ask.response` is unset, the
|
|
463
|
+
card reads the answer from that message.
|
|
464
|
+
- A pending ask takes focus when it appears: DOM focus on the card, labelled with the ask's
|
|
465
|
+
`title`, on web; accessibility focus on the question, an accessibility header, on native.
|
|
466
|
+
- Answered and cancelled asks collapse to a one-line summary.
|
|
467
|
+
|
|
468
|
+
The example AI screen, `example-frontend/app/(tabs)/ai.tsx`, handles the stream events, saved
|
|
469
|
+
rows, answers, and errors. Steps: [Add agent asks to a chat](../how-to/agent-ui-asks.md).
|
|
470
|
+
|
|
471
|
+
### BlocksView
|
|
472
|
+
|
|
473
|
+
`BlocksView` paints a whole-reply document from `@terreno/blocks`. Pass the assistant text
|
|
474
|
+
as `document`. Leaf blocks in this slice are `heading`, `text`, `metric`, `badge`,
|
|
475
|
+
`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`,
|
|
477
|
+
`AreaChart`, or `DonutChart` from an inline dataset or from `points`. The series is only
|
|
478
|
+
`{label, value}` — a point color in the document is not passed through. A `table` draws
|
|
479
|
+
`DataTable`. A `ref` dataset stays empty until `resolveDataset` returns its rows, and the
|
|
480
|
+
chart shows `loading` while that promise is in flight. A rejected fetch clears that
|
|
481
|
+
loading state. A segmented control highlights the option whose `data` matches the target
|
|
482
|
+
chart or table. A `select` action switches a table the same way it switches a chart.
|
|
483
|
+
An image `file:` ref is not passed to `Image` until `resolveImage` returns a URL; the
|
|
484
|
+
alt text still shows.
|
|
485
|
+
|
|
486
|
+
A string that is not a document becomes one `text` block. A document that fails
|
|
487
|
+
`validateBlocks` shows a `Banner` with the first three errors and the raw text inside a
|
|
488
|
+
collapsed `Accordion`.
|
|
489
|
+
|
|
490
|
+
An `actions` block draws `Button` and `SegmentedControl`. `onAction` receives
|
|
491
|
+
`{action, blockId, elementId}` for reply, open, select, and callback. A `select`
|
|
492
|
+
action also stores the chosen dataset on the target chart for this view. Pass
|
|
493
|
+
`hostActions` to disable callback buttons whose names are not in that list. Omit
|
|
494
|
+
`hostActions` and every callback stays enabled. `pendingElementIds` shows those
|
|
495
|
+
buttons as loading. `overrides` replaces a block by id.
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
<BlocksView
|
|
499
|
+
document={reply}
|
|
500
|
+
hostActions={["export_csv"]}
|
|
501
|
+
onAction={handleAction}
|
|
502
|
+
testID="assistant-blocks"
|
|
503
|
+
/>
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Demo story: `BlocksView`.
|
|
507
|
+
|
|
508
|
+
### HtmlFrame
|
|
509
|
+
|
|
510
|
+
`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.
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
<HtmlFrame height="md" html="<h1>Invoice</h1>" title="Invoice preview" />
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### AskCard
|
|
517
|
+
|
|
518
|
+
One agent ask in a chat transcript: controls while it is pending, a summary line after. `GPTChat`
|
|
519
|
+
renders it for messages with `ask`. Render it directly in a custom transcript. A saved ask whose
|
|
520
|
+
input no longer validates, or whose `accept` answer has no `content` object, shows the generic
|
|
521
|
+
line ("You answered this question.") and no sent values.
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
<AskCard ask={ask} errors={errors} onSubmit={handleAskSubmit} testID="plan-ask" />
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
| Prop | Type | Description |
|
|
528
|
+
| --- | --- | --- |
|
|
529
|
+
| `ask` | `ChatAsk` | The ask. Pending asks are interactive. Answered and cancelled asks show a summary. |
|
|
530
|
+
| `errors` | `AskValidationError[]` | Errors for the last answer, shown under the controls |
|
|
531
|
+
| `onSubmit` | `AskSubmitHandler` | Called with `{toolCallId, response}`. Without it, the card cannot be answered: buttons and the select are disabled, and radio and checkbox options show as plain text. When the promise it returns rejects, the ask stays open and the card shows "Your answer could not be sent. Try again." until the next answer. |
|
|
532
|
+
| `promptRef` | `React.Ref<Text>` (React Native) | Receives the question's native text, which is an accessibility header. `GPTChat` uses it to move screen reader focus to a pending ask on native. |
|
|
533
|
+
| `resolveAskFiles` | `AskFilesResolver` | For a `files` ask: turns the picked files into refs on Submit. Defaults to data URLs. `GPTChat` passes its own `resolveAskFiles`. |
|
|
534
|
+
| `testID` | string | Defaults to `ask-card`. `GPTChat` passes `gpt-ask-<toolCallId>`. |
|
|
535
|
+
|
|
536
|
+
A saved ask whose input no longer passes `validateAskInput` shows "This question cannot be shown.
|
|
537
|
+
Send a message to continue." while pending, and "You answered this question." once answered.
|
|
538
|
+
|
|
539
|
+
`choice` controls:
|
|
540
|
+
|
|
541
|
+
| Ask | Controls |
|
|
542
|
+
| --- | --- |
|
|
543
|
+
| The simple card has a button for every option (`handoff: false`), and every option label is at most 20 characters | The card's buttons as quick replies, then Skip unless `allowDecline` is `false`. A tap answers. |
|
|
544
|
+
| Up to 8 options | `RadioField`, then Submit (`submitLabel`) and Skip |
|
|
545
|
+
| More than 8 options | Searchable `SelectField`, then Submit and Skip |
|
|
546
|
+
| `select: "many"` | `MultiselectField` with a hint such as "Choose 1 to 3.", then a `TextField` titled `otherLabel` (default "Other") when `allowOther` is `true`, then Submit and Skip |
|
|
547
|
+
|
|
548
|
+
The selection starts on the ask's `default`. Submit is enabled only when `validateAskResponse`
|
|
549
|
+
accepts the selection. For `select: "many"`, the answer lists the checked ids in option order and
|
|
550
|
+
sends the Other text trimmed, leaving `other` out when the field is blank. When the checked
|
|
551
|
+
options and the Other text add up to more than `maxSelected`, the checkboxes say so ("You chose
|
|
552
|
+
4. Choose at most 3."). An Other text over 500 characters, or a server error at `content.other`,
|
|
553
|
+
shows on the Other field. When `ask.simple` is absent, the card derives it with `toSimpleCard`. An
|
|
554
|
+
ask whose input fails `validateAskInput` shows "This question cannot be shown. Send a message to
|
|
555
|
+
continue." instead of controls.
|
|
556
|
+
|
|
557
|
+
`confirm` controls: the simple card's two buttons in its order, the approve button (`confirmLabel`,
|
|
558
|
+
default "Confirm") first and the deny button (`denyLabel`, default "Cancel") last. The approve
|
|
559
|
+
button uses the `destructive` variant when the ask sets `destructive: true`, else `primary`; the
|
|
560
|
+
deny button uses `ghost`. Skip follows only when `allowDecline` is `true`. A tap answers
|
|
561
|
+
`{"confirmed": true}` or `{"confirmed": false}`. Without `onSubmit`, both buttons are disabled.
|
|
562
|
+
|
|
563
|
+
`markdown` controls: a `MarkdownEditorField` (edit and preview, at most 320 pt tall) that starts
|
|
564
|
+
on the ask's `initial` draft and shows `placeholder` while empty, then Submit (`submitLabel`) and
|
|
565
|
+
Skip unless `allowDecline` is `false`. Under the editor a hint gives the length and the bounds,
|
|
566
|
+
such as "1,240 / 2,000 characters. At least 20." Submit is enabled only when
|
|
567
|
+
`validateAskResponse` accepts the text, and it sends `{markdown, changed}` with `changed` true
|
|
568
|
+
when the text differs from `initial`. Text over `maxLength`, or a server error at
|
|
569
|
+
`content.markdown`, shows on the editor. Without `onSubmit`, the editor and both buttons are
|
|
570
|
+
disabled. An answered markdown ask shows the sent text under its summary; text over 280
|
|
571
|
+
characters shows a preview cut at a word, with Show all and Show less.
|
|
572
|
+
|
|
573
|
+
`form` controls: one field per entry in `fields`, in order, titled with the field's `label`
|
|
574
|
+
("(required)" appended for required fields) and its `helperText`, then Submit (`submitLabel`) and
|
|
575
|
+
Skip unless `allowDecline` is `false`. Each field uses the `@terreno/ui` control for its type:
|
|
576
|
+
|
|
577
|
+
| Field `type` | Control | Sends |
|
|
578
|
+
| --- | --- | --- |
|
|
579
|
+
| `text`, `email`, `url` | `TextField` (`text`, `email`, `url`) | The trimmed text |
|
|
580
|
+
| `phone` | `TextField` (`phoneNumber`) | E.164, such as `+14155552671`, when the number parses (as a US number without a country code); else the trimmed text |
|
|
581
|
+
| `number` | `TextField` | A number when the text is plain decimal notation; else the text, so validation says why |
|
|
582
|
+
| `textarea` | `TextArea` | The trimmed text |
|
|
583
|
+
| `date` | `DateTimeField` (`date`) | `YYYY-MM-DD` |
|
|
584
|
+
| `time` | `DateTimeField` (`time`) with its time zone picker (the device's zone to start) | 24-hour `HH:mm` as shown; the zone is not sent |
|
|
585
|
+
| `datetime` | `DateTimeField` (`datetime`) with a time zone picker | An ISO datetime with the chosen zone's offset, such as `2026-10-01T09:30:00-07:00` |
|
|
586
|
+
| `boolean` | `BooleanField` | `true` or `false`, always |
|
|
587
|
+
| `select` | `SelectField` | The option id |
|
|
588
|
+
| `multiselect` | `MultiselectField` | The checked option ids |
|
|
589
|
+
|
|
590
|
+
Fields start on their `default`. Blank fields are left out of `values`. Submit is enabled only
|
|
591
|
+
when `validateAskResponse` accepts the values and no date, time, or datetime field holds an
|
|
592
|
+
unfinished entry. `DateTimeField` reports that through `onEntryStatusChange`, such as
|
|
593
|
+
"0 / 5 / 026": the field says "Enter a complete date, or clear it." until the user finishes it,
|
|
594
|
+
and clearing every part leaves the field out. After the user edits a field, it says what is
|
|
595
|
+
wrong in plain words, such as "Enter a number from 1 to 500." or "This field is required." A
|
|
596
|
+
server error whose path is `content.values.<id>` (or an item under it) shows on that field until
|
|
597
|
+
the user edits it; other errors show under the form. Without `onSubmit`, every field and both
|
|
598
|
+
buttons are disabled. An answered form lists each sent field as its label and a readable value
|
|
599
|
+
(Yes or No, option labels, "Oct 1, 2026", "9:30 AM", text shortened to one line of 80
|
|
600
|
+
characters) under its summary.
|
|
601
|
+
|
|
602
|
+
`files` controls: a hint built from the counts and types, such as "Up to 3 files: images, PDFs or
|
|
603
|
+
text files.", then a "Choose files" `FilePickerButton` ("Add files" once some are picked), the
|
|
604
|
+
picked files as an `AttachmentPreview` with a remove control on each, then Submit (`submitLabel`,
|
|
605
|
+
default "Submit") and Skip unless `allowDecline` is `false`. The picker offers Photo Library only
|
|
606
|
+
when `accept` has `image`, and its document picker offers only the accepted MIME types. It allows
|
|
607
|
+
several files when `maxFiles` is more than 1 and is disabled once `maxFiles` files are picked.
|
|
608
|
+
Submit is enabled only when `validateAskResponse` accepts the picked names, types, and sizes. On
|
|
609
|
+
Submit, the card calls `resolveAskFiles` with the picked files and sends `{files: refs}`; Submit
|
|
610
|
+
shows a spinner and Skip is disabled until the answer is sent. When the ask ends another way while
|
|
611
|
+
the resolver runs, such as an answer from another tab, its refs are not sent. If the resolver throws, the ask
|
|
612
|
+
stays open and the card shows "The files could not be sent. Try again, or pick them again." Server
|
|
613
|
+
errors, such as `MIME_MISMATCH`, show under the picked files. Each file's type is
|
|
614
|
+
`selectedFileMimeType(file)`, so a CSV a picker reports as `application/vnd.ms-excel` is sent as
|
|
615
|
+
`text/csv`.
|
|
616
|
+
|
|
617
|
+
| How the ask ended | Summary |
|
|
618
|
+
| --- | --- |
|
|
619
|
+
| `files` `accept` | You sent `<n>` files: `<filenames>`, or You sent 1 file: `<filename>` |
|
|
620
|
+
| `form` `accept` | You sent the form (`<n>` fields) |
|
|
621
|
+
| `markdown` `accept` | You approved the draft as is (`changed: false`), or You edited the draft (`<n>` characters) |
|
|
622
|
+
| `confirm` `accept` | You confirmed: `<confirmLabel>`, or You declined: `<denyLabel>`, with the default labels when the ask sets none |
|
|
623
|
+
| `choice` `accept` | You chose: `<option labels>`. With Other text, it adds "`<otherLabel>`: `<text>`" (the label defaults to "Other"), or shows only that when no option was checked. An empty `select: "many"` answer shows "You chose none of the options." |
|
|
624
|
+
| `decline` | You skipped this question. |
|
|
625
|
+
| `cancel` with `user_sent_message` | Not answered: you sent a message instead. |
|
|
626
|
+
| `cancel` with `one_ask_at_a_time` | Not asked: the assistant asked another question first. |
|
|
627
|
+
| Any other `cancel`, or status `cancelled` without a response | This question was cancelled. |
|
|
628
|
+
| Status `answered` without a response | You answered this question. |
|
|
629
|
+
|
|
630
|
+
| Element | testID |
|
|
631
|
+
| --- | --- |
|
|
632
|
+
| Card | `{testID}` |
|
|
633
|
+
| Quick reply row | `{testID}-quick-replies` |
|
|
634
|
+
| Quick reply or Skip button | `{testID}-button-<button id>`, such as `{testID}-button-option:team` or `{testID}-button-skip` |
|
|
635
|
+
| Confirm button row | `{testID}-confirm-buttons` |
|
|
636
|
+
| Confirm approve or deny button | `{testID}-button-approve`, `{testID}-button-deny` |
|
|
637
|
+
| Radio options (up to 8) | `{testID}-radio` |
|
|
638
|
+
| Select | `{testID}-select` |
|
|
639
|
+
| Checkboxes (`select: "many"`) | `{testID}-multiselect` |
|
|
640
|
+
| Other text field | `{testID}-other` |
|
|
641
|
+
| Markdown editor, and its text input | `{testID}-editor`, `{testID}-editor-input` |
|
|
642
|
+
| Sent markdown under an answered ask, and its Show all toggle | `{testID}-answer`, `{testID}-answer-toggle` |
|
|
643
|
+
| Form field, and its wrapper | `{testID}-field-<field id>` (a `BooleanField` switch is `{testID}-field-<field id>.switch`), `{testID}-form-field-<field id>` |
|
|
644
|
+
| Sent form values under an answered ask | `{testID}-answer` |
|
|
645
|
+
| Files hint, picker, and picked files | `{testID}-hint`, `{testID}-picker`, `{testID}-selected` |
|
|
646
|
+
| Files that could not be sent | `{testID}-resolve-error` |
|
|
647
|
+
| Radio or checkbox options as plain text, without `onSubmit` | `{testID}-options` |
|
|
648
|
+
| Submit | `{testID}-submit` |
|
|
649
|
+
| Answer errors | `{testID}-errors` |
|
|
650
|
+
| Summary | `{testID}-summary` |
|
|
651
|
+
| Ask that cannot be shown | `{testID}-invalid` |
|
|
652
|
+
| Answer that could not be sent | `{testID}-submit-error` |
|
|
653
|
+
|
|
654
|
+
Types: `AskCardProps`, `ChatAsk`, `ChatAskState`, `ChatAskStatus`, `AskSubmission`,
|
|
655
|
+
`AskSubmitHandler`, `AskFilesResolver`. Demo story: `AskCard`.
|
|
656
|
+
|
|
657
|
+
File ref helpers:
|
|
658
|
+
|
|
659
|
+
| Export | Description |
|
|
660
|
+
| --- | --- |
|
|
661
|
+
| `AskFilesResolver` | `(files: SelectedFile[]) => Promise<AskFileRef[]>`. Throw to keep the ask open. |
|
|
662
|
+
| `resolveAskFilesAsDataUrls` | The default resolver: every file as a `{url}` data URL |
|
|
663
|
+
| `selectedFileToDataUrlRef(file)` | One picked file as `{filename, mimeType, size, url}`. The data URL's media type is `selectedFileMimeType(file)`, and `size` is the decoded byte count. |
|
|
664
|
+
| `selectedFileMimeType(file)` | The picked file's normalized type. When the picker reports none, `application/octet-stream`, or `application/vnd.ms-excel` (Windows reports CSV files so), the type comes from the extension: `.csv`, `.gif`, `.jpeg`, `.jpg`, `.json`, `.pdf`, `.png`, `.txt`, `.webp`. Otherwise the reported type is kept. |
|
|
665
|
+
| `normalizeMimeType(mimeType)` | Drops parameters and lowercases: `Text/CSV; charset=utf-8` becomes `text/csv` |
|
|
666
|
+
|
|
667
|
+
`FilePickerButton` props used by the files card, also available to any caller:
|
|
668
|
+
|
|
669
|
+
| Prop | Type | Description |
|
|
670
|
+
| --- | --- | --- |
|
|
671
|
+
| `documentTypes` | `string[]` | MIME types the document picker offers. Defaults to PDF, text, CSV, and JSON. An empty list hides Document. |
|
|
672
|
+
| `includeImages` | boolean | Offer Photo Library. Defaults to `true`. When `false`, the button opens the document picker directly. |
|
|
673
|
+
| `text` | string | Shows an outline button with this label instead of the paperclip icon |
|
|
674
|
+
|
|
675
|
+
`SelectedFile.size` is the file's size in bytes when the picker reports it.
|
|
676
|
+
|
|
677
|
+
### SimpleAskCard
|
|
678
|
+
|
|
679
|
+
Any agent ask as its [simple card](agent-ui-asks.md#simple-cards): the title, the question, and up
|
|
680
|
+
to three full-width buttons that each send an exact answer. Use it in narrow layouts, such as a
|
|
681
|
+
watch-sized preview. It reads the card's `kind` only to word the handoff line.
|
|
682
|
+
|
|
683
|
+
```tsx
|
|
684
|
+
const [runTurn] = useGpthistoriesTurnMutation();
|
|
685
|
+
|
|
686
|
+
<SimpleAskCard
|
|
687
|
+
card={pendingAsk.simple}
|
|
688
|
+
onPress={(button) =>
|
|
689
|
+
runTurn({
|
|
690
|
+
body: {buttonId: button.id, surface: "compact", toolCallId: pendingAsk.toolCallId},
|
|
691
|
+
id: historyId,
|
|
692
|
+
})
|
|
693
|
+
}
|
|
694
|
+
pendingButtonId={sendingButtonId}
|
|
695
|
+
/>
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
| Prop | Type | Description |
|
|
699
|
+
| --- | --- | --- |
|
|
700
|
+
| `card` | `SimpleCard` | `pendingAsk.simple` from the server, or `simple` on an `{ask}` event |
|
|
701
|
+
| `onPress` | `(button: SimpleCardButton) => void \| Promise<void>` | Called with the pressed button. Send `{toolCallId: card.toolCallId, buttonId: button.id}` to the history's [`turn` action](agent-ui-asks.md#headless-endpoints), or send `button.response` as the answer. |
|
|
702
|
+
| `pendingButtonId` | string? | The button whose answer is still sending. It shows a spinner, the other buttons are disabled, and presses are ignored. |
|
|
703
|
+
| `testID` | string? | Defaults to `simple-ask-card` |
|
|
704
|
+
|
|
705
|
+
- A card with `handoff: true` shows "Continue on your phone" under the question, because its
|
|
706
|
+
buttons cannot give every answer. A `markdown` card shows "Edit on your phone" instead, a
|
|
707
|
+
`form` card "Fill it in on your phone", and a `files` card "Upload on your phone".
|
|
708
|
+
- Button styles map to `Button` variants the same way as `AskCard` quick replies: `primary` to
|
|
709
|
+
`primary`, `default` to `outline`, `destructive` to `destructive`, and `cancel` to `ghost`.
|
|
710
|
+
|
|
711
|
+
| Element | testID |
|
|
712
|
+
| --- | --- |
|
|
713
|
+
| Card | `{testID}` |
|
|
714
|
+
| Continue on your phone, or Edit on your phone | `{testID}-handoff` |
|
|
715
|
+
| Button | `{testID}-button-<button id>`, such as `{testID}-button-option:team` |
|
|
716
|
+
|
|
717
|
+
Types: `SimpleAskCardProps`, and `SimpleCard` and `SimpleCardButton` from `@terreno/blocks`. Demo
|
|
718
|
+
story: `SimpleAskCard`. Its demo answers a plan card on a watch-sized 198×242 pt screen, and its
|
|
719
|
+
"Every fixture" story draws every valid fixture's card at that size.
|
|
720
|
+
|
|
397
721
|
### SplitPage
|
|
398
722
|
|
|
399
723
|
Master-detail layout. Pass `listViewData` plus `renderListViewItem` for the list, and
|