Day 2 D2-Lab6 Lab 75 minutes

Where the spec becomes software — task by task, spec in context every time

Lab 6 — Code Generation Sprint

Where the spec becomes software

Every prompt has the spec in context. Every commit references a spec section. Every deviation from the spec is caught and fixed before the next task begins.

Surface: Antigravity codegen agent + editor + two terminals Artifacts: backend/ (Express + SQLite) + frontend/ (React) — ≥4 spec-referenced commits Outcome: the StackLog app runs locally and shows Day-1 MCP entries


🟡 THE GOLDEN RULE OF LAB 6

The spec section goes into the agent BEFORE the task. Every time.

  1. Paste the relevant spec section from docs/spec.md
  2. Paste the BMAD task from docs/tasks.md
  3. Add: "Generate the code for this task. Follow the spec exactly."

Never paste a task without the spec. Never the spec without the task. Both, every time. This is the whole discipline of spec-driven development.


⚠️ V6 framing + tool note


Phase 1 · Setup + TASK-001 class demo (12 min)

Step 1 — Folder structure

🪟  cd $env:USERPROFILE\stacklog-workshop
🍎🐧 cd ~/stacklog-workshop
🪟  mkdir backend\src\routes, frontend\src\components
🍎🐧 mkdir -p backend/src/routes frontend/src/components

Step 2 — Initialise both projects

Backend:

cd backend; npm init -y
npm install express cors better-sqlite3
npm install -D @types/node @types/express @types/cors typescript tsx nodemon

⚠️ If better-sqlite3 fails to build: npm install better-sqlite3 --prefer-binary (uses a prebuilt binary — avoids node-gyp toolchain issues).

Frontend (Vite):

cd ../; npm create vite@latest frontend -- --template react-ts
cd frontend; npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

✅ Both folders have node_modules, no errors.


Step 3–4 — TASK-001 as a class demo (the 10-step workflow)

Do TASK-001 (backend/src/db.ts) on the projector to teach the golden rule. Open docs/tasks.md (read TASK-001), open docs/spec.md to Tech Spec §3 (schema), select it, and feed the agent:

=== SPEC SECTION (Tech Spec §3) ===
Database file: stacklog.db (in backend root)
Migration: run on startup if table does not exist
Table: entries
  id        TEXT PRIMARY KEY   -- UUID
  title     TEXT NOT NULL      -- max 80 chars
  content   TEXT NOT NULL      -- markdown
  tags      TEXT DEFAULT ''    -- comma-separated lowercase
  timestamp TEXT NOT NULL      -- ISO 8601
Index: entries_timestamp_idx ON entries(timestamp DESC)
Index: entries_tags_idx       ON entries(tags)

=== TASK-001 ===
Target file: backend/src/db.ts
Action: SQLite connection + auto-migration on startup
Output: export the better-sqlite3 Database instance (default export)

Generate backend/src/db.ts. Use better-sqlite3 (synchronous API).
Path: path.join(__dirname, '..', 'stacklog.db').
Run CREATE TABLE IF NOT EXISTS on startup. Create both indexes.
Follow the spec exactly — field names, types, constraints must match.

Step 3–4 — review & commit (cont.)

Review before accepting (3 things): field names match exactly (id,title,content,tags,timestamp); tags has DEFAULT '' not NULL; both indexes present. Then create the file, and commit:

git add backend/src/db.ts
git commit -m "feat(db): SQLite connection and auto-migration -- satisfies spec §3"

✅ The class now knows the 10-step workflow (see the reference table at the end).


Phase 2 · Task Set A — backend (28 min)

Build the backend first. The frontend needs real endpoints, not mock data. If the backend isn't running before Task Set B, the finale (entries in the browser) is impossible.

Work through these in order (one bmad-dev-story run each; spec §ref in context every time):

TASK-002 · backend/src/server.ts — Express bootstrap (spec §4 + §2) Express app, JSON middleware, CORS (allow http://localhost:3000 only), mount the entries router at /api, start on port 3001, import db from ./db so migration runs on startup. → commit: feat(server): Express bootstrap, CORS, port 3001 -- satisfies spec §2, §4

TASK-003 · backend/src/routes/entries.tsGET /entries (spec §2) Router + GET / → all entries ORDER BY timestamp DESC; optional ?tag= filter (WHERE tags LIKE '%'+tag+'%'). Returns 200 + Entry[] | 500. Export router as default. → Test: run server, open http://localhost:3001/api/entries. → commit: feat(api): GET /entries with tag filter -- satisfies spec §2


Phase 2 — Task Set A (cont.)

TASK-004 · same file — GET /entries/search (spec §2, story 2.1) Add GET /search to the existing router (don't make a new file). Require q (400 if missing). Case-insensitive: WHERE LOWER(title) LIKE ? OR LOWER(content) LIKE ? OR LOWER(tags) LIKE ?, param '%'+q.toLowerCase()+'%'. Returns 200 | 400 | 500. → commit: feat(api): GET /entries/search -- satisfies spec §2, story 2.1 AC#1

TASK-005 · same file — GET /entries/week (spec §2) Add GET /week, optional ?start=YYYY-MM-DD (default: most recent Monday 00:00). Return entries WHERE timestamp >= ? AND timestamp < ? (start + 7 days), ISO-string comparison. → commit: feat(api): GET /entries/week -- satisfies spec §2

Backend checkpoint — keep the server running for all of Task Set B:

cd backend; npx tsx src/server.ts   # "Server running on port 3001"

http://localhost:3001/api/entries returns [] or JSON; search endpoint responds; 3–4 commits on GitHub. Don't close this terminal.


Phase 3 · Task Set B — frontend (30 min)

Step 5 — Vite proxy FIRST (critical for the finale) — TASK-012 (spec §5)

Before any component, configure frontend/vite.config.ts to proxy /apihttp://localhost:3001 and set the dev port to 3000. Without this, the browser hits CORS errors.

Step 6 — Tailwind

tailwind.config.jscontent: ["./index.html","./src/**/*.{ts,tsx}"]. Add the three @tailwind directives to src/index.css.

Component cards (in order)

Every component prompt: paste the spec component-tree entry + any story AC, paste the task, then add: "Use TypeScript + Tailwind utility classes, no custom CSS. The Entry type is { id:string; title:string; content:string; tags:string; timestamp:string }." (Pinning the Entry type stops the agent inventing its own — and note tags is a comma-separated string, not an array.)


Phase 3 — component cards (cont.)

Task File Build Drift to watch
TASK-009 TagBadge.tsx props tag:string, onClick?:(tag)=>void; chip (rounded-full, px-2 py-0.5). onClick must pass tag, not a click event
TASK-008 EntryCard.tsx props entry:Entry; title, timestamp, 200-char preview, TagBadge list. tags is a string → entry.tags.split(','), not .map() directly
TASK-007 EntryFeed.tsx props entries:Entry[], isLoading:boolean; spinner / "No entries yet." / map to EntryCard. don't hardcode tags as array
TASK-010 SearchBar.tsx props onSearch:(q)=>void; controlled input, 300ms debounce (setTimeout/clearTimeout), trimmed. debounce often omitted — verify setTimeout
TASK-011 WeeklySummary.tsx props entries:Entry[]; group by date, heading per day, EntryCard per entry. group via timestamp.slice(0,10)not new Date() (timezone bugs)
TASK-006 App.tsx fetch /api/entries on mount; pass to EntryFeed; onSearch/api/entries/search?q=; toggle WeeklySummary. fetch('/api/...') depends on the Vite proxy (Step 5)

Commit each with a spec reference, e.g. feat(ui): EntryCard -- satisfies spec §1, story 1.1 AC#2. The S11 review needs these references.


Phase 4 · Spec drift — stop, diagnose, fix (4 min)

Triggers: an out-of-scope feature appears (login, POST /entries); prop names differ from the spec; an endpoint returns the wrong shape/status; tags treated as an array; a port other than 3000/3001.

Two-question diagnostic:

  1. Is the task ambiguous? → fix docs/tasks.md, regenerate.
  2. Is the spec ambiguous? → fix docs/spec.md, regenerate the task, then the code. If neither — the model drifted: "The previous output didn't follow the spec. Specifically: [deviation]. Regenerate following the spec exactly."

Never fix drift by editing generated code directly. Fix the root cause (task or spec), then regenerate — or the same drift returns next task. This is the muscle S11 builds on.


Phase 5 · The finale + commit

Step 7 — Run both servers

# Terminal 1 — backend
cd backend; npx tsx src/server.ts
# Terminal 2 — frontend
cd frontend; npm run dev

Open http://localhost:3000 → the StackLog app loads.


Step 8 — Seed Day-1 entries into SQLite (the bridge)

The Day-1 MCP server wrote to stacklog-entries.json; the backend reads stacklog.dbdifferent files. Seed once so yesterday's entries appear. Generate backend/src/seed.ts:

Read ~/stacklog-entries.json (process.env.HOME || process.env.USERPROFILE), parse the array, INSERT OR IGNORE each row by id, log the count. Critical: the Day-1 JSON stores tags as an array; the SQLite column is TEXT → convert with tags.join(',') before inserting. (This is the exact JSON→SQLite shape mismatch — getting it right is the lab's data-bridge lesson.)

cd backend; npx tsx src/seed.ts    # "N entries inserted"

Refresh http://localhost:3000the entries the agent created yesterday now display. That's the two-day bridge, live.


Step 9 — Final commit

cd ~/stacklog-workshop  (or  $env:USERPROFILE\stacklog-workshop)
git add backend/ frontend/
git commit -m "lab6: StackLog app complete -- backend API + React frontend"
git push origin main

Lab 6 complete when task files are on GitHub and localhost:3000 renders the StackLog app.


Per-task workflow (keep visible)

  1. Open task — read TASK-NNN fully. 2. Find spec §ref — select the section in spec.md. 3. New agent chat. 4. Paste spec first. 5. Paste task + "follow the spec exactly." 6. Review every line vs spec + AC; fix drift now. 7. Create the file at the exact path. 8. Quick check (run endpoint / render). 9. Commit with spec-referenced message. 10. Next task.

Troubleshooting (top 7)

  1. better-sqlite3 build failsnpm install better-sqlite3 --prefer-binary (prebuilt; avoids node-gyp).
  2. CORS error → Vite proxy not set; add it to vite.config.ts (Step 5) and restart the dev server.
  3. Cannot find module on backend → use npx tsx (handles TS/ESM resolution); if compiling, imports need .js extensions under Node16 resolution.
  4. Tags show [object Object] / undefined → tags is a comma-separated string: entry.tags.split(','), not .map(). (Log it as spec drift.)
  5. "No entries yet" after seed → backend on 3001? (/api/entries directly) · seed ran? (count logged) · proxy set?
  6. No codegen agent / Copilot → use the Antigravity agent; or install GitHub Copilot Chat; same spec-in-context discipline either way.
  7. WeeklySummary off-by-one day → group with timestamp.slice(0,10) (string), never new Date() (timezone).

Bridge to S11 (Code Review)

The app runs. But does it match the spec? Next session uses bmad-code-review (Reviewer thinking) to check every diff against the spec section it claims to satisfy — and makes the call: update the spec, or fix the code.

Before S11: keep both servers runnable; keep docs/spec.md open; note any drift you spotted — the review surfaces them systematically.