PR #165 (legal/contact pages + MarketingFooter) merged as ba45cfe.
All code blockers for self-serve cutover are now on main.
Records the real Stripe live-mode activation blocker: user does not
yet have an EIN for ResolutionFlow, LLC, and Stripe requires a tax
ID before it will activate live mode. Applying via IRS.gov 2026-05-13.
Mailing-address decision (2026-05-12): user will enter their home
address into the Stripe business profile temporarily so live-mode
isn't blocked on the P.O. Box. This is a private Stripe field. The
public-facing TODO in ContactPage.tsx and PoliciesPage.tsx stays
"available on request" until the P.O. Box is purchased — explicitly
DO NOT put the home address on the public site. Stripe accepts
updating the address later without re-verification, so swapping
in the P.O. Box when it arrives is non-disruptive.
Nothing on the code side blocks live-mode flip. Apex DNS at
Namecheap remains pending but only matters once Stripe runs its
site-verification step, which happens after the business-profile
fields are accepted.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6.9 KiB
HANDOFF.md
Last updated: 2026-05-12
Active task: Phase O cutover for self-serve signup. All code blockers are closed on main (PR #164 3f04911, PR #165 ba45cfe). Currently 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. Applying via IRS.gov on 2026-05-13. Mailing-address decision made 2026-05-12: user will enter home address into the Stripe business profile temporarily so live-mode isn't blocked on the P.O. Box; the public-facing mailing-address TODO in ContactPage.tsx and PoliciesPage.tsx stays "available on request" until the P.O. Box is set up (do NOT put the home address on the public site). Stripe address can be updated to the P.O. Box later without re-verification. Apex DNS at Namecheap is still missing (separate user-side issue tracked below); only matters once Stripe runs its site-verification step, which happens after the business-profile fields are accepted. Nothing on the code side blocks live-mode flip.
Where this session ended
PR #165 squash-merged (ba45cfe feat(legal): add /policies, /contact, /promotions pages + MarketingFooter (#165)):
- New pages, all SPA, matching existing
/privacyand/termspattern:/policies(consolidated Customer Policies — customer service contact, return/refund/dispute policy, cancellation, U.S. legal and export restrictions, promotional terms; anchor IDs per subsection),/contact(phone (470) 949-4131, support/sales/billing/security inboxes, response-time SLAs),/promotions(stub stating no promotions currently active — satisfies Policies §6.2 cross-ref). MarketingFooter(frontend/src/components/common/MarketingFooter.tsx) extracted from inline landing footer and mounted on/landing,/pricing,/contact-sales. Reuses existinglanding-footer*CSS — must be rendered inside a.landing-pagewrapper (documented in a JSX comment) because--lp-*vars are scoped there. All four legal links (Privacy / Terms / Policies / Contact) are now reachable from every marketing surface.- Privacy and Terms closing sections updated to point at
/contact+/policiesand the correct inbox per area (security@andsupport@respectively). Stalehello@resolutionflow.commailto removed everywhere. - Mailing address left as TODO comments in
ContactPage.tsxandPoliciesPage.tsx(one each). Rendered publicly as "available on request — email support@". Fill in when the P.O. Box is purchased.
tsc --project tsconfig.app.json --noEmit and eslint clean. Local vite build and tsc -b are blocked by root-owned node_modules/.tmp and node_modules/.vite-temp cache directories — CI rebuilds from a clean env and was green.
Working tree clean (only pre-existing untracked files: abc-feat-self-serve-signup-phase-2-design-...md, core.*, docs/architecture/, docs/tutorials/ — same set noted in prior handoffs as "do not stage").
Single alembic head: 4ce3e594cb87 (no schema changes in this PR).
Resume point
Phase O manual ops — entirely user-side, gated on the apex DNS fix below:
- Stripe Dashboard live-mode:
- 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/stripewith 5 events. Save live signing secret. - Business profile fields: Customer service URL
https://resolutionflow.com/contact. Refund/cancellation policy URLhttps://resolutionflow.com/policies. Termshttps://resolutionflow.com/terms. Privacyhttps://resolutionflow.com/privacy. Phone(470) 949-4131. Mailing address per Stripe form (not required on website).
- 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=<allowlist>, prod Google + Microsoft OAuth credentials. - Sync against prod:
railway run python -m scripts.sync_stripe_plan_ids. Verifyplan_billingrows havesk_live_*price IDs. - Internal validation (Task 46): 9 scenarios with internal testers whose emails match
INTERNAL_TESTER_EMAILS. - 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 per SOAdns1.registrar-servers.com.). Whenwwwwas reconfigured in Railway, the apex record got dropped from the zone.wwwworks (cert provisioned 2026-05-08 01:40 UTC, valid Let's Encrypt SAN). Symptom: apex unreachable from user's machine; Stripe verifier "URL couldn't be reached." User to re-add apex record at Namecheap (ALIAS Record host=@value=c9g7uku8.up.railway.app) or re-add the apex as a Railway custom domain and follow Railway's DNS instructions. The 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(deleteresolutionflow.comandwww.resolutionflow.com) +#dnsclear host cache +#socketsflush.
Carry-forward
- Annual pricing intentionally NOT implemented — user wants exit flexibility. Schema columns preserved as nullable.
sync_stripe_plan_ids.pyleaves annual fields NULL. INTERNAL_TESTER_EMAILSparsed 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 at
~/.gstack/projects/chihlasm-resolutionflow/abc-feat-self-serve-signup-phase-2-design-20260507-112020.md(documentation-builder thesis). 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 addressinfrontend/src/pages/ContactPage.tsxandfrontend/src/pages/PoliciesPage.tsx(one each) once P.O. Box is purchased. - Bot-crawlability of legal pages: still SPA-rendered. Stripe didn't enforce content scraping last time (issue turned out to be 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.