minutework 0.1.40 → 0.1.41

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 (162) hide show
  1. package/EXTERNAL_ALPHA.md +17 -1
  2. package/README.md +21 -1
  3. package/assets/claude-local/skills/README.md +5 -0
  4. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +15 -0
  5. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +25 -9
  6. package/assets/claude-local/skills/shell-architecture/SKILL.md +15 -0
  7. package/assets/claude-local/skills/vuilder-public-site-authoring/SKILL.md +10 -4
  8. package/assets/claude-local/skills/vuilder-workspace-architecture/SKILL.md +78 -0
  9. package/assets/templates/vuilder-public-site/.env.example +11 -0
  10. package/assets/templates/vuilder-public-site/README.md +15 -0
  11. package/assets/templates/vuilder-public-site/eslint.config.mjs +6 -0
  12. package/assets/templates/vuilder-public-site/next-env.d.ts +4 -0
  13. package/assets/templates/vuilder-public-site/next.config.mjs +28 -0
  14. package/assets/templates/vuilder-public-site/package.json +39 -0
  15. package/assets/templates/vuilder-public-site/postcss.config.mjs +5 -0
  16. package/assets/templates/vuilder-public-site/src/app/api/onboarding/start/route.ts +19 -0
  17. package/assets/templates/vuilder-public-site/src/app/blog/[slug]/page.tsx +25 -0
  18. package/assets/templates/vuilder-public-site/src/app/blog/page.tsx +26 -0
  19. package/assets/templates/vuilder-public-site/src/app/globals.css +103 -0
  20. package/assets/templates/vuilder-public-site/src/app/layout.tsx +18 -0
  21. package/assets/templates/vuilder-public-site/src/app/onboarding/[[...step]]/page.tsx +39 -0
  22. package/assets/templates/vuilder-public-site/src/app/page.tsx +43 -0
  23. package/assets/templates/vuilder-public-site/src/lib/env.server.ts +31 -0
  24. package/assets/templates/vuilder-public-site/src/lib/platform.server.ts +47 -0
  25. package/assets/templates/vuilder-public-site/src/lib/public-dj.server.ts +86 -0
  26. package/assets/templates/vuilder-public-site/src/lib/public-dj.test.ts +8 -0
  27. package/assets/templates/vuilder-public-site/src/lib/routes.test.ts +13 -0
  28. package/assets/templates/vuilder-public-site/src/lib/routes.ts +12 -0
  29. package/assets/templates/vuilder-public-site/template.json +21 -0
  30. package/assets/templates/vuilder-public-site/tools/template/validate-template.mjs +44 -0
  31. package/assets/templates/vuilder-public-site/tsconfig.json +23 -0
  32. package/assets/templates/vuilder-public-site/vitest.config.ts +13 -0
  33. package/assets/templates/vuilder-shell/.env.example +8 -0
  34. package/assets/templates/vuilder-shell/.storybook/main.ts +19 -0
  35. package/assets/templates/vuilder-shell/.storybook/preview.tsx +38 -0
  36. package/assets/templates/vuilder-shell/README.md +49 -0
  37. package/assets/templates/vuilder-shell/components.json +21 -0
  38. package/assets/templates/vuilder-shell/eslint.config.mjs +41 -0
  39. package/assets/templates/vuilder-shell/next-env.d.ts +6 -0
  40. package/assets/templates/vuilder-shell/next.config.mjs +33 -0
  41. package/assets/templates/vuilder-shell/package.json +61 -0
  42. package/assets/templates/vuilder-shell/postcss.config.mjs +8 -0
  43. package/assets/templates/vuilder-shell/public/.gitkeep +1 -0
  44. package/assets/templates/vuilder-shell/src/app/api/auth/accept-shell-session/route.test.ts +105 -0
  45. package/assets/templates/vuilder-shell/src/app/api/auth/accept-shell-session/route.ts +63 -0
  46. package/assets/templates/vuilder-shell/src/app/api/auth/logout/route.test.ts +63 -0
  47. package/assets/templates/vuilder-shell/src/app/api/auth/logout/route.ts +24 -0
  48. package/assets/templates/vuilder-shell/src/app/api/auth/session/route.test.ts +70 -0
  49. package/assets/templates/vuilder-shell/src/app/api/auth/session/route.ts +27 -0
  50. package/assets/templates/vuilder-shell/src/app/app/layout.tsx +17 -0
  51. package/assets/templates/vuilder-shell/src/app/app/page.tsx +30 -0
  52. package/assets/templates/vuilder-shell/src/app/blog/[slug]/page.tsx +15 -0
  53. package/assets/templates/vuilder-shell/src/app/blog/page.tsx +15 -0
  54. package/assets/templates/vuilder-shell/src/app/docs/[...slug]/page.tsx +15 -0
  55. package/assets/templates/vuilder-shell/src/app/docs/page.tsx +15 -0
  56. package/assets/templates/vuilder-shell/src/app/globals.css +70 -0
  57. package/assets/templates/vuilder-shell/src/app/layout.tsx +69 -0
  58. package/assets/templates/vuilder-shell/src/app/login/route.test.ts +33 -0
  59. package/assets/templates/vuilder-shell/src/app/login/route.ts +21 -0
  60. package/assets/templates/vuilder-shell/src/app/page.tsx +16 -0
  61. package/assets/templates/vuilder-shell/src/app/pricing/page.tsx +15 -0
  62. package/assets/templates/vuilder-shell/src/app/providers.tsx +25 -0
  63. package/assets/templates/vuilder-shell/src/app/robots.ts +21 -0
  64. package/assets/templates/vuilder-shell/src/app/sitemap.ts +33 -0
  65. package/assets/templates/vuilder-shell/src/app/w/[workspace_slug]/connect/page.tsx +31 -0
  66. package/assets/templates/vuilder-shell/src/app/w/[workspace_slug]/page.tsx +54 -0
  67. package/assets/templates/vuilder-shell/src/components/ui/button.tsx +59 -0
  68. package/assets/templates/vuilder-shell/src/components/ui/input.tsx +21 -0
  69. package/assets/templates/vuilder-shell/src/design-system/docs/governance.mdx +26 -0
  70. package/assets/templates/vuilder-shell/src/design-system/patterns/panel-frame.stories.tsx +48 -0
  71. package/assets/templates/vuilder-shell/src/design-system/patterns/panel-frame.tsx +26 -0
  72. package/assets/templates/vuilder-shell/src/design-system/patterns/status-badge.stories.tsx +26 -0
  73. package/assets/templates/vuilder-shell/src/design-system/patterns/status-badge.tsx +35 -0
  74. package/assets/templates/vuilder-shell/src/design-system/patterns/theme-mode-toggle.stories.tsx +21 -0
  75. package/assets/templates/vuilder-shell/src/design-system/patterns/theme-mode-toggle.tsx +75 -0
  76. package/assets/templates/vuilder-shell/src/design-system/primitives/button.stories.tsx +37 -0
  77. package/assets/templates/vuilder-shell/src/design-system/primitives/button.ts +1 -0
  78. package/assets/templates/vuilder-shell/src/design-system/primitives/input.stories.tsx +26 -0
  79. package/assets/templates/vuilder-shell/src/design-system/primitives/input.ts +1 -0
  80. package/assets/templates/vuilder-shell/src/design-system/recipes/chrome.ts +28 -0
  81. package/assets/templates/vuilder-shell/src/design-system/tokens/foundation.css +31 -0
  82. package/assets/templates/vuilder-shell/src/design-system/tokens/index.css +3 -0
  83. package/assets/templates/vuilder-shell/src/design-system/tokens/manifest.json +85 -0
  84. package/assets/templates/vuilder-shell/src/design-system/tokens/manifest.ts +87 -0
  85. package/assets/templates/vuilder-shell/src/design-system/tokens/semantic.css +105 -0
  86. package/assets/templates/vuilder-shell/src/design-system/tokens/theme.css +59 -0
  87. package/assets/templates/vuilder-shell/src/design-system/tokens/tokens.stories.tsx +71 -0
  88. package/assets/templates/vuilder-shell/src/features/dashboard/components/tenant-dashboard.tsx +134 -0
  89. package/assets/templates/vuilder-shell/src/features/public-shell/components/static-public-page.tsx +58 -0
  90. package/assets/templates/vuilder-shell/src/features/shell/components/authenticated-app-layout-shell.tsx +84 -0
  91. package/assets/templates/vuilder-shell/src/features/shell/components/private-app-shell.tsx +22 -0
  92. package/assets/templates/vuilder-shell/src/features/vuilder-shell/components/vuilder-connect-screen.tsx +89 -0
  93. package/assets/templates/vuilder-shell/src/features/vuilder-shell/components/vuilder-workspace-screen.tsx +49 -0
  94. package/assets/templates/vuilder-shell/src/lib/app-routes.test.ts +37 -0
  95. package/assets/templates/vuilder-shell/src/lib/app-routes.ts +86 -0
  96. package/assets/templates/vuilder-shell/src/lib/auth-routes.server.test.ts +26 -0
  97. package/assets/templates/vuilder-shell/src/lib/auth-routes.server.ts +53 -0
  98. package/assets/templates/vuilder-shell/src/lib/http/same-origin.test.ts +23 -0
  99. package/assets/templates/vuilder-shell/src/lib/http/same-origin.ts +18 -0
  100. package/assets/templates/vuilder-shell/src/lib/platform/client.server.test.ts +201 -0
  101. package/assets/templates/vuilder-shell/src/lib/platform/client.server.ts +540 -0
  102. package/assets/templates/vuilder-shell/src/lib/platform/contracts.ts +190 -0
  103. package/assets/templates/vuilder-shell/src/lib/platform/endpoints.server.ts +29 -0
  104. package/assets/templates/vuilder-shell/src/lib/platform/env.server.ts +82 -0
  105. package/assets/templates/vuilder-shell/src/lib/platform/route-response.ts +33 -0
  106. package/assets/templates/vuilder-shell/src/lib/platform/session.server.ts +145 -0
  107. package/assets/templates/vuilder-shell/src/lib/public-site.test.ts +20 -0
  108. package/assets/templates/vuilder-shell/src/lib/public-site.ts +48 -0
  109. package/assets/templates/vuilder-shell/src/lib/theme-config.ts +10 -0
  110. package/assets/templates/vuilder-shell/src/lib/theme.tsx +159 -0
  111. package/assets/templates/vuilder-shell/src/lib/utils.ts +6 -0
  112. package/assets/templates/vuilder-shell/template.json +28 -0
  113. package/assets/templates/vuilder-shell/template.schema.json +171 -0
  114. package/assets/templates/vuilder-shell/test/server-only-stub.ts +1 -0
  115. package/assets/templates/vuilder-shell/tools/design-system/build-token-manifest.mjs +3 -0
  116. package/assets/templates/vuilder-shell/tools/design-system/check-imports.mjs +9 -0
  117. package/assets/templates/vuilder-shell/tools/design-system/check-stories.mjs +9 -0
  118. package/assets/templates/vuilder-shell/tools/design-system/check-values.mjs +9 -0
  119. package/assets/templates/vuilder-shell/tools/design-system/checks.mjs +238 -0
  120. package/assets/templates/vuilder-shell/tools/design-system/eslint-plugin-design-system.mjs +184 -0
  121. package/assets/templates/vuilder-shell/tools/design-system/playwright.config.mjs +34 -0
  122. package/assets/templates/vuilder-shell/tools/design-system/run-checks.mjs +22 -0
  123. package/assets/templates/vuilder-shell/tools/design-system/shared.mjs +166 -0
  124. package/assets/templates/vuilder-shell/tools/design-system/visual.spec.ts +41 -0
  125. package/assets/templates/vuilder-shell/tools/template/validate-route-contract.mjs +373 -0
  126. package/assets/templates/vuilder-shell/tools/template/validate-template.mjs +45 -0
  127. package/assets/templates/vuilder-shell/tools/template/with-public-site-fixture.mjs +45 -0
  128. package/assets/templates/vuilder-shell/tsconfig.json +42 -0
  129. package/assets/templates/vuilder-shell/vitest.config.ts +23 -0
  130. package/dist/auth.js +66 -14
  131. package/dist/auth.js.map +1 -1
  132. package/dist/deploy-state.d.ts +1 -0
  133. package/dist/deploy-state.js.map +1 -1
  134. package/dist/deploy.js +18 -4
  135. package/dist/deploy.js.map +1 -1
  136. package/dist/developer-client.d.ts +1 -1
  137. package/dist/index.js +12 -2
  138. package/dist/index.js.map +1 -1
  139. package/dist/init-prompt.js +21 -13
  140. package/dist/init-prompt.js.map +1 -1
  141. package/dist/init.d.ts +3 -1
  142. package/dist/init.js +103 -12
  143. package/dist/init.js.map +1 -1
  144. package/dist/orchestrator-context.js +17 -5
  145. package/dist/orchestrator-context.js.map +1 -1
  146. package/dist/orchestrator-state.d.ts +2 -2
  147. package/dist/orchestrator-state.js.map +1 -1
  148. package/dist/publish.js +12 -2
  149. package/dist/publish.js.map +1 -1
  150. package/dist/state.d.ts +2 -0
  151. package/dist/state.js +9 -0
  152. package/dist/state.js.map +1 -1
  153. package/package.json +3 -3
  154. package/vendor/workspace-mcp/context.d.ts +3 -1
  155. package/vendor/workspace-mcp/context.js +134 -21
  156. package/vendor/workspace-mcp/context.js.map +1 -1
  157. package/vendor/workspace-mcp/types.d.ts +72 -7
  158. package/vendor/workspace-mcp/types.js +8 -4
  159. package/vendor/workspace-mcp/types.js.map +1 -1
  160. package/assets/templates/fastapi-sidecar/poetry.lock +0 -757
  161. package/assets/templates/next-tenant-app/package-lock.json +0 -9682
  162. package/assets/templates/next-tenant-app/pnpm-lock.yaml +0 -6062
@@ -0,0 +1,39 @@
1
+ import { getSiteManifest } from "@/lib/public-dj.server";
2
+
3
+ export default async function OnboardingPage({
4
+ params,
5
+ }: {
6
+ params: Promise<{ step?: string[] }>;
7
+ }) {
8
+ const { step = [] } = await params;
9
+ const manifest = await getSiteManifest();
10
+
11
+ return (
12
+ <main className="site-main">
13
+ <p className="eyebrow">Onboarding</p>
14
+ <h1>Start with {manifest.display_name}</h1>
15
+ <div className="panel">
16
+ <p>Current step: {step.join("/") || "intake"}</p>
17
+ <form action="/api/onboarding/start" method="post">
18
+ <p>
19
+ <label>
20
+ Email
21
+ <br />
22
+ <input name="email" type="email" required />
23
+ </label>
24
+ </p>
25
+ <p>
26
+ <label>
27
+ Name
28
+ <br />
29
+ <input name="name" type="text" />
30
+ </label>
31
+ </p>
32
+ <button className="button" type="submit">
33
+ Continue
34
+ </button>
35
+ </form>
36
+ </div>
37
+ </main>
38
+ );
39
+ }
@@ -0,0 +1,43 @@
1
+ import Link from "next/link";
2
+
3
+ import { appRoutes } from "@/lib/routes";
4
+ import { getHomePageRef, getSiteManifest } from "@/lib/public-dj.server";
5
+
6
+ export default async function HomePage() {
7
+ const [manifest, homePage] = await Promise.all([getSiteManifest(), getHomePageRef()]);
8
+ const headline = String(manifest.payload.metadata.headline ?? manifest.display_name);
9
+ const summary = String(
10
+ manifest.payload.metadata.summary ??
11
+ "A custom Vuilder public site rendered from pinned public-dj content.",
12
+ );
13
+
14
+ return (
15
+ <div className="site-shell">
16
+ <header className="site-header">
17
+ <strong>{manifest.display_name}</strong>
18
+ <nav>
19
+ <Link href={appRoutes.blog}>Blog</Link>
20
+ {" / "}
21
+ <Link href={appRoutes.onboarding()}>Start</Link>
22
+ </nav>
23
+ </header>
24
+ <main className="site-main">
25
+ <section className="hero">
26
+ <p className="eyebrow">{manifest.vertical_slug}</p>
27
+ <h1>{headline}</h1>
28
+ <p>{summary}</p>
29
+ <Link className="button" href={appRoutes.onboarding()}>
30
+ Start onboarding
31
+ </Link>
32
+ </section>
33
+ <section className="panel">
34
+ <p>
35
+ Release manifest: {manifest.content_ref} / {manifest.digest}
36
+ </p>
37
+ <p>Home page ref: {homePage?.content_ref ?? "not configured"}</p>
38
+ </section>
39
+ </main>
40
+ <footer className="site-footer">Powered by MinuteWork public-dj CMS.</footer>
41
+ </div>
42
+ );
43
+ }
@@ -0,0 +1,31 @@
1
+ import "server-only";
2
+
3
+ import { z } from "zod";
4
+
5
+ const envSchema = z.object({
6
+ MW_PUBLIC_DJ_BASE_URL: z.string().url(),
7
+ MW_AUTH_BASE_URL: z.string().url(),
8
+ MW_PLATFORM_BASE_URL: z.string().url(),
9
+ MW_PUBLIC_BASE_URL: z.string().url(),
10
+ MW_PUBLIC_SITE_PROPERTY_KEY: z.string().min(1),
11
+ MW_PUBLIC_SITE_ENV: z.enum(["preview", "live"]).default("preview"),
12
+ MW_PUBLIC_RELEASE_CLASS: z.enum(["static_export", "ssr_container"]).default("ssr_container"),
13
+ MW_PUBLIC_CONTENT_REF: z.string().min(1),
14
+ MW_PUBLIC_CONTENT_DIGEST: z.string().min(1),
15
+ MW_PUBLIC_DJ_READ_TOKEN: z.string().min(1),
16
+ MW_VUILDER_ONBOARDING_INTENT_TOKEN: z.string().min(1),
17
+ });
18
+
19
+ export const env = envSchema.parse({
20
+ MW_PUBLIC_DJ_BASE_URL: process.env.MW_PUBLIC_DJ_BASE_URL,
21
+ MW_AUTH_BASE_URL: process.env.MW_AUTH_BASE_URL,
22
+ MW_PLATFORM_BASE_URL: process.env.MW_PLATFORM_BASE_URL,
23
+ MW_PUBLIC_BASE_URL: process.env.MW_PUBLIC_BASE_URL,
24
+ MW_PUBLIC_SITE_PROPERTY_KEY: process.env.MW_PUBLIC_SITE_PROPERTY_KEY,
25
+ MW_PUBLIC_SITE_ENV: process.env.MW_PUBLIC_SITE_ENV,
26
+ MW_PUBLIC_RELEASE_CLASS: process.env.MW_PUBLIC_RELEASE_CLASS,
27
+ MW_PUBLIC_CONTENT_REF: process.env.MW_PUBLIC_CONTENT_REF,
28
+ MW_PUBLIC_CONTENT_DIGEST: process.env.MW_PUBLIC_CONTENT_DIGEST,
29
+ MW_PUBLIC_DJ_READ_TOKEN: process.env.MW_PUBLIC_DJ_READ_TOKEN,
30
+ MW_VUILDER_ONBOARDING_INTENT_TOKEN: process.env.MW_VUILDER_ONBOARDING_INTENT_TOKEN,
31
+ });
@@ -0,0 +1,47 @@
1
+ import "server-only";
2
+
3
+ import { env } from "@/lib/env.server";
4
+ import { getSiteManifest } from "@/lib/public-dj.server";
5
+
6
+ export async function createCustomerVerticalSignupIntent(input: {
7
+ email?: string;
8
+ name?: string;
9
+ answers?: Record<string, unknown>;
10
+ }) {
11
+ const manifest = await getSiteManifest();
12
+ const url = new URL("/api/v1/vuilder/onboarding-intents/", env.MW_PLATFORM_BASE_URL);
13
+ const response = await fetch(url.toString(), {
14
+ method: "POST",
15
+ headers: {
16
+ accept: "application/json",
17
+ authorization: `Bearer ${env.MW_VUILDER_ONBOARDING_INTENT_TOKEN}`,
18
+ "content-type": "application/json",
19
+ },
20
+ body: JSON.stringify({
21
+ kind: "customer_vertical_signup",
22
+ source: "vuilder_public_site",
23
+ vuilder_workspace_external_key: String(
24
+ manifest.payload.metadata.vuilder_workspace_external_key ?? "",
25
+ ),
26
+ property_key: env.MW_PUBLIC_SITE_PROPERTY_KEY,
27
+ site_release_key: String(manifest.payload.metadata.site_release_key ?? ""),
28
+ public_site_manifest_ref: manifest.content_ref,
29
+ public_site_manifest_digest: manifest.digest,
30
+ vertical_package_ref: manifest.payload.vertical_package?.content_ref ?? "",
31
+ vertical_package_digest: manifest.payload.vertical_package?.digest ?? "",
32
+ onboarding_flow_ref: manifest.payload.onboarding_flow?.content_ref ?? "",
33
+ onboarding_flow_digest: manifest.payload.onboarding_flow?.digest ?? "",
34
+ intake_payload: {
35
+ email: input.email ?? "",
36
+ name: input.name ?? "",
37
+ answers: input.answers ?? {},
38
+ },
39
+ }),
40
+ });
41
+
42
+ if (!response.ok) {
43
+ throw new Error(`Unable to create onboarding intent (${response.status}).`);
44
+ }
45
+
46
+ return response.json();
47
+ }
@@ -0,0 +1,86 @@
1
+ import "server-only";
2
+
3
+ import { cache } from "react";
4
+ import { z } from "zod";
5
+
6
+ import { env } from "@/lib/env.server";
7
+
8
+ const refDigestSchema = z.object({
9
+ content_ref: z.string().min(1),
10
+ digest: z.string().min(1),
11
+ });
12
+
13
+ const marketplacePackageRefSchema = z
14
+ .string()
15
+ .min(1)
16
+ .regex(/^core:app_pack_catalog:[^:]+:[^:]+:[^:]+$/);
17
+
18
+ const verticalPackageRefDigestSchema = refDigestSchema.extend({
19
+ content_ref: marketplacePackageRefSchema,
20
+ });
21
+
22
+ const siteManifestSchema = z.object({
23
+ content_ref: z.string(),
24
+ digest: z.string(),
25
+ content_kind: z.literal("public_site_manifest"),
26
+ vertical_key: z.string(),
27
+ vertical_slug: z.string(),
28
+ property_key: z.string(),
29
+ display_name: z.string(),
30
+ payload: z.object({
31
+ pages: z.array(
32
+ refDigestSchema.extend({
33
+ page_key: z.string(),
34
+ path: z.string(),
35
+ }),
36
+ ),
37
+ blog_posts: z.array(refDigestSchema).default([]),
38
+ onboarding_flow: refDigestSchema.nullable().optional(),
39
+ vertical_package: verticalPackageRefDigestSchema.nullable().optional(),
40
+ navigation: z.array(z.unknown()).default([]),
41
+ media: z.array(z.unknown()).default([]),
42
+ theme_tokens: z.record(z.unknown()).default({}),
43
+ metadata: z.record(z.unknown()).default({}),
44
+ }),
45
+ });
46
+
47
+ export type PublicSiteManifest = z.infer<typeof siteManifestSchema>;
48
+
49
+ async function resolvePublicContent(contentRef: string, digest: string) {
50
+ const url = new URL("/api/v1/content/resolve/", env.MW_PUBLIC_DJ_BASE_URL);
51
+ url.searchParams.set("content_ref", contentRef);
52
+ url.searchParams.set("digest", digest);
53
+ const response = await fetch(url.toString(), {
54
+ headers: {
55
+ accept: "application/json",
56
+ authorization: `Bearer ${env.MW_PUBLIC_DJ_READ_TOKEN}`,
57
+ },
58
+ next: {
59
+ revalidate: env.MW_PUBLIC_SITE_ENV === "live" ? 300 : 0,
60
+ tags: ["vuilder-public-site", env.MW_PUBLIC_SITE_PROPERTY_KEY],
61
+ },
62
+ });
63
+
64
+ if (!response.ok) {
65
+ throw new Error(`Unable to resolve public-dj content (${response.status}).`);
66
+ }
67
+
68
+ return response.json();
69
+ }
70
+
71
+ export const getSiteManifest = cache(async (): Promise<PublicSiteManifest> => {
72
+ const payload = await resolvePublicContent(
73
+ env.MW_PUBLIC_CONTENT_REF,
74
+ env.MW_PUBLIC_CONTENT_DIGEST,
75
+ );
76
+ return siteManifestSchema.parse(payload);
77
+ });
78
+
79
+ export async function getHomePageRef() {
80
+ const manifest = await getSiteManifest();
81
+ return (
82
+ manifest.payload.pages.find((page) => page.path === "/") ??
83
+ manifest.payload.pages[0] ??
84
+ null
85
+ );
86
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ describe("public-dj template contract", () => {
4
+ it("uses public site manifest refs instead of storing component code in env", () => {
5
+ expect("MW_PUBLIC_CONTENT_REF").toContain("CONTENT_REF");
6
+ expect("MW_PUBLIC_CONTENT_DIGEST").toContain("DIGEST");
7
+ });
8
+ });
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { appRoutes } from "@/lib/routes";
4
+
5
+ describe("vuilder public site routes", () => {
6
+ it("declares public website and onboarding routes only", () => {
7
+ expect(appRoutes.home).toBe("/");
8
+ expect(appRoutes.blog).toBe("/blog");
9
+ expect(appRoutes.onboarding()).toBe("/onboarding");
10
+ expect(Object.values(appRoutes).join(" ")).not.toContain("/w/");
11
+ expect(Object.values(appRoutes).join(" ")).not.toContain("/app");
12
+ });
13
+ });
@@ -0,0 +1,12 @@
1
+ import type { Route } from "next";
2
+
3
+ export const appRoutes = {
4
+ home: "/" as Route,
5
+ blog: "/blog" as Route,
6
+ blogPost(slug: string) {
7
+ return `/blog/${encodeURIComponent(slug)}` as Route;
8
+ },
9
+ onboarding(step = "") {
10
+ return step ? (`/onboarding/${encodeURIComponent(step)}` as Route) : ("/onboarding" as Route);
11
+ },
12
+ };
@@ -0,0 +1,21 @@
1
+ {
2
+ "template_id": "vuilder-public-site",
3
+ "template_kind": "public_site",
4
+ "template_profile": "public_dj_cms",
5
+ "template_bundle_ref": "runtime/builder/templates/vuilder-public-site",
6
+ "template_version": "0.1.0",
7
+ "materialize": {
8
+ "destination": "app"
9
+ },
10
+ "builder_edit_mode": "workspace_copy_only",
11
+ "seed_source": "runtime/builder/templates/vuilder-public-site",
12
+ "required_bootstrap_steps": [
13
+ "next_typegen"
14
+ ],
15
+ "runtime_contract_refs": [
16
+ "reference/mwv3-dj6-docs/published_web_property_contract.md",
17
+ "reference/mwv3-dj6-docs/runtime_app_deployment_and_provisioning_contract.md",
18
+ "reference/mwv3-dj6-docs/auth_and_credential_contract.md"
19
+ ],
20
+ "example_features": {}
21
+ }
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const templateRoot = path.resolve(__dirname, "..", "..");
7
+ const manifestPath = path.join(templateRoot, "template.json");
8
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
9
+
10
+ const requiredFields = [
11
+ "template_id",
12
+ "template_kind",
13
+ "template_profile",
14
+ "template_version",
15
+ "materialize",
16
+ "builder_edit_mode",
17
+ "seed_source",
18
+ "required_bootstrap_steps",
19
+ "example_features",
20
+ ];
21
+
22
+ for (const field of requiredFields) {
23
+ if (!(field in manifest)) {
24
+ throw new Error(`template.json is missing required field "${field}"`);
25
+ }
26
+ }
27
+
28
+ if (manifest.template_id !== "vuilder-public-site") {
29
+ throw new Error("template_id must be vuilder-public-site");
30
+ }
31
+
32
+ if (manifest.template_kind !== "public_site") {
33
+ throw new Error("template_kind must be public_site");
34
+ }
35
+
36
+ if (manifest.template_profile !== "public_dj_cms") {
37
+ throw new Error("template_profile must be public_dj_cms");
38
+ }
39
+
40
+ if (manifest.materialize?.destination !== "app") {
41
+ throw new Error("vuilder-public-site must materialize to app");
42
+ }
43
+
44
+ console.log("template.json is valid");
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": false,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": {
18
+ "@/*": ["./src/*"]
19
+ }
20
+ },
21
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "exclude": ["node_modules"]
23
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ globals: true,
7
+ },
8
+ resolve: {
9
+ alias: {
10
+ "@": new URL("./src", import.meta.url).pathname,
11
+ },
12
+ },
13
+ });
@@ -0,0 +1,8 @@
1
+ NEXT_PUBLIC_MW_APP_ID=vuilder.vertical
2
+ MW_TEMPLATE_APP_NAME=Vuilder Shell
3
+ MW_PUBLIC_BASE_URL=http://127.0.0.1:3301
4
+ MW_AUTH_BASE_URL=http://127.0.0.1:3400
5
+ MW_PUBLIC_SITE_PROPERTY_KEY=vuilder-shell
6
+ MW_PUBLIC_SITE_ENV=preview
7
+ MW_PLATFORM_BASE_URL=http://127.0.0.1:8000
8
+ MW_PLATFORM_FETCH_TIMEOUT_MS=15000
@@ -0,0 +1,19 @@
1
+ import { defineMain } from "@storybook/nextjs-vite/node";
2
+
3
+ export default defineMain({
4
+ stories: [
5
+ "../src/design-system/**/*.stories.@(ts|tsx|mdx)",
6
+ "../src/design-system/docs/**/*.mdx",
7
+ ],
8
+ addons: ["@storybook/addon-docs", "@storybook/addon-a11y"],
9
+ framework: {
10
+ name: "@storybook/nextjs-vite",
11
+ options: {
12
+ nextConfigPath: "../next.config.mjs",
13
+ },
14
+ },
15
+ staticDirs: ["../public"],
16
+ docs: {
17
+ defaultName: "Overview",
18
+ },
19
+ });
@@ -0,0 +1,38 @@
1
+ import { definePreview } from "@storybook/nextjs-vite";
2
+
3
+ import "../src/app/globals.css";
4
+ import { ThemeProvider } from "../src/lib/theme";
5
+
6
+ export default definePreview({
7
+ decorators: [
8
+ (Story) => (
9
+ <ThemeProvider
10
+ attribute="class"
11
+ defaultTheme="light"
12
+ enableSystem
13
+ disableTransitionOnChange
14
+ >
15
+ <div className="min-h-screen bg-background p-6 text-foreground">
16
+ <Story />
17
+ </div>
18
+ </ThemeProvider>
19
+ ),
20
+ ],
21
+ parameters: {
22
+ layout: "centered",
23
+ nextjs: {
24
+ appDirectory: true,
25
+ },
26
+ controls: {
27
+ matchers: {
28
+ color: /(background|color)$/i,
29
+ date: /Date$/i,
30
+ },
31
+ },
32
+ options: {
33
+ storySort: {
34
+ order: ["Design System"],
35
+ },
36
+ },
37
+ },
38
+ });
@@ -0,0 +1,49 @@
1
+ # Vuilder Shell Template
2
+
3
+ `vuilder-shell` is the branded customer tenant shell for Vuilder-owned verticals.
4
+ It is generated next to `vuilder-app` in a Vuilder workspace.
5
+
6
+ The shell opens after the customer has signed in through central SSO and the
7
+ platform has completed the `customer_vertical_signup` intent. The platform owns
8
+ identity, tenant runtime provisioning, and app-pack install authority. This
9
+ template owns customer-visible navigation, workspace status, and vertical
10
+ product UI.
11
+
12
+ ## Route Shape
13
+
14
+ - public route at `/`
15
+ - platform member auth handoff at `/login`, which redirects to central SSO with
16
+ a shell return URL
17
+ - branded tenant setup/status route at `/w/[workspace_slug]/connect`
18
+ - branded tenant workspace route at `/w/[workspace_slug]`
19
+ - compatibility authenticated app route at `/app`
20
+
21
+ ## Auth Profile
22
+
23
+ This template uses central platform SSO plus a tenant-bound shell session minted
24
+ by the platform after membership is established. `/login` sets a short-lived
25
+ host-only shell handoff state cookie before redirecting to SSO. SSO returns to
26
+ `/api/auth/accept-shell-session` with a short-lived one-use code and the echoed
27
+ state; the shell validates the state, exchanges the code server-side, sets a
28
+ host-only `mw_vuilder_shell_session` cookie, and redirects to the clean
29
+ workspace URL. Raw platform session and CSRF cookies stay on the SSO origin; the
30
+ shell never builds Django session or CSRF headers. The shell does not render a
31
+ password form and does not expose a local credential-login BFF; `/login` is only
32
+ an SSO redirect handoff. Local `/api/auth/session` and `/api/auth/logout` are
33
+ thin shell-token helpers; logout best-effort revokes the tenant-bound shell
34
+ token before clearing cookies. Browser React never stores platform credentials,
35
+ runtime keys, provisioning secrets, package refs, or install coordinates.
36
+
37
+ For production handoff from `auth.*` to a branded shell on a sibling subdomain,
38
+ configure `MW_AUTH_BASE_URL` and `MW_PUBLIC_BASE_URL`. Do not configure a shared
39
+ parent-domain shell cookie; the shell session cookie is host-only on the branded
40
+ shell origin.
41
+
42
+ `vuilder-shell` is not a `tenant-app` and must not use `@minutework/web-auth`
43
+ or same-origin `/_mw` customer routes.
44
+
45
+ ## Vuilder Boundary
46
+
47
+ Vuilders may customize UI, route layout, visual design, tenant settings,
48
+ runtime-backed data views, agents, workflows, and branding. They must not move
49
+ provisioning decisions into React state or browser-submitted payloads.
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/design-system",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/design-system/primitives",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
@@ -0,0 +1,41 @@
1
+ import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
2
+ import nextTypeScript from "eslint-config-next/typescript";
3
+
4
+ import designSystemPlugin from "./tools/design-system/eslint-plugin-design-system.mjs";
5
+
6
+ const eslintConfig = [
7
+ ...nextCoreWebVitals,
8
+ ...nextTypeScript,
9
+ {
10
+ ignores: [
11
+ ".next/**",
12
+ "node_modules/**",
13
+ "next-env.d.ts",
14
+ "storybook-static/**",
15
+ "src/design-system/tokens/manifest.ts",
16
+ ],
17
+ },
18
+ {
19
+ files: ["src/app/**/*.{ts,tsx}", "src/features/**/*.{ts,tsx}"],
20
+ plugins: {
21
+ "design-system": designSystemPlugin,
22
+ },
23
+ rules: {
24
+ "design-system/no-design-system-bypass-imports": "error",
25
+ "design-system/no-cva-outside-design-system": "error",
26
+ "design-system/no-raw-design-values": "error",
27
+ "design-system/no-bespoke-visual-stacks-in-features": "error",
28
+ },
29
+ },
30
+ {
31
+ files: ["src/design-system/**/*.{ts,tsx}"],
32
+ plugins: {
33
+ "design-system": designSystemPlugin,
34
+ },
35
+ rules: {
36
+ "design-system/no-raw-design-values": "error",
37
+ },
38
+ },
39
+ ];
40
+
41
+ export default eslintConfig;
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,33 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { existsSync } from "node:fs";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ function resolveTurbopackRoot(startDirectory) {
8
+ let directory = startDirectory;
9
+
10
+ while (true) {
11
+ if (existsSync(path.join(directory, "pnpm-workspace.yaml"))) {
12
+ return directory;
13
+ }
14
+
15
+ const parent = path.dirname(directory);
16
+ if (parent === directory) {
17
+ return startDirectory;
18
+ }
19
+ directory = parent;
20
+ }
21
+ }
22
+
23
+ const nextConfig = {
24
+ reactStrictMode: true,
25
+ typedRoutes: true,
26
+ // Prefer the nearest workspace root so scaffolded tenant-app packages inside a
27
+ // monorepo and the in-repo template both resolve dependencies correctly.
28
+ turbopack: {
29
+ root: resolveTurbopackRoot(__dirname),
30
+ },
31
+ };
32
+
33
+ export default nextConfig;