qualia-framework 6.8.1 → 6.9.2

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 (85) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/install.js +184 -9
  3. package/bin/report-payload.js +12 -0
  4. package/bin/state.js +70 -5
  5. package/docs/EMPLOYEE-QUICKSTART.md +164 -0
  6. package/docs/erp-contract.md +10 -1
  7. package/docs/qualia-manual.html +396 -0
  8. package/hooks/session-start.js +24 -4
  9. package/hooks/usage-capture.js +108 -0
  10. package/package.json +3 -1
  11. package/skills/qualia-doctor/SKILL.md +62 -0
  12. package/skills/qualia-new/REFERENCE.md +7 -0
  13. package/skills/qualia-new/SKILL.md +42 -0
  14. package/skills/qualia-report/SKILL.md +16 -0
  15. package/templates/planning.gitignore +3 -0
  16. package/templates/stacks/README.md +110 -0
  17. package/templates/stacks/ai-agent/env.required.json +44 -0
  18. package/templates/stacks/ai-agent/phases.md +53 -0
  19. package/templates/stacks/ai-agent/scaffold/.env.example +12 -0
  20. package/templates/stacks/ai-agent/scaffold/README.md +31 -0
  21. package/templates/stacks/ai-agent/scaffold/app/api/chat/route.ts +33 -0
  22. package/templates/stacks/ai-agent/scaffold/app/globals.css +13 -0
  23. package/templates/stacks/ai-agent/scaffold/app/layout.tsx +19 -0
  24. package/templates/stacks/ai-agent/scaffold/app/page.tsx +12 -0
  25. package/templates/stacks/ai-agent/scaffold/evals/cases.json +23 -0
  26. package/templates/stacks/ai-agent/scaffold/lib/openrouter/client.ts +51 -0
  27. package/templates/stacks/ai-agent/scaffold/lib/prompts/system.ts +6 -0
  28. package/templates/stacks/ai-agent/scaffold/lib/supabase/client.ts +10 -0
  29. package/templates/stacks/ai-agent/scaffold/lib/supabase/server.ts +28 -0
  30. package/templates/stacks/ai-agent/scaffold/next.config.mjs +7 -0
  31. package/templates/stacks/ai-agent/scaffold/package.json +29 -0
  32. package/templates/stacks/ai-agent/scaffold/postcss.config.mjs +7 -0
  33. package/templates/stacks/ai-agent/scaffold/supabase/migrations/0001_init.sql +41 -0
  34. package/templates/stacks/ai-agent/scaffold/tsconfig.json +21 -0
  35. package/templates/stacks/ai-agent/stack.json +9 -0
  36. package/templates/stacks/ai-agent/verify-checklist.md +31 -0
  37. package/templates/stacks/full-app/env.required.json +20 -0
  38. package/templates/stacks/full-app/phases.md +45 -0
  39. package/templates/stacks/full-app/scaffold/.env.example +7 -0
  40. package/templates/stacks/full-app/scaffold/README.md +28 -0
  41. package/templates/stacks/full-app/scaffold/app/globals.css +14 -0
  42. package/templates/stacks/full-app/scaffold/app/layout.tsx +19 -0
  43. package/templates/stacks/full-app/scaffold/app/page.tsx +20 -0
  44. package/templates/stacks/full-app/scaffold/lib/supabase/client.ts +10 -0
  45. package/templates/stacks/full-app/scaffold/lib/supabase/server.ts +31 -0
  46. package/templates/stacks/full-app/scaffold/next.config.mjs +7 -0
  47. package/templates/stacks/full-app/scaffold/package.json +29 -0
  48. package/templates/stacks/full-app/scaffold/postcss.config.mjs +7 -0
  49. package/templates/stacks/full-app/scaffold/supabase/migrations/0001_init.sql +27 -0
  50. package/templates/stacks/full-app/scaffold/tsconfig.json +21 -0
  51. package/templates/stacks/full-app/stack.json +9 -0
  52. package/templates/stacks/full-app/verify-checklist.md +32 -0
  53. package/templates/stacks/internal-tool/env.required.json +20 -0
  54. package/templates/stacks/internal-tool/phases.md +45 -0
  55. package/templates/stacks/internal-tool/scaffold/.env.example +7 -0
  56. package/templates/stacks/internal-tool/scaffold/README.md +29 -0
  57. package/templates/stacks/internal-tool/scaffold/app/globals.css +13 -0
  58. package/templates/stacks/internal-tool/scaffold/app/layout.tsx +20 -0
  59. package/templates/stacks/internal-tool/scaffold/app/page.tsx +22 -0
  60. package/templates/stacks/internal-tool/scaffold/lib/supabase/client.ts +9 -0
  61. package/templates/stacks/internal-tool/scaffold/lib/supabase/server.ts +28 -0
  62. package/templates/stacks/internal-tool/scaffold/next.config.mjs +6 -0
  63. package/templates/stacks/internal-tool/scaffold/package.json +29 -0
  64. package/templates/stacks/internal-tool/scaffold/postcss.config.mjs +7 -0
  65. package/templates/stacks/internal-tool/scaffold/supabase/migrations/0001_init.sql +28 -0
  66. package/templates/stacks/internal-tool/scaffold/tsconfig.json +21 -0
  67. package/templates/stacks/internal-tool/stack.json +9 -0
  68. package/templates/stacks/internal-tool/verify-checklist.md +31 -0
  69. package/templates/stacks/landing-page/env.required.json +8 -0
  70. package/templates/stacks/landing-page/phases.md +42 -0
  71. package/templates/stacks/landing-page/scaffold/.env.example +3 -0
  72. package/templates/stacks/landing-page/scaffold/README.md +25 -0
  73. package/templates/stacks/landing-page/scaffold/app/globals.css +14 -0
  74. package/templates/stacks/landing-page/scaffold/app/layout.tsx +19 -0
  75. package/templates/stacks/landing-page/scaffold/app/page.tsx +21 -0
  76. package/templates/stacks/landing-page/scaffold/next.config.mjs +7 -0
  77. package/templates/stacks/landing-page/scaffold/package.json +26 -0
  78. package/templates/stacks/landing-page/scaffold/postcss.config.mjs +7 -0
  79. package/templates/stacks/landing-page/scaffold/tsconfig.json +21 -0
  80. package/templates/stacks/landing-page/stack.json +9 -0
  81. package/templates/stacks/landing-page/verify-checklist.md +28 -0
  82. package/tests/bin.test.sh +8 -7
  83. package/tests/hooks.test.sh +32 -0
  84. package/tests/install-smoke.test.sh +4 -3
  85. package/tests/state.test.sh +83 -0
@@ -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
+ }
@@ -0,0 +1,28 @@
1
+ # Landing Page — Definition of Done
2
+
3
+ Per-stack verify checklist. Inherits `rules/constitution.md` and the `website` archetype DoD.
4
+
5
+ ## Build & Deploy
6
+ - [ ] `npx tsc --noEmit` exits 0.
7
+ - [ ] `next build` succeeds; output is static/SSG (no accidental server-only data fetch on a marketing page).
8
+ - [ ] Deploys to Vercel; homepage returns HTTP 200.
9
+ - [ ] Custom domain wired (production).
10
+
11
+ ## Design (anti-slop)
12
+ - [ ] DESIGN.md committed; distinctive type pair (NOT Inter/Roboto/Arial/Helvetica/system-ui).
13
+ - [ ] OKLCH palette; neutrals tinted toward brand hue; no raw `#000`/`#fff`.
14
+ - [ ] 8px spacing grid + fluid `clamp()` page padding.
15
+ - [ ] Responsive at 375px and 1440px; touch targets ≥44px.
16
+
17
+ ## Content & Conversion
18
+ - [ ] Real copy — zero "lorem ipsum", zero placeholder text.
19
+ - [ ] Primary CTA present, reachable, and instrumented with an analytics event.
20
+ - [ ] Contact form (if any) validates client-side and has success + error states.
21
+
22
+ ## SEO & a11y
23
+ - [ ] Per-page title + meta description + OG tags; sitemap + favicon present.
24
+ - [ ] Alt text on every image; labels on every input; keyboard navigable.
25
+ - [ ] AA contrast verified.
26
+
27
+ ## Out of scope (by design)
28
+ - No Supabase, no auth, no `SUPABASE_SERVICE_ROLE_KEY`. If the project needs accounts or persisted data, it is a `full-app`, not a `landing-page`.
package/tests/bin.test.sh CHANGED
@@ -487,14 +487,15 @@ else
487
487
  fail_case "CLAUDE.md role substitution"
488
488
  fi
489
489
 
490
- # 31. All 13 hooks installed (block-env-edit removed in v3.2.0;
490
+ # 31. All 14 hooks installed (block-env-edit removed in v3.2.0;
491
491
  # git-guardrails + stop-session-log added in v4.2.0;
492
492
  # vercel-account-guard + env-empty-guard + supabase-destructive-guard added in v5.0.0;
493
493
  # fawzi-approval-guard added in v6.2.11; pre-compact removed in v6.2.0 and
494
- # REINTRODUCED in v6.3.2 with sidecar-snapshot mechanism)
494
+ # REINTRODUCED in v6.3.2 with sidecar-snapshot mechanism;
495
+ # usage-capture added in v6.9.1 — UserPromptSubmit telemetry capture)
495
496
  HOOK_COUNT=$(ls "$TMP/.claude/hooks/"*.js 2>/dev/null | wc -l)
496
- if [ "$HOOK_COUNT" -eq 13 ]; then
497
- pass "13 hooks installed in hooks/ (incl. pre-compact v6.3.2)"
497
+ if [ "$HOOK_COUNT" -eq 14 ]; then
498
+ pass "14 hooks installed in hooks/ (incl. usage-capture v6.9.1)"
498
499
  else
499
500
  fail_case "hook count" "got $HOOK_COUNT"
500
501
  fi
@@ -1466,10 +1467,10 @@ mkdir -p "$TMP/.codex"
1466
1467
  echo "OLD USER CONTENT" > "$TMP/.codex/AGENTS.md"
1467
1468
  printf 'QS-FAWZI-11\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
1468
1469
  EXIT=$?
1469
- BAK_COUNT=$(ls "$TMP/.codex/"AGENTS.md.bak.* 2>/dev/null | wc -l)
1470
+ BAK_COUNT=$(ls "$TMP/.codex/backups/"AGENTS.md.bak.* 2>/dev/null | wc -l)
1470
1471
  if [ "$EXIT" -eq 0 ] \
1471
1472
  && [ "$BAK_COUNT" -ge 1 ] \
1472
- && grep -q "OLD USER CONTENT" "$TMP/.codex/"AGENTS.md.bak.* \
1473
+ && grep -q "OLD USER CONTENT" "$TMP/.codex/backups/"AGENTS.md.bak.* \
1473
1474
  && ! grep -q "OLD USER CONTENT" "$TMP/.codex/AGENTS.md"; then
1474
1475
  pass "Codex AGENTS.md backup-before-overwrite preserves prior content"
1475
1476
  else
@@ -1482,7 +1483,7 @@ TMP=$(mktmp)
1482
1483
  printf 'QS-FAWZI-11\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log.txt" 2>&1
1483
1484
  # Re-run with same input — content should be identical, no new backup.
1484
1485
  printf 'QS-FAWZI-11\n2\n' | HOME="$TMP" $NODE "$INSTALL_JS" > "$TMP/log2.txt" 2>&1
1485
- BAK_COUNT=$(ls "$TMP/.codex/"AGENTS.md.bak.* 2>/dev/null | wc -l)
1486
+ BAK_COUNT=$(ls "$TMP/.codex/backups/"AGENTS.md.bak.* 2>/dev/null | wc -l)
1486
1487
  if [ "$BAK_COUNT" -eq 0 ]; then
1487
1488
  pass "Codex re-install with identical content → no redundant .bak"
1488
1489
  else
@@ -420,6 +420,38 @@ else
420
420
  fi
421
421
  rm -rf "$TMP"
422
422
 
423
+ # --- session-start.js — stale update banner self-clears (regression: v6.9.1) ---
424
+ # A notif file whose advertised `latest` is already installed must NOT render a
425
+ # false "update available" banner; it must be deleted. A genuinely-newer notif
426
+ # must be preserved.
427
+ QH=$(mktemp -d)
428
+ mkdir -p "$QH/bin"
429
+ echo 'process.exit(0)' > "$QH/bin/qualia-ui.js" # dummy UI so render path is reachable
430
+ echo '{"code":"QS-FAWZI-11","version":"6.9.0"}' > "$QH/.qualia-config.json"
431
+
432
+ # Case 1: installed (6.9.0) >= notif.latest (6.9.0) → stale, must self-clear
433
+ echo '{"current":"6.8.1","latest":"6.9.0","detected_at":"t"}' > "$QH/.qualia-update-available.json"
434
+ (cd "$QH" && QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/session-start.js" >/dev/null 2>&1)
435
+ if [ ! -f "$QH/.qualia-update-available.json" ]; then
436
+ echo " ✓ stale update notice self-clears when version caught up"
437
+ PASS=$((PASS + 1))
438
+ else
439
+ echo " ✗ stale update notice was NOT cleared"
440
+ FAIL=$((FAIL + 1))
441
+ fi
442
+
443
+ # Case 2: installed (6.9.0) < notif.latest (9.9.9) → genuine, must persist
444
+ echo '{"current":"6.9.0","latest":"9.9.9","detected_at":"t"}' > "$QH/.qualia-update-available.json"
445
+ (cd "$QH" && QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/session-start.js" >/dev/null 2>&1)
446
+ if [ -f "$QH/.qualia-update-available.json" ]; then
447
+ echo " ✓ genuine update notice is preserved"
448
+ PASS=$((PASS + 1))
449
+ else
450
+ echo " ✗ genuine update notice was wrongly cleared"
451
+ FAIL=$((FAIL + 1))
452
+ fi
453
+ rm -rf "$QH"
454
+
423
455
  # pre-compact.js removed in v6.2.0 — state.js journal provides crash safety.
424
456
 
425
457
  # --- auto-update.js ---
@@ -124,10 +124,11 @@ else
124
124
  fi
125
125
 
126
126
  if [ -d "$HOME_DIR/.claude/hooks" ] \
127
- && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "13" ] \
127
+ && [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "14" ] \
128
128
  && [ -f "$HOME_DIR/.claude/hooks/fawzi-approval-guard.js" ] \
129
- && [ -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
130
- pass "packaged install has 13 hooks including pre-compact (v6.3.2 sidecar snapshot)"
129
+ && [ -f "$HOME_DIR/.claude/hooks/pre-compact.js" ] \
130
+ && [ -f "$HOME_DIR/.claude/hooks/usage-capture.js" ]; then
131
+ pass "packaged install has 14 hooks including usage-capture (v6.9.1 telemetry)"
131
132
  else
132
133
  fail_case "packaged hook set mismatch"
133
134
  fi