@terreno/mcp 57.7.0 → 57.9.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/resources/ui.md +5 -4
- package/dist/docs/upgrades/57.8.0.md +61 -0
- package/dist/docs/upgrades/57.9.0.md +28 -0
- package/dist/docs/versioned/next/explanation/charts.md +12 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +2 -1
- package/dist/docs/versioned/next/explanation/model-router-actions.md +1 -1
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +34 -0
- package/dist/docs/versioned/next/how-to/README.md +1 -1
- package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +1 -1
- package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +5 -0
- package/dist/docs/versioned/next/how-to/circleci.md +30 -14
- package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +8 -0
- package/dist/docs/versioned/next/how-to/install-agent-skills.md +1 -1
- package/dist/docs/versioned/next/how-to/preview-demo.md +19 -0
- package/dist/docs/versioned/next/how-to/product-announcements.md +3 -0
- package/dist/docs/versioned/next/implementationPlans/ai-observability.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +3 -2
- package/dist/docs/versioned/next/implementationPlans/dashboard-chart-parity.md +275 -0
- package/dist/docs/versioned/next/implementationPlans/demo-workshop.md +219 -0
- package/dist/docs/versioned/next/reference/ai.md +41 -10
- package/dist/docs/versioned/next/reference/announcement-release-packs.md +146 -0
- package/dist/docs/versioned/next/reference/announcements.md +7 -3
- package/dist/docs/versioned/next/reference/api.md +1 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +2 -1
- package/dist/docs/versioned/next/reference/feature-flags.md +1 -1
- package/dist/docs/versioned/next/reference/ui.md +108 -17
- package/dist/docs/versioned/next/tasks/dashboard-chart-parity.md +145 -0
- package/dist/docs/versioned/next/tasks/demo-workshop.md +150 -0
- package/package.json +2 -2
- package/src/docs/resources/ui.md +5 -4
- package/src/docs/upgrades/57.8.0.md +61 -0
- package/src/docs/upgrades/57.9.0.md +28 -0
- package/src/docs/versioned/next/explanation/charts.md +12 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +2 -1
- package/src/docs/versioned/next/explanation/model-router-actions.md +1 -1
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +34 -0
- package/src/docs/versioned/next/how-to/README.md +1 -1
- package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +1 -1
- package/src/docs/versioned/next/how-to/charts-and-dashboards.md +5 -0
- package/src/docs/versioned/next/how-to/circleci.md +30 -14
- package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +8 -0
- package/src/docs/versioned/next/how-to/install-agent-skills.md +1 -1
- package/src/docs/versioned/next/how-to/preview-demo.md +19 -0
- package/src/docs/versioned/next/how-to/product-announcements.md +3 -0
- package/src/docs/versioned/next/implementationPlans/ai-observability.md +1 -1
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
- package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +3 -2
- package/src/docs/versioned/next/implementationPlans/dashboard-chart-parity.md +275 -0
- package/src/docs/versioned/next/implementationPlans/demo-workshop.md +219 -0
- package/src/docs/versioned/next/reference/ai.md +41 -10
- package/src/docs/versioned/next/reference/announcement-release-packs.md +146 -0
- package/src/docs/versioned/next/reference/announcements.md +7 -3
- package/src/docs/versioned/next/reference/api.md +1 -0
- package/src/docs/versioned/next/reference/environment-variables.md +2 -1
- package/src/docs/versioned/next/reference/feature-flags.md +1 -1
- package/src/docs/versioned/next/reference/ui.md +108 -17
- package/src/docs/versioned/next/tasks/dashboard-chart-parity.md +145 -0
- package/src/docs/versioned/next/tasks/demo-workshop.md +150 -0
- package/dist/docs/ui-types-documentation.json +0 -33
- package/src/docs/ui-types-documentation.json +0 -33
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Implementation Plan: Dashboard chart parity
|
|
2
|
+
|
|
3
|
+
**Status:** Approved — 2026-09-25
|
|
4
|
+
**Branch:** `cursor/dashboard-chart-parity-8a50`
|
|
5
|
+
**Owner:** —
|
|
6
|
+
**Created:** 2026-09-24
|
|
7
|
+
**Roadmap issue:** — (create via `roadmap-item` after this IP is Approved; follow-up to [#1175](https://github.com/TerrenoLabs/terreno/issues/1175))
|
|
8
|
+
**Task list:** [dashboard-chart-parity.md](../tasks/dashboard-chart-parity.md)
|
|
9
|
+
**Depends on:** [charts-and-dashboards.md](charts-and-dashboards.md) (shipped Phases 1–5)
|
|
10
|
+
**RTK deprecation flag:** None
|
|
11
|
+
**Program:** [B2B platform](b2b-platform-program.md) track E
|
|
12
|
+
|
|
13
|
+
## Goal
|
|
14
|
+
|
|
15
|
+
Make `@terreno/ui` able to compose an ops dashboard like the “How’s it going?”
|
|
16
|
+
reference: KPI scorecards with comparison sparklines, titled chart cards with period
|
|
17
|
+
badges, multi-series lines, donut hole labels, spanning grids, and Looker-like table
|
|
18
|
+
chrome — without copying Looker paint, and without requiring the Chart children grammar.
|
|
19
|
+
|
|
20
|
+
App authors rebuild that page from first-class primitives (`Scorecard`, `SparklineChart`,
|
|
21
|
+
`ChartCard`, spanning `DashboardGrid`, additive chart facades, `DataTable` extras). Proof
|
|
22
|
+
is a demo gallery fixture of the reference layout plus rendered PNG goldens.
|
|
23
|
+
|
|
24
|
+
## Non-Goals
|
|
25
|
+
|
|
26
|
+
- Pixel-identical Looker colors, fonts, or screenshot matching of the PDF JPEG.
|
|
27
|
+
- Shipping a date-range picker. Period badges may call `onPeriodPress`.
|
|
28
|
+
- Variable `DataTable` row height (would break virtualization).
|
|
29
|
+
- Dual-period numbers inside one table cell (current + previous stacked in the same cell).
|
|
30
|
+
- Replacing `{label, value}[]` facades with composition-only `Chart*` children.
|
|
31
|
+
- Implementing the Phase 6 composition grammar in this slice (still future work on
|
|
32
|
+
`charts-and-dashboards`).
|
|
33
|
+
- Example-frontend screen, comms dashboard wiring, zoom/pan, combo charts, maps, heatmaps.
|
|
34
|
+
- Cross-chart filtering / drill-down.
|
|
35
|
+
|
|
36
|
+
## Decisions
|
|
37
|
+
|
|
38
|
+
| Question | Decision |
|
|
39
|
+
|----------|----------|
|
|
40
|
+
| Scope | All chart surfaces, headers, period badges, KPI cards, spanning grid, and the table chrome named below. Not the whole PDF pixel-for-pixel. |
|
|
41
|
+
| Easy-build surface | `ChartCard` (title + period badge + child) + `Scorecard` + spanning `DashboardGrid`. |
|
|
42
|
+
| Scorecards | Dedicated `Scorecard` facade plus reusable `SparklineChart`. |
|
|
43
|
+
| Comparison | First-class `comparisonData` with dotted stroke on sparkline and optional cartesian overlay. |
|
|
44
|
+
| Facades vs grammar | Keep `LineChart` / `BarChart` / `AreaChart` / `DonutChart`. Add additive props. This dashboard must not require a composition tree. |
|
|
45
|
+
| Visual language | Capabilities are theme-configurable. Default paint stays Terreno tokens, not a Looker preset. |
|
|
46
|
+
| Responsive grid | Configurable `span` on items. Reference fixture: KPI row 5→2→1; lower chart row 3→1. |
|
|
47
|
+
| Tables | Same IP. After the chart tracer: period header groups, sticky totals row, `1–50 of N` range status. Reuse existing pin/sort/pagination. |
|
|
48
|
+
| Wrapped names | Wrap up to two lines inside the existing `rowHeight`. Keep virtualization. |
|
|
49
|
+
| Period badge | Label plus optional `onPress`. No picker in this slice. |
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
DashboardGrid
|
|
55
|
+
DashboardGridItem span={{sm,md,lg}}
|
|
56
|
+
ChartCard title periodLabel onPeriodPress?
|
|
57
|
+
Scorecard | LineChart | BarChart | DonutChart | DataTable
|
|
58
|
+
Scorecard
|
|
59
|
+
SparklineChart (plot only; solid + dotted comparison)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Existing facades stay the simple path. `ChartCard` is chrome around any child (chart or
|
|
63
|
+
table). Facade `title` / `periodLabel` / `onPeriodPress` are shortcuts that render the
|
|
64
|
+
same header inside the chart; callers use **either** `ChartCard` **or** those props, not
|
|
65
|
+
both.
|
|
66
|
+
|
|
67
|
+
### Public props (additive)
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface ChartSeries {
|
|
71
|
+
color?: string;
|
|
72
|
+
data: Array<{color?: string; label: string; value: number}>;
|
|
73
|
+
id: string;
|
|
74
|
+
label: string;
|
|
75
|
+
stroke?: "solid" | "dotted";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface CartesianChartProps {
|
|
79
|
+
comparisonData?: Array<{label: string; value: number}>;
|
|
80
|
+
data: Array<{color?: string; label: string; value: number}>;
|
|
81
|
+
formatValue?: (value: number) => string;
|
|
82
|
+
onPeriodPress?: () => void;
|
|
83
|
+
periodLabel?: string;
|
|
84
|
+
series?: ChartSeries[];
|
|
85
|
+
title?: string;
|
|
86
|
+
xTickPolicy?: "auto" | "truncate" | "rotate";
|
|
87
|
+
// existing: height, legendLabel, emptyText, loading, accessibilityLabel, testID
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface DonutChartProps extends CartesianChartProps {
|
|
91
|
+
centerTitle?: string;
|
|
92
|
+
centerValue?: string;
|
|
93
|
+
formatShare?: (value: number, total: number) => string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface SparklineChartProps {
|
|
97
|
+
comparisonData?: Array<{label: string; value: number}>;
|
|
98
|
+
data: Array<{label: string; value: number}>;
|
|
99
|
+
height?: number;
|
|
100
|
+
testID?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface ScorecardProps {
|
|
104
|
+
comparisonData?: Array<{label: string; value: number}>;
|
|
105
|
+
formatValue?: (value: number) => string;
|
|
106
|
+
onPeriodPress?: () => void;
|
|
107
|
+
periodLabel?: string;
|
|
108
|
+
sparklineData?: Array<{label: string; value: number}>;
|
|
109
|
+
title: string;
|
|
110
|
+
value: number | string;
|
|
111
|
+
testID?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface ChartCardProps {
|
|
115
|
+
children: React.ReactNode;
|
|
116
|
+
filterSummary?: string;
|
|
117
|
+
onPeriodPress?: () => void;
|
|
118
|
+
periodLabel?: string;
|
|
119
|
+
title: string;
|
|
120
|
+
testID?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface DashboardGridItemProps {
|
|
124
|
+
children: React.ReactNode;
|
|
125
|
+
span?: {lg?: number; md?: number; sm?: number};
|
|
126
|
+
testID?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface DataTableProps {
|
|
130
|
+
footerRows?: DataTableCellData[][];
|
|
131
|
+
headerGroups?: Array<{columnCount: number; subtitle?: string; title: string}>;
|
|
132
|
+
rangeStart?: number;
|
|
133
|
+
rangeTotal?: number;
|
|
134
|
+
wrapLines?: 1 | 2; // default 1; 2 = wrap inside rowHeight
|
|
135
|
+
// existing page / setPage / totalPages remain; range copy uses page size math
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`series` when present is the cartesian data source (`data` is ignored except as a
|
|
140
|
+
single-series fallback). `comparisonData` aligns on `label` and paints a dotted stroke
|
|
141
|
+
(sparkline/line) or a lighter overlay (bar — if labels match). Default `xTickPolicy` is
|
|
142
|
+
`"auto"`: rotate ~45° when labels would truncate; keep horizontal for ≤7 categorical
|
|
143
|
+
labels.
|
|
144
|
+
|
|
145
|
+
Donut legend rows become `swatch + label + formatShare` (default percent of total).
|
|
146
|
+
`centerTitle` / `centerValue` sit in the hole via overlay `Text`.
|
|
147
|
+
|
|
148
|
+
`DashboardGridItem` span is in column units of the current breakpoint count (span 5 in a
|
|
149
|
+
5-column KPI row is full width on `sm` when `columns.sm === 1`).
|
|
150
|
+
|
|
151
|
+
`SparklineChart` draws no axes, grid, legend, or reserved tooltip row. Hit targets stay
|
|
152
|
+
≥24px when press/hover is wired; default is non-interactive (KPI sparkline).
|
|
153
|
+
|
|
154
|
+
`headerGroups` paint a second header row above `columns` (period title + optional
|
|
155
|
+
subtitle such as `(Previous period)`). `footerRows` stick to the bottom of the scroll
|
|
156
|
+
body. Range status is `rangeStart–rangeEnd of rangeTotal` next to existing pagination.
|
|
157
|
+
|
|
158
|
+
### File layout
|
|
159
|
+
|
|
160
|
+
| Path | Role |
|
|
161
|
+
|------|------|
|
|
162
|
+
| `ui/src/SparklineChart.tsx` | Public sparkline |
|
|
163
|
+
| `ui/src/Scorecard.tsx` | Public KPI tile |
|
|
164
|
+
| `ui/src/ChartCard.tsx` | Public title + period badge chrome |
|
|
165
|
+
| `ui/src/DashboardGridItem.tsx` | Public span wrapper (or named export from `DashboardGrid.tsx` — no barrel) |
|
|
166
|
+
| `ui/src/LineChart.tsx` etc. | Additive `series`, `comparisonData`, ticks, header shortcuts |
|
|
167
|
+
| `ui/src/DonutChart.tsx` | Center stack + share legend |
|
|
168
|
+
| `ui/src/DataTable.tsx` | Header groups, footer rows, range status, wrapLines |
|
|
169
|
+
| `ui/src/Pagination.tsx` | Range copy when totals provided |
|
|
170
|
+
| `demo/chartVisual/fixtureCatalog.ts` | New hard fixtures including `hows-it-going-dashboard` |
|
|
171
|
+
| `docs/how-to/charts-and-dashboards.md` | Rewrite around Scorecard + ChartCard + spans |
|
|
172
|
+
|
|
173
|
+
`DashboardGrid` stays eager. New chart widgets join `heavyOptionalExports`. `ChartCard`
|
|
174
|
+
and `Scorecard` are layout+text heavy enough to sit with charts (lazy). `DashboardGridItem`
|
|
175
|
+
stays eager with the grid.
|
|
176
|
+
|
|
177
|
+
### Chart visual regression
|
|
178
|
+
|
|
179
|
+
Add fixtures that exercise the reference patterns (not the JPEG pixels):
|
|
180
|
+
|
|
181
|
+
| id | Pattern |
|
|
182
|
+
|----|---------|
|
|
183
|
+
| `scorecard-sparkline-comparison` | Five KPI tiles, solid + dotted sparkline |
|
|
184
|
+
| `bar-time-rotated-ticks` | Vertical bars, rotated dates, y `formatValue` |
|
|
185
|
+
| `line-three-series` | Three lines, markers, per-series legend |
|
|
186
|
+
| `bar-day-of-week` | Seven categorical bars, horizontal ticks |
|
|
187
|
+
| `donut-center-and-share` | Hole label + percent legend |
|
|
188
|
+
| `donut-single-slice` | One 100% slice + center total |
|
|
189
|
+
| `hows-it-going-dashboard` | Spanning grid: 5 KPIs, time bar, 3-line, DOW bar, two donuts, one table card |
|
|
190
|
+
|
|
191
|
+
`bun run ui:charts:compare` must cover these. Agents use `review-chart-visuals`.
|
|
192
|
+
|
|
193
|
+
## Models / APIs / Notifications
|
|
194
|
+
|
|
195
|
+
None.
|
|
196
|
+
|
|
197
|
+
## UI
|
|
198
|
+
|
|
199
|
+
Universal React Native + web. Demo stories + visual gallery only. Tables stay `DataTable`
|
|
200
|
+
inside `ChartCard`.
|
|
201
|
+
|
|
202
|
+
## Phases
|
|
203
|
+
|
|
204
|
+
1. **Tracer:** `SparklineChart` + `Scorecard` + `ChartCard` + `comparisonData` on the
|
|
205
|
+
sparkline; demo story; docs stub; golden for `scorecard-sparkline-comparison`.
|
|
206
|
+
2. **Cartesian additive:** `series`, `comparisonData`, `xTickPolicy`, header shortcuts on
|
|
207
|
+
line/bar/area; goldens for time bar and 3-line.
|
|
208
|
+
3. **Donut:** center stack + share legend; goldens for two-slice and 100% slice.
|
|
209
|
+
4. **Grid spans:** `DashboardGridItem`; reference KPI wrap; compose `hows-it-going-dashboard`
|
|
210
|
+
charts (table slot placeholder ok until Phase 5).
|
|
211
|
+
5. **Table chrome:** header groups, footer rows, range status, `wrapLines={2}`; table card
|
|
212
|
+
in the hard fixture.
|
|
213
|
+
6. **Packaging + docs:** lazy exports, import regression, how-to rewrite, explanation,
|
|
214
|
+
reference, TypeDoc, changelog, ui rule export list.
|
|
215
|
+
|
|
216
|
+
## Feature Flags & Migrations
|
|
217
|
+
|
|
218
|
+
None. Additive public props. Existing `{label, value}[]` callers stay valid. Not a major.
|
|
219
|
+
|
|
220
|
+
`legendLabel` remains for single-series cartesian. Multi-series uses `series[].label`.
|
|
221
|
+
Donut still ignores `legendLabel`.
|
|
222
|
+
|
|
223
|
+
## Activity Log & User Updates
|
|
224
|
+
|
|
225
|
+
None.
|
|
226
|
+
|
|
227
|
+
## Not Included / Future Work
|
|
228
|
+
|
|
229
|
+
- Chart children grammar (`Chart`, `ChartXAxis`, …) — still `charts-and-dashboards` Phase 6.
|
|
230
|
+
- Date-range picker control.
|
|
231
|
+
- Dual-period table cells.
|
|
232
|
+
- Variable row height.
|
|
233
|
+
- Example-frontend / comms wiring.
|
|
234
|
+
- Zoom/pan, combo charts.
|
|
235
|
+
|
|
236
|
+
## Files to Create / Modify
|
|
237
|
+
|
|
238
|
+
See the task list. Docs in-slice:
|
|
239
|
+
|
|
240
|
+
- `docs/how-to/charts-and-dashboards.md` (rewrite)
|
|
241
|
+
- `docs/explanation/charts.md` (multi-series, scorecard, why not grammar-first)
|
|
242
|
+
- `docs/reference/ui.md` (new exports)
|
|
243
|
+
- `docs/how-to/compare-chart-rendered-snapshots.md` (new fixture ids)
|
|
244
|
+
- `.rulesync/rules/ui/00-ui.mdc`
|
|
245
|
+
- `changelog/unreleased/dashboard-chart-parity.md`
|
|
246
|
+
- TypeDoc props (`bun run website:generate` in the docs task)
|
|
247
|
+
|
|
248
|
+
## Task List
|
|
249
|
+
|
|
250
|
+
[docs/tasks/dashboard-chart-parity.md](../tasks/dashboard-chart-parity.md)
|
|
251
|
+
|
|
252
|
+
## Acceptance Criteria
|
|
253
|
+
|
|
254
|
+
- [ ] `Scorecard`, `SparklineChart`, and `ChartCard` are importable from `@terreno/ui` and subpaths.
|
|
255
|
+
- [ ] A scorecard shows title, formatted value, and a sparkline with dotted `comparisonData`.
|
|
256
|
+
- [ ] `LineChart` renders three series from `series` with a legend item each.
|
|
257
|
+
- [ ] Time-series x ticks can rotate; day-of-week ticks stay horizontal under `auto`.
|
|
258
|
+
- [ ] `DonutChart` draws `centerTitle` / `centerValue` and percent legend rows.
|
|
259
|
+
- [ ] `DashboardGridItem` spans reproduce 5→2→1 KPI and 3→1 chart wrapping in the reference fixture.
|
|
260
|
+
- [ ] `DataTable` shows period header groups, a sticky totals row, `1–50 of N`, and two-line wrap inside `rowHeight`.
|
|
261
|
+
- [ ] Existing single-series `{label, value}[]` charts and tests stay green.
|
|
262
|
+
- [ ] `bun run ui:charts:compare` matches goldens including `hows-it-going-dashboard`.
|
|
263
|
+
- [ ] How-to shows the easy-build composition (Scorecard + ChartCard + spans), not only raw `Card` + `LineChart`.
|
|
264
|
+
- [ ] Chart implementations stay lazy on the root; grid item stays eager.
|
|
265
|
+
- [ ] Frontend verification: demo gallery exercised on web; artifacts on the implementation PR.
|
|
266
|
+
|
|
267
|
+
## Risks
|
|
268
|
+
|
|
269
|
+
| Risk | Mitigation |
|
|
270
|
+
|------|------------|
|
|
271
|
+
| Dual header APIs (`ChartCard` vs facade title) | Document exclusive use; tests assert no double header when only one is set |
|
|
272
|
+
| `DataTable` wrap vs virtualization | Cap at two lines; keep `rowHeight`; screenshot a wrapped first column |
|
|
273
|
+
| Additive `series` vs later grammar | Facades remain the supported easy path; grammar stays a follow-up |
|
|
274
|
+
| Golden flake on dense dashboards | One hard fixture at a fixed gallery width; review-chart-visuals on mismatch |
|
|
275
|
+
| Header groups vs pinned columns | Totals and groups must align with `pinnedColumns` scroll sync; regression tests |
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Implementation Plan: Demo workshop
|
|
2
|
+
|
|
3
|
+
**Status:** Approved
|
|
4
|
+
**Branch:** `cursor/demo-workshop-cc39`
|
|
5
|
+
**Owner:** —
|
|
6
|
+
**Created:** 2026-09-26
|
|
7
|
+
**Task list:** [demo-workshop.md](../tasks/demo-workshop.md)
|
|
8
|
+
**Depends on:** —
|
|
9
|
+
**RTK deprecation flag:** None
|
|
10
|
+
**Supersedes:** the visual-regression non-goal in [examples-demo-coverage.md](examples-demo-coverage.md). Story coverage from that plan stays as it is.
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
Turn the Expo demo into the workshop for `@terreno/ui`: a person can find a component, copy a usage snippet, change theme and viewport from the URL, and see whether every stable story passes interaction, accessibility, and visual checks on web and Android.
|
|
15
|
+
|
|
16
|
+
Released demo majors stay online. The site opens on the newest released major (57 today). A dropdown lists older majors, such as 56, and the next major when `preview/<major>` exists, such as 58 preview.
|
|
17
|
+
|
|
18
|
+
## Non-Goals
|
|
19
|
+
|
|
20
|
+
- An addon or plugin system.
|
|
21
|
+
- iOS simulator runs, iOS screenshot baselines, and iOS accessibility checks.
|
|
22
|
+
- A translation catalog for `@terreno/ui`.
|
|
23
|
+
- The commented combinatorial prop matrix in `demo/app/demo/[component].tsx`.
|
|
24
|
+
- A pull-request comment bot.
|
|
25
|
+
- Rebuilding frozen majors on later master commits.
|
|
26
|
+
- Android baselines for historical majors. Android CI proves the branch under test.
|
|
27
|
+
- Feature-branch Netlify aliases (`pr-*`) appearing in the version dropdown.
|
|
28
|
+
|
|
29
|
+
## Decisions
|
|
30
|
+
|
|
31
|
+
| Question | Decision |
|
|
32
|
+
|----------|----------|
|
|
33
|
+
| Platforms | Web and Android in this plan. iOS is a later plan. |
|
|
34
|
+
| Visual coverage | Every stable named story. A story may opt out only with `stability: "exclude"` and a non-empty reason. |
|
|
35
|
+
| Usage snippets | One curated `usageExample` string on each story config, shown with a copy button. |
|
|
36
|
+
| Preview controls | Theme, viewport, background, locale, right-to-left, and reduced motion on demo and dev routes. State lives in the URL. Embeds apply the query and hide the bar. |
|
|
37
|
+
| Theme values | Light, plus the existing canonical dark role remap (`demo/components/palette/darkTheme.ts`). Custom palettes stay on `/palette`. |
|
|
38
|
+
| Viewports | The Box breakpoints already documented for web: 320, 375, 1024, and 1280. |
|
|
39
|
+
| Locale | The toolbar passes the selected locale only to components that already read one (consent content, date and calendar formatting). Right-to-left is a separate control. |
|
|
40
|
+
| Collaboration | CI artifacts and a check summary. No pull-request comment. |
|
|
41
|
+
| Agent access | Public `@terreno/mcp` tools read a generated demo catalog and the version manifest URL. |
|
|
42
|
+
| Visual comparison | Separate cropped web and Android PNG baselines. Reuse the chart pixel matcher. An Android-specific tolerance requires a note in the how-to. |
|
|
43
|
+
| Android CI | The full Android suite blocks pull requests that change `ui/` or `demo/`. |
|
|
44
|
+
| Preview branch | `preview/<major>` (for example `preview/58`) is the only branch labeled as the next-major preview. |
|
|
45
|
+
| Frozen majors | When master moves to a new major, keep the previous major at its last master commit. The current major redeploys on every master merge. Backfill 56 from its last master commit. |
|
|
46
|
+
| Version dropdown | Persistent top bar on the home, demo, and dev pages. Default is the highest released major, which is the production deploy. |
|
|
47
|
+
| Docs embeds | A docs version embeds the demo major it documents. The next docs version embeds `preview/<major>` when that deploy exists. |
|
|
48
|
+
| Accessibility | Web uses axe and fails a story on serious or critical violations. Android asserts the accessibility tree from the same step list. Moderate axe findings are warnings in the health report. |
|
|
49
|
+
| Interaction source | One declarative step list per story. Playwright runs it on web. A generated Maestro flow runs it on Android. |
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
demo/demoConfig.tsx
|
|
55
|
+
usageExample, related routes, story stability, interaction steps
|
|
56
|
+
│
|
|
57
|
+
├─ home/dev search + category filters + linked related components
|
|
58
|
+
├─ preview bar (URL state) and version bar (versions.json)
|
|
59
|
+
├─ web Playwright: interactions, axe, cropped PNG
|
|
60
|
+
├─ Android Maestro: same steps, accessibility tree, cropped PNG
|
|
61
|
+
└─ demo-catalog.json ── @terreno/mcp list/get tools
|
|
62
|
+
|
|
63
|
+
master deploy ── production site = current major (57)
|
|
64
|
+
last 56 master commit ── Netlify alias v56 (frozen)
|
|
65
|
+
preview/58 ── Netlify alias v58 (preview: true)
|
|
66
|
+
production /versions.json ── dropdown on every major
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Catalog contract
|
|
70
|
+
|
|
71
|
+
`DemoConfiguration` gains:
|
|
72
|
+
|
|
73
|
+
- `usageExample: string` — copyable usage, including the import when it is a normal `@terreno/ui` import.
|
|
74
|
+
- `related` entries resolve through `findDemoConfig`. Unknown names fail the catalog test.
|
|
75
|
+
- `stories[name].stability`: `"stable"` by default, or `"exclude"` with `excludeReason`.
|
|
76
|
+
- `stories[name].showInDemo` is honored. `false` hides that story from demo mode and keeps it on the dev route.
|
|
77
|
+
- `stories[name].interactions`: ordered steps `{action: "press" | "type" | "expectText" | "expectRole", targetTestID?, value?, role?, name?}`.
|
|
78
|
+
- Control defaults come from the control type: `false` for boolean, `0` for number, `""` for string. The `""` fallback for every type goes away.
|
|
79
|
+
|
|
80
|
+
`demo/collectRegisteredStoryRenders.ts` remains the inventory. Story ids stay `<Component> / <story>`.
|
|
81
|
+
|
|
82
|
+
### Preview and version chrome
|
|
83
|
+
|
|
84
|
+
`DemoChrome` wraps home, demo, and dev. The version dropdown is on every non-embed page. Preview controls are on demo and dev component routes. `?embed=1` hides both and still applies preview query params.
|
|
85
|
+
|
|
86
|
+
Preview query keys: `theme`, `viewport`, `background`, `locale`, `rtl`, `reducedMotion`.
|
|
87
|
+
|
|
88
|
+
The version dropdown does not switch code inside one bundle. It navigates to that major's deploy. Every bundle loads `versions.json` from the production demo origin so a frozen major learns about a new preview without a rebuild. That file is served with CORS for the demo aliases.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
interface DemoVersionManifest {
|
|
92
|
+
currentMajor: number;
|
|
93
|
+
versions: {
|
|
94
|
+
major: number;
|
|
95
|
+
label: string;
|
|
96
|
+
url: string;
|
|
97
|
+
preview: boolean;
|
|
98
|
+
released: boolean;
|
|
99
|
+
}[];
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`currentMajor` is the highest `released && !preview` entry. Production is that major. `preview/58` adds `{major: 58, label: "58 preview", preview: true, released: false}` while the branch exists. Merging 58 to master freezes 57 onto alias `v57`, points production at 58, and removes the preview flag.
|
|
104
|
+
|
|
105
|
+
Feature-branch aliases stay out of the manifest.
|
|
106
|
+
|
|
107
|
+
### Proof
|
|
108
|
+
|
|
109
|
+
| Check | Web | Android |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| Render | Existing `demo/storiesSmoke.test.tsx` | Maestro opens the story |
|
|
112
|
+
| Interaction | Playwright runs `interactions` | Generated Maestro flow |
|
|
113
|
+
| Accessibility | axe, serious and critical fail | Accessibility-tree assertions from `expectRole` / labels |
|
|
114
|
+
| Visual | Cropped PNG in `demo/rendered-snapshots/web/` | Cropped PNG in `demo/rendered-snapshots/android/` |
|
|
115
|
+
|
|
116
|
+
Chart goldens stay in `demo/rendered-snapshots/` until the chart task moves them. The chart compare script keeps working for chart fixtures.
|
|
117
|
+
|
|
118
|
+
A stable story without a web golden, an Android golden, or an exclusion fails CI. The same rule applies to interaction steps.
|
|
119
|
+
|
|
120
|
+
`demo/story-health.json` is produced by those jobs and copied into the demo build. The component page shows pass, fail, or not-run for render, interaction, accessibility, and visual, per platform. Missing file means not-run.
|
|
121
|
+
|
|
122
|
+
CI uploads the JSON, PNGs, and diffs as job artifacts and writes a check summary. It does not comment on the pull request.
|
|
123
|
+
|
|
124
|
+
Android runs on a CircleCI Android image with one pinned emulator: Pixel 6, API 34. The job is path-filtered like today's Maestro demo job (`ui/**`, `demo/**`) and is required on those pull requests.
|
|
125
|
+
|
|
126
|
+
### MCP
|
|
127
|
+
|
|
128
|
+
`bun run demo:catalog` writes `demo/demo-catalog.json` with component name, category, route, snippet, story ids, stability, and the production `versions.json` URL. `@terreno/mcp` exposes:
|
|
129
|
+
|
|
130
|
+
- `terreno_list_demo_stories`
|
|
131
|
+
- `terreno_get_demo_story`
|
|
132
|
+
|
|
133
|
+
Tools return catalog data. They do not drive a browser and they do not run Android.
|
|
134
|
+
|
|
135
|
+
### Docs embeds
|
|
136
|
+
|
|
137
|
+
`website` `DEMO_URL` becomes per docs version. Version 57.x points at the 57 demo alias (production while 57 is current). The next docs version points at the `preview/<major>` URL when the manifest lists that preview, and otherwise at production. `ComponentDemo` keeps `?embed=1`.
|
|
138
|
+
|
|
139
|
+
## Models
|
|
140
|
+
|
|
141
|
+
None.
|
|
142
|
+
|
|
143
|
+
## APIs
|
|
144
|
+
|
|
145
|
+
`GET /versions.json` on the production demo host, shape above.
|
|
146
|
+
|
|
147
|
+
MCP tools `terreno_list_demo_stories` and `terreno_get_demo_story`. No new HTTP API on `@terreno/api`.
|
|
148
|
+
|
|
149
|
+
## Notifications
|
|
150
|
+
|
|
151
|
+
None.
|
|
152
|
+
|
|
153
|
+
## UI
|
|
154
|
+
|
|
155
|
+
- Home and dev: search box and category filters over `DemoConfig`.
|
|
156
|
+
- Component page: copyable snippet, linked related components, preview controls, per-story health.
|
|
157
|
+
- Persistent version dropdown on home, demo, and dev.
|
|
158
|
+
- Embed mode unchanged except that preview query params apply.
|
|
159
|
+
|
|
160
|
+
Frontend verification uses the running web demo. Android verification uses the emulator flow for Button, then the full stable catalog.
|
|
161
|
+
|
|
162
|
+
## Phases
|
|
163
|
+
|
|
164
|
+
1. Catalog contract, search, snippets, and the preview bar. Button is the tracer.
|
|
165
|
+
2. Web interaction, axe, and visual baselines, then the health report.
|
|
166
|
+
3. Android emulator CI for Button, then every stable story.
|
|
167
|
+
4. Version manifest, frozen majors, preview branch, docs embeds.
|
|
168
|
+
5. MCP catalog tools.
|
|
169
|
+
|
|
170
|
+
## Feature Flags & Migrations
|
|
171
|
+
|
|
172
|
+
None. New demo query params are optional. Existing `/demo/<component>` URLs keep working. Production keeps serving the current major at the existing demo host.
|
|
173
|
+
|
|
174
|
+
## Not Included / Future Work
|
|
175
|
+
|
|
176
|
+
- iOS simulator coverage and iOS goldens.
|
|
177
|
+
- Library-wide translations.
|
|
178
|
+
- Demo addon API.
|
|
179
|
+
- Pixel baselines for every historical major.
|
|
180
|
+
|
|
181
|
+
## Files to Create / Modify
|
|
182
|
+
|
|
183
|
+
Create
|
|
184
|
+
|
|
185
|
+
- `docs/how-to/preview-demo.md`
|
|
186
|
+
- `docs/how-to/demo-versions.md`
|
|
187
|
+
- `docs/how-to/compare-demo-snapshots.md`
|
|
188
|
+
- `demo/components/DemoChrome.tsx`
|
|
189
|
+
- `scripts/demo/` runners and manifest publisher
|
|
190
|
+
- `demo/demo-catalog.json` (generated)
|
|
191
|
+
|
|
192
|
+
Modify
|
|
193
|
+
|
|
194
|
+
- `demo/demoConfig.tsx`, `demo/story-config/*.config.tsx`, `demo/app/demo/[component].tsx`
|
|
195
|
+
- `demo/components/DemoHomePage.tsx`, `demo/components/DevHomePage.tsx`
|
|
196
|
+
- `demo/README.md`
|
|
197
|
+
- `docs/reference/test.md`, `docs/reference/ui.md`, `docs/reference/mcp-server.md`
|
|
198
|
+
- `docs/how-to/run-tests-locally.md`, `docs/how-to/circleci.md`
|
|
199
|
+
- `.circleci/config.yml`, `.circleci/continue-config.yml`
|
|
200
|
+
- `scripts/ci/netlify-deploy.sh`
|
|
201
|
+
- `website/src/components/ComponentDemo/index.tsx`, `website/docusaurus.config.ts`
|
|
202
|
+
- `mcp-server/src/tools.ts`
|
|
203
|
+
|
|
204
|
+
## Task List
|
|
205
|
+
|
|
206
|
+
[docs/tasks/demo-workshop.md](../tasks/demo-workshop.md)
|
|
207
|
+
|
|
208
|
+
## Acceptance Criteria
|
|
209
|
+
|
|
210
|
+
- [ ] Search on the home grid finds Button by name and by category, and Button's related components are links.
|
|
211
|
+
- [ ] Every registered component has a `usageExample`, and the Button page copies it.
|
|
212
|
+
- [ ] A URL on the Button demo sets theme, viewport, background, locale, right-to-left, and reduced motion. Embed hides the bar and keeps the query.
|
|
213
|
+
- [ ] Every stable story has web interaction steps or an exclusion reason, an axe result, and a web PNG baseline.
|
|
214
|
+
- [ ] The same stable stories have Android interaction, accessibility-tree, and PNG results. The Android job fails the pull request when `ui/` or `demo/` changes.
|
|
215
|
+
- [ ] The Button page shows health from `story-health.json`. CI publishes a check summary and artifacts and does not comment.
|
|
216
|
+
- [ ] Production opens on major 57. Alias `v56` serves the last major-56 master commit. `preview/58` appears as "58 preview" and is not the default.
|
|
217
|
+
- [ ] A 57 docs page embeds the 57 demo. The next docs version embeds the 58 preview when that alias exists.
|
|
218
|
+
- [ ] `terreno_get_demo_story` returns Button's snippet, route, and the version manifest URL.
|
|
219
|
+
- [ ] `demo/README.md`, the how-to pages named above, and the reference pages named above match this behavior.
|
|
@@ -104,7 +104,7 @@ import {AIService} from "@terreno/ai";
|
|
|
104
104
|
import {google} from "@ai-sdk/google";
|
|
105
105
|
|
|
106
106
|
const aiService = new AIService({
|
|
107
|
-
model: google("gemini-
|
|
107
|
+
model: google("gemini-3.8-flash"),
|
|
108
108
|
defaultTemperature: 1.0,
|
|
109
109
|
});
|
|
110
110
|
```
|
|
@@ -211,7 +211,9 @@ Conversation history with multi-modal prompts.
|
|
|
211
211
|
| `userId` | ObjectId | Owner (required) |
|
|
212
212
|
| `title` | string? | Auto-generated on first `/gpt/prompt` response when empty |
|
|
213
213
|
| `projectId` | ObjectId? | Optional project association |
|
|
214
|
-
| `prompts` | array | Messages: `text`, `type` (`user` \| `assistant` \| `system` \| `tool-call` \| `tool-result`), optional `content` parts, `model`, `rating`, tool fields |
|
|
214
|
+
| `prompts` | array | Messages: `text`, `type` (`user` \| `assistant` \| `system` \| `tool-call` \| `tool-result`), optional `content` parts, `model`, `rating`, tool fields, `status`, `streamId`. `text` is required unless `content` has parts or `status` is set; an image-only assistant response saves `text: ""` |
|
|
215
|
+
|
|
216
|
+
Assistant replies from `/gpt/prompt` carry `streamId` and `status`: `streaming` while partial text is persisted, then `complete` or `error`. `buildMessages` skips `streaming` replies and `error` replies with no output. Attachment `content` parts carry an optional `gcsKey` when the file was uploaded to durable storage.
|
|
215
217
|
|
|
216
218
|
**Virtual:** `ownerId` aliases `userId` for `Permissions.IsOwner`.
|
|
217
219
|
|
|
@@ -252,8 +254,36 @@ GPT project with persistent context and memories.
|
|
|
252
254
|
| `/gpt/prompt` | POST | `IsAuthenticated` | SSE streaming chat; body: `prompt`, optional `historyId`, `systemPrompt`, `attachments`, `model`, `projectId` |
|
|
253
255
|
| `/gpt/remix` | POST | `IsAuthenticated` | Non-streaming text remix; body: `{text}` |
|
|
254
256
|
| `/gpt/histories/:id/rating` | PATCH | `IsAuthenticated` | Rate a prompt; body: `{promptIndex, rating: "up" \| "down" \| null}` |
|
|
257
|
+
| `/gpt/histories/:id/stream` | GET | `IsAuthenticated` (owner) | SSE resume of an in-flight reply; query: optional `streamId`, `offset` |
|
|
255
258
|
| `/gpt/tools` | GET | `IsAuthenticated` | List builtin + MCP tools |
|
|
256
259
|
|
|
260
|
+
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. On later turns, `buildMessages` sends an image-only assistant prompt to the model as the text `[Generated image]`, because providers reject empty assistant turns.
|
|
261
|
+
|
|
262
|
+
#### Attachments
|
|
263
|
+
|
|
264
|
+
Each `attachments` item is `{type: "image" | "file", url, mimeType, filename?}`. The `url` must be `http(s):` or `data:`. Client-only URLs (`blob:`, `file:`, `content:`, `ph:`) return `400` before streaming starts, because neither the model provider nor a later page load can read them. Upload the file with `POST /files/upload` first, or send it as a `data:` URL.
|
|
265
|
+
|
|
266
|
+
When `fileStorageService` is set, `data:` attachments are uploaded with `FileStorageService.upload`. The saved user prompt then stores the storage `url` plus `gcsKey`, not the base64 payload. The model still receives the original data for that turn. On later turns, parts with a `gcsKey` are sent to the model as 1-hour signed URLs. A failed upload returns `502 Attachment upload failed`. Without storage, attachments are saved as sent.
|
|
267
|
+
|
|
268
|
+
`fileUploadsEnabled` turns uploads off without removing storage. Pass `false`, or a function that returns `false`, and any prompt that includes attachments returns `403 File uploads are disabled` before streaming. Omit it, or pass `true`, to leave uploads enabled. The example app wires this to the `file-uploads` feature flag.
|
|
269
|
+
|
|
270
|
+
#### Stream events and resume
|
|
271
|
+
|
|
272
|
+
`/gpt/prompt` saves the user turn and a `status: "streaming"` assistant placeholder before streaming. While the reply streams, partial text is persisted about every second (`streamPersistIntervalMs`), plus a heartbeat every 10 seconds. The final reply replaces the placeholder with `status: "complete"`. On failure, partial text is kept with `status: "error"`; an empty placeholder is removed.
|
|
273
|
+
|
|
274
|
+
| Event | Sent by | Meaning |
|
|
275
|
+
|-------|---------|---------|
|
|
276
|
+
| `{historyId, started: true, streamId}` | prompt | First event; the turn is saved and resumable |
|
|
277
|
+
| `{historyId, resumed: true, streamId?}` | resume | First event; `streamId` is absent when nothing is streaming |
|
|
278
|
+
| `{text}` | both | Text delta |
|
|
279
|
+
| `{replace: true, text}` | resume | Authoritative whole reply: sent first when the client provides `offset`, and whenever persisted text was rewritten (for example, a step became a tool call) |
|
|
280
|
+
| `{image: {mimeType, url}}` | both | Generated image |
|
|
281
|
+
| `{file}`, `{toolCall}`, `{toolResult}` | prompt | File and tool events |
|
|
282
|
+
| `{error}` | both | Error; resume sends it when the reply ended as `error` or went stale |
|
|
283
|
+
| `{done: true, historyId, title?}` | both | Reply finished |
|
|
284
|
+
|
|
285
|
+
`GET /gpt/histories/:id/stream` re-attaches after a reload or remount. Pass `offset` as the number of characters the client already shows, usually the stored placeholder `text`. The endpoint polls the stored history (`streamResumePollIntervalMs`, default 500 ms), so it works across server instances. A `streaming` reply with no update for `streamStaleAfterMs` (default 60 s) is marked `error`.
|
|
286
|
+
|
|
257
287
|
AI resolution order: `x-ai-api-key` header + `createModelFn` → `createServerModelFn(modelId)` → configured `aiService` → demo SSE response when `demoMode` and none available.
|
|
258
288
|
|
|
259
289
|
### addGptHistoryRoutes(router, options?)
|
|
@@ -281,7 +311,7 @@ Requires `fileStorageService` and `gcsBucket` (registered by `AiApp` when both a
|
|
|
281
311
|
|
|
282
312
|
| Endpoint | Method | Auth | Description |
|
|
283
313
|
|----------|--------|------|-------------|
|
|
284
|
-
| `/files/upload` | POST | `IsAuthenticated` | Multipart upload (`file` field); allowed MIME: images, PDF, plain text, CSV, JSON |
|
|
314
|
+
| `/files/upload` | POST | `IsAuthenticated` | Multipart upload (`file` field); allowed MIME: images, PDF, plain text, CSV, JSON. Returns `403` when `fileUploadsEnabled` is off |
|
|
285
315
|
| `/files/*gcsKey` | GET | None | Returns signed read URL (1 hour) |
|
|
286
316
|
| `/files/*gcsKey` | DELETE | `IsAuthenticated` (owner) | Soft-delete attachment and remove from GCS |
|
|
287
317
|
|
|
@@ -309,7 +339,7 @@ Requires `mcpService` (registered by `AiApp` when set).
|
|
|
309
339
|
import {AiApp, AIService, FileStorageService, MCPService} from "@terreno/ai";
|
|
310
340
|
import {google} from "@ai-sdk/google";
|
|
311
341
|
|
|
312
|
-
const aiService = new AIService({model: google("gemini-
|
|
342
|
+
const aiService = new AIService({model: google("gemini-3.8-flash")});
|
|
313
343
|
|
|
314
344
|
new AiApp({
|
|
315
345
|
aiService,
|
|
@@ -318,7 +348,7 @@ new AiApp({
|
|
|
318
348
|
mcpService: new MCPService([{name: "tools", transport: {type: "sse", url: "..."}}]),
|
|
319
349
|
tools: myToolDefinitions,
|
|
320
350
|
demoMode: false,
|
|
321
|
-
createModelFn: (apiKey, modelId) => google(modelId ?? "gemini-
|
|
351
|
+
createModelFn: (apiKey, modelId) => google(modelId ?? "gemini-3.8-flash", {apiKey}),
|
|
322
352
|
openApiOptions: options,
|
|
323
353
|
}).register(app);
|
|
324
354
|
```
|
|
@@ -329,7 +359,8 @@ new AiApp({
|
|
|
329
359
|
| `createModelFn` | Build model from per-request `x-ai-api-key` |
|
|
330
360
|
| `createServerModelFn` | Server-side model factory (e.g. Vertex ADC) without per-request key |
|
|
331
361
|
| `demoMode` | Return canned responses when no AI service resolves |
|
|
332
|
-
| `fileStorageService` + `gcsBucket` | Enable file upload routes |
|
|
362
|
+
| `fileStorageService` + `gcsBucket` | Enable file upload routes and durable `/gpt/prompt` attachments |
|
|
363
|
+
| `fileUploadsEnabled` | `false` or a function returning `false` rejects uploads and chat attachments with `403`. Omit to leave uploads enabled |
|
|
333
364
|
| `mcpService` | Enable MCP routes and tool discovery in chat |
|
|
334
365
|
| `tools` | Static Vercel AI SDK tool definitions for chat |
|
|
335
366
|
| `toolChoice` | `"auto"` \| `"none"` \| `"required"` (default `"auto"` when tools present) |
|
|
@@ -445,10 +476,10 @@ import {
|
|
|
445
476
|
} from "@terreno/ai";
|
|
446
477
|
|
|
447
478
|
const vertex = await createVertexProvider({project: "my-gcp-project"});
|
|
448
|
-
const model = vertex.languageModel("gemini-
|
|
479
|
+
const model = vertex.languageModel("gemini-3.8-flash");
|
|
449
480
|
```
|
|
450
481
|
|
|
451
|
-
Env fallbacks: `GOOGLE_VERTEX_PROJECT`, `GOOGLE_VERTEX_LOCATION` (default `
|
|
482
|
+
Env fallbacks: `GOOGLE_VERTEX_PROJECT`, `GOOGLE_VERTEX_LOCATION` (default `global`).
|
|
452
483
|
|
|
453
484
|
## Web search types
|
|
454
485
|
|
|
@@ -461,7 +492,7 @@ import {TerrenoApp} from "@terreno/api";
|
|
|
461
492
|
import {AiApp, AIService, LangfuseApp} from "@terreno/ai";
|
|
462
493
|
import {google} from "@ai-sdk/google";
|
|
463
494
|
|
|
464
|
-
const aiService = new AIService({model: google("gemini-
|
|
495
|
+
const aiService = new AIService({model: google("gemini-3.8-flash")});
|
|
465
496
|
|
|
466
497
|
new TerrenoApp({userModel: User})
|
|
467
498
|
.register(new AiApp({aiService, openApiOptions: {}}))
|
|
@@ -481,7 +512,7 @@ Legacy `setupServer` pattern: call `addGptHistoryRoutes`, `addGptRoutes`, etc. i
|
|
|
481
512
|
| Variable | Used by | Description |
|
|
482
513
|
|----------|---------|-------------|
|
|
483
514
|
| `GOOGLE_VERTEX_PROJECT` | `createVertexProvider` | GCP project for Vertex models |
|
|
484
|
-
| `GOOGLE_VERTEX_LOCATION` | `createVertexProvider` | Vertex region (default `
|
|
515
|
+
| `GOOGLE_VERTEX_LOCATION` | `createVertexProvider` | Vertex region (default `global`) |
|
|
485
516
|
| `LANGFUSE_PUBLIC_KEY` | `LangfuseApp` | Langfuse public key |
|
|
486
517
|
| `LANGFUSE_SECRET_KEY` | `LangfuseApp` | Langfuse secret key |
|
|
487
518
|
| `LANGFUSE_BASE_URL` | Langfuse client | Langfuse host URL |
|