@terreno/mcp 57.12.0 → 57.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/docs/upgrades/57.13.0.md +75 -0
  2. package/dist/docs/versioned/next/explanation/README.md +1 -1
  3. package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
  4. package/dist/docs/versioned/next/explanation/example-coverage.md +3 -3
  5. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
  6. package/dist/docs/versioned/next/how-to/README.md +2 -0
  7. package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
  8. package/dist/docs/versioned/next/how-to/circleci.md +2 -0
  9. package/dist/docs/versioned/next/how-to/preview-demo.md +5 -5
  10. package/dist/docs/versioned/next/how-to/rate-limiting.md +17 -0
  11. package/dist/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
  12. package/dist/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
  13. package/dist/docs/versioned/next/reference/README.md +1 -1
  14. package/dist/docs/versioned/next/reference/admin-backend.md +26 -0
  15. package/dist/docs/versioned/next/reference/admin-frontend.md +15 -0
  16. package/dist/docs/versioned/next/reference/ai.md +55 -3
  17. package/dist/docs/versioned/next/reference/api.md +4 -0
  18. package/dist/docs/versioned/next/reference/blocks.md +243 -11
  19. package/dist/docs/versioned/next/reference/environment-variables.md +3 -0
  20. package/dist/docs/versioned/next/reference/ui.md +212 -10
  21. package/dist/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
  22. package/package.json +3 -3
  23. package/src/docs/upgrades/57.13.0.md +75 -0
  24. package/src/docs/versioned/next/explanation/README.md +1 -1
  25. package/src/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
  26. package/src/docs/versioned/next/explanation/example-coverage.md +3 -3
  27. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
  28. package/src/docs/versioned/next/how-to/README.md +2 -0
  29. package/src/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
  30. package/src/docs/versioned/next/how-to/circleci.md +2 -0
  31. package/src/docs/versioned/next/how-to/preview-demo.md +5 -5
  32. package/src/docs/versioned/next/how-to/rate-limiting.md +17 -0
  33. package/src/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
  34. package/src/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
  35. package/src/docs/versioned/next/reference/README.md +1 -1
  36. package/src/docs/versioned/next/reference/admin-backend.md +26 -0
  37. package/src/docs/versioned/next/reference/admin-frontend.md +15 -0
  38. package/src/docs/versioned/next/reference/ai.md +55 -3
  39. package/src/docs/versioned/next/reference/api.md +4 -0
  40. package/src/docs/versioned/next/reference/blocks.md +243 -11
  41. package/src/docs/versioned/next/reference/environment-variables.md +3 -0
  42. package/src/docs/versioned/next/reference/ui.md +212 -10
  43. package/src/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
@@ -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.13.0",
15
+ "@terreno/blocks": "57.13.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.13.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`
@@ -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
@@ -9,19 +9,79 @@ in [Agent UI Asks](agent-ui-asks.md).
9
9
  The design record is
10
10
  [agent-ui-blocks.md](../implementationPlans/agent-ui-blocks.md). Pick executes that work as
11
11
  Phase 5 of [Agent UI Asks](../implementationPlans/agent-ui-asks.md).
12
+ The stepper, checklist, gallery, list, card eyebrow, and copy additions are recorded in
13
+ `docs/implementationPlans/agent-ui-rich-replies.md`.
12
14
 
13
15
  ## Why a closed catalog
14
16
 
15
17
  An agent that can emit arbitrary components can also emit styles, scripts, and layout the
16
18
  app theme does not own. The catalog is the list of `@terreno/ui` components the chat can
17
- paint: heading, text, metric, badge, divider, context, chart, table, actions, and the two
18
- layout blocks `columns` and `card`. Unknown
19
- keys and unknown block types fail validation, so a reply cannot smuggle a `style` object
20
- or a hex color.
19
+ paint. Unknown keys and unknown block types fail validation, so a reply cannot smuggle a
20
+ `style` object or a hex color.
21
+
22
+ | Group | Blocks | Notes |
23
+ | --- | --- | --- |
24
+ | Text | `heading`, `text`, `context`, `callout`, `details` | `text` is markdown, including GFM tables |
25
+ | Values | `metric`, `badge`, `divider` | |
26
+ | Data | `chart`, `table` | Read an inline or `ref` dataset |
27
+ | Media | `image`, `gallery`, `list` | Sources are `data:image`, `file:`, or an allowed `https` host |
28
+ | Interactive | `actions` (`button`, `segmented`), `stepper`, `checklist` | Button actions: `reply`, `open`, `select`, `callback`, `copy` |
29
+ | Layout | `columns`, `card` (optional `title` and `eyebrow`) | Leaf blocks only inside |
30
+ | Escape hatch | `html` | Only with `uiBlocks.html`; sanitized before storage |
31
+
32
+ Field rules for each block are in the [reference](../reference/blocks.md).
21
33
 
22
34
  The same pure functions run wherever a document is checked. `parseBlocks` and
23
- `validateBlocks` live in `@terreno/blocks`, which has no React and no Express. The renderer
24
- and the chat route will call them; they do not grow a second grammar.
35
+ `validateBlocks` live in `@terreno/blocks`, which has no React and no Express. The renderer,
36
+ the chat route, and the `terreno-blocks` CLI all call them; none grows a second grammar.
37
+
38
+ ## Why interactive blocks call the server
39
+
40
+ A stepper that rescales a shopping list, or a checklist that remembers ticks, needs logic.
41
+ Terreno does not give the document a shared `state` with `{{formula}}` bindings, and it does
42
+ not run scripted html. Both need an expression language the theme and the validator cannot
43
+ see into. Instead, an interactive block names a host callback, as a callback button does:
44
+
45
+ | Interaction | Who runs it | Result |
46
+ | --- | --- | --- |
47
+ | Stepper − / + | Host action with `handles: "stepper"`, through `POST /gpt/actions` | The replacement stepper, swapped in by block id |
48
+ | Checklist tick | Host action with `handles: "checklist"`, or the device when the checklist has no callback | The replacement checklist, or a local tick |
49
+ | `copy` button | The device | Text on the clipboard; nothing reaches the server |
50
+
51
+ The cost is one round trip per tap. The view disables the block while the call is pending, so
52
+ fast taps are dropped, not queued, and a checklist tick cannot send ticks that miss the one
53
+ before it. The gain is that the agent writes plain data, and the app's code decides what a
54
+ tap means. `scaleStepperHostAction` and `toggleChecklistHostAction` in `@terreno/ai` cover the
55
+ common case. They read the block the agent wrote from the stored history and never trust the
56
+ tap request's copy of it. The history owner can still edit stored prompts, so an app whose
57
+ numbers matter (prices, stock) registers its own action over its own data.
58
+
59
+ Overrides live in client memory. A reload shows the agent's original block again.
60
+
61
+ ## Why photos come from a stored library
62
+
63
+ A model cannot make a photo while it writes a reply, and an `https` image needs an
64
+ `imageHosts` allowlist the model cannot know. The example app generates a photo library once
65
+ per environment with the Vertex image model, stores it through `FileStorageService`, and gives
66
+ the agent a `findPhotos` tool that returns `file:` ids. The reply cites ids; the client
67
+ resolves each id to a signed URL. Generation cost and latency stay out of the chat turn, and
68
+ every photo the user sees was made and stored ahead of time. See
69
+ [Give the agent photos](../how-to/agent-ui-blocks.md#give-the-agent-photos).
70
+
71
+ ## Rollout: rich blocks are on by default
72
+
73
+ `uiBlocks.richBlocks` defaults to `true`. The prompt then offers `checklist`, `gallery`,
74
+ `list`, `card.eyebrow`, and `copy`, and offers `stepper` when a host action has
75
+ `handles: "stepper"`. A stepper without such an action would have dead buttons, so the
76
+ prompt does not mention it.
77
+
78
+ The server and the web client upgrade together. A native build already in the app stores
79
+ does not know the new blocks: a reply that uses them fails its validation and shows the
80
+ error banner with the raw YAML. A host with such builds sets `uiBlocks.richBlocks: false`
81
+ until they update. The prompt is then the same as before rich blocks. Validation still
82
+ accepts the new blocks, so a stored reply that uses them still renders on new clients.
83
+ A client-declared catalog version, so the server picks blocks per client, would remove that
84
+ manual step; it is not built yet.
25
85
 
26
86
  ## Why the whole reply is YAML
27
87
 
@@ -49,9 +109,9 @@ another, which makes a small document able to expand in ways the limits cannot s
49
109
 
50
110
  | Layer | Owns |
51
111
  | --- | --- |
52
- | `@terreno/blocks` | Schema, parse, partial parse, validate, JSON Schema, prompt section, CLI, limits, error codes |
53
- | `@terreno/ui` | `BlocksView` for heading, text, metric, badge, divider, context, chart, table, actions, columns, and card. A select action changes the target chart or table dataset in that view. A segmented control starts on the option that matches that dataset. A callback stays disabled when `hostActions` is set and does not include its name. `GPTChat` `uiBlocks` renders assistant messages through `BlocksView`, including a spinner while the reply is still streaming. A `file:` image stays as alt text until `resolveImage` returns a URL. |
54
- | `@terreno/ai` | When `uiBlocks` is on: the chat system prompt, the post-stream `{blocks}` check, `AIDataset` storage, and `POST /gpt/actions`. `AIService.generateBlocks` returns one validated document outside a chat turn. |
112
+ | `@terreno/blocks` | Schema, parse, partial parse, validate, JSON Schema, prompt section, `blockPlainText`, CLI, limits, error codes |
113
+ | `@terreno/ui` | `BlocksView` renders every block in the catalog. A select action changes the target chart or table dataset in that view. A segmented control starts on the option that matches that dataset. A callback button or stepper stays disabled when `hostActions` is set and does not include its name; a checklist with such a callback ticks locally instead. A `copy` button writes the clipboard on the device. `GPTChat` `uiBlocks` renders assistant messages through `BlocksView`, including a spinner while the reply is still streaming, and swaps a block when a callback returns `{replace: "block"}`. A `file:` image stays as alt text until `resolveImage` returns a URL. |
114
+ | `@terreno/ai` | When `uiBlocks` is on: the chat system prompt, the post-stream `{blocks}` check, `AIDataset` storage, and `POST /gpt/actions`. The opt-in `scaleStepperHostAction` and `toggleChecklistHostAction`. `AIService.generateBlocks` returns one validated document outside a chat turn. |
55
115
 
56
116
  Asks use the same package under `src/asks/`. A block shows something. An ask collects an
57
117
  answer and returns it to the agent.
@@ -16,14 +16,14 @@ that file (and its imports) to a working example. Gaps stay explicit.
16
16
  | Admin panel (embedded) | yes — Admin plugin registration in `example-backend/src/server.ts` | yes — `example-frontend/app/admin/` | Expo routes wrap `@terreno/admin-frontend` screens. |
17
17
  | Admin SPA | yes — `AdminSpaServeApp` in `example-backend/src/server.ts` | n/a | Standalone SPA is served from the backend; not a frontend route. |
18
18
  | AI streaming chat | yes — `example-backend/src/api/ai.ts` (`addGptRoutes`) | yes — `example-frontend/app/(tabs)/ai.tsx` (`GPTChat`, optional `mascot`) | Empty-chat mascot is consumer-owned; the example fox is not a framework default. |
19
- | UI blocks | yes — `uiBlocks` with `repair: true` and `exportDataset` in `example-backend/src/ai/hostActions.ts`, wired from `example-backend/src/api/ai.ts`; `todoStats` in `example-backend/src/ai/tools.ts` stores a `ref` dataset | yes — `example-frontend/app/(tabs)/ai.tsx` (`uiBlocks`, dataset and action hooks, `hostActions` includes `exportDataset`) | e2e mocks a document with one inline and one `ref` chart. Steps: [agent-ui-blocks.md](../how-to/agent-ui-blocks.md). |
19
+ | UI blocks | yes — `uiBlocks` with `repair: true`, `exportDataset`, and the opt-in `scaleStepper` and `toggleChecklist` callbacks in `example-backend/src/ai/hostActions.ts`, wired from `example-backend/src/api/ai.ts`; `todoStats` in `example-backend/src/ai/tools.ts` stores a `ref` dataset; `findPhotos` in the same file cites the generated photo library, and `example-backend/src/api/photoLibrary.ts` signs its URLs | yes — `example-frontend/app/(tabs)/ai.tsx` (`uiBlocks`, dataset and action hooks, `hostActions` includes `exportDataset`, `scaleStepper`, and `toggleChecklist`, `resolveImage` resolves `file:` photo ids through `useLazyPhotoLibraryUrlQuery`) | e2e mocks a document with one inline and one `ref` chart. A second e2e test stores the Sunday roast reply (`blocks/src/fixtures/golden/sunday-roast.yaml` with `file:` photo ids) in a real `GptHistory`, streams it from the mocked model, and runs the stepper and checklist through the real `POST /gpt/actions`; photo URLs come from the real `GET /photoLibrary/:id/url`, and only the image download is stubbed (`example-frontend/e2e/helpers/seedGptHistory.ts`). Steps: [agent-ui-blocks.md](../how-to/agent-ui-blocks.md). |
20
20
  | Agent asks | yes — `asks: {approvals}` in `example-backend/src/api/ai.ts`, with the per-request `deleteCompletedTodos` approval tool from `example-backend/src/api/todoTools.ts`; it also passes `chat` to `addGptHistoryRoutes` for the headless `pendingAsks` and `turn` endpoints; keyless scripted agent with short compact-surface replies in `example-backend/src/api/demoAgent.ts` | yes — `example-frontend/app/(tabs)/ai.tsx` (`onAskSubmit`, `askErrors`), helpers in `example-frontend/lib/gptAsks.ts` | Without `GEMINI_API_KEY`, "Help me pick a plan" asks a `choice` in chat, "Pick toppings" asks a `select: "many"` `choice` with an Other field, "Send the weekly report" and "Archive old chats" ask a `confirm` (the second `destructive`), "Draft an announcement" asks a `markdown` edit of a draft, "Fill in the invoice details" asks a `form` with eight typed fields, "Upload a receipt" asks for `files`, sent as uploads when `GCS_BUCKET` is set and as data URLs otherwise, and "Delete my completed todos" calls `deleteCompletedTodos`, which runs only after the user approves the server's destructive approval card. e2e: `example-frontend/e2e/ai-chat.spec.ts`. Small clients: the curl walkthrough in the how-to and the demo's watch-sized `SimpleAskCard` story. Steps: [agent-ui-asks.md](../how-to/agent-ui-asks.md). |
21
21
  | AI structured output | no | no | `@terreno/ai` supports structured generation; the example only streams chat. Recorded gap — not claimed by a current tutorial. |
22
22
  | Feature flags + live updates | yes — `FeatureFlagsApp` in `example-backend/src/server.ts`, seed `example-backend/src/scripts/seed-feature-flags.ts` | yes — `useTerrenoFeatureFlags` in `example-frontend/app/_layout.tsx` | Socket-backed live updates share the realtime connection. |
23
23
  | Websockets / realtime | yes — `RealtimeApp` in `example-backend/src/server.ts`, `example-backend/src/websockets.ts` | yes — `useSocketConnection` in `example-frontend/app/_layout.tsx` | Sync deltas also use the socket (`SyncApp` + `@terreno/syncdb`). |
24
- | Consent forms | yes — `example-backend/src/consentDefinitions.ts`, seed scripts | yes — `example-frontend/app/(tabs)/consents.tsx`, admin `example-frontend/app/admin/consent-forms/` | First-login flow is covered by e2e (`example-frontend/e2e/consents.spec.ts`). |
24
+ | Consent forms | yes — `example-backend/src/consentDefinitions.ts`, seed scripts | yes — `example-frontend/app/(tabs)/documents.tsx` (Consents section), admin `example-frontend/app/admin/consent-forms/` | First-login flow is covered by e2e (`example-frontend/e2e/consents.spec.ts`). |
25
25
  | Product announcements | yes — `AnnouncementsApp` in `example-backend/src/server.ts`, release upload token via `ANNOUNCEMENTS_UPLOAD_TOKEN`, seed `example-backend/src/scripts/seed-announcements.ts`, sample release pack `example-backend/announcements/releases/1.14.0/` with `bun run announcements:pack` | yes — `AnnouncementNavigator` in `example-frontend/app/_layout.tsx` and admin announcement routes | Release packs support multiple Markdown surfaces and idempotent draft or explicit live API import. |
26
- | File upload / GCS | yes — `example-backend/src/api/settings.ts`, AI file routes in `example-backend/src/api/ai.ts`, `file-uploads` flag in `example-backend/src/api/fileUploads.ts` | yes — `example-frontend/app/(tabs)/files.tsx`, `example-frontend/app/(tabs)/ai.tsx`, `example-frontend/app/gcs-settings.tsx` | The `file-uploads` feature flag hides upload controls and rejects uploads when off. A missing flag leaves uploads enabled. |
26
+ | File upload / GCS | yes — `example-backend/src/api/settings.ts`, AI file routes in `example-backend/src/api/ai.ts`, `file-uploads` flag in `example-backend/src/api/fileUploads.ts` | yes — `example-frontend/app/(tabs)/documents.tsx` (Files section, every signed-in user, 1 upload / IP / min), `example-frontend/app/(tabs)/ai.tsx`, `example-frontend/app/gcs-settings.tsx` | The `file-uploads` feature flag hides upload controls and rejects uploads when off. A missing flag leaves uploads enabled. Deployed bucket is terraform-managed (`google_storage_bucket.example_documents`). |
27
27
  | Better Auth | yes — `example-backend/src/utils/betterAuthConfig.ts`, `BetterAuthApp` in `example-backend/src/server.ts` | yes — `example-frontend/app/login.tsx`, `example-frontend/lib/betterAuth.ts` | Default `AUTH_PROVIDER` is `better-auth`. |
28
28
  | syncdb local-first | yes — `SyncApp` in `example-backend/src/server.ts` | yes — `example-frontend/store/syncdb.ts`, `example-frontend/app/(tabs)/index.tsx` | PR #869 closed without merge; `@terreno/syncdb` shipped on the launch line and is the example todos path. |
29
29
  | SyncDB diagnostics | n/a | yes — `debug` registration in `example-frontend/store/syncdb.ts`, debugger UI at `example-frontend/app/syncdb-debug.tsx` | `terreno-mcp-local` can inspect, snapshot/compare/merge, mutate, flush, reconcile/resync, and exercise offline/conflict paths against the running development client. |
@@ -1319,6 +1319,18 @@ One easy-to-use harness inside `@terreno/ai` that runs long-lived agentic work d
1319
1319
 
1320
1320
  ---
1321
1321
 
1322
+ ## agent-ui-rich-replies
1323
+
1324
+ **Title:** `Agent UI Rich Replies: steppers, checklists, galleries, and copy`
1325
+
1326
+ **Labels:** `area:dx`, `type:feature`
1327
+ **Project fields:** Area=`dx`, Target=`Next`, Impact=`Feature`, IP=`agent-ui-rich-replies`, Status=`Planned`
1328
+
1329
+ - **Implementation plan:** [agent-ui-rich-replies.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/implementationPlans/agent-ui-rich-replies.md)
1330
+ - **Tasks:** [agent-ui-rich-replies.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/tasks/agent-ui-rich-replies.md)
1331
+
1332
+ ---
1333
+
1322
1334
  # Shipped, umbrella, and declined IPs
1323
1335
 
1324
1336
  Tracking issues created for IPs that previously lacked a `**Roadmap issue:**` header.
@@ -53,6 +53,8 @@ Problem-oriented, practical steps. Use these when you know what you want to do.
53
53
  - [Add a GPT chat mascot](add-gpt-chat-mascot.md) — Optional consumer-owned character on empty `GPTChat`
54
54
  - [Add agent asks to a chat](agent-ui-asks.md) — Turn on `asks`, show and answer them in `GPTChat`, try the keyless demo agent, and answer asks from an Apple Watch or another small client
55
55
  - [Validate a block document locally](agent-ui-blocks.md) — `terreno-blocks validate` for a whole-reply YAML document
56
+ - [Add a stepper callback](agent-ui-blocks.md#add-a-stepper-callback) — `scaleStepperHostAction` or your own `handles: "stepper"` action; checklist ticks, copy buttons, the `richBlocks: false` opt-out, and the `blocks:smoke` model check are on the same page
57
+ - [Give the agent photos](agent-ui-blocks.md#give-the-agent-photos) — Generate a stored photo library once and let the agent cite it with `findPhotos`
56
58
 
57
59
  ## Admin
58
60