@terreno/mcp 57.11.0 → 57.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/upgrades/57.12.0.md +89 -0
- package/dist/docs/upgrades/57.13.0.md +75 -0
- package/dist/docs/versioned/next/explanation/README.md +1 -1
- package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
- package/dist/docs/versioned/next/explanation/ai-workflows.md +4 -3
- package/dist/docs/versioned/next/explanation/example-coverage.md +3 -3
- package/dist/docs/versioned/next/explanation/roadmap-process.md +4 -2
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
- package/dist/docs/versioned/next/how-to/README.md +2 -0
- package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
- package/dist/docs/versioned/next/how-to/circleci.md +8 -2
- package/dist/docs/versioned/next/how-to/github-actions-ci.md +5 -6
- package/dist/docs/versioned/next/how-to/preview-demo.md +5 -5
- package/dist/docs/versioned/next/how-to/rate-limiting.md +17 -0
- package/dist/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
- package/dist/docs/versioned/next/reference/README.md +1 -1
- package/dist/docs/versioned/next/reference/admin-backend.md +26 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +15 -0
- package/dist/docs/versioned/next/reference/ai-harness.md +33 -5
- package/dist/docs/versioned/next/reference/ai.md +88 -7
- package/dist/docs/versioned/next/reference/api.md +4 -0
- package/dist/docs/versioned/next/reference/blocks.md +243 -11
- package/dist/docs/versioned/next/reference/environment-variables.md +3 -0
- package/dist/docs/versioned/next/reference/ui.md +222 -12
- package/dist/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
- package/package.json +3 -3
- package/src/docs/upgrades/57.12.0.md +89 -0
- package/src/docs/upgrades/57.13.0.md +75 -0
- package/src/docs/versioned/next/explanation/README.md +1 -1
- package/src/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
- package/src/docs/versioned/next/explanation/ai-workflows.md +4 -3
- package/src/docs/versioned/next/explanation/example-coverage.md +3 -3
- package/src/docs/versioned/next/explanation/roadmap-process.md +4 -2
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
- package/src/docs/versioned/next/how-to/README.md +2 -0
- package/src/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
- package/src/docs/versioned/next/how-to/circleci.md +8 -2
- package/src/docs/versioned/next/how-to/github-actions-ci.md +5 -6
- package/src/docs/versioned/next/how-to/preview-demo.md +5 -5
- package/src/docs/versioned/next/how-to/rate-limiting.md +17 -0
- package/src/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
- package/src/docs/versioned/next/reference/README.md +1 -1
- package/src/docs/versioned/next/reference/admin-backend.md +26 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +15 -0
- package/src/docs/versioned/next/reference/ai-harness.md +33 -5
- package/src/docs/versioned/next/reference/ai.md +88 -7
- package/src/docs/versioned/next/reference/api.md +4 -0
- package/src/docs/versioned/next/reference/blocks.md +243 -11
- package/src/docs/versioned/next/reference/environment-variables.md +3 -0
- package/src/docs/versioned/next/reference/ui.md +222 -12
- package/src/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
|
@@ -61,6 +61,55 @@ const customStyle: StyleProp<ViewStyle> = {
|
|
|
61
61
|
- Ensures type compatibility when passing styles to @terreno/ui components
|
|
62
62
|
- Simplifies imports (one package instead of two)
|
|
63
63
|
|
|
64
|
+
## Theme modes
|
|
65
|
+
|
|
66
|
+
`TerrenoProvider` defaults to the Flourish Health light mode exported from
|
|
67
|
+
Figma. Select the matching Figma dark mode explicitly or follow the operating
|
|
68
|
+
system:
|
|
69
|
+
|
|
70
|
+
``````tsx
|
|
71
|
+
import {TerrenoProvider} from "@terreno/ui";
|
|
72
|
+
|
|
73
|
+
<TerrenoProvider colorScheme="system">
|
|
74
|
+
<App />
|
|
75
|
+
</TerrenoProvider>
|
|
76
|
+
``````
|
|
77
|
+
|
|
78
|
+
`colorScheme` accepts `"light"`, `"dark"`, or `"system"` and defaults to
|
|
79
|
+
`"light"` for deterministic server rendering. `"system"` follows the operating
|
|
80
|
+
system. Change the choice after startup with `setColorScheme`:
|
|
81
|
+
|
|
82
|
+
``````tsx
|
|
83
|
+
import {SelectField, themeColorSchemeOptions, useTheme, type ThemeColorScheme} from "@terreno/ui";
|
|
84
|
+
|
|
85
|
+
const AppearanceSetting = () => {
|
|
86
|
+
const {colorSchemeSetting, setColorScheme} = useTheme();
|
|
87
|
+
return (
|
|
88
|
+
<SelectField
|
|
89
|
+
onChange={(value) => {
|
|
90
|
+
setColorScheme(value as ThemeColorScheme);
|
|
91
|
+
}}
|
|
92
|
+
options={themeColorSchemeOptions}
|
|
93
|
+
title="Appearance"
|
|
94
|
+
value={colorSchemeSetting}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
``````
|
|
99
|
+
|
|
100
|
+
Dark `surface.warningLight` uses `warning200`. The lighter warning orange, `warning100`, is 2.52:1 against warning text and misses WCAG AA. Highlighted `DataTable` cells switch to `text.inverted` when `text.primary` is below 4.5:1 on that fill. Inverted `MarkdownView` code uses `surface.neutralDark` when `surface.neutralLight` is below 4.5:1 against that text, and blockquotes do the same with `surface.baseAlternate`. Primary `Button` labels use `text.secondaryDark` when that pair meets 4.5:1 on `surface.primary`, and `text.inverted` otherwise, so `setTheme(darkThemeConfig)` stays readable without changing `colorScheme`. The demo palette generator emits status `050` steps so custom palettes update those dark error, success, and warning surfaces.
|
|
101
|
+
|
|
102
|
+
`themeColorSchemeOptions` labels those values Light, Dark, and Follow system.
|
|
103
|
+
`useTheme()` also returns the resolved `colorScheme` (`"light"` or `"dark"`)
|
|
104
|
+
with `theme`, `setTheme`, `setPrimitives`, and `resetTheme`. An explicit
|
|
105
|
+
`colorScheme` prop replaces the current choice whenever the parent changes it.
|
|
106
|
+
|
|
107
|
+
The public `lightThemeConfig`, `darkThemeConfig`, and
|
|
108
|
+
`defaultThemePrimitives` exports are the canonical token maps. They include the
|
|
109
|
+
Figma AI surface/border roles and the dark status ramps. Custom overrides set
|
|
110
|
+
through `useTheme()` are layered over the active mode; `resetTheme()` removes
|
|
111
|
+
those overrides without changing the provider's selected mode.
|
|
112
|
+
|
|
64
113
|
### DashboardGrid and DashboardGridItem
|
|
65
114
|
|
|
66
115
|
Eager layout-only wrapping grid. Default columns `{sm: 1, md: 2, lg: 3}`. Children stay caller-supplied `Card`s. Cell width is `(rowWidth - gap × (columns - 1)) / columns` so flex `gap` does not wrap extra columns.
|
|
@@ -473,10 +522,17 @@ rows, answers, and errors. Steps: [Add agent asks to a chat](../how-to/agent-ui-
|
|
|
473
522
|
`BlocksView` paints a whole-reply document from `@terreno/blocks`. Pass the assistant text
|
|
474
523
|
as `document`. Leaf blocks in this slice are `heading`, `text`, `metric`, `badge`,
|
|
475
524
|
`divider`, and `context`. `columns` is a row from the `md` breakpoint and a stack on `sm`.
|
|
476
|
-
`card` groups children under an optional title. A `callout` draws `Banner` and is not dismissible. An `image` draws `Image` and requires `alt`; pass `imageHosts` to allow `https` hosts. A `details` block draws `Accordion`. A `chart` draws `LineChart`, `BarChart`,
|
|
525
|
+
`card` groups children under an optional title, with an optional small muted `eyebrow` above it (test ID `<card path>-eyebrow`). A `callout` draws `Banner` and is not dismissible. An `image` draws `Image` and requires `alt`; pass `imageHosts` to allow `https` hosts. A `details` block draws `Accordion`. A `chart` draws `LineChart`, `BarChart`,
|
|
477
526
|
`AreaChart`, or `DonutChart` from an inline dataset or from `points`. The series is only
|
|
478
527
|
`{label, value}` — a point color in the document is not passed through. A `table` draws
|
|
479
|
-
`DataTable
|
|
528
|
+
`DataTable` and maps each dataset column type to a cell: `number` to a right-aligned
|
|
529
|
+
`number` cell, `date` to a `date` cell (Luxon `DATE_MED`, such as "Mar 14, 2026"), and
|
|
530
|
+
`string` to `text`. A listed column that the dataset does not have yet is `text`. Each header
|
|
531
|
+
is the column name with underscores shown as spaces (`bone_in_lamb` reads "bone in lamb"). The table
|
|
532
|
+
measures its container with `onLayout` and splits the width evenly across its columns, with
|
|
533
|
+
at least 96 per column; past that it scrolls sideways. Before the first layout each column
|
|
534
|
+
is 120 wide. Its height fits the header plus its rows (54 each), up to 10 rows; a longer table
|
|
535
|
+
scrolls inside that height, so it never spills over the next block. A `ref` dataset stays empty until `resolveDataset` returns its rows, and the
|
|
480
536
|
chart shows `loading` while that promise is in flight. A rejected fetch clears that
|
|
481
537
|
loading state. A segmented control highlights the option whose `data` matches the target
|
|
482
538
|
chart or table. A `select` action switches a table the same way it switches a chart.
|
|
@@ -494,6 +550,83 @@ action also stores the chosen dataset on the target chart for this view. Pass
|
|
|
494
550
|
`hostActions` and every callback stays enabled. `pendingElementIds` shows those
|
|
495
551
|
buttons as loading. `overrides` replaces a block by id.
|
|
496
552
|
|
|
553
|
+
A `stepper` draws its `label` as a small muted line, then `IconButton` − and + around the
|
|
554
|
+
value and its `unit`, then `itemsTitle`, a two-column grid of item labels and amounts
|
|
555
|
+
(formatted with `decimals`, so `{amount: 2, unit: kg, decimals: 1}` reads "2.0 kg"), and the
|
|
556
|
+
muted `note`. − is disabled when `value - step` would fall below `min`, and + when `value + step`
|
|
557
|
+
would pass `max`, so with `max: 20` and `step: 2` + is disabled at 19. Both are disabled while either button's
|
|
558
|
+
element id is in `pendingElementIds`, and when `hostActions` is passed without the stepper's
|
|
559
|
+
`callback.name`. The buttons are labelled "Decrease <label>" and "Increase <label>". A tap
|
|
560
|
+
calls `onAction` with `blockId: <id>`, `elementId: <id>_decrease` or `<id>_increase`, and
|
|
561
|
+
`{kind: "callback", name, payload: {...payload, value: value ± step}}`. A host returns the
|
|
562
|
+
new stepper, and `overrides[<id>]` paints it.
|
|
563
|
+
|
|
564
|
+
A `checklist` draws its `title` on the left and an "n of m" counter on the right, then one
|
|
565
|
+
`CheckBox` row per item: the bold `text` first, then the small muted `meta` (such as a time),
|
|
566
|
+
then the muted `detail`. Each row is a pressable `Box` with the `checkbox` role, labelled with the item
|
|
567
|
+
text, that reports `accessibilityState` `{checked, disabled}` (`aria-checked` and
|
|
568
|
+
`aria-disabled` on web); Space toggles a focused row on web. A locked row keeps its role and
|
|
569
|
+
label and reports `disabled: true`. Test ids are stable across states: the item wrapper is
|
|
570
|
+
`<path>-<id>_<item id>`, the pressable row `<path>-<id>_<item id>-row-clickable`, its
|
|
571
|
+
`CheckBox` `<path>-<id>_<item id>-checkbox`, its lines `<path>-<id>_<item id>-text`, `-meta`,
|
|
572
|
+
and `-detail`, and the counter `<path>-counter`. A tick goes to the host when the checklist has a `callback`,
|
|
573
|
+
`onAction` is passed, and `hostActions` is omitted (as for the stepper) or lists
|
|
574
|
+
`callback.name`. Then a tap calls `onAction` with `blockId: <id>`,
|
|
575
|
+
`elementId: <id>_<item id>`, and `{kind: "callback", name, payload: {...payload, itemId,
|
|
576
|
+
checked, state}}`, where `state` maps every item id to its tick after this one. The tick is
|
|
577
|
+
not drawn ahead of the reply. `BlocksView` disables the checklist while a tick is pending:
|
|
578
|
+
while any of its `<id>_<item id>` element ids is in `pendingElementIds`, no row is pressable,
|
|
579
|
+
so a second tick cannot send a `state` that misses the first. The counter changes when
|
|
580
|
+
`overrides[<id>]` brings the returned checklist. Otherwise a tick stays in this `BlocksView` only and updates the counter at once.
|
|
581
|
+
When an override for the checklist arrives or changes, earlier local ticks on it are dropped
|
|
582
|
+
and the override's `checked` values show.
|
|
583
|
+
On the server, `toggleChecklistHostAction` from `@terreno/ai` handles the callback and returns
|
|
584
|
+
the stored checklist with the ticks from `state` (see [Host actions](ai.md#host-actions)). A host
|
|
585
|
+
that records progress registers its own `handles: "checklist"` action.
|
|
586
|
+
|
|
587
|
+
A `gallery` draws each image as a 4:3 `Image` tile (`fit` cover), with `alt` as the tile's
|
|
588
|
+
accessible label and the optional `caption` as small muted text under it. Tiles are 8 apart
|
|
589
|
+
(the `gap={2}` spacing step). The gallery measures its container with `onLayout`: up to three
|
|
590
|
+
images share one row at equal widths, and more wrap into a three-column grid whose last row
|
|
591
|
+
keeps the same tile width. When that width would fall under 160, every tile stays 160 wide in
|
|
592
|
+
one row that scrolls sideways. A measured width decides this rather than the `sm` breakpoint, so
|
|
593
|
+
a gallery inside a narrow `columns` child or card also scrolls. Before the first layout each tile
|
|
594
|
+
is 160 wide. A `file:` tile loads through `resolveImage` like an `image` block. Until it has a
|
|
595
|
+
URL, or when the lookup fails, the tile is a muted 4:3 placeholder that shows the alt text and
|
|
596
|
+
keeps it as the label. Test ids: the gallery `<path>`, each grid row `<path>-row-<n>`, the
|
|
597
|
+
scrolling row `<path>-scroll`, each tile `<path>-image-<index>`, the frame around a loaded
|
|
598
|
+
image `<path>-image-<index>-image`, its caption `<path>-image-<index>-caption`, and a
|
|
599
|
+
placeholder `<path>-image-<index>-placeholder`.
|
|
600
|
+
`Image` passes `alt` to the native image as its `accessibilityLabel`.
|
|
601
|
+
|
|
602
|
+
A `list` draws one row per item, 12 apart (`gap={3}`). The row starts with a 3:4 portrait
|
|
603
|
+
`Image` thumbnail (`fit` cover), always 112 wide and 149 tall, with `alt` as its accessible label.
|
|
604
|
+
Beside it, the optional `meta` sits small and muted above the bold `title`, and the optional
|
|
605
|
+
`text` follows in muted body text. `text` is plain text, not markdown. When any item in the list
|
|
606
|
+
has a thumbnail, an item without one keeps an empty 112-wide gutter so every title starts at the
|
|
607
|
+
same place; a list with no thumbnails draws text only, with no gutter. A `file:` thumbnail loads
|
|
608
|
+
through `resolveImage`, including in a list inside a card. Until it has a URL, or when the lookup
|
|
609
|
+
fails, it is a muted 112 by 149 placeholder that shows the alt text and keeps it as the label.
|
|
610
|
+
Test ids: the list `<path>`, each row `<path>-item-<index>`, its thumbnail
|
|
611
|
+
`<path>-item-<index>-image`, placeholder `<path>-item-<index>-placeholder`, gutter
|
|
612
|
+
`<path>-item-<index>-gutter`, text column `<path>-item-<index>-body`, and the
|
|
613
|
+
`<path>-item-<index>-meta`, `-title`, and `-text` lines.
|
|
614
|
+
|
|
615
|
+
A `copy` button writes the clipboard on the device and never calls `onAction`, so
|
|
616
|
+
`hostActions` does not disable it. A press builds the text with `blockPlainText` from
|
|
617
|
+
`@terreno/blocks`: a literal `text` as written, or the `target` block as it is shown now. That
|
|
618
|
+
means the `overrides` replacement (a stepper at 6 copies the scaled amounts), the checklist's
|
|
619
|
+
ticks made on the device, and a table's selected dataset, inline or a `ref` that
|
|
620
|
+
`resolveDataset` has returned. `BlocksView` writes it with `expo-clipboard` `setStringAsync`
|
|
621
|
+
(web and native), then shows "Copied" in small muted text beside the button for 2 seconds. A
|
|
622
|
+
failed write, or a target with no text yet (such as a `ref` table still loading), shows
|
|
623
|
+
"Couldn't copy" in error text for 2 seconds instead, logs `console.warn`, and does not throw.
|
|
624
|
+
The status sits in a `Box` with `accessibilityLiveRegion="polite"`, which stays mounted while
|
|
625
|
+
empty, so screen readers on web and Android announce the status when it appears (`Text` has
|
|
626
|
+
no live-region prop, and VoiceOver on iOS does not announce React Native live regions).
|
|
627
|
+
Test ids: the button `<path>-<element id>`, the live region `<path>-<element id>-status`, and
|
|
628
|
+
the status text `<path>-<element id>-status-text`.
|
|
629
|
+
|
|
497
630
|
```tsx
|
|
498
631
|
<BlocksView
|
|
499
632
|
document={reply}
|
|
@@ -505,6 +638,23 @@ buttons as loading. `overrides` replaces a block by id.
|
|
|
505
638
|
|
|
506
639
|
Demo story: `BlocksView`.
|
|
507
640
|
|
|
641
|
+
### MarkdownView
|
|
642
|
+
|
|
643
|
+
`MarkdownView` renders markdown with `react-native-markdown-display` and the current theme.
|
|
644
|
+
GPTChat and the `text` block render through it.
|
|
645
|
+
|
|
646
|
+
GFM tables use theme colors, never fixed hex values:
|
|
647
|
+
|
|
648
|
+
- Cell borders draw a grid in `theme.border.default`.
|
|
649
|
+
- Header cells sit on `theme.surface.secondaryLight` and use the bold text font (`text-bold`).
|
|
650
|
+
Body cells use `text-regular`.
|
|
651
|
+
- Every column gets an equal share of the width, at least 96. A narrow table fills its
|
|
652
|
+
container. A table wider than its container scrolls sideways in a horizontal `ScrollView`.
|
|
653
|
+
|
|
654
|
+
Test ids: `markdown-table-scroll` (the scroll view), `markdown-table`,
|
|
655
|
+
`markdown-table-header-<column>`, and `markdown-table-cell-<row>-<column>` (body rows count
|
|
656
|
+
from 0).
|
|
657
|
+
|
|
508
658
|
### HtmlFrame
|
|
509
659
|
|
|
510
660
|
`HtmlFrame` paints one `html` block. On web it is an `<iframe sandbox="" referrerpolicy="no-referrer">` whose `srcdoc` starts with a Content-Security-Policy meta tag (`default-src 'none'`). On native it is a WebView with JavaScript off, and navigation after the first load is rejected. `BlocksView` renders that frame only when `allowHtml` is true and `streaming` is false. Otherwise the block is a card that says the preview is off or still arriving. `GPTChat` passes `allowHtml` through.
|
|
@@ -725,12 +875,21 @@ Master-detail layout. Pass `listViewData` plus `renderListViewItem` for the list
|
|
|
725
875
|
screens the detail replaces the list until the user goes back.
|
|
726
876
|
|
|
727
877
|
`desktopChildrenMinWidth` opts into a minimum pixel width for each desktop child when there
|
|
728
|
-
are one or two children. `childColumnRounding` sets the border radius on each detail
|
|
729
|
-
|
|
878
|
+
are one or two children. `childColumnRounding` sets the border radius on each desktop detail
|
|
879
|
+
pane (defaults to `md`, 4px); the list column is excluded. The pane clips to that radius. The
|
|
880
|
+
column inside it scrolls: content taller than the pane scrolls in the column, and a child with
|
|
881
|
+
`height: "100%"` fills the visible pane so a scroll view inside that child scrolls on its own.
|
|
882
|
+
On a narrow web viewport the pager page is height-bounded to that same visible pane. Its rounded
|
|
883
|
+
corners still clip, and a `height: "100%"` child scrolls inside the page. On the labeled pager,
|
|
884
|
+
previous and next sit in fixed left and right slots in a row under the column. A missing direction
|
|
885
|
+
leaves that slot empty, so the other arrow stays put. The column height is the measured pane minus
|
|
886
|
+
top spacing, the navigation row, bottom spacing, and `bottomNavBarHeight`.
|
|
887
|
+
`narrowViewportChildLabels`
|
|
730
888
|
opts into a labeled full-width pager on the narrow viewport. `narrowBelowWidth` uses that
|
|
731
889
|
viewport when the window is at or below the given pixel width; when omitted, the narrow
|
|
732
890
|
viewport follows `isNarrowViewport()`. These props are web only; the native `SplitPage`
|
|
733
|
-
ignores them. See `SplitPageProps` for when each prop applies and what is ignored.
|
|
891
|
+
ignores them. See `SplitPageProps` for when each prop applies and what is ignored. An unset
|
|
892
|
+
`color` is `baseAlternate` on web and native.
|
|
734
893
|
`IconButton`'s `backgroundOpacity` tints only that button's background.
|
|
735
894
|
|
|
736
895
|
```typescript
|
|
@@ -764,12 +923,25 @@ Buttons use a scale animation by default. Set `pressAnimation="opacity"` for an
|
|
|
764
923
|
|
|
765
924
|
Disabled and loading buttons use a non-interactive pressable regardless of the selected animation.
|
|
766
925
|
|
|
926
|
+
### Tooltip
|
|
927
|
+
|
|
928
|
+
`Tooltip` (and `tooltipText` on `Button` / `IconButton`) renders its bubble outside the trigger's clipping and stacking context:
|
|
929
|
+
|
|
930
|
+
| Platform | Mount point | Positioning |
|
|
931
|
+
| --- | --- | --- |
|
|
932
|
+
| Web | `document.body` | `position: fixed`, `z-index: 9999`, so it shows above an open `Modal` and nothing else covers the page |
|
|
933
|
+
| iOS / Android | `TerrenoProvider` portal host | `position: absolute`, `z-index: 999` |
|
|
934
|
+
|
|
935
|
+
The bubble stays off screen at `opacity: 0` until the trigger is measured, then `getTooltipPosition` places it at `idealPosition` or the first side that fits on screen.
|
|
936
|
+
|
|
767
937
|
### Toast
|
|
768
938
|
|
|
769
939
|
`TerrenoProvider` mounts the toast container. Call `useToast()` for `success`, `info`, `warn`, `error`, `show`, `hide`, and `catch`.
|
|
770
940
|
|
|
771
941
|
On web, that container is portaled to `document.body` (`position: fixed`, `z-index: 999999`, `pointerEvents: "box-none"`), so a toast stays above an open `Modal` and its backdrop. The web container is sized with `100%` rather than the measured window, so statically exported pages (where `Dimensions` reports 0×0 at build time) still center the toast on screen. Native iOS and Android keep the in-tree absolute container sized to the window.
|
|
772
942
|
|
|
943
|
+
Only the visible toast takes clicks. Every full-width layer around it (the container, its safe area, and the `Toast` layout wrapper) uses `pointerEvents: "box-none"`, so a button at the same height as an open toast stays clickable. Declare that style through `StyleSheet.create`; react-native-web drops `pointerEvents` from inline styles.
|
|
944
|
+
|
|
773
945
|
## Authentication Components
|
|
774
946
|
|
|
775
947
|
### SocialLoginButton
|
|
@@ -1106,6 +1278,32 @@ Responsive `Box` direction props update automatically when the window resizes or
|
|
|
1106
1278
|
All responsive Boxes share one dimension listener; non-responsive Boxes do not subscribe.
|
|
1107
1279
|
When multiple direction props match, the largest active breakpoint wins (`xl` over `lg` over `md` over `sm`).
|
|
1108
1280
|
|
|
1281
|
+
`Box` takes an optional `accessibilityState` of `{checked?, disabled?}`. It is sent to screen
|
|
1282
|
+
readers as `accessibilityState` and as `aria-checked` / `aria-disabled`, on both a plain and a
|
|
1283
|
+
clickable Box. On a clickable Box, `disabled: true` also stops presses while the role and
|
|
1284
|
+
label stay. A clickable Box with `accessibilityRole` `checkbox` or `switch` also toggles on
|
|
1285
|
+
Space on web, as ARIA expects.
|
|
1286
|
+
|
|
1287
|
+
```tsx
|
|
1288
|
+
<Box
|
|
1289
|
+
accessibilityHint="Marks this item as done"
|
|
1290
|
+
accessibilityLabel="Preheat the oven"
|
|
1291
|
+
accessibilityRole="checkbox"
|
|
1292
|
+
accessibilityState={{checked: false, disabled: isSaving}}
|
|
1293
|
+
onClick={toggle}
|
|
1294
|
+
/>
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
`Box` also takes `accessibilityLiveRegion` (`polite` or `assertive`) on a Box without
|
|
1298
|
+
`onClick`. It is sent as `aria-live`, so screen readers announce changes to the Box's contents
|
|
1299
|
+
on web and Android. VoiceOver on iOS does not announce React Native live regions.
|
|
1300
|
+
|
|
1301
|
+
```tsx
|
|
1302
|
+
<Box accessibilityLiveRegion="polite" testID="copy-status">
|
|
1303
|
+
{status ? <Text size="sm">{status}</Text> : null}
|
|
1304
|
+
</Box>
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1109
1307
|
## Icons
|
|
1110
1308
|
|
|
1111
1309
|
Terreno uses **FontAwesome 6** by default. Pass icon names via `iconName` on `Icon`, `Button`, `IconButton`, form fields, `Badge`, and other icon-aware components.
|
|
@@ -1225,8 +1423,8 @@ const buttonStyles = toggle(isPressed, pressedStyles, defaultStyles);
|
|
|
1225
1423
|
|
|
1226
1424
|
@terreno/ui components do not require environment variables. All configuration is done at runtime via:
|
|
1227
1425
|
|
|
1228
|
-
- **TerrenoProvider props** —
|
|
1229
|
-
- **Theme hooks** — `useTheme()`, `setTheme()`, `setPrimitives()`
|
|
1426
|
+
- **TerrenoProvider props** — `colorScheme`, `initialPrimitives`, custom icons, and OpenAPI spec URL
|
|
1427
|
+
- **Theme hooks** — `useTheme()`, `setColorScheme()`, `setTheme()`, `setPrimitives()`
|
|
1230
1428
|
- **Component props** — Direct prop overrides for individual components
|
|
1231
1429
|
|
|
1232
1430
|
**Example configuration:**
|
|
@@ -1235,16 +1433,28 @@ const buttonStyles = toggle(isPressed, pressedStyles, defaultStyles);
|
|
|
1235
1433
|
import {TerrenoProvider} from "@terreno/ui";
|
|
1236
1434
|
|
|
1237
1435
|
<TerrenoProvider
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
surface: {primary: "secondary500"},
|
|
1241
|
-
}}
|
|
1242
|
-
onError={(error) => console.error(error)}
|
|
1436
|
+
colorScheme="system"
|
|
1437
|
+
openAPISpecUrl="https://api.example.com/openapi.json"
|
|
1243
1438
|
>
|
|
1244
1439
|
{children}
|
|
1245
1440
|
</TerrenoProvider>
|
|
1246
1441
|
``````
|
|
1247
1442
|
|
|
1443
|
+
## DataTable column types
|
|
1444
|
+
|
|
1445
|
+
`columnType` picks the cell a column draws, unless `customColumnComponentMap` has an entry
|
|
1446
|
+
for that type:
|
|
1447
|
+
|
|
1448
|
+
| `columnType` | Cell |
|
|
1449
|
+
| --- | --- |
|
|
1450
|
+
| `text` (and any unmapped type) | The value as a string |
|
|
1451
|
+
| `number` | The value as a string, right-aligned, with the header right-aligned to match. A number or a numeric string both work |
|
|
1452
|
+
| `date` | An ISO string or a `Date`, formatted with Luxon `DATE_MED` ("Mar 14, 2026") in the local zone. A value Luxon cannot parse is shown as written, and an empty value is blank |
|
|
1453
|
+
| `boolean` | A check or a cross icon |
|
|
1454
|
+
|
|
1455
|
+
A value that is already formatted, such as "Oct 8, 2026, 3:00 PM", is not ISO, so a `date`
|
|
1456
|
+
column shows it unchanged.
|
|
1457
|
+
|
|
1248
1458
|
## DataTable server-side filtering
|
|
1249
1459
|
|
|
1250
1460
|
`DataTable` is data-layer agnostic. Optional filter and search props emit a
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Task List: Agent UI Rich Replies
|
|
2
|
+
|
|
3
|
+
**Status:** approved 2026-10-07. Start with T1 (or T19, which is independent).
|
|
4
|
+
**IP:** [`docs/implementationPlans/agent-ui-rich-replies.md`](../implementationPlans/agent-ui-rich-replies.md). The contracts, acceptance criteria (AC1–AC14, including AC6b), and open questions are there.
|
|
5
|
+
**Supporting skills:** `terreno-ui`, `terreno-backend-api`, `ai-prompt-governance`, `update-docs`, `verify-ui-changes`, `backend-test-env`.
|
|
6
|
+
|
|
7
|
+
Two write chains share files, so their tasks are ordered.
|
|
8
|
+
|
|
9
|
+
- **Contract chain:** T1 → T2 → T5 → T6 → T7 → T8 → T9. These write `blocks/src/schema.ts`, `limits.ts`, `lint.ts`, `prompt.ts`, fixtures, and `docs/reference/blocks.md`.
|
|
10
|
+
- **Renderer chain:** T1 → T3 → T10 → T11 → T12 → T13 → T14. These write `ui/src/blocks/blockRenderers.tsx`, `BlocksView.test.tsx`, `demo/stories/BlocksView.stories.tsx`, and the BlocksView section of `docs/reference/ui.md`.
|
|
11
|
+
|
|
12
|
+
Each renderer task also waits for its own contract task. T4 sits in the renderer chain between T3 and T10 because it also edits `docs/reference/ui.md`. Every contract task that adds fixtures also edits `blocks/src/parse.test.ts` (`expectedInvalid`); invalid cases that need validate options go in `blocks/src/lint.test.ts`. Fixtures under `valid/` use `data:image` or `file:` image sources.
|
|
13
|
+
|
|
14
|
+
### Phase 1: Tracer
|
|
15
|
+
|
|
16
|
+
- [x] **T1** — Stepper block round trips through a host callback
|
|
17
|
+
- Depends on: none
|
|
18
|
+
- Files: `blocks/src/schema.ts`, `blocks/src/limits.ts`, `blocks/src/errors.ts` (`OUT_OF_RANGE`), `blocks/src/lint.ts` (stepper `callback.name` in `UNKNOWN_HOST_ACTION`; reserved `<id>_increase` / `<id>_decrease` in `DUPLICATE_ID`), `blocks/src/fixtures/valid/stepper*.yaml`, `blocks/src/fixtures/invalid/stepper*.yaml`, `blocks/src/parse.test.ts`, `blocks/src/lint.test.ts`, `blocks/src/index.ts`; `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/BlocksView.test.tsx`; `ai/src/service/agentBlocks.ts` (`findAgentBlock`), `ai/src/service/agentBlocks.test.ts`, `ai/src/service/scaleStepper.ts`, `ai/src/service/scaleStepper.test.ts`, `ai/src/types/index.ts` (`handles?`, `logResponse?` on host actions), `ai/src/routes/gptActions.ts` (honour `logResponse: false`), `ai/src/routes/gptActions.test.ts`, `ai/src/index.ts`; `docs/reference/blocks.md`, `docs/reference/ai.md`, `docs/reference/ui.md`.
|
|
19
|
+
- Delivers:
|
|
20
|
+
- The `stepper` contract from the IP.
|
|
21
|
+
- The renderer: − value +, unit, `itemsTitle`, an items grid formatted with `decimals`, and the note. It emits the callback with `{...payload, value}`. Buttons are disabled at the bounds, while pending, and when the callback is not in `hostActions`.
|
|
22
|
+
- `scaleStepperHostAction`: payload `z.object({value: z.number()}).passthrough()`. It reads the agent's stepper through `findAgentBlock`: `history.prompts[n]` (`msg-<n>`), or the only prompt holding that stepper id (409 when there are several, 404 when none). It checks `value` and returns `{replace: "block", blocks: {v: 1, blocks: [stepper]}}`.
|
|
23
|
+
- The `logResponse` host-action option. With it off, the log keeps the ids-only prompt plus `value`, with no response.
|
|
24
|
+
- Acceptance: AC4; AC5; the stepper rows of AC1 and AC2. `bun test blocks/`, `cd ui && bun test src/blocks`, and `cd ai && bun test --preload ./src/tests/bunSetup.ts src/service/scaleStepper.test.ts src/routes/gptActions.test.ts` are green.
|
|
25
|
+
|
|
26
|
+
- [x] **T2** — Prompt offers the new blocks by default, with an opt-out
|
|
27
|
+
- Depends on: T1
|
|
28
|
+
- Files: `blocks/src/prompt.ts`, `blocks/src/prompt.test.ts`, `blocks/src/validate.ts` (`stepperActions`), `blocks/src/lint.ts`; `ai/src/types/index.ts` (`uiBlocks.richBlocks`), `ai/src/service/prompts.ts`, `ai/src/service/chatTurn.ts` (derive `stepperActions` and `checklistActions` from the host actions' `handles`; pass `richBlocks`), `ai/src/routes/gpt.test.ts`; `docs/reference/blocks.md`, `docs/reference/ai.md`.
|
|
29
|
+
- Delivers: the default-on table in the IP. It adds the `richBlocks` plumbing (default `true` in both `uiBlocks` and `blocksPromptSection`), the stepper gate (a `handles: "stepper"` action), and the `checklistActions` list that T5 uses. With `richBlocks: false`, the prompt text is byte-identical to `master`, even with a stepper action registered. Later contract tasks add their prompt line under `richBlocks`.
|
|
30
|
+
- Acceptance: the AC3 `false` case, the default-on check, and the stepper line; a chat-turn test where a stepper naming a non-stepper action fails `UNKNOWN_HOST_ACTION`. The full on-case snapshot is T9's.
|
|
31
|
+
|
|
32
|
+
### Phase 2: Tables
|
|
33
|
+
|
|
34
|
+
- [x] **T3** — Table block renders typed columns
|
|
35
|
+
- Depends on: T1
|
|
36
|
+
- Files: `ui/src/DataTable.tsx` (number and date cells), `ui/src/DataTable.test.tsx`, `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/BlocksView.test.tsx`, `docs/reference/ui.md` (DataTable and BlocksView sections).
|
|
37
|
+
- Delivers: right-aligned `number` cells and Luxon `DATE_MED` `date` cells in `DataTable`. The `table` block maps the dataset column types to these cells, measures its width with `onLayout`, and splits the width evenly, at least 96 per column, scrolling sideways past that.
|
|
38
|
+
- Acceptance: AC9 (table half); the new tests fail on `master`.
|
|
39
|
+
|
|
40
|
+
- [x] **T4** — Markdown tables in `text` get theme styling
|
|
41
|
+
- Depends on: T3 (shares `docs/reference/ui.md`; T10 follows T4 for the same reason)
|
|
42
|
+
- Files: `ui/src/MarkdownView.tsx`, `ui/src/MarkdownView.test.tsx`, `docs/reference/ui.md` (MarkdownView section only).
|
|
43
|
+
- Delivers: `border.default` cell borders, a bold header on `surface.secondaryLight`, and horizontal scroll when the table is wide.
|
|
44
|
+
- Acceptance: AC9 (markdown half); the test fails on `master`.
|
|
45
|
+
|
|
46
|
+
### Phase 3: Contracts
|
|
47
|
+
|
|
48
|
+
- [x] **T5** — `checklist` contract
|
|
49
|
+
- Depends on: T2
|
|
50
|
+
- Files: `blocks/src/schema.ts`, `blocks/src/limits.ts`, `blocks/src/lint.ts` (item ids; reserved `<id>_<item id>` element ids; checklist `callback.name` against `checklistActions`), `blocks/src/prompt.ts`, `blocks/src/fixtures/{valid,invalid}/checklist*.yaml`, `blocks/src/parse.test.ts`, `docs/reference/blocks.md`.
|
|
51
|
+
- Delivers: the `checklist` schema from the IP, including the optional `callback`. Its prompt line sits under `richBlocks` and names the checklist action when there is one.
|
|
52
|
+
- Acceptance: the checklist rows of AC1 and AC3; `bun test blocks/`.
|
|
53
|
+
|
|
54
|
+
- [x] **T6** — `gallery` contract
|
|
55
|
+
- Depends on: T5
|
|
56
|
+
- Files: `blocks/src/schema.ts`, `blocks/src/limits.ts`, `blocks/src/lint.ts` (image hosts per tile), `blocks/src/prompt.ts`, `blocks/src/fixtures/{valid,invalid}/gallery*.yaml`, `blocks/src/parse.test.ts`, `docs/reference/blocks.md`.
|
|
57
|
+
- Delivers: the `gallery` schema. `IMAGE_HOST_NOT_ALLOWED` names the tile path (`blocks[0].images[2].src`).
|
|
58
|
+
- Acceptance: the gallery rows of AC1 and AC3; an invalid fixture with a disallowed host.
|
|
59
|
+
|
|
60
|
+
- [x] **T7** — `list` contract
|
|
61
|
+
- Depends on: T6
|
|
62
|
+
- Files: `blocks/src/schema.ts`, `blocks/src/limits.ts`, `blocks/src/lint.ts`, `blocks/src/prompt.ts`, `blocks/src/fixtures/{valid,invalid}/list*.yaml`, `blocks/src/parse.test.ts`, `docs/reference/blocks.md`.
|
|
63
|
+
- Delivers: the `list` schema. The optional image on each item goes through image-host lint.
|
|
64
|
+
- Acceptance: the list rows of AC1 and AC3.
|
|
65
|
+
|
|
66
|
+
- [x] **T8** — `card.eyebrow` contract
|
|
67
|
+
- Depends on: T7
|
|
68
|
+
- Files: `blocks/src/schema.ts`, `blocks/src/limits.ts`, `blocks/src/prompt.ts`, `blocks/src/fixtures/{valid,invalid}/card-eyebrow*.yaml`, `blocks/src/parse.test.ts`, `docs/reference/blocks.md`.
|
|
69
|
+
- Delivers: an optional `eyebrow` (1–60) on `card`.
|
|
70
|
+
- Acceptance: the eyebrow rows of AC1 and AC3.
|
|
71
|
+
|
|
72
|
+
- [x] **T9** — `copy` action contract and `blockPlainText`
|
|
73
|
+
- Depends on: T8
|
|
74
|
+
- Files: `blocks/src/schema.ts`, `blocks/src/errors.ts` (`COPY_TARGET_INVALID`), `blocks/src/lint.ts` (exactly one of `text` / `target`, as `open` does), `blocks/src/prompt.ts`, `blocks/src/plainText.ts`, `blocks/src/plainText.test.ts`, `blocks/src/index.ts`, `blocks/src/fixtures/{valid,invalid}/copy*.yaml`, `blocks/src/parse.test.ts`, `docs/reference/blocks.md`.
|
|
75
|
+
- Delivers: the `copy` action kind, target lint, and `blockPlainText` as the IP table describes it.
|
|
76
|
+
- Acceptance: the copy rows of AC1, AC2, and AC3; the full AC3 `richBlocks`-on snapshot. `plainText.test.ts` covers every row of the IP table.
|
|
77
|
+
|
|
78
|
+
### Phase 4: Renderers
|
|
79
|
+
|
|
80
|
+
- [x] **T10** — Checklist renders and toggles
|
|
81
|
+
- Depends on: T3, T4, T5
|
|
82
|
+
- Files: `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/useChecklistState.ts`, `ui/src/blocks/BlocksView.tsx`, `ui/src/blocks/BlocksView.test.tsx`, `demo/stories/BlocksView.stories.tsx`, `docs/reference/ui.md`.
|
|
83
|
+
- Delivers: a `CheckBox` row per item (meta, bold text, detail) and the "n of m" counter. With a callback in `hostActions`, a tick emits `{...payload, itemId, checked, state}`, disables the whole checklist while a tick is pending (so a second tick cannot send a stale `state`), and shows the returned block. Without one, ticks are local for each rendered document.
|
|
84
|
+
- Acceptance: AC6.
|
|
85
|
+
|
|
86
|
+
- [x] **T11** — Gallery renders
|
|
87
|
+
- Depends on: T6, T10
|
|
88
|
+
- Files: `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/BlocksView.test.tsx`, `demo/stories/BlocksView.stories.tsx`, `docs/reference/ui.md`.
|
|
89
|
+
- Delivers: one row of 4:3 `Image` tiles up to 3, a 3-column grid above that, captions, `file:` ids through `useResolvedImages`, and sideways scroll on narrow screens.
|
|
90
|
+
- Acceptance: AC7 (gallery).
|
|
91
|
+
|
|
92
|
+
- [x] **T12** — List renders
|
|
93
|
+
- Depends on: T7, T11
|
|
94
|
+
- Files: `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/BlocksView.test.tsx`, `demo/stories/BlocksView.stories.tsx`, `docs/reference/ui.md`.
|
|
95
|
+
- Delivers: for each item, a 3:4 thumbnail on the left, a bold title, muted text, and the meta.
|
|
96
|
+
- Acceptance: AC7 (list).
|
|
97
|
+
|
|
98
|
+
- [x] **T13** — Card eyebrow renders
|
|
99
|
+
- Depends on: T8, T12
|
|
100
|
+
- Files: `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/BlocksView.test.tsx`, `docs/reference/ui.md`.
|
|
101
|
+
- Delivers: small, muted eyebrow text above the card title.
|
|
102
|
+
- Acceptance: AC7 (eyebrow).
|
|
103
|
+
|
|
104
|
+
- [x] **T14** — Copy action writes the clipboard
|
|
105
|
+
- Depends on: T9, T13
|
|
106
|
+
- Files: `ui/src/blocks/BlocksView.tsx`, `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/BlocksView.test.tsx`, `docs/reference/ui.md`.
|
|
107
|
+
- Delivers: a copy button builds its text with `blockPlainText` from the current block (override and toggles applied), writes it with `expo-clipboard`, and shows "Copied" in a polite live region for 2 s. It never calls `onAction`.
|
|
108
|
+
- Acceptance: AC8.
|
|
109
|
+
|
|
110
|
+
### Phase 5: The roast reply end to end
|
|
111
|
+
|
|
112
|
+
- [x] **T15** — Sunday roast golden document and playground story
|
|
113
|
+
- Depends on: T14
|
|
114
|
+
- Files: `blocks/src/fixtures/golden/sunday-roast.yaml`, `blocks/src/golden.test.ts` (validate with `hostActions: ["scaleStepper"]`, `stepperActions: ["scaleStepper"]`, `imageHosts: ["images.example.com"]`), `demo/stories/BlocksPlayground.stories.tsx` (a roast preset).
|
|
115
|
+
- Delivers: the GPT-6 demo reply as one document: the title; the gallery; the summary card with an eyebrow; the menu list; the stepper and copy card; the checklist with its first item checked; the USDA note as `context`; the tips as `text`; and follow-up `reply` buttons. It also includes the GPT-5.6 "How much lamb?" table as a `table` block. Images use `https://images.example.com/...` and validate only with `imageHosts`, so the file lives under `golden/`, not `valid/`.
|
|
116
|
+
- Acceptance: AC10; a playground screenshot under `/opt/cursor/artifacts/`.
|
|
117
|
+
|
|
118
|
+
- [x] **T16** — Example app runs the roast reply against the real callback route
|
|
119
|
+
- Depends on: T15, T20, T21
|
|
120
|
+
- Files:
|
|
121
|
+
- `example-backend/src/ai/hostActions.ts`: register `scaleStepper: scaleStepperHostAction` and `toggleChecklist: toggleChecklistHostAction` (`richBlocks` is on by default).
|
|
122
|
+
- `example-frontend/app/(tabs)/ai.tsx`: add `scaleStepper` and `toggleChecklist` to `hostActions`.
|
|
123
|
+
- `example-frontend/e2e/helpers/seedGptHistory.ts` (new): `POST /gpt/histories` as the logged-in user, with the roast document as the assistant prompt.
|
|
124
|
+
- `example-frontend/e2e/helpers/mockGpt.ts`: the mocked stream returns the seeded `historyId`, and `/gpt/actions` is no longer mocked for this spec.
|
|
125
|
+
- `example-frontend/e2e/ai-chat.spec.ts`.
|
|
126
|
+
- `docs/explanation/example-coverage.md`.
|
|
127
|
+
- Delivers: the seeded roast document uses `file:` ids from a test `PhotoLibraryEntry`. Playwright presses + (5 → 6, the quantities change) and ticks a checklist item, both through the real `POST /gpt/actions`, then presses copy. Only the signed-URL image download is stubbed.
|
|
128
|
+
- Acceptance: AC11.
|
|
129
|
+
|
|
130
|
+
- [x] **T17** — Model smoke: a real model writes the roast reply
|
|
131
|
+
- Depends on: T15, T20
|
|
132
|
+
- Files: `example-backend/src/scripts/blocksSmoke.ts`, `example-backend/package.json` and root `package.json` (`blocks:smoke` script).
|
|
133
|
+
- Delivers: a script that runs the post's prompt through `AIService` with the opted-in blocks prompt and checks the reply. It skips cleanly when no model key is set.
|
|
134
|
+
- Acceptance: AC12.
|
|
135
|
+
|
|
136
|
+
- [x] **T18** — Docs sweep, changelog, rules
|
|
137
|
+
- Depends on: T15, T20, T21
|
|
138
|
+
- Files:
|
|
139
|
+
- `docs/explanation/agent-ui-blocks.md`: the catalog list, adding the missing `callout`, `image`, `details`, and `html`; why the stepper is a callback; the default-on rollout and the `richBlocks: false` opt-out for shipped native builds.
|
|
140
|
+
- `docs/how-to/agent-ui-blocks.md`: a section "Add a stepper callback", including the warning that stored history prompts are owner-writable, so a handler whose numbers matter must use its own data.
|
|
141
|
+
- `changelog/unreleased/agent-ui-rich-replies.md`, with a "Behaviour change" note: hosts with `uiBlocks` get the new blocks in the prompt by default; set `uiBlocks.richBlocks: false` to keep the old catalog.
|
|
142
|
+
- `.rulesync/rules/ui/00-ui.md` and `.rulesync/rules/ai/00-ai.md`, then `bun run rules`.
|
|
143
|
+
- Delivers: docs a stranger can follow to build the roast reply without reading the PR.
|
|
144
|
+
- Acceptance: AC13.
|
|
145
|
+
|
|
146
|
+
- [x] **T19** — Generated photo library: generate images once and store them
|
|
147
|
+
- Depends on: none
|
|
148
|
+
- Files: `example-backend/src/models/photoLibraryEntry.ts`, `example-backend/src/types/models/photoLibraryEntryTypes.ts`, `example-backend/src/scripts/photoPrompts.ts`, `example-backend/src/scripts/generatePhotoLibrary.ts`, `example-backend/src/scripts/generatePhotoLibrary.test.ts`, `knip.jsonc` (script entry), `example-backend/package.json` (`photos:generate`).
|
|
149
|
+
- Delivers: the `PhotoLibraryEntry` model and the `photos:generate` script from the IP. It runs AI SDK `generateImage` with the Vertex image model, then `FileStorageService.upload`, then upserts by `prompt`. It validates the required env up front, skips existing prompts, and takes `--force`. Supporting skill: `mongoose-schema-safety`.
|
|
150
|
+
- Acceptance: AC14 (generation half), using a fake image model and fake storage.
|
|
151
|
+
|
|
152
|
+
- [x] **T20** — `findPhotos` tool and photo URLs in the example app
|
|
153
|
+
- Depends on: T19
|
|
154
|
+
- Files: `example-backend/src/ai/tools.ts` (`findPhotos`), `example-backend/src/ai/tools.test.ts`, `example-backend/src/api/photoLibrary.ts` (`modelRouter` with instance action `url`), `example-backend/src/api/photoLibrary.test.ts`, `example-backend/src/server.ts` (register it), `example-frontend/store/openApiSdk.ts` (regenerated with `bun run sdk`), `example-frontend/app/(tabs)/ai.tsx` (`resolveImage`), `docs/how-to/agent-ui-blocks.md` ("Give the agent photos"; T18 follows it).
|
|
155
|
+
- Delivers: `findPhotos({query, count})` returns `{src: "file:<id>", alt}` from the library. `GET /photoLibrary/:id/url` returns a signed URL to any authenticated user. The example chat resolves `file:` ids through the generated hook. Supporting skills: `model-router-actions`, `generate-sdk`.
|
|
156
|
+
- Acceptance: AC14 (tool and route half).
|
|
157
|
+
|
|
158
|
+
- [x] **T21** — Checklist ticks round trip through a host callback
|
|
159
|
+
- Depends on: T10
|
|
160
|
+
- Files: `ai/src/service/toggleChecklist.ts`, `ai/src/service/toggleChecklist.test.ts`, `ai/src/routes/gptActions.test.ts`, `ai/src/index.ts`, `docs/reference/ai.md`.
|
|
161
|
+
- Delivers: `toggleChecklistHostAction` from the IP, built on `findAgentBlock`.
|
|
162
|
+
- Acceptance: AC6b.
|
|
163
|
+
|
|
164
|
+
### Phase 6: Playground follow-up (D7)
|
|
165
|
+
|
|
166
|
+
- [x] **T22** — Every block type has a playground preset
|
|
167
|
+
- Depends on: T15
|
|
168
|
+
- Files: `demo/stories/BlocksPlayground.stories.tsx`, `demo/story-config/BlocksPlayground.config.tsx`, a demo test that every schema block type appears in a preset and every preset validates.
|
|
169
|
+
- Delivers: one preset per block type plus "All blocks", covering every action kind and the segmented control.
|
|
170
|
+
- Acceptance: the coverage test fails when a block type has no preset; every preset validates; runtime screenshots at 1280 and 390.
|
|
171
|
+
|
|
172
|
+
- [x] **T23** — Stepper and checklist callbacks run in the playground
|
|
173
|
+
- Depends on: T22
|
|
174
|
+
- Files: `blocks/src/hostActionLogic.ts` (or similar) + tests + `blocks/src/index.ts`, `ai/src/service/scaleStepper.ts`, `ai/src/service/toggleChecklist.ts`, `demo/stories/BlocksPlayground.stories.tsx`, `docs/reference/blocks.md`.
|
|
175
|
+
- Delivers: pure `scaleStepperBlock` and `applyChecklistState` in `@terreno/blocks`, used by both host actions and the playground; other actions show a toast.
|
|
176
|
+
- Acceptance: in the playground, + shows 6 and "2.4 kg", a tick shows "2 of 8", a reply button shows a toast; ai host-action tests unchanged and green.
|
|
177
|
+
|
|
178
|
+
- [x] **T24** — Checklist rows read bold title, then time, then description
|
|
179
|
+
- Depends on: none
|
|
180
|
+
- Files: `ui/src/blocks/blockRenderers.tsx`, `ui/src/blocks/BlocksView.test.tsx`, `docs/reference/ui.md`.
|
|
181
|
+
- Delivers: the row order from D7, test IDs unchanged.
|
|
182
|
+
- Acceptance: a test asserts the order; the e2e roast spec still passes.
|
|
183
|
+
|
|
184
|
+
- [x] **T25** — Demo blocks use real photos
|
|
185
|
+
- Depends on: T22
|
|
186
|
+
- Files: `demo/assets/blocks/*.jpg`, `demo/assets/blocks/ATTRIBUTION.md`, `demo/stories/BlocksView.stories.tsx`, `demo/stories/BlocksPlayground.stories.tsx`.
|
|
187
|
+
- Delivers: gallery, list, and image presets and stories show bundled public-domain photos through `file:` ids and `resolveImage`.
|
|
188
|
+
- Acceptance: in the browser every photo loads (`naturalWidth` 800); attribution lists each source and license.
|