minutework 0.1.32 → 0.1.34
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/assets/claude-local/skills/README.md +2 -0
- package/assets/claude-local/skills/app-pack-authoring/SKILL.md +24 -1
- package/assets/claude-local/skills/contract-first-public-intake/SKILL.md +11 -3
- package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +19 -3
- package/assets/claude-local/skills/published-web-and-mw-core-site/SKILL.md +9 -6
- package/assets/claude-local/skills/standalone-mobile-client/SKILL.md +5 -4
- package/assets/templates/next-tenant-app/README.md +37 -135
- package/assets/templates/next-tenant-app/package.json +1 -0
- package/assets/templates/next-tenant-app/src/app/app/demo/page.tsx +15 -0
- package/assets/templates/next-tenant-app/src/app/app/layout.tsx +1 -4
- package/assets/templates/next-tenant-app/src/app/app/page.tsx +2 -17
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +9 -67
- package/assets/templates/next-tenant-app/src/app/blog/page.tsx +10 -46
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +9 -65
- package/assets/templates/next-tenant-app/src/app/docs/page.tsx +10 -46
- package/assets/templates/next-tenant-app/src/app/layout.tsx +8 -10
- package/assets/templates/next-tenant-app/src/app/login/page.tsx +3 -23
- package/assets/templates/next-tenant-app/src/app/page.tsx +11 -44
- package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +10 -44
- package/assets/templates/next-tenant-app/src/app/providers.tsx +2 -1
- package/assets/templates/next-tenant-app/src/app/robots.ts +7 -18
- package/assets/templates/next-tenant-app/src/app/sitemap.ts +4 -39
- package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +97 -98
- package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +43 -78
- package/assets/templates/next-tenant-app/src/features/demo/components/manifest-demo.tsx +89 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/static-public-page.tsx +58 -0
- package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +48 -552
- package/assets/templates/next-tenant-app/src/lib/app-routes.ts +2 -2
- package/assets/templates/next-tenant-app/src/lib/public-site.ts +5 -30
- package/assets/templates/next-tenant-app/src/mw/client.ts +18 -0
- package/assets/templates/next-tenant-app/src/mw/mock.test.ts +21 -0
- package/assets/templates/next-tenant-app/src/mw/mock.ts +35 -0
- package/assets/templates/next-tenant-app/src/mw/provider.tsx +17 -0
- package/assets/templates/next-tenant-app/template.json +3 -3
- package/assets/templates/next-tenant-app/template.schema.json +1 -0
- package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +4 -5
- package/package.json +2 -2
- package/vendor/workspace-mcp/types.d.ts +4 -0
- package/assets/templates/next-tenant-app/src/app/(cms)/[...path]/page.tsx +0 -89
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +0 -90
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +0 -78
- package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +0 -31
- package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +0 -16
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +0 -79
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +0 -40
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +0 -42
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +0 -29
- package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +0 -26
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +0 -40
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +0 -47
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +0 -43
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +0 -45
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +0 -83
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +0 -30
- package/assets/templates/next-tenant-app/src/app/app/page.test.ts +0 -62
- package/assets/templates/next-tenant-app/src/app/app/private-content-source.test.ts +0 -88
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +0 -70
- package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +0 -46
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +0 -70
- package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +0 -46
- package/assets/templates/next-tenant-app/src/app/login/page.test.ts +0 -55
- package/assets/templates/next-tenant-app/src/app/page.test.ts +0 -90
- package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +0 -59
- package/assets/templates/next-tenant-app/src/app/robots.test.ts +0 -40
- package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +0 -63
- package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +0 -342
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +0 -66
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +0 -108
- package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +0 -111
- package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +0 -111
- package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.test.ts +0 -38
- package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.tsx +0 -145
- package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +0 -189
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +0 -444
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +0 -383
- package/assets/templates/next-tenant-app/src/lib/content/contracts.test.ts +0 -138
- package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +0 -399
- package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +0 -5
- package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +0 -96
- package/assets/templates/next-tenant-app/src/lib/content/release-manifest.test.ts +0 -93
- package/assets/templates/next-tenant-app/src/lib/content/release-manifest.ts +0 -123
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +0 -75
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +0 -25
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +0 -170
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +0 -661
- package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +0 -131
- package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +0 -34
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +0 -211
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +0 -151
- package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +0 -33
- package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +0 -108
|
@@ -15,6 +15,8 @@ receive:
|
|
|
15
15
|
|
|
16
16
|
- Builder should compose shared substrate before generating bespoke tenant code
|
|
17
17
|
- `tenant-app` is the combined public plus private web starter
|
|
18
|
+
- `tenant-app` auth/data uses `@minutework/web-auth` and thin `src/mw/`
|
|
19
|
+
substrate, not per-app auth/gateway BFF routes
|
|
18
20
|
- `mw.core.site` is a runtime baseline capability
|
|
19
21
|
- tenant public authoring is runtime/CMS-backed through `mw.core.site`
|
|
20
22
|
- Vuilder-owned public sites use `vuilder-public-site` and public-dj CMS
|
|
@@ -16,6 +16,29 @@ An `app pack` is the shipped product unit.
|
|
|
16
16
|
contract boundaries.
|
|
17
17
|
- Start with declarative schema/manifests and add code surfaces only when needed.
|
|
18
18
|
- Use `tenant-app` for the combined public-site plus private-app web surface.
|
|
19
|
+
- `tenant-app` auth/data must stay on the frozen `@minutework/web-auth`
|
|
20
|
+
contract. Wire it through the thin `src/mw/` substrate only; do not add
|
|
21
|
+
per-app `src/app/api/auth/*`, `src/app/api/gateway/*`, platform-session BFF
|
|
22
|
+
layers, platform credential login screens, operator-console links, or
|
|
23
|
+
runtime-command demos.
|
|
24
|
+
- If a generated workspace reports `@minutework/web-auth` as missing, treat it
|
|
25
|
+
as dependency installation, package publishing, or package sync work. Do not
|
|
26
|
+
hand-roll auth/data routes or browser tokens as a workaround.
|
|
27
|
+
- The template may render a local `/login` page that calls SDK hooks. The SDK
|
|
28
|
+
also exposes hosted UI helpers for same-origin `/_mw/login`,
|
|
29
|
+
`/_mw/signup`, and `/_mw/verify-email`; both choices remain SDK-only.
|
|
30
|
+
- Client-side `/app` session checks are UX gating only. Real authorization is
|
|
31
|
+
enforced server-side by platform `/_mw` routes and runtime dispatch checks:
|
|
32
|
+
active customer membership, email verification, app publication, and
|
|
33
|
+
`webCustomerExposed` / `web_customer_exposed` manifest declarations.
|
|
34
|
+
- Browser calls from `tenant-app` should use `mw.query(...)` and
|
|
35
|
+
`mw.action(..., { idempotencyKey })`; actions require a non-empty
|
|
36
|
+
idempotency key. The runtime surface must be declared on the manifest with
|
|
37
|
+
`webCustomerExposed` / `web_customer_exposed`, and V1 customer roles are
|
|
38
|
+
`customer` only.
|
|
39
|
+
- The authenticated `tenant-app` principal is `tenant_customer`. Do not model
|
|
40
|
+
the generated customer app as an operator/platform-session app, and do not
|
|
41
|
+
persist bearer/session/runtime tokens in browser JavaScript.
|
|
19
42
|
- Use `sidecar` for internal APIs, workers, integrations, or Python-heavy compute.
|
|
20
43
|
- For member-facing collaboration and operator workflows, check shell fit first
|
|
21
44
|
before proposing standalone `tenant-app` routes or dashboards.
|
|
@@ -77,7 +100,7 @@ An `app pack` is the shipped product unit.
|
|
|
77
100
|
`SCHEMA_KEY_UPSERT_REGISTRY` may ship in the active manifest.
|
|
78
101
|
- For `mw.core.site.form`, do not author `form_key` or `app_pack_ref` inside
|
|
79
102
|
`data`; the install pipe injects both. Keep `surface_key == form_key` for
|
|
80
|
-
public intake
|
|
103
|
+
public intake gateway handling.
|
|
81
104
|
- For `mw.runtime.agent`, use `logical_key` as the runtime `Agent.slug`.
|
|
82
105
|
Author stable agent fields in `data` such as `name`, `system_instruction`,
|
|
83
106
|
`enabled_tools`, `trigger_intents`, `channels_enabled`, and
|
|
@@ -35,6 +35,14 @@ guest-continuity threads, public submission routing, or claim-after-auth flows.
|
|
|
35
35
|
- Post-auth continuation stays on the existing `handoff_key` and flows through
|
|
36
36
|
`apply_post_auth_access_policy()`. Do not invent automatic membership upgrades
|
|
37
37
|
or a parallel claim token model.
|
|
38
|
+
- Tenant web auth `claimRef` is an opaque platform-signed handoff claim token,
|
|
39
|
+
not a raw `PublicHandoff.id` or `core:public_handoff:*` ref. A `tenant-app`
|
|
40
|
+
may pass that token through `@minutework/web-auth` signup, but platform must
|
|
41
|
+
store it pending verification and link only after the authenticating email is
|
|
42
|
+
verified, the signed handoff is bound to that verified identity, and
|
|
43
|
+
`apply_post_auth_access_policy()` succeeds.
|
|
44
|
+
- Do not create customer identity claims during unverified signup. Anonymous or
|
|
45
|
+
unbound handoffs must not be auto-linked.
|
|
38
46
|
|
|
39
47
|
## Privacy And Prompt Semantics
|
|
40
48
|
|
|
@@ -65,9 +73,9 @@ guest-continuity threads, public submission routing, or claim-after-auth flows.
|
|
|
65
73
|
source that can drive later `action:` or `flow:` execution.
|
|
66
74
|
- Do not gate public intake render on a hardcoded client-side token
|
|
67
75
|
allowlist. The handoff token is a platform-issued, server-validated
|
|
68
|
-
payload; the workspace UI should pass it through to the
|
|
69
|
-
platform decide validity. A client-side allowlist drifts
|
|
70
|
-
token registry and silently breaks once real tokens are minted.
|
|
76
|
+
payload; the workspace UI should pass it through to the hosted gateway/auth
|
|
77
|
+
path and let the platform decide validity. A client-side allowlist drifts
|
|
78
|
+
from the real token registry and silently breaks once real tokens are minted.
|
|
71
79
|
|
|
72
80
|
## Vuilder Vertical Onboarding
|
|
73
81
|
|
|
@@ -22,13 +22,29 @@ the monorepo or a live tenant runtime.
|
|
|
22
22
|
- `tenant-app`, optional `sidecar`, and the optional `mobile` starter are
|
|
23
23
|
implementation surfaces inside the tenant product, not the whole architecture.
|
|
24
24
|
- `tenant-app` is the combined public plus private web starter.
|
|
25
|
+
- In `tenant-app`, keep MinuteWork web auth/data access inside the thin
|
|
26
|
+
`src/mw/` substrate and `@minutework/web-auth`. Product UI may call that
|
|
27
|
+
substrate, but it must not recreate platform-session BFF routes, platform
|
|
28
|
+
credential login, or browser-exposed platform/runtime tokens.
|
|
29
|
+
- If `@minutework/web-auth` is not installed or resolvable in a generated
|
|
30
|
+
workspace, fix dependency installation, package publishing, or package sync.
|
|
31
|
+
Do not replace the SDK with local auth/gateway routes.
|
|
32
|
+
- A local `/login` page may call SDK hooks, while SDK hosted UI helpers point
|
|
33
|
+
to same-origin `/_mw/login`, `/_mw/signup`, and `/_mw/verify-email`; both
|
|
34
|
+
are valid only when they stay on the SDK/`/_mw` contract.
|
|
35
|
+
- Client-side app guards are UX only. Platform `/_mw` routes and runtime
|
|
36
|
+
dispatch enforce active customer membership, email verification, app
|
|
37
|
+
publication, and manifest exposure server-side.
|
|
38
|
+
- `tenant-app` runtime data access goes through `mw.query(...)` and
|
|
39
|
+
`mw.action(..., { idempotencyKey })` against manifest-declared
|
|
40
|
+
`webCustomerExposed` / `web_customer_exposed` customer surfaces.
|
|
25
41
|
- A mobile / native client (Expo, React Native, native iOS/Android) is a
|
|
26
42
|
standalone-client exception authored in the `mobile` starter, not in
|
|
27
43
|
`tenant-app` or `sidecar`. It is a direct platform API client that consumes
|
|
28
44
|
the platform native-token API (`/api/v1/native/...`) directly with a
|
|
29
|
-
platform-issued native session token, rather than the `tenant-app`
|
|
30
|
-
session. The mobile starter remains npm-owned standalone app
|
|
31
|
-
not be added to the generated root `pnpm-workspace.yaml`.
|
|
45
|
+
platform-issued native session token, rather than the `tenant-app` web SDK /
|
|
46
|
+
hosted-cookie session. The mobile starter remains npm-owned standalone app
|
|
47
|
+
code and should not be added to the generated root `pnpm-workspace.yaml`.
|
|
32
48
|
- If the `mobile` starter is enabled (`starters.mobile.enabled`), read
|
|
33
49
|
`standalone-mobile-client/SKILL.md` before proposing the mobile surface.
|
|
34
50
|
- `vuilder-public-site` is a Vuilder-owned public-site authoring workspace for
|
|
@@ -20,18 +20,21 @@ flows, or the default MinuteWork site model.
|
|
|
20
20
|
runtime-canonical through `mw.core.site` and installed app-pack data.
|
|
21
21
|
- `tenant-app` is the combined web starter for public routes plus the private
|
|
22
22
|
`/app` workspace.
|
|
23
|
+
- Do not add a server-only public content token adapter to `tenant-app`.
|
|
24
|
+
Public content should come from hosted published releases/snapshots or
|
|
25
|
+
gateway-approved public-content routes over the MinuteWork substrate, not
|
|
26
|
+
from a baked-in platform token in the generated app.
|
|
23
27
|
- Author public content against runtime/CMS records, not a fixed in-repo site
|
|
24
28
|
starter.
|
|
25
29
|
- For contract-first intake pipeline decisions, also read
|
|
26
30
|
`contract-first-public-intake/SKILL.md`.
|
|
27
31
|
- Use `published-web` and hosted public-release flows for anonymous delivery by
|
|
28
32
|
default.
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
`MW_PUBLIC_SITE_ENV`
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- `MW_PUBLIC_SITE_ENV=live` is for publication-safe reads only.
|
|
33
|
+
- Legacy starter variables such as `MW_PUBLIC_CONTENT_SOURCE`,
|
|
34
|
+
`MW_CONTENT_API_TOKEN`, `MW_PUBLIC_SITE_PROPERTY_KEY`,
|
|
35
|
+
`MW_STATIC_PUBLIC_CONTENT_PATH`, and `MW_PUBLIC_SITE_ENV` describe old
|
|
36
|
+
adapter modes. Do not introduce them into new `tenant-app` work unless a
|
|
37
|
+
compatibility task explicitly targets that legacy adapter.
|
|
35
38
|
- Anonymous live traffic should prefer published snapshots instead of direct
|
|
36
39
|
runtime reads on every request.
|
|
37
40
|
- `runtime_local_sidecar` is an explicit exception path for live serving, not
|
|
@@ -80,9 +80,9 @@ The native token flow mirrors the existing CLI developer-token flow
|
|
|
80
80
|
freshly returned pair. Re-run the browser-assisted authorize step only when
|
|
81
81
|
the refresh token itself is invalid or revoked.
|
|
82
82
|
|
|
83
|
-
The
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
The `tenant-app` web SDK / hosted-cookie path is **web-only**. The mobile app
|
|
84
|
+
does not ride the `tenant-app` cookie jar, and it does not read or forward
|
|
85
|
+
CSRF -- native uses the bearer token directly against the platform.
|
|
86
86
|
|
|
87
87
|
### Token binding (what is and isn't enforced)
|
|
88
88
|
|
|
@@ -100,7 +100,8 @@ boundary.
|
|
|
100
100
|
- Do not mint a JWT inside the app or stand up a local user table / token issuer.
|
|
101
101
|
- Do not expose access or refresh tokens to web pages or generic React state;
|
|
102
102
|
keep them in `expo-secure-store`.
|
|
103
|
-
- Do not route the mobile app through the `tenant-app`
|
|
103
|
+
- Do not route the mobile app through the `tenant-app` web SDK / hosted-cookie
|
|
104
|
+
session.
|
|
104
105
|
- Do not read or forward CSRF from native; bearer-token auth does not use it.
|
|
105
106
|
- Do not put native code inside `tenant-app` or `sidecar`.
|
|
106
107
|
|
|
@@ -3,157 +3,59 @@
|
|
|
3
3
|
This directory is the canonical combined Next.js web starter for Builder.
|
|
4
4
|
|
|
5
5
|
In the runtime Builder flow, this bundle materializes into
|
|
6
|
-
`BuilderWorkspace.sandbox_root/app/`.
|
|
6
|
+
`BuilderWorkspace.sandbox_root/app/`. In the external CLI scaffold flow, the
|
|
7
|
+
same starter is copied into `tenant-app/`.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
On managed tenant VMs, `BuilderWorkspace.sandbox_root` normally lives under
|
|
12
|
-
`/srv/minutework/workspaces/<workspace_id>/`, so the Builder-edited app copy is
|
|
13
|
-
typically `/srv/minutework/workspaces/<workspace_id>/app/`.
|
|
14
|
-
|
|
15
|
-
`apps/mw-next-client` is still the repo-side seed used to harden and evolve this scaffold. It is not the runtime template location.
|
|
9
|
+
`apps/mw-next-client` may still seed visual conventions, but this template is
|
|
10
|
+
no longer a platform-session BFF app.
|
|
16
11
|
|
|
17
12
|
## Auth profile
|
|
18
13
|
|
|
19
|
-
This template
|
|
20
|
-
|
|
21
|
-
- browsers use this BFF only (cookie session, server-owned CSRF); native clients
|
|
22
|
-
(e.g. a mobile app) instead use a platform-issued native session token
|
|
23
|
-
directly against `/api/v1/native/...` -- see the `mobile` starter /
|
|
24
|
-
`standalone-mobile-client` skill
|
|
25
|
-
- upstream platform `sessionid` and `csrftoken` stay server-owned
|
|
26
|
-
- unsafe upstream requests bootstrap and forward CSRF through the BFF
|
|
27
|
-
- there is no browser-to-runtime direct access
|
|
28
|
-
- there is no local JWT system, local user table, or parallel auth stack
|
|
29
|
-
|
|
30
|
-
## Runtime requirements
|
|
14
|
+
This template uses the `tenant_web_auth_sdk` profile:
|
|
31
15
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
16
|
+
- browser auth and manifest API calls go through `@minutework/web-auth`
|
|
17
|
+
- the browser stores no bearer token, runtime key, or platform credential
|
|
18
|
+
- SDK calls use same-origin `/_mw` routes with `credentials: "include"`
|
|
19
|
+
- CSRF is handled by the SDK using the non-secret CSRF cookie
|
|
20
|
+
- runtime query/action calls are authorized as `principal_kind=tenant_customer`
|
|
34
21
|
|
|
35
|
-
|
|
22
|
+
Only `src/mw/` is MinuteWork substrate. Keep product UI and product logic in
|
|
23
|
+
`src/app`, `src/features`, and developer-owned modules.
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
The local `/login` route is product UI that calls SDK hooks. The SDK also
|
|
26
|
+
provides hosted UI helpers for same-origin `/_mw/login`, `/_mw/signup`, and
|
|
27
|
+
`/_mw/verify-email` URLs; both paths stay on the same SDK contract.
|
|
39
28
|
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
Client-side `/app` session checks are only UX gating. Authorization is enforced
|
|
30
|
+
by the platform `/_mw` routes and runtime dispatch: active customer membership,
|
|
31
|
+
email verification, app publication, and manifest `web_customer_exposed`
|
|
32
|
+
declarations are checked server-side.
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
If a generated workspace reports `@minutework/web-auth` as missing, treat that
|
|
35
|
+
as a dependency installation or package publishing/sync issue. Do not recreate
|
|
36
|
+
platform BFF routes, browser bearer tokens, or platform credential login as a
|
|
37
|
+
workaround.
|
|
46
38
|
|
|
47
39
|
## Default route shape
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- optional runtime example under `/app/examples/runtime-commands`
|
|
54
|
-
|
|
55
|
-
Public marketing, docs, and blog routes render through a swappable public-content adapter seam. The authenticated shell keeps tenant context controls, password flows, and the platform-session BFF boundary.
|
|
56
|
-
|
|
57
|
-
## Public content adapter
|
|
58
|
-
|
|
59
|
-
The public content seam lives under `src/lib/content`.
|
|
60
|
-
|
|
61
|
-
The default built-in adapter is `MinuteWorkPublicSiteContentAdapter`. It calls the
|
|
62
|
-
MinuteWork gateway with a server-only content token and reads the narrow
|
|
63
|
-
`mw.core.site` public-site snapshot contract.
|
|
64
|
-
|
|
65
|
-
`mw.core.site` is treated as a runtime baseline capability. This starter
|
|
66
|
-
composes against that baseline rather than trying to install it locally.
|
|
67
|
-
|
|
68
|
-
The gateway response carries an explicit boundary:
|
|
69
|
-
|
|
70
|
-
- `environment=preview` must declare `source_boundary=runtime_preview`
|
|
71
|
-
- `environment=live` may declare `source_boundary=published_live` for hosted releases or `source_boundary=runtime_live` for runtime-served sidecar releases
|
|
72
|
-
|
|
73
|
-
That keeps preview free to use runtime-backed draft reads while keeping live
|
|
74
|
-
publication-safe for hosted delivery while still allowing an explicit
|
|
75
|
-
runtime-served public lane when the release is activated through
|
|
76
|
-
`runtime_local_sidecar`.
|
|
77
|
-
|
|
78
|
-
To replace the built-ins for a tenant-specific CMS or content API, implement the hook in `src/lib/content/custom-adapter.ts`.
|
|
79
|
-
|
|
80
|
-
## Example gating
|
|
81
|
-
|
|
82
|
-
`MW_ENABLE_RUNTIME_COMMAND_EXAMPLE=false` by default.
|
|
41
|
+
- public routes at `/`, `/pricing`, `/docs`, and `/blog`
|
|
42
|
+
- customer auth at `/login`
|
|
43
|
+
- authenticated workspace at `/app`
|
|
44
|
+
- SDK manifest demo at `/app/demo`
|
|
83
45
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
- the example gateway API routes return `404`
|
|
88
|
-
- the example navigation entry is omitted
|
|
46
|
+
The template intentionally does not include `src/lib/platform/*`,
|
|
47
|
+
`src/app/api/auth/*`, `src/app/api/gateway/*`, an operator-console link, a
|
|
48
|
+
runtime-command demo, or a server-only public-content token adapter.
|
|
89
49
|
|
|
90
50
|
## Environment
|
|
91
51
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
- `MW_PLATFORM_BASE_URL` is required
|
|
98
|
-
- `MW_PUBLIC_CONTENT_SOURCE` defaults to `none` when omitted; supported values are `minutework_cms`, `custom`, `static_json`, and `none`
|
|
99
|
-
- `MW_CONTENT_API_TOKEN` is required only for `minutework_cms` and must stay server-only
|
|
100
|
-
- `MW_TEMPLATE_APP_NAME` defaults to `MinuteWork Combined Starter`
|
|
101
|
-
- `MW_PUBLIC_BASE_URL` is required for public content sources and should match the deployed public origin; it may be omitted when `MW_PUBLIC_CONTENT_SOURCE=none`
|
|
102
|
-
- `MW_PUBLIC_SITE_PROPERTY_KEY` is required only for `minutework_cms` and selects the `PublishedWebProperty` backing the site
|
|
103
|
-
- `MW_PUBLIC_SITE_ENV` defaults to `preview`
|
|
104
|
-
- `MW_STATIC_PUBLIC_CONTENT_PATH` defaults to `content/public-site.json` and is used when `MW_PUBLIC_CONTENT_SOURCE=static_json`
|
|
105
|
-
- `MW_ENABLE_RUNTIME_COMMAND_EXAMPLE` defaults to `false`
|
|
106
|
-
|
|
107
|
-
## SEO baseline
|
|
108
|
-
|
|
109
|
-
The public route surface ships with:
|
|
110
|
-
|
|
111
|
-
- canonical URLs derived from `MW_PUBLIC_BASE_URL`
|
|
112
|
-
- `robots.ts` and `sitemap.ts`
|
|
113
|
-
- Open Graph metadata for marketing and article/detail pages
|
|
114
|
-
- server-rendered public pages by default, with no tenant/session reads during render
|
|
115
|
-
- docs/blog static params generated from the same public-site snapshot used during render
|
|
116
|
-
- graceful empty states for fresh properties with no authored or no published content
|
|
117
|
-
|
|
118
|
-
Private routes under `/app` are marked `noindex`.
|
|
119
|
-
|
|
120
|
-
## Generated artifact policy
|
|
121
|
-
|
|
122
|
-
Checked-in generated artifacts are limited to:
|
|
123
|
-
|
|
124
|
-
- `next-env.d.ts`
|
|
125
|
-
- `src/design-system/tokens/manifest.ts`
|
|
126
|
-
- `src/design-system/tokens/manifest.json`
|
|
127
|
-
- `pnpm-lock.yaml`
|
|
128
|
-
|
|
129
|
-
Generated build outputs such as `.next/`, `storybook-static/`, `test-results/`, and `tools/design-system/__screenshots__/` are not canonical template contents.
|
|
52
|
+
- `NEXT_PUBLIC_MW_APP_ID` selects the manifest app id used by the SDK and
|
|
53
|
+
defaults to `tenant.app`
|
|
54
|
+
- `MW_TEMPLATE_APP_NAME` controls the display name and defaults to `Tenant App`
|
|
55
|
+
- `MW_PUBLIC_BASE_URL` is optional and only affects metadata routes
|
|
130
56
|
|
|
131
57
|
## Validation
|
|
132
58
|
|
|
133
|
-
Use `pnpm validate` from this directory. It runs
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
2. `template:route-contract`
|
|
137
|
-
3. `next typegen`
|
|
138
|
-
4. `design-system:tokens`
|
|
139
|
-
5. `design-system:check`
|
|
140
|
-
6. `typecheck`
|
|
141
|
-
7. `lint`
|
|
142
|
-
8. `test`
|
|
143
|
-
9. `build:validate`
|
|
144
|
-
10. `build-storybook:validate`
|
|
145
|
-
|
|
146
|
-
`template:validate` validates `template.json` against the bundled `template.schema.json`, so the template remains self-validating after Builder materializes it into a workspace sandbox.
|
|
147
|
-
|
|
148
|
-
When the CLI scaffolds this starter into `tenant-app/`, the same validation
|
|
149
|
-
contract still applies to that copied workspace surface. When Runtime Builder
|
|
150
|
-
materializes it into `app/`, the same validation contract applies there too.
|
|
151
|
-
|
|
152
|
-
The validation-only build steps inject fixture values for the required
|
|
153
|
-
production env vars and run against a local public-site fixture server, so the
|
|
154
|
-
template can prove its build contract in-repo without weakening the real
|
|
155
|
-
`pnpm build` requirement.
|
|
156
|
-
|
|
157
|
-
`template:route-contract` verifies that the combined public/private route tree, metadata routes, and content-adapter entrypoints remain present in the template.
|
|
158
|
-
|
|
159
|
-
Inside the repo, the shared schema at `runtime/builder/templates/template.schema.json` is authoritative. The bundled `template.schema.json` inside this template must stay byte-equivalent, and `template:validate` fails if the two drift.
|
|
59
|
+
Use `pnpm validate` from this directory. It runs template validation, route
|
|
60
|
+
contract validation, typegen, design-system checks, typecheck, tests, and build
|
|
61
|
+
validation.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ManifestDemo } from "@/features/demo/components/manifest-demo";
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: "Demo",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default function DemoPage() {
|
|
8
|
+
return (
|
|
9
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
10
|
+
<div className="mx-auto flex min-h-screen max-w-5xl flex-col gap-6 px-6 py-8">
|
|
11
|
+
<ManifestDemo />
|
|
12
|
+
</div>
|
|
13
|
+
</main>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
|
|
4
|
-
import { resolveAuthenticatedSession } from "@/lib/platform/auth.server";
|
|
5
|
-
|
|
6
4
|
export const metadata: Metadata = {
|
|
7
5
|
robots: {
|
|
8
6
|
index: false,
|
|
@@ -10,11 +8,10 @@ export const metadata: Metadata = {
|
|
|
10
8
|
},
|
|
11
9
|
};
|
|
12
10
|
|
|
13
|
-
export default
|
|
11
|
+
export default function AuthenticatedAppLayout({
|
|
14
12
|
children,
|
|
15
13
|
}: {
|
|
16
14
|
children: ReactNode;
|
|
17
15
|
}) {
|
|
18
|
-
await resolveAuthenticatedSession();
|
|
19
16
|
return children;
|
|
20
17
|
}
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import { PrivateAppShell } from "@/features/shell/components/private-app-shell";
|
|
2
|
-
import { resolveAuthenticatedSession } from "@/lib/platform/auth.server";
|
|
3
|
-
import { platformAuthEndpoints } from "@/lib/platform/endpoints.server";
|
|
4
|
-
import { env } from "@/lib/platform/env.server";
|
|
5
2
|
|
|
6
3
|
export const metadata = {
|
|
7
4
|
title: "Workspace",
|
|
8
|
-
description:
|
|
9
|
-
"Authenticated workspace surface for the combined MinuteWork starter.",
|
|
10
5
|
};
|
|
11
6
|
|
|
12
|
-
export default
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<PrivateAppShell
|
|
17
|
-
initialSession={session}
|
|
18
|
-
appName={env.MW_TEMPLATE_APP_NAME}
|
|
19
|
-
operatorConsoleHref={platformAuthEndpoints.operatorConsole}
|
|
20
|
-
runtimeCommandExampleEnabled={env.MW_ENABLE_RUNTIME_COMMAND_EXAMPLE}
|
|
21
|
-
view="dashboard"
|
|
22
|
-
/>
|
|
23
|
-
);
|
|
7
|
+
export default function AppHomePage() {
|
|
8
|
+
return <PrivateAppShell appName={process.env.MW_TEMPLATE_APP_NAME || "Tenant App"} />;
|
|
24
9
|
}
|
|
@@ -1,73 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { getEntry, getSiteConfig, listEntries } from "@/lib/content/adapter.server";
|
|
6
|
-
import { appRoutes } from "@/lib/app-routes";
|
|
7
|
-
import {
|
|
8
|
-
buildDisabledPublicMetadata,
|
|
9
|
-
buildPublicMetadata,
|
|
10
|
-
isPublicContentDisabled,
|
|
11
|
-
} from "@/lib/public-site";
|
|
12
|
-
|
|
13
|
-
type BlogArticlePageProps = {
|
|
14
|
-
params: Promise<{ slug: string }>;
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: "Blog",
|
|
15
5
|
};
|
|
16
6
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
export async function generateStaticParams() {
|
|
20
|
-
if (isPublicContentDisabled()) {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return (await listEntries("blog")).map((entry) => ({
|
|
25
|
-
slug: entry.slug[0],
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function generateMetadata({ params }: BlogArticlePageProps) {
|
|
30
|
-
if (isPublicContentDisabled()) {
|
|
31
|
-
return buildDisabledPublicMetadata();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const [{ slug }, siteConfig] = await Promise.all([params, getSiteConfig()]);
|
|
35
|
-
const entry = await getEntry("blog", [slug]);
|
|
36
|
-
|
|
37
|
-
if (!entry) {
|
|
38
|
-
return buildPublicMetadata({
|
|
39
|
-
title: siteConfig.collections.blog.title,
|
|
40
|
-
description: siteConfig.collections.blog.description,
|
|
41
|
-
path: appRoutes.blogIndex,
|
|
42
|
-
siteName: siteConfig.siteName,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return buildPublicMetadata({
|
|
47
|
-
title: entry.seo.title,
|
|
48
|
-
description: entry.seo.description,
|
|
49
|
-
path: appRoutes.blogPost(entry.slug),
|
|
50
|
-
siteName: siteConfig.siteName,
|
|
51
|
-
type: "article",
|
|
52
|
-
publishedTime: entry.publishedAt,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default async function BlogArticlePage({ params }: BlogArticlePageProps) {
|
|
57
|
-
if (isPublicContentDisabled()) {
|
|
58
|
-
notFound();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const [{ slug }, siteConfig] = await Promise.all([params, getSiteConfig()]);
|
|
62
|
-
const entry = await getEntry("blog", [slug]);
|
|
63
|
-
|
|
64
|
-
if (!entry) {
|
|
65
|
-
notFound();
|
|
66
|
-
}
|
|
67
|
-
|
|
7
|
+
export default function BlogPostPage() {
|
|
68
8
|
return (
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
9
|
+
<StaticPublicPage
|
|
10
|
+
eyebrow="Blog"
|
|
11
|
+
title="Update"
|
|
12
|
+
body="A customer-facing update."
|
|
13
|
+
/>
|
|
72
14
|
);
|
|
73
15
|
}
|
|
@@ -1,51 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { appRoutes } from "@/lib/app-routes";
|
|
7
|
-
import {
|
|
8
|
-
buildDisabledPublicMetadata,
|
|
9
|
-
buildPublicMetadata,
|
|
10
|
-
isPublicContentDisabled,
|
|
11
|
-
} from "@/lib/public-site";
|
|
12
|
-
|
|
13
|
-
export async function generateMetadata() {
|
|
14
|
-
if (isPublicContentDisabled()) {
|
|
15
|
-
return buildDisabledPublicMetadata();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const siteConfig = await getSiteConfig();
|
|
19
|
-
const collection = siteConfig.collections.blog;
|
|
20
|
-
|
|
21
|
-
return buildPublicMetadata({
|
|
22
|
-
title: collection.title,
|
|
23
|
-
description: collection.description,
|
|
24
|
-
path: appRoutes.blogIndex,
|
|
25
|
-
siteName: siteConfig.siteName,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default async function BlogIndexPage() {
|
|
30
|
-
if (isPublicContentDisabled()) {
|
|
31
|
-
notFound();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const [siteConfig, entries] = await Promise.all([
|
|
35
|
-
getSiteConfig(),
|
|
36
|
-
listEntries("blog"),
|
|
37
|
-
]);
|
|
38
|
-
const collection = siteConfig.collections.blog;
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: "Blog",
|
|
5
|
+
};
|
|
39
6
|
|
|
7
|
+
export default function BlogIndexPage() {
|
|
40
8
|
return (
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
kind="blog"
|
|
47
|
-
title={collection.title}
|
|
48
|
-
/>
|
|
49
|
-
</PublicSiteShell>
|
|
9
|
+
<StaticPublicPage
|
|
10
|
+
eyebrow="Blog"
|
|
11
|
+
title="Updates"
|
|
12
|
+
body="News, releases, and customer notes."
|
|
13
|
+
/>
|
|
50
14
|
);
|
|
51
15
|
}
|