Commit graph

4 commits

Author SHA1 Message Date
DJP
2764123cf7 Per-report theme system: brief picker + Stage 10 injection + SPA boot apply
Phase 6a of the dashboard overhaul (plan:
~/.claude/plans/thsi-is-a-app-zippy-reef.md). Closes the
white-labelling story — every brief gets its own accent, heading font,
background preset, and agency label, inherited by every report
generated from it. Logo upload lands in Phase 6b.

Brief schema (v2/server/schemas/brief.ts):
- New BRIEF_THEME Zod object: accent_hex (#rrggbb), accent_2_hex
  (optional, auto-derived), heading_font (fraunces|playfair|inter|
  space-grotesk), background (cream|paper|ink), agency_name (≤40 chars),
  logo_path (Phase 6b placeholder).
- Brief row gets a new theme JSONB column. Idempotent boot-time
  ALTER TABLE IF NOT EXISTS picks up the column on existing prod DBs;
  init.sql also updated for fresh installs.

Server (v2/server/):
- DAO: setBriefTheme(id, theme | null), BriefRow.theme typed.
- New endpoints (editor role required):
    PUT    /api/briefs/:id/theme   — write theme JSON
    DELETE /api/briefs/:id/theme   — reset to defaults (NULL)
- publicBrief() exposes theme so the operator-app prefills the editor.

Pipeline (v2/pipeline/):
- New lib/colors.ts: deriveAccent2(hex) — HSL math to compute a darker
  companion accent when the picker only specified one. Mirrors the
  Original-project relationship between sienna #c2602a and oxblood
  #8a3a1a.
- Stage 10 takes a third optional param (theme: BriefTheme | null) and
  injects dataset.theme into dataset_v2.json with accent_2 always
  populated. Cli.ts call sites pass briefRow.theme.

Dashboard SPA (v2/templates/dashboard_template/):
- Types extended with DatasetTheme.
- App.tsx applies theme at boot via document.documentElement.style
  .setProperty before first render — avoids a colour-flash on any
  non-default theme. FONT_STACKS map heads_font enum to the actual
  CSS font stack (fonts already preloaded in index.html).
- Background preset 'paper' lightens; 'ink' flips the surface/text
  axis for dark-deck reports (still picks up the brand accent).
- Topbar renders agency_name + logo placeholder. Falls back to
  "SOCIAL LISTENING" eyebrow when no theme is set.

Operator app (v2/operator-app/):
- New ThemeEditor component on the brief edit page:
    - 8 accent preset swatches (Sienna/Oxblood/Forest/Slate/Olive/
      Wine/Plum/Ink) + custom hex input.
    - 4 heading-font tiles each rendering "The Branded Glass Moment"
      in the candidate stack — WYSIWYG without a separate preview.
    - 3 background presets (Cream/Paper/Ink) shown as colour swatches.
    - Agency name text input (≤40 chars).
    - Save / Reset to defaults.
- New hooks: useUpdateBriefTheme, useResetBriefTheme.

What's intentionally NOT in this phase:
- Logo upload (Phase 6b — needs multipart parser, sharp downscaling,
  SVG sanitisation).
- Free-form CSS textarea (out of scope by design — maintenance trap).
- Custom body / line / format / maturity colours (categorical signals;
  changing them breaks comparability across reports).

Both Vite builds pass. tsc --noEmit clean. mom_compare unit test
fixture untouched (no schema break).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 10:56:19 -04:00
DJP
b1c58ffab0 Demo brief JSON file: same content as the inline operator-app demo
The inline DEMO_BRIEF in list.tsx was beefed up in 15aa5a6, but the file at
v2/examples/dove-demo-brief.json that ships in the repo wasn't — it still had
the old thin shape (3 competitors, 2 KPIs, 4 interests, one-line positioning).
Mirroring the inline content so anyone downloading the example file gets the
same realistic example as the "Load Dove demo" button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 20:37:42 -04:00
DJP
3e71df8a79 Lower default engagement floor 10×; richer Stage 3 diagnostic
The 1000 likes / 10000 plays defaults are calibrated for top-of-funnel
beauty/fitness scrapes; in narrower TikTok niches almost every video lands
below them and Stage 2 returns 0 keepers. Defaults dropped to 100 likes /
1000 plays across:
- server/schemas/brief.ts (Zod default)
- db/init.sql (column default for new DBs)
- examples/dove-demo-brief.json
- operator-app's brief-form initial values
- operator-app's "Load Dove demo" inline brief

Stage 3 empty-pass1 error now reads pass1/spend_log.json and reports the
actual scrape breakdown — total $ spent, total raw videos returned, and
how many got dropped by each floor (zero-plays / min_plays / min_likes /
min_stl_pct). So instead of a generic "lower the floor", the user sees:
"Spent $5.42 across 7 scrapes; 1400 videos returned. Dropped: 12 zero-plays,
1305 below min_plays=10000, 31 below min_likes=1000."

Existing briefs are unaffected (column default applies to NEW rows). For the
in-flight Dove2 run the user can edit the brief and lower the floor, then
click Retry pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 20:28:23 -04:00
DJP
e47c5fa308 Briefs: import + export + demo brief; clean cli usage text
- briefs/list: new "Import JSON" button; collapsible panel with file upload,
  paste textarea, "Load Dove demo" button (inlines the same demo we ship
  in v2/examples/dove-demo-brief.json). On submit POSTs the JSON to
  /api/briefs and surfaces server-side Zod issues inline.
- briefs/detail: new "Export JSON" button — downloads `<slug>.brief.json`
  using the brief_yaml the server now exposes under `full`.
- v2/examples/dove-demo-brief.json: real Dove TikTok demo brief, $30
  budget, ready to run end-to-end via the Run pipeline button.
- pipeline/cli.ts: usage() text de-stale-ified — every stage is real now,
  the "TODO Phase X" tags removed; new commands documented (`all`,
  `backfill-covers`, `--run-id`, `--drop-failing`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:48:36 -04:00