@terreno/agent-plugin 0.0.0-stage → 58.0.0-beta.2
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/.claude-plugin/plugin.json +32 -0
- package/CHANGELOG.md +2213 -0
- package/LICENSE +21 -0
- package/README.md +57 -2
- package/agents/pre-commit.md +65 -0
- package/agents/ui-verifier.md +93 -0
- package/package.json +32 -4
- package/references/async-review-bots.md +76 -0
- package/references/documentation-contract.md +61 -0
- package/references/execution-state.schema.json +77 -0
- package/references/github-attention-contract.md +165 -0
- package/references/independent-review.md +31 -0
- package/references/lifecycle-contract.md +211 -0
- package/references/loop-engineering.md +125 -0
- package/references/pick-roast-loop.md +112 -0
- package/references/pr-deployments.md +69 -0
- package/references/product-ci.md +164 -0
- package/references/push-ownership.md +91 -0
- package/references/stage-result.schema.json +82 -0
- package/references/subagent-briefing.md +43 -0
- package/references/testing-plan.md +163 -0
- package/references/user-guide.md +171 -0
- package/skills/1-grow/SKILL.md +145 -0
- package/skills/1-grow/references/grilling.md +243 -0
- package/skills/2-pick/SKILL.md +170 -0
- package/skills/2-pick/references/mocking.md +34 -0
- package/skills/2-pick/references/testing.md +43 -0
- package/skills/3-roast/SKILL.md +168 -0
- package/skills/4-brew/SKILL.md +201 -0
- package/skills/5-taste/SKILL.md +311 -0
- package/skills/ai-prompt-governance/SKILL.md +100 -0
- package/skills/attach-pr-media/SKILL.md +89 -0
- package/skills/attach-pr-media/scripts/upload-pr-media.sh +151 -0
- package/skills/backend-test-env/SKILL.md +64 -0
- package/skills/backfill-regression/SKILL.md +101 -0
- package/skills/backfill-user-guide/SKILL.md +96 -0
- package/skills/bootstrap-mcp-server/SKILL.md +244 -0
- package/skills/building-admin-interfaces/SKILL.md +86 -0
- package/skills/building-admin-interfaces/agents/openai.yaml +4 -0
- package/skills/building-admin-interfaces/references/nav-and-shell.md +42 -0
- package/skills/building-admin-interfaces/references/patterns-and-tools.md +74 -0
- package/skills/building-admin-interfaces/references/screens-and-routes.md +50 -0
- package/skills/building-terreno-apps/SKILL.md +294 -0
- package/skills/building-terreno-apps/agents/openai.yaml +4 -0
- package/skills/building-terreno-apps/references/navigation.md +108 -0
- package/skills/building-terreno-apps/references/route-structure.md +104 -0
- package/skills/building-terreno-apps/references/screen-patterns.md +174 -0
- package/skills/debug-syncdb-with-mcp/SKILL.md +75 -0
- package/skills/deploy-gcp/SKILL.md +73 -0
- package/skills/deploy-gcp/references/troubleshooting.md +29 -0
- package/skills/generate-release-announcements/SKILL.md +145 -0
- package/skills/generate-sdk/SKILL.md +165 -0
- package/skills/mongoose-schema-safety/SKILL.md +164 -0
- package/skills/pick-roast-loop/SKILL.md +152 -0
- package/skills/planning-loop/SKILL.md +161 -0
- package/skills/prd/SKILL.md +138 -0
- package/skills/set-up-social-login/SKILL.md +76 -0
- package/skills/store-assets/SKILL.md +221 -0
- package/skills/store-assets/examples/screenshots.yaml +31 -0
- package/skills/store-assets/examples/storeAssets.config.json +44 -0
- package/skills/store-assets/scripts/banner.ts +165 -0
- package/skills/store-assets/scripts/config.ts +255 -0
- package/skills/store-assets/scripts/devClient.ts +635 -0
- package/skills/store-assets/scripts/devices.ts +286 -0
- package/skills/store-assets/scripts/icon.ts +81 -0
- package/skills/store-assets/scripts/maestro/goHome.yaml +21 -0
- package/skills/store-assets/scripts/maestro/login.yaml +27 -0
- package/skills/store-assets/scripts/maestro/openApp.yaml +26 -0
- package/skills/store-assets/scripts/screenshots.ts +848 -0
- package/skills/taste-sweep/SKILL.md +172 -0
- package/skills/terreno-backend-api/SKILL.md +355 -0
- package/skills/terreno-backend-api/agents/openai.yaml +4 -0
- package/skills/terreno-backend-api/references/custom-routes.md +132 -0
- package/skills/terreno-backend-api/references/model-router.md +112 -0
- package/skills/terreno-backend-api/references/server-setup.md +116 -0
- package/skills/terreno-backend-tests/SKILL.md +176 -0
- package/skills/terreno-data-fetching/SKILL.md +288 -0
- package/skills/terreno-data-fetching/agents/openai.yaml +4 -0
- package/skills/terreno-data-fetching/references/auth-and-tokens.md +89 -0
- package/skills/terreno-data-fetching/references/realtime.md +50 -0
- package/skills/terreno-data-fetching/references/sdk-customization.md +55 -0
- package/skills/terreno-frontend-tests/SKILL.md +190 -0
- package/skills/terreno-mobile-tests/SKILL.md +127 -0
- package/skills/terreno-test-setup/SKILL.md +81 -0
- package/skills/terreno-test-setup/references/backend.md +131 -0
- package/skills/terreno-test-setup/references/frontend.md +169 -0
- package/skills/terreno-test-setup/references/mobile.md +134 -0
- package/skills/terreno-ui/SKILL.md +252 -0
- package/skills/terreno-ui/agents/openai.yaml +4 -0
- package/skills/terreno-ui/references/data-display.md +87 -0
- package/skills/terreno-ui/references/feedback.md +122 -0
- package/skills/terreno-ui/references/forms.md +119 -0
- package/skills/terreno-ui/references/layout.md +124 -0
- package/skills/terreno-ui/references/theming.md +87 -0
- package/skills/update-docs/SKILL.md +82 -0
- package/skills/upgrading-terreno/SKILL.md +93 -0
- package/skills/upgrading-terreno/references/ordering.md +40 -0
- package/skills/verify-ui-changes/SKILL.md +605 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backend-test-env
|
|
3
|
+
description: >-
|
|
4
|
+
Invoke when writing tests that mutate `process.env` in `@terreno/api`,
|
|
5
|
+
`@terreno/ai`, or `example-backend` — feature flags, auth secrets, integration
|
|
6
|
+
toggles. Covers the preload contract, `setupEnvironment()`, and safe mutation
|
|
7
|
+
patterns. Lifecycle composition: Pick when writing these tests; Roast when
|
|
8
|
+
independently reproducing env-sensitive behavior.
|
|
9
|
+
---
|
|
10
|
+
# Backend Test — `process.env` Contract
|
|
11
|
+
|
|
12
|
+
Bun's test runner preloads a per-package setup file (configured in each package's `bunfig.toml` under `[test].preload`):
|
|
13
|
+
|
|
14
|
+
| Package | Preload file |
|
|
15
|
+
|---------|--------------|
|
|
16
|
+
| `@terreno/api` | `api/src/tests/bunSetup.ts` |
|
|
17
|
+
| `@terreno/admin-backend` | `admin-backend/src/tests/testEnv.ts` then `api/src/tests/bunSetup.ts` (`testEnv` sets `TERRENO_TEST_USE_MEMORY_MONGO` for **mongodb-memory-server**) |
|
|
18
|
+
| `@terreno/ai` | `ai/src/tests/bunSetup.ts` |
|
|
19
|
+
| `@terreno/comms` | `comms/src/tests/bunSetup.ts` |
|
|
20
|
+
| `example-backend` | `example-backend/src/tests/setup.ts` |
|
|
21
|
+
|
|
22
|
+
The `@terreno/api` preload uses a global `beforeEach` to (1) reset the canonical auth secrets, (2) call `setupEnvironment()` (exported from `expressServer.ts`) for the wider baseline, and (3) re-silence the winston loggers. The next test always starts from the canonical baseline — you normally **do not** need to re-call `setupEnvironment()` in a file-level `beforeEach`.
|
|
23
|
+
|
|
24
|
+
**Mongo in tests:** `api/src/tests/bunSetup.ts` connects to `mongodb://127.0.0.1/terreno` by default. If `TERRENO_TEST_USE_MEMORY_MONGO=true`, it starts **MongoMemoryServer** and connects to that URI instead (then stops it in `afterAll`). `TERRENO_TEST_MONGODB_URI` overrides both and points at any MongoDB you choose.
|
|
25
|
+
|
|
26
|
+
## Default — Rely on the Preload
|
|
27
|
+
|
|
28
|
+
- Do not assume a toggle left by another file. Treat the preload's `beforeEach` as the source of truth for the canonical baseline.
|
|
29
|
+
- Set only the keys this test needs in its own `beforeEach` or test body.
|
|
30
|
+
- Don't reach in and call `setupEnvironment()` again unless you're explicitly testing env-driven behavior that the preload didn't cover.
|
|
31
|
+
|
|
32
|
+
## When a Test Mutates `process.env`
|
|
33
|
+
|
|
34
|
+
1. **Add the key to the preload's reset list** if it should always start from a fixed test value. Otherwise it leaks into the next test in the same worker.
|
|
35
|
+
2. **Set only what this case needs** in the test body — avoid redundant manual restore if the preload already resets that key in `beforeEach`.
|
|
36
|
+
3. **For keys that should be absent (unset)**, use **`Reflect.deleteProperty(process.env, "KEY")`**. Avoid `process.env.KEY = ""` to mean "unset" — on some platforms empty string and unset behave differently and can make tests flaky.
|
|
37
|
+
4. **Avoid whole-env snapshots** (`const OLD_ENV = process.env; ...; process.env = OLD_ENV;`). This pattern exists in some older test files (`auth.test.ts`) — don't propagate it. It captures pre-`beforeEach` state and breaks ordering guarantees if the preload's `beforeEach` runs in between. Use targeted mutations + the preload's reset instead.
|
|
38
|
+
|
|
39
|
+
## Adding a New Required Env Var
|
|
40
|
+
|
|
41
|
+
If you introduce a new `process.env.FOO` that backend code reads at startup:
|
|
42
|
+
|
|
43
|
+
1. Add `FOO` to `setupEnvironment()` in `api/src/expressServer.ts` so every package picks it up via the preload's `setupEnvironment()` call.
|
|
44
|
+
2. If `example-backend` has its own startup needs for it, also add to `example-backend/src/tests/setup.ts`'s `beforeAll`.
|
|
45
|
+
3. If a test needs to mutate it, follow the rules above — don't snapshot the whole env.
|
|
46
|
+
|
|
47
|
+
## Auth-Related Keys
|
|
48
|
+
|
|
49
|
+
`api/src/tests/bunSetup.ts` resets four auth keys in `beforeEach`: `TOKEN_SECRET`, `TOKEN_ISSUER`, `SESSION_SECRET`, `REFRESH_TOKEN_SECRET`. If your test changes any of these, you do not need to restore — the next `beforeEach` already does. If your test relies on them being **absent** for a code path, use `Reflect.deleteProperty` within the test, knowing the next `beforeEach` will restore.
|
|
50
|
+
|
|
51
|
+
`auth.test.ts` is the established home for env-driven auth assertions; mirror its newer patterns (per-test `beforeEach` setting only what changes) rather than its older whole-env-snapshot blocks.
|
|
52
|
+
|
|
53
|
+
## Log Capture & Sentry
|
|
54
|
+
|
|
55
|
+
The preload silences winston and `console.*` and captures into an in-memory buffer (cleared in `beforeEach`/`afterEach`). It also mocks `@sentry/bun`. Don't re-mock Sentry inside individual tests — the preload's mock is shared and reset across tests.
|
|
56
|
+
|
|
57
|
+
## Checklist
|
|
58
|
+
|
|
59
|
+
- [ ] New env var added to `setupEnvironment()` and (if needed) `example-backend/src/tests/setup.ts`
|
|
60
|
+
- [ ] Test mutations are targeted — no whole-env snapshots
|
|
61
|
+
- [ ] "Unset" expressed as `Reflect.deleteProperty(process.env, "KEY")`, not `= ""`
|
|
62
|
+
- [ ] No manual restore that duplicates the preload's `beforeEach` reset
|
|
63
|
+
- [ ] No re-mock of `@sentry/bun` — the preload's mock is already in place
|
|
64
|
+
- [ ] If you added an env-driven branch in code, a test covers both the set and the unset case
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backfill-regression
|
|
3
|
+
description: >-
|
|
4
|
+
Backfill a repository's manual regression suites. Survey the app for behavior that only a
|
|
5
|
+
developer or QA can check by hand (push notifications and other hardware or OS behavior,
|
|
6
|
+
real email or SMS delivery, full cross-device flows, how the app feels) plus a short check
|
|
7
|
+
that the app works as intended, propose candidate cases one area at a time, and have the
|
|
8
|
+
human approve, edit, or deny each set before anything is written. Loops until no
|
|
9
|
+
candidates remain or the human stops. Use when asked to backfill, seed, or audit regression
|
|
10
|
+
tests or a manual QA checklist; not for planning a feature (Grow writes those cases) or for
|
|
11
|
+
writing automated tests.
|
|
12
|
+
---
|
|
13
|
+
# Backfill regression
|
|
14
|
+
|
|
15
|
+
Build the minimal manual pass a developer or QA walks before a release. Every case is
|
|
16
|
+
approved by the human before it is written. The suites stay small: fewer, sharper cases
|
|
17
|
+
beat coverage.
|
|
18
|
+
|
|
19
|
+
Read [`testing-plan`](../../references/testing-plan.md) first. Its "Keep regression
|
|
20
|
+
minimal" section decides what may be a case, its suite file format decides how a case is
|
|
21
|
+
written, and its regression-step bar decides whether a case is good enough.
|
|
22
|
+
|
|
23
|
+
## Inputs
|
|
24
|
+
|
|
25
|
+
- The resolved `E2E guide`, `Regression suites`, and `Smoke command` per the testing plan
|
|
26
|
+
- Every existing suite file, its `Area:` prefix, and its highest ID
|
|
27
|
+
- The ledger at `.terreno/regression-backfill.json`, when present (see Ledger)
|
|
28
|
+
- Optional scope from the human: an area, a package, or a list of features
|
|
29
|
+
|
|
30
|
+
## Procedure
|
|
31
|
+
|
|
32
|
+
When `Regression suites` resolves to `none` (the testing-plan opt-out), stop: report that
|
|
33
|
+
the repository opted out of manual regression suites, and write nothing.
|
|
34
|
+
|
|
35
|
+
1. **Survey.** Use explore subagents in parallel. Collect evidence of behavior automation
|
|
36
|
+
cannot reach, each with a file and line:
|
|
37
|
+
- hardware and OS: push registration and delivery, notification permission, camera,
|
|
38
|
+
photo library, location, biometrics, secure storage across reinstall, deep and
|
|
39
|
+
universal links from outside the app, background and resume
|
|
40
|
+
- delivery outside the app: email, SMS, and push that must reach a real inbox or device
|
|
41
|
+
- full flows across apps, devices, or channels: a change on one device showing on
|
|
42
|
+
another, an admin action reaching a user's phone, offline then online
|
|
43
|
+
- feel: gestures, drawing, keyboard avoidance, animation, layout on small screens
|
|
44
|
+
- the app works: sign in, the main screen loads, the core action succeeds, on each
|
|
45
|
+
shipped platform. One short case per platform, not per screen.
|
|
46
|
+
2. **Filter.** Drop a candidate when an existing suite case already covers it, when an
|
|
47
|
+
e2e spec, smoke, or unit test already proves it, when a guide layer could drive and
|
|
48
|
+
assert it (note it as an automation gap in the final report instead), or when the
|
|
49
|
+
ledger records it as denied. Merge near-duplicates into one case.
|
|
50
|
+
3. **Group into sets.** One set per area, at most five cases, highest risk first. A
|
|
51
|
+
set names its destination suite file, existing or new, with its `Area:` prefix.
|
|
52
|
+
Write each case in the suite table format with a provisional `<AREA>-new-<n>` ID,
|
|
53
|
+
`Automated by: manual`, and `Added: regression-backfill`. Every case meets the
|
|
54
|
+
regression-step bar, and its evidence line explains why it cannot be automated.
|
|
55
|
+
4. **Ask, one set at a time.** Show the set as a table with each case's evidence, and
|
|
56
|
+
ask the human, using the structured question tool when available:
|
|
57
|
+
- **Approve all**: write every case in the set.
|
|
58
|
+
- **Approve some**: the human names the case IDs to keep; deny the rest.
|
|
59
|
+
- **Edit**: the human's changes are applied, and the set is shown again.
|
|
60
|
+
- **Deny all**: write nothing for this set.
|
|
61
|
+
- **Stop**: end the loop now.
|
|
62
|
+
Never write a case the human has not approved.
|
|
63
|
+
5. **Write approved cases.** Append them to the suite file with real IDs: one past the
|
|
64
|
+
highest ID the file has ever held, never reused. A new suite file follows the suite
|
|
65
|
+
file format, with `Last full run: never` and `Owner:` from the human or `unassigned`.
|
|
66
|
+
Add a new area to the suite index when the repository keeps one. Run the repository's
|
|
67
|
+
suite check when it has one, and fix any failure before the next set.
|
|
68
|
+
6. **Record.** Append each case to the ledger as approved or denied, then go to the next
|
|
69
|
+
set. When every set is answered, survey once more for areas the answers opened up;
|
|
70
|
+
stop when a pass yields no new candidate.
|
|
71
|
+
|
|
72
|
+
## Ledger
|
|
73
|
+
|
|
74
|
+
`.terreno/regression-backfill.json` stays out of version control: add `.terreno/` to
|
|
75
|
+
`.gitignore` when it is missing. It keeps a denied case from being proposed again:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{"cases": [{"area": "TODO", "case": "A todo shows on a second device", "decision": "denied", "date": "2026-01-15"}]}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Match a candidate against the ledger by area and meaning, not exact wording.
|
|
82
|
+
|
|
83
|
+
## Report
|
|
84
|
+
|
|
85
|
+
When the loop ends, report once:
|
|
86
|
+
|
|
87
|
+
- cases written, per suite file, with their real IDs
|
|
88
|
+
- sets and cases denied
|
|
89
|
+
- automation gaps: candidates a guide layer could cover, each with the layer and the
|
|
90
|
+
spec path the guide prescribes, offered as follow-up work, not built
|
|
91
|
+
- the suite check result
|
|
92
|
+
|
|
93
|
+
Do not commit or push unless the human asks. A commit message says which suites gained
|
|
94
|
+
cases.
|
|
95
|
+
|
|
96
|
+
## Never
|
|
97
|
+
|
|
98
|
+
- write a case the human did not approve, or re-propose one the ledger records as denied
|
|
99
|
+
- add a case for something an automated test can prove
|
|
100
|
+
- walk a case or change `Last full run`; only a developer or QA does that
|
|
101
|
+
- renumber or delete an existing case
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backfill-user-guide
|
|
3
|
+
description: >-
|
|
4
|
+
Backfill a repository's end-user guide. Survey the shipped app for user-facing features
|
|
5
|
+
(screens, navigation, forms, settings, admin tools) that have no guide page, draft pages in
|
|
6
|
+
the user-guide format one area at a time, and have the human approve, edit, or deny each set
|
|
7
|
+
before anything is written. Loops until every shipped feature has a page or a recorded
|
|
8
|
+
denial, or the human stops. Use when asked to backfill, seed, or audit the user guide, help
|
|
9
|
+
pages, or "how do I" docs for chat agents; not for planning a feature (Grow plans those
|
|
10
|
+
pages) or for developer docs.
|
|
11
|
+
---
|
|
12
|
+
# Backfill user guide
|
|
13
|
+
|
|
14
|
+
Write the pages a user, or a chat agent answering for one, needs to use the features the
|
|
15
|
+
app already ships. Every page is approved by the human before it is written.
|
|
16
|
+
|
|
17
|
+
Read [`user guide`](../../references/user-guide.md) first. Its page format decides how a
|
|
18
|
+
page is written, its guide-entry bar decides whether a page is good enough, and its opt-out
|
|
19
|
+
rule decides whether to run at all: when the repository's resolved `User guide` is `none`,
|
|
20
|
+
report that the guide is off and stop.
|
|
21
|
+
|
|
22
|
+
## Inputs
|
|
23
|
+
|
|
24
|
+
- The resolved `User guide` path per the user-guide contract, its `README.md` index, and
|
|
25
|
+
every existing page with its `area` and `questions`
|
|
26
|
+
- The ledger at `.terreno/user-guide-backfill.json`, when present (see Ledger)
|
|
27
|
+
- The repository's guide check command, when it has one
|
|
28
|
+
- Optional scope from the human: an area, an app, or a list of features
|
|
29
|
+
|
|
30
|
+
## Procedure
|
|
31
|
+
|
|
32
|
+
1. **Survey.** Use explore subagents in parallel, one per app or top-level area. Collect
|
|
33
|
+
each shipped user-facing feature with evidence (a file and line):
|
|
34
|
+
- every routed screen and tab, and how a user reaches it
|
|
35
|
+
- every action a user takes there: create, edit, delete, search, filter, share, export
|
|
36
|
+
- account flows: sign up, sign in, sign out, password reset, profile, settings,
|
|
37
|
+
consent, notifications, deleting an account
|
|
38
|
+
- staff and admin tools, recorded with `audience: admin` or `staff`
|
|
39
|
+
- feature flags and platform checks that hide a feature from some users or platforms
|
|
40
|
+
Read the on-screen labels from the source (button text, titles, headings), never from
|
|
41
|
+
memory, and note which platforms render each screen. When the repository has an
|
|
42
|
+
inventory command for the guide, run it to list screens no page covers.
|
|
43
|
+
2. **Filter.** Drop a feature when an existing page already covers it, when it is not
|
|
44
|
+
reachable by any user (dead code, a flag that is off everywhere, a developer-only
|
|
45
|
+
screen), or when the ledger records it as denied. Merge features a user thinks of as
|
|
46
|
+
one task into one page.
|
|
47
|
+
3. **Group into sets.** One set per area, at most five pages, most-used features first.
|
|
48
|
+
Draft each page in full in the page format, with `added: user-guide-backfill`. Every
|
|
49
|
+
page meets the guide-entry bar: user words only, every step names a real on-screen
|
|
50
|
+
label, and nothing a chat agent must not repeat (personal data, secrets, internal
|
|
51
|
+
URLs, staff procedures on a `user` page).
|
|
52
|
+
4. **Ask, one set at a time.** Show each page's title, audience, platforms, questions, and
|
|
53
|
+
steps, with the evidence for its labels, and ask the human, using the structured
|
|
54
|
+
question tool when available:
|
|
55
|
+
- **Approve all**: write every page in the set.
|
|
56
|
+
- **Approve some**: the human names the pages to keep; deny the rest.
|
|
57
|
+
- **Edit**: the human's changes are applied, and the set is shown again.
|
|
58
|
+
- **Deny all**: write nothing for this set.
|
|
59
|
+
- **Stop**: end the loop now.
|
|
60
|
+
Never write a page the human has not approved.
|
|
61
|
+
5. **Write approved pages.** Write each page at `<User guide>/<area>/<feature>.md` and add
|
|
62
|
+
it to the index table. A missing guide directory gets its `README.md` index first. Run
|
|
63
|
+
the repository's guide check when it has one, and fix any failure before the next set.
|
|
64
|
+
6. **Record.** Append each page to the ledger as approved or denied, then go to the next
|
|
65
|
+
set. When every set is answered, survey once more for features the answers opened up;
|
|
66
|
+
stop when a pass yields no new candidate.
|
|
67
|
+
|
|
68
|
+
## Ledger
|
|
69
|
+
|
|
70
|
+
`.terreno/user-guide-backfill.json` stays out of version control: add `.terreno/` to
|
|
71
|
+
`.gitignore` when it is missing. It keeps a denied page from being proposed again:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{"pages": [{"area": "todos", "title": "Export todos as CSV", "decision": "denied", "date": "2026-01-15"}]}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Match a candidate against the ledger by area and meaning, not exact wording.
|
|
78
|
+
|
|
79
|
+
## Report
|
|
80
|
+
|
|
81
|
+
When the loop ends, report once:
|
|
82
|
+
|
|
83
|
+
- pages written, per area, with their paths
|
|
84
|
+
- sets and pages denied
|
|
85
|
+
- features left without a page, and why
|
|
86
|
+
- the guide check result
|
|
87
|
+
|
|
88
|
+
Do not commit or push unless the human asks. A commit message says which areas gained
|
|
89
|
+
pages.
|
|
90
|
+
|
|
91
|
+
## Never
|
|
92
|
+
|
|
93
|
+
- write a page the human did not approve, or re-propose one the ledger records as denied
|
|
94
|
+
- describe a feature that does not ship, or a label you did not read in the source
|
|
95
|
+
- put personal or health information, real accounts, secrets, or internal URLs in a page
|
|
96
|
+
- run when the repository opts out of the user guide
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bootstrap-mcp-server
|
|
3
|
+
description: Bootstrap a backend-only Terreno MCP server that ships as a single Bun executable with the admin console embedded. Scaffolds the backend with create-terreno-app, drops the frontend, registers MCP tools, service tokens, optional MCP OAuth using the admin console's consent screen, a request log, tool on/off switches, and the MCP admin screens, then compiles everything (admin SPA included) into one binary. Trigger with "bootstrap an MCP server", "new Terreno MCP server", "MCP-only backend", or "ship the MCP server as a binary". Not for a full Expo app (use the terreno_bootstrap prompt / create-terreno-app) or for adding MCP tools to an existing app (see https://terreno-docs.netlify.app/how-to/expose-mcp-tools).
|
|
4
|
+
targets: ['*']
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Bootstrap an MCP server
|
|
8
|
+
|
|
9
|
+
The MCP-only sibling of the `terreno_bootstrap` prompt. Output: one directory with a Terreno
|
|
10
|
+
backend whose product surface is `POST /mcp`, an admin console at `/console`, and a
|
|
11
|
+
`bun run build:binary` script that emits **one executable** with the admin SPA
|
|
12
|
+
embedded. MongoDB stays external (a replica set, as for every Terreno backend).
|
|
13
|
+
|
|
14
|
+
Requires the `@terreno/*` release that ships `mcpAdminModels`, `mcpRequestLog`,
|
|
15
|
+
`mcpToolSettings`, and the admin SPA consent route (check `CHANGELOG.md` for "MCP admin screens";
|
|
16
|
+
`create-terreno-app` scaffolds that version or newer).
|
|
17
|
+
|
|
18
|
+
## When to use
|
|
19
|
+
|
|
20
|
+
- The user wants a remote MCP server (Claude.ai / ChatGPT connectors, Claude Code, Cursor,
|
|
21
|
+
Perplexity) backed by Terreno auth, permissions, and models.
|
|
22
|
+
- The user wants that server deployable as a single file (`scp` + `systemd`, a tiny container,
|
|
23
|
+
or a homelab box behind a Cloudflare Tunnel).
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
Ask only for what is missing:
|
|
28
|
+
|
|
29
|
+
1. `appName` (kebab-case) and `appDisplayName`.
|
|
30
|
+
2. Public origin the server will be reached at (for example `https://mcp.example.com`). Needed
|
|
31
|
+
for `PUBLIC_API_URL`, the OAuth `issuer`, and the Better Auth URL. Use
|
|
32
|
+
`http://localhost:4000` until the user has one.
|
|
33
|
+
3. Which clients must connect: static-key clients (Perplexity, Cursor, Claude Code) need
|
|
34
|
+
**service tokens**; Claude.ai / ChatGPT connectors need **MCP OAuth**. Default: both.
|
|
35
|
+
|
|
36
|
+
## Steps
|
|
37
|
+
|
|
38
|
+
Complete each step before the next. Each ends with a check.
|
|
39
|
+
|
|
40
|
+
### 1. Scaffold the backend only
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bunx create-terreno-app <appName> --display-name "<appDisplayName>"
|
|
44
|
+
cd <appName>
|
|
45
|
+
rm -rf frontend # MCP-only: the admin console is the only UI
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Remove `frontend` from the root workspace list and root scripts. Check: `bun install` succeeds
|
|
49
|
+
and `cd backend && bun run compile` (or `bunx tsc --noEmit`) passes.
|
|
50
|
+
|
|
51
|
+
### 2. Add the admin packages
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd backend
|
|
55
|
+
bun add @terreno/admin-backend @terreno/admin-spa
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Register, in this order, after `BetterAuthApp`:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import {AdminApp, mcpAdminModels, mcpAdminScripts} from "@terreno/admin-backend";
|
|
62
|
+
import {AdminSpaServeApp} from "@terreno/admin-spa";
|
|
63
|
+
|
|
64
|
+
terreno
|
|
65
|
+
.register(
|
|
66
|
+
new AdminApp({
|
|
67
|
+
models: [...appAdminModels, ...mcpAdminModels({requestLog: true, toolSettings: true})],
|
|
68
|
+
scripts: mcpAdminScripts(),
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
.register(
|
|
72
|
+
new AdminSpaServeApp({
|
|
73
|
+
appConfig: {brandName: APP_NAME, providers: ["email"]},
|
|
74
|
+
basePath: "/console",
|
|
75
|
+
// Set by src/embeddedAssets.ts in the compiled binary; undefined in dev.
|
|
76
|
+
distDir: process.env.ADMIN_SPA_DIST_DIR,
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`mcpAdminModels({requestLog: true, toolSettings: true})` adds the **MCP** sidebar group (service
|
|
82
|
+
tokens, OAuth clients, OAuth connections, request log, tools; keep the two flags matched to the
|
|
83
|
+
`TerrenoApp` options) and `mcpAdminScripts()` adds `mcp-sign-out-everywhere`. If the
|
|
84
|
+
app uses RBAC (`accessControl`), spread `mcpAdminStatements` into its statements. See
|
|
85
|
+
https://terreno-docs.netlify.app/how-to/manage-mcp-in-admin. Check: `curl -I localhost:4000/console` returns 200 (the
|
|
86
|
+
published package ships the prebuilt SPA).
|
|
87
|
+
|
|
88
|
+
### 3. Turn on the MCP surface
|
|
89
|
+
|
|
90
|
+
In the `TerrenoApp` options:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
mcpServiceTokens: {enabled: true, publicMcpUrl: process.env.PUBLIC_API_URL},
|
|
94
|
+
mcpOAuth: process.env.MCP_OAUTH_ENABLED === "true"
|
|
95
|
+
? {
|
|
96
|
+
allowedRedirectHosts: ["claude.ai", "chatgpt.com"],
|
|
97
|
+
consentUrl: `${process.env.PUBLIC_API_URL}/console/oauth/consent`,
|
|
98
|
+
dynamicClientRegistration: true,
|
|
99
|
+
enabled: true,
|
|
100
|
+
issuer: process.env.PUBLIC_API_URL!,
|
|
101
|
+
}
|
|
102
|
+
: undefined,
|
|
103
|
+
mcpRequestLog: {enabled: true, retentionDays: 30}, // metadata only, never tool arguments
|
|
104
|
+
mcpToolSettings: true, // admin on/off switch per tool
|
|
105
|
+
rateLimit: {store: "memory", trustProxy: "loopback"}, // registration and token endpoints are unauthenticated
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Expose tools with `mcp: {methods: ["list", "read"]}` on `modelRouter` and custom tools with
|
|
109
|
+
`registerMCPTool` (see https://terreno-docs.netlify.app/how-to/expose-mcp-tools). Ship at least one tool, or `/mcp` is
|
|
110
|
+
not mounted.
|
|
111
|
+
|
|
112
|
+
**Consent screen (OAuth only).** `consentUrl` points at the admin console's built-in consent
|
|
113
|
+
screen (`@terreno/admin-spa`, route `/oauth/consent`). Any signed-in user can approve there, not
|
|
114
|
+
only admins; a signed-out user signs in and comes back. Keep the `/console` part equal to
|
|
115
|
+
`AdminSpaServeApp`'s `basePath`. Check: `curl -s -X POST localhost:4000/mcp` returns 401 with a
|
|
116
|
+
`WWW-Authenticate: Bearer resource_metadata=...` header when OAuth is on.
|
|
117
|
+
|
|
118
|
+
### 4. Check the MCP admin screens
|
|
119
|
+
|
|
120
|
+
Seed an admin, sign in at `/console`, and open the **MCP** group. Check: **MCP tools** lists every
|
|
121
|
+
tool with `enabled: true`; switching one off removes it from `tools/list`.
|
|
122
|
+
|
|
123
|
+
### 5. Embed the admin console in the binary
|
|
124
|
+
|
|
125
|
+
`bun build --compile` resolves `__dirname` to `$bunfs`, so `AdminSpaServeApp` cannot find the
|
|
126
|
+
SPA bundle on disk. The published `@terreno/admin-spa` already ships its prebuilt export in
|
|
127
|
+
`node_modules/@terreno/admin-spa/dist`; embed that and extract it at boot. No Expo or Node build
|
|
128
|
+
step is needed.
|
|
129
|
+
|
|
130
|
+
`scripts/generateEmbeddedAssets.ts` (writes a manifest of `with {type: "file"}` imports):
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import {readdirSync, statSync, writeFileSync} from "node:fs";
|
|
134
|
+
import {join, relative, resolve} from "node:path";
|
|
135
|
+
|
|
136
|
+
const root = resolve(process.argv[2] ?? "node_modules/@terreno/admin-spa/dist");
|
|
137
|
+
const files: string[] = [];
|
|
138
|
+
const walk = (dir: string): void => {
|
|
139
|
+
for (const name of readdirSync(dir)) {
|
|
140
|
+
const path = join(dir, name);
|
|
141
|
+
if (statSync(path).isDirectory()) {
|
|
142
|
+
walk(path);
|
|
143
|
+
} else {
|
|
144
|
+
files.push(path);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
walk(root);
|
|
149
|
+
const lines = files.map((file, i) => `import f${i} from ${JSON.stringify(file)} with {type: "file"};`);
|
|
150
|
+
lines.push(
|
|
151
|
+
`export const ADMIN_SPA_FILES: {path: string; file: string}[] = [${files
|
|
152
|
+
.map((file, i) => `{path: ${JSON.stringify(relative(root, file))}, file: f${i}}`)
|
|
153
|
+
.join(",\n")}];`
|
|
154
|
+
);
|
|
155
|
+
writeFileSync("src/embeddedAdminSpa.generated.ts", `${lines.join("\n")}\n`);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Commit `src/embeddedAdminSpa.generated.ts` as this stub so a fresh clone typechecks and
|
|
159
|
+
`bun run dev` works. `build:binary` (step 6) restores it after compiling:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
export const ADMIN_SPA_FILES: {path: string; file: string}[] = [];
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`src/embeddedAdminSpa.ts`, imported at the top of `src/index.ts` before the server module:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import {mkdirSync, writeFileSync} from "node:fs";
|
|
169
|
+
import {tmpdir} from "node:os";
|
|
170
|
+
import {dirname, join} from "node:path";
|
|
171
|
+
import {ADMIN_SPA_FILES} from "./embeddedAdminSpa.generated";
|
|
172
|
+
|
|
173
|
+
// Extract the embedded admin bundle once per process so AdminSpaServeApp can serve it from disk.
|
|
174
|
+
if (ADMIN_SPA_FILES.length > 0 && !process.env.ADMIN_SPA_DIST_DIR) {
|
|
175
|
+
const out = join(tmpdir(), `<appName>-admin-spa-${process.pid}`);
|
|
176
|
+
for (const {path, file} of ADMIN_SPA_FILES) {
|
|
177
|
+
const dest = join(out, path);
|
|
178
|
+
mkdirSync(dirname(dest), {recursive: true});
|
|
179
|
+
writeFileSync(dest, new Uint8Array(await Bun.file(file).arrayBuffer()));
|
|
180
|
+
}
|
|
181
|
+
process.env.ADMIN_SPA_DIST_DIR = out;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
With the stub (dev), nothing is extracted and `AdminSpaServeApp` uses its package default. If the
|
|
186
|
+
app later adds migrations, embed `migrations/` the same way and set `MIGRATIONS_DIR`.
|
|
187
|
+
|
|
188
|
+
### 6. Build the binary
|
|
189
|
+
|
|
190
|
+
Add to `backend/package.json`:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
"build:binary": "bun scripts/generateEmbeddedAssets.ts && bun build --compile --minify src/index.ts --outfile dist/<appName> && git checkout src/embeddedAdminSpa.generated.ts"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Cross-compile with `--target=bun-linux-x64` (or `bun-linux-arm64`, `bun-darwin-arm64`). Expect a
|
|
197
|
+
binary over 100 MB: the Bun runtime plus about 120 admin assets (fonts, images, JS).
|
|
198
|
+
|
|
199
|
+
### 7. Verify the binary on its own
|
|
200
|
+
|
|
201
|
+
Copy the binary to an empty directory (or move `node_modules` aside) so nothing resolves from
|
|
202
|
+
disk, then:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
MONGO_URI=... BETTER_AUTH_SECRET=... PUBLIC_API_URL=http://localhost:4000 ./dist/<appName> &
|
|
206
|
+
curl -sf localhost:4000/health
|
|
207
|
+
curl -sf -o /dev/null -w '%{http_code}\n' localhost:4000/console # 200
|
|
208
|
+
curl -sf localhost:4000/console/app-config.json
|
|
209
|
+
curl -sf -o /dev/null -w '%{http_code}\n' localhost:4000/console/_expo/static/js/web/<entry>.js # 200
|
|
210
|
+
curl -s -X POST localhost:4000/mcp -H 'content-type: application/json' \
|
|
211
|
+
-H "authorization: Bearer mcp_..." \
|
|
212
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Seed an admin, log in at `/console`, mint a service token with `POST /mcp/service-tokens`, run
|
|
216
|
+
`tools/list` with it, and confirm a row appears in **MCP request log** after a `tools/call`. Save a screenshot of the MCP admin
|
|
217
|
+
group to `/opt/cursor/artifacts/` when that directory exists.
|
|
218
|
+
|
|
219
|
+
### 8. Expose it
|
|
220
|
+
|
|
221
|
+
Never put `localhost:4000` on the internet by port-forwarding. Follow
|
|
222
|
+
https://terreno-docs.netlify.app/how-to/expose-mcp-server-safely: a named Cloudflare Tunnel, Cloudflare Access on
|
|
223
|
+
`/admin`, plus `/console` and `/api/auth` when every OAuth approver is in the Access policy
|
|
224
|
+
(never `/mcp`, `/.well-known`, or `/oauth/*`), OAuth `issuer`
|
|
225
|
+
equal to the tunnel hostname, rate limiting on with `trustProxy` set for the proxy in front.
|
|
226
|
+
|
|
227
|
+
## Done when
|
|
228
|
+
|
|
229
|
+
- `./dist/<appName>` starts with only Mongo and env vars, and the step 7 checks pass.
|
|
230
|
+
- `/console` shows the MCP admin group, and a service token minted there lists tools.
|
|
231
|
+
- With `MCP_OAUTH_ENABLED=true`, Claude.ai's custom connector completes the consent flow
|
|
232
|
+
against the public hostname.
|
|
233
|
+
|
|
234
|
+
## Pitfalls
|
|
235
|
+
|
|
236
|
+
- A quick tunnel (`trycloudflare.com`) hostname changes on every run. OAuth discovery and
|
|
237
|
+
registered clients break; use a named tunnel.
|
|
238
|
+
- `dynamicClientRegistration` with an empty `allowedRedirectHosts` throws at startup on purpose.
|
|
239
|
+
- Non-admin users can open `/console/oauth/consent` to approve a connection; the rest of the
|
|
240
|
+
console shows them the forbidden page. That is expected.
|
|
241
|
+
- Run one binary per Mongo database.
|
|
242
|
+
- The OAuth consent routes trust the Better Auth session cookie because `BetterAuthApp`
|
|
243
|
+
mounts its session middleware before `TerrenoApp` adds `/oauth/*`. Register `BetterAuthApp`
|
|
244
|
+
on the same `TerrenoApp`; do not mount it on a separate Express app.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: building-admin-interfaces
|
|
3
|
+
description: >-
|
|
4
|
+
Build Terreno admin UIs with @terreno/admin-frontend and @terreno/admin-backend.
|
|
5
|
+
Use when adding or changing admin screens, AdminShell sidebar nav, AdminHome,
|
|
6
|
+
custom screens, AdminScreenRouter, AdminProvider widgets, admin-spa routes,
|
|
7
|
+
or operator tools. Covers where models vs custom screens vs platform tools go.
|
|
8
|
+
Not for product (non-admin) Expo screens — use building-terreno-apps.
|
|
9
|
+
---
|
|
10
|
+
# Building Admin Interfaces
|
|
11
|
+
|
|
12
|
+
Use this skill when the work is an **admin** surface: `/admin/**`, `/console`,
|
|
13
|
+
`AdminApp`, `AdminShellLayout`, model changelists, custom operator screens, or
|
|
14
|
+
admin home widgets.
|
|
15
|
+
|
|
16
|
+
**Architecture source (read first):**
|
|
17
|
+
[How admin interfaces are shaped](../../../../docs/explanation/admin-interface.md)
|
|
18
|
+
and [Build admin screens](../../../../docs/how-to/build-admin-screens.md).
|
|
19
|
+
|
|
20
|
+
**Related skills:** `building-terreno-apps` (Expo/`@terreno/ui` habits),
|
|
21
|
+
`terreno-ui` (components), `terreno-data-fetching` (windowed syncdb + RPC),
|
|
22
|
+
`terreno-backend-api` (`modelRouter` + `admin:`), `verify-ui-changes`.
|
|
23
|
+
|
|
24
|
+
## Tracer bullet
|
|
25
|
+
|
|
26
|
+
1. Classify the need: model CRUD, home widget, custom screen, or dedicated workflow
|
|
27
|
+
route. Done when the choice matches the table in `docs/how-to/build-admin-screens.md`.
|
|
28
|
+
2. Backend first: `modelRouter({admin})`, `AdminApp.customScreens`, plugin
|
|
29
|
+
`adminContribution()`, or `home.slots`. Done when `GET /admin/config` lists the
|
|
30
|
+
new model or screen for an admin user and omits it without `admin:access`.
|
|
31
|
+
3. Frontend wiring: `AdminProvider` + `AdminShellLayout` with matching `apiBase` /
|
|
32
|
+
`routeBase`. Register `widgets.screens[name]` only for host-owned custom screens.
|
|
33
|
+
Done when the sidebar link opens the right component without a missing-widget
|
|
34
|
+
placeholder.
|
|
35
|
+
4. Routes: keep `[model]/index.tsx` on `AdminScreenRouter`; `create.tsx` / `[id].tsx`
|
|
36
|
+
on `AdminModelForm`. Add extra Expo files only for extra URL segments. Done when
|
|
37
|
+
no per-model copy of the table exists.
|
|
38
|
+
5. UI: `@terreno/ui` + `AdminScreenPage` for custom screens; `color="transparent"`
|
|
39
|
+
inside the shell. String-`_id` models with `adminBroadcast` use windowed
|
|
40
|
+
syncdb; ObjectId/API-only model CRUD uses `useAdminModelApi`; custom RPC uses
|
|
41
|
+
`useAdminRpc` + `useAdminRpcQuery`. All of it goes through the `AdminProvider`
|
|
42
|
+
fetch client. Done when there is no app-owned `fetch`/`axios` and no `api`
|
|
43
|
+
prop on admin components.
|
|
44
|
+
6. Verify with `verify-ui-changes` on the example admin or admin-spa. Done when
|
|
45
|
+
home, sidebar, and the new flow are exercised as an admin user.
|
|
46
|
+
|
|
47
|
+
## References
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
references/
|
|
51
|
+
screens-and-routes.md File layout, AdminScreenRouter, dedicated routes
|
|
52
|
+
nav-and-shell.md Sidebar sections, routeBase vs apiBase, mobile drawer
|
|
53
|
+
patterns-and-tools.md Widgets, platform tools, RBAC, MCP, built-in IDs
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Hard guardrails
|
|
57
|
+
|
|
58
|
+
- Do not hand-roll a DataTable per model. Use `AdminModelTable` / `AdminScreenRouter`.
|
|
59
|
+
- Do not put a second sidebar inside a screen. `AdminShell` owns nav.
|
|
60
|
+
- Do not pass an `api` prop to admin components (removed in Terreno 58).
|
|
61
|
+
`AdminProvider` (`credentials` for cookie SPAs, `apiOrigin` = `baseUrl` from
|
|
62
|
+
`@terreno/syncdb/config` + `getAuthHeaders` reading the app's
|
|
63
|
+
`authProvider.getToken()` for embedded apps) is the only auth binding.
|
|
64
|
+
- Do not invent sidebar entries. Config (filtered by RBAC) is the nav source.
|
|
65
|
+
- Do not use `router.back()` as the custom-screen back action; use `AdminScreenPage`.
|
|
66
|
+
- Do not edit generated `store/apiSdk.ts` / `store/syncDbSdk.ts` by hand.
|
|
67
|
+
|
|
68
|
+
## Decision tree
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Admin work?
|
|
72
|
+
|-- CRUD for a model?
|
|
73
|
+
| \-- modelRouter admin block; generic [model] routes
|
|
74
|
+
|
|
|
75
|
+
|-- Tile on Home?
|
|
76
|
+
| \-- home.slots + widgets.home
|
|
77
|
+
|
|
|
78
|
+
|-- Operator page that is not a model?
|
|
79
|
+
| \-- customScreens.name + widgets.screens[name]
|
|
80
|
+
|
|
|
81
|
+
|-- Extra URL segments or a workflow form cannot express?
|
|
82
|
+
| \-- Dedicated Expo route; keep the generic table for search
|
|
83
|
+
|
|
|
84
|
+
\-- Done coding?
|
|
85
|
+
\-- verify-ui-changes
|
|
86
|
+
```
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "Building Admin Interfaces"
|
|
3
|
+
short_description: "Build Terreno admin screens with AdminShell nav, AdminScreenRouter, custom screens, and admin-backend config"
|
|
4
|
+
default_prompt: "Use $building-admin-interfaces to add or change an admin screen, sidebar nav, or operator tool with @terreno/admin-frontend."
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Nav and shell
|
|
2
|
+
|
|
3
|
+
Canonical shape: [How admin interfaces are shaped](../../../../../docs/explanation/admin-interface.md).
|
|
4
|
+
|
|
5
|
+
## `apiBase` vs `routeBase`
|
|
6
|
+
|
|
7
|
+
| Prop | Meaning | Embedded | admin-spa |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| `apiBase` | HTTP prefix for `/config` and model CRUD | `"/admin"` | `"/admin"` |
|
|
10
|
+
| `routeBase` | Expo Router prefix for `router.push` | `"/admin"` | `""` |
|
|
11
|
+
|
|
12
|
+
`AdminShell` navigates to `` `${routeBase}/${model.name}` ``. Using `apiBase` as
|
|
13
|
+
`routeBase` on the SPA sends the browser to `/admin/User` instead of `/console/User`.
|
|
14
|
+
|
|
15
|
+
## Sidebar sections (do not reorder in app code)
|
|
16
|
+
|
|
17
|
+
1. Home → `{routeBase}/`
|
|
18
|
+
2. Models, grouped by `admin.group` (empty group → **General**, sorted last)
|
|
19
|
+
3. Screens from `customScreens`
|
|
20
|
+
4. Platform, filtered by `platformTools`: Scripts (`/__scripts`), Roles, Version,
|
|
21
|
+
Audit Log, Feature Flags, Configuration
|
|
22
|
+
|
|
23
|
+
Pass `configurationPath`, `rolesPath`, and `versionConfigPath` on
|
|
24
|
+
`AdminShellLayout`. Audit log and Feature Flags are still models; the shell
|
|
25
|
+
relocates those two into Platform.
|
|
26
|
+
|
|
27
|
+
## Chrome
|
|
28
|
+
|
|
29
|
+
- Default sidebar: `sidebarVariant="colorful"` (teal rail). Optional `"clinical"`.
|
|
30
|
+
- Main column: theme canvas. Nested `Page` / `AdminScreenPage`:
|
|
31
|
+
`color="transparent"`, `padding={0}` unless the screen supplies its own pad.
|
|
32
|
+
- Breadcrumbs and `headerActions` enable the white top bar. Home typically uses
|
|
33
|
+
`breadcrumbs={[{label: "Admin"}]}`.
|
|
34
|
+
- Below the desktop floor (1024pt, `isSupportedDesktopWidth`): hamburger + drawer. Do not hide the shell on mobile.
|
|
35
|
+
- Custom-screen back: `AdminScreenPage` → `router.push(backHref)`. Never
|
|
36
|
+
`router.back()` as the default.
|
|
37
|
+
|
|
38
|
+
## Auth gate
|
|
39
|
+
|
|
40
|
+
`admin:access` opens the page (`GET /admin/config` 403 without it). Hosts call
|
|
41
|
+
`canOpenAdminPage({admin, permissions})` from `@terreno/syncdb/app` and hide product
|
|
42
|
+
nav to admin with the same check.
|