@terreno/mcp 57.8.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/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 +1 -1
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +20 -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 +4 -3
- 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/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/reference/ai.md +39 -10
- package/dist/docs/versioned/next/reference/environment-variables.md +1 -1
- package/dist/docs/versioned/next/reference/feature-flags.md +1 -1
- package/dist/docs/versioned/next/reference/ui.md +94 -11
- package/dist/docs/versioned/next/tasks/dashboard-chart-parity.md +145 -0
- package/package.json +2 -2
- 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 +1 -1
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +20 -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 +4 -3
- 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/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/reference/ai.md +39 -10
- package/src/docs/versioned/next/reference/environment-variables.md +1 -1
- package/src/docs/versioned/next/reference/feature-flags.md +1 -1
- package/src/docs/versioned/next/reference/ui.md +94 -11
- package/src/docs/versioned/next/tasks/dashboard-chart-parity.md +145 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Upgrading to 57.9.0
|
|
2
|
+
|
|
3
|
+
**Action required:** No
|
|
4
|
+
**Affected packages:** @terreno/ai, @terreno/ui
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
This release adds durable and resumable GPT chat streams, safer attachment handling, operations-dashboard components, and improved web toast and file-picker behavior. Existing applications continue to work without migration.
|
|
9
|
+
|
|
10
|
+
## Breaking changes
|
|
11
|
+
|
|
12
|
+
None
|
|
13
|
+
|
|
14
|
+
## Deprecations
|
|
15
|
+
|
|
16
|
+
None
|
|
17
|
+
|
|
18
|
+
## New capabilities
|
|
19
|
+
|
|
20
|
+
- Configure `fileStorageService` to persist chat attachments and resume in-flight replies through `GET /gpt/histories/:id/stream`.
|
|
21
|
+
- Compose operations dashboards with scorecards, comparison series, spanning items, and richer chart presentation.
|
|
22
|
+
- Set `maxHeight` on growing `TextField` and `TextArea` components.
|
|
23
|
+
|
|
24
|
+
## Verification
|
|
25
|
+
|
|
26
|
+
- Run `bun run compile` and `bun run lint` at the app root.
|
|
27
|
+
- Exercise GPT chat attachment upload, reload during a response, and stream resumption when adopting the new AI capabilities.
|
|
28
|
+
- Check web toasts above an open modal and file selection through `FilePickerButton`.
|
|
@@ -20,6 +20,14 @@ Height works the same way: `height` is the whole chart, and the tick row, toolti
|
|
|
20
20
|
row come out of that budget. A chart therefore fits a fixed-height slot, and reserving the tooltip
|
|
21
21
|
row even when no tooltip is showing keeps hovering from reflowing the page.
|
|
22
22
|
|
|
23
|
+
Cartesian facades use `xTickPolicy="auto"` by default: up to seven categorical labels
|
|
24
|
+
stay horizontal, while denser sets rotate 45 degrees and reserve a taller axis row.
|
|
25
|
+
That taller row comes out of `height`; the plot shrinks so the chart still fits the
|
|
26
|
+
requested slot. `SparklineChart` scales to the data range so a small period-over-period
|
|
27
|
+
change stays visible.
|
|
28
|
+
Callers may force `"truncate"` or `"rotate"`. `title` and `periodLabel` are convenience
|
|
29
|
+
props backed by the same `ChartCard` used for explicit chart/table composition.
|
|
30
|
+
|
|
23
31
|
`DashboardGrid` is layout only, so it stays on the eager root export. Chart implementations sit behind the same lazy root boundary as `GPTChat`. Cell width subtracts flex `gap` so `md`/`lg` column counts actually fit.
|
|
24
32
|
|
|
25
33
|
Chart paint is proven with **rendered PNG goldens**, not React test-renderer JSON.
|
|
@@ -27,3 +35,7 @@ Chart paint is proven with **rendered PNG goldens**, not React test-renderer JSO
|
|
|
27
35
|
(`/demo/chart-visual-gallery`), screenshots each fixture from easy to hard, and
|
|
28
36
|
pixel-diffs against `demo/rendered-snapshots/`. Agents use the `review-chart-visuals`
|
|
29
37
|
skill to read golden / actual / diff images when a compare fails.
|
|
38
|
+
|
|
39
|
+
The example app admin home (`/admin`) mounts `AdminCharts` so scorecards, a multi-series line,
|
|
40
|
+
bars, an area, and a donut with a center label render together. See
|
|
41
|
+
[Compose charts in a dashboard grid](../how-to/charts-and-dashboards.md).
|
|
@@ -21,7 +21,7 @@ that file (and its imports) to a working example. Gaps stay explicit.
|
|
|
21
21
|
| 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`). |
|
|
22
22
|
| 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`). |
|
|
23
23
|
| 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. |
|
|
24
|
-
| File upload / GCS | yes — `example-backend/src/api/settings.ts`, AI file routes in `example-backend/src/api/ai.ts` | yes — `example-frontend/app/(tabs)/files.tsx`, `example-frontend/app/gcs-settings.tsx` | |
|
|
24
|
+
| 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. |
|
|
25
25
|
| 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`. |
|
|
26
26
|
| 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. |
|
|
27
27
|
| 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. |
|
|
@@ -639,6 +639,26 @@ docs are the proof; comms admin stats consume these primitives later.
|
|
|
639
639
|
|
|
640
640
|
---
|
|
641
641
|
|
|
642
|
+
## dashboard-chart-parity
|
|
643
|
+
|
|
644
|
+
**Title:** `Ops dashboard chart parity`
|
|
645
|
+
|
|
646
|
+
**Labels:** `area:ui`, `type:feature`
|
|
647
|
+
**Project fields:** Area=`ui`, Target=`Next`, Impact=`Feature`, IP=`dashboard-chart-parity`, Status=`Planned`
|
|
648
|
+
|
|
649
|
+
Follow-up to charts-and-dashboards: scorecards with comparison sparklines, `ChartCard`
|
|
650
|
+
period badges, additive multi-series facades, donut hole labels, spanning `DashboardGrid`
|
|
651
|
+
items, and DataTable period headers / totals / range status so apps can compose a
|
|
652
|
+
“How’s it going?”-class ops dashboard. Terreno theme defaults; no Looker preset. Grammar
|
|
653
|
+
not required.
|
|
654
|
+
|
|
655
|
+
- **Implementation plan:** [dashboard-chart-parity.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/implementationPlans/dashboard-chart-parity.md)
|
|
656
|
+
- **Tasks:** [dashboard-chart-parity.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/tasks/dashboard-chart-parity.md)
|
|
657
|
+
- **RTK flag:** None
|
|
658
|
+
- **Depends on:** charts-and-dashboards
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
642
662
|
## dark-mode-theme
|
|
643
663
|
|
|
644
664
|
**Title:** `First-class dark mode`
|
|
@@ -39,7 +39,7 @@ Problem-oriented, practical steps. Use these when you know what you want to do.
|
|
|
39
39
|
- [Upgrade banner](upgrade-banner.md) — Soft warning and hard-block app update UX
|
|
40
40
|
- [Upgrade Terreno](upgrade-terreno.md) — Lockstep `@terreno/*` bumps, note range, order, rollback
|
|
41
41
|
- [Track upstream Expo](track-upstream-expo.md) — Morning probe for SDK betas; `release-X.Y.Z` loop log + native fingerprint deps
|
|
42
|
-
- [Compose charts in a dashboard grid](charts-and-dashboards.md) — `LineChart` / `BarChart` / `AreaChart` / `DonutChart` inside `Card`s in `DashboardGrid`
|
|
42
|
+
- [Compose charts in a dashboard grid](charts-and-dashboards.md) — `LineChart` / `BarChart` / `AreaChart` / `DonutChart` inside `Card`s in `DashboardGrid` (ops-dashboard scorecards and spans: [dashboard-chart-parity](../implementationPlans/dashboard-chart-parity.md))
|
|
43
43
|
- [Compare rendered chart snapshots](compare-chart-rendered-snapshots.md) — PNG goldens for the chart visual gallery, not JSON snapshots
|
|
44
44
|
- [Migrate from @terreno/rtk to @terreno/syncdb](migrate-rtk-to-syncdb.md) — Move data sync to the local-first layer
|
|
45
45
|
- [Generate a REST CLI from OpenAPI](generate-a-rest-cli.md) — Scaffold `terreno generate rest-cli` for an app API
|
|
@@ -18,7 +18,7 @@ const app = new TerrenoApp({userModel: User})
|
|
|
18
18
|
|
|
19
19
|
- `FeatureFlagsApp`: FeatureFlag model and `feature-flags-overrides` home widget.
|
|
20
20
|
- `ConsentApp`: ConsentForm and ConsentResponse models plus consent field-widget IDs.
|
|
21
|
-
- `DocumentStorageApp`: `documents` custom screen.
|
|
21
|
+
- `DocumentStorageApp`: `documents` custom screen. Pass `fileUploadsEnabled: false` (or a function that returns `false`) to reject uploads while listing and download stay available. The example app wires this to the `file-uploads` feature flag.
|
|
22
22
|
- `AIAdminApp`: `ai-requests` custom screen and explorer API.
|
|
23
23
|
- `JobsApp`: `jobs` custom screen and home widget (`dead` / `running` counts). With
|
|
24
24
|
`accessControl`, requires **both** `admin:access` and `admin:jobs`; without it, legacy
|
|
@@ -35,3 +35,8 @@ export const OpsDashboard: FC = () => {
|
|
|
35
35
|
Charts have no width prop: each one fills the container you put it in and redraws when that width changes, so size the `Card` or `Box` around it. `height` is the chart's whole height, tick row and tooltip row included, so `height={140}` fits a 140px slot; the tooltip row is always reserved so hovering never reflows the page. Long x labels truncate inside their band rather than widening the chart.
|
|
36
36
|
|
|
37
37
|
Prove paint with `bun run ui:charts:compare` against `demo/rendered-snapshots/`, not with JSON snapshots. See [Compare rendered chart snapshots](compare-chart-rendered-snapshots.md).
|
|
38
|
+
|
|
39
|
+
The example app admin home (`example-frontend/components/AdminCharts.tsx`, mounted
|
|
40
|
+
below the admin dashboard at `/admin`) shows the same pieces together: scorecards with comparison
|
|
41
|
+
sparklines, a multi-series line, a donut with a center label, a bar chart, and an
|
|
42
|
+
area chart inside a spanning `DashboardGrid`.
|
|
@@ -242,9 +242,10 @@ restrict `terreno-release` and `terreno-npm` to tag/manual release pipelines.
|
|
|
242
242
|
Until contexts exist, e2e jobs may use **project env vars for `E2E_*` secrets only**
|
|
243
243
|
(or the in-job `ci-e2e-*-secret` fallbacks). **Do not** put `GITHUB_TOKEN` (or any
|
|
244
244
|
GitHub PAT) in project env vars — those are injected into every job, including
|
|
245
|
-
`bun` scripts from the PR.
|
|
246
|
-
project
|
|
247
|
-
`dco` and `architectural-pr-review
|
|
245
|
+
`bun` scripts from the PR. `terreno-github-api` must hold the PAT as
|
|
246
|
+
`GITHUB_TOKEN`; restrict it to this project and leave fork-PR secret passing off.
|
|
247
|
+
The `always` workflow attaches it **only** to `dco` and `architectural-pr-review`
|
|
248
|
+
(the latter also gets `terreno-agentic`). DCO skips if `GITHUB_TOKEN` is unset.
|
|
248
249
|
`architectural-pr-review` also skips if `GITHUB_TOKEN` or `CURSOR_API_KEY` is
|
|
249
250
|
unset, and it skips fork PRs. The job checks out `origin/master` before running
|
|
250
251
|
the review script so a PR cannot rewrite the reviewer.
|
|
@@ -33,4 +33,12 @@ easy-through-hard charts in the demo app and diffs those PNGs against goldens.
|
|
|
33
33
|
Agents reviewing a fail should follow the `review-chart-visuals` skill and look at the
|
|
34
34
|
PNGs, not only the code diff.
|
|
35
35
|
|
|
36
|
+
Hard fixture `scorecard-sparkline-comparison` renders five KPI tiles with solid current
|
|
37
|
+
and dotted previous-period sparklines.
|
|
38
|
+
Cartesian parity fixtures are `bar-time-rotated-ticks`, `bar-day-of-week`, and
|
|
39
|
+
`line-three-series`.
|
|
40
|
+
Donut parity fixtures are `donut-center-and-share` and `donut-single-slice`.
|
|
41
|
+
Hard fixture `hows-it-going-dashboard` composes KPI scorecards, time-series charts, donuts,
|
|
42
|
+
and table placeholders in a spanning `DashboardGrid`.
|
|
43
|
+
|
|
36
44
|
`CHART_VISUAL_BASE_URL` overrides the demo origin when the gallery is already hosted.
|
|
@@ -70,7 +70,7 @@ scan stages come from
|
|
|
70
70
|
| Docs | `update-docs`, `update-agent-docs`, architecture skills |
|
|
71
71
|
| GitHub | issues, review, UI verification, release, dependency updates |
|
|
72
72
|
| Code scans | `terreno-scan-1-aim` … `terreno-scan-5-track`, `terreno-scan-campaign`, `terreno-scan-loop` |
|
|
73
|
-
| Expo and native | `track-upstream-expo`, `upgrading-expo`, deployment / EAS skills |
|
|
73
|
+
| Expo and native | `track-upstream-expo`, `upgrading-expo`, `store-assets`, deployment / EAS skills |
|
|
74
74
|
| Plugin agents | `pre-commit`, `ui-verifier` |
|
|
75
75
|
|
|
76
76
|
`skills.sh.json` at the repo root groups those names on [skills.sh](https://skills.sh).
|
|
@@ -274,7 +274,7 @@ Env (example-backend):
|
|
|
274
274
|
| `AI_OBS_DATASETS_PRIMARY` | default `local` |
|
|
275
275
|
| `AI_OBS_EXPERIMENTS_PRIMARY` | must equal datasets |
|
|
276
276
|
| `AI_OBS_SAMPLE_RATE` | `0`–`1`, default `0` |
|
|
277
|
-
| `AI_OBS_PRICE_MAP_JSON` | `{ "gemini-
|
|
277
|
+
| `AI_OBS_PRICE_MAP_JSON` | `{ "gemini-3.8-flash": { "inputPerMTok": 0.1, "outputPerMTok": 0.4 } }` |
|
|
278
278
|
| existing Langfuse keys | unchanged |
|
|
279
279
|
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | optional OTLP |
|
|
280
280
|
|
|
@@ -36,7 +36,7 @@ org switcher, dark mode).
|
|
|
36
36
|
| C | Communications (pluggable) | `comms-abstraction`, one item **per adapter**: `comms-adapter-expo-push` (first push adapter, per D4), `comms-adapter-twilio-sms`, `comms-adapter-twilio-verify`, `comms-adapter-sendgrid` (first mail adapter, per D2); operations: `comms-admin-dashboard`; consumers: `password-reset-and-email-verification`, `notification-center` |
|
|
37
37
|
| B | Billing | `inbound-webhooks`, `billing-stripe` (web-first, per D1), `mobile-iap-revenuecat` (Future) |
|
|
38
38
|
| D | Zero-to-deployed DX | `create-terreno-app`, `mongo-migrations` (deploy items already exist in the OSS launch program) |
|
|
39
|
-
| E | B2B UI surfaces | `data-grid-server-filters`, `charts-and-dashboards`, `dark-mode-theme`, `command-palette`, `wizard-stepper`, `wysiwyg-editor` (markdown stays for now, per D3), `global-search` |
|
|
39
|
+
| E | B2B UI surfaces | `data-grid-server-filters`, `charts-and-dashboards`, `dashboard-chart-parity`, `dark-mode-theme`, `command-palette`, `wizard-stepper`, `wysiwyg-editor` (markdown stays for now, per D3), `global-search` |
|
|
40
40
|
| F | Enterprise & scale | `framework-audit-log` (Next), `rate-limiting` (Next), `job-queues` (Next), `enterprise-sso`, `mfa-step-up-auth` |
|
|
41
41
|
| — | Native baseline | `native-module-baseline` — all new native modules land in one major release |
|
|
42
42
|
|
|
@@ -194,8 +194,9 @@ None.
|
|
|
194
194
|
|
|
195
195
|
## Not Included / Future Work
|
|
196
196
|
|
|
197
|
-
-
|
|
198
|
-
-
|
|
197
|
+
- Ops-dashboard parity (scorecards, comparison sparklines, additive multi-series, donut centers, spanning grid, table chrome) — [`dashboard-chart-parity.md`](dashboard-chart-parity.md).
|
|
198
|
+
- Composition grammar (Phase 6) — recorded Grow decisions; implement on a follow-up Pick. Not required for the How’s it going dashboard.
|
|
199
|
+
- Multi-series cartesian via additive `series` lands in `dashboard-chart-parity`; grammar `dataKey` / `stackId` remains later.
|
|
199
200
|
- Wiring `CommsDashboardScreen` charts to the stats endpoint.
|
|
200
201
|
- Example-frontend dashboard.
|
|
201
202
|
- Zoom/pan.
|
|
@@ -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 |
|
|
@@ -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,10 +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
|
|
|
257
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.
|
|
258
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
|
+
|
|
259
287
|
AI resolution order: `x-ai-api-key` header + `createModelFn` → `createServerModelFn(modelId)` → configured `aiService` → demo SSE response when `demoMode` and none available.
|
|
260
288
|
|
|
261
289
|
### addGptHistoryRoutes(router, options?)
|
|
@@ -283,7 +311,7 @@ Requires `fileStorageService` and `gcsBucket` (registered by `AiApp` when both a
|
|
|
283
311
|
|
|
284
312
|
| Endpoint | Method | Auth | Description |
|
|
285
313
|
|----------|--------|------|-------------|
|
|
286
|
-
| `/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 |
|
|
287
315
|
| `/files/*gcsKey` | GET | None | Returns signed read URL (1 hour) |
|
|
288
316
|
| `/files/*gcsKey` | DELETE | `IsAuthenticated` (owner) | Soft-delete attachment and remove from GCS |
|
|
289
317
|
|
|
@@ -311,7 +339,7 @@ Requires `mcpService` (registered by `AiApp` when set).
|
|
|
311
339
|
import {AiApp, AIService, FileStorageService, MCPService} from "@terreno/ai";
|
|
312
340
|
import {google} from "@ai-sdk/google";
|
|
313
341
|
|
|
314
|
-
const aiService = new AIService({model: google("gemini-
|
|
342
|
+
const aiService = new AIService({model: google("gemini-3.8-flash")});
|
|
315
343
|
|
|
316
344
|
new AiApp({
|
|
317
345
|
aiService,
|
|
@@ -320,7 +348,7 @@ new AiApp({
|
|
|
320
348
|
mcpService: new MCPService([{name: "tools", transport: {type: "sse", url: "..."}}]),
|
|
321
349
|
tools: myToolDefinitions,
|
|
322
350
|
demoMode: false,
|
|
323
|
-
createModelFn: (apiKey, modelId) => google(modelId ?? "gemini-
|
|
351
|
+
createModelFn: (apiKey, modelId) => google(modelId ?? "gemini-3.8-flash", {apiKey}),
|
|
324
352
|
openApiOptions: options,
|
|
325
353
|
}).register(app);
|
|
326
354
|
```
|
|
@@ -331,7 +359,8 @@ new AiApp({
|
|
|
331
359
|
| `createModelFn` | Build model from per-request `x-ai-api-key` |
|
|
332
360
|
| `createServerModelFn` | Server-side model factory (e.g. Vertex ADC) without per-request key |
|
|
333
361
|
| `demoMode` | Return canned responses when no AI service resolves |
|
|
334
|
-
| `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 |
|
|
335
364
|
| `mcpService` | Enable MCP routes and tool discovery in chat |
|
|
336
365
|
| `tools` | Static Vercel AI SDK tool definitions for chat |
|
|
337
366
|
| `toolChoice` | `"auto"` \| `"none"` \| `"required"` (default `"auto"` when tools present) |
|
|
@@ -447,10 +476,10 @@ import {
|
|
|
447
476
|
} from "@terreno/ai";
|
|
448
477
|
|
|
449
478
|
const vertex = await createVertexProvider({project: "my-gcp-project"});
|
|
450
|
-
const model = vertex.languageModel("gemini-
|
|
479
|
+
const model = vertex.languageModel("gemini-3.8-flash");
|
|
451
480
|
```
|
|
452
481
|
|
|
453
|
-
Env fallbacks: `GOOGLE_VERTEX_PROJECT`, `GOOGLE_VERTEX_LOCATION` (default `
|
|
482
|
+
Env fallbacks: `GOOGLE_VERTEX_PROJECT`, `GOOGLE_VERTEX_LOCATION` (default `global`).
|
|
454
483
|
|
|
455
484
|
## Web search types
|
|
456
485
|
|
|
@@ -463,7 +492,7 @@ import {TerrenoApp} from "@terreno/api";
|
|
|
463
492
|
import {AiApp, AIService, LangfuseApp} from "@terreno/ai";
|
|
464
493
|
import {google} from "@ai-sdk/google";
|
|
465
494
|
|
|
466
|
-
const aiService = new AIService({model: google("gemini-
|
|
495
|
+
const aiService = new AIService({model: google("gemini-3.8-flash")});
|
|
467
496
|
|
|
468
497
|
new TerrenoApp({userModel: User})
|
|
469
498
|
.register(new AiApp({aiService, openApiOptions: {}}))
|
|
@@ -483,7 +512,7 @@ Legacy `setupServer` pattern: call `addGptHistoryRoutes`, `addGptRoutes`, etc. i
|
|
|
483
512
|
| Variable | Used by | Description |
|
|
484
513
|
|----------|---------|-------------|
|
|
485
514
|
| `GOOGLE_VERTEX_PROJECT` | `createVertexProvider` | GCP project for Vertex models |
|
|
486
|
-
| `GOOGLE_VERTEX_LOCATION` | `createVertexProvider` | Vertex region (default `
|
|
515
|
+
| `GOOGLE_VERTEX_LOCATION` | `createVertexProvider` | Vertex region (default `global`) |
|
|
487
516
|
| `LANGFUSE_PUBLIC_KEY` | `LangfuseApp` | Langfuse public key |
|
|
488
517
|
| `LANGFUSE_SECRET_KEY` | `LangfuseApp` | Langfuse secret key |
|
|
489
518
|
| `LANGFUSE_BASE_URL` | Langfuse client | Langfuse host URL |
|
|
@@ -120,7 +120,7 @@ Resolution order for API base URL (`rtk/src/constants.ts`):
|
|
|
120
120
|
| `GEMINI_API_KEY` | `@terreno/ai` | ❌ | — | Yes | server |
|
|
121
121
|
| `OPENAI_API_KEY` | `@terreno/ai` | ❌ | — | Yes | server |
|
|
122
122
|
| `GOOGLE_VERTEX_PROJECT` | `@terreno/ai` | ❌ | — | No | server |
|
|
123
|
-
| `GOOGLE_VERTEX_LOCATION` | `@terreno/ai` | ❌ |
|
|
123
|
+
| `GOOGLE_VERTEX_LOCATION` | `@terreno/ai` | ❌ | `global` | No | server |
|
|
124
124
|
| `GOOGLE_VERTEX_ALLOWED_MODELS` | `@terreno/ai` | ❌ | all | No | server |
|
|
125
125
|
| `CLAUDE_KEY` | tooling | ❌ | — | Yes | tooling |
|
|
126
126
|
| `LANGFUSE_SECRET_KEY` | `@terreno/ai` | ❌ | — | Yes | server |
|
|
@@ -63,7 +63,7 @@ New code can also use OpenFeature React hooks (`useBooleanFlagValue`, `useString
|
|
|
63
63
|
|
|
64
64
|
## OpenFeature integration
|
|
65
65
|
|
|
66
|
-
`FeatureFlagsApp` registers an OpenFeature **server** provider (`MongoFeatureFlagProvider`) on a dedicated domain (default `"feature-flags"`) so the global default provider is unchanged. The authenticated bulk endpoint **`GET {basePath}/flagConfiguration`** returns a `FlagConfiguration`-compatible map (suitable for `TypedInMemoryProvider` on the client). The legacy **`GET {basePath}/evaluate`** response shape is unchanged but is **deprecated**: responses include `Deprecation: true` and a `Sunset` HTTP-date header (~90 days). Prefer `useTerrenoFeatureFlags` / `useFeatureFlags` from `@terreno/rtk`, or migrate direct HTTP callers to `/flagConfiguration`.
|
|
66
|
+
`FeatureFlagsApp` registers an OpenFeature **server** provider (`MongoFeatureFlagProvider`) on a dedicated domain (default `"feature-flags"`) so the global default provider is unchanged. The authenticated bulk endpoint **`GET {basePath}/flagConfiguration`** returns a `FlagConfiguration`-compatible map (suitable for `TypedInMemoryProvider` on the client). Disabled **boolean** flags are included and resolve to `off`, so a client can tell a turned-off flag from one that was never created. Disabled variant flags and archived flags are omitted. The legacy **`GET {basePath}/evaluate`** response shape is unchanged but is **deprecated**: responses include `Deprecation: true` and a `Sunset` HTTP-date header (~90 days). Prefer `useTerrenoFeatureFlags` / `useFeatureFlags` from `@terreno/rtk`, or migrate direct HTTP callers to `/flagConfiguration`.
|
|
67
67
|
|
|
68
68
|
### `defaultVariant` on `FeatureFlag`
|
|
69
69
|
|