minutework 0.1.39 → 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 (166) hide show
  1. package/EXTERNAL_ALPHA.md +17 -1
  2. package/README.md +21 -1
  3. package/assets/claude-local/skills/README.md +6 -0
  4. package/assets/claude-local/skills/ai-capability-defaults/SKILL.md +3 -0
  5. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +15 -0
  6. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +25 -9
  7. package/assets/claude-local/skills/integration-broker-and-connectors/SKILL.md +122 -0
  8. package/assets/claude-local/skills/layering-and-import-modes/SKILL.md +4 -0
  9. package/assets/claude-local/skills/project-overview-and-strategy/SKILL.md +6 -0
  10. package/assets/claude-local/skills/shell-architecture/SKILL.md +15 -0
  11. package/assets/claude-local/skills/vuilder-public-site-authoring/SKILL.md +10 -4
  12. package/assets/claude-local/skills/vuilder-workspace-architecture/SKILL.md +78 -0
  13. package/assets/templates/vuilder-public-site/.env.example +11 -0
  14. package/assets/templates/vuilder-public-site/README.md +15 -0
  15. package/assets/templates/vuilder-public-site/eslint.config.mjs +6 -0
  16. package/assets/templates/vuilder-public-site/next-env.d.ts +4 -0
  17. package/assets/templates/vuilder-public-site/next.config.mjs +28 -0
  18. package/assets/templates/vuilder-public-site/package.json +39 -0
  19. package/assets/templates/vuilder-public-site/postcss.config.mjs +5 -0
  20. package/assets/templates/vuilder-public-site/src/app/api/onboarding/start/route.ts +19 -0
  21. package/assets/templates/vuilder-public-site/src/app/blog/[slug]/page.tsx +25 -0
  22. package/assets/templates/vuilder-public-site/src/app/blog/page.tsx +26 -0
  23. package/assets/templates/vuilder-public-site/src/app/globals.css +103 -0
  24. package/assets/templates/vuilder-public-site/src/app/layout.tsx +18 -0
  25. package/assets/templates/vuilder-public-site/src/app/onboarding/[[...step]]/page.tsx +39 -0
  26. package/assets/templates/vuilder-public-site/src/app/page.tsx +43 -0
  27. package/assets/templates/vuilder-public-site/src/lib/env.server.ts +31 -0
  28. package/assets/templates/vuilder-public-site/src/lib/platform.server.ts +47 -0
  29. package/assets/templates/vuilder-public-site/src/lib/public-dj.server.ts +86 -0
  30. package/assets/templates/vuilder-public-site/src/lib/public-dj.test.ts +8 -0
  31. package/assets/templates/vuilder-public-site/src/lib/routes.test.ts +13 -0
  32. package/assets/templates/vuilder-public-site/src/lib/routes.ts +12 -0
  33. package/assets/templates/vuilder-public-site/template.json +21 -0
  34. package/assets/templates/vuilder-public-site/tools/template/validate-template.mjs +44 -0
  35. package/assets/templates/vuilder-public-site/tsconfig.json +23 -0
  36. package/assets/templates/vuilder-public-site/vitest.config.ts +13 -0
  37. package/assets/templates/vuilder-shell/.env.example +8 -0
  38. package/assets/templates/vuilder-shell/.storybook/main.ts +19 -0
  39. package/assets/templates/vuilder-shell/.storybook/preview.tsx +38 -0
  40. package/assets/templates/vuilder-shell/README.md +49 -0
  41. package/assets/templates/vuilder-shell/components.json +21 -0
  42. package/assets/templates/vuilder-shell/eslint.config.mjs +41 -0
  43. package/assets/templates/vuilder-shell/next-env.d.ts +6 -0
  44. package/assets/templates/vuilder-shell/next.config.mjs +33 -0
  45. package/assets/templates/vuilder-shell/package.json +61 -0
  46. package/assets/templates/vuilder-shell/postcss.config.mjs +8 -0
  47. package/assets/templates/vuilder-shell/public/.gitkeep +1 -0
  48. package/assets/templates/vuilder-shell/src/app/api/auth/accept-shell-session/route.test.ts +105 -0
  49. package/assets/templates/vuilder-shell/src/app/api/auth/accept-shell-session/route.ts +63 -0
  50. package/assets/templates/vuilder-shell/src/app/api/auth/logout/route.test.ts +63 -0
  51. package/assets/templates/vuilder-shell/src/app/api/auth/logout/route.ts +24 -0
  52. package/assets/templates/vuilder-shell/src/app/api/auth/session/route.test.ts +70 -0
  53. package/assets/templates/vuilder-shell/src/app/api/auth/session/route.ts +27 -0
  54. package/assets/templates/vuilder-shell/src/app/app/layout.tsx +17 -0
  55. package/assets/templates/vuilder-shell/src/app/app/page.tsx +30 -0
  56. package/assets/templates/vuilder-shell/src/app/blog/[slug]/page.tsx +15 -0
  57. package/assets/templates/vuilder-shell/src/app/blog/page.tsx +15 -0
  58. package/assets/templates/vuilder-shell/src/app/docs/[...slug]/page.tsx +15 -0
  59. package/assets/templates/vuilder-shell/src/app/docs/page.tsx +15 -0
  60. package/assets/templates/vuilder-shell/src/app/globals.css +70 -0
  61. package/assets/templates/vuilder-shell/src/app/layout.tsx +69 -0
  62. package/assets/templates/vuilder-shell/src/app/login/route.test.ts +33 -0
  63. package/assets/templates/vuilder-shell/src/app/login/route.ts +21 -0
  64. package/assets/templates/vuilder-shell/src/app/page.tsx +16 -0
  65. package/assets/templates/vuilder-shell/src/app/pricing/page.tsx +15 -0
  66. package/assets/templates/vuilder-shell/src/app/providers.tsx +25 -0
  67. package/assets/templates/vuilder-shell/src/app/robots.ts +21 -0
  68. package/assets/templates/vuilder-shell/src/app/sitemap.ts +33 -0
  69. package/assets/templates/vuilder-shell/src/app/w/[workspace_slug]/connect/page.tsx +31 -0
  70. package/assets/templates/vuilder-shell/src/app/w/[workspace_slug]/page.tsx +54 -0
  71. package/assets/templates/vuilder-shell/src/components/ui/button.tsx +59 -0
  72. package/assets/templates/vuilder-shell/src/components/ui/input.tsx +21 -0
  73. package/assets/templates/vuilder-shell/src/design-system/docs/governance.mdx +26 -0
  74. package/assets/templates/vuilder-shell/src/design-system/patterns/panel-frame.stories.tsx +48 -0
  75. package/assets/templates/vuilder-shell/src/design-system/patterns/panel-frame.tsx +26 -0
  76. package/assets/templates/vuilder-shell/src/design-system/patterns/status-badge.stories.tsx +26 -0
  77. package/assets/templates/vuilder-shell/src/design-system/patterns/status-badge.tsx +35 -0
  78. package/assets/templates/vuilder-shell/src/design-system/patterns/theme-mode-toggle.stories.tsx +21 -0
  79. package/assets/templates/vuilder-shell/src/design-system/patterns/theme-mode-toggle.tsx +75 -0
  80. package/assets/templates/vuilder-shell/src/design-system/primitives/button.stories.tsx +37 -0
  81. package/assets/templates/vuilder-shell/src/design-system/primitives/button.ts +1 -0
  82. package/assets/templates/vuilder-shell/src/design-system/primitives/input.stories.tsx +26 -0
  83. package/assets/templates/vuilder-shell/src/design-system/primitives/input.ts +1 -0
  84. package/assets/templates/vuilder-shell/src/design-system/recipes/chrome.ts +28 -0
  85. package/assets/templates/vuilder-shell/src/design-system/tokens/foundation.css +31 -0
  86. package/assets/templates/vuilder-shell/src/design-system/tokens/index.css +3 -0
  87. package/assets/templates/vuilder-shell/src/design-system/tokens/manifest.json +85 -0
  88. package/assets/templates/vuilder-shell/src/design-system/tokens/manifest.ts +87 -0
  89. package/assets/templates/vuilder-shell/src/design-system/tokens/semantic.css +105 -0
  90. package/assets/templates/vuilder-shell/src/design-system/tokens/theme.css +59 -0
  91. package/assets/templates/vuilder-shell/src/design-system/tokens/tokens.stories.tsx +71 -0
  92. package/assets/templates/vuilder-shell/src/features/dashboard/components/tenant-dashboard.tsx +134 -0
  93. package/assets/templates/vuilder-shell/src/features/public-shell/components/static-public-page.tsx +58 -0
  94. package/assets/templates/vuilder-shell/src/features/shell/components/authenticated-app-layout-shell.tsx +84 -0
  95. package/assets/templates/vuilder-shell/src/features/shell/components/private-app-shell.tsx +22 -0
  96. package/assets/templates/vuilder-shell/src/features/vuilder-shell/components/vuilder-connect-screen.tsx +89 -0
  97. package/assets/templates/vuilder-shell/src/features/vuilder-shell/components/vuilder-workspace-screen.tsx +49 -0
  98. package/assets/templates/vuilder-shell/src/lib/app-routes.test.ts +37 -0
  99. package/assets/templates/vuilder-shell/src/lib/app-routes.ts +86 -0
  100. package/assets/templates/vuilder-shell/src/lib/auth-routes.server.test.ts +26 -0
  101. package/assets/templates/vuilder-shell/src/lib/auth-routes.server.ts +53 -0
  102. package/assets/templates/vuilder-shell/src/lib/http/same-origin.test.ts +23 -0
  103. package/assets/templates/vuilder-shell/src/lib/http/same-origin.ts +18 -0
  104. package/assets/templates/vuilder-shell/src/lib/platform/client.server.test.ts +201 -0
  105. package/assets/templates/vuilder-shell/src/lib/platform/client.server.ts +540 -0
  106. package/assets/templates/vuilder-shell/src/lib/platform/contracts.ts +190 -0
  107. package/assets/templates/vuilder-shell/src/lib/platform/endpoints.server.ts +29 -0
  108. package/assets/templates/vuilder-shell/src/lib/platform/env.server.ts +82 -0
  109. package/assets/templates/vuilder-shell/src/lib/platform/route-response.ts +33 -0
  110. package/assets/templates/vuilder-shell/src/lib/platform/session.server.ts +145 -0
  111. package/assets/templates/vuilder-shell/src/lib/public-site.test.ts +20 -0
  112. package/assets/templates/vuilder-shell/src/lib/public-site.ts +48 -0
  113. package/assets/templates/vuilder-shell/src/lib/theme-config.ts +10 -0
  114. package/assets/templates/vuilder-shell/src/lib/theme.tsx +159 -0
  115. package/assets/templates/vuilder-shell/src/lib/utils.ts +6 -0
  116. package/assets/templates/vuilder-shell/template.json +28 -0
  117. package/assets/templates/vuilder-shell/template.schema.json +171 -0
  118. package/assets/templates/vuilder-shell/test/server-only-stub.ts +1 -0
  119. package/assets/templates/vuilder-shell/tools/design-system/build-token-manifest.mjs +3 -0
  120. package/assets/templates/vuilder-shell/tools/design-system/check-imports.mjs +9 -0
  121. package/assets/templates/vuilder-shell/tools/design-system/check-stories.mjs +9 -0
  122. package/assets/templates/vuilder-shell/tools/design-system/check-values.mjs +9 -0
  123. package/assets/templates/vuilder-shell/tools/design-system/checks.mjs +238 -0
  124. package/assets/templates/vuilder-shell/tools/design-system/eslint-plugin-design-system.mjs +184 -0
  125. package/assets/templates/vuilder-shell/tools/design-system/playwright.config.mjs +34 -0
  126. package/assets/templates/vuilder-shell/tools/design-system/run-checks.mjs +22 -0
  127. package/assets/templates/vuilder-shell/tools/design-system/shared.mjs +166 -0
  128. package/assets/templates/vuilder-shell/tools/design-system/visual.spec.ts +41 -0
  129. package/assets/templates/vuilder-shell/tools/template/validate-route-contract.mjs +373 -0
  130. package/assets/templates/vuilder-shell/tools/template/validate-template.mjs +45 -0
  131. package/assets/templates/vuilder-shell/tools/template/with-public-site-fixture.mjs +45 -0
  132. package/assets/templates/vuilder-shell/tsconfig.json +42 -0
  133. package/assets/templates/vuilder-shell/vitest.config.ts +23 -0
  134. package/dist/auth.js +66 -14
  135. package/dist/auth.js.map +1 -1
  136. package/dist/deploy-state.d.ts +1 -0
  137. package/dist/deploy-state.js.map +1 -1
  138. package/dist/deploy.js +18 -4
  139. package/dist/deploy.js.map +1 -1
  140. package/dist/developer-client.d.ts +1 -1
  141. package/dist/index.js +12 -2
  142. package/dist/index.js.map +1 -1
  143. package/dist/init-prompt.js +21 -13
  144. package/dist/init-prompt.js.map +1 -1
  145. package/dist/init.d.ts +3 -1
  146. package/dist/init.js +103 -12
  147. package/dist/init.js.map +1 -1
  148. package/dist/orchestrator-context.js +17 -5
  149. package/dist/orchestrator-context.js.map +1 -1
  150. package/dist/orchestrator-state.d.ts +2 -2
  151. package/dist/orchestrator-state.js.map +1 -1
  152. package/dist/publish.js +12 -2
  153. package/dist/publish.js.map +1 -1
  154. package/dist/state.d.ts +2 -0
  155. package/dist/state.js +9 -0
  156. package/dist/state.js.map +1 -1
  157. package/package.json +2 -2
  158. package/vendor/workspace-mcp/context.d.ts +3 -1
  159. package/vendor/workspace-mcp/context.js +134 -21
  160. package/vendor/workspace-mcp/context.js.map +1 -1
  161. package/vendor/workspace-mcp/types.d.ts +72 -7
  162. package/vendor/workspace-mcp/types.js +8 -4
  163. package/vendor/workspace-mcp/types.js.map +1 -1
  164. package/assets/templates/fastapi-sidecar/poetry.lock +0 -757
  165. package/assets/templates/next-tenant-app/package-lock.json +0 -9682
  166. package/assets/templates/next-tenant-app/pnpm-lock.yaml +0 -6062
@@ -0,0 +1,15 @@
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
+
3
+ export const metadata = {
4
+ title: "Docs",
5
+ };
6
+
7
+ export default function DocsPage() {
8
+ return (
9
+ <StaticPublicPage
10
+ eyebrow="Docs"
11
+ title="Documentation page"
12
+ body="Helpful context for customer workflows."
13
+ />
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
+
3
+ export const metadata = {
4
+ title: "Docs",
5
+ };
6
+
7
+ export default function DocsIndexPage() {
8
+ return (
9
+ <StaticPublicPage
10
+ eyebrow="Docs"
11
+ title="Customer documentation"
12
+ body="Guides, onboarding notes, and product documentation."
13
+ />
14
+ );
15
+ }
@@ -0,0 +1,70 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "../design-system/tokens/index.css";
4
+
5
+ @layer base {
6
+ html {
7
+ height: 100%;
8
+ }
9
+
10
+ body {
11
+ min-height: 100%;
12
+ margin: 0;
13
+ }
14
+
15
+ * {
16
+ @apply border-border outline-ring/50;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ @apply bg-background text-foreground antialiased;
22
+ }
23
+
24
+ a {
25
+ color: inherit;
26
+ }
27
+ }
28
+
29
+ @layer components {
30
+ .public-site-background {
31
+ background:
32
+ linear-gradient(
33
+ 180deg,
34
+ var(--color-background) 0%,
35
+ color-mix(in oklab, var(--color-surface-raised) 68%, white) 100%
36
+ );
37
+ }
38
+
39
+ .public-site-orb {
40
+ background:
41
+ radial-gradient(
42
+ circle at top left,
43
+ color-mix(in oklab, var(--color-primary) 28%, white) 0%,
44
+ transparent 48%
45
+ ),
46
+ radial-gradient(
47
+ circle at top right,
48
+ color-mix(in oklab, var(--color-chart-2) 18%, white) 0%,
49
+ transparent 42%
50
+ );
51
+ }
52
+
53
+ .marketing-hero-surface {
54
+ background:
55
+ linear-gradient(
56
+ 135deg,
57
+ color-mix(in oklab, var(--color-surface) 78%, white) 0%,
58
+ color-mix(in oklab, var(--color-primary) 8%, white) 100%
59
+ );
60
+ }
61
+
62
+ .marketing-hero-orb {
63
+ background:
64
+ radial-gradient(
65
+ circle at center,
66
+ color-mix(in oklab, var(--color-chart-2) 14%, white) 0%,
67
+ transparent 70%
68
+ );
69
+ }
70
+ }
@@ -0,0 +1,69 @@
1
+ import "./globals.css";
2
+ import type { Metadata } from "next";
3
+ import { Geist, Geist_Mono } from "next/font/google";
4
+ import { cookies } from "next/headers";
5
+ import type { ReactNode } from "react";
6
+
7
+ import { AppProviders } from "./providers";
8
+ import { resolvePublicMetadataBase } from "@/lib/public-site";
9
+ import {
10
+ isThemeMode,
11
+ THEME_COOKIE_NAME,
12
+ type ThemeMode,
13
+ } from "@/lib/theme-config";
14
+
15
+ const geistSans = Geist({
16
+ subsets: ["latin"],
17
+ variable: "--font-sans",
18
+ });
19
+
20
+ const geistMono = Geist_Mono({
21
+ subsets: ["latin"],
22
+ variable: "--font-mono",
23
+ });
24
+
25
+ export function generateMetadata(): Metadata {
26
+ const metadataBase = resolvePublicMetadataBase();
27
+ const appName = process.env.MW_TEMPLATE_APP_NAME || "Vuilder Shell";
28
+
29
+ return {
30
+ ...(metadataBase ? { metadataBase } : {}),
31
+ title: {
32
+ default: appName,
33
+ template: `%s | ${appName}`,
34
+ },
35
+ description: "Customer-facing branded tenant shell.",
36
+ openGraph: {
37
+ title: appName,
38
+ description: "Customer-facing branded tenant shell.",
39
+ siteName: appName,
40
+ type: "website",
41
+ ...(process.env.MW_PUBLIC_BASE_URL ? { url: process.env.MW_PUBLIC_BASE_URL } : {}),
42
+ },
43
+ };
44
+ }
45
+
46
+ export default async function RootLayout({ children }: { children: ReactNode }) {
47
+ const cookieStore = await cookies();
48
+ const themeCookie = cookieStore.get(THEME_COOKIE_NAME)?.value;
49
+ const initialTheme: ThemeMode = isThemeMode(themeCookie) ? themeCookie : "system";
50
+ const resolvedThemeClass =
51
+ initialTheme === "light" || initialTheme === "dark" ? initialTheme : undefined;
52
+ const colorScheme =
53
+ initialTheme === "light" || initialTheme === "dark" ? initialTheme : undefined;
54
+
55
+ return (
56
+ <html
57
+ lang="en"
58
+ suppressHydrationWarning
59
+ className={resolvedThemeClass}
60
+ style={colorScheme ? { colorScheme } : undefined}
61
+ >
62
+ <body
63
+ className={`${geistSans.variable} ${geistMono.variable} bg-background font-sans text-foreground antialiased`}
64
+ >
65
+ <AppProviders initialTheme={initialTheme}>{children}</AppProviders>
66
+ </body>
67
+ </html>
68
+ );
69
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ const mocks = vi.hoisted(() => ({
4
+ applyVuilderShellHandoffStateToResponse: vi.fn(),
5
+ createVuilderShellHandoffState: vi.fn(() => "state-1"),
6
+ }));
7
+
8
+ vi.mock("@/lib/platform/session.server", () => ({
9
+ applyVuilderShellHandoffStateToResponse:
10
+ mocks.applyVuilderShellHandoffStateToResponse,
11
+ createVuilderShellHandoffState: mocks.createVuilderShellHandoffState,
12
+ }));
13
+
14
+ import { GET } from "./route";
15
+
16
+ describe("GET /login", () => {
17
+ it("sets shell handoff state and redirects to central SSO", () => {
18
+ const response = GET(
19
+ new Request("https://shell.example.com/login?return_to=%2Fw%2Ffleet-alpha"),
20
+ );
21
+
22
+ expect(response.status).toBe(307);
23
+ expect(response.headers.get("location")).toBe(
24
+ "http://127.0.0.1:3400/login?returnTo=http%3A%2F%2F127.0.0.1%3A3301%2Fw%2Ffleet-alpha%2Fconnect%3Fmw_shell_state%3Dstate-1",
25
+ );
26
+ expect(response.headers.get("cache-control")).toBe("no-store");
27
+ expect(response.headers.get("referrer-policy")).toBe("no-referrer");
28
+ expect(mocks.applyVuilderShellHandoffStateToResponse).toHaveBeenCalledWith(
29
+ expect.any(Response),
30
+ "state-1",
31
+ );
32
+ });
33
+ });
@@ -0,0 +1,21 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { buildCentralSsoLoginUrl } from "@/lib/auth-routes.server";
4
+ import {
5
+ applyVuilderShellHandoffStateToResponse,
6
+ createVuilderShellHandoffState,
7
+ } from "@/lib/platform/session.server";
8
+
9
+ export function GET(request: Request) {
10
+ const url = new URL(request.url);
11
+ const returnPath =
12
+ url.searchParams.get("return_to") ?? url.searchParams.get("returnTo");
13
+ const handoffState = createVuilderShellHandoffState();
14
+ const response = NextResponse.redirect(
15
+ buildCentralSsoLoginUrl(returnPath, handoffState),
16
+ );
17
+ response.headers.set("Cache-Control", "no-store");
18
+ response.headers.set("Referrer-Policy", "no-referrer");
19
+ applyVuilderShellHandoffStateToResponse(response, handoffState);
20
+ return response;
21
+ }
@@ -0,0 +1,16 @@
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
+
3
+ export const metadata = {
4
+ title: "Vuilder Shell",
5
+ description: "Customer-facing branded tenant shell.",
6
+ };
7
+
8
+ export default function HomePage() {
9
+ return (
10
+ <StaticPublicPage
11
+ eyebrow="Vuilder Shell"
12
+ title="A branded customer workspace connected to MinuteWork"
13
+ body="Customer workspace routes stay branded while identity, provisioning, and app-pack install authority stay on the platform."
14
+ />
15
+ );
16
+ }
@@ -0,0 +1,15 @@
1
+ import { StaticPublicPage } from "@/features/public-shell/components/static-public-page";
2
+
3
+ export const metadata = {
4
+ title: "Pricing",
5
+ };
6
+
7
+ export default function PricingPage() {
8
+ return (
9
+ <StaticPublicPage
10
+ eyebrow="Pricing"
11
+ title="Plans for customer access"
12
+ body="Clear plan options for customer workspaces."
13
+ />
14
+ );
15
+ }
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ import { ThemeProvider } from "@/lib/theme";
6
+ import type { ThemeMode } from "@/lib/theme-config";
7
+
8
+ export function AppProviders({
9
+ children,
10
+ initialTheme,
11
+ }: {
12
+ children: ReactNode;
13
+ initialTheme: ThemeMode;
14
+ }) {
15
+ return (
16
+ <ThemeProvider
17
+ attribute="class"
18
+ defaultTheme={initialTheme}
19
+ enableSystem
20
+ disableTransitionOnChange
21
+ >
22
+ {children}
23
+ </ThemeProvider>
24
+ );
25
+ }
@@ -0,0 +1,21 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ import { resolvePublicMetadataBase } from "@/lib/public-site";
4
+
5
+ export default function robots(): MetadataRoute.Robots {
6
+ const metadataBase = resolvePublicMetadataBase();
7
+ return {
8
+ rules: [
9
+ {
10
+ userAgent: "*",
11
+ allow: "/",
12
+ },
13
+ ],
14
+ ...(metadataBase
15
+ ? {
16
+ sitemap: new URL("/sitemap.xml", metadataBase).toString(),
17
+ host: metadataBase.host,
18
+ }
19
+ : {}),
20
+ };
21
+ }
@@ -0,0 +1,33 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ import { appRoutes } from "@/lib/app-routes";
4
+ import { resolvePublicSiteUrl } from "@/lib/public-site";
5
+
6
+ function buildSitemapUrl(pathname: string) {
7
+ return resolvePublicSiteUrl(pathname)?.toString() ?? pathname;
8
+ }
9
+
10
+ export default function sitemap(): MetadataRoute.Sitemap {
11
+ return [
12
+ {
13
+ url: buildSitemapUrl(appRoutes.publicHome),
14
+ changeFrequency: "weekly",
15
+ priority: 1,
16
+ },
17
+ {
18
+ url: buildSitemapUrl(appRoutes.pricing),
19
+ changeFrequency: "monthly",
20
+ priority: 0.8,
21
+ },
22
+ {
23
+ url: buildSitemapUrl(appRoutes.docsIndex),
24
+ changeFrequency: "weekly",
25
+ priority: 0.8,
26
+ },
27
+ {
28
+ url: buildSitemapUrl(appRoutes.blogIndex),
29
+ changeFrequency: "weekly",
30
+ priority: 0.8,
31
+ },
32
+ ];
33
+ }
@@ -0,0 +1,31 @@
1
+ import { notFound } from "next/navigation";
2
+
3
+ import { VuilderConnectScreen } from "@/features/vuilder-shell/components/vuilder-connect-screen";
4
+ import { loadWorkspaceShellSession } from "@/lib/platform/client.server";
5
+ import { readPlatformAuthState } from "@/lib/platform/session.server";
6
+
7
+ type WorkspaceConnectPageProps = {
8
+ params: Promise<{
9
+ workspace_slug: string;
10
+ }>;
11
+ };
12
+
13
+ export const metadata = {
14
+ title: "Connect Workspace",
15
+ };
16
+
17
+ export default async function WorkspaceConnectPage({
18
+ params,
19
+ }: WorkspaceConnectPageProps) {
20
+ const { workspace_slug: workspaceSlug } = await params;
21
+ const authState = await readPlatformAuthState();
22
+ const shellSession = (await loadWorkspaceShellSession(authState, workspaceSlug))
23
+ .data;
24
+ const { membership, session, workspaceCanView } = shellSession;
25
+
26
+ if (session.authenticated && (!membership || !workspaceCanView)) {
27
+ notFound();
28
+ }
29
+
30
+ return <VuilderConnectScreen session={session} workspaceSlug={workspaceSlug} />;
31
+ }
@@ -0,0 +1,54 @@
1
+ import { notFound, redirect } from "next/navigation";
2
+
3
+ import { AuthenticatedAppLayoutShell } from "@/features/shell/components/authenticated-app-layout-shell";
4
+ import { VuilderWorkspaceScreen } from "@/features/vuilder-shell/components/vuilder-workspace-screen";
5
+ import { loadWorkspaceShellSession } from "@/lib/platform/client.server";
6
+ import { appRoutes } from "@/lib/app-routes";
7
+ import { readPlatformAuthState } from "@/lib/platform/session.server";
8
+
9
+ type WorkspacePageProps = {
10
+ params: Promise<{
11
+ workspace_slug: string;
12
+ }>;
13
+ };
14
+
15
+ export const metadata = {
16
+ title: "Workspace",
17
+ };
18
+
19
+ export default async function WorkspacePage({ params }: WorkspacePageProps) {
20
+ const { workspace_slug: workspaceSlug } = await params;
21
+ const appName = process.env.MW_TEMPLATE_APP_NAME || "Vuilder Shell";
22
+ const authState = await readPlatformAuthState();
23
+ const shellSession = (await loadWorkspaceShellSession(authState, workspaceSlug))
24
+ .data;
25
+ const { membership, session, workspaceCanOpen, workspaceCanView } =
26
+ shellSession;
27
+
28
+ if (!session.authenticated) {
29
+ redirect(appRoutes.loginForWorkspace(workspaceSlug));
30
+ }
31
+
32
+ if (!membership || !workspaceCanView) {
33
+ notFound();
34
+ }
35
+
36
+ if (!workspaceCanOpen) {
37
+ redirect(appRoutes.workspaceConnect(workspaceSlug));
38
+ }
39
+
40
+ return (
41
+ <AuthenticatedAppLayoutShell
42
+ appName={appName}
43
+ membership={membership}
44
+ session={session}
45
+ >
46
+ <VuilderWorkspaceScreen
47
+ appName={appName}
48
+ membership={membership}
49
+ session={session}
50
+ workspaceSlug={workspaceSlug}
51
+ />
52
+ </AuthenticatedAppLayoutShell>
53
+ );
54
+ }
@@ -0,0 +1,59 @@
1
+ import * as React from "react";
2
+
3
+ import { Slot } from "@radix-ui/react-slot";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const buttonVariants = cva(
9
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-white hover:bg-destructive/90 dark:bg-destructive/60",
16
+ outline:
17
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
18
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
24
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
25
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
26
+ icon: "size-9",
27
+ "icon-sm": "size-8",
28
+ "icon-lg": "size-10",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default",
34
+ },
35
+ },
36
+ );
37
+
38
+ function Button({
39
+ className,
40
+ variant,
41
+ size,
42
+ asChild = false,
43
+ ...props
44
+ }: React.ComponentProps<"button"> &
45
+ VariantProps<typeof buttonVariants> & {
46
+ asChild?: boolean;
47
+ }) {
48
+ const Comp = asChild ? Slot : "button";
49
+
50
+ return (
51
+ <Comp
52
+ data-slot="button"
53
+ className={cn(buttonVariants({ variant, size, className }))}
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+
59
+ export { Button, buttonVariants };
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30 md:text-sm",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Input };
@@ -0,0 +1,26 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Design System/Governance" />
4
+
5
+ # Governance
6
+
7
+ The design system lives under `src/design-system` and is the only public visual API for application code.
8
+
9
+ ## Import contract
10
+
11
+ - Allowed in route and feature code: `@/design-system/primitives/*`, `@/design-system/patterns/*`, `@/design-system/recipes/*`, and `@/design-system/tokens/manifest`.
12
+ - Forbidden outside `src/design-system`: `@/components/ui/*`, `@radix-ui/*`, and direct raw token CSS imports.
13
+
14
+ ## Authoring rules
15
+
16
+ - Literal color values belong only in `src/design-system/tokens`.
17
+ - Route and feature code compose primitives and patterns instead of creating one-off inline visual systems.
18
+ - Inline styles are allowed only for CSS variable references or truly dynamic layout values.
19
+ - Reused UI should be promoted into `src/design-system/patterns`.
20
+
21
+ ## Enforcement
22
+
23
+ - `pnpm validate` runs template validation, type generation, token regeneration, `pnpm design-system:check`, and the app test/build pipeline.
24
+ - `pnpm design-system:check` validates imports, raw values, and story coverage.
25
+ - `pnpm build-storybook` keeps documentation coverage intact.
26
+ - `pnpm design-system:visual` is optional for local visual baselines, but `tools/design-system/__screenshots__/` is not shipped in the canonical template bundle.
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+
3
+ import { Button } from "@/design-system/primitives/button";
4
+ import { PanelFrame } from "@/design-system/patterns/panel-frame";
5
+
6
+ const meta = {
7
+ title: "Design System/Patterns/Panel Frame",
8
+ component: PanelFrame,
9
+ tags: ["autodocs"],
10
+ } satisfies Meta<typeof PanelFrame>;
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ export const Raised: Story = {
17
+ render: () => (
18
+ <PanelFrame tone="raised" className="w-96 space-y-4">
19
+ <div className="space-y-1">
20
+ <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
21
+ Tenant thread
22
+ </p>
23
+ <h3 className="text-lg font-semibold text-foreground">Missing rent receipt</h3>
24
+ <p className="text-sm text-muted-foreground">
25
+ Keep recurring workspace panels visually consistent across login, command, and status
26
+ surfaces.
27
+ </p>
28
+ </div>
29
+ <div className="flex gap-2">
30
+ <Button size="sm">Follow up</Button>
31
+ <Button size="sm" variant="outline">
32
+ Open details
33
+ </Button>
34
+ </div>
35
+ </PanelFrame>
36
+ ),
37
+ };
38
+
39
+ export const Sidebar: Story = {
40
+ render: () => (
41
+ <PanelFrame tone="sidebar" radius="none" className="w-80 space-y-3 border-l">
42
+ <h3 className="text-sm font-semibold text-foreground">Shell surface</h3>
43
+ <p className="text-sm text-muted-foreground">
44
+ Use the sidebar tone for docked navigation or support surfaces.
45
+ </p>
46
+ </PanelFrame>
47
+ ),
48
+ };
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+
3
+ import { type VariantProps } from "class-variance-authority";
4
+
5
+ import { panelFrameVariants } from "@/design-system/recipes/chrome";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ type PanelFrameProps = React.ComponentProps<"div"> &
9
+ VariantProps<typeof panelFrameVariants>;
10
+
11
+ function PanelFrame({
12
+ className,
13
+ tone,
14
+ radius,
15
+ padding,
16
+ ...props
17
+ }: PanelFrameProps) {
18
+ return (
19
+ <div
20
+ className={cn(panelFrameVariants({ tone, radius, padding }), className)}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ export { PanelFrame, panelFrameVariants };
@@ -0,0 +1,26 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+
3
+ import { StatusBadge } from "@/design-system/patterns/status-badge";
4
+
5
+ const meta = {
6
+ title: "Design System/Patterns/Status Badge",
7
+ component: StatusBadge,
8
+ tags: ["autodocs"],
9
+ } satisfies Meta<typeof StatusBadge>;
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const AllTones: Story = {
16
+ render: () => (
17
+ <div className="flex flex-wrap gap-2">
18
+ <StatusBadge tone="default">Draft</StatusBadge>
19
+ <StatusBadge tone="primary">AI suggested</StatusBadge>
20
+ <StatusBadge tone="success">Completed</StatusBadge>
21
+ <StatusBadge tone="warning">Needs review</StatusBadge>
22
+ <StatusBadge tone="danger">Failed</StatusBadge>
23
+ <StatusBadge tone="info">Queued</StatusBadge>
24
+ </div>
25
+ ),
26
+ };
@@ -0,0 +1,35 @@
1
+ import * as React from "react";
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const statusBadgeVariants = cva(
8
+ "inline-flex items-center rounded px-2 py-0.5 text-xs font-medium",
9
+ {
10
+ variants: {
11
+ tone: {
12
+ default: "bg-muted text-muted-foreground",
13
+ primary: "bg-primary/15 text-text-link",
14
+ success: "bg-success/20 text-success",
15
+ warning: "bg-warning/20 text-warning",
16
+ danger: "bg-danger/20 text-danger",
17
+ info: "bg-info/20 text-info",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ tone: "default",
22
+ },
23
+ },
24
+ );
25
+
26
+ type StatusBadgeProps = React.ComponentProps<"span"> &
27
+ VariantProps<typeof statusBadgeVariants>;
28
+
29
+ function StatusBadge({ className, tone, ...props }: StatusBadgeProps) {
30
+ return (
31
+ <span className={cn(statusBadgeVariants({ tone }), className)} {...props} />
32
+ );
33
+ }
34
+
35
+ export { StatusBadge, statusBadgeVariants };