Phase 3: polish and onboarding
What phase 3 delivered, with acceptance evidence, deviations and open items
Status: ✅ complete. Phase 4 started per Esmee's instruction (review items collected, not blocking).
What was built
- Design system: Tailwind 4, warm stone/amber owl brand, shared component set (
components/ui.tsx); owl personality confined to empty states, error states and the digest header; billing and error-recovery copy stays professional. Auth/billing/legal pages restyled without content changes. - Onboarding wizard (
/onboarding, SPEC §6): step 1 sources (starter-bundle cards, add-by-URL via core feed detection, OPML import — the wizard was rebuilt to five steps in July and OPML dropped in D-33), step 2 rhythm (day/time, default Sunday 17:00, tenant timezone), step 3 instant first digest with a patient pending state. Home redirects new tenants (zero sources) into the wizard. Bundle selection also seeds the preference profile with the bundle's editorial direction. - Starter bundles (
lib/onboarding/starter-bundles.ts, [REVIEW]): 5 bundles × 6 feeds, every URL fetched and verified as live RSS/Atom on 2026-07-08; rejected candidates documented (First Round: no public feed; Paul Graham: HTML not RSS; Andrew Chen & Brand New: stale; Designer News: HTTP 500; De Correspondent: members-only; Stratechery: paywalled). - Dashboard: home (lifecycle banners, stats, next digest, run-now), sources management (grouped per category, pause/resume/delete, add/import), digest archive + web view (narratives, verdict badges, skips collapsed, 👍/👎 per item and section — "More/Less like this"), settings (profile editor, schedule, billing, sign-out).
- Email path: digest mail wired end-to-end (Resend → owner's verified account email, sender
owl@owlypost.comoverridable via env). Delivery itself is intentionally unverified until the Phase 4 domain verification — a mail failure never fails a digest run. - Legal:
/terms+/privacydrafts with all mandated clauses ([REVIEW] — drafts only; Esmee reviewed and adjusted both before go-live). - Core perf contribution (public repo):
summarizePendingItemsnow runs a worker pool (concurrency 16, was sequential) — a backlogged first ingest dropped from ~9 minutes to ~1–2. Reverted a day later with the rest of the Phase 0 core work (P4-6): the cloud does not get to optimise the open source project for its own onboarding metric. Accepted consequence — the first digest went back to ~9 minutes until the core lands the change on its own merits.
Acceptance evidence (SPEC §12, Phase 3)
Run end-to-end in the browser as the Phase 2 e2e user (fresh tenant, zero sources):
| Step | Evidence |
|---|---|
| Wizard renders + bundle apply | Design bundle → 6 sources, category, profile seeded (638 chars) in one click |
| Schedule | Saved (Sunday 17:00 Europe/Amsterdam) |
| Instant first digest | Real run: 125 items ingested from 6 live feeds, 100 summarized (cap), digest ready via claude-sonnet-4-6 (16k in / 5k out) |
| Digest web view | Renders sections with genuinely editorial narratives (real NN/g/Smashing/CSS-Tricks content), verdict badges, collapsed skips |
| Feedback loop | Section 👍 clicked in UI → section_feedback row (Design, up) |
| Timing | Wizard interaction itself takes well under a minute; the first digest runs behind the patient pending state while ingest and summaries complete. |
Deviations → DECISIONS_LOG P3-1…P3-4
[REVIEW] items touched
- Starter-bundle contents + profile texts (SPEC §14.4) —
lib/onboarding/starter-bundles.ts. /terms+/privacywording incl. price-change clause (SPEC §14.5) — placeholders flagged inline: contact emailmail@owlypost.com, 18+ requirement, invoice-retention note, SCC mention.- Trial LLM budget headroom: the heavy first digest cost ~41 cents against the 75-cent trial budget ([REVIEW] §5 numbers) — a second manual digest in the trial would flirt with the cap. Consider raising
trial_overrides.monthly_llm_budget_cents(e.g. 150) or accepting the block message.
Open items
- Email delivery verification (needs owlypost.com DNS + Resend verification — Phase 4).
- Feedback can be flipped 👍↔👎 but not cleared (the core's feedback write has no delete — minor UX gap; a core change is the maintainer's call, not the cloud's).
runDigestNowlinks to/digestsrather than deep-linking the fresh digest (needs a small return-value change).