@terreno/mcp 57.7.0 → 57.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/docs/resources/ui.md +5 -4
  2. package/dist/docs/upgrades/57.8.0.md +61 -0
  3. package/dist/docs/versioned/next/explanation/example-coverage.md +1 -0
  4. package/dist/docs/versioned/next/explanation/model-router-actions.md +1 -1
  5. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +14 -0
  6. package/dist/docs/versioned/next/how-to/circleci.md +26 -11
  7. package/dist/docs/versioned/next/how-to/preview-demo.md +19 -0
  8. package/dist/docs/versioned/next/how-to/product-announcements.md +3 -0
  9. package/dist/docs/versioned/next/implementationPlans/demo-workshop.md +219 -0
  10. package/dist/docs/versioned/next/reference/ai.md +3 -1
  11. package/dist/docs/versioned/next/reference/announcement-release-packs.md +146 -0
  12. package/dist/docs/versioned/next/reference/announcements.md +7 -3
  13. package/dist/docs/versioned/next/reference/api.md +1 -0
  14. package/dist/docs/versioned/next/reference/environment-variables.md +1 -0
  15. package/dist/docs/versioned/next/reference/ui.md +14 -6
  16. package/dist/docs/versioned/next/tasks/demo-workshop.md +150 -0
  17. package/package.json +2 -2
  18. package/src/docs/resources/ui.md +5 -4
  19. package/src/docs/upgrades/57.8.0.md +61 -0
  20. package/src/docs/versioned/next/explanation/example-coverage.md +1 -0
  21. package/src/docs/versioned/next/explanation/model-router-actions.md +1 -1
  22. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +14 -0
  23. package/src/docs/versioned/next/how-to/circleci.md +26 -11
  24. package/src/docs/versioned/next/how-to/preview-demo.md +19 -0
  25. package/src/docs/versioned/next/how-to/product-announcements.md +3 -0
  26. package/src/docs/versioned/next/implementationPlans/demo-workshop.md +219 -0
  27. package/src/docs/versioned/next/reference/ai.md +3 -1
  28. package/src/docs/versioned/next/reference/announcement-release-packs.md +146 -0
  29. package/src/docs/versioned/next/reference/announcements.md +7 -3
  30. package/src/docs/versioned/next/reference/api.md +1 -0
  31. package/src/docs/versioned/next/reference/environment-variables.md +1 -0
  32. package/src/docs/versioned/next/reference/ui.md +14 -6
  33. package/src/docs/versioned/next/tasks/demo-workshop.md +150 -0
  34. package/dist/docs/ui-types-documentation.json +0 -33
  35. package/src/docs/ui-types-documentation.json +0 -33
@@ -27,6 +27,7 @@ new TerrenoApp({ userModel: User })
27
27
  | `help.enabled` | `boolean` | `false` | Registers help search/detail routes for MCP and in-app help |
28
28
  | `matchAudience` | `(user, announcement) => boolean` | always `true` | Opaque audience JSON filter composed with `audienceType` via `matchAudienceByType` |
29
29
  | `isStaff` | `(user) => boolean` | `user.admin === true` | Staff check used by `matchAudienceByType` for `audienceType` |
30
+ | `uploadToken` | `string` | unset | Dedicated bearer token for idempotent `POST /announcements/import-release` automation |
30
31
  | `permissions` | partial CRUD overrides | admin-only | Overrides default `IsAdmin` permissions on announcement CRUD |
31
32
 
32
33
  ## Acknowledgement policy
@@ -52,7 +53,7 @@ Legacy MongoDB documents that still store `requiresAcknowledgement: true` map to
52
53
 
53
54
  ## Models
54
55
 
55
- - **Announcement** — `title`, `body` (markdown), `status` (`draft` | `published` | `archived`), `version`, `priority`, `displayMode` (`modal` | `banner` | `feed`, default `modal`), `audienceType` (`staff` | `patient` | `all`, default `all`), `acknowledgementPolicy`, optional `minBuildNumber`, `audience` (Mixed), `publishAt`, `expiresAt`, `platforms`, `primaryAction`
56
+ - **Announcement** — `title`, `body` (markdown), `status` (`draft` | `published` | `archived`), `version`, `priority`, `displayMode` (`modal` | `banner` | `feed`, default `modal`), `audienceType` (`staff` | `patient` | `all`, default `all`), `acknowledgementPolicy`, optional `minBuildNumber`, `audience` (Mixed), `publishAt`, `expiresAt`, `platforms`, `primaryAction`, and optional imported `release` metadata plus `releaseSlug`
56
57
  - **AnnouncementAcknowledgement** — per-user acknowledgement at a specific `version`
57
58
  - **AnnouncementImpression** — per-view analytics row
58
59
  - **AnnouncementClickEvent** — per-click analytics row (`action: "primaryAction"`, `version` at click time, optional `platform`)
@@ -83,7 +84,7 @@ The plugin applies `matchAudienceByType` before `matchAudience`. When `isStaff`
83
84
  | GET | `/announcements/pending` | Current interrupt (`modal` or `banner` only) + `remainingCount` (`platform` query: `ios` \| `android` \| `web`; optional `version` build number) |
84
85
  | GET | `/announcements/feed` | Paginated published changelog — all display modes (`platform` and optional `version` as above) |
85
86
  | POST | `/announcements/:id/acknowledge` | Record acknowledgement (idempotent per version). Same visibility as pending/feed; **404** when not visible. |
86
- | POST | `/announcements/:id/impression` | Record a view. Same visibility as pending/feed; **404** when not visible. |
87
+ | POST | `/announcements/:id/impression` | Record a view (`{ platform? }`). Same visibility as pending/feed; **404** when not visible. **400** for unknown keys or an invalid `platform`. |
87
88
  | POST | `/announcements/:id/click` | Record a primary-action click (`{ action: "primaryAction", platform? }`; optional `?version=` for min-build visibility). Returns **404** when not visible (checked before action/CTA validation). **400** when `action` is invalid, `primaryAction` is absent, or an explicit `platform` value is invalid. |
88
89
 
89
90
  `current` and feed items include resolved `requiresAcknowledgement` (boolean) and `displayMode` derived from policy resolution and stored fields above.
@@ -96,7 +97,7 @@ Optional integer build number on `GET /pending`, `GET /feed`, and help routes. W
96
97
  - `?version=10` (or higher) shows it
97
98
  - omitting `version` does **not** hide gated items
98
99
 
99
- `POST /announcements/:id/acknowledge`, `POST /announcements/:id/impression`, and `POST /announcements/:id/click` use the same visibility rules as pending/feed (schedule, expiry, platform, min build, `audienceType`, and `matchAudience`). All three return **404** when the announcement is not visible to the caller, so targeted IDs cannot be confirmed by writing event rows. Click checks visibility before action/CTA validation. Returns **400** when `action` is not `"primaryAction"`, the announcement has no `primaryAction`, or an explicit body `platform` is not `ios` / `android` / `web` (omit `platform` to use query/user-agent resolution). Each click inserts a new row (not idempotent). Acknowledge is idempotent per `(userId, announcementId, version)`.
100
+ `POST /announcements/:id/acknowledge`, `POST /announcements/:id/impression`, and `POST /announcements/:id/click` use the same visibility rules as pending/feed (schedule, expiry, platform, min build, `audienceType`, and `matchAudience`). All three return **404** when the announcement is not visible to the caller, so targeted IDs cannot be confirmed by writing event rows. Impression and click bodies are validated with strict Zod schemas (`{ platform? }` and `{ action: "primaryAction", platform? }`); unknown keys or invalid values return **400** `Validation failed` with per-field messages in `meta.fields`. Click validates `platform` first, then checks visibility before validating `action` and the CTA. Returns **400** when `action` is not `"primaryAction"`, the announcement has no `primaryAction`, or an explicit body `platform` is not `ios` / `android` / `web` (omit `platform` to use query/user-agent resolution). Each click inserts a new row (not idempotent). Acknowledge is idempotent per `(userId, announcementId, version)`.
100
101
 
101
102
  ## Admin routes
102
103
 
@@ -104,9 +105,12 @@ Admin CRUD is on `/announcements` via `modelRouter`. Custom actions:
104
105
 
105
106
  - `GET /announcements/config` — returns `{ data: { defaultAcknowledgementPolicy } }` from the plugin constructor (admin only; defaults to `"dismiss-only"` when omitted). Used to pre-fill the admin editor acknowledgement policy field.
106
107
  - `GET /announcements/overview` — paginated admin dashboard with per-announcement metrics and aggregate totals. Access uses `adminOverviewPermissions` (defaults to `IsAdmin`). Query: `page` (default `1`), `limit` (default `20`, max `100`).
108
+ - `POST /announcements/import-release` — idempotently imports up to 100 announcements keyed by release product/version/channel plus slug. Admin auth or the configured `uploadToken` bearer token is required. New items default to draft; request `publish: true` to publish them. The example deployment's token is `terreno-example-announcement-upload`, seeded by Terraform.
107
109
  - `POST /announcements/:id/publish` — draft → published
108
110
  - `POST /announcements/:id/archive` — published → archived
109
111
 
112
+ See [Announcement release packs](announcement-release-packs.md) for the Markdown/frontmatter format, JSON request shape, version rules, and draft/live behavior.
113
+
110
114
  ### `GET /announcements/overview`
111
115
 
112
116
  Admin-only list for launch dashboards. Includes **all** lifecycle statuses (`draft`, `published`, `archived`). Soft-deleted announcements and event rows are excluded.
@@ -222,6 +222,7 @@ export const todoRouter = modelRouter("/todos", Todo, {
222
222
  ```
223
223
 
224
224
  Do not add `endpoints: (router) => { router.get(...) }` when an action fits.
225
+ Action config supports `allowAnonymous: true` as a per-action override; it does not change authentication for CRUD or sibling actions.
225
226
 
226
227
  ## modelRouter array operations
227
228
 
@@ -239,6 +239,7 @@ secrets and `PUBLIC_API_URL` / `COMMS_WEBHOOK_PUBLIC_URL` as above. See
239
239
  | Variable | Read by | Required | Default | Secret | Scope |
240
240
  |----------|---------|----------|---------|--------|-------|
241
241
  | `ADMIN_SPA_ENABLED` | example-backend | ❌ | — | No | server |
242
+ | `ANNOUNCEMENTS_UPLOAD_TOKEN` | example-backend `AnnouncementsApp` | ❌ | example deploy: `terreno-example-announcement-upload` | Yes outside the example app | server |
242
243
  | `ADMIN_SPA_DIST_DIR` | example-backend Dockerfile | ❌ | — | No | server |
243
244
  | `ADMIN_SPA_DEV_PROXY` | admin-spa plugin | ❌ | — | No | server |
244
245
  | `ADMIN_SPA_BACKEND_URL` | admin-spa tests | ❌ | — | No | tooling |
@@ -320,9 +320,17 @@ images once per mount.
320
320
  ### SplitPage
321
321
 
322
322
  Master-detail layout. Pass `listViewData` plus `renderListViewItem` for the list, and
323
- `renderContent` for the detail pane. On large screens both panes stay visible. On small
323
+ `renderContent` or children for the detail pane. On large screens both panes stay visible. On small
324
324
  screens the detail replaces the list until the user goes back.
325
325
 
326
+ `desktopChildrenMinWidth` opts into a minimum pixel width for each desktop child when there
327
+ are one or two children. `narrowViewportChildLabels` opts into a labeled full-width pager on
328
+ the narrow viewport. `narrowBelowWidth` uses that viewport when the window is at or below the
329
+ given pixel width; when omitted, the narrow viewport follows `isNarrowViewport()`. These props
330
+ are web only; the native `SplitPage` ignores them. See `SplitPageProps` for when each prop
331
+ applies and what is ignored.
332
+ `IconButton`'s `backgroundOpacity` tints only that button's background.
333
+
326
334
  ```typescript
327
335
  import {SplitPage, Text} from "@terreno/ui";
328
336
 
@@ -627,7 +635,7 @@ import {
627
635
  mediaQuery,
628
636
  mediaQueryLargerThan,
629
637
  mediaQuerySmallerThan,
630
- isMobileDevice,
638
+ isNarrowViewport,
631
639
  } from "@terreno/ui";
632
640
 
633
641
  // Read the current breakpoint
@@ -645,9 +653,9 @@ if (mediaQuerySmallerThan("lg")) {
645
653
  console.log("Smaller than large");
646
654
  }
647
655
 
648
- // Detect mobile
649
- if (isMobileDevice()) {
650
- console.info("Running on mobile device");
656
+ // Current window is below the desktop breakpoint
657
+ if (isNarrowViewport()) {
658
+ console.info("Narrow viewport");
651
659
  }
652
660
  ``````
653
661
 
@@ -673,7 +681,7 @@ Web (desktop staff):
673
681
 
674
682
  On web, `sm` (320) and `md` (375) still classify widths below 1024 so layouts can remain accessible.
675
683
 
676
- `isMobileDevice()` is true below the supported desktop floor: native width < 1024 (`xl`), web width < 1024 (`lg`).
684
+ `isNarrowViewport()` is true below the supported desktop floor: native width < 1024 (`xl`), web width < 1024 (`lg`). `isMobileDevice()` is the same check and is deprecated in favor of `isNarrowViewport`.
677
685
 
678
686
  Responsive `Box` direction props update automatically when the window resizes or a device rotates:
679
687
 
@@ -0,0 +1,150 @@
1
+ # Task List: Demo workshop
2
+
3
+ See: [`docs/implementationPlans/demo-workshop.md`](../implementationPlans/demo-workshop.md)
4
+
5
+ **RTK deprecation flag:** None
6
+
7
+ ## Instructions for the implementing agent
8
+
9
+ - Work the frontier: any task whose blockers are complete. The tracer is Task 1.1 (Button).
10
+ - Do not start iOS work, a translation catalog, or an addon API.
11
+ - Docs named on a task ship in that task. Use the `update-docs` skill.
12
+ - UI tasks also use `verify-ui-changes` and `terreno-ui`.
13
+ - Visual tasks use `review-chart-visuals` for the PNG diff workflow, then the new demo how-to.
14
+ - Run the task's verification command before handoff. `bun run lint` and `bun run demo:compile` on every demo task.
15
+
16
+ ## Phase 1: Catalog, snippets, and preview bar
17
+
18
+ - [x] **Task 1.1**: Button catalog contract
19
+ - Delivers: Button has a copyable usage snippet, resolvable related links, stable stories, one declarative interaction list, and typed control defaults.
20
+ - Files: `demo/demoConfig.tsx`, `demo/story-config/Button.config.tsx`, `demo/stories/Button.stories.tsx`, `demo/collectRegisteredStoryRenders.ts`, `demo/app/demo/[component].tsx`, `demo/README.md`
21
+ - Blocked by: none
22
+ - Acceptance: a unit test fails when a related name does not resolve, when an excluded story has an empty reason, or when a boolean control defaults to `""`. Button's snippet renders and copies. `showInDemo: false` hides a Button story in demo mode and leaves it on the dev route.
23
+ - Verification: `bun test demo/collectRegisteredStoryRenders.ts demo/demoConfig` (or the new colocated test), `bun run demo:compile`, and a web demo pass that opens Button, copies the snippet, and follows one related link.
24
+ - Docs: `demo/README.md` catalog-contract section
25
+ - Skills: `update-docs`, `terreno-ui`, `verify-ui-changes`
26
+
27
+ - [x] **Task 1.2**: Search, categories, and related links
28
+ - Delivers: the home grid and dev index filter by text and category, and every component's related row is links.
29
+ - Files: `demo/components/DemoHomePage.tsx`, `demo/components/DevHomePage.tsx`, `demo/app/demo/[component].tsx`, story configs whose `related` strings do not resolve, `demo/README.md`
30
+ - Blocked by: Task 1.1
31
+ - Acceptance: searching "button" shows Button; choosing a category hides other categories; a broken related name fails the catalog test; the home grid still does not nest pressables (`demo/components/DemoCard.test.tsx`).
32
+ - Verification: catalog unit test, `demo/components/DemoCard.test.tsx`, and a web demo pass of search, category filter, and a related link.
33
+ - Docs: `demo/README.md`
34
+ - Skills: `update-docs`, `terreno-ui`, `verify-ui-changes`
35
+
36
+ - [x] **Task 1.3**: Usage snippet on every component
37
+ - Delivers: every registered story config has `usageExample`, and the component page can copy it.
38
+ - Files: `demo/story-config/*.config.tsx`, `demo/app/demo/[component].tsx`, `scripts/check-demo-coverage.ts` or a sibling check, `demo/README.md`
39
+ - Blocked by: Task 1.1
40
+ - Acceptance: `bun run check:demo-coverage` (or the new snippet check it calls) fails when any registered config lacks a non-empty `usageExample`. The Button page copy control still works.
41
+ - Verification: the check command, plus one web copy action on a second component (TextField).
42
+ - Docs: `demo/README.md`
43
+ - Skills: `update-docs`, `verify-ui-changes`
44
+
45
+ - [x] **Task 1.4**: Preview toolbar and URL state
46
+ - Delivers: demo and dev component routes change theme, viewport, background, locale, right-to-left, and reduced motion from the URL. Embeds apply that state and hide the bar.
47
+ - Files: `demo/components/DemoChrome.tsx`, `demo/app/_layout.tsx`, `demo/app/demo/_layout.tsx`, `demo/app/dev/_layout.tsx`, `demo/contexts/EmbedModeContext.tsx`, `docs/how-to/preview-demo.md`, `demo/README.md`
48
+ - Blocked by: Task 1.1
49
+ - Acceptance: Button at a documented query uses the dark remap and the 375 viewport. Consent or date stories that already read locale receive the toolbar locale. Embed hides the bar. Reloading keeps the query.
50
+ - Verification: a focused demo unit test for query parse/serialize, `bun run demo:compile`, and a web demo pass of the Button query plus one embed URL.
51
+ - Docs: `docs/how-to/preview-demo.md`, `demo/README.md`
52
+ - Skills: `update-docs`, `terreno-ui`, `verify-ui-changes`
53
+
54
+ ## Phase 2: Web proof and health
55
+
56
+ - [ ] **Task 2.1**: Web interaction and axe for Button
57
+ - Delivers: Playwright runs Button's step list and axe against the Button stories.
58
+ - Files: `scripts/demo/runWebStoryChecks.ts`, `demo/story-config/Button.config.tsx`, root `package.json` (axe dependency if needed), `.circleci/config.yml`, `.circleci/continue-config.yml`, `docs/how-to/run-tests-locally.md`, `docs/reference/test.md`
59
+ - Blocked by: Task 1.1
60
+ - Acceptance: the Button interaction steps pass in Chromium. A serious axe violation fails the command. An excluded story with a reason is skipped. The job is required for `ui/**` and `demo/**` pull requests.
61
+ - Verification: `bun run demo:web-checks --only=Button` (the script this task adds) locally against demo web, then the CircleCI path-filter test if one exists beside `.circleci/`.
62
+ - Docs: `docs/how-to/run-tests-locally.md`, `docs/reference/test.md`
63
+ - Skills: `update-docs`, `verify-ui-changes`
64
+
65
+ - [ ] **Task 2.2**: Web visual harness for Button
66
+ - Delivers: one cropped web PNG baseline for each stable Button story, compared with the chart pixel matcher.
67
+ - Files: `scripts/demo/compareDemoSnapshots.ts`, `demo/rendered-snapshots/web/`, `scripts/charts/compareChartImages.ts` (reuse only), `docs/how-to/compare-demo-snapshots.md`, `docs/reference/ui.md`
68
+ - Blocked by: Task 1.1
69
+ - Acceptance: `bun run demo:snapshots:compare --only=Button` passes against the new goldens and fails when a golden is missing. Chart compare still passes for the existing chart fixtures.
70
+ - Verification: the new compare command and `bun run ui:charts:compare`.
71
+ - Docs: `docs/how-to/compare-demo-snapshots.md`, `docs/reference/ui.md`, `docs/how-to/compare-chart-rendered-snapshots.md` (pointer only)
72
+ - Skills: `update-docs`, `review-chart-visuals`
73
+
74
+ - [ ] **Task 2.3**: Web proof for every stable story
75
+ - Delivers: every stable story has interaction steps or an exclusion reason, an axe run, and a web PNG baseline.
76
+ - Files: `demo/story-config/*.config.tsx`, `demo/stories/*.stories.tsx`, `demo/rendered-snapshots/web/`, the web check and snapshot scripts
77
+ - Blocked by: Task 2.1, Task 2.2
78
+ - Acceptance: the web check and snapshot commands, with no `--only`, exit 0. Removing one stable story's golden or steps fails the matching command. Excluded stories name a reason.
79
+ - Verification: `bun run demo:web-checks` and `bun run demo:snapshots:compare`.
80
+ - Docs: `docs/how-to/compare-demo-snapshots.md`, `docs/reference/test.md`
81
+ - Skills: `update-docs`, `review-chart-visuals`
82
+
83
+ - [ ] **Task 2.4**: Story health and CI summary
84
+ - Delivers: the component page shows render, interaction, accessibility, and visual status from `demo/story-health.json`. CI stores that file and writes a check summary.
85
+ - Files: `demo/app/demo/[component].tsx`, `scripts/demo/writeStoryHealth.ts`, `.circleci/continue-config.yml`, `docs/reference/test.md`
86
+ - Blocked by: Task 2.3
87
+ - Acceptance: Button shows a pass row after the web commands. A forced failing story shows fail. A missing health file shows not-run. The CircleCI job summary lists failed story ids. The job does not call `gh pr comment`.
88
+ - Verification: unit test for health rendering, the web commands, and a CI config test or review that the job uploads artifacts and writes the summary.
89
+ - Docs: `docs/reference/test.md`, `demo/README.md`
90
+ - Skills: `update-docs`, `verify-ui-changes`
91
+
92
+ ## Phase 3: Android
93
+
94
+ - [ ] **Task 3.1**: Android proof for Button
95
+ - Delivers: a CircleCI Android emulator job runs Button's steps, accessibility-tree assertions, and a cropped Android PNG.
96
+ - Files: `.circleci/config.yml`, `.circleci/continue-config.yml`, `scripts/demo/generateMaestroFlows.ts`, `.maestro/flows/demo/generated/`, `demo/rendered-snapshots/android/`, `docs/how-to/circleci.md`, `docs/how-to/run-tests-locally.md`
97
+ - Blocked by: Task 2.1
98
+ - Acceptance: Pixel 6 API 34 runs the generated Button flow. The job is required when `ui/**` or `demo/**` changes. A missing Android golden fails the job. Web axe is unchanged.
99
+ - Verification: the local Maestro command documented in the how-to, against an emulator, for the Button flow. Record the CircleCI job name.
100
+ - Docs: `docs/how-to/circleci.md`, `docs/how-to/run-tests-locally.md`, `docs/reference/test.md`
101
+ - Skills: `update-docs`, `verify-ui-changes`
102
+
103
+ - [ ] **Task 3.2**: Android proof for every stable story
104
+ - Delivers: every stable story has an Android flow, accessibility-tree check, and PNG, or an exclusion reason.
105
+ - Files: generated Maestro flows, `demo/rendered-snapshots/android/`, `demo/story-health.json` writer
106
+ - Blocked by: Task 3.1, Task 2.3
107
+ - Acceptance: the Android job without `--only` exits 0. Health on Button includes the Android columns. An excluded story is absent from the Android failure list and its reason is in the summary.
108
+ - Verification: the Android compare and Maestro commands across the stable catalog.
109
+ - Docs: `docs/how-to/compare-demo-snapshots.md`, `docs/reference/test.md`
110
+ - Skills: `update-docs`, `review-chart-visuals`
111
+
112
+ ## Phase 4: Major versions
113
+
114
+ - [ ] **Task 4.1**: Version manifest and dropdown
115
+ - Delivers: production stays on the current major, `versions.json` lists released majors and a `preview/<major>` branch, and the top bar dropdown navigates there. The default is the highest released major.
116
+ - Files: `scripts/demo/publishDemoVersions.ts`, `scripts/ci/netlify-deploy.sh`, `demo/components/DemoChrome.tsx`, `docs/how-to/demo-versions.md`, `.circleci/continue-config.yml`
117
+ - Blocked by: Task 1.4
118
+ - Acceptance: a unit test of the manifest builder marks `preview/58` as preview and not current, marks 57 as current, and omits `pr-*` aliases. The dropdown on the home page shows those labels. `versions.json` on the production origin sends CORS headers the aliases can read.
119
+ - Verification: the manifest unit test, a deploy dry-run, and a web demo pass of the dropdown.
120
+ - Docs: `docs/how-to/demo-versions.md`, `demo/README.md`
121
+ - Skills: `update-docs`, `verify-ui-changes`
122
+
123
+ - [ ] **Task 4.2**: Freeze the previous major and backfill 56
124
+ - Delivers: moving master from 57 to a later major freezes 57 on alias `v57`. Alias `v56` serves the last master commit whose package major is 56.
125
+ - Files: `scripts/demo/publishDemoVersions.ts`, `.circleci/continue-config.yml`, `docs/how-to/demo-versions.md`
126
+ - Blocked by: Task 4.1
127
+ - Acceptance: a test with a fake deploy history freezes the previous production deploy when the major increases, and leaves 57 updating on ordinary master commits. The backfill command documents the git revision it deployed for 56.
128
+ - Verification: unit test of the freeze decision, plus the one-time 56 alias deploy recorded in the how-to.
129
+ - Docs: `docs/how-to/demo-versions.md`
130
+ - Skills: `update-docs`
131
+
132
+ - [ ] **Task 4.3**: Docs embeds follow the demo major
133
+ - Delivers: a 57 docs page embeds the 57 demo. The next docs version embeds the 58 preview when the manifest lists it.
134
+ - Files: `website/docusaurus.config.ts`, `website/src/components/ComponentDemo/index.tsx`, `docs/how-to/demo-versions.md`, the docs reference page that describes component embeds
135
+ - Blocked by: Task 4.1
136
+ - Acceptance: `ComponentDemo` for the 57 docs build requests the 57 demo URL with `?embed=1`. When the manifest has an unreleased preview, the next docs build uses that preview URL. A missing preview falls back to production.
137
+ - Verification: a website unit test or build-arg test for the URL choice, and `bun run website:build`.
138
+ - Docs: `docs/how-to/demo-versions.md` and the existing component-embed reference section
139
+ - Skills: `update-docs`
140
+
141
+ ## Phase 5: MCP catalog
142
+
143
+ - [ ] **Task 5.1**: Public demo catalog tools
144
+ - Delivers: `terreno_list_demo_stories` and `terreno_get_demo_story` return Button's snippet, route, story ids, and the version manifest URL.
145
+ - Files: `demo/demo-catalog.json`, `demo/package.json`, `mcp-server/src/tools.ts`, MCP tests, `docs/reference/mcp-server.md`
146
+ - Blocked by: Task 1.3, Task 4.1
147
+ - Acceptance: `terreno_get_demo_story` for Button includes `usageExample` and a `/demo/Button` route. The list tool filters by category. The response points at production `versions.json`. A catalog drift test fails when a registered component is missing from the JSON.
148
+ - Verification: `cd mcp-server && bun test` for the new tool tests, and `bun run demo:catalog` followed by the drift check.
149
+ - Docs: `docs/reference/mcp-server.md`, `demo/README.md`
150
+ - Skills: `update-docs`
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "@modelcontextprotocol/node": "^2.0.0",
12
12
  "@modelcontextprotocol/server": "^2.0.0",
13
13
  "@sentry/bun": "^10.39.0",
14
- "@terreno/api": "57.7.0",
14
+ "@terreno/api": "57.8.0",
15
15
  "express": "^5.2.1",
16
16
  "luxon": "^3.7.2",
17
17
  "minisearch": "^7.2.0",
@@ -104,5 +104,5 @@
104
104
  "test:coverage": "bun run ../scripts/check-coverage.ts"
105
105
  },
106
106
  "type": "module",
107
- "version": "57.7.0"
107
+ "version": "57.8.0"
108
108
  }
@@ -253,7 +253,7 @@ radiusSm, radiusMd, radiusLg, radiusXl, radius2xl, radius3xl
253
253
  ## Utilities
254
254
 
255
255
  ```typescript
256
- import { useStoredState, MediaQuery, isMobileDevice } from "@terreno/ui";
256
+ import { useStoredState, MediaQuery, isNarrowViewport } from "@terreno/ui";
257
257
 
258
258
  // Persist state to storage
259
259
  const [value, setValue] = useStoredState("key", defaultValue);
@@ -263,9 +263,10 @@ const [value, setValue] = useStoredState("key", defaultValue);
263
263
  <DesktopLayout />
264
264
  </MediaQuery>
265
265
 
266
- // Device detection
267
- if (isMobileDevice()) {
268
- // Mobile-specific code
266
+ // Current window is below the desktop breakpoint.
267
+ // isMobileDevice() is deprecated in favor of isNarrowViewport().
268
+ if (isNarrowViewport()) {
269
+ // Narrow-viewport code
269
270
  }
270
271
  ```
271
272
 
@@ -0,0 +1,61 @@
1
+ # Upgrading to 57.8.0
2
+
3
+ **Action required:** No
4
+ **Affected packages:** @terreno/ui, @terreno/announcements
5
+
6
+ ## Summary
7
+
8
+ Bump every lockstep `@terreno/*` package together to `57.8.0`. `@terreno/ui` adds opt-in `SplitPage` web layouts and `IconButton` `backgroundOpacity`, and deprecates `isMobileDevice` in favor of `isNarrowViewport` (the old function still returns the same result). `@terreno/announcements` can import a Markdown release pack through `POST /announcements/import-release`. `DropdownPanel` anchors to its trigger, and `/gpt/prompt` emits each generated image once. No Expo upgrade is required if you are already on `57.x`.
9
+
10
+ ## Breaking changes
11
+
12
+ None
13
+
14
+ ## Deprecations
15
+
16
+ ### `isMobileDevice`
17
+
18
+ **What changed**
19
+
20
+ `isMobileDevice` is deprecated. `isNarrowViewport` is the supported name. Both stay exported and return the same boolean: true when the window is below the supported desktop floor (web width under 1024, native width under 1024).
21
+
22
+ **Why**
23
+
24
+ `isMobileDevice` described a window-width check. The new name matches that behavior.
25
+
26
+ **Migration**
27
+
28
+ ```typescript
29
+ // Before
30
+ import {isMobileDevice} from "@terreno/ui";
31
+
32
+ if (isMobileDevice()) {
33
+ // narrow layout
34
+ }
35
+
36
+ // After
37
+ import {isNarrowViewport} from "@terreno/ui";
38
+
39
+ if (isNarrowViewport()) {
40
+ // narrow layout
41
+ }
42
+ ```
43
+
44
+ ## New capabilities
45
+
46
+ `SplitPage` web props are opt-in. Existing callers keep the previous layout.
47
+
48
+ - `desktopChildrenMinWidth` keeps one or two desktop children at least that many pixels wide and scrolls the row when it is tighter. More than two children keeps the segmented control.
49
+ - `narrowViewportChildLabels`, `narrowViewportSelectionActive`, `narrowViewportSelectionKey`, and `narrowViewportListButtonLabel` replace the dotted narrow swiper with a labeled pager.
50
+ - `narrowBelowWidth` uses the narrow layout when the window width is less than or equal to that pixel value. Omit it to follow `isNarrowViewport()`.
51
+ - Native `SplitPage` ignores these props.
52
+ - `IconButton` `backgroundOpacity` tints only that button's background.
53
+
54
+ `@terreno/announcements` accepts a `terreno.announcement-pack/v1` directory (`pack.yaml` plus one Markdown file per announcement) at `POST /announcements/import-release`. Imports are idempotent by product, release version, channel, and slug, default to drafts, and publish immediately when `publish: true`. Admin auth or bearer `ANNOUNCEMENTS_UPLOAD_TOKEN` can call the route. The token is optional.
55
+
56
+ ## Verification
57
+
58
+ - `bun add` every lockstep `@terreno/*` dependency to `57.8.0`, then `bun run compile` at the app root
59
+ - Replace `isMobileDevice` imports with `isNarrowViewport` before a future major removes the alias
60
+ - Open a `DropdownPanel` whose trigger is not at the start of its row and confirm the panel opens on the trigger
61
+ - Import a draft announcement pack with `POST /announcements/import-release` and confirm a second import updates the same slug instead of inserting a duplicate
@@ -20,6 +20,7 @@ that file (and its imports) to a working example. Gaps stay explicit.
20
20
  | Feature flags + live updates | yes — `FeatureFlagsApp` in `example-backend/src/server.ts`, seed `example-backend/src/scripts/seed-feature-flags.ts` | yes — `useTerrenoFeatureFlags` in `example-frontend/app/_layout.tsx` | Socket-backed live updates share the realtime connection. |
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
+ | 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. |
23
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
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`. |
25
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. |
@@ -101,7 +101,7 @@ HTTP method and scope map to a CRUD permission check:
101
101
  **403** — denied after the instance document is loaded (e.g. `IsOwner` with a document that fails the check).
102
102
  **401** — unauthenticated when a permission requires auth (from existing auth middleware).
103
103
 
104
- `authenticateMiddleware` honors the router’s `allowAnonymous` option, same as CRUD.
104
+ `authenticateMiddleware` honors the router’s `allowAnonymous` option, same as CRUD. An action may set `allowAnonymous: true` to accept unauthenticated requests without making the router's CRUD routes or other actions anonymous. Keep a non-empty permission such as `Permissions.IsAny` and authenticate any service token in the handler.
105
105
 
106
106
  ## Validation
107
107
 
@@ -1257,6 +1257,20 @@ IP and tracking issue; this entry is the release checklist, not a plan.
1257
1257
 
1258
1258
  ---
1259
1259
 
1260
+ ## demo-workshop
1261
+
1262
+ **Title:** `Demo workshop`
1263
+
1264
+ **Labels:** `area:dx`, `type:feature`
1265
+ **Project fields:** Area=`dx`, Target=`Next`, Impact=`Feature`, IP=`demo-workshop`, Status=`Planned`
1266
+
1267
+ 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.
1268
+
1269
+ - **Implementation plan:** [demo-workshop.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/implementationPlans/demo-workshop.md)
1270
+ - **Tasks:** [demo-workshop.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/tasks/demo-workshop.md)
1271
+
1272
+ ---
1273
+
1260
1274
  # Shipped, umbrella, and declined IPs
1261
1275
 
1262
1276
  Tracking issues created for IPs that previously lacked a `**Roadmap issue:**` header.
@@ -351,20 +351,35 @@ a closed PR.
351
351
  `manual-publish-package` also accepts `feature-flags`. Versions must be semver.
352
352
  Semver git tags (`57.3.0`, `57.3.0-beta.1`) automatically start
353
353
  `publish-release`; prereleases publish to their prerelease npm dist-tag.
354
- `scripts/ci/publish-package.sh` pins `workspace:*` to the tag version for the
355
- tarball, then compiles and tests against the root workspace install. It must
356
- not `bun install` after that pin: sibling `@terreno/*` packages are not on npm
357
- yet, so bun would look up `@terreno/test@X.Y.Z` (and similar) on the registry
358
- and fail the whole job. Tests use `test:ci` when that script exists, not
359
- `test`. `@terreno/ui`'s `test` is `bun test --watch` and would hang the
360
- publish step after the suite finishes. `publish-release` uses a 20-minute
361
- no-output timeout as a backstop. If a package's tag version is already on npm,
362
- `publish-package.sh` skips it so a recut of the same tag can finish the rest.
363
-
364
- Only stable tags (`57.3.0`) run `deploy-demo` after publish. Use
354
+ `publish-release` compiles the 16 lockstep packages once with
355
+ `bun run --filter ... compile` (dependency order), then runs
356
+ `scripts/ci/publish-package.sh` for all of them in parallel with
357
+ `TERRENO_PUBLISH_PREBUILT=1`. It does not rerun tests: the tagged commit
358
+ already passed CI on master, and rerunning every suite per package made
359
+ releases take 20+ minutes. The job has no Mongo sidecar. Each publish writes
360
+ its own temporary npmrc (`NPM_CONFIG_USERCONFIG`) so parallel publishes do not
361
+ remove each other's token.
362
+
363
+ `publish-package.sh` pins `workspace:*` to the tag version for the tarball.
364
+ It must not `bun install` after that pin: sibling `@terreno/*` packages are
365
+ not on npm yet, so bun would look up `@terreno/test@X.Y.Z` (and similar) on
366
+ the registry and fail the whole job. Without `TERRENO_PUBLISH_PREBUILT` (the
367
+ manual single-package publish), it compiles the package and its workspace
368
+ dependencies and runs `test:ci` when that script exists, not `test`.
369
+ `@terreno/ui`'s `test` is `bun test --watch` and would hang. If a package's
370
+ tag version is already on npm, `publish-package.sh` skips it so a recut of
371
+ the same tag can finish the rest.
372
+
373
+ Only stable tags (`57.3.0`) run `deploy-demo`. It builds the demo from source,
374
+ so it runs alongside `publish-release` instead of waiting for it. Use
365
375
  `{"run-demo-deploy":true}` on `master` if a prerelease must also refresh the
366
376
  demo site.
367
377
 
378
+ `scripts/ci/netlify-deploy.sh` builds each site itself and calls
379
+ `netlify deploy --no-build`. Without `--no-build`, netlify-cli runs the root
380
+ `netlify.toml` docs build (wipe `node_modules`, reinstall, full Docusaurus),
381
+ which added about 3.5 minutes to every demo and frontend deploy.
382
+
368
383
  CircleCI does not receive GitHub `pull_request.closed` events, so
369
384
  `.github/workflows/preview-cleanup.yml` forwards them: it runs master's
370
385
  `scripts/ci/trigger-circleci-pipeline.sh` with `{"run-preview-cleanup":"<PR>"}`
@@ -0,0 +1,19 @@
1
+ # Preview a component demo
2
+
3
+ Open a component demo and change the shareable query. The preview bar is on demo and
4
+ dev component routes. `?embed=1` applies the same query and hides the bar.
5
+
6
+ ```text
7
+ /demo/Button?theme=dark&viewport=375&background=inverse&locale=en-US&rtl=1&reducedMotion=1
8
+ ```
9
+
10
+ | Query | Values |
11
+ | --- | --- |
12
+ | `theme` | `light` (default) or `dark` (canonical dark role remap) |
13
+ | `viewport` | `full` (default), `320`, `375`, `1024`, `1280` |
14
+ | `background` | `default`, `inverse`, `transparent` |
15
+ | `locale` | Passed to components that already read a locale, such as consent |
16
+ | `rtl` | `1` sets document direction to `rtl` on web |
17
+ | `reducedMotion` | `1` sets `data-reduced-motion="1"` on the document element |
18
+
19
+ Reload keeps the query. The bar shows the current query when any value is not the default. Choosing a control's default removes that key from the URL. Leaving the component route restores the app theme and, on web, document direction and reduced motion. Custom palettes stay on `/palette`.
@@ -11,6 +11,7 @@ import {AnnouncementsApp} from "@terreno/announcements";
11
11
 
12
12
  new AnnouncementsApp({
13
13
  defaultAcknowledgementPolicy: "dismiss-only",
14
+ uploadToken: process.env.ANNOUNCEMENTS_UPLOAD_TOKEN,
14
15
  // audienceType staff/patient/all is applied inside the plugin via matchAudienceByType.
15
16
  isStaff: (user) => (user as {admin?: boolean}).admin === true,
16
17
  matchAudience: (user, announcement) => {
@@ -46,6 +47,8 @@ For Flourish-style surfaces on one collection:
46
47
 
47
48
  2. Create announcements in admin (draft → publish). Use `AnnouncementOverview`, `AnnouncementEditor`, and (optionally) `AnnouncementList` from `@terreno/admin-frontend` with dedicated Expo routes (see `example-frontend/app/admin/announcements/`). The built-in admin screen widget key `announcements` renders the overview when your host uses `AdminScreenRouter` for custom screens. Published `title`/`body` edits auto-increment `version`, which re-shows the surface to users who only acknowledged the previous version.
48
49
 
50
+ For release-driven batches, use an [announcement release pack](../reference/announcement-release-packs.md). It keeps one `pack.yaml` manifest plus multiple Markdown announcements in source control, validates release and build versioning, and can import idempotent drafts or explicitly publish the whole pack through a dedicated bearer token. The example Cloud Run service accepts `Authorization: Bearer terreno-example-announcement-upload` (Terraform variable `announcements_upload_token`).
51
+
49
52
  ### Admin editor fields
50
53
 
51
54
  `AnnouncementEditor` exposes structured targeting controls (no raw `requiresAcknowledgement` boolean):