@terreno/mcp 57.12.0 → 57.14.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.
Files changed (49) hide show
  1. package/dist/docs/upgrades/57.13.0.md +75 -0
  2. package/dist/docs/upgrades/57.14.0.md +43 -0
  3. package/dist/docs/versioned/next/explanation/README.md +1 -1
  4. package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
  5. package/dist/docs/versioned/next/explanation/example-coverage.md +3 -3
  6. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
  7. package/dist/docs/versioned/next/how-to/README.md +3 -0
  8. package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
  9. package/dist/docs/versioned/next/how-to/circleci.md +2 -0
  10. package/dist/docs/versioned/next/how-to/preview-demo.md +5 -5
  11. package/dist/docs/versioned/next/how-to/rate-limiting.md +17 -0
  12. package/dist/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
  13. package/dist/docs/versioned/next/how-to/use-syncdb-correctly.md +111 -0
  14. package/dist/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
  15. package/dist/docs/versioned/next/reference/README.md +1 -1
  16. package/dist/docs/versioned/next/reference/admin-backend.md +26 -0
  17. package/dist/docs/versioned/next/reference/admin-frontend.md +15 -0
  18. package/dist/docs/versioned/next/reference/ai.md +55 -3
  19. package/dist/docs/versioned/next/reference/api.md +4 -0
  20. package/dist/docs/versioned/next/reference/blocks.md +243 -11
  21. package/dist/docs/versioned/next/reference/environment-variables.md +3 -0
  22. package/dist/docs/versioned/next/reference/syncdb.md +1 -0
  23. package/dist/docs/versioned/next/reference/ui.md +212 -10
  24. package/dist/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
  25. package/package.json +3 -3
  26. package/src/docs/upgrades/57.13.0.md +75 -0
  27. package/src/docs/upgrades/57.14.0.md +43 -0
  28. package/src/docs/versioned/next/explanation/README.md +1 -1
  29. package/src/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
  30. package/src/docs/versioned/next/explanation/example-coverage.md +3 -3
  31. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
  32. package/src/docs/versioned/next/how-to/README.md +3 -0
  33. package/src/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
  34. package/src/docs/versioned/next/how-to/circleci.md +2 -0
  35. package/src/docs/versioned/next/how-to/preview-demo.md +5 -5
  36. package/src/docs/versioned/next/how-to/rate-limiting.md +17 -0
  37. package/src/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
  38. package/src/docs/versioned/next/how-to/use-syncdb-correctly.md +111 -0
  39. package/src/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
  40. package/src/docs/versioned/next/reference/README.md +1 -1
  41. package/src/docs/versioned/next/reference/admin-backend.md +26 -0
  42. package/src/docs/versioned/next/reference/admin-frontend.md +15 -0
  43. package/src/docs/versioned/next/reference/ai.md +55 -3
  44. package/src/docs/versioned/next/reference/api.md +4 -0
  45. package/src/docs/versioned/next/reference/blocks.md +243 -11
  46. package/src/docs/versioned/next/reference/environment-variables.md +3 -0
  47. package/src/docs/versioned/next/reference/syncdb.md +1 -0
  48. package/src/docs/versioned/next/reference/ui.md +212 -10
  49. 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`. A `ref` dataset stays empty until `resolveDataset` returns its rows, and the
528
+ `DataTable` and maps each dataset column type to a cell: `number` to a right-aligned
529
+ `number` cell, `date` to a `date` cell (Luxon `DATE_MED`, such as "Mar 14, 2026"), and
530
+ `string` to `text`. A listed column that the dataset does not have yet is `text`. Each header
531
+ is the column name with underscores shown as spaces (`bone_in_lamb` reads "bone in lamb"). The table
532
+ measures its container with `onLayout` and splits the width evenly across its columns, with
533
+ at least 96 per column; past that it scrolls sideways. Before the first layout each column
534
+ is 120 wide. Its height fits the header plus its rows (54 each), up to 10 rows; a longer table
535
+ scrolls inside that height, so it never spills over the next block. A `ref` dataset stays empty until `resolveDataset` returns its rows, and the
480
536
  chart shows `loading` while that promise is in flight. A rejected fetch clears that
481
537
  loading state. A segmented control highlights the option whose `data` matches the target
482
538
  chart or table. A `select` action switches a table the same way it switches a chart.
@@ -494,6 +550,83 @@ action also stores the chosen dataset on the target chart for this view. Pass
494
550
  `hostActions` and every callback stays enabled. `pendingElementIds` shows those
495
551
  buttons as loading. `overrides` replaces a block by id.
496
552
 
553
+ A `stepper` draws its `label` as a small muted line, then `IconButton` − and + around the
554
+ value and its `unit`, then `itemsTitle`, a two-column grid of item labels and amounts
555
+ (formatted with `decimals`, so `{amount: 2, unit: kg, decimals: 1}` reads "2.0 kg"), and the
556
+ muted `note`. − is disabled when `value - step` would fall below `min`, and + when `value + step`
557
+ would pass `max`, so with `max: 20` and `step: 2` + is disabled at 19. Both are disabled while either button's
558
+ element id is in `pendingElementIds`, and when `hostActions` is passed without the stepper's
559
+ `callback.name`. The buttons are labelled "Decrease <label>" and "Increase <label>". A tap
560
+ calls `onAction` with `blockId: <id>`, `elementId: <id>_decrease` or `<id>_increase`, and
561
+ `{kind: "callback", name, payload: {...payload, value: value ± step}}`. A host returns the
562
+ new stepper, and `overrides[<id>]` paints it.
563
+
564
+ A `checklist` draws its `title` on the left and an "n of m" counter on the right, then one
565
+ `CheckBox` row per item: the bold `text` first, then the small muted `meta` (such as a time),
566
+ then the muted `detail`. Each row is a pressable `Box` with the `checkbox` role, labelled with the item
567
+ text, that reports `accessibilityState` `{checked, disabled}` (`aria-checked` and
568
+ `aria-disabled` on web); Space toggles a focused row on web. A locked row keeps its role and
569
+ label and reports `disabled: true`. Test ids are stable across states: the item wrapper is
570
+ `<path>-<id>_<item id>`, the pressable row `<path>-<id>_<item id>-row-clickable`, its
571
+ `CheckBox` `<path>-<id>_<item id>-checkbox`, its lines `<path>-<id>_<item id>-text`, `-meta`,
572
+ and `-detail`, and the counter `<path>-counter`. A tick goes to the host when the checklist has a `callback`,
573
+ `onAction` is passed, and `hostActions` is omitted (as for the stepper) or lists
574
+ `callback.name`. Then a tap calls `onAction` with `blockId: <id>`,
575
+ `elementId: <id>_<item id>`, and `{kind: "callback", name, payload: {...payload, itemId,
576
+ checked, state}}`, where `state` maps every item id to its tick after this one. The tick is
577
+ not drawn ahead of the reply. `BlocksView` disables the checklist while a tick is pending:
578
+ while any of its `<id>_<item id>` element ids is in `pendingElementIds`, no row is pressable,
579
+ so a second tick cannot send a `state` that misses the first. The counter changes when
580
+ `overrides[<id>]` brings the returned checklist. Otherwise a tick stays in this `BlocksView` only and updates the counter at once.
581
+ When an override for the checklist arrives or changes, earlier local ticks on it are dropped
582
+ and the override's `checked` values show.
583
+ On the server, `toggleChecklistHostAction` from `@terreno/ai` handles the callback and returns
584
+ the stored checklist with the ticks from `state` (see [Host actions](ai.md#host-actions)). A host
585
+ that records progress registers its own `handles: "checklist"` action.
586
+
587
+ A `gallery` draws each image as a 4:3 `Image` tile (`fit` cover), with `alt` as the tile's
588
+ accessible label and the optional `caption` as small muted text under it. Tiles are 8 apart
589
+ (the `gap={2}` spacing step). The gallery measures its container with `onLayout`: up to three
590
+ images share one row at equal widths, and more wrap into a three-column grid whose last row
591
+ keeps the same tile width. When that width would fall under 160, every tile stays 160 wide in
592
+ one row that scrolls sideways. A measured width decides this rather than the `sm` breakpoint, so
593
+ a gallery inside a narrow `columns` child or card also scrolls. Before the first layout each tile
594
+ is 160 wide. A `file:` tile loads through `resolveImage` like an `image` block. Until it has a
595
+ URL, or when the lookup fails, the tile is a muted 4:3 placeholder that shows the alt text and
596
+ keeps it as the label. Test ids: the gallery `<path>`, each grid row `<path>-row-<n>`, the
597
+ scrolling row `<path>-scroll`, each tile `<path>-image-<index>`, the frame around a loaded
598
+ image `<path>-image-<index>-image`, its caption `<path>-image-<index>-caption`, and a
599
+ placeholder `<path>-image-<index>-placeholder`.
600
+ `Image` passes `alt` to the native image as its `accessibilityLabel`.
601
+
602
+ A `list` draws one row per item, 12 apart (`gap={3}`). The row starts with a 3:4 portrait
603
+ `Image` thumbnail (`fit` cover), always 112 wide and 149 tall, with `alt` as its accessible label.
604
+ Beside it, the optional `meta` sits small and muted above the bold `title`, and the optional
605
+ `text` follows in muted body text. `text` is plain text, not markdown. When any item in the list
606
+ has a thumbnail, an item without one keeps an empty 112-wide gutter so every title starts at the
607
+ same place; a list with no thumbnails draws text only, with no gutter. A `file:` thumbnail loads
608
+ through `resolveImage`, including in a list inside a card. Until it has a URL, or when the lookup
609
+ fails, it is a muted 112 by 149 placeholder that shows the alt text and keeps it as the label.
610
+ Test ids: the list `<path>`, each row `<path>-item-<index>`, its thumbnail
611
+ `<path>-item-<index>-image`, placeholder `<path>-item-<index>-placeholder`, gutter
612
+ `<path>-item-<index>-gutter`, text column `<path>-item-<index>-body`, and the
613
+ `<path>-item-<index>-meta`, `-title`, and `-text` lines.
614
+
615
+ A `copy` button writes the clipboard on the device and never calls `onAction`, so
616
+ `hostActions` does not disable it. A press builds the text with `blockPlainText` from
617
+ `@terreno/blocks`: a literal `text` as written, or the `target` block as it is shown now. That
618
+ means the `overrides` replacement (a stepper at 6 copies the scaled amounts), the checklist's
619
+ ticks made on the device, and a table's selected dataset, inline or a `ref` that
620
+ `resolveDataset` has returned. `BlocksView` writes it with `expo-clipboard` `setStringAsync`
621
+ (web and native), then shows "Copied" in small muted text beside the button for 2 seconds. A
622
+ failed write, or a target with no text yet (such as a `ref` table still loading), shows
623
+ "Couldn't copy" in error text for 2 seconds instead, logs `console.warn`, and does not throw.
624
+ The status sits in a `Box` with `accessibilityLiveRegion="polite"`, which stays mounted while
625
+ empty, so screen readers on web and Android announce the status when it appears (`Text` has
626
+ no live-region prop, and VoiceOver on iOS does not announce React Native live regions).
627
+ Test ids: the button `<path>-<element id>`, the live region `<path>-<element id>-status`, and
628
+ the status text `<path>-<element id>-status-text`.
629
+
497
630
  ```tsx
498
631
  <BlocksView
499
632
  document={reply}
@@ -505,6 +638,23 @@ buttons as loading. `overrides` replaces a block by id.
505
638
 
506
639
  Demo story: `BlocksView`.
507
640
 
641
+ ### MarkdownView
642
+
643
+ `MarkdownView` renders markdown with `react-native-markdown-display` and the current theme.
644
+ GPTChat and the `text` block render through it.
645
+
646
+ GFM tables use theme colors, never fixed hex values:
647
+
648
+ - Cell borders draw a grid in `theme.border.default`.
649
+ - Header cells sit on `theme.surface.secondaryLight` and use the bold text font (`text-bold`).
650
+ Body cells use `text-regular`.
651
+ - Every column gets an equal share of the width, at least 96. A narrow table fills its
652
+ container. A table wider than its container scrolls sideways in a horizontal `ScrollView`.
653
+
654
+ Test ids: `markdown-table-scroll` (the scroll view), `markdown-table`,
655
+ `markdown-table-header-<column>`, and `markdown-table-cell-<row>-<column>` (body rows count
656
+ from 0).
657
+
508
658
  ### HtmlFrame
509
659
 
510
660
  `HtmlFrame` paints one `html` block. On web it is an `<iframe sandbox="" referrerpolicy="no-referrer">` whose `srcdoc` starts with a Content-Security-Policy meta tag (`default-src 'none'`). On native it is a WebView with JavaScript off, and navigation after the first load is rejected. `BlocksView` renders that frame only when `allowHtml` is true and `streaming` is false. Otherwise the block is a card that says the preview is off or still arriving. `GPTChat` passes `allowHtml` through.
@@ -738,7 +888,8 @@ top spacing, the navigation row, bottom spacing, and `bottomNavBarHeight`.
738
888
  opts into a labeled full-width pager on the narrow viewport. `narrowBelowWidth` uses that
739
889
  viewport when the window is at or below the given pixel width; when omitted, the narrow
740
890
  viewport follows `isNarrowViewport()`. These props are web only; the native `SplitPage`
741
- ignores them. See `SplitPageProps` for when each prop applies and what is ignored.
891
+ ignores them. See `SplitPageProps` for when each prop applies and what is ignored. An unset
892
+ `color` is `baseAlternate` on web and native.
742
893
  `IconButton`'s `backgroundOpacity` tints only that button's background.
743
894
 
744
895
  ```typescript
@@ -772,12 +923,25 @@ Buttons use a scale animation by default. Set `pressAnimation="opacity"` for an
772
923
 
773
924
  Disabled and loading buttons use a non-interactive pressable regardless of the selected animation.
774
925
 
926
+ ### Tooltip
927
+
928
+ `Tooltip` (and `tooltipText` on `Button` / `IconButton`) renders its bubble outside the trigger's clipping and stacking context:
929
+
930
+ | Platform | Mount point | Positioning |
931
+ | --- | --- | --- |
932
+ | Web | `document.body` | `position: fixed`, `z-index: 9999`, so it shows above an open `Modal` and nothing else covers the page |
933
+ | iOS / Android | `TerrenoProvider` portal host | `position: absolute`, `z-index: 999` |
934
+
935
+ The bubble stays off screen at `opacity: 0` until the trigger is measured, then `getTooltipPosition` places it at `idealPosition` or the first side that fits on screen.
936
+
775
937
  ### Toast
776
938
 
777
939
  `TerrenoProvider` mounts the toast container. Call `useToast()` for `success`, `info`, `warn`, `error`, `show`, `hide`, and `catch`.
778
940
 
779
941
  On web, that container is portaled to `document.body` (`position: fixed`, `z-index: 999999`, `pointerEvents: "box-none"`), so a toast stays above an open `Modal` and its backdrop. The web container is sized with `100%` rather than the measured window, so statically exported pages (where `Dimensions` reports 0×0 at build time) still center the toast on screen. Native iOS and Android keep the in-tree absolute container sized to the window.
780
942
 
943
+ Only the visible toast takes clicks. Every full-width layer around it (the container, its safe area, and the `Toast` layout wrapper) uses `pointerEvents: "box-none"`, so a button at the same height as an open toast stays clickable. Declare that style through `StyleSheet.create`; react-native-web drops `pointerEvents` from inline styles.
944
+
781
945
  ## Authentication Components
782
946
 
783
947
  ### SocialLoginButton
@@ -1114,6 +1278,32 @@ Responsive `Box` direction props update automatically when the window resizes or
1114
1278
  All responsive Boxes share one dimension listener; non-responsive Boxes do not subscribe.
1115
1279
  When multiple direction props match, the largest active breakpoint wins (`xl` over `lg` over `md` over `sm`).
1116
1280
 
1281
+ `Box` takes an optional `accessibilityState` of `{checked?, disabled?}`. It is sent to screen
1282
+ readers as `accessibilityState` and as `aria-checked` / `aria-disabled`, on both a plain and a
1283
+ clickable Box. On a clickable Box, `disabled: true` also stops presses while the role and
1284
+ label stay. A clickable Box with `accessibilityRole` `checkbox` or `switch` also toggles on
1285
+ Space on web, as ARIA expects.
1286
+
1287
+ ```tsx
1288
+ <Box
1289
+ accessibilityHint="Marks this item as done"
1290
+ accessibilityLabel="Preheat the oven"
1291
+ accessibilityRole="checkbox"
1292
+ accessibilityState={{checked: false, disabled: isSaving}}
1293
+ onClick={toggle}
1294
+ />
1295
+ ```
1296
+
1297
+ `Box` also takes `accessibilityLiveRegion` (`polite` or `assertive`) on a Box without
1298
+ `onClick`. It is sent as `aria-live`, so screen readers announce changes to the Box's contents
1299
+ on web and Android. VoiceOver on iOS does not announce React Native live regions.
1300
+
1301
+ ```tsx
1302
+ <Box accessibilityLiveRegion="polite" testID="copy-status">
1303
+ {status ? <Text size="sm">{status}</Text> : null}
1304
+ </Box>
1305
+ ```
1306
+
1117
1307
  ## Icons
1118
1308
 
1119
1309
  Terreno uses **FontAwesome 6** by default. Pass icon names via `iconName` on `Icon`, `Button`, `IconButton`, form fields, `Badge`, and other icon-aware components.
@@ -1233,8 +1423,8 @@ const buttonStyles = toggle(isPressed, pressedStyles, defaultStyles);
1233
1423
 
1234
1424
  @terreno/ui components do not require environment variables. All configuration is done at runtime via:
1235
1425
 
1236
- - **TerrenoProvider props** — Theme customization, custom icon registry (`icons`), OpenAPI spec URL
1237
- - **Theme hooks** — `useTheme()`, `setTheme()`, `setPrimitives()`
1426
+ - **TerrenoProvider props** — `colorScheme`, `initialPrimitives`, custom icons, and OpenAPI spec URL
1427
+ - **Theme hooks** — `useTheme()`, `setColorScheme()`, `setTheme()`, `setPrimitives()`
1238
1428
  - **Component props** — Direct prop overrides for individual components
1239
1429
 
1240
1430
  **Example configuration:**
@@ -1243,16 +1433,28 @@ const buttonStyles = toggle(isPressed, pressedStyles, defaultStyles);
1243
1433
  import {TerrenoProvider} from "@terreno/ui";
1244
1434
 
1245
1435
  <TerrenoProvider
1246
- baseUrl="https://api.example.com"
1247
- theme={{
1248
- surface: {primary: "secondary500"},
1249
- }}
1250
- onError={(error) => console.error(error)}
1436
+ colorScheme="system"
1437
+ openAPISpecUrl="https://api.example.com/openapi.json"
1251
1438
  >
1252
1439
  {children}
1253
1440
  </TerrenoProvider>
1254
1441
  ``````
1255
1442
 
1443
+ ## DataTable column types
1444
+
1445
+ `columnType` picks the cell a column draws, unless `customColumnComponentMap` has an entry
1446
+ for that type:
1447
+
1448
+ | `columnType` | Cell |
1449
+ | --- | --- |
1450
+ | `text` (and any unmapped type) | The value as a string |
1451
+ | `number` | The value as a string, right-aligned, with the header right-aligned to match. A number or a numeric string both work |
1452
+ | `date` | An ISO string or a `Date`, formatted with Luxon `DATE_MED` ("Mar 14, 2026") in the local zone. A value Luxon cannot parse is shown as written, and an empty value is blank |
1453
+ | `boolean` | A check or a cross icon |
1454
+
1455
+ A value that is already formatted, such as "Oct 8, 2026, 3:00 PM", is not ISO, so a `date`
1456
+ column shows it unchanged.
1457
+
1256
1458
  ## DataTable server-side filtering
1257
1459
 
1258
1460
  `DataTable` is data-layer agnostic. Optional filter and search props emit a
@@ -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.
package/package.json CHANGED
@@ -11,8 +11,8 @@
11
11
  "@modelcontextprotocol/node": "^2.0.0",
12
12
  "@modelcontextprotocol/server": "^2.0.0",
13
13
  "@sentry/bun": "^10.39.0",
14
- "@terreno/api": "57.12.0",
15
- "@terreno/blocks": "57.12.0",
14
+ "@terreno/api": "57.14.0",
15
+ "@terreno/blocks": "57.14.0",
16
16
  "express": "^5.2.1",
17
17
  "luxon": "^3.7.2",
18
18
  "minisearch": "^7.2.0",
@@ -105,5 +105,5 @@
105
105
  "test:coverage": "bun run ../scripts/check-coverage.ts"
106
106
  },
107
107
  "type": "module",
108
- "version": "57.12.0"
108
+ "version": "57.14.0"
109
109
  }
@@ -0,0 +1,75 @@
1
+ # Upgrading to 57.13.0
2
+
3
+ **Action required:** Yes
4
+ **Affected packages:** @terreno/ai, @terreno/ui
5
+
6
+ ## Summary
7
+
8
+ Agent replies can use rich blocks (stepper, checklist, gallery, list, card eyebrow, copy), and hosts with `uiBlocks` on offer them in the prompt by default. `DataTable` number and date columns render differently, `neutral500` is a slightly darker gray, and the admin Roles screen groups permissions by resource. `@terreno/api` adds a per-route rate limiter, and `DocumentStorageApp` can be opened to signed-in non-admins. Action is required only for hosts with `uiBlocks` on that still have native builds from before this release.
9
+
10
+ ## Breaking changes
11
+
12
+ ### Rich blocks are offered in the chat prompt by default
13
+
14
+ **What changed**
15
+
16
+ With `uiBlocks` on, the chat system prompt now offers `checklist`, `gallery`, `list`, card `eyebrow`, and `copy`, plus `stepper` when a host action has `handles: "stepper"`. A native build shipped before 57.13.0 cannot render those blocks and shows an error banner for a reply that uses them.
17
+
18
+ **Why**
19
+
20
+ Rich blocks make agent replies more useful, so new hosts get them without extra setup.
21
+
22
+ **Migration**
23
+
24
+ Keep the old catalog until every shipped native build is on 57.13.0 or later:
25
+
26
+ ```typescript
27
+ // Before
28
+ addGptRoutes(router, {aiService, uiBlocks: {hostActions}});
29
+
30
+ // After (until old native builds age out)
31
+ addGptRoutes(router, {aiService, uiBlocks: {hostActions, richBlocks: false}});
32
+ ```
33
+
34
+ With `richBlocks: false` the prompt is the same as before. Validation accepts the new blocks either way.
35
+
36
+ ### `DataTable` number and date cells
37
+
38
+ **What changed**
39
+
40
+ `columnType: "number"` columns right-align cells and header. `columnType: "date"` cells format an ISO string or `Date` as Luxon `DATE_MED` (date only, local zone). Strings that are not ISO dates show as before.
41
+
42
+ **Why**
43
+
44
+ Numbers line up by digit, and raw ISO timestamps were hard to read.
45
+
46
+ **Migration**
47
+
48
+ To keep the old look for a column, render it with `customColumnComponentMap`:
49
+
50
+ ```typescript
51
+ // Before
52
+ {columnType: "date", title: "Created", width: 150}
53
+
54
+ // After (keep the raw value)
55
+ {columnType: "rawDate", title: "Created", width: 150}
56
+ // with customColumnComponentMap={{rawDate: ({cellData}) => <Text>{String(cellData.value)}</Text>}}
57
+ ```
58
+
59
+ ## Deprecations
60
+
61
+ None
62
+
63
+ ## New capabilities
64
+
65
+ - `createRouteRateLimitMiddleware({name, max, windowMs, store?, keyBy?})` from `@terreno/api` for a fixed-window limit on one route.
66
+ - `DocumentStorageApp` `access: "authenticated"` (non-admins see only `users/<userId>/`) and `uploadRateLimit`.
67
+ - `scaleStepperHostAction`, `toggleChecklistHostAction`, `findAgentBlock`, and `runBufferedChatTurn` from `@terreno/ai`.
68
+ - `SegmentedControl` `size="sm"`; `Box` `accessibilityState` and `accessibilityLiveRegion`.
69
+ - The `neutral500` primitive is `#949494`. Override it with `setPrimitives({neutral500: "#9A9A9A"})` to keep the old gray.
70
+
71
+ ## Verification
72
+
73
+ - `bun run compile` and `bun run lint` at the app root
74
+ - Send a chat prompt that asks for a checklist; on a current build it renders, and with `richBlocks: false` the agent replies with the old block types
75
+ - Open a screen with a `DataTable` date column and check the dates read as `Oct 10, 2026`
@@ -0,0 +1,43 @@
1
+ # Upgrading to 57.14.0
2
+
3
+ **Action required:** No
4
+ **Affected packages:** @terreno/syncdb
5
+
6
+ ## Summary
7
+
8
+ `@terreno/syncdb` pages snapshot streams in parallel (up to 6 at once by default) instead of one at a time, so cold start, reconcile, subscribe catch-up, `forceResync`, and wipe re-bootstrap finish faster when an app has many collections or scopes. Each stream now runs one snapshot pass at a time, which stops a concurrent pass from marking a purged stream bootstrapped over rows it never paged. No code change is required.
9
+
10
+ ## Breaking changes
11
+
12
+ None
13
+
14
+ ## Deprecations
15
+
16
+ None
17
+
18
+ ## New capabilities
19
+
20
+ ### `snapshotConcurrency` on `createSyncDb`
21
+
22
+ The client now sends up to `snapshotConcurrency` snapshot requests at once (default `DEFAULT_SNAPSHOT_CONCURRENCY`, 6). If your backend cannot take the extra parallel load at cold start, lower it. `1` restores the old one-stream-at-a-time behavior.
23
+
24
+ ```typescript
25
+ // Before
26
+ export const syncDb = createSyncDb({authProvider, baseUrl, collections, name: "my-app"});
27
+
28
+ // After (only if you need to limit parallel snapshot requests)
29
+ export const syncDb = createSyncDb({
30
+ authProvider,
31
+ baseUrl,
32
+ collections,
33
+ name: "my-app",
34
+ snapshotConcurrency: 2,
35
+ });
36
+ ```
37
+
38
+ A new how-to, `docs/how-to/use-syncdb-correctly.md`, lists library behavior apps have to handle.
39
+
40
+ ## Verification
41
+
42
+ - `bun run compile` and `bun run lint` at the app root
43
+ - Log in on a fresh device or after `wipeLocalData`; synced screens load, and the backend logs show several `/sync/snapshot` requests in flight together
@@ -19,7 +19,7 @@ Understanding-oriented documentation: concepts, architecture, and context.
19
19
  - [Durable agent harness](durable-agent-harness.md) — Phases, checkpoints, and why the audit span shares the checkpoint transaction
20
20
  - [AI-powered workflows](ai-workflows.md) — Autonomous documentation, testing, and maintenance workflows
21
21
  - [Agent UI Asks](agent-ui-asks.md) — Why agent questions are client-side tool calls, the pause and resume round trip, why cards carry exact answers, the watch paths, asks vs blocks
22
- - [Agent UI blocks](agent-ui-blocks.md) — Why chat replies use a closed YAML catalog instead of free-form UI
22
+ - [Agent UI blocks](agent-ui-blocks.md) — Why chat replies use a closed YAML catalog instead of free-form UI, why steppers and checklists call the server, and the rich-blocks rollout
23
23
  - [Authentication architecture](authentication.md) — Better Auth, JWT, and optional MCP service tokens
24
24
  - [Organization tenancy](organizations.md) — Optional Membership-backed tenancy, context, and isolation boundaries
25
25
  - [Configuration system](configuration-system.md) — Runtime configuration with database persistence