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.
- package/EXTERNAL_ALPHA.md +33 -33
- package/README.md +34 -34
- package/assets/claude-local/CLAUDE.md.template +12 -12
- package/assets/claude-local/skills/README.md +3 -1
- package/assets/claude-local/skills/app-pack-authoring/SKILL.md +17 -4
- package/assets/claude-local/skills/capability-gap-reporting/SKILL.md +3 -3
- package/assets/claude-local/skills/contract-first-public-intake/SKILL.md +11 -3
- package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +12 -5
- package/assets/claude-local/skills/layering-and-import-modes/SKILL.md +2 -2
- package/assets/claude-local/skills/openclaw-skill-importer/SKILL.md +2 -2
- package/assets/claude-local/skills/project-overview-and-strategy/SKILL.md +8 -8
- package/assets/claude-local/skills/published-web-and-mw-core-site/SKILL.md +11 -8
- package/assets/claude-local/skills/standalone-mobile-client/SKILL.md +6 -5
- package/assets/claude-local/skills/vuilder-discovery-output-contract/SKILL.md +6 -6
- package/assets/claude-local/skills/workspace-guidance-refresh/SKILL.md +4 -4
- package/assets/templates/fastapi-sidecar/pyproject.toml +1 -1
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/main.py +1 -1
- package/assets/templates/mobile-app/.env.example +4 -4
- package/assets/templates/mobile-app/AGENTS.md +3 -3
- package/assets/templates/mobile-app/README.md +10 -10
- package/assets/templates/mobile-app/app/(app)/_layout.tsx +2 -2
- package/assets/templates/mobile-app/app/(app)/index.tsx +2 -2
- package/assets/templates/mobile-app/app/(auth)/login.tsx +3 -3
- package/assets/templates/mobile-app/app/_layout.tsx +1 -1
- package/assets/templates/mobile-app/babel.config.js +1 -1
- package/assets/templates/mobile-app/eas.json +1 -1
- package/assets/templates/mobile-app/expo-env.d.ts +1 -1
- package/assets/templates/mobile-app/metro.config.js +2 -2
- package/assets/templates/mobile-app/package.json +1 -1
- package/assets/templates/mobile-app/src/mw/client.ts +3 -3
- package/assets/templates/mobile-app/src/mw/contracts.ts +2 -2
- package/assets/templates/mobile-app/src/mw/endpoints.ts +2 -2
- package/assets/templates/mobile-app/src/mw/env.ts +4 -4
- package/assets/templates/mobile-app/src/mw/session.ts +1 -1
- package/assets/templates/mobile-app/template.json +1 -1
- package/assets/templates/mobile-app/tools/template/validate-template.mjs +2 -2
- package/assets/templates/mobile-app/tsconfig.json +1 -1
- package/assets/templates/next-tenant-app/.env.example +1 -1
- package/assets/templates/next-tenant-app/README.md +26 -138
- package/assets/templates/next-tenant-app/package.json +1 -0
- package/assets/templates/next-tenant-app/src/app/app/demo/page.tsx +15 -0
- package/assets/templates/next-tenant-app/src/app/app/layout.tsx +1 -4
- package/assets/templates/next-tenant-app/src/app/app/page.tsx +2 -17
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +9 -67
- package/assets/templates/next-tenant-app/src/app/blog/page.tsx +10 -46
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +9 -65
- package/assets/templates/next-tenant-app/src/app/docs/page.tsx +10 -46
- package/assets/templates/next-tenant-app/src/app/layout.tsx +8 -10
- package/assets/templates/next-tenant-app/src/app/login/page.tsx +3 -23
- package/assets/templates/next-tenant-app/src/app/page.tsx +11 -44
- package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +10 -44
- package/assets/templates/next-tenant-app/src/app/providers.tsx +2 -1
- package/assets/templates/next-tenant-app/src/app/robots.ts +7 -18
- package/assets/templates/next-tenant-app/src/app/sitemap.ts +4 -39
- package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +97 -98
- package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +43 -78
- package/assets/templates/next-tenant-app/src/features/demo/components/manifest-demo.tsx +89 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/static-public-page.tsx +58 -0
- package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +48 -552
- package/assets/templates/next-tenant-app/src/lib/app-routes.ts +2 -2
- package/assets/templates/next-tenant-app/src/lib/public-site.test.ts +1 -1
- package/assets/templates/next-tenant-app/src/lib/public-site.ts +5 -30
- package/assets/templates/next-tenant-app/src/mw/client.ts +18 -0
- package/assets/templates/next-tenant-app/src/mw/mock.test.ts +21 -0
- package/assets/templates/next-tenant-app/src/mw/mock.ts +35 -0
- package/assets/templates/next-tenant-app/src/mw/provider.tsx +17 -0
- package/assets/templates/next-tenant-app/template.json +3 -3
- package/assets/templates/next-tenant-app/template.schema.json +1 -0
- package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +4 -5
- package/assets/templates/next-tenant-app/tools/template/with-public-site-fixture.mjs +2 -2
- package/bin/minutework.js +1 -1
- package/dist/agent.js +7 -7
- package/dist/agent.js.map +1 -1
- package/dist/auth.js +7 -7
- package/dist/auth.js.map +1 -1
- package/dist/compile.js +5 -5
- package/dist/config.js +6 -6
- package/dist/config.js.map +1 -1
- package/dist/deploy.js +7 -7
- package/dist/deploy.js.map +1 -1
- package/dist/developer-client.js +2 -2
- package/dist/developer-client.js.map +1 -1
- package/dist/index.js +30 -30
- package/dist/index.js.map +1 -1
- package/dist/init.js +10 -10
- package/dist/init.js.map +1 -1
- package/dist/launcher.js +1 -1
- package/dist/launcher.js.map +1 -1
- package/dist/managed-engine.js +6 -6
- package/dist/managed-engine.js.map +1 -1
- package/dist/orchestrator-context.js +1 -1
- package/dist/orchestrator-context.js.map +1 -1
- package/dist/orchestrator.js +15 -15
- package/dist/orchestrator.js.map +1 -1
- package/dist/paths.js +1 -1
- package/dist/paths.js.map +1 -1
- package/dist/publish.js +3 -3
- package/dist/publish.js.map +1 -1
- package/dist/reporting.js +8 -8
- package/dist/reporting.js.map +1 -1
- package/dist/sandbox.js +5 -5
- package/dist/sandbox.js.map +1 -1
- package/dist/state.js +1 -1
- package/dist/state.js.map +1 -1
- package/dist/tokens.js +9 -9
- package/dist/tokens.js.map +1 -1
- package/dist/workspace-assets.js +6 -6
- package/dist/workspace-assets.js.map +1 -1
- package/dist/workspace.js +3 -3
- package/dist/workspace.js.map +1 -1
- package/package.json +3 -3
- package/vendor/workspace-mcp/context.d.ts +6 -6
- package/vendor/workspace-mcp/context.js +56 -56
- package/vendor/workspace-mcp/context.js.map +1 -1
- package/vendor/workspace-mcp/types.d.ts +4 -0
- package/assets/templates/next-tenant-app/src/app/(cms)/[...path]/page.tsx +0 -89
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +0 -90
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +0 -78
- package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +0 -31
- package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +0 -16
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +0 -79
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +0 -40
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +0 -42
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +0 -29
- package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +0 -26
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +0 -40
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +0 -47
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +0 -43
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +0 -45
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +0 -83
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +0 -30
- package/assets/templates/next-tenant-app/src/app/app/page.test.ts +0 -62
- package/assets/templates/next-tenant-app/src/app/app/private-content-source.test.ts +0 -88
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +0 -70
- package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +0 -46
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +0 -70
- package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +0 -46
- package/assets/templates/next-tenant-app/src/app/login/page.test.ts +0 -55
- package/assets/templates/next-tenant-app/src/app/page.test.ts +0 -90
- package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +0 -59
- package/assets/templates/next-tenant-app/src/app/robots.test.ts +0 -40
- package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +0 -63
- package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +0 -342
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +0 -66
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +0 -108
- package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +0 -111
- package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +0 -111
- package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.test.ts +0 -38
- package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.tsx +0 -145
- package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +0 -189
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +0 -444
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +0 -383
- package/assets/templates/next-tenant-app/src/lib/content/contracts.test.ts +0 -138
- package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +0 -399
- package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +0 -5
- package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +0 -96
- package/assets/templates/next-tenant-app/src/lib/content/release-manifest.test.ts +0 -93
- package/assets/templates/next-tenant-app/src/lib/content/release-manifest.ts +0 -123
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +0 -75
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +0 -25
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +0 -170
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +0 -661
- package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +0 -131
- package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +0 -34
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +0 -211
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +0 -151
- package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +0 -33
- package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +0 -108
package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx
DELETED
|
@@ -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
|
-
});
|
package/assets/templates/next-tenant-app/src/features/public-shell/components/section-renderer.tsx
DELETED
|
@@ -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
|
-
}
|
package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import type { PublicContentSnapshot } from "@/lib/content/contracts";
|
|
2
|
-
|
|
3
|
-
export const publicSiteFixtureSnapshot: PublicContentSnapshot = {
|
|
4
|
-
site: {
|
|
5
|
-
siteName: "PandaWork Combined Starter",
|
|
6
|
-
siteDescription:
|
|
7
|
-
"SEO-first marketing, docs, blog, and authenticated workspace routes served from one governed Next.js starter.",
|
|
8
|
-
organizationName: "PandaWork",
|
|
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 PandaWork 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 PandaWork content snapshots.",
|
|
44
|
-
heroBody:
|
|
45
|
-
"The tenant-app starter loads public-site content from the PandaWork 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: "PandaWork 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 PandaWork 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: "PandaWork Combined Starter Pricing",
|
|
117
|
-
description:
|
|
118
|
-
"PandaWork 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 PandaWork.",
|
|
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 PandaWork 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 PandaWork 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
|
-
"PandaWork 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 PandaWork public-site content reads during preview and build.",
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
};
|