create-ailk 0.6.0 → 0.7.0

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 (248) hide show
  1. package/README.md +44 -5
  2. package/component-catalog.json +146 -21
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module-core.d.ts +22 -0
  5. package/dist/add-module-core.js +50 -0
  6. package/dist/add-module.d.ts +89 -0
  7. package/dist/add-module.js +334 -0
  8. package/dist/category-profile.d.ts +126 -0
  9. package/dist/category-profile.js +262 -0
  10. package/dist/cli.js +124 -13
  11. package/dist/copy.d.ts +14 -0
  12. package/dist/copy.js +38 -1
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +6 -1
  15. package/dist/lib/extract-module-bundle.d.ts +76 -2
  16. package/dist/lib/extract-module-bundle.js +219 -8
  17. package/dist/lib/license-key-files.d.ts +15 -0
  18. package/dist/lib/license-key-files.js +102 -0
  19. package/dist/lib/module-license-gate.d.ts +6 -1
  20. package/dist/lib/module-license-gate.js +6 -1
  21. package/dist/lib/pro-callout.d.ts +16 -10
  22. package/dist/lib/pro-callout.js +27 -4
  23. package/dist/license-set.d.ts +34 -0
  24. package/dist/license-set.js +194 -0
  25. package/dist/module-architecture.d.ts +20 -0
  26. package/dist/module-architecture.js +77 -19
  27. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  28. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  29. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  30. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  31. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  32. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  33. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  34. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  35. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  36. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  37. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  38. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  39. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  40. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  41. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  42. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  43. package/dist/parse-args.d.ts +7 -0
  44. package/dist/parse-args.js +23 -1
  45. package/dist/programmatic.d.ts +46 -0
  46. package/dist/programmatic.js +144 -19
  47. package/dist/site-chrome.d.ts +18 -7
  48. package/dist/site-chrome.js +32 -14
  49. package/dist/site-roster.d.ts +127 -0
  50. package/dist/site-roster.js +494 -0
  51. package/dist/surfaces.d.ts +20 -7
  52. package/dist/surfaces.js +149 -28
  53. package/dist/sync-routes.d.ts +9 -0
  54. package/dist/sync-routes.js +2 -2
  55. package/dist/template-manifest.d.ts +58 -2
  56. package/dist/template-manifest.js +196 -69
  57. package/package.json +6 -4
  58. package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
  59. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
  60. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
  61. package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
  62. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
  63. package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
  64. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
  65. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
  66. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
  67. package/site-templates/designs/minimalism/DESIGN.md +162 -0
  68. package/site-templates/designs/standard/DESIGN.md +162 -0
  69. package/site-templates/marketing-site/DESIGN.md +162 -0
  70. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
  71. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
  72. package/site-templates/marketing-site/ailk-template.json +1 -0
  73. package/site-templates/marketing-site-free/DESIGN.md +162 -0
  74. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
  75. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
  76. package/site-templates/marketing-site-free/ailk-template.json +3 -2
  77. package/site-templates/minimalism-site/DESIGN.md +162 -0
  78. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
  79. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
  80. package/site-templates/minimalism-site/ailk-template.json +3 -2
  81. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
  82. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
  83. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  84. package/site-templates/saas-product/DESIGN.md +162 -0
  85. package/site-templates/simple-service/DESIGN.md +162 -0
  86. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
  87. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
  88. package/site-templates/simple-service/ailk-template.json +4 -3
  89. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
  90. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
  91. package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  92. package/templates/.claude/settings.json +15 -0
  93. package/templates/.claude/skills/README.md +8 -8
  94. package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
  95. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
  96. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
  97. package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
  98. package/templates/.claude/skills/add-page/references/page-types.md +111 -0
  99. package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
  100. package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
  101. package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
  102. package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
  103. package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
  104. package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
  105. package/templates/.claude/skills/fill-page/SKILL.md +180 -0
  106. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
  107. package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
  108. package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
  109. package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
  110. package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
  111. package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
  112. package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
  113. package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
  114. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
  115. package/templates/.github/workflows/ci.yml +5 -0
  116. package/templates/CLAUDE.md +10 -5
  117. package/templates/DESIGN.md +162 -0
  118. package/templates/README.md +134 -38
  119. package/templates/apps/api/.env.example +2 -1
  120. package/templates/apps/api/package.json +1 -1
  121. package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
  122. package/templates/apps/api/src/config/index.ts +19 -6
  123. package/templates/apps/api/src/lib/license-key-store.ts +45 -0
  124. package/templates/apps/api/src/lib/site.ts +5 -3
  125. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
  126. package/templates/apps/api/src/middleware/auth.ts +9 -0
  127. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
  128. package/templates/apps/api/src/routes/content/get-page.ts +86 -2
  129. package/templates/apps/api/src/server.ts +6 -0
  130. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
  131. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
  132. package/templates/apps/mcp/package.json +2 -1
  133. package/templates/apps/mcp/src/index.ts +7 -0
  134. package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
  135. package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
  136. package/templates/apps/web/PRODUCT.md +86 -0
  137. package/templates/apps/web/__tests__/setup.ts +18 -0
  138. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
  139. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
  140. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
  141. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
  142. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
  143. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
  144. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
  145. package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
  146. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
  147. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  148. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  149. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
  150. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
  151. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
  152. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
  153. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
  154. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
  155. package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
  156. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  157. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
  158. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
  159. package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
  160. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  161. package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
  162. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  163. package/templates/apps/web/app/[locale]/layout.tsx +113 -51
  164. package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
  165. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  166. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  167. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
  168. package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
  169. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
  170. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  171. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
  172. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  173. package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
  174. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  175. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
  176. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
  177. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
  178. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
  179. package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
  180. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  181. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
  182. package/templates/apps/web/app/error-fallback.tsx +17 -45
  183. package/templates/apps/web/app/not-found.tsx +14 -38
  184. package/templates/apps/web/app/og/route.tsx +6 -0
  185. package/templates/apps/web/eslint.config.mjs +3 -0
  186. package/templates/apps/web/instrumentation.ts +14 -0
  187. package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
  188. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
  189. package/templates/apps/web/lib/license-key-store.ts +52 -0
  190. package/templates/apps/web/lib/nav-config.ts +64 -12
  191. package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
  192. package/templates/apps/web/lib/site-brand.tsx +4 -8
  193. package/templates/apps/web/lib/site-chrome.ts +3 -3
  194. package/templates/apps/web/lib/site-identity.ts +134 -0
  195. package/templates/apps/web/lib/site-theme.ts +11 -0
  196. package/templates/apps/web/messages/en.json +52 -37
  197. package/templates/apps/web/middleware.ts +1 -1
  198. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  199. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  200. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  201. package/templates/apps/web/public/favicon-16x16.png +0 -0
  202. package/templates/apps/web/public/favicon-32x32.png +0 -0
  203. package/templates/apps/web/public/favicon.ico +0 -0
  204. package/templates/apps/web/public/favicon.svg +3 -3
  205. package/templates/apps/web/public/logomark.svg +6 -3
  206. package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
  207. package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
  208. package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
  209. package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
  210. package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
  211. package/templates/apps/web/public/logos/salessmyth.svg +4 -0
  212. package/templates/apps/web/public/site.webmanifest +4 -4
  213. package/templates/apps/web/public/wordmark-light.svg +4 -0
  214. package/templates/apps/web/public/wordmark.svg +3 -3
  215. package/templates/apps/web/security-headers.config.ts +5 -1
  216. package/templates/apps/web/src/lib/commerce-sections.ts +35 -3
  217. package/templates/content/_site.mdx +41 -3
  218. package/templates/content/en/AboutPage/about.mdx +150 -144
  219. package/templates/content/en/BlogList/blog.mdx +105 -0
  220. package/templates/content/en/ContactPage/contact.mdx +44 -85
  221. package/templates/content/en/FAQPage/faq.mdx +86 -57
  222. package/templates/content/en/HomePage/home.mdx +276 -217
  223. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
  224. package/templates/content/en/SoftwareProduct/product.mdx +175 -0
  225. package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
  226. package/templates/database/CHANGELOG.md +14 -0
  227. package/templates/database/package.json +3 -2
  228. package/templates/package.json +3 -1
  229. package/templates/tools/lint-architecture.ts +315 -0
  230. package/templates/tools/tsconfig.json +14 -0
  231. package/versions.json +33 -33
  232. package/templates/.claude/skills/add-schema/SKILL.md +0 -277
  233. package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
  234. package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
  235. package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
  236. package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
  237. package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
  238. package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
  239. package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
  240. package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
  241. package/templates/apps/web/public/logo-dark.svg +0 -21
  242. package/templates/apps/web/public/logo-light.svg +0 -21
  243. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
  244. package/templates/content/en/LandingPage/blog.mdx +0 -83
  245. package/templates/content/en/LandingPage/features.mdx +0 -103
  246. package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
  247. package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
  248. package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
@@ -2,9 +2,17 @@
2
2
  "use client";
3
3
  // Shared base for all per-segment error boundaries in apps/web.
4
4
  // Each boundary supplies flow-specific heading and body copy; this component
5
- // owns the token-sourced styling and the reset button.
5
+ // owns the layout and the reset button, composed from @working-theory/ui
6
+ // primitives (#6514).
7
+ //
8
+ // app/error.tsx renders under the pass-through root layout, outside
9
+ // app/[locale]/layout.tsx — the only other importer of globals.css — so this
10
+ // module imports the stylesheet itself; without it the primitives' token
11
+ // classes would render unstyled at the root boundary.
12
+ import "./globals.css";
13
+
6
14
  import { clientError } from "@working-theory/page-renderer/edge";
7
- import { tokens } from "@working-theory/theme";
15
+ import { Button, Heading, Text } from "@working-theory/ui/primitives";
8
16
  import type { ReactElement } from "react";
9
17
  import { useEffect } from "react";
10
18
 
@@ -15,44 +23,6 @@ export interface ErrorFallbackProps {
15
23
  body: string;
16
24
  }
17
25
 
18
- const styles = {
19
- main: {
20
- background: tokens.colors.light.bg,
21
- color: tokens.colors.light.fg,
22
- minHeight: "100vh",
23
- display: "flex",
24
- flexDirection: "column" as const,
25
- alignItems: "center",
26
- justifyContent: "center",
27
- padding: tokens.radius.lg,
28
- gap: tokens.radius.md,
29
- },
30
- heading: {
31
- fontSize: "1.5rem",
32
- lineHeight: tokens.lineHeight.md,
33
- color: tokens.colors.light.errorFg,
34
- background: tokens.colors.light.errorBg,
35
- padding: `${tokens.radius.sm} ${tokens.radius.md}`,
36
- borderRadius: tokens.radius.sm,
37
- margin: 0,
38
- },
39
- body: {
40
- fontSize: "1rem",
41
- lineHeight: tokens.lineHeight.md,
42
- margin: 0,
43
- },
44
- button: {
45
- fontSize: "1rem",
46
- lineHeight: tokens.lineHeight.md,
47
- background: tokens.colors.light.brand,
48
- color: tokens.colors.light.bg,
49
- border: "none",
50
- borderRadius: tokens.radius.sm,
51
- padding: `${tokens.radius.sm} ${tokens.radius.md}`,
52
- cursor: "pointer",
53
- },
54
- } as const;
55
-
56
26
  export function ErrorFallback({
57
27
  error,
58
28
  reset,
@@ -64,12 +34,14 @@ export function ErrorFallback({
64
34
  }, [error]);
65
35
 
66
36
  return (
67
- <main style={styles.main}>
68
- <h1 style={styles.heading}>{heading}</h1>
69
- <p style={styles.body}>{body}</p>
70
- <button type="button" onClick={reset} style={styles.button}>
37
+ <main className="flex min-h-screen flex-col items-center justify-center gap-4 bg-(--color-bg) p-card text-(--color-fg)">
38
+ <Heading level={1} visual={3}>
39
+ {heading}
40
+ </Heading>
41
+ <Text>{body}</Text>
42
+ <Button type="button" onClick={reset}>
71
43
  Try again
72
- </button>
44
+ </Button>
73
45
  </main>
74
46
  );
75
47
  }
@@ -1,48 +1,24 @@
1
1
  /* eslint-disable no-restricted-syntax -- i18n-allow: not-found boundary renders outside the [locale] intl provider; translations unavailable at this segment (ADR 0019) */
2
2
  // Next.js App Router not-found boundary. Renders for any route that calls
3
3
  // notFound() or when no route matches. Server Component by default.
4
- import { tokens } from "@working-theory/theme";
5
- import type { ReactElement } from "react";
4
+ //
5
+ // This boundary renders under the pass-through root layout, outside
6
+ // app/[locale]/layout.tsx — the only other importer of globals.css — so it
7
+ // imports the stylesheet itself; without it the primitives' token classes
8
+ // would render unstyled here (#6514).
9
+ import "./globals.css";
6
10
 
7
- const styles = {
8
- main: {
9
- background: tokens.colors.light.bg,
10
- color: tokens.colors.light.fg,
11
- minHeight: "100vh",
12
- display: "flex",
13
- flexDirection: "column" as const,
14
- alignItems: "center",
15
- justifyContent: "center",
16
- padding: tokens.radius.lg,
17
- gap: tokens.radius.md,
18
- },
19
- heading: {
20
- fontSize: "1.5rem",
21
- lineHeight: tokens.lineHeight.md,
22
- margin: 0,
23
- },
24
- body: {
25
- fontSize: "1rem",
26
- lineHeight: tokens.lineHeight.md,
27
- margin: 0,
28
- },
29
- link: {
30
- color: tokens.colors.light.brand,
31
- fontSize: "1rem",
32
- lineHeight: tokens.lineHeight.md,
33
- },
34
- } as const;
11
+ import { Heading, Link, Text } from "@working-theory/ui/primitives";
12
+ import type { ReactElement } from "react";
35
13
 
36
14
  export default function NotFound(): ReactElement {
37
15
  return (
38
- <main style={styles.main}>
39
- <h1 style={styles.heading}>We couldn&apos;t find that page</h1>
40
- <p style={styles.body}>
41
- The page you are looking for does not exist or may have moved.
42
- </p>
43
- <a href="/" style={styles.link}>
44
- Return home
45
- </a>
16
+ <main className="flex min-h-screen flex-col items-center justify-center gap-4 bg-(--color-bg) p-card text-(--color-fg)">
17
+ <Heading level={1} visual={3}>
18
+ We couldn&apos;t find that page
19
+ </Heading>
20
+ <Text>The page you are looking for does not exist or may have moved.</Text>
21
+ <Link href="/">Return home</Link>
46
22
  </main>
47
23
  );
48
24
  }
@@ -15,6 +15,12 @@
15
15
  * - Strip control characters \x00–\x1F and \x7F from title and schemaType
16
16
  * - Cap title at 200 chars, schemaType at 50 chars
17
17
  * - If sanitization yields an empty title, fall back to static PNG redirect
18
+ *
19
+ * Composition exception (#6514): this route renders raw elements with inline
20
+ * `style` objects, and is deliberately left out of the page-surface
21
+ * `no-raw-html-elements` scope in eslint.config.ts. Satori (next/og) rasterises
22
+ * a JSX tree to PNG — it reads only inline styles, supports no stylesheet or
23
+ * Tailwind classes, and cannot render @working-theory/ui components.
18
24
  */
19
25
  import { clientError } from "@working-theory/page-renderer/edge";
20
26
  import { tokens } from "@working-theory/theme";
@@ -28,6 +28,9 @@ export default tseslint.config(
28
28
  "**/dist/**",
29
29
  "**/generated/**",
30
30
  "**/node_modules/**",
31
+ // fumadocs-mdx's generated collection code (#6680) — present only with
32
+ // the docs-blog module, and never hand-edited.
33
+ "**/.source/**",
31
34
  ],
32
35
  },
33
36
  js.configs.recommended,
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Next.js instrumentation hook — runs once per server instance at boot.
3
+ *
4
+ * Registers the durable home of a renewed licence key (#6677 DD-6) before any
5
+ * request resolves the licence. Node runtime only: the store reaches the
6
+ * database through Prisma, which the Edge runtime cannot load.
7
+ */
8
+
9
+ export async function register(): Promise<void> {
10
+ if (process.env.NEXT_RUNTIME === "nodejs") {
11
+ const { registerLicenseKeyStore } = await import("./lib/license-key-store");
12
+ await registerLicenseKeyStore();
13
+ }
14
+ }
@@ -4,11 +4,28 @@
4
4
  * This is the end-to-end claim the wiring makes — a site declares a theme and
5
5
  * the built page renders in those colours — asserted on the actual repo file
6
6
  * rather than a fixture, so deleting the `theme:` block would fail here.
7
+ *
8
+ * Every expectation is DERIVED from whichever `_site.mdx` the test reads, never
9
+ * hard-coded (#6460). This file is snapshotted into `create-ailk`'s
10
+ * `templates/`, so it runs against TWO different `_site.mdx` files: this
11
+ * monorepo's, and the scaffold's own. Hard-coded values made it a snapshot of
12
+ * one tree's palette masquerading as a round-trip test — green here, red in a
13
+ * scaffold whose theme differs, which is exactly what happened when the base
14
+ * scaffold moved to the black-on-white foundation. It also meant a CUSTOMER who
15
+ * took the file's own invitation to "declare only what you change" got a red
16
+ * suite for editing their own colours.
17
+ *
18
+ * Deriving keeps every claim the hard-coded form made — the mapping is applied,
19
+ * the values reach the CSS, and nothing else moves — while making all three
20
+ * true of any declared palette. Deleting the `theme:` block still fails, on the
21
+ * `toBeDefined()` below.
7
22
  */
8
- import { mkdtempSync, writeFileSync } from "node:fs";
23
+ import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
9
24
  import { tmpdir } from "node:os";
10
25
  import { join } from "node:path";
11
26
 
27
+ import { parse as parseYaml } from "yaml";
28
+
12
29
  import { PartialColorPaletteSchema } from "@working-theory/site-config/schema";
13
30
  import { siteColorTokenMap, themeToCssVarsBothModes } from "@working-theory/theme";
14
31
 
@@ -26,6 +43,46 @@ function darkBlock(css: string): string {
26
43
  return css.slice(css.indexOf(':root[data-theme="dark"]'));
27
44
  }
28
45
 
46
+ /** The two modes, as the palette and the emitted CSS both name them. */
47
+ const MODES = ["light", "dark"] as const;
48
+
49
+ /**
50
+ * The `theme.colors` block exactly as the `_site.mdx` under test declares it —
51
+ * raw SLOT names (`text`, `primary`), not token keys. Read with the same
52
+ * frontmatter parse `site-theme.ts` uses, so the test cannot pass against a
53
+ * file the implementation reads differently.
54
+ */
55
+ function declaredColors(): Partial<Record<(typeof MODES)[number], Record<string, string>>> {
56
+ const raw = readFileSync(join(REPO_CONTENT_ROOT, "_site.mdx"), "utf8");
57
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
58
+ const parsed = parseYaml(frontmatter?.[1] ?? "") as
59
+ | { theme?: { colors?: Record<string, Record<string, string>> } }
60
+ | null;
61
+ return parsed?.theme?.colors ?? {};
62
+ }
63
+
64
+ /**
65
+ * The CSS custom property a token key is emitted as — `fgOnBrand` →
66
+ * `--color-fg-on-brand`. The colon in every call site anchors the match, so
67
+ * `--color-surface` never collides with `--color-surface-secondary`.
68
+ */
69
+ function cssVar(tokenKey: string): string {
70
+ return `--color-${tokenKey.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`;
71
+ }
72
+
73
+ /** `{ [tokenKey]: value }` for one mode's declared slots, mapping applied. */
74
+ function declaredTokens(mode: (typeof MODES)[number]): Record<string, string> {
75
+ const out: Record<string, string> = {};
76
+ for (const [slot, value] of Object.entries(declaredColors()[mode] ?? {})) {
77
+ const tokenKey = siteColorTokenMap[slot as keyof typeof siteColorTokenMap];
78
+ // A slot with no token counterpart cannot reach the CSS at all —
79
+ // `siteThemeToBrandTokens` throws on it, which the schema's own tests
80
+ // cover. Nothing to assert about it here.
81
+ if (typeof tokenKey === "string") out[tokenKey] = value;
82
+ }
83
+ return out;
84
+ }
85
+
29
86
  describe("loadSiteBrandTokens — the real content/_site.mdx", () => {
30
87
  const originalRoot = process.env.AILK_CONTENT_ROOT;
31
88
 
@@ -38,35 +95,93 @@ describe("loadSiteBrandTokens — the real content/_site.mdx", () => {
38
95
  else process.env.AILK_CONTENT_ROOT = originalRoot;
39
96
  });
40
97
 
41
- it("translates the declared theme into the token overlay", () => {
42
- expect(loadSiteBrandTokens()).toEqual({
43
- colors: {
44
- light: { bg: "#fdfcfa", fg: "#1c1917" },
45
- dark: { bg: "#0c0a09" },
46
- },
47
- });
98
+ it("translates every colour the file declares into its mapped token", () => {
99
+ const overlay = loadSiteBrandTokens();
100
+ // The `theme:` block exists and produced an overlay — the claim the
101
+ // hard-coded form made by naming values, kept without naming them.
102
+ expect(overlay).toBeDefined();
103
+
104
+ // `BrandTokenOverrides` is deliberately `Readonly<Record<string, unknown>>`
105
+ // — `@working-theory/theme` cannot name the overlay's shape without
106
+ // pinning it to one token set — so the test states the shape it asserts
107
+ // about. The hard-coded form sidestepped this by comparing whole objects.
108
+ const colors = (overlay?.["colors"] ?? {}) as Partial<
109
+ Record<(typeof MODES)[number], Record<string, string>>
110
+ >;
111
+
112
+ let asserted = 0;
113
+ for (const mode of MODES) {
114
+ const expectedTokens = declaredTokens(mode);
115
+ // Exactly the declared slots, nothing invented and nothing dropped.
116
+ expect(colors[mode] ?? {}).toEqual(expectedTokens);
117
+ asserted += Object.keys(expectedTokens).length;
118
+ }
119
+ // Non-vacuity: a file that declared nothing would satisfy the loop above.
120
+ expect(asserted).toBeGreaterThan(0);
48
121
  });
49
122
 
50
123
  it("reaches the emitted CSS custom properties", () => {
51
124
  const brand = loadSiteBrandTokens();
52
125
  const css = themeToCssVarsBothModes(brand ? { brand } : {});
53
-
54
- expect(lightBlock(css)).toContain("--color-bg: #fdfcfa;");
55
- expect(lightBlock(css)).toContain("--color-fg: #1c1917;");
56
- expect(darkBlock(css)).toContain("--color-bg: #0c0a09;");
126
+ const blockFor = { light: lightBlock(css), dark: darkBlock(css) };
127
+
128
+ let asserted = 0;
129
+ for (const mode of MODES) {
130
+ for (const [tokenKey, value] of Object.entries(declaredTokens(mode))) {
131
+ expect(blockFor[mode]).toContain(`${cssVar(tokenKey)}: ${value};`);
132
+ asserted += 1;
133
+ }
134
+ }
135
+ expect(asserted).toBeGreaterThan(0);
57
136
  });
58
137
 
59
138
  it("changes exactly the declared variables and nothing else", () => {
60
139
  const brand = loadSiteBrandTokens();
61
140
  const themed = themeToCssVarsBothModes(brand ? { brand } : {});
62
- const expected = themeToCssVarsBothModes()
63
- // light block — the two declared light colours
64
- .replace("--color-bg: #ffffff;", "--color-bg: #fdfcfa;")
65
- .replace("--color-fg: #030712;", "--color-fg: #1c1917;")
66
- // dark block — the one declared dark colour
67
- .replace("--color-bg: #0a0a0a;", "--color-bg: #0c0a09;");
68
-
69
- expect(themed).toBe(expected);
141
+
142
+ // Rebuild the expectation from the DEFAULT CSS by overwriting, per mode
143
+ // block, only the variables this site declares. Anything the overlay
144
+ // touched beyond them makes the final equality fail — which is the whole
145
+ // point of this case.
146
+ const base = themeToCssVarsBothModes();
147
+ const rewritten = { light: lightBlock(base), dark: darkBlock(base) };
148
+ for (const mode of MODES) {
149
+ for (const [tokenKey, value] of Object.entries(declaredTokens(mode))) {
150
+ const pattern = new RegExp(`(${cssVar(tokenKey)}: )[^;]+;`);
151
+ // The variable must exist in the default block for the overwrite to
152
+ // mean anything — a replace with no target would weaken the equality
153
+ // below into a tautology. Presence is the right check rather than
154
+ // "the text changed": a site may legitimately declare a value that
155
+ // already MATCHES the default (the black-on-white base declares
156
+ // `bg: #ffffff`, which light mode already is), and that no-op is
157
+ // correct, not vacuous.
158
+ expect(rewritten[mode]).toMatch(pattern);
159
+ rewritten[mode] = rewritten[mode].replace(pattern, `$1${value};`);
160
+ }
161
+
162
+ // `--color-surface-raised` is DERIVED, not independent: the emitter
163
+ // resolves it from `surfaceSecondary` in light mode and from `surface`
164
+ // in dark (`packages/theme/src/theme-to-css.ts`, and the rule is
165
+ // documented at `site-theme.ts`'s header). So a site that declares
166
+ // either of those moves it too, and the equality below has to account
167
+ // for it — re-derived here from whatever the block now carries rather
168
+ // than restated as a value, so the two cannot drift.
169
+ //
170
+ // Kept inside the total equality instead of excluded from it: this
171
+ // variable is SUPPOSED to move, and excluding it would stop the test
172
+ // noticing if it ever stopped moving.
173
+ const derivedFrom = mode === "light" ? "surfaceSecondary" : "surface";
174
+ const sourceValue = new RegExp(`${cssVar(derivedFrom)}: ([^;]+);`).exec(
175
+ rewritten[mode],
176
+ )?.[1];
177
+ expect(sourceValue).toBeDefined();
178
+ rewritten[mode] = rewritten[mode].replace(
179
+ /(--color-surface-raised: )[^;]+;/,
180
+ `$1${sourceValue};`,
181
+ );
182
+ }
183
+
184
+ expect(themed).toBe(rewritten.light + rewritten.dark);
70
185
  });
71
186
 
72
187
  it("treats a bare `theme:` key as no theme, rather than throwing", () => {
@@ -56,6 +56,10 @@ import {
56
56
  import type { ReactElement } from "react";
57
57
  import { parse as parseYaml } from "yaml";
58
58
 
59
+ // Registers the site's `_site.mdx` identity at module load, as the `[locale]`
60
+ // layout does in production (#6597).
61
+ import { readSiteIdentity } from "../site-identity";
62
+
59
63
  // Detail routes call notFound() on a missing page; a thrown error is how the
60
64
  // test sees it (the same shape the per-surface suites use).
61
65
  jest.mock("next/navigation", () => ({
@@ -187,6 +191,38 @@ function extractJsonLdBlocks(container: HTMLElement): JsonLdObject[] {
187
191
  return blocks;
188
192
  }
189
193
 
194
+ const SITE_IDENTITY = readSiteIdentity(CONTENT_ROOT);
195
+ const SITE_ORIGIN = (
196
+ process.env.NEXT_PUBLIC_SITE_URL ??
197
+ process.env.MARKETING_WEB_BASE_URL ??
198
+ "http://localhost:3000"
199
+ ).replace(/\/+$/, "");
200
+
201
+ /**
202
+ * The sitewide identity trio every rendered page carries when the site's own
203
+ * `_site.mdx` reads (#6597): exactly one identity node at its `@id`, exactly
204
+ * one `WebSite` published by it, and the page's primary linked `isPartOf`.
205
+ * `null` when the page carries all three.
206
+ */
207
+ function identityFailure(blocks: JsonLdObject[], label: string): string | null {
208
+ if (SITE_IDENTITY === undefined) return null;
209
+ const identityId = `${SITE_ORIGIN}/#${SITE_IDENTITY.identity.kind === "Person" ? "person" : "organization"}`;
210
+ const webSiteId = `${SITE_ORIGIN}/#website`;
211
+ const identity = blocks.filter((b) => b["@id"] === identityId);
212
+ const webSite = blocks.filter((b) => b["@id"] === webSiteId);
213
+ const problems: string[] = [];
214
+ if (identity.length !== 1 || identity[0]?.["@type"] !== SITE_IDENTITY.identity.kind)
215
+ problems.push(`expected one ${SITE_IDENTITY.identity.kind} at ${identityId}, got ${identity.length}`);
216
+ if (webSite.length !== 1 || webSite[0]?.["@type"] !== "WebSite")
217
+ problems.push(`expected one WebSite at ${webSiteId}, got ${webSite.length}`);
218
+ else if ((webSite[0]["publisher"] as { "@id"?: unknown } | undefined)?.["@id"] !== identityId)
219
+ problems.push(`WebSite publisher is not {"@id": "${identityId}"}`);
220
+ // A page's own `isPartOf` wins over the sitewide link (composeGraph), so the
221
+ // check is that the primary is linked, not what to.
222
+ if (blocks[0]?.["isPartOf"] === undefined) problems.push("the primary carries no isPartOf");
223
+ return problems.length === 0 ? null : `${label}: sitewide identity — ${problems.join("; ")}`;
224
+ }
225
+
190
226
  // ── 1. Content-driven: every content page builds valid JSON-LD ──────────────
191
227
 
192
228
  describe("structured data — every content page builds valid JSON-LD", () => {
@@ -510,6 +546,8 @@ describe("structured data — every route emits a valid JSON-LD script", () => {
510
546
  }
511
547
  const failure = validateFor(blocks, type, pageLabel);
512
548
  if (failure !== null) failures.push(failure);
549
+ const missingIdentity = identityFailure(blocks, pageLabel);
550
+ if (missingIdentity !== null) failures.push(missingIdentity);
513
551
  }
514
552
  expect(failures).toEqual([]);
515
553
  });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Registers the durable home of a renewed licence key (#6677 DD-6).
3
+ *
4
+ * `@working-theory/license` renews the deployment's key before it lapses and
5
+ * keeps the renewed key in a `LicenseKeyStore`. Its default store is a file,
6
+ * which a redeploy wipes — and this app's one real `checkLicense()` caller
7
+ * (`src/lib/commerce-claim.ts`, gating /purchase/* and /.well-known/mcp.json)
8
+ * would then run on the older `AILK_LICENSE_KEY` alone. With a database
9
+ * configured, this registers the store backed by ONE deployment-wide
10
+ * `SiteSetting` row, `(DEFAULT_WORKSPACE_ID, "license.renewed_key")`, shared
11
+ * with apps/api and apps/mcp.
12
+ *
13
+ * NARROW, NAMED EXCEPTION to "apps/web never touches the database directly"
14
+ * (spec 6677 DD-6 and § 7; architecture.yaml `web.description`; the
15
+ * `prisma-for-auth.ts` header): this file, and only this file, hands
16
+ * `prismaForAuth.siteSetting` to `createSiteSettingLicenseKeyStore`, which
17
+ * reads and writes the one `license.renewed_key` row and nothing else. The
18
+ * alternative — an apps/api route serving the key over HTTP — is a hard floor
19
+ * and a wider exposure of a bearer credential.
20
+ *
21
+ * Imported from `instrumentation.ts` `register()` on the Node runtime only.
22
+ * Both imports are dynamic and reached only when `DATABASE_URL` is set.
23
+ */
24
+
25
+ /**
26
+ * Register the SiteSetting-backed store when `DATABASE_URL` is set. An
27
+ * explicitly set `AILK_LICENSE_STORAGE_PATH` is the operator choosing the
28
+ * file store, and is honoured. Never throws: a failure logs and leaves the
29
+ * file store in place.
30
+ */
31
+ export async function registerLicenseKeyStore(): Promise<void> {
32
+ if (!process.env.DATABASE_URL || process.env.AILK_LICENSE_STORAGE_PATH) return;
33
+ try {
34
+ const [{ prismaForAuth: prisma }, { DEFAULT_WORKSPACE_ID }, license] =
35
+ await Promise.all([
36
+ import("./prisma-for-auth"),
37
+ import("@working-theory/database/tenancy"),
38
+ import("@working-theory/license"),
39
+ ]);
40
+ license.setLicenseKeyStore(
41
+ license.createSiteSettingLicenseKeyStore(
42
+ prisma.siteSetting,
43
+ DEFAULT_WORKSPACE_ID,
44
+ ),
45
+ );
46
+ } catch (err) {
47
+ console.error(
48
+ "[web] could not register the SiteSetting licence-key store — the file store stays in place:",
49
+ err instanceof Error ? err.name : "non-Error thrown",
50
+ );
51
+ }
52
+ }
@@ -1,33 +1,85 @@
1
- // nav-config.ts — Nav item registry for apps/web (#1451, re-shaped by #5469).
1
+ // nav-config.ts — Nav item registry for apps/web (#1451, re-shaped by #5469,
2
+ // #6508).
2
3
  //
3
- // The Free marketing-site template's nav (the approved chrome, #5469 D5):
4
- // a Product flyout (How it works · Features · FAQ), then How it works,
5
- // Pricing and About as top-level items. Paths are locale-agnostic; Nav
6
- // prefixes them with the active locale at render.
4
+ // The Free marketing-site template's nav (the approved chrome, #5469 D5,
5
+ // #6508 D5): a Product flyout (How it works · Features · FAQ) rendered as a
6
+ // fixed-640px two-column panel (the Standard Issue direction — #6508), then
7
+ // How it works, Pricing and About as top-level items. Paths are
8
+ // locale-agnostic; Nav prefixes them with the active locale at render.
9
+ // `footerColumns` carries the footer's column groups (#6468), keys only — the
10
+ // layout resolves the labels.
7
11
  //
8
12
  // In a SCAFFOLD this file is regenerated by create-ailk from the declared
9
13
  // route surfaces (`renderNavConfig` in packages/create-ailk/src/surfaces.ts)
10
14
  // so the nav never links a page the scaffold does not carry — keep the two in
11
15
  // step when the base item set changes.
16
+ //
17
+ // SOFT_LOCK_OK: #6508 — Product's flyout is `width: "panel"` (D4/D5): a fixed
18
+ // 640px, two-column grid, replacing the prior "list"/"trigger" one-column
19
+ // menu. `presentation` is omitted — it defaults to "grid" (D5). The `docs`
20
+ // action is dropped (it duplicated the first feature's /how-it-works
21
+ // destination, § 1.4). This note lives here, not inside the `navItems` array
22
+ // below, so this file's `navItems`/`footerColumns` blocks stay byte-identical
23
+ // to `renderNavConfig`'s generated output — the mirror test (#6468) diffs the
24
+ // arrays verbatim.
12
25
 
13
26
  import type { NavItem } from "@working-theory/ui";
14
- import { BookOpen, HelpCircle, Layers, Sparkles } from "lucide-react";
27
+ import { HelpCircle, Layers, Sparkles } from "lucide-react";
15
28
 
16
29
  export const navItems: NavItem[] = [
17
30
  {
18
31
  labelKey: "product",
19
32
  flyout: {
20
- presentation: "list",
21
- width: "trigger",
33
+ width: "panel",
22
34
  features: [
23
- { labelKey: "howItWorks", descKey: "howItWorksDesc", icon: Layers, path: "/how-it-works" },
24
- { labelKey: "features", descKey: "featuresDesc", icon: Sparkles, path: "/features" },
35
+ { labelKey: "useCases", descKey: "useCasesDesc", icon: Layers, path: "/use-cases" },
36
+ { labelKey: "features", descKey: "featuresDesc", icon: Sparkles, path: "/software/product" },
25
37
  { labelKey: "faq", descKey: "faqDesc", icon: HelpCircle, path: "/faq" },
26
38
  ],
27
- actions: [{ labelKey: "docs", icon: BookOpen, path: "/how-it-works" }],
39
+ actions: [],
28
40
  },
29
41
  },
30
- { labelKey: "howItWorks", path: "/how-it-works" },
42
+ { labelKey: "useCases", path: "/use-cases" },
43
+ { labelKey: "blog", path: "/blog" },
31
44
  { labelKey: "pricing", path: "/pricing" },
32
45
  { labelKey: "about", path: "/about" },
33
46
  ];
47
+
48
+ export interface FooterColumnConfig {
49
+ headingKey: string;
50
+ items: { labelKey: string; path: string }[];
51
+ }
52
+
53
+ export const footerColumns: FooterColumnConfig[] = [
54
+ {
55
+ headingKey: "footerSitePagesHeading",
56
+ items: [
57
+ { labelKey: "features", path: "/software/product" },
58
+ { labelKey: "pricing", path: "/pricing" },
59
+ { labelKey: "useCases", path: "/use-cases" },
60
+ { labelKey: "about", path: "/about" },
61
+ { labelKey: "contact", path: "/contact" },
62
+ { labelKey: "bookACall", path: "/book-a-call" },
63
+ ],
64
+ },
65
+ {
66
+ headingKey: "footerFeaturesHeading",
67
+ items: [
68
+ { labelKey: "feature1", path: "/software/product" },
69
+ { labelKey: "feature2", path: "/software/product" },
70
+ { labelKey: "feature3", path: "/software/product" },
71
+ { labelKey: "feature4", path: "/software/product" },
72
+ { labelKey: "feature5", path: "/software/product" },
73
+ { labelKey: "allFeatures", path: "/software/product" },
74
+ ],
75
+ },
76
+ {
77
+ headingKey: "footerResourcesHeading",
78
+ items: [
79
+ { labelKey: "blog", path: "/blog" },
80
+ { labelKey: "glossary", path: "/glossary" },
81
+ { labelKey: "newsletter", path: "/newsletter" },
82
+ { labelKey: "faq", path: "/faq" },
83
+ ],
84
+ },
85
+ ];
@@ -5,16 +5,19 @@
5
5
  * Better Auth migration (#587). Driver adapter wrapping landed in Phase B
6
6
  * of the Prisma 7 migration (#661) ahead of the v7 bump in Phase C.
7
7
  *
8
- * Only `apps/web/auth.ts` should import this — business-logic callers go
9
- * through apps/api, never the database directly (architecture.yaml
10
- * `apps/web` may_not_import_from `database`; the single-file singleton
11
- * here is the documented exception, matching the pre-migration carve-out
12
- * for NextAuth's adapter).
8
+ * Two files import this: `apps/web/auth.ts`, for Better Auth, and
9
+ * `lib/license-key-store.ts`, which hands `prismaForAuth.siteSetting` to the
10
+ * licence client's SiteSetting store for the ONE deployment-wide
11
+ * `license.renewed_key` row (#6677 DD-6 — a narrow, named exception).
12
+ * Every other business-logic caller goes through apps/api, never the
13
+ * database directly (architecture.yaml `apps/web` may_not_import_from
14
+ * `database`; the single-file singleton here is the documented exception,
15
+ * matching the pre-migration carve-out for NextAuth's adapter).
13
16
  */
14
17
 
15
18
 
16
- import { PrismaClient } from "@working-theory/database";
17
19
  import { PrismaPg } from "@prisma/adapter-pg";
20
+ import { PrismaClient } from "@working-theory/database";
18
21
 
19
22
  declare global {
20
23
  var __ailk_prisma: PrismaClient | undefined;
@@ -108,14 +108,10 @@ export const SITE_BASE_URL =
108
108
  process.env.NEXT_PUBLIC_BASE_URL ?? siteFrontmatter?.baseUrl ?? FALLBACK_BASE_URL;
109
109
 
110
110
  /**
111
- * Shared brand mark — the light/dark logo pair, mode-switched via the theme
112
- * `dark:` utility. RSC-safe (no hooks, no client state).
111
+ * Shared brand mark — the square logomark (the central brand library's
112
+ * `logomark/loop/tile-black.svg`). The Nav pairs it with `/wordmark.svg`.
113
+ * RSC-safe (no hooks, no client state).
113
114
  */
114
115
  export function siteLogo(): ReactElement {
115
- return (
116
- <>
117
- <img src="/logo-light.svg" alt={SITE_NAME} height={28} className="block dark:hidden" />
118
- <img src="/logo-dark.svg" alt={SITE_NAME} height={28} className="hidden dark:block" />
119
- </>
120
- );
116
+ return <img src="/logomark.svg" alt={SITE_NAME} width={28} height={28} className="block" />;
121
117
  }
@@ -2,8 +2,8 @@
2
2
  //
3
3
  // GENERATED by create-ailk from the declared template's `layout` block
4
4
  // (#5612). A scaffold with no template, or a template with no `layout`
5
- // block, gets these defaults — byte-identical to every scaffold before this
6
- // file existed. `apps/web/app/[locale]/layout.tsx` reads this to decide:
5
+ // block, gets the defaults below (the announcement banner is off unless a
6
+ // template turns it on, #6469). `apps/web/app/[locale]/layout.tsx` reads this to decide:
7
7
  // - nav: "mark-only" → Nav renders the brand mark + right-hand controls
8
8
  // only (no nav links).
9
9
  // - footer: "centered" → Footer renders its existing centered variant.
@@ -24,6 +24,6 @@ export interface SiteChrome {
24
24
  export const siteChrome: SiteChrome = {
25
25
  nav: "full",
26
26
  footer: "columns",
27
- announcementBanner: true,
27
+ announcementBanner: false,
28
28
  home: "default",
29
29
  };