minutework 0.1.32 → 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 (91) hide show
  1. package/assets/claude-local/skills/README.md +2 -0
  2. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +14 -1
  3. package/assets/claude-local/skills/contract-first-public-intake/SKILL.md +11 -3
  4. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +10 -3
  5. package/assets/claude-local/skills/published-web-and-mw-core-site/SKILL.md +9 -6
  6. package/assets/claude-local/skills/standalone-mobile-client/SKILL.md +5 -4
  7. package/assets/templates/next-tenant-app/README.md +26 -138
  8. package/assets/templates/next-tenant-app/package.json +1 -0
  9. package/assets/templates/next-tenant-app/src/app/app/demo/page.tsx +15 -0
  10. package/assets/templates/next-tenant-app/src/app/app/layout.tsx +1 -4
  11. package/assets/templates/next-tenant-app/src/app/app/page.tsx +2 -17
  12. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +9 -67
  13. package/assets/templates/next-tenant-app/src/app/blog/page.tsx +10 -46
  14. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +9 -65
  15. package/assets/templates/next-tenant-app/src/app/docs/page.tsx +10 -46
  16. package/assets/templates/next-tenant-app/src/app/layout.tsx +8 -10
  17. package/assets/templates/next-tenant-app/src/app/login/page.tsx +3 -23
  18. package/assets/templates/next-tenant-app/src/app/page.tsx +11 -44
  19. package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +10 -44
  20. package/assets/templates/next-tenant-app/src/app/providers.tsx +2 -1
  21. package/assets/templates/next-tenant-app/src/app/robots.ts +7 -18
  22. package/assets/templates/next-tenant-app/src/app/sitemap.ts +4 -39
  23. package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +97 -98
  24. package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +43 -78
  25. package/assets/templates/next-tenant-app/src/features/demo/components/manifest-demo.tsx +89 -0
  26. package/assets/templates/next-tenant-app/src/features/public-shell/components/static-public-page.tsx +58 -0
  27. package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +48 -552
  28. package/assets/templates/next-tenant-app/src/lib/app-routes.ts +2 -2
  29. package/assets/templates/next-tenant-app/src/lib/public-site.ts +5 -30
  30. package/assets/templates/next-tenant-app/src/mw/client.ts +18 -0
  31. package/assets/templates/next-tenant-app/src/mw/mock.test.ts +21 -0
  32. package/assets/templates/next-tenant-app/src/mw/mock.ts +35 -0
  33. package/assets/templates/next-tenant-app/src/mw/provider.tsx +17 -0
  34. package/assets/templates/next-tenant-app/template.json +3 -3
  35. package/assets/templates/next-tenant-app/template.schema.json +1 -0
  36. package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +4 -5
  37. package/package.json +2 -2
  38. package/vendor/workspace-mcp/types.d.ts +4 -0
  39. package/assets/templates/next-tenant-app/src/app/(cms)/[...path]/page.tsx +0 -89
  40. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +0 -90
  41. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +0 -78
  42. package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +0 -31
  43. package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +0 -16
  44. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +0 -79
  45. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +0 -40
  46. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +0 -42
  47. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +0 -29
  48. package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +0 -26
  49. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +0 -40
  50. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +0 -47
  51. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +0 -43
  52. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +0 -45
  53. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +0 -83
  54. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +0 -30
  55. package/assets/templates/next-tenant-app/src/app/app/page.test.ts +0 -62
  56. package/assets/templates/next-tenant-app/src/app/app/private-content-source.test.ts +0 -88
  57. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +0 -70
  58. package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +0 -46
  59. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +0 -70
  60. package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +0 -46
  61. package/assets/templates/next-tenant-app/src/app/login/page.test.ts +0 -55
  62. package/assets/templates/next-tenant-app/src/app/page.test.ts +0 -90
  63. package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +0 -59
  64. package/assets/templates/next-tenant-app/src/app/robots.test.ts +0 -40
  65. package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +0 -63
  66. package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +0 -342
  67. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +0 -66
  68. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +0 -108
  69. package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +0 -111
  70. package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +0 -111
  71. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.test.ts +0 -38
  72. package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.tsx +0 -145
  73. package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +0 -189
  74. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +0 -444
  75. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +0 -383
  76. package/assets/templates/next-tenant-app/src/lib/content/contracts.test.ts +0 -138
  77. package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +0 -399
  78. package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +0 -5
  79. package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +0 -96
  80. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.test.ts +0 -93
  81. package/assets/templates/next-tenant-app/src/lib/content/release-manifest.ts +0 -123
  82. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +0 -75
  83. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +0 -25
  84. package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +0 -170
  85. package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +0 -661
  86. package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +0 -131
  87. package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +0 -34
  88. package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +0 -211
  89. package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +0 -151
  90. package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +0 -33
  91. package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +0 -108
@@ -1,111 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import type { Route } from "next";
3
- import Link from "next/link";
4
-
5
- import { ArrowUpRight, Sparkles } from "lucide-react";
6
-
7
- import { ThemeModeToggle } from "@/design-system/patterns/theme-mode-toggle";
8
- import { Button } from "@/design-system/primitives/button";
9
- import type { SiteConfig } from "@/lib/content/contracts";
10
-
11
- function navLinkClass(active: boolean) {
12
- return active
13
- ? "rounded-full border border-border bg-surface px-4 py-2 text-sm font-medium text-foreground shadow-sm"
14
- : "rounded-full border border-transparent px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:border-border hover:bg-surface hover:text-foreground";
15
- }
16
-
17
- export function PublicSiteShell({
18
- activeHref,
19
- children,
20
- siteConfig,
21
- }: {
22
- activeHref: string;
23
- children: ReactNode;
24
- siteConfig: SiteConfig;
25
- }) {
26
- return (
27
- <div className="public-site-background min-h-screen text-foreground">
28
- <div className="relative isolate overflow-hidden">
29
- <div className="public-site-orb absolute inset-x-0 top-0 -z-10 h-[28rem] opacity-80" />
30
-
31
- <header className="sticky top-0 z-20 border-b border-border/70 bg-background/80 backdrop-blur">
32
- <div className="mx-auto flex max-w-6xl flex-col gap-4 px-6 py-4 lg:flex-row lg:items-center lg:justify-between">
33
- <div className="flex items-center justify-between gap-4">
34
- <Link href="/" className="flex items-center gap-3">
35
- <span className="flex size-11 items-center justify-center rounded-2xl border border-border bg-surface shadow-sm">
36
- <Sparkles className="size-5 text-primary" />
37
- </span>
38
- <div className="space-y-1">
39
- <p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
40
- Combined Starter
41
- </p>
42
- <p className="text-base font-semibold tracking-tight text-foreground">
43
- {siteConfig.siteName}
44
- </p>
45
- </div>
46
- </Link>
47
-
48
- <div className="lg:hidden">
49
- <ThemeModeToggle className="w-fit" />
50
- </div>
51
- </div>
52
-
53
- <nav className="flex flex-wrap items-center gap-2">
54
- {siteConfig.primaryNavigation.map((item) => (
55
- <Link
56
- key={item.href}
57
- href={item.href as Route}
58
- className={navLinkClass(item.href === activeHref)}
59
- >
60
- {item.label}
61
- </Link>
62
- ))}
63
- </nav>
64
-
65
- <div className="hidden items-center gap-3 lg:flex">
66
- <ThemeModeToggle className="w-fit" />
67
- <Button asChild type="button" variant="outline">
68
- <Link href={siteConfig.secondaryCta.href as Route}>
69
- {siteConfig.secondaryCta.label}
70
- </Link>
71
- </Button>
72
- <Button asChild type="button">
73
- <Link href={siteConfig.primaryCta.href as Route}>
74
- {siteConfig.primaryCta.label}
75
- <ArrowUpRight className="size-4" />
76
- </Link>
77
- </Button>
78
- </div>
79
- </div>
80
- </header>
81
-
82
- <div className="mx-auto max-w-6xl px-6 pb-20 pt-10">{children}</div>
83
-
84
- <footer className="border-t border-border/70 bg-background/90">
85
- <div className="mx-auto grid max-w-6xl gap-6 px-6 py-8 md:grid-cols-[1.2fr,0.8fr]">
86
- <div className="space-y-2">
87
- <p className="text-sm font-semibold uppercase tracking-[0.24em] text-muted-foreground">
88
- {siteConfig.organizationName}
89
- </p>
90
- <p className="max-w-2xl text-sm leading-7 text-muted-foreground">
91
- {siteConfig.footerBlurb}
92
- </p>
93
- </div>
94
-
95
- <div className="flex flex-wrap items-center gap-3 md:justify-end">
96
- {siteConfig.primaryNavigation.map((item) => (
97
- <Link
98
- key={`${item.href}-footer`}
99
- href={item.href as Route}
100
- className="text-sm text-muted-foreground transition-colors hover:text-foreground"
101
- >
102
- {item.label}
103
- </Link>
104
- ))}
105
- </div>
106
- </div>
107
- </footer>
108
- </div>
109
- </div>
110
- );
111
- }
@@ -1,38 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import type { ContentSection } from "@/lib/content/contracts";
4
-
5
- describe("section-renderer", () => {
6
- it("sorts sections by sort_order", async () => {
7
- const sections: ContentSection[] = [
8
- { section_key: "b", section_type: "text", sort_order: 2, data: { heading: "B" } },
9
- { section_key: "a", section_type: "text", sort_order: 0, data: { heading: "A" } },
10
- { section_key: "c", section_type: "text", sort_order: 1, data: { heading: "C" } },
11
- ];
12
-
13
- const sorted = [...sections].sort(
14
- (a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0),
15
- );
16
-
17
- expect(sorted.map((s) => s.section_key)).toEqual(["a", "c", "b"]);
18
- });
19
-
20
- it("accepts arbitrary section types without error", () => {
21
- const sections: ContentSection[] = [
22
- {
23
- section_key: "custom",
24
- section_type: "my_custom_widget",
25
- data: { arbitrary: "tenant-defined", nested: { deep: true } },
26
- },
27
- {
28
- section_key: "hero",
29
- section_type: "hero_banner",
30
- data: { heading: "Welcome" },
31
- },
32
- ];
33
-
34
- // Content types are open strings — no validation error.
35
- expect(sections.length).toBe(2);
36
- expect(sections[0]!.section_type).toBe("my_custom_widget");
37
- });
38
- });
@@ -1,145 +0,0 @@
1
- import { PanelFrame } from "@/design-system/patterns/panel-frame";
2
- import type { ContentSection } from "@/lib/content/contracts";
3
-
4
- /**
5
- * Registry of section_type -> React component.
6
- *
7
- * Section types are extensible strings. Unknown types render a
8
- * generic fallback. Tenant-specific or extension-pack sections
9
- * can be added here without modifying the baseline.
10
- */
11
-
12
- function HeroSection({ section }: { section: ContentSection }) {
13
- const { heading, subheading, body, cta_label, cta_href } = section.data as Record<
14
- string,
15
- string | undefined
16
- >;
17
- return (
18
- <PanelFrame tone="floating" radius="xl" padding="lg" className="space-y-4 border-border/70">
19
- <div className="space-y-3">
20
- {subheading ? (
21
- <p className="text-sm font-semibold uppercase tracking-[0.28em] text-muted-foreground">
22
- {subheading}
23
- </p>
24
- ) : null}
25
- <h2 className="text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
26
- {heading || section.section_key}
27
- </h2>
28
- {body ? <p className="max-w-2xl text-base leading-8 text-muted-foreground">{body}</p> : null}
29
- </div>
30
- {cta_label && cta_href ? (
31
- <a
32
- href={cta_href}
33
- className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground"
34
- >
35
- {cta_label}
36
- </a>
37
- ) : null}
38
- </PanelFrame>
39
- );
40
- }
41
-
42
- function TextSection({ section }: { section: ContentSection }) {
43
- const { heading, body, eyebrow } = section.data as Record<string, string | undefined>;
44
- return (
45
- <PanelFrame tone="raised" radius="xl" padding="lg" className="space-y-3 border-border/70">
46
- {eyebrow ? (
47
- <p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
48
- {eyebrow}
49
- </p>
50
- ) : null}
51
- <h3 className="text-xl font-semibold tracking-tight">{heading || section.section_key}</h3>
52
- {body ? <p className="text-sm leading-7 text-muted-foreground">{body}</p> : null}
53
- </PanelFrame>
54
- );
55
- }
56
-
57
- function FeatureGridSection({ section }: { section: ContentSection }) {
58
- const { heading, items } = section.data as {
59
- heading?: string;
60
- items?: { title: string; description?: string; icon?: string }[];
61
- };
62
- return (
63
- <div className="space-y-4">
64
- {heading ? <h3 className="text-xl font-semibold tracking-tight">{heading}</h3> : null}
65
- <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
66
- {(items ?? []).map((item) => (
67
- <PanelFrame
68
- key={item.title}
69
- tone="raised"
70
- radius="xl"
71
- padding="lg"
72
- className="space-y-2 border-border/70"
73
- >
74
- <h4 className="font-semibold">{item.title}</h4>
75
- {item.description ? (
76
- <p className="text-sm text-muted-foreground">{item.description}</p>
77
- ) : null}
78
- </PanelFrame>
79
- ))}
80
- </div>
81
- </div>
82
- );
83
- }
84
-
85
- function GenericSection({ section }: { section: ContentSection }) {
86
- const { heading, body, eyebrow } = section.data as Record<string, string | undefined>;
87
- return (
88
- <PanelFrame tone="raised" radius="xl" padding="lg" className="space-y-3 border-border/70">
89
- {eyebrow ? (
90
- <p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
91
- {eyebrow}
92
- </p>
93
- ) : null}
94
- <h3 className="text-xl font-semibold tracking-tight">
95
- {heading || section.section_type}
96
- </h3>
97
- {body ? <p className="text-sm leading-7 text-muted-foreground">{body}</p> : null}
98
- </PanelFrame>
99
- );
100
- }
101
-
102
- const SECTION_RENDERERS: Record<
103
- string,
104
- (props: { section: ContentSection }) => React.ReactNode
105
- > = {
106
- hero_banner: HeroSection,
107
- hero: HeroSection,
108
- text: TextSection,
109
- text_block: TextSection,
110
- feature_grid: FeatureGridSection,
111
- };
112
-
113
- /**
114
- * Render a single content section by its section_type.
115
- *
116
- * Known types get specialized renderers. Unknown types get a generic
117
- * fallback that renders heading + body from the section data.
118
- */
119
- export function SectionRenderer({ section }: { section: ContentSection }) {
120
- const Renderer = SECTION_RENDERERS[section.section_type] ?? GenericSection;
121
- return <Renderer section={section} />;
122
- }
123
-
124
- /**
125
- * Render an ordered list of content sections from a page's content_structure.
126
- *
127
- * Sections are sorted by sort_order (if present), then rendered in order.
128
- */
129
- export function ContentStructureRenderer({
130
- sections,
131
- }: {
132
- sections: ContentSection[];
133
- }) {
134
- const sorted = [...sections].sort(
135
- (a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0),
136
- );
137
-
138
- return (
139
- <div className="grid gap-6">
140
- {sorted.map((section) => (
141
- <SectionRenderer key={section.section_key} section={section} />
142
- ))}
143
- </div>
144
- );
145
- }
@@ -1,189 +0,0 @@
1
- import type { PublicContentSnapshot } from "@/lib/content/contracts";
2
-
3
- export const publicSiteFixtureSnapshot: PublicContentSnapshot = {
4
- site: {
5
- siteName: "MinuteWork Combined Starter",
6
- siteDescription:
7
- "SEO-first marketing, docs, blog, and authenticated workspace routes served from one governed Next.js starter.",
8
- organizationName: "MinuteWork",
9
- footerBlurb:
10
- "One codebase for public discovery, operator sign-in, and the private Builder-ready workspace shell.",
11
- primaryCta: {
12
- label: "Sign In",
13
- href: "/login",
14
- },
15
- secondaryCta: {
16
- label: "Docs",
17
- href: "/docs",
18
- },
19
- primaryNavigation: [
20
- { label: "Home", href: "/" },
21
- { label: "Pricing", href: "/pricing" },
22
- { label: "Docs", href: "/docs" },
23
- { label: "Blog", href: "/blog" },
24
- ],
25
- collections: {
26
- docs: {
27
- eyebrow: "Docs",
28
- title: "Starter Docs",
29
- description: "Route public content through the MinuteWork public-site snapshot seam.",
30
- },
31
- blog: {
32
- eyebrow: "Blog",
33
- title: "Starter Blog",
34
- description: "Notes about the combined public and private starter.",
35
- },
36
- },
37
- },
38
- marketingPages: [
39
- {
40
- pageKey: "home",
41
- path: "/",
42
- heroEyebrow: "Public Site API",
43
- heroTitle: "Public routes now read from MinuteWork content snapshots.",
44
- heroBody:
45
- "The tenant-app starter loads public-site content from the MinuteWork gateway during preview and build, while private routes stay behind the platform-session BFF.",
46
- primaryCta: {
47
- label: "Sign In",
48
- href: "/login",
49
- },
50
- secondaryCta: {
51
- label: "Read the docs",
52
- href: "/docs",
53
- },
54
- sections: [
55
- {
56
- eyebrow: "Authoring",
57
- title: "Preview reads stay server-only",
58
- body:
59
- "The Next server fetches preview or live-safe public-site content with a server-only content token.",
60
- },
61
- {
62
- eyebrow: "Publishing",
63
- title: "Live reads stay publication-safe",
64
- body:
65
- "Anonymous production traffic reads static output generated from a published-live snapshot contract.",
66
- },
67
- {
68
- eyebrow: "Boundaries",
69
- title: "Private app routes stay on the BFF path",
70
- body:
71
- "Public discovery routes and private workspace routes keep separate auth and data boundaries.",
72
- },
73
- ],
74
- seo: {
75
- title: "MinuteWork Combined Starter",
76
- description:
77
- "A combined starter with public-site snapshot reads for anonymous routes and a platform-session BFF for private routes.",
78
- },
79
- },
80
- {
81
- pageKey: "pricing",
82
- path: "/pricing",
83
- heroEyebrow: "Pricing",
84
- heroTitle: "Static-first public pages with remote content by default.",
85
- heroBody:
86
- "Pricing, docs, and blog routes build from the same public-site snapshot contract, so developers do not have to maintain local seed content.",
87
- primaryCta: {
88
- label: "Get started",
89
- href: "/login",
90
- },
91
- secondaryCta: {
92
- label: "See docs",
93
- href: "/docs",
94
- },
95
- sections: [
96
- {
97
- eyebrow: "DX",
98
- title: "No seeded runtime mode",
99
- body:
100
- "The generated starter expects real public-site content from MinuteWork during development and build.",
101
- },
102
- {
103
- eyebrow: "Safety",
104
- title: "Empty snapshots still render",
105
- body:
106
- "Fresh properties can ship before authored content exists because built-in empty-state shells handle missing marketing pages.",
107
- },
108
- {
109
- eyebrow: "Ops",
110
- title: "One API for preview and live",
111
- body:
112
- "The same gateway path serves runtime-backed preview reads and published-live snapshots with an explicit source boundary.",
113
- },
114
- ],
115
- seo: {
116
- title: "MinuteWork Combined Starter Pricing",
117
- description:
118
- "MinuteWork public-site snapshots let pricing and landing pages stay static-first without local content seeds.",
119
- },
120
- },
121
- ],
122
- docs: [
123
- {
124
- kind: "docs",
125
- slug: ["guides", "public-site-content"],
126
- eyebrow: "Guide",
127
- title: "Use the public-site snapshot API",
128
- description:
129
- "Configure the tenant-app starter to load preview and live-safe public-site content from MinuteWork.",
130
- publishedAt: "2026-03-26T09:00:00.000Z",
131
- readingTime: "3 min read",
132
- body: {
133
- intro:
134
- "The public-site adapter now talks to the MinuteWork gateway with a server-only content token and unwraps a public-site snapshot envelope.",
135
- sections: [
136
- {
137
- heading: "Environment contract",
138
- paragraphs: [
139
- "Use MW_CONTENT_API_TOKEN for server-only authentication.",
140
- "Use MW_PUBLIC_SITE_PROPERTY_KEY to select the PublishedWebProperty backing the site.",
141
- ],
142
- },
143
- {
144
- heading: "Preview and live",
145
- paragraphs: [
146
- "MW_PUBLIC_SITE_ENV=preview fetches runtime-backed preview snapshots.",
147
- "MW_PUBLIC_SITE_ENV=live fetches publication-safe published snapshots only.",
148
- ],
149
- },
150
- ],
151
- },
152
- seo: {
153
- title: "Use the public-site snapshot API",
154
- description:
155
- "Configure the MinuteWork tenant-app starter to fetch public-site snapshots during preview and build.",
156
- },
157
- },
158
- ],
159
- blog: [
160
- {
161
- kind: "blog",
162
- slug: ["public-site-api-default"],
163
- eyebrow: "Launch note",
164
- title: "Public-site snapshots are now the starter default",
165
- description:
166
- "The generated tenant-app no longer depends on seeded public content for its public route surface.",
167
- publishedAt: "2026-03-26T10:00:00.000Z",
168
- readingTime: "2 min read",
169
- body: {
170
- intro:
171
- "MinuteWork tenant-app starters now fetch public-site content from a gateway API using a dedicated server-only content token.",
172
- sections: [
173
- {
174
- heading: "What changed",
175
- paragraphs: [
176
- "Seeded local public content is no longer the runtime path for generated starters.",
177
- "Public routes render from a remote snapshot envelope with explicit preview and live boundaries.",
178
- ],
179
- },
180
- ],
181
- },
182
- seo: {
183
- title: "Public-site snapshots are now the starter default",
184
- description:
185
- "The tenant-app starter now defaults to server-only MinuteWork public-site content reads during preview and build.",
186
- },
187
- },
188
- ],
189
- };