# HANDOFF.md **Last updated:** 2026-05-14 **Active task:** Phase O cutover for self-serve signup. All code blockers remain closed on `main`. **Still blocked on Stripe live-mode activation — root cause is EIN, not code.** User does not yet have an EIN for ResolutionFlow, LLC; Stripe requires a tax ID for live-mode activation. EIN application via IRS.gov was scheduled for 2026-05-13 — confirm status at next session start. Mailing-address decision (carried forward from 2026-05-12): user enters home address into Stripe's **private** business profile temporarily so live-mode isn't blocked on the P.O. Box; public `ContactPage`/`PoliciesPage` mailing-address TODOs stay "available on request" until the P.O. Box is purchased. Stripe accepts an address update later without re-verification. Apex DNS at Namecheap is still missing (separate user-side issue, only matters once Stripe runs site-verification). Nothing on the code side blocks live-mode flip. **Bug-pending-capture item (2026-05-12) — likely resolved:** Prior session noted "user reported finding a bug, will send screenshot next session." This session surfaced two concrete UX bugs that were fixed and merged (PR #168): the dashboard "Start a session" CTA was a dead link, and welcome step-2's PSA setup had a near-invisible "Connect now →" link that didn't even persist `primary_psa`. **Confirm with user at next session start whether the screenshot bug was one of these or something else still pending.** ## Where this session ended Two PRs merged into main: - **PR #166** (`fe0e692`) — docs/handoff doc updates from prior session. Squash-merged 2026-05-14. - **PR #168** (`3a35121`) — session expiration policy + dashboard NextStep CTA fix + welcome step-2 PSA CTA reshape. Merge-committed 2026-05-14. Three notable additions: - `feat(dashboard)` `8d79dd9` — The "Start a session" CTAs on NextStepCard and SetupChecklist used to `Link`-navigate to `/`, leaving the user on the same page (the StartSessionInput lives on the dashboard) with no visible response. Replaced with a `FOCUS_START_SESSION_EVENT` window event the StartSessionInput listens for: scrolls input to viewport top (`scrollIntoView({block:'start'})`), focuses the textarea (with `preventScroll:true` so it doesn't fight the smooth scroll), pulses a `rgba(96,165,250,…)` ring for 900ms. NextStepCard hides itself via local `locallyHidden` state on click so the user isn't double-prompted while typing. SetupChecklist gets the same event-dispatch treatment for its `ran_session` row. - `feat(welcome)` `dc88797` — Welcome step-2 PSA CTA reshaped. Selecting a real PSA now swaps the single Continue + tiny "Connect now →" link for an explicit two-button choice: `Connect now` (primary, blue — saves `primary_psa` then routes to `/account/integrations`) and `Connect later` (secondary outlined — saves `primary_psa` then continues to step 3). **Important pre-existing bug fixed**: the old subtle Link never actually persisted `primary_psa` before navigating away. Both new buttons do. "No PSA yet" and no-selection states still show the original single Continue. Skip-this-step and Skip-the-rest unchanged. Existing tests pass without edits (testids `welcome-step-2-connect-now` and `welcome-step-2-continue` reused). - `docs:` `e5b2624` — added `docs/plans/2026-05-13-public-landing-routing-refactor.md`, `docs/architecture/` reports (god-node map + report 2026-05-06, workflows.json/html, workflows-analysis.html), `docs/tutorials/build-a-page.md`, and `abc-feat-self-serve-signup-phase-2-design-20260507-112020.md` at repo root. `tsc --project tsconfig.app.json --noEmit` clean across all changes. Local vitest blocked by root-owned `node_modules/.vite-temp` (same env issue noted in prior handoffs); CI ran the suite green. **Two issues filed for session leftovers:** - **Issue #171** — Test coverage for the new welcome step-2 "Connect now" path (existing tests still pass but don't exercise the new button's save + redirect-to-integrations behavior). - **Issue #172** — Repo hygiene: gitignore `core.[0-9]*` + `**/.remember/`, and delete the existing 20MB core dumps (`core.144926`, `core.145678`, `docs/architecture/core.1392564`) and `docs/architecture/.remember/`. Carried forward across multiple sessions. Working tree clean except those persistent untracked items (intentionally left for issue #172). Single alembic head: `4ce3e594cb87` (no schema changes this session). ## Resume point **First thing next session:** 1. Confirm with user whether the "bug-pending-capture" screenshot bug from 2026-05-12 was one of the two PR #168 fixes or something else still pending. 2. Check EIN application status (filed 2026-05-13 via IRS.gov). If granted, unblocks the Phase O Stripe live-mode setup chain. After that — **Phase O manual ops, all user-side, all gated on EIN landing first:** 1. **EIN application status check** (user, applied 2026-05-13). 2. **Stripe Dashboard live-mode** (once EIN is in hand): - 3 Products (Starter, Pro, Enterprise). Monthly Prices for Starter ($19.99) + Pro ($29.99). No Prices on Enterprise (sales-led). - Customer Portal with plan-switching disabled. - Webhook at `https://api.resolutionflow.com/api/v1/webhooks/stripe` with 5 events. Save live signing secret. - **Business profile fields**: Customer service URL `https://resolutionflow.com/contact`. Refund/cancellation policy URL `https://resolutionflow.com/policies`. Terms `https://resolutionflow.com/terms`. Privacy `https://resolutionflow.com/privacy`. Phone `(470) 949-4131`. Mailing address = user's home address temporarily (private Stripe field; swap to P.O. Box later without re-verification). EIN = the newly-issued tax ID. 3. **Apex DNS fix at Namecheap** (re-add `@` ALIAS → `c9g7uku8.up.railway.app`, or re-add apex as a Railway custom domain). Becomes the next blocker once Stripe runs site-verification. 4. **Railway prod env**: `STRIPE_SECRET_KEY=sk_live_...`, `STRIPE_WEBHOOK_SECRET`, `STRIPE_PUBLISHABLE_KEY` + `VITE_STRIPE_PUBLISHABLE_KEY` (frontend redeploy required — Vite bake-at-build, Lesson 60), `OAUTH_REDIRECT_BASE=https://resolutionflow.com`, `SELF_SERVE_ENABLED=false` (still false at this point), `INTERNAL_TESTER_EMAILS=`, prod Google + Microsoft OAuth credentials. 5. **Bootstrap prod super-admin** via `create_site_admin.py` (PR #167) — already done end-to-end on prod per 2026-05-12 user confirmation. Re-runnable if needed. 6. **Sync Stripe → DB**: `railway run python -m scripts.sync_stripe_plan_ids` (or via `railway ssh`). Verify `plan_billing` rows have `sk_live_*` price IDs. 7. **Internal validation (Phase O Task 46)**: 9 scenarios with internal testers whose emails match `INTERNAL_TESTER_EMAILS`. 8. **Flag flip (Task 47)**: email pilots, set `SELF_SERVE_ENABLED=true` + `VITE_SELF_SERVE_ENABLED=true` (frontend redeploy). PostHog signup-funnel dashboard + Sentry alert at >1/hour Stripe webhook errors. ## Open issues from prior session (non-code, user-side) - **Apex DNS missing.** `resolutionflow.com` (apex) returns no A/CNAME at the authoritative DNS (Namecheap). When `www` was reconfigured in Railway, the apex record got dropped from the zone. `www` works (cert provisioned 2026-05-08 01:40 UTC). User to re-add apex record at Namecheap (ALIAS `@` → `c9g7uku8.up.railway.app`) or re-add the apex as a Railway custom domain. Railway path is more durable. - **Edge HSTS sticky state on user's machine.** Browser remembers the earlier broken-cert visit. Fix: `edge://net-internals/#hsts` (delete `resolutionflow.com` and `www.resolutionflow.com`) + `#dns` clear host cache + `#sockets` flush. ## Carry-forward - Annual pricing intentionally NOT implemented — user wants exit flexibility. Schema columns preserved as nullable. `sync_stripe_plan_ids.py` leaves annual fields NULL. - `INTERNAL_TESTER_EMAILS` parsed comma-separated → normalized lowercase list. Anonymous callers always see the global flag — allowlist never leaks via unauthenticated request content (regression test enforces). - Office-hours design doc now at `docs/` root (`abc-feat-self-serve-signup-phase-2-design-20260507-112020.md`) as of this session. NOT yet adopted as roadmap — gated on 3 cold calls with external Directors of Onboarding. - Mailing address fill-in: search for `TODO: replace with full mailing address` in `frontend/src/pages/ContactPage.tsx` and `frontend/src/pages/PoliciesPage.tsx` (one each) once P.O. Box is purchased. - `backend/scripts/create_site_admin.py` is the durable site-admin bootstrap tool — idempotent. Three modes: `--send-reset`, `--print-reset`, `--promote-only`. Run from inside the deployed backend container via `railway ssh`. - Bot-crawlability of legal pages: still SPA-rendered. Stripe didn't enforce content scraping last time (issue was DNS). If a future vendor review flags it, pre-render with `vite-plugin-prerender-spa` (~half day). - Frontend env additions for cutover: `VITE_SELF_SERVE_ENABLED`, `VITE_GOOGLE_CLIENT_ID`, `VITE_MS_CLIENT_ID`, `VITE_OAUTH_REDIRECT_BASE`, `VITE_CALENDLY_URL`, `VITE_STRIPE_PUBLISHABLE_KEY`. - **Branch hygiene note (process learning):** PR #168 ended up bundling unrelated work — session expiration policy (the original scope of `feat/session-expiration-policy`) plus dashboard CTA fixes plus welcome step-2 reshape. The mixed scope was deliberate (user wanted it on the same PR), but worth flagging for future PRs: if onboarding-UX work continues, branch it separately from auth/session work.