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,70 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const notFound = vi.fn(() => {
4
- throw new Error("notFound");
5
- });
6
- const getEntry = vi.fn();
7
- const getSiteConfig = vi.fn();
8
- const listEntries = vi.fn();
9
-
10
- vi.mock("next/navigation", () => ({
11
- notFound,
12
- }));
13
-
14
- vi.mock("@/features/public-shell/components/content-article", () => ({
15
- ContentArticle: () => null,
16
- }));
17
-
18
- vi.mock("@/features/public-shell/components/public-site-shell", () => ({
19
- PublicSiteShell: ({ children }: { children: unknown }) => children,
20
- }));
21
-
22
- vi.mock("@/lib/content/adapter.server", () => ({
23
- getEntry,
24
- getSiteConfig,
25
- listEntries,
26
- }));
27
-
28
- describe("docs article page", () => {
29
- beforeEach(() => {
30
- vi.clearAllMocks();
31
- vi.resetModules();
32
- });
33
-
34
- it("returns notFound for unknown docs slugs", async () => {
35
- getSiteConfig.mockResolvedValue({
36
- collections: {
37
- docs: {
38
- title: "Starter Docs",
39
- description: "Docs",
40
- },
41
- },
42
- siteName: "MinuteWork Combined Starter",
43
- });
44
- getEntry.mockResolvedValue(null);
45
-
46
- const page = await import("./page");
47
-
48
- await expect(
49
- page.default({
50
- params: Promise.resolve({ slug: ["missing-entry"] }),
51
- }),
52
- ).rejects.toThrow("notFound");
53
- expect(notFound).toHaveBeenCalledTimes(1);
54
- });
55
-
56
- it("derives static params from published docs entries", async () => {
57
- listEntries.mockResolvedValue([
58
- { slug: ["guides", "public-site-content"] },
59
- { slug: ["reference", "api"] },
60
- ]);
61
-
62
- const page = await import("./page");
63
-
64
- await expect(page.generateStaticParams()).resolves.toEqual([
65
- { slug: ["guides", "public-site-content"] },
66
- { slug: ["reference", "api"] },
67
- ]);
68
- expect(page.dynamicParams).toBe(false);
69
- });
70
- });
@@ -1,46 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const getSiteConfig = vi.fn();
4
- const listEntries = vi.fn();
5
-
6
- vi.mock("@/features/public-shell/components/content-collection", () => ({
7
- ContentCollection: () => null,
8
- }));
9
-
10
- vi.mock("@/features/public-shell/components/public-site-shell", () => ({
11
- PublicSiteShell: ({ children }: { children: unknown }) => children,
12
- }));
13
-
14
- vi.mock("@/lib/content/adapter.server", () => ({
15
- getSiteConfig,
16
- listEntries,
17
- }));
18
-
19
- vi.mock("next/navigation", () => ({
20
- notFound: vi.fn(),
21
- }));
22
-
23
- describe("docs index page", () => {
24
- beforeEach(() => {
25
- vi.clearAllMocks();
26
- vi.resetModules();
27
- });
28
-
29
- it("renders the public docs collection", async () => {
30
- getSiteConfig.mockResolvedValue({
31
- collections: {
32
- docs: {
33
- eyebrow: "Docs",
34
- title: "Starter Docs",
35
- description: "Route content through the adapter seam.",
36
- },
37
- },
38
- });
39
- listEntries.mockResolvedValue([]);
40
-
41
- const page = await import("./page");
42
-
43
- await expect(page.default()).resolves.toBeDefined();
44
- expect(listEntries).toHaveBeenCalledWith("docs");
45
- });
46
- });
@@ -1,55 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const redirect = vi.fn((href: string) => {
4
- throw new Error(`redirect:${href}`);
5
- });
6
- const resolveCurrentSession = vi.fn();
7
- const readPlatformAuthState = vi.fn();
8
-
9
- vi.mock("next/navigation", () => ({
10
- redirect,
11
- }));
12
-
13
- vi.mock("@/features/auth/components/login-screen", () => ({
14
- LoginScreen: () => null,
15
- }));
16
-
17
- vi.mock("@/lib/platform/client.server", () => ({
18
- resolveCurrentSession,
19
- }));
20
-
21
- vi.mock("@/lib/platform/endpoints.server", () => ({
22
- platformAuthEndpoints: {
23
- operatorConsole: "http://127.0.0.1:8000/ops/login/",
24
- },
25
- }));
26
-
27
- vi.mock("@/lib/platform/env.server", () => ({
28
- env: {
29
- MW_TEMPLATE_APP_NAME: "MinuteWork Combined Starter",
30
- },
31
- }));
32
-
33
- vi.mock("@/lib/platform/session.server", () => ({
34
- readPlatformAuthState,
35
- }));
36
-
37
- describe("login page", () => {
38
- beforeEach(() => {
39
- vi.clearAllMocks();
40
- vi.resetModules();
41
- });
42
-
43
- it("redirects authenticated users back to the home shell", async () => {
44
- readPlatformAuthState.mockResolvedValue({
45
- sessionId: "session-1",
46
- csrfToken: "csrf-1",
47
- });
48
- resolveCurrentSession.mockResolvedValue({ authenticated: true });
49
-
50
- const page = await import("./page");
51
-
52
- await expect(page.default()).rejects.toThrow("redirect:/app");
53
- expect(redirect).toHaveBeenCalledWith("/app");
54
- });
55
- });
@@ -1,90 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const getMarketingPage = vi.fn();
4
- const getSiteConfig = vi.fn();
5
- const redirect = vi.fn();
6
- const originalPublicContentSource = process.env.MW_PUBLIC_CONTENT_SOURCE;
7
-
8
- vi.mock("@/features/public-shell/components/marketing-page-canvas", () => ({
9
- MarketingPageCanvas: () => null,
10
- }));
11
-
12
- vi.mock("@/features/public-shell/components/public-site-shell", () => ({
13
- PublicSiteShell: ({ children }: { children: unknown }) => children,
14
- }));
15
-
16
- vi.mock("@/lib/content/adapter.server", () => ({
17
- getMarketingPage,
18
- getSiteConfig,
19
- }));
20
-
21
- vi.mock("next/navigation", () => ({
22
- redirect,
23
- }));
24
-
25
- describe("home page", () => {
26
- beforeEach(() => {
27
- if (originalPublicContentSource === undefined) {
28
- delete process.env.MW_PUBLIC_CONTENT_SOURCE;
29
- } else {
30
- process.env.MW_PUBLIC_CONTENT_SOURCE = originalPublicContentSource;
31
- }
32
- vi.clearAllMocks();
33
- vi.resetModules();
34
- });
35
-
36
- afterEach(() => {
37
- if (originalPublicContentSource === undefined) {
38
- delete process.env.MW_PUBLIC_CONTENT_SOURCE;
39
- } else {
40
- process.env.MW_PUBLIC_CONTENT_SOURCE = originalPublicContentSource;
41
- }
42
- });
43
-
44
- it("renders public home content without auth redirects", async () => {
45
- getSiteConfig.mockResolvedValue({
46
- siteName: "MinuteWork Combined Starter",
47
- siteDescription: "Combined starter",
48
- primaryCta: { label: "Sign In", href: "/login" },
49
- secondaryCta: { label: "Docs", href: "/docs" },
50
- });
51
- getMarketingPage.mockResolvedValue({
52
- pageKey: "home",
53
- });
54
-
55
- const page = await import("./page");
56
-
57
- await expect(page.default()).resolves.toBeDefined();
58
- expect(getSiteConfig).toHaveBeenCalledTimes(1);
59
- expect(getMarketingPage).toHaveBeenCalledWith("home");
60
- });
61
-
62
- it("renders the built-in empty home shell when no home page has been published", async () => {
63
- getSiteConfig.mockResolvedValue({
64
- siteName: "MinuteWork Combined Starter",
65
- siteDescription: "Combined starter",
66
- primaryCta: { label: "Sign In", href: "/login" },
67
- secondaryCta: { label: "Docs", href: "/docs" },
68
- });
69
- getMarketingPage.mockResolvedValue(null);
70
-
71
- const page = await import("./page");
72
-
73
- await expect(page.default()).resolves.toBeDefined();
74
- });
75
-
76
- it("redirects the private-only root route to /app", async () => {
77
- vi.resetModules();
78
- process.env.MW_PUBLIC_CONTENT_SOURCE = "none";
79
- redirect.mockImplementation(() => {
80
- throw new Error("NEXT_REDIRECT");
81
- });
82
-
83
- const page = await import("./page");
84
-
85
- await expect(page.default()).rejects.toThrow("NEXT_REDIRECT");
86
- expect(redirect).toHaveBeenCalledWith("/app");
87
- expect(getSiteConfig).not.toHaveBeenCalled();
88
- expect(getMarketingPage).not.toHaveBeenCalled();
89
- });
90
- });
@@ -1,59 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const getMarketingPage = vi.fn();
4
- const getSiteConfig = vi.fn();
5
-
6
- vi.mock("@/features/public-shell/components/marketing-page-canvas", () => ({
7
- MarketingPageCanvas: () => null,
8
- }));
9
-
10
- vi.mock("@/features/public-shell/components/public-site-shell", () => ({
11
- PublicSiteShell: ({ children }: { children: unknown }) => children,
12
- }));
13
-
14
- vi.mock("@/lib/content/adapter.server", () => ({
15
- getMarketingPage,
16
- getSiteConfig,
17
- }));
18
-
19
- vi.mock("next/navigation", () => ({
20
- notFound: vi.fn(),
21
- }));
22
-
23
- describe("pricing page", () => {
24
- beforeEach(() => {
25
- vi.clearAllMocks();
26
- vi.resetModules();
27
- });
28
-
29
- it("renders seeded pricing content", async () => {
30
- getSiteConfig.mockResolvedValue({
31
- siteName: "MinuteWork Combined Starter",
32
- siteDescription: "Combined starter",
33
- primaryCta: { label: "Sign In", href: "/login" },
34
- secondaryCta: { label: "Docs", href: "/docs" },
35
- });
36
- getMarketingPage.mockResolvedValue({
37
- pageKey: "pricing",
38
- });
39
-
40
- const page = await import("./page");
41
-
42
- await expect(page.default()).resolves.toBeDefined();
43
- expect(getMarketingPage).toHaveBeenCalledWith("pricing");
44
- });
45
-
46
- it("renders the built-in empty pricing shell when no pricing page has been published", async () => {
47
- getSiteConfig.mockResolvedValue({
48
- siteName: "MinuteWork Combined Starter",
49
- siteDescription: "Combined starter",
50
- primaryCta: { label: "Sign In", href: "/login" },
51
- secondaryCta: { label: "Docs", href: "/docs" },
52
- });
53
- getMarketingPage.mockResolvedValue(null);
54
-
55
- const page = await import("./page");
56
-
57
- await expect(page.default()).resolves.toBeDefined();
58
- });
59
- });
@@ -1,40 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- describe("robots route", () => {
4
- beforeEach(() => {
5
- vi.resetModules();
6
- vi.doUnmock("@/lib/public-site");
7
- });
8
-
9
- it("returns a public robots policy and sitemap url", async () => {
10
- expect(process.env.MW_PUBLIC_BASE_URL).toBe("http://127.0.0.1:3000");
11
-
12
- const { default: robots } = await import("./robots");
13
- const metadata = robots();
14
-
15
- expect(metadata.host).toBe("127.0.0.1:3000");
16
- expect(metadata.sitemap).toBe("http://127.0.0.1:3000/sitemap.xml");
17
- expect(metadata.rules).toEqual([
18
- {
19
- userAgent: "*",
20
- allow: "/",
21
- },
22
- ]);
23
- });
24
-
25
- it("disallows indexing when public content is disabled", async () => {
26
- vi.doMock("@/lib/public-site", () => ({
27
- isPublicContentDisabled: () => true,
28
- resolvePublicMetadataBase: () => null,
29
- }));
30
-
31
- const { default: robots } = await import("./robots");
32
-
33
- expect(robots().rules).toEqual([
34
- {
35
- userAgent: "*",
36
- disallow: "/",
37
- },
38
- ]);
39
- });
40
- });
@@ -1,63 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const { listEntries } = vi.hoisted(() => ({
4
- listEntries: vi.fn(),
5
- }));
6
-
7
- vi.mock("@/lib/content/adapter.server", () => ({
8
- listEntries,
9
- }));
10
-
11
- describe("sitemap route", () => {
12
- beforeEach(() => {
13
- vi.clearAllMocks();
14
- vi.resetModules();
15
- vi.doUnmock("@/lib/public-site");
16
- });
17
-
18
- it("includes the public route surface and published content entries", async () => {
19
- listEntries.mockImplementation(async (kind: string) =>
20
- kind === "docs"
21
- ? [{ slug: ["guides", "public-site-content"] }]
22
- : [{ slug: ["public-site-api-default"], publishedAt: "2026-03-26T10:00:00.000Z" }],
23
- );
24
-
25
- expect(process.env.MW_PUBLIC_BASE_URL).toBe("http://127.0.0.1:3000");
26
-
27
- const { default: sitemap } = await import("./sitemap");
28
- const entries = await sitemap();
29
- const urls = entries.map((entry) => entry.url);
30
-
31
- expect(urls).toContain("http://127.0.0.1:3000/");
32
- expect(urls).toContain("http://127.0.0.1:3000/pricing");
33
- expect(urls).toContain("http://127.0.0.1:3000/docs");
34
- expect(urls).toContain("http://127.0.0.1:3000/docs/guides/public-site-content");
35
- expect(urls).toContain("http://127.0.0.1:3000/blog");
36
- expect(urls).toContain("http://127.0.0.1:3000/blog/public-site-api-default");
37
- });
38
-
39
- it("keeps home and pricing in the sitemap even when the snapshot has no marketing pages", async () => {
40
- listEntries.mockResolvedValue([]);
41
-
42
- const { default: sitemap } = await import("./sitemap");
43
- const entries = await sitemap();
44
- const urls = entries.map((entry) => entry.url);
45
-
46
- expect(urls).toContain("http://127.0.0.1:3000/");
47
- expect(urls).toContain("http://127.0.0.1:3000/pricing");
48
- expect(urls).toContain("http://127.0.0.1:3000/docs");
49
- expect(urls).toContain("http://127.0.0.1:3000/blog");
50
- });
51
-
52
- it("returns an empty sitemap when public content is disabled", async () => {
53
- vi.doMock("@/lib/public-site", () => ({
54
- isPublicContentDisabled: () => true,
55
- resolvePublicSiteUrl: vi.fn(),
56
- }));
57
-
58
- const { default: sitemap } = await import("./sitemap");
59
-
60
- await expect(sitemap()).resolves.toEqual([]);
61
- expect(listEntries).not.toHaveBeenCalled();
62
- });
63
- });