@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.
- package/dist/docs/upgrades/57.13.0.md +75 -0
- package/dist/docs/upgrades/57.14.0.md +43 -0
- package/dist/docs/versioned/next/explanation/README.md +1 -1
- package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
- package/dist/docs/versioned/next/explanation/example-coverage.md +3 -3
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
- package/dist/docs/versioned/next/how-to/README.md +3 -0
- package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
- package/dist/docs/versioned/next/how-to/circleci.md +2 -0
- package/dist/docs/versioned/next/how-to/preview-demo.md +5 -5
- package/dist/docs/versioned/next/how-to/rate-limiting.md +17 -0
- package/dist/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
- package/dist/docs/versioned/next/how-to/use-syncdb-correctly.md +111 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
- package/dist/docs/versioned/next/reference/README.md +1 -1
- package/dist/docs/versioned/next/reference/admin-backend.md +26 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +15 -0
- package/dist/docs/versioned/next/reference/ai.md +55 -3
- package/dist/docs/versioned/next/reference/api.md +4 -0
- package/dist/docs/versioned/next/reference/blocks.md +243 -11
- package/dist/docs/versioned/next/reference/environment-variables.md +3 -0
- package/dist/docs/versioned/next/reference/syncdb.md +1 -0
- package/dist/docs/versioned/next/reference/ui.md +212 -10
- package/dist/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
- package/package.json +3 -3
- package/src/docs/upgrades/57.13.0.md +75 -0
- package/src/docs/upgrades/57.14.0.md +43 -0
- package/src/docs/versioned/next/explanation/README.md +1 -1
- package/src/docs/versioned/next/explanation/agent-ui-blocks.md +69 -9
- package/src/docs/versioned/next/explanation/example-coverage.md +3 -3
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +12 -0
- package/src/docs/versioned/next/how-to/README.md +3 -0
- package/src/docs/versioned/next/how-to/agent-ui-blocks.md +187 -3
- package/src/docs/versioned/next/how-to/circleci.md +2 -0
- package/src/docs/versioned/next/how-to/preview-demo.md +5 -5
- package/src/docs/versioned/next/how-to/rate-limiting.md +17 -0
- package/src/docs/versioned/next/how-to/test-ui-accessibility.md +35 -0
- package/src/docs/versioned/next/how-to/use-syncdb-correctly.md +111 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-rich-replies.md +359 -0
- package/src/docs/versioned/next/reference/README.md +1 -1
- package/src/docs/versioned/next/reference/admin-backend.md +26 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +15 -0
- package/src/docs/versioned/next/reference/ai.md +55 -3
- package/src/docs/versioned/next/reference/api.md +4 -0
- package/src/docs/versioned/next/reference/blocks.md +243 -11
- package/src/docs/versioned/next/reference/environment-variables.md +3 -0
- package/src/docs/versioned/next/reference/syncdb.md +1 -0
- package/src/docs/versioned/next/reference/ui.md +212 -10
- package/src/docs/versioned/next/tasks/agent-ui-rich-replies.md +188 -0
|
@@ -90,13 +90,16 @@ src/
|
|
|
90
90
|
|
|
91
91
|
- **Plugins:** `AiApp`, `LangfuseApp`
|
|
92
92
|
- **Service:** `AIService`, `TemperaturePresets`, `FileStorageService`, `MCPService`,
|
|
93
|
-
`getMCPTools`
|
|
93
|
+
`getMCPTools`, `runBufferedChatTurn` (one chat turn to completion without HTTP: the same system
|
|
94
|
+
prompt, tools, and block checks as `/gpt/histories/:id/turn`; takes `{body: {historyId, prompt},
|
|
95
|
+
options, req}` where `req` carries `user` and `headers`)
|
|
94
96
|
- **Models:** `AIRequest`, `GptHistory`, `FileAttachment`, `Project` (lazy; prefer `getProjectModel()`)
|
|
95
97
|
- **Routes:** `addGptRoutes`, `addGptHistoryRoutes`, `addAiRequestsExplorerRoutes`, `addFileRoutes`, `addProjectRoutes`, `addMcpRoutes`
|
|
96
98
|
- **Structured output:** `parseAiJson`, `normalizeLlmJsonTextForStructuredOutput`, re-exported `Output`, `jsonSchema`, `JSONValue`, `FlexibleSchema` from `ai`
|
|
97
99
|
- **Langfuse:** `initLangfuseClient`, `getLangfuseClient`, `shutdownLangfuseClient`, `compilePrompt`, `createPrompt`, `getPrompt`, `createTelemetryConfig`, `preparePromptForAI`, `initTracing`, `shutdownTracing`, `LangfuseCache`, cache helpers
|
|
98
100
|
- **Gemini / Vertex:** `listGeminiApiModels`, `normalizeGeminiModelId`, `GEMINI_API_BASE_URL`, `createVertexProvider`, `listEnabledVertexModels`, `verifyVertexModelsEnabled`, `assertVertexModelsEnabled`, `isVertexModelAllowed`, `normalizeVertexModelId`, `DEFAULT_VERTEX_LOCATION`
|
|
99
101
|
- **Prompts:** `COMPACT_SURFACE_SYSTEM_PROMPT`, `CONTENT_SUMMARY_PROMPT`, `DEFAULT_GPT_MEMORY`, `JSON_VALUE_SYSTEM_PROMPT`, `REMIX_PROMPT`, `TERRENO_ASKS_SYSTEM_PROMPT`, `TITLE_GENERATION_PROMPT`, `TRANSLATION_PROMPT`
|
|
102
|
+
- **Block host actions:** `scaleStepperHostAction`, `toggleChecklistHostAction`, `findAgentBlock`, types `HostAction`, `HostActionContext`, `HostActionResult` ([Host actions](#host-actions))
|
|
100
103
|
- **Asks:** `createAskTools({kinds, surface?})`, `TERRENO_ASKS_SYSTEM_PROMPT`, `COMPACT_SURFACE_SYSTEM_PROMPT`, types `AsksOptions`, `ApprovalAskInput`, `AskOrigin`, `GptHistoryPendingAsk`, `GptHistoryPromptAsk`, `GptHistoryAskStatus`, and `Ask`, `AskKind`, `AskResponse`, `AskValidationError`, `SimpleCard`, `SimpleCardButton` re-exported from `@terreno/blocks` ([Agent UI Asks](agent-ui-asks.md))
|
|
101
104
|
- **Web search:** `WebSearchProvider`, `WebSearchResult` types
|
|
102
105
|
- **Harness (subpath `@terreno/ai/harness`):** `Harness`, `defineTask`, `defineAgent`, `defineTool`, `defineExtension`, `section`, `hook`, `wrapTool`, `HarnessExtensionError`, `HARNESS_HOOK_KINDS`, `HarnessConversationHandle`, `HarnessConversationBusyError`, `HarnessConversationOwnedError`, `HARNESS_EVENT_TYPES`, `HARNESS_SUBMIT_DISPOSITIONS`, `HARNESS_WHEN_BUSY`, `HarnessModelCallError`, `HarnessSubagentError`, `isRetryableModelError`, `AGENT_TURN_TASK_NAME`, `AGENT_TOOL_TASK_NAME`, `HARNESS_AGENT_DEFAULT_MAX_STEPS`, `HARNESS_CONVERSATION_STATUSES`, `HARNESS_INTERRUPT_ACTIONS`, `HARNESS_MESSAGE_ROLES`, `HARNESS_MODEL_RETRY_DEFAULTS`, `InProcessRunner`, `HarnessCommitConflictError`, `HARNESS_RESOLVE_ACTIONS`, `HARNESS_RETRY_DEFAULTS`, `HARNESS_TASK_STATUSES`, `HARNESS_WAIT_KINDS`, `HARNESS_WAIT_POLICIES`, `HARNESS_WAIT_RESOLUTIONS`, `IN_PROCESS_RUNNER_ROLES`, `approvalGate`, `approvalTaskInput`, `HarnessApp`, `HarnessApprovalConflictError`, `HARNESS_APPROVAL_STATUSES`, `HARNESS_DEFAULT_APPROVERS` — see [AI harness reference](ai-harness.md)
|
|
@@ -273,7 +276,7 @@ GPT project with persistent context and memories. Registered lazily: `getProject
|
|
|
273
276
|
| `/gpt/histories/:id/stream` | GET | `IsAuthenticated` (owner) | SSE resume of an in-flight reply; query: optional `streamId`, `offset` |
|
|
274
277
|
| `/gpt/tools` | GET | `IsAuthenticated` | List builtin + MCP tools (ask tools are not listed) |
|
|
275
278
|
| `/gpt/datasets/:id` | GET | owner (`IsOwner`; another user is 404) | Read a stored dataset. Mounted only when `uiBlocks` is on. Query: `grain` (`hour` \| `day` \| `week` \| `month`), `limit` (default 500, max 1000), `page`. Response `data`: `{columns, rows, rowCount, page, more}`. `grain` buckets the first date column in UTC. An offset is converted before `startOf`. A date with no zone is that UTC day. Null date cells are skipped. Number columns are summed. Without `page`, a series longer than `limit` is LTTB-downsampled and `more` is false. With `page`, rows are a page and `more` is true when another page remains. |
|
|
276
|
-
| `/gpt/actions` | POST | `IsAuthenticated` plus history owner (another user is 403) | Run a host callback. Mounted only when `uiBlocks` is on, on the `/gpt` path. Body: `{historyId, messageId, blockId, elementId, name, payload?}`. Unknown `name` is 404. A payload that fails the host schema is 400 with `meta.fields`. The handler has 10 seconds (`actionTimeoutMs` can set another cap) and then 504. Response `data`: `{text?, blocks?, replace?}`. An invalid `blocks` document is 500. Logged as `AIRequest` `requestType: "ui_action"
|
|
279
|
+
| `/gpt/actions` | POST | `IsAuthenticated` plus history owner (another user is 403) | Run a host callback. Mounted only when `uiBlocks` is on, on the `/gpt` path. Body: `{historyId, messageId, blockId, elementId, name, payload?}`. Unknown `name` is 404. A payload that fails the host schema is 400 with `meta.fields`. The handler has 10 seconds (`actionTimeoutMs` can set another cap) and then 504. Response `data`: `{text?, blocks?, replace?}`. An invalid `blocks` document is 500. Logged as `AIRequest` `requestType: "ui_action"` with an ids-only prompt and the response; an action with `logResponse: false` adds a numeric `payload.value` to the prompt and logs no response. |
|
|
277
280
|
|
|
278
281
|
Generated images (image-output models such as `gemini-3-pro-image`) arrive as SSE `image` events: `{image: {mimeType, url}}` with a base64 data URL. Each image is sent once, even when the model reports it both as a stream file part and in the final `result.files`. The saved assistant prompt stores one `image` content part per image and `text: ""` when there is no text. On later turns, `buildMessages` sends an image-only assistant prompt to the model as the text `[Generated image]`, because providers reject empty assistant turns.
|
|
279
282
|
|
|
@@ -281,7 +284,56 @@ AI resolution order: `x-ai-api-key` header + `createModelFn` → `createServerMo
|
|
|
281
284
|
|
|
282
285
|
Pass `asks: true` (or `{kinds: ["choice"]}`) to let the model ask the user typed questions in the chat. Asks are off by default; with them off, tools, system prompt, and SSE events are unchanged. See [Agent UI Asks](agent-ui-asks.md).
|
|
283
286
|
|
|
284
|
-
Pass `uiBlocks: true` (or `{hostActions, html, imageHosts, repair, datasetTtlDays, datasetMaxRows}`) to require each assistant reply to be a block document. `html: true` allows `html` blocks and sanitizes them before they are stored. `imageHosts` lists hostnames allowed on `https` image sources. The client receives that document once, after missing action ids are filled, `repair: true` rewrites it, and html is sanitized, and before `{ask}`. `{replace: "text", text}` is sent only when text was already streamed and then changed. `datasetTtlDays` defaults to `0` (keep the dataset). `datasetMaxRows` defaults to 50,000. Off by default; with it off, the system prompt, SSE events, `/gpt/datasets`, and `/gpt/actions` are unchanged. When it is on, the system prompt gains `TERRENO_UI_BLOCKS_SYSTEM_PROMPT` (host callback names included when `hostActions` is set). After the final text, the route validates it and sends `{blocks: {ok, errors, warnings}}` before `{done}`. `hostActions` is the callback allowlist: a name outside it fails with `UNKNOWN_HOST_ACTION`. `{repair: true}` runs one repair call when validation fails and stores that reply. A document that is still invalid is stored with a `Block validation errors:` note so the next turn sees it. See [Validate a block document locally](../how-to/agent-ui-blocks.md).
|
|
287
|
+
Pass `uiBlocks: true` (or `{hostActions, html, imageHosts, repair, richBlocks, datasetTtlDays, datasetMaxRows}`) to require each assistant reply to be a block document. `html: true` allows `html` blocks and sanitizes them before they are stored. `imageHosts` lists hostnames allowed on `https` image sources. The client receives that document once, after missing action ids are filled, `repair: true` rewrites it, and html is sanitized, and before `{ask}`. `{replace: "text", text}` is sent only when text was already streamed and then changed. `datasetTtlDays` defaults to `0` (keep the dataset). `datasetMaxRows` defaults to 50,000. Off by default; with it off, the system prompt, SSE events, `/gpt/datasets`, and `/gpt/actions` are unchanged. When it is on, the system prompt gains `TERRENO_UI_BLOCKS_SYSTEM_PROMPT` (host callback names included when `hostActions` is set). After the final text, the route validates it and sends `{blocks: {ok, errors, warnings}}` before `{done}`. `hostActions` is the callback allowlist: a name outside it fails with `UNKNOWN_HOST_ACTION`. A stepper or checklist `callback.name` must also name an action whose `handles` is that block; a stepper that names any other registered action fails with `UNKNOWN_HOST_ACTION`. `{repair: true}` runs one repair call when validation fails and stores that reply. A document that is still invalid is stored with a `Block validation errors:` note so the next turn sees it. See [Validate a block document locally](../how-to/agent-ui-blocks.md).
|
|
288
|
+
|
|
289
|
+
`uiBlocks.richBlocks` defaults to `true`: the prompt offers the rich blocks, and `stepper` when a host action has `handles: "stepper"`. The `checklist` line names the `handles: "checklist"` actions as its callback, or, without one, tells the model to leave `callback` out so ticks stay local. This is a behaviour change on upgrade: every host with `uiBlocks` on gets the new blocks in its prompt. Set `richBlocks: false` while shipped clients (for example older native builds) cannot render them; the prompt is then the same as before rich blocks, even with a stepper action registered. The opt-out changes only what the model is told to write. Validation accepts the rich blocks either way.
|
|
290
|
+
|
|
291
|
+
#### Host actions
|
|
292
|
+
|
|
293
|
+
Each `uiBlocks.hostActions` entry is a `HostAction`:
|
|
294
|
+
|
|
295
|
+
| Field | Type | Description |
|
|
296
|
+
|-------|------|-------------|
|
|
297
|
+
| `handler` | `(context) => HostActionResult` | Gets `{blockId, elementId, history, messageId, payload, user}`. Returns `{text?, blocks?, replace?}`; `blocks` is a whole `{v: 1, blocks}` document |
|
|
298
|
+
| `payload` | Zod-like schema? | Checked with `safeParse` before the handler runs. A failure is 400 `Invalid payload` with `meta.fields` |
|
|
299
|
+
| `handles` | `"stepper"` \| `"checklist"`? | The interactive block this action serves. The prompt offers `stepper` only when some action has `handles: "stepper"`, and names those actions as its callbacks. Validation accepts a stepper or checklist only when its `callback.name` is such an action. |
|
|
300
|
+
| `logResponse` | boolean? | Default `true`. `false` keeps the returned document out of the `ui_action` log and adds a numeric `payload.value` to its ids-only prompt |
|
|
301
|
+
|
|
302
|
+
`scaleStepperHostAction` is an opt-in `stepper` action: `{handler, payload, handles: "stepper", logResponse: false}`. Register it under the name the agent writes in `callback.name`:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
addGptRoutes(router, {
|
|
306
|
+
aiService,
|
|
307
|
+
uiBlocks: {hostActions: {scaleStepper: scaleStepperHostAction}},
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Its payload schema is `z.object({value: z.number()}).passthrough()`, so extra keys from the agent's `callback.payload` pass. The handler:
|
|
312
|
+
|
|
313
|
+
1. Loads the agent's stepper with `findAgentBlock` (below), not from the request.
|
|
314
|
+
2. Checks that `value` is within `min` and `max` and on the `step` grid from the agent's `value`. Otherwise 400 `Invalid stepper value`.
|
|
315
|
+
3. Returns `{replace: "block", blocks: {v: 1, blocks: [stepper]}}`. The stepper has the new `value`, and each item's `amount` is `amount × value / agent value`, rounded to its `decimals` (`round: up` rounds up). A stepper the agent wrote with `value: 0` keeps its amounts.
|
|
316
|
+
|
|
317
|
+
Steps 2 and 3 are `isStepperValueAllowed` and `scaleStepperBlock` from `@terreno/blocks`, so a client without a server (the demo playground) scales the same way. Scaling always starts from the stored original, so rounding does not drift tap after tap. The owner of a history can edit its stored prompts with `PATCH /gpt/histories/:id`, so an app whose numbers matter (prices, stock) registers its own `handles: "stepper"` action over its own data.
|
|
318
|
+
|
|
319
|
+
`toggleChecklistHostAction` is an opt-in `checklist` action: `{handler, payload, handles: "checklist", logResponse: false}`. Register it under the name the agent writes in `callback.name`; the prompt then tells the model to set a checklist's `callback` to that name:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
addGptRoutes(router, {
|
|
323
|
+
aiService,
|
|
324
|
+
uiBlocks: {hostActions: {toggleChecklist: toggleChecklistHostAction}},
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Its payload schema is `z.object({itemId: z.string(), checked: z.boolean(), state: z.record(z.string(), z.boolean())}).passthrough()`, so extra keys from the agent's `callback.payload` pass. The handler:
|
|
329
|
+
|
|
330
|
+
1. Loads the agent's checklist with `findAgentBlock` (below), not from the request.
|
|
331
|
+
2. Checks that `itemId` and every `state` key are item ids of that checklist. Otherwise 400 `Unknown checklist item`.
|
|
332
|
+
3. Returns `{replace: "block", blocks: {v: 1, blocks: [checklist]}}`. Each item's `checked` is its `state` value; an item missing from `state` is unchecked (the client always sends every item). The ticked `itemId` always takes `checked`.
|
|
333
|
+
|
|
334
|
+
Steps 2 and 3 are `unknownChecklistItemIds` and `applyChecklistState` from `@terreno/blocks`. It saves nothing, and its `ui_action` log row holds only the ids (no `itemId`, `checked`, or `state`). An app that records progress registers its own `handles: "checklist"` action.
|
|
335
|
+
|
|
336
|
+
`findAgentBlock({history, messageId, blockId, type})` returns the block of `type` with id `blockId` that an assistant prompt holds, including inside `card` and `columns`. When `messageId` is `msg-<n>` it reads `history.prompts[n]` first (stored prompts have no ids). Otherwise, or when that prompt does not hold the block, it uses the only assistant prompt that does. Several matches throw 409 `Block is ambiguous`; none throws 404 `Block not found`.
|
|
285
337
|
|
|
286
338
|
With asks on, a host tool with the AI SDK's `needsApproval: true` runs only after the user approves it: the turn pauses on a server-made `confirm` ask. `asks.approvals` sets that ask's input per tool name, as `(input) => ConfirmAskInput` (`ApprovalAskInput`); without an entry, the ask is "Allow <toolName>?" with the tool's description. With asks off, such a tool never runs. See [Approval asks](agent-ui-asks.md#approval-asks).
|
|
287
339
|
|
|
@@ -117,6 +117,10 @@ new TerrenoApp({
|
|
|
117
117
|
|
|
118
118
|
Skip: `GET /health`, `/healthz`, `/openapi.json`, `/swagger` (trailing slashes and letter case ignored). 429 is `APIError` `code: "rate-limit-exceeded"` with `Retry-After` and `RateLimit` / `RateLimit-Policy`. JWT login/signup/refresh ignore a stale access token. Operator guide: [Rate limiting](../how-to/rate-limiting.md).
|
|
119
119
|
|
|
120
|
+
#### Per-route limits
|
|
121
|
+
|
|
122
|
+
`createRouteRateLimitMiddleware({name, max, windowMs, store?, keyBy?})` returns Express middleware with its own fixed-window bucket, independent of `TerrenoApp` `rateLimit`. `keyBy` is `"ip"` (default, `req.ip`) or `"user"` (falls back to IP). `name` namespaces the bucket. Same stores and 429 shape as the global limiter. Set Express `trust proxy` behind a load balancer so `req.ip` is the client.
|
|
123
|
+
|
|
120
124
|
### MCP service tokens
|
|
121
125
|
|
|
122
126
|
Opt-in. Omitted or `enabled: false` leaves `/mcp/service-tokens` unmounted and ignores `mcp_` Bearer credentials on `/mcp`.
|
|
@@ -54,7 +54,7 @@ A dataset name matches `^[a-z][a-z0-9_]{0,63}$`. A document has at most 8 datase
|
|
|
54
54
|
| `image` | `alt`, `src` | `id` |
|
|
55
55
|
| `details` | `title`, `text` | `id` |
|
|
56
56
|
|
|
57
|
-
`src` is a `data:image` URL, a `file:` ref (`file:` plus an id), or an `https` URL. `https` is allowed only when the hostname is in `validateBlocks` `imageHosts` (the server option is `uiBlocks.imageHosts`). Anything else is `IMAGE_HOST_NOT_ALLOWED`. An empty list rejects every `https` image. A ref dataset `id` uses 1–80 letters, digits, underscores, or hyphens. A block or action-element `id` stays lowercase and starts with a letter. `limit` of `0` is `INVALID_TYPE` (an integer of at least 1), not `TOO_SHORT`.
|
|
57
|
+
`src` (on an `image` block, on each `gallery` tile, and on a `list` item's `image`) is a `data:image` URL, a `file:` ref (`file:` plus an id), or an `https` URL. `https` is allowed only when the hostname is in `validateBlocks` `imageHosts` (the server option is `uiBlocks.imageHosts`). Anything else is `IMAGE_HOST_NOT_ALLOWED`. An empty list rejects every `https` image. A ref dataset `id` uses 1–80 letters, digits, underscores, or hyphens. A block or action-element `id` stays lowercase and starts with a letter. `limit` of `0` is `INVALID_TYPE` (an integer of at least 1), not `TOO_SHORT`.
|
|
58
58
|
|
|
59
59
|
Warnings do not block rendering:
|
|
60
60
|
|
|
@@ -74,15 +74,153 @@ Warnings do not block rendering:
|
|
|
74
74
|
| `badge` | `text` (1–80) | `status`: `info`, `error`, `warning`, `success`, `neutral`, `active`; `id` | `Badge` |
|
|
75
75
|
| `divider` | — | `id` | `SectionDivider` |
|
|
76
76
|
| `context` | `text` (1–280) | `id` | `Text` |
|
|
77
|
+
| `stepper` | see [Stepper](#stepper) | | `IconButton` − and +, `Text`, `Heading` |
|
|
78
|
+
| `checklist` | see [Checklist](#checklist) | | `CheckBox` rows, `Text`, `Heading` |
|
|
79
|
+
| `gallery` | see [Gallery](#gallery) | | `Image` tiles in a row or grid, `Text` captions |
|
|
80
|
+
| `list` | see [List](#list) | | `Image` thumbnails beside `Text` title, text, and meta |
|
|
77
81
|
| `columns` | `children`: 2–4 blocks | `id` | `Box` row |
|
|
78
|
-
| `card` | `children`: at least 1 block | `title` (1–120), `id` | `Card` |
|
|
82
|
+
| `card` | `children`: at least 1 block | `title` (1–120), `eyebrow` (1–60), `id` | `Card` |
|
|
79
83
|
|
|
80
84
|
`columns` and `card` sit at the top level. Their children are leaf blocks. A layout block
|
|
81
85
|
inside another layout block is `DEPTH_EXCEEDED`. Every block counts toward the 50-block
|
|
82
86
|
cap, including the `card` or `columns` block itself, so a card cannot hold 50 children.
|
|
83
87
|
|
|
88
|
+
A card's `eyebrow` is a short label that sits small and muted above the `title` (or above the
|
|
89
|
+
children when there is no title). `BlocksView` draws it as a `Text` in the same small, muted style
|
|
90
|
+
as the stepper label, with test ID `<card path>-eyebrow`.
|
|
91
|
+
|
|
84
92
|
`id` matches `^[a-z][a-z0-9_]{0,63}$`.
|
|
85
93
|
|
|
94
|
+
## Stepper
|
|
95
|
+
|
|
96
|
+
A `stepper` is a − value + control. Each tap calls a host callback, and the host returns the
|
|
97
|
+
replacement stepper (see `scaleStepperHostAction` in [the AI reference](ai.md)).
|
|
98
|
+
|
|
99
|
+
| Field | Required | Rule |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `id` | yes | Block id pattern, at most 54 characters |
|
|
102
|
+
| `label` | yes | 1–80 |
|
|
103
|
+
| `value`, `min`, `max` | yes | Numbers. `min` < `max` and `min` ≤ `value` ≤ `max`, or `OUT_OF_RANGE` |
|
|
104
|
+
| `step` | no | Above 0 (`OUT_OF_RANGE`). Default 1 |
|
|
105
|
+
| `unit` | no | 1–40, shown under the value ("People") |
|
|
106
|
+
| `callback` | yes | `{name, payload?}`. `name` is checked by `UNKNOWN_HOST_ACTION` against `stepperActions` when that is passed, otherwise against `hostActions` |
|
|
107
|
+
| `itemsTitle` | no | 1–80, a small heading above the items |
|
|
108
|
+
| `items` | no | 0–12 of `{label (1–80), amount, unit? (1–20), decimals? (0–3, default 0), round?: nearest or up (default nearest)}`. `decimals` below 0 or above 3 is `OUT_OF_RANGE` |
|
|
109
|
+
| `note` | no | 1–280, muted under the items |
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
- type: stepper
|
|
113
|
+
id: guests
|
|
114
|
+
label: Number of people
|
|
115
|
+
unit: People
|
|
116
|
+
value: 5
|
|
117
|
+
min: 1
|
|
118
|
+
max: 20
|
|
119
|
+
callback: {name: scaleStepper}
|
|
120
|
+
items:
|
|
121
|
+
- {label: Bone-in leg of lamb, amount: 2, unit: kg, decimals: 1}
|
|
122
|
+
- {label: Carrots, amount: 8, round: up}
|
|
123
|
+
note: Generous portions, with a little extra.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The renderer uses the element ids `<id>_decrease` and `<id>_increase` for the − and + buttons
|
|
127
|
+
(`stepperElementIds(id)`). Another block or action element with either id is `DUPLICATE_ID`.
|
|
128
|
+
A tap sends `{kind: callback, name, payload: {...payload, value: value ± step}}` with
|
|
129
|
+
`blockId: <id>`.
|
|
130
|
+
|
|
131
|
+
## Checklist
|
|
132
|
+
|
|
133
|
+
A `checklist` is a list of tickable items with an "n of m" counter. `BlocksView` draws the title
|
|
134
|
+
and counter, then one `CheckBox` row per item: the bold `text`, then the small muted `meta` (such
|
|
135
|
+
as a time), then the muted `detail`. With a `callback` whose name is in `hostActions` (or with `hostActions` omitted), a
|
|
136
|
+
tick calls `onAction` with `blockId: <id>`, `elementId: <id>_<item id>`, and
|
|
137
|
+
`{kind: "callback", name, payload: {...payload, itemId, checked, state}}`. `state` maps every
|
|
138
|
+
item id to its tick after this one. `BlocksView` disables the checklist while a tick is
|
|
139
|
+
pending, and the tick shows only when the host's returned checklist arrives as an override.
|
|
140
|
+
Without a `callback`, or when its name is not in `hostActions`, ticks stay on the device. On the
|
|
141
|
+
server, `toggleChecklistHostAction` from `@terreno/ai` handles the callback and returns the stored
|
|
142
|
+
checklist with `checked` from `state` (see [Host actions](ai.md#host-actions)).
|
|
143
|
+
|
|
144
|
+
| Field | Required | Rule |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `id` | yes | Block id pattern, at most 31 characters |
|
|
147
|
+
| `title` | no | 1–120 |
|
|
148
|
+
| `callback` | no | `{name, payload?}`. `name` is checked by `UNKNOWN_HOST_ACTION` against `checklistActions` when that is passed, otherwise against `hostActions` |
|
|
149
|
+
| `items` | yes | 1–30 of `{id, text (1–120), detail? (1–280), meta? (1–40), checked?: boolean}`. An item `id` uses the block id pattern, at most 32 characters, and is unique in the checklist (`DUPLICATE_ID`) |
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
- type: checklist
|
|
153
|
+
id: cooking
|
|
154
|
+
title: Cooking checklist
|
|
155
|
+
callback: {name: toggleChecklist}
|
|
156
|
+
items:
|
|
157
|
+
- {id: oven, meta: "1:00 pm", text: Preheat the oven, detail: "220 C, fan off.", checked: true}
|
|
158
|
+
- {id: lamb_in, meta: "1:30 pm", text: Put the lamb in}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Each item's tick has the reserved element id `<id>_<item id>` (`checklistElementId(id, itemId)`).
|
|
162
|
+
Another block or action element with that id, or a stepper or checklist that derives the same
|
|
163
|
+
element id, is `DUPLICATE_ID`.
|
|
164
|
+
|
|
165
|
+
## Gallery
|
|
166
|
+
|
|
167
|
+
A `gallery` is a set of 2–6 photos shown together. `BlocksView` draws up to three in one row of
|
|
168
|
+
equal 4:3 tiles and wraps more into a three-column grid. On a narrow screen the tiles keep a
|
|
169
|
+
minimum width and the row scrolls sideways (see [BlocksView](ui.md#blocksview)).
|
|
170
|
+
|
|
171
|
+
| Field | Required | Rule |
|
|
172
|
+
| --- | --- | --- |
|
|
173
|
+
| `id` | no | Block id pattern |
|
|
174
|
+
| `images` | yes | 2–6 of `{src, alt (1–200), caption? (1–120)}`. Fewer is `TOO_FEW`, more is `TOO_MANY` |
|
|
175
|
+
|
|
176
|
+
Each tile's `src` follows the `image` rules above. A tile that breaks them is
|
|
177
|
+
`IMAGE_HOST_NOT_ALLOWED` at that tile's path, such as `blocks[0].images[2].src`.
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
- type: gallery
|
|
181
|
+
id: roast_photos
|
|
182
|
+
images:
|
|
183
|
+
- {src: "file:roast-lamb", alt: Roast leg of lamb on a carving board, caption: Roast lamb}
|
|
184
|
+
- {src: "file:roast-potatoes", alt: Crisp roast potatoes in a tray}
|
|
185
|
+
- {src: "https://images.example.com/carrots.jpg", alt: Glazed carrots, caption: "Honey, thyme, and butter"}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The third tile validates only when `imageHosts` includes `images.example.com`.
|
|
189
|
+
|
|
190
|
+
## List
|
|
191
|
+
|
|
192
|
+
A `list` is a stack of 1–12 entries, each with a title and an optional thumbnail. `BlocksView`
|
|
193
|
+
draws each item as a row: a 3:4 portrait thumbnail on the left, then the small muted `meta`, the
|
|
194
|
+
bold `title`, and the muted `text` (see [BlocksView](ui.md#blocksview)).
|
|
195
|
+
|
|
196
|
+
| Field | Required | Rule |
|
|
197
|
+
| --- | --- | --- |
|
|
198
|
+
| `id` | no | Block id pattern |
|
|
199
|
+
| `items` | yes | 1–12 items. Fewer is `TOO_FEW`, more is `TOO_MANY` |
|
|
200
|
+
| `items[].title` | yes | 1–120 characters |
|
|
201
|
+
| `items[].text` | no | 1–500 characters of plain text, not markdown |
|
|
202
|
+
| `items[].meta` | no | 1–40 characters, a short label such as a time or a price |
|
|
203
|
+
| `items[].image` | no | `{src, alt}`. `alt` is 1–200 characters, the same cap as an `image` block's `alt` (`BLOCK_LIMITS.headingTextMaxLength`) |
|
|
204
|
+
|
|
205
|
+
An item image's `src` follows the `image` rules above. One that breaks them is
|
|
206
|
+
`IMAGE_HOST_NOT_ALLOWED` at that item's path, such as `blocks[0].items[1].image.src`.
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
- type: list
|
|
210
|
+
id: menu
|
|
211
|
+
items:
|
|
212
|
+
- title: Roast leg of lamb
|
|
213
|
+
text: Rubbed with garlic and rosemary, then rested for 20 minutes.
|
|
214
|
+
meta: Main
|
|
215
|
+
image: {src: "file:roast-lamb", alt: Roast leg of lamb on a carving board}
|
|
216
|
+
- title: Honey carrots
|
|
217
|
+
text: "Glazed with honey, thyme, and butter."
|
|
218
|
+
image: {src: "https://images.example.com/carrots.jpg", alt: Glazed carrots}
|
|
219
|
+
- title: Mint sauce
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The second item validates only when `imageHosts` includes `images.example.com`.
|
|
223
|
+
|
|
86
224
|
## Actions
|
|
87
225
|
|
|
88
226
|
An `actions` block requires `id` and `elements` (1–25). An element is a `button` or a
|
|
@@ -94,10 +232,76 @@ An `actions` block requires `id` and `elements` (1–25). An element is a `butto
|
|
|
94
232
|
| `segmented` | `id`, `target` (a chart or table `id`), `options` (2–8 of `{label, data}`) | — |
|
|
95
233
|
|
|
96
234
|
`action.kind` is `reply` (`text`), `open` (`url` or `route`, exactly one), `select`
|
|
97
|
-
(`target` plus `data`),
|
|
98
|
-
that is not a chart or table `id`
|
|
235
|
+
(`target` plus `data`), `callback` (`name` plus optional `payload`), or `copy` (`text` or
|
|
236
|
+
`target`, exactly one; see [Copy](#copy)). A `select` target that is not a chart or table `id`
|
|
237
|
+
is `SELECT_TARGET_INVALID`. When `validateBlocks` is
|
|
99
238
|
called with `hostActions`, a callback `name` outside that list is `UNKNOWN_HOST_ACTION`.
|
|
100
|
-
Omitting `hostActions` skips that check.
|
|
239
|
+
Omitting `hostActions` skips that check. A stepper `callback.name` is checked against
|
|
240
|
+
`stepperActions` instead when that list is passed, so a stepper that names a registered action
|
|
241
|
+
that does not handle steppers is `UNKNOWN_HOST_ACTION`. `checklistActions` does the same for
|
|
242
|
+
checklist callbacks.
|
|
243
|
+
|
|
244
|
+
## Copy
|
|
245
|
+
|
|
246
|
+
A `copy` action puts text on the device clipboard. It never reaches the host, so `hostActions`
|
|
247
|
+
does not apply to it. It is a `button` element's `action`:
|
|
248
|
+
|
|
249
|
+
| Field | Rule |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `text` | 1–4,000 characters (`BLOCK_LIMITS.copyTextMaxLength`), copied as written |
|
|
252
|
+
| `target` | The `id` of a `stepper`, `checklist`, `list`, `table`, or `text` block in this document (`COPY_TARGET_TYPES`). Any other block, or an id no block has, is `COPY_TARGET_INVALID` |
|
|
253
|
+
|
|
254
|
+
Set exactly one of the two. Neither or both is `MISSING_REQUIRED` at the action's path, as for
|
|
255
|
+
`open`. The target may sit anywhere in the document, before or after the button, inside a
|
|
256
|
+
`card` or `columns`.
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
v: 1
|
|
260
|
+
blocks:
|
|
261
|
+
- type: stepper
|
|
262
|
+
id: guests
|
|
263
|
+
label: Number of people
|
|
264
|
+
unit: People
|
|
265
|
+
value: 5
|
|
266
|
+
min: 1
|
|
267
|
+
max: 20
|
|
268
|
+
callback: {name: scaleStepper}
|
|
269
|
+
items:
|
|
270
|
+
- {label: Bone-in leg of lamb, amount: 2, unit: kg, decimals: 1}
|
|
271
|
+
- {label: Carrots, amount: 8}
|
|
272
|
+
- type: actions
|
|
273
|
+
id: shopping
|
|
274
|
+
elements:
|
|
275
|
+
- type: button
|
|
276
|
+
id: copy_list
|
|
277
|
+
text: Copy shopping list
|
|
278
|
+
variant: outline
|
|
279
|
+
action: {kind: copy, target: guests}
|
|
280
|
+
- type: button
|
|
281
|
+
id: copy_note
|
|
282
|
+
text: Copy note
|
|
283
|
+
action: {kind: copy, text: "Lamb 2 kg, carrots 8, mint sauce."}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`blockPlainText(block, {datasets?, checked?})` turns a target block into the copied text. Lines
|
|
287
|
+
are joined with `\n`, with no trailing newline.
|
|
288
|
+
|
|
289
|
+
| Block | Text |
|
|
290
|
+
| --- | --- |
|
|
291
|
+
| `stepper` | `<label>: <value> <unit>`, then one `<item label>: <amount> <unit>` line per item. Amounts use the item's `decimals` (2 at 1 decimal is `2.0`). A missing unit leaves no trailing space. For the example above: `Number of people: 5 People`, `Bone-in leg of lamb: 2.0 kg`, `Carrots: 8` |
|
|
292
|
+
| `checklist` | One `[x] <text>` or `[ ] <text>` line per item. `checked` maps item ids to the current ticks and overrides each item's `checked`; ids the checklist lacks are ignored |
|
|
293
|
+
| `list` | One `- <title>: <text>` line per item, or `- <title>` when the item has no text |
|
|
294
|
+
| `table` | A header row of column names (the table's `columns` in order, or every dataset column) with each `_` shown as a space, as the table on screen shows them (`lamb_kg` is `lamb kg`), then one row per dataset row, tab-separated. A `null` cell is empty. The table's dataset must be inline in `datasets`; a missing dataset or an unresolved `ref` returns an empty string, so resolve refs first |
|
|
295
|
+
| `text` | The `markdown` as written |
|
|
296
|
+
|
|
297
|
+
Any other block returns an empty string. Pass the block as it is shown, with a host's
|
|
298
|
+
replacement block applied, so a stepper at 6 copies the scaled amounts.
|
|
299
|
+
|
|
300
|
+
`BlocksView` performs the copy on the device. A press builds the text with `blockPlainText`
|
|
301
|
+
from the target as it is shown now (the host's override, the device's checklist ticks, and the
|
|
302
|
+
table's selected dataset once it has rows), writes it with `expo-clipboard`, and shows "Copied"
|
|
303
|
+
next to the button for 2 seconds. It never calls `onAction`. See
|
|
304
|
+
[BlocksView](./ui.md#blocksview).
|
|
101
305
|
|
|
102
306
|
## Partial parsing
|
|
103
307
|
|
|
@@ -122,6 +326,21 @@ Unknown fields fail with `UNKNOWN_KEY`. `v` must be `1` (`UNSUPPORTED_VERSION`).
|
|
|
122
326
|
| `columns` children | 2–4 |
|
|
123
327
|
| Elements in one `actions` block | 25 |
|
|
124
328
|
| Text in one `text` block | 4,000 characters |
|
|
329
|
+
| Items in one `stepper` | 12 |
|
|
330
|
+
| `stepper` id | 54 characters |
|
|
331
|
+
| Items in one `checklist` | 1–30 |
|
|
332
|
+
| `checklist` id | 31 characters |
|
|
333
|
+
| `checklist` item id | 32 characters |
|
|
334
|
+
| Images in one `gallery` | 2–6 |
|
|
335
|
+
| `gallery` image `alt` | 200 characters |
|
|
336
|
+
| `gallery` image `caption` | 120 characters |
|
|
337
|
+
| Items in one `list` | 1–12 |
|
|
338
|
+
| `list` item `title` | 120 characters |
|
|
339
|
+
| `list` item `text` | 500 characters |
|
|
340
|
+
| `list` item `meta` | 40 characters |
|
|
341
|
+
| `list` item image `alt` | 200 characters |
|
|
342
|
+
| `card` `eyebrow` | 60 characters |
|
|
343
|
+
| `copy` action `text` | 4,000 characters |
|
|
125
344
|
| Document version | 1 |
|
|
126
345
|
|
|
127
346
|
## Errors
|
|
@@ -134,19 +353,21 @@ heuristics and does not fail `ok`.
|
|
|
134
353
|
| --- | --- |
|
|
135
354
|
| `COLUMN_NOT_FOUND` | A chart or table names a column the dataset does not have. |
|
|
136
355
|
| `COLUMN_TYPE_MISMATCH` | A column value, or a chart axis, does not match the column type. |
|
|
356
|
+
| `COPY_TARGET_INVALID` | A copy action's `target` names a block that is not a `stepper`, `checklist`, `list`, `table`, or `text` block, or an id no block in the document has. |
|
|
137
357
|
| `DATASET_NOT_FOUND` | A chart or table names a dataset the document does not define. |
|
|
138
358
|
| `DATASET_TOO_LARGE` | A dataset has more than 500 rows or 12 columns. |
|
|
139
359
|
| `DEPTH_EXCEEDED` | A `columns` or `card` block is nested inside another layout block. |
|
|
140
|
-
| `DUPLICATE_ID` | A block id, an action-element id, or a column name is used more than once. |
|
|
360
|
+
| `DUPLICATE_ID` | A block id, an action-element id, or a column name is used more than once, an id takes a stepper's `<id>_decrease` or `<id>_increase` or a checklist's `<id>_<item id>`, or a checklist repeats an item id. |
|
|
141
361
|
| `HTML_DISABLED` | An html block is present and this host has not turned HTML on. |
|
|
142
362
|
| `HTML_TOO_LARGE` | An html block is larger than 100,000 bytes. |
|
|
143
|
-
| `IMAGE_HOST_NOT_ALLOWED` | An image URL is not a `data:image` URL, a `file:` ref, or an `https` URL on an allowed host. |
|
|
363
|
+
| `IMAGE_HOST_NOT_ALLOWED` | An `image`, `gallery` tile, or `list` item image URL is not a `data:image` URL, a `file:` ref, or an `https` URL on an allowed host. A gallery error names the tile (`blocks[0].images[2].src`); a list error names the item (`blocks[0].items[1].image.src`). |
|
|
144
364
|
| `INVALID_ENUM` | A value is not one of the allowed values. |
|
|
145
365
|
| `INVALID_FORMAT` | A string does not match its required format. |
|
|
146
|
-
| `INVALID_TYPE` | A value has the wrong type, including a
|
|
366
|
+
| `INVALID_TYPE` | A value has the wrong type, including a ref `limit` below 1. |
|
|
147
367
|
| `KEY_ORDER` | Top-level keys are not in the order `v`, `datasets`, `blocks`. |
|
|
148
|
-
| `MISSING_REQUIRED` | A required field is missing. |
|
|
368
|
+
| `MISSING_REQUIRED` | A required field is missing, or an `open` or `copy` action sets neither or both of its two fields. |
|
|
149
369
|
| `NOT_A_DOCUMENT` | The reply is not one YAML or JSON mapping with a `v` field. |
|
|
370
|
+
| `OUT_OF_RANGE` | A number is outside its allowed range: a stepper `value` outside `min` and `max`, `min` not below `max`, `step` at or below 0, or `decimals` below 0 or above 3. |
|
|
150
371
|
| `SELECT_TARGET_INVALID` | A select action names a block that is not a chart or table. |
|
|
151
372
|
| `ROW_ARITY_MISMATCH` | A dataset row does not have one value per column. |
|
|
152
373
|
| `TABLE_TOO_WIDE` | A table lists more than 12 columns. |
|
|
@@ -167,12 +388,23 @@ heuristics and does not fail `ok`.
|
|
|
167
388
|
| --- | --- |
|
|
168
389
|
| `parseBlocks(text)` | Fence strip, YAML or JSON parse |
|
|
169
390
|
| `parseBlocksPartial(text)` | Completed top-level blocks while a reply is still streaming |
|
|
170
|
-
| `validateBlocks(doc, options?)` | Structure, then dataset, chart, table, action, and html lint. `options.knownDatasets` checks `ref` columns. `options.hostActions` checks callback names. `options.allowHtml` allows `html` blocks. |
|
|
391
|
+
| `validateBlocks(doc, options?)` | Structure, then dataset, chart, table, action, stepper, checklist, image, gallery, and list image src, and html lint. `options.knownDatasets` checks `ref` columns. `options.hostActions` checks callback names. `options.stepperActions` and `options.checklistActions`, when set, check stepper and checklist `callback.name` instead (`UNKNOWN_HOST_ACTION`). `options.allowHtml` allows `html` blocks. |
|
|
171
392
|
| `wrapAsTextDocument(text)` | Display fallback for a non-document |
|
|
172
393
|
| `blocksSchema` | Zod schema |
|
|
173
394
|
| `blocksJsonSchema` | JSON Schema for the same structure |
|
|
174
|
-
| `blocksPromptSection({hostActions, allowHtml})` | System-prompt section. Limits come from `BLOCK_LIMITS`. `allowHtml` adds the `html` block. |
|
|
395
|
+
| `blocksPromptSection({hostActions, allowHtml, imageHosts, richBlocks, stepperActions, checklistActions})` | System-prompt section. Limits come from `BLOCK_LIMITS`. `allowHtml` adds the `html` block. `richBlocks` (default `true`) offers the rich blocks; `false` returns the prompt as it was before them, whatever the other lists hold. With `richBlocks` on and a non-empty `stepperActions`, the prompt adds `stepper`, its rules and limits, and names those actions as its callbacks; without one it never mentions `stepper`. With `richBlocks` on, the prompt adds `checklist`, `gallery`, and `list` with their limits, plus the `card` `eyebrow` and its limit; gallery tile and list item image srcs follow the same image src line as `image`, so `https` is offered only with `imageHosts`. The word `list` appears in the prompt only as this block's name. A non-empty `checklistActions` is named as the callback to set; without one the prompt says to leave `callback` out, so ticks stay local. With `richBlocks` on, one line offers the `copy` action with its text limit and its target block types, naming `stepper` only when the stepper is offered. |
|
|
175
396
|
| `BLOCK_LIMITS` | The numbers in the table above |
|
|
176
397
|
| `BLOCK_ERROR_CODES` | The codes in the table above |
|
|
177
398
|
| `BLOCK_WARNING_CODES` | `BAR_TOO_MANY_CATEGORIES`, `DONUT_TOO_MANY_SLICES`, `LINE_SINGLE_POINT` |
|
|
399
|
+
| `stepperElementIds(id)` | `{decrease, increase}`: the element ids of a stepper's buttons |
|
|
400
|
+
| `checklistElementId(id, itemId)` | `<id>_<item id>`: the element id of a checklist item's tick. `ChecklistBlock`, `ChecklistItem`, and `ChecklistCallback` are type exports. |
|
|
401
|
+
| `scaleStepperBlock(original, value)` | The stepper at `value` with each item's `amount` scaled linearly from `original` (`amount × value / original value`), rounded to its `decimals` (`round: up` rounds up). An `original` with `value: 0` keeps its amounts. Always pass the block the agent wrote, so rounding does not drift. `scaleStepperHostAction` and the demo playground use it |
|
|
402
|
+
| `isStepperValueAllowed(stepper, value)` | `true` when `value` is finite, within `min` and `max`, and a whole number of `step`s from the stepper's `value` |
|
|
403
|
+
| `applyChecklistState(checklist, {itemId, checked, state})` | The checklist with each item's `checked` from `state`; an item missing from `state` is unchecked and `itemId` takes `checked`. `toggleChecklistHostAction` and the demo playground use it. `ChecklistTick` is a type export |
|
|
404
|
+
| `unknownChecklistItemIds(checklist, tick)` | The ids in a tick's `itemId` and `state` that the checklist has no item for, each once |
|
|
405
|
+
| `blockPlainText(block, {datasets?, checked?})` | The text a copy action copies for its target block (see [Copy](#copy)). `BlockPlainTextOptions` is a type export |
|
|
406
|
+
| `COPY_TARGET_TYPES` | `stepper`, `checklist`, `list`, `table`, `text`: the blocks a copy `target` may name. `CopyAction` is a type export |
|
|
407
|
+
| `GalleryBlock`, `GalleryImage` | Type exports for the `gallery` block and its tiles |
|
|
408
|
+
| `ListBlock`, `ListItem`, `ListItemImage` | Type exports for the `list` block, its items, and their thumbnails |
|
|
409
|
+
| `STEPPER_ROUNDING` | `nearest`, `up`. `StepperBlock`, `StepperItem`, and `StepperCallback` are type exports. |
|
|
178
410
|
| `HTML_HEIGHTS`, `CALLOUT_STATUSES` | Allowed `html` heights and `callout` statuses. `HtmlBlock`, `CalloutBlock`, `ImageBlock`, and `DetailsBlock` are type exports. |
|
|
@@ -136,9 +136,12 @@ Resolution order for API base URL (`rtk/src/constants.ts`):
|
|
|
136
136
|
| Variable | Read by | Required | Default | Secret | Scope |
|
|
137
137
|
|----------|---------|----------|---------|--------|-------|
|
|
138
138
|
| `GCS_BUCKET` | example-backend, admin | ❌ | — | No | server |
|
|
139
|
+
| `GCS_FOLDER_PREFIX` | example-backend `DocumentStorageApp` (`folderPrefix`) | ❌ | — | No | server |
|
|
139
140
|
| `GCS_PROJECT_ID` | GCS clients | ❌ | — | No | server |
|
|
140
141
|
| `GCS_SERVICE_ACCOUNT_KEY` | GCS clients | ❌ | — | Yes | server |
|
|
141
142
|
|
|
143
|
+
Example-backend Cloud Run deploys set `GCS_BUCKET` (default `flourish-terreno-example-documents`). Preview deploys also set `GCS_FOLDER_PREFIX=pr-<number>/`.
|
|
144
|
+
|
|
142
145
|
## Feature flags
|
|
143
146
|
|
|
144
147
|
| Variable | Read by | Required | Default | Secret | Scope |
|
|
@@ -185,6 +185,7 @@ be passed to `betterAuthAdapter` directly.
|
|
|
185
185
|
| `wipeOnSignOut` | `boolean` | `false` | `signOut()` also wipes local data |
|
|
186
186
|
| `batchSize` | `number` | `50` (server caps at 100) | Max mutations per batched drain send |
|
|
187
187
|
| `haltQueueOnConflict` | `boolean` | `false` | `true` = conflict halts entire drain; `false` = per-entity blocking |
|
|
188
|
+
| `snapshotConcurrency` | `number` | `6` | Streams snapshot-paged at once by reconcile, subscribe catch-up, `forceResync` and wipe re-bootstrap. Each stream runs one pass at a time. `1` pages one stream at a time |
|
|
188
189
|
| `onDecryptFailure` | `() => void` | wipe + re-bootstrap (with `console.warn`) | Override web decrypt-failure recovery |
|
|
189
190
|
| `tombstoneRetentionMs` | `number` | `7776000000` (90 days); `0` disables | Client tombstone compaction after successful reconcile |
|
|
190
191
|
| `startAuthRetryAttempts` | `number` | `3`; `1` disables | `start()` attempts to resolve `getUserId()` |
|