qualia-framework 6.8.0 → 6.9.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 (77) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/install.js +212 -17
  3. package/bin/state.js +70 -5
  4. package/docs/EMPLOYEE-QUICKSTART.md +162 -0
  5. package/package.json +2 -1
  6. package/skills/qualia-doctor/SKILL.md +62 -0
  7. package/skills/qualia-new/REFERENCE.md +7 -0
  8. package/skills/qualia-new/SKILL.md +42 -0
  9. package/skills/qualia-report/SKILL.md +13 -0
  10. package/templates/stacks/README.md +110 -0
  11. package/templates/stacks/ai-agent/env.required.json +44 -0
  12. package/templates/stacks/ai-agent/phases.md +53 -0
  13. package/templates/stacks/ai-agent/scaffold/.env.example +12 -0
  14. package/templates/stacks/ai-agent/scaffold/README.md +31 -0
  15. package/templates/stacks/ai-agent/scaffold/app/api/chat/route.ts +33 -0
  16. package/templates/stacks/ai-agent/scaffold/app/globals.css +13 -0
  17. package/templates/stacks/ai-agent/scaffold/app/layout.tsx +19 -0
  18. package/templates/stacks/ai-agent/scaffold/app/page.tsx +12 -0
  19. package/templates/stacks/ai-agent/scaffold/evals/cases.json +23 -0
  20. package/templates/stacks/ai-agent/scaffold/lib/openrouter/client.ts +51 -0
  21. package/templates/stacks/ai-agent/scaffold/lib/prompts/system.ts +6 -0
  22. package/templates/stacks/ai-agent/scaffold/lib/supabase/client.ts +10 -0
  23. package/templates/stacks/ai-agent/scaffold/lib/supabase/server.ts +28 -0
  24. package/templates/stacks/ai-agent/scaffold/next.config.mjs +7 -0
  25. package/templates/stacks/ai-agent/scaffold/package.json +29 -0
  26. package/templates/stacks/ai-agent/scaffold/postcss.config.mjs +7 -0
  27. package/templates/stacks/ai-agent/scaffold/supabase/migrations/0001_init.sql +41 -0
  28. package/templates/stacks/ai-agent/scaffold/tsconfig.json +21 -0
  29. package/templates/stacks/ai-agent/stack.json +9 -0
  30. package/templates/stacks/ai-agent/verify-checklist.md +31 -0
  31. package/templates/stacks/full-app/env.required.json +20 -0
  32. package/templates/stacks/full-app/phases.md +45 -0
  33. package/templates/stacks/full-app/scaffold/.env.example +7 -0
  34. package/templates/stacks/full-app/scaffold/README.md +28 -0
  35. package/templates/stacks/full-app/scaffold/app/globals.css +14 -0
  36. package/templates/stacks/full-app/scaffold/app/layout.tsx +19 -0
  37. package/templates/stacks/full-app/scaffold/app/page.tsx +20 -0
  38. package/templates/stacks/full-app/scaffold/lib/supabase/client.ts +10 -0
  39. package/templates/stacks/full-app/scaffold/lib/supabase/server.ts +31 -0
  40. package/templates/stacks/full-app/scaffold/next.config.mjs +7 -0
  41. package/templates/stacks/full-app/scaffold/package.json +29 -0
  42. package/templates/stacks/full-app/scaffold/postcss.config.mjs +7 -0
  43. package/templates/stacks/full-app/scaffold/supabase/migrations/0001_init.sql +27 -0
  44. package/templates/stacks/full-app/scaffold/tsconfig.json +21 -0
  45. package/templates/stacks/full-app/stack.json +9 -0
  46. package/templates/stacks/full-app/verify-checklist.md +32 -0
  47. package/templates/stacks/internal-tool/env.required.json +20 -0
  48. package/templates/stacks/internal-tool/phases.md +45 -0
  49. package/templates/stacks/internal-tool/scaffold/.env.example +7 -0
  50. package/templates/stacks/internal-tool/scaffold/README.md +29 -0
  51. package/templates/stacks/internal-tool/scaffold/app/globals.css +13 -0
  52. package/templates/stacks/internal-tool/scaffold/app/layout.tsx +20 -0
  53. package/templates/stacks/internal-tool/scaffold/app/page.tsx +22 -0
  54. package/templates/stacks/internal-tool/scaffold/lib/supabase/client.ts +9 -0
  55. package/templates/stacks/internal-tool/scaffold/lib/supabase/server.ts +28 -0
  56. package/templates/stacks/internal-tool/scaffold/next.config.mjs +6 -0
  57. package/templates/stacks/internal-tool/scaffold/package.json +29 -0
  58. package/templates/stacks/internal-tool/scaffold/postcss.config.mjs +7 -0
  59. package/templates/stacks/internal-tool/scaffold/supabase/migrations/0001_init.sql +28 -0
  60. package/templates/stacks/internal-tool/scaffold/tsconfig.json +21 -0
  61. package/templates/stacks/internal-tool/stack.json +9 -0
  62. package/templates/stacks/internal-tool/verify-checklist.md +31 -0
  63. package/templates/stacks/landing-page/env.required.json +8 -0
  64. package/templates/stacks/landing-page/phases.md +42 -0
  65. package/templates/stacks/landing-page/scaffold/.env.example +3 -0
  66. package/templates/stacks/landing-page/scaffold/README.md +25 -0
  67. package/templates/stacks/landing-page/scaffold/app/globals.css +14 -0
  68. package/templates/stacks/landing-page/scaffold/app/layout.tsx +19 -0
  69. package/templates/stacks/landing-page/scaffold/app/page.tsx +21 -0
  70. package/templates/stacks/landing-page/scaffold/next.config.mjs +7 -0
  71. package/templates/stacks/landing-page/scaffold/package.json +26 -0
  72. package/templates/stacks/landing-page/scaffold/postcss.config.mjs +7 -0
  73. package/templates/stacks/landing-page/scaffold/tsconfig.json +21 -0
  74. package/templates/stacks/landing-page/stack.json +9 -0
  75. package/templates/stacks/landing-page/verify-checklist.md +28 -0
  76. package/tests/bin.test.sh +3 -3
  77. package/tests/state.test.sh +83 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "ES2022"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": { "@/*": ["./*"] }
18
+ },
19
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
20
+ "exclude": ["node_modules"]
21
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "full-app",
3
+ "title": "Full App",
4
+ "description": "Authenticated product — Supabase auth + Postgres + RLS, Tailwind UI, deployed on Vercel. The default for anything with user accounts and persisted data.",
5
+ "archetype": "web-app",
6
+ "defaultStack": "Next.js 16 (App Router) · Supabase (auth + Postgres + RLS) · Tailwind · Vercel",
7
+ "scaffoldEntry": "app/page.tsx",
8
+ "whenToUse": "Any product with logged-in users, roles, and a database. Pick this when users sign up, data persists per-user, and RLS must isolate tenants."
9
+ }
@@ -0,0 +1,32 @@
1
+ # Full App — Definition of Done
2
+
3
+ Per-stack verify checklist. Inherits `rules/constitution.md` and the `web-app` archetype DoD.
4
+
5
+ ## Auth & Access (constitution — non-negotiable)
6
+ - [ ] Supabase auth with the chosen model; email verification + password reset wired.
7
+ - [ ] **RLS enabled on every table**, policies derived from `app_metadata` (never `user_metadata`).
8
+ - [ ] RLS verified as two+ users — each sees only its own rows.
9
+ - [ ] Every UPDATE policy has a matching SELECT policy.
10
+ - [ ] Postgres views set `security_invoker = true`.
11
+ - [ ] Storage upsert grants INSERT + SELECT + UPDATE on the bucket.
12
+ - [ ] Sessions revoked before a user is deleted.
13
+
14
+ ## Security
15
+ - [ ] `SUPABASE_SERVICE_ROLE_KEY` is server-only — never `NEXT_PUBLIC_`, never imported in a client component.
16
+ - [ ] Mutations use `lib/supabase/server.ts`; client reads use `lib/supabase/client.ts`.
17
+ - [ ] Inputs validated with Zod; no `dangerouslySetInnerHTML`, no `eval()`.
18
+ - [ ] Security headers (HSTS); CSRF/permission checks on every mutation.
19
+
20
+ ## Schema flow
21
+ - [ ] All schema changes are migrations in `supabase/migrations/` — never hand-applied to remote.
22
+ - [ ] `npx supabase gen types` run after schema changes.
23
+
24
+ ## App quality
25
+ - [ ] Every async surface has loading / empty / error states.
26
+ - [ ] Destructive actions confirm; rate limiting on mutating + public endpoints.
27
+ - [ ] No N+1 on list views.
28
+
29
+ ## Build & Deploy
30
+ - [ ] `npx tsc --noEmit` exits 0; `next build` succeeds.
31
+ - [ ] Deploys to Vercel; homepage returns HTTP 200; auth callback responds.
32
+ - [ ] Design anti-slop bar met (DESIGN.md); a11y AA.
@@ -0,0 +1,20 @@
1
+ [
2
+ {
3
+ "name": "NEXT_PUBLIC_SUPABASE_URL",
4
+ "purpose": "Supabase project URL — used by both client and server SDKs.",
5
+ "howToObtain": "Supabase Dashboard → Project Settings → API → Project URL, or `npx supabase projects list`.",
6
+ "ownerIssued": false
7
+ },
8
+ {
9
+ "name": "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY",
10
+ "purpose": "Public (anon/publishable) key for client-side Supabase calls. RLS protects data.",
11
+ "howToObtain": "Supabase Dashboard → Project Settings → API → Publishable/anon key.",
12
+ "ownerIssued": false
13
+ },
14
+ {
15
+ "name": "SUPABASE_SERVICE_ROLE_KEY",
16
+ "purpose": "Server-only service-role key — also used to invite staff users from a server action. NEVER NEXT_PUBLIC_, never in a client component.",
17
+ "howToObtain": "Supabase Dashboard → Project Settings → API → service_role key.",
18
+ "ownerIssued": false
19
+ }
20
+ ]
@@ -0,0 +1,45 @@
1
+ # Internal Tool — Phase Plan
2
+
3
+ Stack: Next.js 16 (App Router) · Supabase (portal auth + RLS) · Tailwind · Vercel.
4
+ Archetype: `web-app` (internal/living product — runs as a rolling release, no terminal Handoff).
5
+ Seed plan copied into ROADMAP.md by `/qualia-new`.
6
+
7
+ ## Phase 1: Foundation, Portal Auth & Roles
8
+
9
+ **Goal:** Stack on a Vercel preview; Supabase wired with RLS on every table; invite-only auth (no public signup); roles in `app_metadata`.
10
+
11
+ **Success criteria:**
12
+ 1. Staff log in (invite / SSO); public signup is disabled.
13
+ 2. Roles stored in `app_metadata`; role-based routing in middleware AND data layer.
14
+ 3. Every table has RLS; verified as two roles — each sees only its scope.
15
+ 4. Deploys to a Vercel preview URL.
16
+
17
+ ## Phase 2: Core Workflows (vertical slices)
18
+
19
+ **Goal:** The primary staff workflows work end-to-end — list, detail, create/edit, with states.
20
+
21
+ **Success criteria:**
22
+ 1. Primary workflow works end-to-end and persists.
23
+ 2. Every async surface has loading / empty / error states.
24
+ 3. Forms validate client AND server; destructive actions confirm + soft-delete where flagged.
25
+ 4. Mutations go through `lib/supabase/server.ts`.
26
+
27
+ ## Phase 3: Admin, Audit & Hardening
28
+
29
+ **Goal:** User management, audit trail, rate limiting, performance.
30
+
31
+ **Success criteria:**
32
+ 1. Admin can invite / deactivate users and assign roles.
33
+ 2. Audit log on sensitive writes; soft-delete where flagged.
34
+ 3. No N+1 on list views; rate limiting on mutating endpoints.
35
+ 4. Every UPDATE policy has a matching SELECT policy.
36
+
37
+ ## Phase 4: Polish & Rolling Release
38
+
39
+ **Goal:** Design pass, a11y, prod deploy. As a living product this repeats per increment — no terminal Handoff.
40
+
41
+ **Success criteria:**
42
+ 1. DESIGN.md anti-slop bar; a11y WCAG 2.2 AA.
43
+ 2. `service_role` server-only; security headers; MFA on accounts.
44
+ 3. Production deploy + smoke (HTTP 200, auth callback, role isolation).
45
+ 4. `/qualia-report` per increment.
@@ -0,0 +1,7 @@
1
+ # Supabase — internal portal. Public signup disabled; staff join by invite.
2
+ NEXT_PUBLIC_SUPABASE_URL=
3
+ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
4
+
5
+ # Server-only — also used to invite staff from a server action.
6
+ # NEVER prefix with NEXT_PUBLIC_, never import into a client component.
7
+ SUPABASE_SERVICE_ROLE_KEY=
@@ -0,0 +1,29 @@
1
+ # Internal Tool
2
+
3
+ Staff-facing portal — Next.js 16 + Supabase (invite-only auth + RLS) + Tailwind + Vercel.
4
+ Public signup is disabled; staff join by invite / SSO. Runs as a rolling release.
5
+
6
+ ## Run
7
+
8
+ ```bash
9
+ npm install
10
+ cp .env.example .env.local # fill Supabase keys
11
+ npx supabase db push
12
+ npm run dev # http://localhost:3000
13
+ ```
14
+
15
+ ## Where things live
16
+
17
+ - `app/page.tsx` — portal entry; reads session + role from `app_metadata`.
18
+ - `lib/supabase/server.ts` — server adapter (mutations, RLS, inviting staff).
19
+ - `lib/supabase/client.ts` — browser adapter (publishable key only).
20
+ - `supabase/migrations/` — domain tables, RLS + role gating from the first migration.
21
+
22
+ ## Non-negotiables (constitution)
23
+
24
+ - Roles live in `app_metadata`, never `user_metadata`.
25
+ - RLS on every table; every UPDATE policy has a matching SELECT.
26
+ - `service_role` server-only; used for inviting users from a server action.
27
+ - Disable public signup in the Supabase Auth settings.
28
+
29
+ This is an MVP skeleton. Build portal auth + workflows per `.planning/ROADMAP.md`.
@@ -0,0 +1,13 @@
1
+ @import "tailwindcss";
2
+
3
+ /* OKLCH design tokens. Source of truth is .planning/DESIGN.md. */
4
+ :root {
5
+ --brand: oklch(0.62 0.18 264);
6
+ --ink: oklch(0.21 0.01 264);
7
+ --paper: oklch(0.99 0.004 264);
8
+ }
9
+
10
+ body {
11
+ background: var(--paper);
12
+ color: var(--ink);
13
+ }
@@ -0,0 +1,20 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "Internal Tool",
6
+ // Internal portal — keep it out of search indexes.
7
+ robots: { index: false, follow: false },
8
+ };
9
+
10
+ export default function RootLayout({
11
+ children,
12
+ }: {
13
+ children: React.ReactNode;
14
+ }) {
15
+ return (
16
+ <html lang="en">
17
+ <body>{children}</body>
18
+ </html>
19
+ );
20
+ }
@@ -0,0 +1,22 @@
1
+ import { createClient } from "@/lib/supabase/server";
2
+
3
+ export default async function Home() {
4
+ const supabase = await createClient();
5
+ const {
6
+ data: { user },
7
+ } = await supabase.auth.getUser();
8
+
9
+ // Role lives in app_metadata (never user_metadata).
10
+ const role = (user?.app_metadata?.role as string | undefined) ?? "none";
11
+
12
+ return (
13
+ <main className="mx-auto flex min-h-dvh max-w-3xl flex-col justify-center gap-6 px-6">
14
+ <h1 className="text-4xl font-semibold tracking-tight">Internal Tool</h1>
15
+ <p className="text-lg text-neutral-600">
16
+ {user
17
+ ? `Signed in as ${user.email} (role: ${role}).`
18
+ : "Staff only — invite required. Wire portal auth + role gating in Phase 1."}
19
+ </p>
20
+ </main>
21
+ );
22
+ }
@@ -0,0 +1,9 @@
1
+ import { createBrowserClient } from "@supabase/ssr";
2
+
3
+ // Client-side Supabase adapter — publishable key only. RLS protects data.
4
+ export function createClient() {
5
+ return createBrowserClient(
6
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
7
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
8
+ );
9
+ }
@@ -0,0 +1,28 @@
1
+ import { createServerClient } from "@supabase/ssr";
2
+ import { cookies } from "next/headers";
3
+
4
+ // Server-side Supabase adapter — all mutations and RLS-protected reads.
5
+ export async function createClient() {
6
+ const cookieStore = await cookies();
7
+
8
+ return createServerClient(
9
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
10
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
11
+ {
12
+ cookies: {
13
+ getAll() {
14
+ return cookieStore.getAll();
15
+ },
16
+ setAll(cookiesToSet) {
17
+ try {
18
+ cookiesToSet.forEach(({ name, value, options }) =>
19
+ cookieStore.set(name, value, options)
20
+ );
21
+ } catch {
22
+ // Server Component context — middleware refreshes the session.
23
+ }
24
+ },
25
+ },
26
+ }
27
+ );
28
+ }
@@ -0,0 +1,6 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ // Internal portal — no public SEO. The root layout sets robots: noindex.
4
+ };
5
+
6
+ export default nextConfig;
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "internal-tool",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "next": "^16.0.0",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0",
16
+ "@supabase/supabase-js": "^2.45.0",
17
+ "@supabase/ssr": "^0.5.0",
18
+ "zod": "^3.23.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.0.0",
22
+ "@types/react": "^19.0.0",
23
+ "@types/react-dom": "^19.0.0",
24
+ "tailwindcss": "^4.0.0",
25
+ "@tailwindcss/postcss": "^4.0.0",
26
+ "postcss": "^8.4.0",
27
+ "typescript": "^5.6.0"
28
+ }
29
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,28 @@
1
+ -- Initial migration. Internal portal: roles gate everything; RLS on every
2
+ -- table from the first migration. Replace `records` with your real domain.
3
+
4
+ create table if not exists public.records (
5
+ id uuid primary key default gen_random_uuid(),
6
+ owner_id uuid references auth.users (id) on delete set null,
7
+ title text not null,
8
+ status text not null default 'open',
9
+ deleted_at timestamptz, -- soft-delete (constitution: where flagged)
10
+ created_at timestamptz not null default now()
11
+ );
12
+
13
+ alter table public.records enable row level security;
14
+
15
+ -- Authenticated staff may read non-deleted records.
16
+ create policy "records_select_staff"
17
+ on public.records for select
18
+ using (auth.role() = 'authenticated' and deleted_at is null);
19
+
20
+ -- Admins (role in app_metadata) may write. Matching SELECT exists above.
21
+ create policy "records_write_admin"
22
+ on public.records for all
23
+ using (
24
+ coalesce(auth.jwt() -> 'app_metadata' ->> 'role', '') = 'admin'
25
+ )
26
+ with check (
27
+ coalesce(auth.jwt() -> 'app_metadata' ->> 'role', '') = 'admin'
28
+ );
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "ES2022"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": { "@/*": ["./*"] }
18
+ },
19
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
20
+ "exclude": ["node_modules"]
21
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "internal-tool",
3
+ "title": "Internal Tool",
4
+ "description": "Staff-facing portal — Supabase auth (invite/SSO, no public signup), role-gated dashboard, Tailwind, Vercel. Built for a known set of internal users.",
5
+ "archetype": "web-app",
6
+ "defaultStack": "Next.js 16 (App Router) · Supabase (portal auth + RLS) · Tailwind · Vercel",
7
+ "scaffoldEntry": "app/page.tsx",
8
+ "whenToUse": "Admin panel, ops dashboard, internal CRM — a known set of staff users, no public signup. Pick this over full-app when the audience is employees, access is invite-only, and roles gate everything."
9
+ }
@@ -0,0 +1,31 @@
1
+ # Internal Tool — Definition of Done
2
+
3
+ Per-stack verify checklist. Inherits `rules/constitution.md` and the `web-app` archetype DoD.
4
+ Internal/living product → runs as a **rolling release**; each increment clears this DoD, no terminal Handoff.
5
+
6
+ ## Portal auth & access (constitution)
7
+ - [ ] Public signup disabled — staff join by invite / SSO only.
8
+ - [ ] Roles stored in `app_metadata` (never `user_metadata`); role-based routing in middleware AND data layer.
9
+ - [ ] **RLS enabled on every table**; verified as two roles — each sees only its scope.
10
+ - [ ] Every UPDATE policy has a matching SELECT policy.
11
+ - [ ] Postgres views set `security_invoker = true`.
12
+ - [ ] Sessions revoked before a user is deleted/deactivated.
13
+
14
+ ## Security
15
+ - [ ] `SUPABASE_SERVICE_ROLE_KEY` server-only; used only in server actions (e.g. inviting users).
16
+ - [ ] Mutations use `lib/supabase/server.ts`; inputs validated with Zod.
17
+ - [ ] Security headers (HSTS); MFA on Supabase/Vercel accounts.
18
+
19
+ ## App quality
20
+ - [ ] Loading / empty / error states on every async surface.
21
+ - [ ] Destructive actions confirm; soft-delete + audit where flagged.
22
+ - [ ] Rate limiting on mutating endpoints; no N+1 on list views.
23
+
24
+ ## Schema flow
25
+ - [ ] All schema changes are migrations in `supabase/migrations/`.
26
+ - [ ] `npx supabase gen types` run after schema changes.
27
+
28
+ ## Build & Deploy
29
+ - [ ] `npx tsc --noEmit` exits 0; `next build` succeeds.
30
+ - [ ] Deploys to Vercel; HTTP 200; auth callback + role isolation verified.
31
+ - [ ] Design anti-slop bar met; a11y AA.
@@ -0,0 +1,8 @@
1
+ [
2
+ {
3
+ "name": "NEXT_PUBLIC_ANALYTICS_ID",
4
+ "purpose": "Optional analytics/measurement id for the marketing site (e.g. Plausible domain, GA4 id). The landing page runs without it.",
5
+ "howToObtain": "Create a property in your analytics provider and copy the site/measurement id. Skip if you are not tracking conversions yet.",
6
+ "ownerIssued": false
7
+ }
8
+ ]
@@ -0,0 +1,42 @@
1
+ # Landing Page — Phase Plan
2
+
3
+ Stack: Next.js 16 (App Router, SSG) · Tailwind · Vercel. No database, no auth.
4
+ Archetype: `website`. This is the seed plan `/qualia-new` copies into ROADMAP.md.
5
+
6
+ ## Phase 1: Foundation & Design System
7
+
8
+ **Goal:** The site renders on a Vercel preview URL with the design system wired (tokens, fonts, OKLCH palette) and a base layout (nav + footer).
9
+
10
+ **Success criteria:**
11
+ 1. `npm run dev` renders the homepage with no console errors.
12
+ 2. DESIGN.md tokens are live (CSS variables, distinctive type pair — never Inter/Arial/system-ui).
13
+ 3. Nav + footer render and are responsive at 375px and 1440px.
14
+ 4. Deploys to a Vercel preview URL.
15
+
16
+ ## Phase 2: Hero & Core Sections
17
+
18
+ **Goal:** The above-the-fold hero plus the primary value sections (features, social proof) are built with real copy.
19
+
20
+ **Success criteria:**
21
+ 1. Hero communicates the offer in one glance; primary CTA is present and reachable.
22
+ 2. Feature/benefit sections render with real copy — no lorem ipsum.
23
+ 3. All sections responsive, with loading-free SSG output.
24
+
25
+ ## Phase 3: Conversion & Contact
26
+
27
+ **Goal:** The conversion path works — CTA → contact form or external link, with success/error states.
28
+
29
+ **Success criteria:**
30
+ 1. Contact form (or CTA link) submits and shows a success state; errors are handled.
31
+ 2. If a form posts anywhere, it validates client-side (Zod or equivalent) — no DB needed.
32
+ 3. Analytics event fires on the primary CTA.
33
+
34
+ ## Phase 4: Polish & Launch
35
+
36
+ **Goal:** Production-ready — SEO meta, a11y, performance, custom domain.
37
+
38
+ **Success criteria:**
39
+ 1. SEO: title/description/OG tags per page; sitemap; favicon.
40
+ 2. a11y: alt text, labels, keyboard nav, AA contrast.
41
+ 3. Lighthouse performance/SEO green on the homepage.
42
+ 4. Custom domain wired; production deploy + smoke (HTTP 200).
@@ -0,0 +1,3 @@
1
+ # Landing Page — no database, no auth. Nothing required to run locally.
2
+ # Optional analytics (uncomment if you wire it):
3
+ # NEXT_PUBLIC_ANALYTICS_ID=
@@ -0,0 +1,25 @@
1
+ # Landing Page
2
+
3
+ Static/SSG marketing site — Next.js 16 + Tailwind + Vercel. No database, no auth.
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev # http://localhost:3000
10
+ ```
11
+
12
+ ## Ship
13
+
14
+ ```bash
15
+ npm run build
16
+ vercel --prod
17
+ ```
18
+
19
+ ## Where things live
20
+
21
+ - `app/page.tsx` — the landing page itself (replace the hero copy).
22
+ - `app/globals.css` — OKLCH design tokens. Source of truth is `.planning/DESIGN.md`.
23
+ - `.env.example` — nothing required; copy to `.env.local` only if you add analytics.
24
+
25
+ This is an MVP skeleton. Build the real sections per `.planning/ROADMAP.md`.
@@ -0,0 +1,14 @@
1
+ @import "tailwindcss";
2
+
3
+ /* Define your OKLCH palette here. Neutrals tinted toward the brand hue.
4
+ NEVER raw #000 / #fff. See .planning/DESIGN.md for the committed tokens. */
5
+ :root {
6
+ --brand: oklch(0.62 0.18 264);
7
+ --ink: oklch(0.21 0.01 264);
8
+ --paper: oklch(0.99 0.004 264);
9
+ }
10
+
11
+ body {
12
+ background: var(--paper);
13
+ color: var(--ink);
14
+ }
@@ -0,0 +1,19 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "Landing Page",
6
+ description: "Replace with your product's one-sentence pitch.",
7
+ };
8
+
9
+ export default function RootLayout({
10
+ children,
11
+ }: {
12
+ children: React.ReactNode;
13
+ }) {
14
+ return (
15
+ <html lang="en">
16
+ <body>{children}</body>
17
+ </html>
18
+ );
19
+ }
@@ -0,0 +1,21 @@
1
+ export default function Home() {
2
+ return (
3
+ <main className="mx-auto flex min-h-dvh max-w-3xl flex-col justify-center gap-6 px-6">
4
+ <h1 className="text-balance text-4xl font-semibold tracking-tight sm:text-6xl">
5
+ Your one-sentence pitch goes here.
6
+ </h1>
7
+ <p className="text-lg text-neutral-600">
8
+ Replace this hero with real copy. No lorem ipsum — the page is the
9
+ product.
10
+ </p>
11
+ <div>
12
+ <a
13
+ href="#contact"
14
+ className="inline-flex h-11 items-center rounded-md bg-neutral-900 px-6 font-medium text-white"
15
+ >
16
+ Primary CTA
17
+ </a>
18
+ </div>
19
+ </main>
20
+ );
21
+ }
@@ -0,0 +1,7 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ // Marketing site: keep it static where possible. Add `output: "export"`
4
+ // if the host is purely static (no Vercel server runtime needed).
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "landing-page",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "next": "^16.0.0",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.0.0",
19
+ "@types/react": "^19.0.0",
20
+ "@types/react-dom": "^19.0.0",
21
+ "tailwindcss": "^4.0.0",
22
+ "@tailwindcss/postcss": "^4.0.0",
23
+ "postcss": "^8.4.0",
24
+ "typescript": "^5.6.0"
25
+ }
26
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "ES2022"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": { "@/*": ["./*"] }
18
+ },
19
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
20
+ "exclude": ["node_modules"]
21
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "landing-page",
3
+ "title": "Landing Page",
4
+ "description": "A static/SSG marketing site — hero, features, CTA, contact. No database, no auth. Built to convert, fast to ship.",
5
+ "archetype": "website",
6
+ "defaultStack": "Next.js 16 (App Router, SSG) · Tailwind · Vercel",
7
+ "scaffoldEntry": "app/page.tsx",
8
+ "whenToUse": "Marketing site, product landing, portfolio, throwaway prototype. Pick this when there is no logged-in user and no persistent data — the page IS the product."
9
+ }