Build Log · June 22, 2026

Hearth: Phase 4 — Invite Flow

Courtney can now join the household. Prod DB split, schema pushed, invite flow live at home.forgekits.build.

  • Hearth
  • Multi-member
  • Auth
  • Deploy
  • DB
new DB table
1

new DB table

access helpers
3

access helpers

new files
5

new files

prod DB split
1

prod DB split

Timeline

  1. Start

    Ignition + orientation

    Read Phase 3 retro, schema, access.ts, proxy.ts

  2. 20m

    invites table + helpers

    Schema addition, createInvite / getInvite / consumeInvite in access.ts

  3. 40m

    Settings page + InviteSection

    Server component with real members list, client invite UI with profile selector

  4. 55m

    /invite/[token] accept page

    Public route, Clerk redirect-back, AcceptInviteButton client component

  5. 1h

    proxy.ts public route

    Added /invite/(.*) to public routes — Clerk was blocking the accept page

  6. 1.1h

    Prod DB decision + split

    New Neon project for prod, Vercel env var updated

  7. 1.25h

    Schema push + seed + backfill

    db:push → db:seed → db:backfill against prod via inline DATABASE_URL

  8. 1.5h

    Local test + Google OAuth fix

    Google test user added, port mismatch (3000 vs 3001) resolved

  9. 1.75h

    Commit + deploy

    tsc clean, build clean, live at home.forgekits.build

What shipped

  • lib/db/schema.ts — invites table: token, householdId, role, profileId, expiresAt, status, acceptedByUserId

  • lib/access.ts — createInvite(): 32-byte hex token, 7-day expiry

  • lib/access.ts — getInvite(token): validates pending + not expired

  • lib/access.ts — consumeInvite(): creates householdMembers row, links profile, idempotent

  • app/(dashboard)/settings/actions.ts — generateInviteAction: auth bridge + createInvite + URL construction

  • app/(dashboard)/settings/page.tsx — real server component: auth, members list, InviteSection

  • components/settings/InviteSection.tsx — client: profile selector, generate button, copyable URL

  • app/invite/[token]/page.tsx — public accept page: invalid/expired state, sign-in redirect, join UI

  • app/invite/actions.ts — acceptInviteAction: Clerk displayName, consumeInvite, redirect /today

  • components/invite/AcceptInviteButton.tsx — client: pending state, error display

  • proxy.ts — /invite/(.*) added to public routes

“Courtney can join now. The household is real.”
— Session close