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.
- package/CHANGELOG.md +32 -0
- package/bin/install.js +184 -9
- package/bin/report-payload.js +12 -0
- package/bin/state.js +70 -5
- package/docs/EMPLOYEE-QUICKSTART.md +164 -0
- package/docs/erp-contract.md +10 -1
- package/docs/qualia-manual.html +396 -0
- package/hooks/session-start.js +24 -4
- package/hooks/usage-capture.js +108 -0
- package/package.json +3 -1
- package/skills/qualia-doctor/SKILL.md +62 -0
- package/skills/qualia-new/REFERENCE.md +7 -0
- package/skills/qualia-new/SKILL.md +42 -0
- package/skills/qualia-report/SKILL.md +16 -0
- package/templates/planning.gitignore +3 -0
- package/templates/stacks/README.md +110 -0
- package/templates/stacks/ai-agent/env.required.json +44 -0
- package/templates/stacks/ai-agent/phases.md +53 -0
- package/templates/stacks/ai-agent/scaffold/.env.example +12 -0
- package/templates/stacks/ai-agent/scaffold/README.md +31 -0
- package/templates/stacks/ai-agent/scaffold/app/api/chat/route.ts +33 -0
- package/templates/stacks/ai-agent/scaffold/app/globals.css +13 -0
- package/templates/stacks/ai-agent/scaffold/app/layout.tsx +19 -0
- package/templates/stacks/ai-agent/scaffold/app/page.tsx +12 -0
- package/templates/stacks/ai-agent/scaffold/evals/cases.json +23 -0
- package/templates/stacks/ai-agent/scaffold/lib/openrouter/client.ts +51 -0
- package/templates/stacks/ai-agent/scaffold/lib/prompts/system.ts +6 -0
- package/templates/stacks/ai-agent/scaffold/lib/supabase/client.ts +10 -0
- package/templates/stacks/ai-agent/scaffold/lib/supabase/server.ts +28 -0
- package/templates/stacks/ai-agent/scaffold/next.config.mjs +7 -0
- package/templates/stacks/ai-agent/scaffold/package.json +29 -0
- package/templates/stacks/ai-agent/scaffold/postcss.config.mjs +7 -0
- package/templates/stacks/ai-agent/scaffold/supabase/migrations/0001_init.sql +41 -0
- package/templates/stacks/ai-agent/scaffold/tsconfig.json +21 -0
- package/templates/stacks/ai-agent/stack.json +9 -0
- package/templates/stacks/ai-agent/verify-checklist.md +31 -0
- package/templates/stacks/full-app/env.required.json +20 -0
- package/templates/stacks/full-app/phases.md +45 -0
- package/templates/stacks/full-app/scaffold/.env.example +7 -0
- package/templates/stacks/full-app/scaffold/README.md +28 -0
- package/templates/stacks/full-app/scaffold/app/globals.css +14 -0
- package/templates/stacks/full-app/scaffold/app/layout.tsx +19 -0
- package/templates/stacks/full-app/scaffold/app/page.tsx +20 -0
- package/templates/stacks/full-app/scaffold/lib/supabase/client.ts +10 -0
- package/templates/stacks/full-app/scaffold/lib/supabase/server.ts +31 -0
- package/templates/stacks/full-app/scaffold/next.config.mjs +7 -0
- package/templates/stacks/full-app/scaffold/package.json +29 -0
- package/templates/stacks/full-app/scaffold/postcss.config.mjs +7 -0
- package/templates/stacks/full-app/scaffold/supabase/migrations/0001_init.sql +27 -0
- package/templates/stacks/full-app/scaffold/tsconfig.json +21 -0
- package/templates/stacks/full-app/stack.json +9 -0
- package/templates/stacks/full-app/verify-checklist.md +32 -0
- package/templates/stacks/internal-tool/env.required.json +20 -0
- package/templates/stacks/internal-tool/phases.md +45 -0
- package/templates/stacks/internal-tool/scaffold/.env.example +7 -0
- package/templates/stacks/internal-tool/scaffold/README.md +29 -0
- package/templates/stacks/internal-tool/scaffold/app/globals.css +13 -0
- package/templates/stacks/internal-tool/scaffold/app/layout.tsx +20 -0
- package/templates/stacks/internal-tool/scaffold/app/page.tsx +22 -0
- package/templates/stacks/internal-tool/scaffold/lib/supabase/client.ts +9 -0
- package/templates/stacks/internal-tool/scaffold/lib/supabase/server.ts +28 -0
- package/templates/stacks/internal-tool/scaffold/next.config.mjs +6 -0
- package/templates/stacks/internal-tool/scaffold/package.json +29 -0
- package/templates/stacks/internal-tool/scaffold/postcss.config.mjs +7 -0
- package/templates/stacks/internal-tool/scaffold/supabase/migrations/0001_init.sql +28 -0
- package/templates/stacks/internal-tool/scaffold/tsconfig.json +21 -0
- package/templates/stacks/internal-tool/stack.json +9 -0
- package/templates/stacks/internal-tool/verify-checklist.md +31 -0
- package/templates/stacks/landing-page/env.required.json +8 -0
- package/templates/stacks/landing-page/phases.md +42 -0
- package/templates/stacks/landing-page/scaffold/.env.example +3 -0
- package/templates/stacks/landing-page/scaffold/README.md +25 -0
- package/templates/stacks/landing-page/scaffold/app/globals.css +14 -0
- package/templates/stacks/landing-page/scaffold/app/layout.tsx +19 -0
- package/templates/stacks/landing-page/scaffold/app/page.tsx +21 -0
- package/templates/stacks/landing-page/scaffold/next.config.mjs +7 -0
- package/templates/stacks/landing-page/scaffold/package.json +26 -0
- package/templates/stacks/landing-page/scaffold/postcss.config.mjs +7 -0
- package/templates/stacks/landing-page/scaffold/tsconfig.json +21 -0
- package/templates/stacks/landing-page/stack.json +9 -0
- package/templates/stacks/landing-page/verify-checklist.md +28 -0
- package/tests/bin.test.sh +8 -7
- package/tests/hooks.test.sh +32 -0
- package/tests/install-smoke.test.sh +4 -3
- 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,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,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,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,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,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
|
|
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
|
|
497
|
-
pass "
|
|
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
|
package/tests/hooks.test.sh
CHANGED
|
@@ -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 ' ')" = "
|
|
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" ]
|
|
130
|
-
|
|
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
|