Files
resolutionflow/.ai/HANDOFF.md
Michael Chihlas fee4cb5b74 docs(handoff): capture Phase 2 (frontend cutover) code completion
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>
2026-05-06 23:46:15 -04:00

7.2 KiB
Raw Blame History

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 2744 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 2731):

  • 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-limits GET/PUT round-trips plan_billing (Stripe IDs, prices, public/archived flags) in one transaction; BillingService.invalidate_billing_cache no-op stub for future cache.
  • GET /config/public ({self_serve_enabled, oauth_providers}); register-endpoint gate now REQUIRE_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_email for invitee-via-OAuth path; rejects existing-email user with email_already_registered_use_login.
  • GET /plans/public (Phase 42).
  • POST /beta-signup returns 307 to ${FRONTEND_URL}/register?from=beta.
  • OnboardingStatus extended with email_verified + shop_setup_done; UserResponse exposes onboarding_step_completed + onboarding_dismissed.

Frontend additions:

  • useBillingStore (Zustand, polled 60s via useBillingPoll mounted in AppLayout), useFeature / useFeatureLimit / useTrialBanner hooks, FeatureGate / UpgradePrompt / EmailVerificationGate components.
  • RegisterPage redesign: OAuth (Google/Microsoft) buttons + invite-code-conditional. OAuthCallbackPage at /auth/{google,microsoft}/callback with CSRF state validation. useAppConfig hook (consumes /config/public, falls back to VITE_SELF_SERVE_ENABLED).
  • AcceptInvitePage at /accept-invite?code=... (locked email, 3 sign-in options, /?welcome=teammate on success).
  • EmailVerificationBanner refactored to design-system tokens + grace-period hide; EmailVerificationWall polished; VerifyEmailPage at /verify-email?token=... (single-fire guard).
  • WelcomeRouter + WelcomeStep1/2/3 at /welcome*. PSA tile UI + bulk invite emails per row.
  • TrialPill in topbar (pristine/warning/urgent/expired/paid/complimentary/past_due/canceled).
  • NextStepCard + SetupChecklist (replaces orphaned OnboardingChecklist); unified list, no SOLO/TEAM split, no Script Builder item.
  • PricingPage at /pricing (B-style, 3 plan cards, hardcoded comparison v1).
  • ContactSalesPage at /contact-sales. LandingPage got "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

  1. Review the branch and open a PR. 18 commits, all squashed-or-not at user discretion. Branch feat/self-serve-signup-phase-2 from main.
  2. Phase O — manual operational tasks (Tasks 4547 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_*). Run python -m scripts.sync_stripe_plan_ids to populate plan_billing rows with live Stripe IDs.
    • Task 46: Internal validation pass via INTERNAL_TESTER_EMAILS allowlist (per-email bypass of SELF_SERVE_ENABLED=false). Backend support for the allowlist itself is NOT implemented — needs a small addition to auth.py/config.py if 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.
  3. Followups deferred during Phase 2 (logged below).

Followups deferred from Phase 2

  • PostHog server-side capture infrastructure missing. /sales-leads calls _capture_posthog_event which lazy-imports app.core.analytics.posthog (no-op if module absent). Wire up the real PostHog server SDK before cutover — needs app/core/analytics.py with a configured client, plus python-posthog dep.
  • Frontend /usage/{field} endpoint missing. useFeatureLimit lazy-fetches apiClient.get('/usage/' + field) — endpoint doesn't exist; hook silently falls back to used=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.subscription legacy + billingStore.subscription new). Subscription.status union in frontend/src/types/account.ts is missing 'complimentary'. Phase 2 didn't introduce — but worth deprecating the old store before any UI reads complimentary from the wrong source.
  • INTERNAL_TESTER_EMAILS per-email allowlist (Task 46): backend support not implemented. Add when Phase O Task 46 needs it.
  • accept-invite not gated by self_serve_enabled — by design (invites work in any mode). Confirm if pilot wants stricter gating.
  • UserResponse.onboarding_step_completed validator suggested by reviewer (reject complete without data) — skipped, spec types data? 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 gh CLI — Gitea API via $GITEA_TOKEN for PR/issue work.
  • Single alembic head: c6cbfc534fad (from Phase 1). Phase 2 added no migrations.