From 857ef1a994b2b9ad7cd7ae5525c7800a29649960 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 16 Sep 2026 09:44:19 +0200 Subject: [PATCH] chore(seed): demo client, project, users and cards --- ...-09-16-phase-1b-design-system-and-board.md | 7 +- scripts/seed-demo.ts | 156 ++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 scripts/seed-demo.ts diff --git a/docs/superpowers/plans/2026-09-16-phase-1b-design-system-and-board.md b/docs/superpowers/plans/2026-09-16-phase-1b-design-system-and-board.md index aa16dbd..20d2417 100644 --- a/docs/superpowers/plans/2026-09-16-phase-1b-design-system-and-board.md +++ b/docs/superpowers/plans/2026-09-16-phase-1b-design-system-and-board.md @@ -42,6 +42,7 @@ src/ │ ├── globals.css # REWRITE — token system │ ├── layout.tsx # REWRITE — font, theme, no-flash │ ├── page.tsx # REWRITE — role-aware home +│ ├── api/owner/cards/[id]/move/route.ts # owner drag persistence │ └── p/[clientSlug]/[projectSlug]/ │ └── page.tsx # board route (server component) ├── components/ @@ -2200,6 +2201,8 @@ git add src/components/board git commit -m "feat(board): refresh the board when the window regains focus" ``` +--- + ## Task 13: Full verification - [ ] **Step 1: Run every test** @@ -2216,7 +2219,7 @@ Expected: PASS. Unit tests now total 68 — the 47 from Plan 1a plus 10 in `date npm run build ``` -Expected: build succeeds with no type errors. The route list includes `/`, `/login`, and `/p/[clientSlug]/[projectSlug]`. +Expected: build succeeds with no type errors. The route list includes `/`, `/login`, `/p/[clientSlug]/[projectSlug]`, and `/api/owner/cards/[id]/move`. - [ ] **Step 3: Check both themes at both roles** @@ -2249,4 +2252,6 @@ git commit -m "fix(theme): resolve contrast and token issues found in review" Plan 1c: card detail view with description, subtasks, comments and activity timeline; admin pages for clients, projects and users; the Inbox accept flow that sets `accepted = true` and `start_date`; card creation UI for both roles; archive. +Also deferred to Plan 1c: **true PocketBase realtime**. It needs an authenticated PocketBase connection in the browser, which means exposing the auth token to JavaScript. That is a security tradeoff worth making deliberately, with the alternatives compared, rather than acquiring by accident while building a board. Task 12's refresh-on-focus covers the case that actually matters in the meantime. + Phases 2–5 per the spec: Forgejo ingestion and the effort dashboard — which is where the KPI row with sparklines from the second design reference belongs — AI humanization, the client intake wizard, and the roadmap view. diff --git a/scripts/seed-demo.ts b/scripts/seed-demo.ts new file mode 100644 index 0000000..4a03bff --- /dev/null +++ b/scripts/seed-demo.ts @@ -0,0 +1,156 @@ +/** + * Seed a demo dataset: one client, one project with default columns, an owner, + * a client manager, and a few cards. + * + * Idempotent — re-running updates the existing records rather than duplicating + * them, so it is safe to run between test runs. + * + * Run with: npm run seed (PocketBase must be running) + */ + +import PocketBase from "pocketbase"; + +import { buildColumnRecords, assertColumnInvariants } from "../src/lib/columns/seed"; +import { SPACING } from "../src/lib/ordering/fractional"; + +const PB_URL = process.env.NEXT_PUBLIC_PB_URL ?? "http://127.0.0.1:8090"; + +export const DEMO = { + clientSlug: "wf", + projectSlug: "wfdash-webapp", + ownerEmail: "owner@example.com", + ownerPassword: "owner-password-123", + managerEmail: "manager@wf.example.com", + managerPassword: "manager-password-123", +}; + +async function upsert( + pb: PocketBase, + collection: string, + filter: string, + data: Record +): Promise { + const existing = await pb + .collection(collection) + .getFirstListItem(filter) + .catch(() => null); + + if (existing) { + return pb.collection(collection).update(existing.id, data); + } + return pb.collection(collection).create(data); +} + +async function main() { + const email = process.env.PB_SUPERUSER_EMAIL; + const password = process.env.PB_SUPERUSER_PASSWORD; + + if (!email || !password) { + throw new Error( + "PB_SUPERUSER_EMAIL and PB_SUPERUSER_PASSWORD must be set. Copy .env.example to .env.local and export them." + ); + } + + const pb = new PocketBase(PB_URL); + pb.autoCancellation(false); + await pb.collection("_superusers").authWithPassword(email, password); + + const client = await upsert<{ id: string }>( + pb, + "clients", + `slug = "${DEMO.clientSlug}"`, + { + name: "WF", + slug: DEMO.clientSlug, + brand_color: "#3b82f6", + active: true, + } + ); + + const project = await upsert<{ id: string }>( + pb, + "projects", + `slug = "${DEMO.projectSlug}"`, + { + client: client.id, + name: "WFDash Web App", + slug: DEMO.projectSlug, + description: "Operations web application.", + status: "active", + color: "#3b82f6", + repo_owner: "David", + repo_name: "WFDASH-WEBAPP", + } + ); + + const columnInputs = buildColumnRecords(project.id); + assertColumnInvariants(columnInputs); + + const columns: Record = {}; + for (const input of columnInputs) { + const record = await upsert<{ id: string }>( + pb, + "columns", + `project = "${project.id}" && name = "${input.name}"`, + input + ); + columns[input.name] = record.id; + } + + await upsert(pb, "users", `email = "${DEMO.ownerEmail}"`, { + email: DEMO.ownerEmail, + password: DEMO.ownerPassword, + passwordConfirm: DEMO.ownerPassword, + name: "Dave", + role: "owner", + verified: true, + }); + + await upsert(pb, "users", `email = "${DEMO.managerEmail}"`, { + email: DEMO.managerEmail, + password: DEMO.managerPassword, + passwordConfirm: DEMO.managerPassword, + name: "WF Manager", + role: "client_manager", + client: client.id, + verified: true, + }); + + const cards = [ + { title: "Quote attachments split", column: "In Progress", priority: "high" }, + { title: "Stock inline quantity editing", column: "To Do", priority: "medium" }, + { title: "Delivery note grouping", column: "Done", priority: "low" }, + ]; + + let position = SPACING; + for (const card of cards) { + await upsert( + pb, + "cards", + `project = "${project.id}" && title = "${card.title}"`, + { + project: project.id, + column: columns[card.column], + title: card.title, + description: "", + position, + priority: card.priority, + created_by_role: "owner", + accepted: true, + archived: false, + } + ); + position += SPACING; + } + + console.log("Seeded:"); + console.log(` client ${client.id} (${DEMO.clientSlug})`); + console.log(` project ${project.id} (${DEMO.projectSlug})`); + console.log(` owner ${DEMO.ownerEmail} / ${DEMO.ownerPassword}`); + console.log(` manager ${DEMO.managerEmail} / ${DEMO.managerPassword}`); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); \ No newline at end of file