minutework 0.1.31 → 0.1.33

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 (168) hide show
  1. package/EXTERNAL_ALPHA.md +33 -33
  2. package/README.md +34 -34
  3. package/assets/claude-local/CLAUDE.md.template +12 -12
  4. package/assets/claude-local/skills/README.md +3 -1
  5. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +17 -4
  6. package/assets/claude-local/skills/capability-gap-reporting/SKILL.md +3 -3
  7. package/assets/claude-local/skills/contract-first-public-intake/SKILL.md +11 -3
  8. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +12 -5
  9. package/assets/claude-local/skills/layering-and-import-modes/SKILL.md +2 -2
  10. package/assets/claude-local/skills/openclaw-skill-importer/SKILL.md +2 -2
  11. package/assets/claude-local/skills/project-overview-and-strategy/SKILL.md +8 -8
  12. package/assets/claude-local/skills/published-web-and-mw-core-site/SKILL.md +11 -8
  13. package/assets/claude-local/skills/standalone-mobile-client/SKILL.md +6 -5
  14. package/assets/claude-local/skills/vuilder-discovery-output-contract/SKILL.md +6 -6
  15. package/assets/claude-local/skills/workspace-guidance-refresh/SKILL.md +4 -4
  16. package/assets/templates/fastapi-sidecar/pyproject.toml +1 -1
  17. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/main.py +1 -1
  18. package/assets/templates/mobile-app/.env.example +4 -4
  19. package/assets/templates/mobile-app/AGENTS.md +3 -3
  20. package/assets/templates/mobile-app/README.md +10 -10
  21. package/assets/templates/mobile-app/app/(app)/_layout.tsx +2 -2
  22. package/assets/templates/mobile-app/app/(app)/index.tsx +2 -2
  23. package/assets/templates/mobile-app/app/(auth)/login.tsx +3 -3
  24. package/assets/templates/mobile-app/app/_layout.tsx +1 -1
  25. package/assets/templates/mobile-app/babel.config.js +1 -1
  26. package/assets/templates/mobile-app/eas.json +1 -1
  27. package/assets/templates/mobile-app/expo-env.d.ts +1 -1
  28. package/assets/templates/mobile-app/metro.config.js +2 -2
  29. package/assets/templates/mobile-app/package.json +1 -1
  30. package/assets/templates/mobile-app/src/mw/client.ts +3 -3
  31. package/assets/templates/mobile-app/src/mw/contracts.ts +2 -2
  32. package/assets/templates/mobile-app/src/mw/endpoints.ts +2 -2
  33. package/assets/templates/mobile-app/src/mw/env.ts +4 -4
  34. package/assets/templates/mobile-app/src/mw/session.ts +1 -1
  35. package/assets/templates/mobile-app/template.json +1 -1
  36. package/assets/templates/mobile-app/tools/template/validate-template.mjs +2 -2
  37. package/assets/templates/mobile-app/tsconfig.json +1 -1
  38. package/assets/templates/next-tenant-app/.env.example +1 -1
  39. package/assets/templates/next-tenant-app/README.md +26 -138
  40. package/assets/templates/next-tenant-app/package.json +1 -0
  41. package/assets/templates/next-tenant-app/src/app/app/demo/page.tsx +15 -0
  42. package/assets/templates/next-tenant-app/src/app/app/layout.tsx +1 -4
  43. package/assets/templates/next-tenant-app/src/app/app/page.tsx +2 -17
  44. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +9 -67
  45. package/assets/templates/next-tenant-app/src/app/blog/page.tsx +10 -46
  46. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +9 -65
  47. package/assets/templates/next-tenant-app/src/app/docs/page.tsx +10 -46
  48. package/assets/templates/next-tenant-app/src/app/layout.tsx +8 -10
  49. package/assets/templates/next-tenant-app/src/app/login/page.tsx +3 -23
  50. package/assets/templates/next-tenant-app/src/app/page.tsx +11 -44
  51. package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +10 -44
  52. package/assets/templates/next-tenant-app/src/app/providers.tsx +2 -1
  53. package/assets/templates/next-tenant-app/src/app/robots.ts +7 -18
  54. package/assets/templates/next-tenant-app/src/app/sitemap.ts +4 -39
  55. package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +97 -98
  56. package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +43 -78
  57. package/assets/templates/next-tenant-app/src/features/demo/components/manifest-demo.tsx +89 -0
  58. package/assets/templates/next-tenant-app/src/features/public-shell/components/static-public-page.tsx +58 -0
  59. package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +48 -552
  60. package/assets/templates/next-tenant-app/src/lib/app-routes.ts +2 -2
  61. package/assets/templates/next-tenant-app/src/lib/public-site.test.ts +1 -1
  62. package/assets/templates/next-tenant-app/src/lib/public-site.ts +5 -30
  63. package/assets/templates/next-tenant-app/src/mw/client.ts +18 -0
  64. package/assets/templates/next-tenant-app/src/mw/mock.test.ts +21 -0
  65. package/assets/templates/next-tenant-app/src/mw/mock.ts +35 -0
  66. package/assets/templates/next-tenant-app/src/mw/provider.tsx +17 -0
  67. package/assets/templates/next-tenant-app/template.json +3 -3
  68. package/assets/templates/next-tenant-app/template.schema.json +1 -0
  69. package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +4 -5
  70. package/assets/templates/next-tenant-app/tools/template/with-public-site-fixture.mjs +2 -2
  71. package/bin/minutework.js +1 -1
  72. package/dist/agent.js +7 -7
  73. package/dist/agent.js.map +1 -1
  74. package/dist/auth.js +7 -7
  75. package/dist/auth.js.map +1 -1
  76. package/dist/compile.js +5 -5
  77. package/dist/config.js +6 -6
  78. package/dist/config.js.map +1 -1
  79. package/dist/deploy.js +7 -7
  80. package/dist/deploy.js.map +1 -1
  81. package/dist/developer-client.js +2 -2
  82. package/dist/developer-client.js.map +1 -1
  83. package/dist/index.js +30 -30
  84. package/dist/index.js.map +1 -1
  85. package/dist/init.js +10 -10
  86. package/dist/init.js.map +1 -1
  87. package/dist/launcher.js +1 -1
  88. package/dist/launcher.js.map +1 -1
  89. package/dist/managed-engine.js +6 -6
  90. package/dist/managed-engine.js.map +1 -1
  91. package/dist/orchestrator-context.js +1 -1
  92. package/dist/orchestrator-context.js.map +1 -1
  93. package/dist/orchestrator.js +15 -15
  94. package/dist/orchestrator.js.map +1 -1
  95. package/dist/paths.js +1 -1
  96. package/dist/paths.js.map +1 -1
  97. package/dist/publish.js +3 -3
  98. package/dist/publish.js.map +1 -1
  99. package/dist/reporting.js +8 -8
  100. package/dist/reporting.js.map +1 -1
  101. package/dist/sandbox.js +5 -5
  102. package/dist/sandbox.js.map +1 -1
  103. package/dist/state.js +1 -1
  104. package/dist/state.js.map +1 -1
  105. package/dist/tokens.js +9 -9
  106. package/dist/tokens.js.map +1 -1
  107. package/dist/workspace-assets.js +6 -6
  108. package/dist/workspace-assets.js.map +1 -1
  109. package/dist/workspace.js +3 -3
  110. package/dist/workspace.js.map +1 -1
  111. package/package.json +3 -3
  112. package/vendor/workspace-mcp/context.d.ts +6 -6
  113. package/vendor/workspace-mcp/context.js +56 -56
  114. package/vendor/workspace-mcp/context.js.map +1 -1
  115. package/vendor/workspace-mcp/types.d.ts +4 -0
  116. package/assets/templates/next-tenant-app/src/app/(cms)/[...path]/page.tsx +0 -89
  117. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +0 -90
  118. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +0 -78
  119. package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +0 -31
  120. package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +0 -16
  121. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +0 -79
  122. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +0 -40
  123. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +0 -42
  124. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +0 -29
  125. package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +0 -26
  126. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +0 -40
  127. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +0 -47
  128. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +0 -43
  129. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +0 -45
  130. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +0 -83
  131. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +0 -30
  132. package/assets/templates/next-tenant-app/src/app/app/page.test.ts +0 -62
  133. package/assets/templates/next-tenant-app/src/app/app/private-content-source.test.ts +0 -88
  134. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +0 -70
  135. package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +0 -46
  136. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +0 -70
  137. package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +0 -46
  138. package/assets/templates/next-tenant-app/src/app/login/page.test.ts +0 -55
  139. package/assets/templates/next-tenant-app/src/app/page.test.ts +0 -90
  140. package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +0 -59
  141. package/assets/templates/next-tenant-app/src/app/robots.test.ts +0 -40
  142. package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +0 -63
  143. package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +0 -342
  144. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +0 -66
  145. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +0 -108
  146. package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +0 -111
  147. package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +0 -111
  148. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.test.ts +0 -38
  149. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.tsx +0 -145
  150. package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +0 -189
  151. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +0 -444
  152. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +0 -383
  153. package/assets/templates/next-tenant-app/src/lib/content/contracts.test.ts +0 -138
  154. package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +0 -399
  155. package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +0 -5
  156. package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +0 -96
  157. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.test.ts +0 -93
  158. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.ts +0 -123
  159. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +0 -75
  160. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +0 -25
  161. package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +0 -170
  162. package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +0 -661
  163. package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +0 -131
  164. package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +0 -34
  165. package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +0 -211
  166. package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +0 -151
  167. package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +0 -33
  168. package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +0 -108
@@ -3,157 +3,45 @@
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
- In the external CLI scaffold flow, the same starter is copied into
9
- `tenant-app/`.
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 stays fixed to the `platform_session_bff` profile:
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
31
-
32
- - Node.js `20.9.0` or newer
33
- - `pnpm@9.0.0`
14
+ This template uses the `tenant_web_auth_sdk` profile:
34
15
 
35
- ## Direct web hosts
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`
36
21
 
37
- When this starter lives inside an external CLI workspace with an optional
38
- `sidecar`, deploy direct web hosts against `tenant-app` only.
39
-
40
- - Root `pnpm install` stays Node-only and does not auto-install sidecar Python deps.
41
- - If you need the sidecar locally, run `pnpm run install:sidecar` from the workspace root or `cd sidecar && poetry install`.
42
- - For Vercel, set the project Root Directory to `tenant-app`.
43
-
44
- Those host-specific instructions apply to the CLI scaffold layout, not the
45
- runtime Builder sandbox layout under `app/`.
22
+ Only `src/mw/` is MinuteWork substrate. Keep product UI and product logic in
23
+ `src/app`, `src/features`, and developer-owned modules.
46
24
 
47
25
  ## Default route shape
48
26
 
49
- The template ships one combined app with two explicit route surfaces:
50
-
51
- - public routes at the root: `/`, `/pricing`, `/docs`, `/blog`, `/login`
52
- - authenticated routes under `/app`
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
- PandaWork 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
27
+ - public routes at `/`, `/pricing`, `/docs`, and `/blog`
28
+ - customer auth at `/login`
29
+ - authenticated workspace at `/app`
30
+ - SDK manifest demo at `/app/demo`
81
31
 
82
- `MW_ENABLE_RUNTIME_COMMAND_EXAMPLE=false` by default.
83
-
84
- When disabled:
85
-
86
- - `/app/examples/runtime-commands` returns `404`
87
- - the example gateway API routes return `404`
88
- - the example navigation entry is omitted
32
+ The template intentionally does not include `src/lib/platform/*`,
33
+ `src/app/api/auth/*`, `src/app/api/gateway/*`, an operator-console link, a
34
+ runtime-command demo, or a server-only public-content token adapter.
89
35
 
90
36
  ## Environment
91
37
 
92
- Copy `.env.example` to `.env.local` when running the template directly. The
93
- example file uses `MW_PUBLIC_CONTENT_SOURCE=none` so the private `/app` surface
94
- can boot without PandaWork CMS credentials; set `MW_PUBLIC_CONTENT_SOURCE` to
95
- `minutework_cms` and fill the CMS values below when enabling the public site.
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 `PandaWork 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.
38
+ - `NEXT_PUBLIC_MW_APP_ID` selects the manifest app id used by the SDK and
39
+ defaults to `tenant.app`
40
+ - `MW_TEMPLATE_APP_NAME` controls the display name and defaults to `Tenant App`
41
+ - `MW_PUBLIC_BASE_URL` is optional and only affects metadata routes
130
42
 
131
43
  ## Validation
132
44
 
133
- Use `pnpm validate` from this directory. It runs:
134
-
135
- 1. `template:validate`
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.
45
+ Use `pnpm validate` from this directory. It runs template validation, route
46
+ contract validation, typegen, design-system checks, typecheck, tests, and build
47
+ validation.
@@ -27,6 +27,7 @@
27
27
  "design-system:visual": "playwright test -c tools/design-system/playwright.config.mjs"
28
28
  },
29
29
  "dependencies": {
30
+ "@minutework/web-auth": "^0.1.0",
30
31
  "@radix-ui/react-slot": "^1.2.4",
31
32
  "class-variance-authority": "^0.7.1",
32
33
  "clsx": "^2.1.1",
@@ -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 async function AuthenticatedAppLayout({
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 PandaWork starter.",
10
5
  };
11
6
 
12
- export default async function AppHomePage() {
13
- const session = await resolveAuthenticatedSession();
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 { notFound } from "next/navigation";
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
2
 
3
- import { ContentArticle } from "@/features/public-shell/components/content-article";
4
- import { PublicSiteShell } from "@/features/public-shell/components/public-site-shell";
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 const dynamicParams = false;
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
- <PublicSiteShell activeHref={appRoutes.blogIndex} siteConfig={siteConfig}>
70
- <ContentArticle entry={entry} />
71
- </PublicSiteShell>
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 { notFound } from "next/navigation";
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
2
 
3
- import { ContentCollection } from "@/features/public-shell/components/content-collection";
4
- import { PublicSiteShell } from "@/features/public-shell/components/public-site-shell";
5
- import { 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
- 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
- <PublicSiteShell activeHref={appRoutes.blogIndex} siteConfig={siteConfig}>
42
- <ContentCollection
43
- description={collection.description}
44
- entries={entries}
45
- eyebrow={collection.eyebrow}
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
  }
@@ -1,71 +1,15 @@
1
- import { notFound } from "next/navigation";
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
2
 
3
- import { ContentArticle } from "@/features/public-shell/components/content-article";
4
- import { PublicSiteShell } from "@/features/public-shell/components/public-site-shell";
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 DocsArticlePageProps = {
14
- params: Promise<{ slug: string[] }>;
3
+ export const metadata = {
4
+ title: "Docs",
15
5
  };
16
6
 
17
- export const dynamicParams = false;
18
-
19
- export async function generateStaticParams() {
20
- if (isPublicContentDisabled()) {
21
- return [];
22
- }
23
-
24
- return (await listEntries("docs")).map((entry) => ({
25
- slug: entry.slug,
26
- }));
27
- }
28
-
29
- export async function generateMetadata({ params }: DocsArticlePageProps) {
30
- if (isPublicContentDisabled()) {
31
- return buildDisabledPublicMetadata();
32
- }
33
-
34
- const [{ slug }, siteConfig] = await Promise.all([params, getSiteConfig()]);
35
- const entry = await getEntry("docs", slug);
36
-
37
- if (!entry) {
38
- return buildPublicMetadata({
39
- title: siteConfig.collections.docs.title,
40
- description: siteConfig.collections.docs.description,
41
- path: appRoutes.docsIndex,
42
- siteName: siteConfig.siteName,
43
- });
44
- }
45
-
46
- return buildPublicMetadata({
47
- title: entry.seo.title,
48
- description: entry.seo.description,
49
- path: appRoutes.docsPage(entry.slug),
50
- siteName: siteConfig.siteName,
51
- });
52
- }
53
-
54
- export default async function DocsArticlePage({ params }: DocsArticlePageProps) {
55
- if (isPublicContentDisabled()) {
56
- notFound();
57
- }
58
-
59
- const [{ slug }, siteConfig] = await Promise.all([params, getSiteConfig()]);
60
- const entry = await getEntry("docs", slug);
61
-
62
- if (!entry) {
63
- notFound();
64
- }
65
-
7
+ export default function DocsPage() {
66
8
  return (
67
- <PublicSiteShell activeHref={appRoutes.docsIndex} siteConfig={siteConfig}>
68
- <ContentArticle entry={entry} />
69
- </PublicSiteShell>
9
+ <StaticPublicPage
10
+ eyebrow="Docs"
11
+ title="Documentation page"
12
+ body="Helpful context for customer workflows."
13
+ />
70
14
  );
71
15
  }
@@ -1,51 +1,15 @@
1
- import { notFound } from "next/navigation";
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
2
 
3
- import { ContentCollection } from "@/features/public-shell/components/content-collection";
4
- import { PublicSiteShell } from "@/features/public-shell/components/public-site-shell";
5
- import { 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
- export async function generateMetadata() {
14
- if (isPublicContentDisabled()) {
15
- return buildDisabledPublicMetadata();
16
- }
17
-
18
- const siteConfig = await getSiteConfig();
19
- const collection = siteConfig.collections.docs;
20
-
21
- return buildPublicMetadata({
22
- title: collection.title,
23
- description: collection.description,
24
- path: appRoutes.docsIndex,
25
- siteName: siteConfig.siteName,
26
- });
27
- }
28
-
29
- export default async function DocsIndexPage() {
30
- if (isPublicContentDisabled()) {
31
- notFound();
32
- }
33
-
34
- const [siteConfig, entries] = await Promise.all([
35
- getSiteConfig(),
36
- listEntries("docs"),
37
- ]);
38
- const collection = siteConfig.collections.docs;
3
+ export const metadata = {
4
+ title: "Docs",
5
+ };
39
6
 
7
+ export default function DocsIndexPage() {
40
8
  return (
41
- <PublicSiteShell activeHref={appRoutes.docsIndex} siteConfig={siteConfig}>
42
- <ContentCollection
43
- description={collection.description}
44
- entries={entries}
45
- eyebrow={collection.eyebrow}
46
- kind="docs"
47
- title={collection.title}
48
- />
49
- </PublicSiteShell>
9
+ <StaticPublicPage
10
+ eyebrow="Docs"
11
+ title="Customer documentation"
12
+ body="Guides, onboarding notes, and product documentation."
13
+ />
50
14
  );
51
15
  }
@@ -5,7 +5,6 @@ import { cookies } from "next/headers";
5
5
  import type { ReactNode } from "react";
6
6
 
7
7
  import { AppProviders } from "./providers";
8
- import { env } from "@/lib/platform/env.server";
9
8
  import { resolvePublicMetadataBase } from "@/lib/public-site";
10
9
  import {
11
10
  isThemeMode,
@@ -25,22 +24,21 @@ const geistMono = Geist_Mono({
25
24
 
26
25
  export function generateMetadata(): Metadata {
27
26
  const metadataBase = resolvePublicMetadataBase();
27
+ const appName = process.env.MW_TEMPLATE_APP_NAME || "Tenant App";
28
28
 
29
29
  return {
30
30
  ...(metadataBase ? { metadataBase } : {}),
31
31
  title: {
32
- default: env.MW_TEMPLATE_APP_NAME,
33
- template: `%s | ${env.MW_TEMPLATE_APP_NAME}`,
32
+ default: appName,
33
+ template: `%s | ${appName}`,
34
34
  },
35
- description:
36
- "SEO-first public and authenticated Next.js starter with a server-owned platform session BFF.",
35
+ description: "Customer-facing tenant app.",
37
36
  openGraph: {
38
- title: env.MW_TEMPLATE_APP_NAME,
39
- description:
40
- "SEO-first public and authenticated Next.js starter with a server-owned platform session BFF.",
41
- siteName: env.MW_TEMPLATE_APP_NAME,
37
+ title: appName,
38
+ description: "Customer-facing tenant app.",
39
+ siteName: appName,
42
40
  type: "website",
43
- ...(env.MW_PUBLIC_BASE_URL ? { url: env.MW_PUBLIC_BASE_URL } : {}),
41
+ ...(process.env.MW_PUBLIC_BASE_URL ? { url: process.env.MW_PUBLIC_BASE_URL } : {}),
44
42
  },
45
43
  };
46
44
  }
@@ -1,33 +1,13 @@
1
- import { redirect } from "next/navigation";
2
-
3
1
  import { LoginScreen } from "@/features/auth/components/login-screen";
4
- import { appRoutes } from "@/lib/app-routes";
5
- import { resolveCurrentSession } from "@/lib/platform/client.server";
6
- import { platformAuthEndpoints } from "@/lib/platform/endpoints.server";
7
- import { env } from "@/lib/platform/env.server";
8
- import { readPlatformAuthState } from "@/lib/platform/session.server";
9
2
 
10
3
  export const metadata = {
11
- title: "Sign In",
12
- description:
13
- "Authenticate into the combined PandaWork starter to continue into the private workspace surface under /app.",
4
+ title: "Log In",
14
5
  robots: {
15
6
  index: false,
16
7
  follow: false,
17
8
  },
18
9
  };
19
10
 
20
- export default async function LoginPage() {
21
- const session = await resolveCurrentSession(await readPlatformAuthState());
22
-
23
- if (session.authenticated) {
24
- redirect(appRoutes.appHome);
25
- }
26
-
27
- return (
28
- <LoginScreen
29
- appName={env.MW_TEMPLATE_APP_NAME}
30
- operatorConsoleHref={platformAuthEndpoints.operatorConsole}
31
- />
32
- );
11
+ export default function LoginPage() {
12
+ return <LoginScreen appName={process.env.MW_TEMPLATE_APP_NAME || "Tenant App"} />;
33
13
  }