feat(db): migration 0004 — phase 2 schema + 4 new tokens

users: adds pull_quote, member_number (unique partial index, NULLs allowed),
focus_tags. title already exists from 0003 — not re-added. Backfills
member_number for existing cab users in COALESCE(cab_joined_date,
created_at) asc, tiebreak id asc. Lars gets #1.

events: rebuild required to widen the kind CHECK constraint (SQLite can't
ALTER it in place). Adds working_session as a new kind. Same rebuild adds
four new columns: audience, duration_label, action_label, notes_url. Data
preserved.

dispatches: new entity, status enum draft/published/archived, kind enum
decision/update/behind_the_scenes/note. published_at nullable until
publishPulse-equivalent stamps it. Indexes on (status, published_at) and
author_id.

Tokens (src/styles/tokens.css): adds --surface-card, --surface-card-border,
--ink, --ink-text, --ink-muted. Spec called these out as the only
additions; existing --radius-lg covers the spec's --border-radius-lg
reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Hvid 2026-05-11 15:46:53 +02:00
parent 7f6668f909
commit 865347f682
2 changed files with 81 additions and 0 deletions

View file

@ -0,0 +1,74 @@
-- Phase 2 — council portal revisions
-- Adds pull_quote/member_number/focus_tags to users (title already exists
-- from 0003), rebuilds events to widen the kind CHECK + add four columns,
-- creates dispatches, backfills member_numbers for existing cab users.
-- ── users: new columns ─────────────────────────────────────────────
ALTER TABLE users ADD COLUMN pull_quote TEXT;
ALTER TABLE users ADD COLUMN member_number INTEGER;
ALTER TABLE users ADD COLUMN focus_tags TEXT; -- JSON array, capped 3 × 24 chars at app layer
CREATE UNIQUE INDEX idx_users_member_number ON users(member_number) WHERE member_number IS NOT NULL;
-- Backfill member_numbers for existing CAB users in member-since order,
-- tiebreaking on user.id ascending. Deterministic across machines.
WITH ranked AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY COALESCE(cab_joined_date, created_at) ASC, id ASC) AS rn
FROM users
WHERE role = 'cab'
)
UPDATE users
SET member_number = (SELECT rn FROM ranked WHERE ranked.id = users.id)
WHERE role = 'cab' AND member_number IS NULL;
-- ── events: rebuild for widened kind enum + 4 new columns ─────────
-- SQLite can't ALTER a CHECK constraint; full rebuild required.
CREATE TABLE events_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
kind TEXT NOT NULL DEFAULT 'dinner'
CHECK(kind IN ('dinner','office_hours','summit','virtual','working_session')),
description TEXT NOT NULL DEFAULT '',
location TEXT NOT NULL DEFAULT '',
starts_at TEXT NOT NULL,
ends_at TEXT,
capacity INTEGER,
photo_url TEXT,
audience TEXT,
duration_label TEXT,
action_label TEXT,
notes_url TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
created_by INTEGER REFERENCES users(id)
);
INSERT INTO events_new
(id, slug, title, kind, description, location, starts_at, ends_at, capacity, photo_url, created_at, created_by)
SELECT id, slug, title, kind, description, location, starts_at, ends_at, capacity, photo_url, created_at, created_by
FROM events;
DROP TABLE events;
ALTER TABLE events_new RENAME TO events;
CREATE INDEX idx_events_starts_at ON events(starts_at);
-- ── dispatches ────────────────────────────────────────────────────
CREATE TABLE dispatches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL,
excerpt TEXT,
kind TEXT NOT NULL DEFAULT 'note'
CHECK (kind IN ('decision','update','behind_the_scenes','note')),
author_id INTEGER NOT NULL REFERENCES users(id),
status TEXT NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft','published','archived')),
published_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_dispatches_published ON dispatches(status, published_at);
CREATE INDEX idx_dispatches_author ON dispatches(author_id);

View file

@ -41,6 +41,13 @@
--pigment-indigo: #5a6d83;
--pigment-heather: #8d7a85;
/* --- Phase 2: white card surfaces + deep ink accent --- */
--surface-card: #ffffff;
--surface-card-border: rgba(0, 0, 0, 0.08);
--ink: #2c3a52; /* deep indigo — membership card + event hero */
--ink-text: #e8e0d0; /* readable cream on --ink */
--ink-muted: #b8a989; /* muted label tone on --ink */
/* --- Semantic state mappings --- */
--color-success: var(--pigment-copper);
--color-warning: var(--pigment-ochre);