@donotdev/cli 0.0.14 → 0.0.16
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/dependencies-matrix.json +372 -88
- package/dist/bin/commands/agent-setup.js +7 -1
- package/dist/bin/commands/build.js +141 -44
- package/dist/bin/commands/bump.js +81 -41
- package/dist/bin/commands/cacheout.js +37 -9
- package/dist/bin/commands/create-app.js +276 -121
- package/dist/bin/commands/create-project.js +506 -217
- package/dist/bin/commands/deploy.js +1785 -694
- package/dist/bin/commands/dev.js +177 -43
- package/dist/bin/commands/doctor.d.ts +6 -0
- package/dist/bin/commands/doctor.d.ts.map +1 -0
- package/dist/bin/commands/{lint.js → doctor.js} +1215 -156
- package/dist/bin/commands/doctor.js.map +1 -0
- package/dist/bin/commands/emu.js +451 -104
- package/dist/bin/commands/format.js +37 -9
- package/dist/bin/commands/make-admin.js +77499 -11
- package/dist/bin/commands/preview.js +181 -43
- package/dist/bin/commands/setup.d.ts +6 -0
- package/dist/bin/commands/setup.d.ts.map +1 -0
- package/dist/bin/commands/setup.js +11733 -0
- package/dist/bin/commands/setup.js.map +1 -0
- package/dist/bin/commands/supabase-setup.d.ts +6 -0
- package/dist/bin/commands/supabase-setup.d.ts.map +1 -0
- package/dist/bin/commands/supabase-setup.js +7 -0
- package/dist/bin/commands/supabase-setup.js.map +1 -0
- package/dist/bin/commands/sync-secrets.js +211 -34
- package/dist/bin/commands/type-check.d.ts +14 -0
- package/dist/bin/commands/type-check.d.ts.map +1 -0
- package/dist/bin/commands/type-check.js +2049 -0
- package/dist/bin/commands/type-check.js.map +1 -0
- package/dist/bin/commands/wai.js +3 -1
- package/dist/bin/dndev.js +73 -52
- package/dist/bin/donotdev.js +54 -45
- package/dist/index.js +4212 -3050
- package/package.json +3 -3
- package/templates/app-demo/src/App.tsx.example +1 -0
- package/templates/app-demo/src/pages/FullPage.tsx.example +2 -2
- package/templates/app-demo/src/pages/components/DemoLayout.tsx.example +2 -2
- package/templates/app-demo/src/themes.css.example +5 -12
- package/templates/app-expo/.env.example +44 -0
- package/templates/app-expo/.expo/README.md.example +5 -0
- package/templates/app-expo/.gitignore.example +36 -0
- package/templates/app-expo/README.md.example +58 -0
- package/templates/app-expo/app/.gitkeep +2 -0
- package/templates/app-expo/app/_layout.tsx.example +41 -0
- package/templates/app-expo/app/form.tsx.example +52 -0
- package/templates/app-expo/app/index.tsx.example +89 -0
- package/templates/app-expo/app/list.tsx.example +32 -0
- package/templates/app-expo/app/profile.tsx.example +76 -0
- package/templates/app-expo/app/signin.tsx.example +53 -0
- package/templates/app-expo/app.json.example +39 -0
- package/templates/app-expo/assets/adaptive-icon.png +0 -0
- package/templates/app-expo/assets/favicon.png +0 -0
- package/templates/app-expo/assets/icon.png +0 -0
- package/templates/app-expo/assets/splash.png +0 -0
- package/templates/app-expo/babel.config.js.example +10 -0
- package/templates/app-expo/eas.json.example +20 -0
- package/templates/app-expo/expo-env.d.ts.example +4 -0
- package/templates/app-expo/metro.config.js.example +20 -0
- package/templates/app-expo/service-account-key.json.example +12 -0
- package/templates/app-expo/src/config/app.ts.example +46 -0
- package/templates/app-expo/src/config/providers.ts.example +7 -0
- package/templates/app-expo/tsconfig.json.example +19 -0
- package/templates/app-next/.env.example +4 -33
- package/templates/app-next/src/app/ClientLayout.tsx.example +2 -0
- package/templates/app-next/src/app/layout.tsx.example +7 -6
- package/templates/app-next/src/config/providers.ts.example +7 -0
- package/templates/app-next/src/globals.css.example +2 -11
- package/templates/app-next/src/pages/HomePage.tsx.example +1 -1
- package/templates/app-next/src/themes.css.example +10 -13
- package/templates/app-vite/.env.example +3 -32
- package/templates/app-vite/index.html.example +2 -24
- package/templates/app-vite/src/App.tsx.example +2 -0
- package/templates/app-vite/src/config/providers.ts.example +7 -0
- package/templates/app-vite/src/globals.css.example +2 -12
- package/templates/app-vite/src/pages/FormPageExample.tsx.example +1 -2
- package/templates/app-vite/src/pages/HomePage.tsx.example +2 -2
- package/templates/app-vite/src/themes.css.example +109 -79
- package/templates/app-vite/vercel.json.example +11 -0
- package/templates/functions-firebase/README.md.example +1 -1
- package/templates/functions-firebase/build.mjs.example +2 -72
- package/templates/functions-firebase/functions-firebase/.env.example.example +24 -26
- package/templates/functions-firebase/functions-firebase/README.md.example +1 -1
- package/templates/functions-firebase/functions-firebase/build.mjs.example +2 -72
- package/templates/functions-firebase/functions-firebase/tsconfig.json.example +1 -1
- package/templates/functions-firebase/functions.config.js.example +1 -1
- package/templates/functions-supabase/supabase/config.toml.example +59 -0
- package/templates/functions-supabase/supabase/functions/.env.example +13 -0
- package/templates/functions-supabase/supabase/functions/cancel-subscription/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/functions/change-plan/index.ts.example +11 -0
- package/templates/functions-supabase/supabase/functions/create-checkout-session/index.ts.example +11 -0
- package/templates/functions-supabase/supabase/functions/create-customer-portal/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/functions/crud/index.ts.example +16 -0
- package/templates/functions-supabase/supabase/functions/delete-account/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/functions/deno.json.example +8 -0
- package/templates/functions-supabase/supabase/functions/get-custom-claims/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/functions/get-user-auth-status/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/functions/refresh-subscription-status/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/functions/remove-custom-claims/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/functions/set-custom-claims/index.ts.example +7 -0
- package/templates/functions-supabase/supabase/migrations/20250101000000_idempotency.sql +24 -0
- package/templates/functions-supabase/supabase/migrations/20250101000001_rate_limits.sql +22 -0
- package/templates/functions-supabase/supabase/migrations/20250101000002_cleanup_jobs.sql +28 -0
- package/templates/functions-supabase/supabase/migrations/20250101000003_operation_metrics.sql +28 -0
- package/templates/functions-vercel/functions-vercel/tsconfig.json.example +1 -1
- package/templates/functions-vercel/functions-vercel/vercel.json.example +1 -1
- package/templates/functions-vercel/vercel.json.example +1 -1
- package/templates/github/github/workflows/firebase-deploy.yml.example +1 -1
- package/templates/github/workflows/firebase-deploy.yml.example +1 -1
- package/templates/overlay-firebase/env.fragment.example +34 -0
- package/templates/overlay-firebase/env.fragment.expo.example +34 -0
- package/templates/overlay-firebase/env.fragment.nextjs.example +34 -0
- package/templates/overlay-firebase/src/config/providers.expo.ts.example +49 -0
- package/templates/overlay-firebase/src/config/providers.ts.example +23 -0
- package/templates/overlay-supabase/env.fragment.example +12 -0
- package/templates/overlay-supabase/env.fragment.expo.example +12 -0
- package/templates/overlay-supabase/env.fragment.nextjs.example +12 -0
- package/templates/overlay-supabase/src/config/providers.expo.ts.example +35 -0
- package/templates/overlay-supabase/src/config/providers.ts.example +33 -0
- package/templates/overlay-supabase/vercel.headers.example +23 -0
- package/templates/overlay-supabase/vercel.json.example +22 -0
- package/templates/overlay-vercel/env.fragment.example +34 -0
- package/templates/overlay-vercel/env.fragment.nextjs.example +34 -0
- package/templates/overlay-vercel/src/config/providers.ts.example +24 -0
- package/templates/root-consumer/.claude/agents/architect.md.example +2 -310
- package/templates/root-consumer/.claude/agents/builder.md.example +2 -326
- package/templates/root-consumer/.claude/agents/coder.md.example +2 -83
- package/templates/root-consumer/.claude/agents/extractor.md.example +2 -231
- package/templates/root-consumer/.claude/agents/polisher.md.example +2 -132
- package/templates/root-consumer/.claude/agents/prompt-engineer.md.example +2 -81
- package/templates/root-consumer/.claude/commands/grill.md.example +30 -0
- package/templates/root-consumer/.claude/commands/techdebt.md.example +28 -0
- package/templates/root-consumer/.clinerules.example +1 -0
- package/templates/root-consumer/.cursor/rules/no-docs.mdc.example +15 -0
- package/templates/root-consumer/.cursorrules.example +1 -0
- package/templates/root-consumer/.github/copilot-instructions.md.example +1 -0
- package/templates/root-consumer/.windsurfrules.example +1 -0
- package/templates/root-consumer/AI.md.example +44 -123
- package/templates/root-consumer/CLAUDE.md.example +1 -134
- package/templates/root-consumer/CONVENTIONS.md.example +1 -0
- package/templates/root-consumer/GEMINI.md.example +1 -0
- package/templates/root-consumer/firebase.json.example +1 -1
- package/templates/root-consumer/guides/dndev/AGENT_START_HERE.md.example +22 -2
- package/templates/root-consumer/guides/dndev/COMPONENTS_ADV.md.example +0 -18
- package/templates/root-consumer/guides/dndev/COMPONENTS_UI.md.example +1 -1
- package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +101 -32
- package/templates/root-consumer/guides/dndev/INDEX.md.example +4 -2
- package/templates/root-consumer/guides/dndev/SETUP_APP_CONFIG.md.example +3 -3
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +241 -12
- package/templates/root-consumer/guides/dndev/SETUP_FIREBASE.md.example +13 -7
- package/templates/root-consumer/guides/dndev/SETUP_OAUTH_PROVIDERS.md.example +60 -0
- package/templates/root-consumer/guides/dndev/SETUP_SOC2.md.example +234 -0
- package/templates/root-consumer/guides/dndev/SETUP_STRIPE.md.example +62 -0
- package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +124 -0
- package/templates/root-consumer/guides/dndev/SETUP_THEMES.md.example +6 -2
- package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +176 -0
- package/templates/root-consumer/guides/dndev/USE_ROUTING.md.example +5 -9
- package/templates/root-consumer/guides/dndev/essences_reference.css.example +174 -0
- package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +7 -8
- package/templates/root-consumer/guides/wai-way/agents/builder.md.example +10 -0
- package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +25 -5
- package/templates/root-consumer/guides/wai-way/agents/polisher.md.example +13 -2
- package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +2 -2
- package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +55 -15
- package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +15 -4
- package/templates/root-consumer/guides/wai-way/spec_template.md.example +7 -6
- package/dist/bin/commands/lint.d.ts +0 -11
- package/dist/bin/commands/lint.d.ts.map +0 -1
- package/dist/bin/commands/lint.js.map +0 -1
- package/dist/bin/commands/staging.d.ts +0 -11
- package/dist/bin/commands/staging.d.ts.map +0 -1
- package/dist/bin/commands/staging.js +0 -12
- package/dist/bin/commands/staging.js.map +0 -1
- package/templates/app-payload/.env.example +0 -28
- package/templates/app-payload/README.md.example +0 -233
- package/templates/app-payload/collections/Company.ts.example +0 -125
- package/templates/app-payload/collections/Hero.ts.example +0 -62
- package/templates/app-payload/collections/Media.ts.example +0 -41
- package/templates/app-payload/collections/Products.ts.example +0 -115
- package/templates/app-payload/collections/Services.ts.example +0 -104
- package/templates/app-payload/collections/Testimonials.ts.example +0 -92
- package/templates/app-payload/collections/Users.ts.example +0 -35
- package/templates/app-payload/src/server.ts.example +0 -79
- package/templates/app-payload/tsconfig.json.example +0 -24
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Stripe Setup Guide
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
- Stripe account (https://dashboard.stripe.com)
|
|
5
|
+
- `@donotdev/billing` installed
|
|
6
|
+
|
|
7
|
+
## 1. Get API Keys
|
|
8
|
+
|
|
9
|
+
1. Go to https://dashboard.stripe.com/apikeys
|
|
10
|
+
2. Copy your **Publishable key** (`pk_test_...`) → `.env`
|
|
11
|
+
3. Copy your **Secret key** (`sk_test_...`) → `supabase/functions/.env` or `functions/.env`
|
|
12
|
+
|
|
13
|
+
> **Security:** Never commit secret keys. Stripe also supports restricted keys (`rk_test_...`, `rk_live_...`) for tighter permissions.
|
|
14
|
+
|
|
15
|
+
## 2. Configure Webhook
|
|
16
|
+
|
|
17
|
+
1. Go to https://dashboard.stripe.com/webhooks
|
|
18
|
+
2. Click **Add endpoint**
|
|
19
|
+
3. Set endpoint URL:
|
|
20
|
+
- Supabase: `https://{{PROJECT_ID}}.supabase.co/functions/v1/stripe-webhook`
|
|
21
|
+
- Firebase: `https://europe-west1-{{PROJECT_ID}}.cloudfunctions.net/stripeWebhook` (replace `europe-west1` with your region if different)
|
|
22
|
+
4. Select events:
|
|
23
|
+
- `checkout.session.completed`
|
|
24
|
+
- `customer.subscription.created`
|
|
25
|
+
- `customer.subscription.updated`
|
|
26
|
+
- `customer.subscription.deleted`
|
|
27
|
+
- `invoice.payment_succeeded`
|
|
28
|
+
- `invoice.payment_failed`
|
|
29
|
+
5. Copy the **Signing secret** (`whsec_...`) → `supabase/functions/.env`
|
|
30
|
+
|
|
31
|
+
## 3. Environment Variables
|
|
32
|
+
|
|
33
|
+
### App `.env` (public)
|
|
34
|
+
```
|
|
35
|
+
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Functions `.env` (secret)
|
|
39
|
+
```
|
|
40
|
+
STRIPE_SECRET_KEY=sk_test_...
|
|
41
|
+
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 4. Test Clocks (Optional)
|
|
45
|
+
|
|
46
|
+
For subscription testing without waiting for real billing cycles:
|
|
47
|
+
1. Go to https://dashboard.stripe.com/test/test-clocks
|
|
48
|
+
2. Create a test clock
|
|
49
|
+
3. Create customers attached to the test clock
|
|
50
|
+
4. Advance time to trigger billing events
|
|
51
|
+
|
|
52
|
+
## 5. Verify
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
dndev setup stripe # Interactive key setup + validation
|
|
56
|
+
dndev doctor # Health check for all providers
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Check that:
|
|
60
|
+
- Publishable key format is valid
|
|
61
|
+
- Secret key format is valid
|
|
62
|
+
- Key modes match (both test or both live)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Setup: Supabase
|
|
2
|
+
|
|
3
|
+
**From zero to a working Supabase backend: env, tables, RLS, and adapter behavior.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Step 1: Run Supabase Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
dndev setup supabase
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This command:
|
|
14
|
+
- Lets you choose the target app (if you have an `apps/` directory)
|
|
15
|
+
- Asks for your **public** Supabase project URL and anon key
|
|
16
|
+
- Writes `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` to your app's `.env`
|
|
17
|
+
|
|
18
|
+
**We only ask for public credentials** (safe to ship in your client bundle). We never ask for the service_role key.
|
|
19
|
+
|
|
20
|
+
Get URL and anon key from: [Supabase Dashboard](https://supabase.com/dashboard) → your project → **Settings → API**.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Step 2: Generate Tables from Entities
|
|
25
|
+
|
|
26
|
+
The framework can generate PostgreSQL migrations from your entity definitions (same source as the schema used by the app).
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
dn generate sql
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**What it does:**
|
|
33
|
+
- Discovers entities (e.g. in `entities/` or your configured `--entity-dir`)
|
|
34
|
+
- For each entity: `CREATE TABLE` with columns mapped from field types (text, number, boolean, timestamptz, uuid, jsonb, etc.)
|
|
35
|
+
- Adds technical columns: `id` (uuid, default `gen_random_uuid()`), `user_id`, `created_at`, `updated_at` (with `DEFAULT now()`), `created_by_id`, `updated_by_id`, `status`
|
|
36
|
+
- Enables **Row Level Security (RLS)** and creates policies so rows are scoped by `auth.uid() = user_id`
|
|
37
|
+
- Adds a trigger so `updated_at` is set automatically on every `UPDATE`
|
|
38
|
+
|
|
39
|
+
**Options (optional):**
|
|
40
|
+
- `--entity-dir <path>` — where to find entity files (default: from app root)
|
|
41
|
+
- `--output-dir <path>` — where to write migrations (default: `supabase/migrations`)
|
|
42
|
+
- `--no-single-file` — one migration file per entity instead of one combined file
|
|
43
|
+
|
|
44
|
+
Output is written to `supabase/migrations/` (or your `--output-dir`) as a timestamped `.sql` file. Apply it with the Supabase CLI or Dashboard SQL editor.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Step 3: Apply Migrations
|
|
49
|
+
|
|
50
|
+
After generating SQL:
|
|
51
|
+
|
|
52
|
+
**Option A — Supabase CLI (recommended)**
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
supabase db push
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
(or `supabase migration up` if you manage migrations locally)
|
|
59
|
+
|
|
60
|
+
**Option B — Dashboard**
|
|
61
|
+
|
|
62
|
+
Copy the contents of the generated migration file into the SQL Editor in the Supabase Dashboard and run it.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Step 4: Adapter Behavior (DB-Managed Timestamps)
|
|
67
|
+
|
|
68
|
+
Tables use **snake_case** column names and **timestamptz** for `created_at` / `updated_at`. The framework expects **camelCase** and **ISO date strings** in the app.
|
|
69
|
+
|
|
70
|
+
The **Supabase CRUD adapter** handles this automatically:
|
|
71
|
+
|
|
72
|
+
| Direction | Behavior |
|
|
73
|
+
|-----------|----------|
|
|
74
|
+
| **Read** (get, query, subscribe) | Rows from Supabase are normalized: snake_case → camelCase (e.g. `created_at` → `createdAt`), and timestamp columns are converted to ISO strings. Your UI receives the same shape as with Firebase. |
|
|
75
|
+
| **Write** (add, set, update) | The adapter does **not** send `createdAt` / `updatedAt` (or snake equivalents). The database sets them via `DEFAULT now()` and the `updated_at` trigger. |
|
|
76
|
+
|
|
77
|
+
So you never set timestamps in app code when using Supabase — the DB owns them.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Environment Variables
|
|
82
|
+
|
|
83
|
+
**Client (Vite):** in `apps/<app>/.env`
|
|
84
|
+
|
|
85
|
+
| Variable | Purpose |
|
|
86
|
+
|----------|---------|
|
|
87
|
+
| `VITE_SUPABASE_URL` | Project URL (public) |
|
|
88
|
+
| `VITE_SUPABASE_ANON_KEY` | Anon key (public, safe in bundle) |
|
|
89
|
+
|
|
90
|
+
**Server (e.g. API routes, Edge Functions):** use the same URL and `SUPABASE_SERVICE_ROLE_KEY` for admin operations. Never expose the service_role key to the client. Put it in `functions/.env` or your host’s env (Vercel, etc.).
|
|
91
|
+
|
|
92
|
+
See [ENV_SETUP.md](./ENV_SETUP.md) for where to put secrets.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Storage (Optional)
|
|
97
|
+
|
|
98
|
+
If your app uploads files, create a storage bucket in the Supabase Dashboard (e.g. `uploads`). The default bucket name used by the framework is `uploads`. Configure public or RLS policies in the Dashboard as needed.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Hosting the frontend
|
|
103
|
+
|
|
104
|
+
Supabase gives you **Auth, Postgres, Storage, and Edge Functions**. It does **not** host your built frontend (Vite/Next SPA). You need a separate host for the `dist/` output.
|
|
105
|
+
|
|
106
|
+
**We recommend:**
|
|
107
|
+
|
|
108
|
+
- **Vercel** — Connect your repo, set `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` in the project env, then deploy. Good fit for Next.js or Vite.
|
|
109
|
+
- We scaffold **vercel.json**; run `dndev deploy` and choose Frontend (Vercel) or Frontend + Edge Functions. Set `VITE_SUPABASE_*` in Vercel project env.
|
|
110
|
+
|
|
111
|
+
**Deploy:** Frontend goes to Vercel (scaffolded vercel.json); Edge Functions to Supabase. It does **not** deploy your Supabase app’s frontend to Vercel/Netlify. For that, use the host’s dashboard or CLI (e.g. `vercel`, `netlify deploy`) after building. See [ENV_SETUP.md](./ENV_SETUP.md) for production env vars on Vercel.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Local Development
|
|
116
|
+
|
|
117
|
+
- **Against hosted Supabase:** After `dndev setup supabase`, run `bun dev` — the app talks to your Supabase project.
|
|
118
|
+
- **Local Supabase:** Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and run `supabase start` for a local Postgres + Auth + Storage stack. Point `VITE_SUPABASE_URL` and keys to the local instance.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Summary
|
|
123
|
+
|
|
124
|
+
**`dndev setup supabase`** → paste URL + anon key → **`dn generate sql`** → apply migrations → **`bun dev`**. The adapter normalizes read (snake→camel, ISO) and leaves timestamps to the DB on write.
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Setup: Themes
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Single source of truth:** `src/themes.css`. Import it from `globals.css`; do not set font/color overrides in globals. Framework handles theme switching.
|
|
4
|
+
|
|
5
|
+
**Default essence = SaaS** (Inter, neutral). Optional essences (Brutalist, Luxury) are in the scaffold; they do **not** apply until you set the class on `<html>` (e.g. `class="brutalist"`) or use the theme switcher.
|
|
6
|
+
|
|
7
|
+
**Reference:** Copy Brutalist/Luxury blocks from `guides/dndev/essences_reference.css` into your `src/themes.css` if you need them. Default-essence fonts (Inter, Space Grotesk, Playfair, Roboto) are bundled via `@donotdev/ui`; no Google Fonts, no `public/fonts/` required.
|
|
4
8
|
|
|
5
9
|
---
|
|
6
10
|
|
|
7
11
|
## Standard Use
|
|
8
12
|
|
|
9
|
-
**File:** `src/themes.css` (scaffolded with
|
|
13
|
+
**File:** `src/themes.css` (scaffolded with light, dark, and optional Brutalist/Luxury)
|
|
10
14
|
|
|
11
15
|
**Override colors:**
|
|
12
16
|
```css
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Setup: Vercel
|
|
2
|
+
|
|
3
|
+
**From zero to deployed: Vercel hosting + API routes with Firebase data layer.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
Vercel is your **hosting and API platform** — it serves your frontend and runs serverless API routes.
|
|
10
|
+
Firebase is your **data layer** — Firestore (CRUD), Firebase Auth (users), Firebase Storage (files).
|
|
11
|
+
|
|
12
|
+
The framework generates API routes as Vercel Serverless Functions that talk to Firebase on the backend.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Step 1: Create Firebase Project (Data Layer)
|
|
17
|
+
|
|
18
|
+
Even though you deploy to Vercel, you still need Firebase for data.
|
|
19
|
+
|
|
20
|
+
1. Go to [Firebase Console](https://console.firebase.google.com) → Create a project
|
|
21
|
+
2. Enable **Authentication** → Email/Password (+ OAuth providers if needed)
|
|
22
|
+
3. Enable **Cloud Firestore** → Create Database → select region
|
|
23
|
+
4. Enable **Storage** if your app uploads files
|
|
24
|
+
|
|
25
|
+
Get the Firebase web config from: **Project Settings → General → Your apps → Web app → SDK config**.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Step 2: Run Setup
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dndev setup firebase
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This writes Firebase SDK config to your app's `.env`. The `overlay-vercel` providers.ts initializes the Firebase client SDK.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Step 3: Configure Vercel
|
|
40
|
+
|
|
41
|
+
1. Create a [Vercel](https://vercel.com) account and link your Git repo
|
|
42
|
+
2. Import the project in Vercel Dashboard
|
|
43
|
+
3. Set the **Root Directory** to `apps/<your-app>` (or leave blank if monorepo auto-detected)
|
|
44
|
+
4. Set **Framework Preset** to Vite (or Next.js if using Next)
|
|
45
|
+
|
|
46
|
+
**Environment Variables** (in Vercel Dashboard → Settings → Environment Variables):
|
|
47
|
+
|
|
48
|
+
Copy your Firebase vars from `.env`:
|
|
49
|
+
- `VITE_FIREBASE_API_KEY`
|
|
50
|
+
- `VITE_FIREBASE_PROJECT_ID`
|
|
51
|
+
- `VITE_FIREBASE_AUTH_DOMAIN`
|
|
52
|
+
- `VITE_FIREBASE_STORAGE_BUCKET`
|
|
53
|
+
- `VITE_FIREBASE_MESSAGING_SENDER_ID`
|
|
54
|
+
- `VITE_FIREBASE_APP_ID`
|
|
55
|
+
|
|
56
|
+
For Next.js apps, use `NEXT_PUBLIC_` prefix instead of `VITE_`.
|
|
57
|
+
|
|
58
|
+
**Server secrets** (for API routes):
|
|
59
|
+
- `STRIPE_SECRET_KEY`
|
|
60
|
+
- `STRIPE_WEBHOOK_SECRET`
|
|
61
|
+
- Any OAuth client secrets
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Step 4: API Routes (Functions)
|
|
66
|
+
|
|
67
|
+
Your backend functions are in `functions/` and deploy as Vercel Serverless Functions.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
functions/
|
|
71
|
+
├── src/
|
|
72
|
+
│ ├── auth/ # Auth endpoints (signup, login, etc.)
|
|
73
|
+
│ ├── billing/ # Stripe endpoints (checkout, webhook, etc.)
|
|
74
|
+
│ ├── crud/ # CRUD endpoints (create, read, update, delete)
|
|
75
|
+
│ └── oauth/ # OAuth callback handlers
|
|
76
|
+
├── vercel.json # Route configuration
|
|
77
|
+
├── tsconfig.json
|
|
78
|
+
└── package.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Functions use the Firebase Admin SDK on the server side to access Firestore, verify auth tokens, etc.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Step 5: Deploy
|
|
86
|
+
|
|
87
|
+
**Option A — Git push (recommended)**
|
|
88
|
+
|
|
89
|
+
Push to your connected branch. Vercel auto-deploys.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git push origin main
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Option B — Vercel CLI**
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx vercel --prod
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Environment Variables
|
|
104
|
+
|
|
105
|
+
| File | What Goes Here | Loaded By |
|
|
106
|
+
|------|---------------|-----------|
|
|
107
|
+
| `apps/<app>/.env` | Firebase config, license key, Stripe publishable key | Vite/Next.js (dev + build) |
|
|
108
|
+
| `apps/<app>/.env.local` | Local overrides (gitignored) | Vite/Next.js (overrides .env) |
|
|
109
|
+
| `apps/<app>/.env.production` | Production overrides | Vite/Next.js (build --mode production) |
|
|
110
|
+
| `functions/.env` | Server secrets: STRIPE_SECRET_KEY, OAuth secrets | API routes runtime |
|
|
111
|
+
| Vercel Dashboard | All production env vars (client + server) | Vercel runtime |
|
|
112
|
+
|
|
113
|
+
**Client vars** (browser-safe): `VITE_*` or `NEXT_PUBLIC_*` prefix.
|
|
114
|
+
**Server vars** (secrets): No prefix needed in Vercel Dashboard — only accessible in API routes.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Local Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
bun dev
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The app runs locally, talking to your Firebase project. API routes can be tested with:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
vercel dev
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Or use Firebase emulators for fully local development:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
dndev emu start
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Secrets
|
|
139
|
+
|
|
140
|
+
Server-side secrets go in `functions/.env` locally and in Vercel Dashboard for production.
|
|
141
|
+
|
|
142
|
+
**We NEVER ask for secret keys.** You place them yourself:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# functions/.env
|
|
146
|
+
STRIPE_SECRET_KEY=sk_live_...
|
|
147
|
+
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Then add the same values in Vercel Dashboard → Settings → Environment Variables.
|
|
151
|
+
|
|
152
|
+
See [ENV_SETUP.md → Secrets Philosophy](./ENV_SETUP.md#secrets-philosophy) for the full policy.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Troubleshooting
|
|
157
|
+
|
|
158
|
+
**"Firebase config not loading"**
|
|
159
|
+
→ Check `.env` is in your **app directory** (`apps/<app>/.env`), not repo root
|
|
160
|
+
→ Vite vars must start with `VITE_`, Next.js vars with `NEXT_PUBLIC_`
|
|
161
|
+
|
|
162
|
+
**"401 / Permission denied on API routes"**
|
|
163
|
+
→ Check Firebase service account key is configured in Vercel env vars
|
|
164
|
+
→ Verify `GOOGLE_APPLICATION_CREDENTIALS` or inline credentials in API routes
|
|
165
|
+
|
|
166
|
+
**"CORS error"**
|
|
167
|
+
→ Vercel handles CORS for same-origin requests automatically
|
|
168
|
+
→ For cross-origin: add CORS headers in your API route handler
|
|
169
|
+
|
|
170
|
+
**"Build fails on Vercel"**
|
|
171
|
+
→ Check Root Directory is set to your app directory
|
|
172
|
+
→ Ensure `package.json` has correct `build` script
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
**`dndev setup firebase` → configure Vercel project → set env vars → `git push`. Vercel deploys automatically.**
|
|
@@ -226,18 +226,14 @@ navigate('/products', {
|
|
|
226
226
|
|
|
227
227
|
### 5. Query Parameters
|
|
228
228
|
|
|
229
|
-
**✅ Use framework useSearchParams:**
|
|
229
|
+
**✅ Use framework useSearchParams (read-only, returns URLSearchParams directly):**
|
|
230
230
|
```tsx
|
|
231
231
|
import { useSearchParams } from '@donotdev/ui/routing';
|
|
232
232
|
|
|
233
233
|
function ProductList() {
|
|
234
|
-
const
|
|
234
|
+
const searchParams = useSearchParams();
|
|
235
235
|
const page = searchParams.get('page') || '1';
|
|
236
|
-
|
|
237
|
-
const handlePageChange = (newPage: string) => {
|
|
238
|
-
setSearchParams({ page: newPage });
|
|
239
|
-
};
|
|
240
|
-
|
|
236
|
+
|
|
241
237
|
return <div>Page: {page}</div>;
|
|
242
238
|
}
|
|
243
239
|
```
|
|
@@ -478,8 +474,8 @@ navigate('/products', { replace: true });
|
|
|
478
474
|
// Route params
|
|
479
475
|
const id = useRouteParam('id');
|
|
480
476
|
|
|
481
|
-
// Query params
|
|
482
|
-
const
|
|
477
|
+
// Query params (read-only, returns URLSearchParams directly)
|
|
478
|
+
const searchParams = useSearchParams();
|
|
483
479
|
const page = searchParams.get('page');
|
|
484
480
|
|
|
485
481
|
// Navigation menu
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference: Framework essences (Brutalist, Luxury).
|
|
3
|
+
* Copy the blocks you need into your app's src/themes.css.
|
|
4
|
+
* Default essence = SaaS (Inter); these do not apply until you set the class on <html>.
|
|
5
|
+
*
|
|
6
|
+
* Fonts: Space Grotesk (Brutalist), Playfair Display (Luxury), Inter, Roboto are bundled in @donotdev/ui (no external requests).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** =========================================================================
|
|
10
|
+
* Brutalist — Industrial, monospace body, Space Grotesk headlines, orange/black.
|
|
11
|
+
* Set <html class="brutalist"> to apply.
|
|
12
|
+
* ========================================================================= */
|
|
13
|
+
.brutalist {
|
|
14
|
+
--theme-icon: 'Construction';
|
|
15
|
+
--theme-label: 'Brutalist';
|
|
16
|
+
--theme-is-dark: 1;
|
|
17
|
+
|
|
18
|
+
/* 1. Core Colors - Black + white + industrial orange */
|
|
19
|
+
--background: #000000;
|
|
20
|
+
--foreground: #ffffff;
|
|
21
|
+
--primary: #f97316;
|
|
22
|
+
--secondary: #ffffff;
|
|
23
|
+
--accent: #f97316;
|
|
24
|
+
|
|
25
|
+
/* 2. Semantic */
|
|
26
|
+
--success: #22c55e;
|
|
27
|
+
--warning: #f97316;
|
|
28
|
+
--destructive: #dc2626;
|
|
29
|
+
|
|
30
|
+
/* 3. Surfaces - Dark, industrial */
|
|
31
|
+
--muted: #111111;
|
|
32
|
+
--muted-foreground: #888888;
|
|
33
|
+
--border: #ffffff;
|
|
34
|
+
--input: #111111;
|
|
35
|
+
--ring: #f97316;
|
|
36
|
+
--card: #000000;
|
|
37
|
+
--card-foreground: #f97316;
|
|
38
|
+
--popover: #000000;
|
|
39
|
+
--popover-foreground: #ffffff;
|
|
40
|
+
--surface-1: #0a0a0a;
|
|
41
|
+
|
|
42
|
+
/* 4. Text on Colors */
|
|
43
|
+
--primary-foreground: #ffffff;
|
|
44
|
+
--secondary-foreground: #000000;
|
|
45
|
+
--accent-foreground: #000000;
|
|
46
|
+
--success-foreground: #ffffff;
|
|
47
|
+
--warning-foreground: #000000;
|
|
48
|
+
--destructive-foreground: #ffffff;
|
|
49
|
+
|
|
50
|
+
/* 5. Radius - Hard zero */
|
|
51
|
+
--radius-interactive: 0;
|
|
52
|
+
--radius-surface: 0;
|
|
53
|
+
--radius-floating: 0;
|
|
54
|
+
|
|
55
|
+
/* 6. Typography - Monospace for body, Space Grotesk 700 for headings */
|
|
56
|
+
--font-family: var(--font-mono);
|
|
57
|
+
--font-headline: "Space Grotesk", var(--font-sans);
|
|
58
|
+
--font-weight-semibold: 700;
|
|
59
|
+
--font-weight-bold: 700;
|
|
60
|
+
|
|
61
|
+
/* 7. Borders */
|
|
62
|
+
--border-width: 2px;
|
|
63
|
+
--border-huge: 4px;
|
|
64
|
+
|
|
65
|
+
/* 8. Shadows — flat by default, hard offset on interactive */
|
|
66
|
+
--shadow-color: var(--foreground);
|
|
67
|
+
--shadow-sm: none;
|
|
68
|
+
--shadow-md: none;
|
|
69
|
+
--shadow-xl: 8px 8px 0px 0px var(--shadow-color);
|
|
70
|
+
--shadow-cta: 20px 20px 0px 0px var(--shadow-color);
|
|
71
|
+
|
|
72
|
+
/* 9. Per-variant shadows — only at xl level (elevated/clickable) */
|
|
73
|
+
--shadow-primary: none;
|
|
74
|
+
--shadow-primary-xl: 8px 8px 0px 0px var(--background);
|
|
75
|
+
--shadow-secondary: none;
|
|
76
|
+
--shadow-secondary-xl: 8px 8px 0px 0px var(--primary);
|
|
77
|
+
|
|
78
|
+
/* 10. Header padding for hard-offset shadows */
|
|
79
|
+
--header-shadow-padding: 0.75rem;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Body - grid background */
|
|
83
|
+
.brutalist body {
|
|
84
|
+
background-color: var(--background);
|
|
85
|
+
background-image:
|
|
86
|
+
linear-gradient(#111 1px, transparent 1px),
|
|
87
|
+
linear-gradient(90deg, #111 1px, transparent 1px);
|
|
88
|
+
background-size: 40px 40px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Uppercase headings (both native elements and Text component levels) */
|
|
92
|
+
.brutalist {
|
|
93
|
+
:is(h1, h2, h3, h4, h5, h6),
|
|
94
|
+
[data-level^='h'] {
|
|
95
|
+
text-transform: uppercase;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Flat surface - no gradient glow */
|
|
100
|
+
.brutalist .dndev-surface { background: var(--card); }
|
|
101
|
+
.brutalist .dndev-surface[data-variant='outline'] { border: var(--border-width) solid var(--border); }
|
|
102
|
+
|
|
103
|
+
.brutalist .dndev-card[data-variant='outline'] {
|
|
104
|
+
background: transparent;
|
|
105
|
+
box-shadow: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
/** =========================================================================
|
|
110
|
+
* Luxury — Playfair Display headlines, gold/cream, warm shadows.
|
|
111
|
+
* Set <html class="luxury"> to apply.
|
|
112
|
+
* ========================================================================= */
|
|
113
|
+
.luxury {
|
|
114
|
+
--theme-icon: 'Gem';
|
|
115
|
+
--theme-label: 'Luxury';
|
|
116
|
+
--theme-is-dark: 0;
|
|
117
|
+
|
|
118
|
+
/* 1. Core Colors */
|
|
119
|
+
--background: #faf8f5;
|
|
120
|
+
--foreground: #1c1917;
|
|
121
|
+
--primary: #b45309;
|
|
122
|
+
--secondary: #fef3c7;
|
|
123
|
+
--accent: #92400e;
|
|
124
|
+
|
|
125
|
+
/* 2. Semantic */
|
|
126
|
+
--success: #15803d;
|
|
127
|
+
--warning: #b45309;
|
|
128
|
+
--destructive: #b91c1c;
|
|
129
|
+
|
|
130
|
+
/* 3. Surfaces */
|
|
131
|
+
--muted: #fef9c3;
|
|
132
|
+
--muted-foreground: #78716c;
|
|
133
|
+
--border: #e7e5e4;
|
|
134
|
+
--border-hairline: 1px;
|
|
135
|
+
--input: #ffffff;
|
|
136
|
+
--ring: #b45309;
|
|
137
|
+
--card: #ffffff;
|
|
138
|
+
--card-foreground: #1c1917;
|
|
139
|
+
--popover: #ffffff;
|
|
140
|
+
--popover-foreground: #1c1917;
|
|
141
|
+
--surface-1: #faf8f5;
|
|
142
|
+
|
|
143
|
+
/* 4. Text on Colors */
|
|
144
|
+
--primary-foreground: #ffffff;
|
|
145
|
+
--secondary-foreground: #1c1917;
|
|
146
|
+
--accent-foreground: #ffffff;
|
|
147
|
+
--success-foreground: #ffffff;
|
|
148
|
+
--warning-foreground: #1c1917;
|
|
149
|
+
--destructive-foreground: #ffffff;
|
|
150
|
+
|
|
151
|
+
/* 5. Radius */
|
|
152
|
+
--radius-interactive: 0.375rem;
|
|
153
|
+
--radius-surface: 0.5rem;
|
|
154
|
+
--radius-floating: 0.5rem;
|
|
155
|
+
|
|
156
|
+
/* 6. Typography — lighter weights, serif headlines */
|
|
157
|
+
--font-family: var(--font-sans);
|
|
158
|
+
--font-headline: 'Playfair Display', var(--font-serif);
|
|
159
|
+
--font-weight-normal: 300;
|
|
160
|
+
--font-weight-medium: 400;
|
|
161
|
+
--font-weight-semibold: 500;
|
|
162
|
+
--font-weight-bold: 600;
|
|
163
|
+
|
|
164
|
+
/* 7. Spacing — generous, one notch above expressive */
|
|
165
|
+
--gap-sm: 0.75rem;
|
|
166
|
+
--gap-md: 1.5rem;
|
|
167
|
+
--gap-lg: 3rem;
|
|
168
|
+
|
|
169
|
+
/* 8. Shadows — warm gold-tinted, soft diffused */
|
|
170
|
+
--shadow-color: color-mix(in oklab, #b45309 8%, transparent);
|
|
171
|
+
--shadow-sm: 0 1px 3px var(--shadow-color);
|
|
172
|
+
--shadow-md: 0 4px 12px var(--shadow-color);
|
|
173
|
+
--shadow-xl: 0 8px 24px var(--shadow-color);
|
|
174
|
+
}
|
|
@@ -168,18 +168,17 @@ Present completed spec summary:
|
|
|
168
168
|
**READ:**
|
|
169
169
|
- `guides/wai-way/page_patterns.md` - Page structure patterns
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
# Create new app
|
|
173
|
-
dndev create-app my-app --preset [from spec]
|
|
171
|
+
**`dndev` is an installed CLI — run directly, never via `bunx` or `npx`.**
|
|
174
172
|
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
```bash
|
|
174
|
+
# Create new app (interactive wizard — no --preset flag, it prompts you)
|
|
175
|
+
dndev create-app
|
|
177
176
|
|
|
178
|
-
# Start emulators
|
|
177
|
+
# Start emulators (if Firebase backend was selected)
|
|
179
178
|
dndev emu start
|
|
180
179
|
|
|
181
|
-
#
|
|
182
|
-
|
|
180
|
+
# Start the dev server
|
|
181
|
+
dndev dev
|
|
183
182
|
```
|
|
184
183
|
|
|
185
184
|
### Step 1.1: Create Page Files
|
|
@@ -31,6 +31,10 @@ core_principles:
|
|
|
31
31
|
- HARDCODE strings first - validate UX before i18n
|
|
32
32
|
- Use framework components only - no custom CSS
|
|
33
33
|
- Trust component defaults
|
|
34
|
+
- **70/30 Hierarchy:** Use `primary` variant for the North Star action; `outline`/`ghost` for others.
|
|
35
|
+
- **Benefit-First Copy:** For Heros/Cards, translate technical features into outcomes (e.g., 'Optimize Your Fleet' vs 'Manage Cars').
|
|
36
|
+
- **Success Intent:** Trust framework defaults for CRUD (automatic toasts/loaders). Only propose custom redirects or celebratory components for the 'North Star' action or if the user asks for more polish.
|
|
37
|
+
- **Spec Drift:** If you must deviate from the spec (field types, logic), log it in `spec_changes.md`. Do NOT modify `spec_template.md` directly.
|
|
34
38
|
|
|
35
39
|
crud_pattern:
|
|
36
40
|
list_page: |
|
|
@@ -83,5 +87,11 @@ Rules:
|
|
|
83
87
|
3. HARDCODE all strings (no i18n yet)
|
|
84
88
|
4. Use framework components only
|
|
85
89
|
|
|
90
|
+
**Apply UX Mandates:**
|
|
91
|
+
- **Visual Anchor:** Every page must have ONE clear primary focus (Hero or Main Card).
|
|
92
|
+
- **Mobile First:** Ensure all touch targets are > 44px.
|
|
93
|
+
- **Kano Filter:** Use 'Benefit' copy for marketing/dashboard pages. Use 'Utility' copy for forms.
|
|
94
|
+
- **Success Intent:** Trust framework defaults. Only propose custom redirects/celebration for the 'North Star' action.
|
|
95
|
+
|
|
86
96
|
Build each page following the scaffolded patterns.
|
|
87
97
|
```
|