@terreno/mcp 57.8.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.
Files changed (115) hide show
  1. package/dist/docs/upgrades/57.10.1.md +54 -0
  2. package/dist/docs/upgrades/57.9.0.md +28 -0
  3. package/dist/docs/versioned/next/README.md +1 -0
  4. package/dist/docs/versioned/next/explanation/README.md +4 -0
  5. package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
  6. package/dist/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
  7. package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
  8. package/dist/docs/versioned/next/explanation/ai-observability.md +101 -0
  9. package/dist/docs/versioned/next/explanation/charts.md +12 -0
  10. package/dist/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
  11. package/dist/docs/versioned/next/explanation/example-coverage.md +4 -2
  12. package/dist/docs/versioned/next/explanation/loop-engineering.md +1 -1
  13. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +49 -1
  14. package/dist/docs/versioned/next/explanation/typescript-configuration.md +1 -1
  15. package/dist/docs/versioned/next/explanation/versioning-policy.md +1 -1
  16. package/dist/docs/versioned/next/how-to/README.md +7 -1
  17. package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +1 -1
  18. package/dist/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
  19. package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
  20. package/dist/docs/versioned/next/how-to/ai-feature-development.md +156 -0
  21. package/dist/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
  22. package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +7 -0
  23. package/dist/docs/versioned/next/how-to/circleci.md +9 -6
  24. package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +8 -0
  25. package/dist/docs/versioned/next/how-to/install-agent-skills.md +1 -1
  26. package/dist/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
  27. package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -1
  28. package/dist/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
  29. package/dist/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
  30. package/dist/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
  31. package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
  32. package/dist/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
  33. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
  34. package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +3 -2
  35. package/dist/docs/versioned/next/implementationPlans/dashboard-chart-parity.md +275 -0
  36. package/dist/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
  37. package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
  38. package/dist/docs/versioned/next/reference/README.md +3 -0
  39. package/dist/docs/versioned/next/reference/admin-config.md +2 -0
  40. package/dist/docs/versioned/next/reference/admin-frontend.md +216 -0
  41. package/dist/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
  42. package/dist/docs/versioned/next/reference/ai-harness.md +1881 -0
  43. package/dist/docs/versioned/next/reference/ai.md +283 -28
  44. package/dist/docs/versioned/next/reference/api.md +46 -0
  45. package/dist/docs/versioned/next/reference/blocks.md +178 -0
  46. package/dist/docs/versioned/next/reference/environment-variables.md +2 -1
  47. package/dist/docs/versioned/next/reference/feature-flags.md +1 -1
  48. package/dist/docs/versioned/next/reference/mcp-server.md +17 -0
  49. package/dist/docs/versioned/next/reference/test.md +11 -2
  50. package/dist/docs/versioned/next/reference/ui.md +418 -11
  51. package/dist/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
  52. package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
  53. package/dist/docs/versioned/next/tasks/ai-observability.md +233 -104
  54. package/dist/docs/versioned/next/tasks/dashboard-chart-parity.md +145 -0
  55. package/dist/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
  56. package/dist/tools.d.ts +4 -0
  57. package/dist/tools.d.ts.map +1 -1
  58. package/dist/tools.js +37 -0
  59. package/dist/tools.js.map +1 -1
  60. package/package.json +3 -2
  61. package/src/docs/upgrades/57.10.1.md +54 -0
  62. package/src/docs/upgrades/57.9.0.md +28 -0
  63. package/src/docs/versioned/next/README.md +1 -0
  64. package/src/docs/versioned/next/explanation/README.md +4 -0
  65. package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
  66. package/src/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
  67. package/src/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
  68. package/src/docs/versioned/next/explanation/ai-observability.md +101 -0
  69. package/src/docs/versioned/next/explanation/charts.md +12 -0
  70. package/src/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
  71. package/src/docs/versioned/next/explanation/example-coverage.md +4 -2
  72. package/src/docs/versioned/next/explanation/loop-engineering.md +1 -1
  73. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +49 -1
  74. package/src/docs/versioned/next/explanation/typescript-configuration.md +1 -1
  75. package/src/docs/versioned/next/explanation/versioning-policy.md +1 -1
  76. package/src/docs/versioned/next/how-to/README.md +7 -1
  77. package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +1 -1
  78. package/src/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
  79. package/src/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
  80. package/src/docs/versioned/next/how-to/ai-feature-development.md +156 -0
  81. package/src/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
  82. package/src/docs/versioned/next/how-to/charts-and-dashboards.md +7 -0
  83. package/src/docs/versioned/next/how-to/circleci.md +9 -6
  84. package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +8 -0
  85. package/src/docs/versioned/next/how-to/install-agent-skills.md +1 -1
  86. package/src/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
  87. package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -1
  88. package/src/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
  89. package/src/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
  90. package/src/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
  91. package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
  92. package/src/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
  93. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
  94. package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +3 -2
  95. package/src/docs/versioned/next/implementationPlans/dashboard-chart-parity.md +275 -0
  96. package/src/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
  97. package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
  98. package/src/docs/versioned/next/reference/README.md +3 -0
  99. package/src/docs/versioned/next/reference/admin-config.md +2 -0
  100. package/src/docs/versioned/next/reference/admin-frontend.md +216 -0
  101. package/src/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
  102. package/src/docs/versioned/next/reference/ai-harness.md +1881 -0
  103. package/src/docs/versioned/next/reference/ai.md +283 -28
  104. package/src/docs/versioned/next/reference/api.md +46 -0
  105. package/src/docs/versioned/next/reference/blocks.md +178 -0
  106. package/src/docs/versioned/next/reference/environment-variables.md +2 -1
  107. package/src/docs/versioned/next/reference/feature-flags.md +1 -1
  108. package/src/docs/versioned/next/reference/mcp-server.md +17 -0
  109. package/src/docs/versioned/next/reference/test.md +11 -2
  110. package/src/docs/versioned/next/reference/ui.md +418 -11
  111. package/src/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
  112. package/src/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
  113. package/src/docs/versioned/next/tasks/ai-observability.md +233 -104
  114. package/src/docs/versioned/next/tasks/dashboard-chart-parity.md +145 -0
  115. package/src/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
@@ -4,12 +4,13 @@ React Native UI component library (a large component library). Layout (Box, Page
4
4
 
5
5
  ## Key exports
6
6
 
7
- - Layout: `Box`, `Page`, `SplitPage`, `Card`, `DashboardGrid`
7
+ - Layout: `Box`, `Page`, `SplitPage`, `Card`, `ChartCard`, `DashboardGrid`, `DashboardGridItem`
8
8
  - Forms: `TextField`, `SelectField`, `DateTimeField`, `CheckBox`
9
- - Display: `Text`, `Heading`, `Badge`, `DataTable`, `LineChart`, `BarChart`, `AreaChart`, `DonutChart`
9
+ - Display: `Text`, `Heading`, `Badge`, `DataTable`, `Scorecard`, `SparklineChart`, `LineChart`, `BarChart`, `AreaChart`, `DonutChart`
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,11 +33,11 @@ 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, `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
- their implementation modules load on first render instead of during the initial root import. `MarkdownView` and
39
- `DataTable` header info defer `react-native-markdown-display`; `EmojiSelector` defers `emoji-datasource` until open.
39
+ `MarkdownView` and `DataTable` header info defer `react-native-markdown-display`;
40
+ `EmojiSelector` defers `emoji-datasource` until open.
40
41
 
41
42
  For the smallest cold-start graph, keep using subpaths for screens that only need a few primitives (for example
42
43
  `import {Button} from "@terreno/ui/Button"`).
@@ -60,21 +61,72 @@ const customStyle: StyleProp<ViewStyle> = {
60
61
  - Ensures type compatibility when passing styles to @terreno/ui components
61
62
  - Simplifies imports (one package instead of two)
62
63
 
63
- ### DashboardGrid
64
+ ### DashboardGrid and DashboardGridItem
64
65
 
65
66
  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.
67
+ Wrap a child in `DashboardGridItem` when it must span more than one responsive column.
68
+ Plain children remain one column.
66
69
 
67
70
  ```tsx
68
- <DashboardGrid>
71
+ <DashboardGrid columns={{sm: 1, md: 2, lg: 4}}>
72
+ <DashboardGridItem span={{sm: 1, md: 2, lg: 2}}>
73
+ <Card>
74
+ <LineChart data={points} legendLabel="Signups" />
75
+ </Card>
76
+ </DashboardGridItem>
69
77
  <Card>
70
78
  <LineChart data={points} legendLabel="Signups" />
71
79
  </Card>
72
80
  </DashboardGrid>
73
81
  ```
74
82
 
83
+ ### ChartCard
84
+
85
+ Card chrome for a chart or table: title, optional filter summary, and an optional period
86
+ badge. Pass `onPeriodPress` to make the badge a button; omit it for display-only copy.
87
+
88
+ ```tsx
89
+ <ChartCard title="Cost by Device" periodLabel="Last 30 days">
90
+ <DonutChart data={points} />
91
+ </ChartCard>
92
+ ```
93
+
94
+ ### Scorecard
95
+
96
+ Compact KPI tile with a title, formatted value, and optional current/comparison
97
+ sparkline. Numeric values use `formatValue`; string values render unchanged.
98
+
99
+ ```tsx
100
+ <Scorecard
101
+ title="Cost"
102
+ value={569}
103
+ formatValue={(value) => `$${value}`}
104
+ sparklineData={current}
105
+ comparisonData={previous}
106
+ />
107
+ ```
108
+
109
+ ### SparklineChart
110
+
111
+ Plot-only line chart for compact metrics. `data` draws the current solid line;
112
+ `comparisonData` draws a dotted previous-period line on the same scale. It has no axes,
113
+ grid, legend, tooltip row, or empty-state copy.
114
+
115
+ ```tsx
116
+ <SparklineChart
117
+ data={[{label: "Mon", value: 3}, {label: "Tue", value: 5}]}
118
+ comparisonData={[{label: "Mon", value: 4}, {label: "Tue", value: 4}]}
119
+ />
120
+ ```
121
+
75
122
  ### LineChart
76
123
 
77
- Single-series line chart drawn with `react-native-svg`. Empty data shows `emptyText` (default `"No data"`). `loading` shows a `Spinner`. Press or hover a point for `{label}: {value}`.
124
+ Line chart drawn with `react-native-svg`. Keep the simple `data` + `legendLabel` path for
125
+ one series, or pass named `series` for multiple lines and legend items.
126
+ `comparisonData` adds a dotted previous-period overlay on the shared scale. Empty data
127
+ shows `emptyText` (default `"No data"`). `loading` shows a `Spinner`.
128
+ Set `xTickPolicy` to `auto` (default), `rotate`, or `truncate`. `title`,
129
+ `periodLabel`, and `onPeriodPress` use the same header chrome as `ChartCard`.
78
130
 
79
131
  ```tsx
80
132
  <LineChart
@@ -88,7 +140,10 @@ Hit targets use `Box` `onClick`, so testIDs are `{testID}.point.{index}-clickabl
88
140
 
89
141
  ### BarChart
90
142
 
91
- Single-series bar chart on the same owned-SVG contract as `LineChart` (empty, loading, legend, tooltip).
143
+ Bar chart on the same owned-SVG contract as `LineChart` (empty, loading, legend,
144
+ tooltip, tick policy, and header shortcuts). `comparisonData` paints lighter bars behind
145
+ the current period. When `series` is passed in this facade, its first entry supplies the
146
+ bars and legend; grouped bars remain future work.
92
147
 
93
148
  ```tsx
94
149
  <BarChart
@@ -101,6 +156,8 @@ Single-series bar chart on the same owned-SVG contract as `LineChart` (empty, lo
101
156
  ### AreaChart
102
157
 
103
158
  Filled area plus line on the same owned-SVG contract as `LineChart`.
159
+ Named `series` render one fill/line pair and legend item each; `comparisonData` stays a
160
+ dotted line rather than a filled area.
104
161
 
105
162
  ```tsx
106
163
  <AreaChart
@@ -112,11 +169,16 @@ Filled area plus line on the same owned-SVG contract as `LineChart`.
112
169
 
113
170
  ### DonutChart
114
171
 
115
- One slice per `{label, value}` point. Per-slice `color` overrides the theme paint. Legend is one row per slice (`legendLabel` is ignored).
172
+ One slice per `{label, value}` point. Per-slice `color` overrides the theme paint.
173
+ Legend rows show label + percent by default; customize with `formatShare`.
174
+ `centerValue` and `centerTitle` put an aggregate in the donut hole. `title` /
175
+ `periodLabel` use `ChartCard` chrome (`legendLabel` remains ignored).
116
176
 
117
177
  ```tsx
118
178
  <DonutChart
119
179
  data={[{label: "Open", value: 3}, {color: "#112233", label: "Closed", value: 5}]}
180
+ centerValue="8"
181
+ centerTitle="Total"
120
182
  testID="status-donut"
121
183
  />
122
184
  ```
@@ -189,6 +251,26 @@ Buttons automatically size to their content unless `fullWidth` is specified:
189
251
 
190
252
  Internally, Button sets `alignSelf: 'flex-start'` when `fullWidth={false}` to prevent stretching in column layouts.
191
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
+
266
+ ### TextField grow and maxHeight
267
+
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.
269
+
270
+ ```tsx
271
+ <TextArea grow maxHeight={200} onChange={setNotes} value={notes} />
272
+ ```
273
+
192
274
  ### TextField password visibility
193
275
 
194
276
  `type="password"` masks the value and renders a show/hide eye control at the end of the field.
@@ -295,7 +377,15 @@ Streaming chat surface for `@terreno/ai`. Histories, messages, submit, and optio
295
377
 
296
378
  Pass `mascot` when the app owns a character. Terreno ships none. The node renders only while `currentMessages` is empty, centered in the chat panel above the suggested prompts. The empty hero uses the message viewport height as a minimum so short content stays centered, while taller mascots still scroll. Streaming feedback shares that centered hero instead of creating a second pane. Omit the prop for the default empty chat.
297
379
 
298
- The composer row (attachment picker, tools, input, Send) is vertically centered, so controls stay aligned with the input as it grows. The attachment cell is omitted when `onAttachFiles` is not provided.
380
+ The composer row (attachment picker, tools, input, Send) is vertically centered, so controls stay aligned with the input as it grows. The composer input grows with its text up to 200px, then scrolls. The attachment cell is omitted when `onAttachFiles` is not provided.
381
+
382
+ Each history row keeps rename and delete in a three-dot overflow menu (`gpt-history-menu-{id}`), anchored with `DropdownPanel`. Rename uses an outlined pencil in the dark secondary text color. Titles truncate before the menu trigger, so the trigger stays aligned at any sidebar width.
383
+
384
+ "Scroll to bottom" appears only when content sits more than 100px below the viewport. It never shows in an empty chat.
385
+
386
+ Image content parts render copy-image and (web) download actions. The message copy button on an image-only reply copies the image, not text. On web the image is written to the clipboard as PNG; on native it uses `expo-clipboard` `setImageAsync` for `data:` URLs.
387
+
388
+ The attach control (`FilePickerButton`) opens an anchored dropdown with **Photo Library** and **Document**. On web both open a browser file input from the press itself and return `data:` URLs, so attachments survive a reload; on native they use `expo-image-picker` and `expo-document-picker`.
299
389
 
300
390
  ```tsx
301
391
  <GPTChat
@@ -313,10 +403,321 @@ The composer row (attachment picker, tools, input, Send) is vertically centered,
313
403
  />
314
404
  ```
315
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
+
316
431
  Operator steps: [Add a GPT chat mascot](../how-to/add-gpt-chat-mascot.md). Demo story: `GPTChat` → `Mascot`.
432
+ Blocks playground: `BlocksPlayground`.
317
433
  The example AI screen demonstrates a consumer selecting one of four bundled mascot
318
434
  images once per mount.
319
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
+
320
721
  ### SplitPage
321
722
 
322
723
  Master-detail layout. Pass `listViewData` plus `renderListViewItem` for the list, and
@@ -362,6 +763,12 @@ Buttons use a scale animation by default. Set `pressAnimation="opacity"` for an
362
763
 
363
764
  Disabled and loading buttons use a non-interactive pressable regardless of the selected animation.
364
765
 
766
+ ### Toast
767
+
768
+ `TerrenoProvider` mounts the toast container. Call `useToast()` for `success`, `info`, `warn`, `error`, `show`, `hide`, and `catch`.
769
+
770
+ 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.
771
+
365
772
  ## Authentication Components
366
773
 
367
774
  ### SocialLoginButton