Tasks 27–44 implemented across 18 commits on this branch. Phase O (Stripe live setup, internal validation, flag flip) is the manual operational follow-up and is the resume point. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.2 KiB
HANDOFF.md
Last updated: 2026-05-06 (Phase 2 frontend-cutover code complete; cutover ops still pending)
Active task: None mid-flight. Branch feat/self-serve-signup-phase-2 (HEAD c75ce0c) is ready to PR.
Where this session ended
Tasks 27–44 of Phase 2 implemented across 18 commits on feat/self-serve-signup-phase-2, branched off main (f918b76). Each task went through implementer + spec review + code-quality review per superpowers:subagent-driven-development; review findings either fixed in-flight (commit-amends, no merge churn) or noted as deliberate scope deferrals. Final cross-cutting review found one real bug (relative /beta-signup redirect target) — fixed in the last amend.
Backend additions (Phase I, Tasks 27–31):
BillingService.open_customer_portal+GET /billing/portal-session(allowlisted for canceled/unverified users).PATCH /users/me/onboarding-step+POST /users/me/onboarding-dismiss-rest.POST /sales-leads(public, 5/hr/IP rate limit, fire-and-forget notification email, server-side PostHog event stub)./admin/plan-limitsGET/PUT round-tripsplan_billing(Stripe IDs, prices, public/archived flags) in one transaction;BillingService.invalidate_billing_cacheno-op stub for future cache.GET /config/public({self_serve_enabled, oauth_providers}); register-endpoint gate nowREQUIRE_INVITE_CODE and not SELF_SERVE_ENABLED and not invite_code.GET /accounts/invites/{code}/lookup(Phase 36).- OAuth callback extended to honor
account_invite_code+invited_emailfor invitee-via-OAuth path; rejects existing-email user withemail_already_registered_use_login. GET /plans/public(Phase 42).POST /beta-signupreturns 307 to${FRONTEND_URL}/register?from=beta.OnboardingStatusextended withemail_verified+shop_setup_done;UserResponseexposesonboarding_step_completed+onboarding_dismissed.
Frontend additions:
useBillingStore(Zustand, polled 60s viauseBillingPollmounted inAppLayout),useFeature/useFeatureLimit/useTrialBannerhooks,FeatureGate/UpgradePrompt/EmailVerificationGatecomponents.RegisterPageredesign: OAuth (Google/Microsoft) buttons + invite-code-conditional.OAuthCallbackPageat/auth/{google,microsoft}/callbackwith CSRF state validation.useAppConfighook (consumes/config/public, falls back toVITE_SELF_SERVE_ENABLED).AcceptInvitePageat/accept-invite?code=...(locked email, 3 sign-in options,/?welcome=teammateon success).EmailVerificationBannerrefactored to design-system tokens + grace-period hide;EmailVerificationWallpolished;VerifyEmailPageat/verify-email?token=...(single-fire guard).WelcomeRouter+WelcomeStep1/2/3at/welcome*. PSA tile UI + bulk invite emails per row.TrialPillin topbar (pristine/warning/urgent/expired/paid/complimentary/past_due/canceled).NextStepCard+SetupChecklist(replaces orphanedOnboardingChecklist); unified list, no SOLO/TEAM split, no Script Builder item.PricingPageat/pricing(B-style, 3 plan cards, hardcoded comparison v1).ContactSalesPageat/contact-sales.LandingPagegot "See pricing" CTA + replaced beta-signup form with<Link to="/register?from=beta">.
New env vars (Vite ARG/ENV in Dockerfile + .env.example): VITE_SELF_SERVE_ENABLED, VITE_GOOGLE_CLIENT_ID, VITE_MS_CLIENT_ID, VITE_OAUTH_REDIRECT_BASE, VITE_CALENDLY_URL. Backend env: SALES_LEAD_RECIPIENT_EMAIL (default sales@resolutionflow.com).
Resume point — DO THIS NEXT
- Review the branch and open a PR. 18 commits, all squashed-or-not at user discretion. Branch
feat/self-serve-signup-phase-2frommain. - Phase O — manual operational tasks (Tasks 45–47 from the plan):
- Task 45: Stripe live-mode setup (Products, Prices, Customer Portal, webhook endpoint + signing secret) and Railway prod env vars (
STRIPE_*,OAUTH_REDIRECT_BASE,GOOGLE_CLIENT_*,MS_CLIENT_*). Runpython -m scripts.sync_stripe_plan_idsto populateplan_billingrows with live Stripe IDs. - Task 46: Internal validation pass via
INTERNAL_TESTER_EMAILSallowlist (per-email bypass ofSELF_SERVE_ENABLED=false). Backend support for the allowlist itself is NOT implemented — needs a small addition toauth.py/config.pyif the validation pass requires it. Otherwise validate in test mode with the flag flipped temporarily. - Task 47: Flip
SELF_SERVE_ENABLED=true(backend) +VITE_SELF_SERVE_ENABLED=true(frontend rebuild). Watch PostHog funnel + Stripe webhook error rate.
- Task 45: Stripe live-mode setup (Products, Prices, Customer Portal, webhook endpoint + signing secret) and Railway prod env vars (
- Followups deferred during Phase 2 (logged below).
Followups deferred from Phase 2
- PostHog server-side capture infrastructure missing.
/sales-leadscalls_capture_posthog_eventwhich lazy-importsapp.core.analytics.posthog(no-op if module absent). Wire up the real PostHog server SDK before cutover — needsapp/core/analytics.pywith a configured client, pluspython-posthogdep. - Frontend
/usage/{field}endpoint missing.useFeatureLimitlazy-fetchesapiClient.get('/usage/' + field)— endpoint doesn't exist; hook silently falls back toused=0. Build the backend endpoint before any UI surface relies on real usage counts. - OAuth state CSRF — semi-validated. RegisterPage path validates state round-trip on callback. AcceptInvitePage path uses a base64url-JSON envelope with embedded csrf — also validated. UTF-8-safe via
unescape(encodeURIComponent(...)). - Pre-existing dual subscription stores (
authStore.subscriptionlegacy +billingStore.subscriptionnew).Subscription.statusunion infrontend/src/types/account.tsis missing'complimentary'. Phase 2 didn't introduce — but worth deprecating the old store before any UI readscomplimentaryfrom the wrong source. INTERNAL_TESTER_EMAILSper-email allowlist (Task 46): backend support not implemented. Add when Phase O Task 46 needs it.accept-invitenot gated byself_serve_enabled— by design (invites work in any mode). Confirm if pilot wants stricter gating.UserResponse.onboarding_step_completedvalidator suggested by reviewer (rejectcompletewithout data) — skipped, spec typesdata?as optional.- Welcome wizard behind
EmailVerificationGate: Day 7+ unverified users hit the wall on/welcome/*and have no path back into the wizard until they verify. Per spec — wall says "Resend verification" → user verifies → wall closes → wizard resumes. If product wants a different UX, allowlist/welcome/*in the gate.
Environment notes (carry-forward)
- Code-server LXC: docker-only, no native python/node/npm. Use
docker exec resolutionflow_{backend,frontend} .... - Pytest:
docker exec resolutionflow_backend pytest tests/<file> -v --override-ini="addopts=". - Vitest:
docker exec -w /app resolutionflow_frontend npm test -- <path> --run. - TS build:
docker exec -w /app resolutionflow_frontend npx tsc -b. - Alembic:
docker exec -w /app resolutionflow_backend alembic .... Never--rev-id. - No
ghCLI — Gitea API via$GITEA_TOKENfor PR/issue work. - Single alembic head:
c6cbfc534fad(from Phase 1). Phase 2 added no migrations.