Files
resolutionflow/.ai/HANDOFF.md
Michael Chihlas 4a37a47887
Some checks failed
Mirror to GitHub / mirror (push) Successful in 5s
CI / frontend (pull_request) Failing after 1m8s
CI / e2e (pull_request) Successful in 12m9s
CI / backend (pull_request) Successful in 15m24s
chore(env): standardize backend python on 3.12
Co-Authored-By: Codex <noreply@openai.com>
2026-05-07 11:31:28 -04:00

73 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- Keep under ~2K tokens. Old handoffs live in SESSION_LOG.md. Do not let this file accumulate history. -->
# HANDOFF.md
**Last updated:** 2026-05-07 (Python 3.12 standardization applied; cutover ops still pending)
**Active task:** None mid-flight. Branch `feat/self-serve-signup-phase-2` (HEAD `502c0a4`) 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`. Cross-cutting review caught one redirect bug (fixed in-flight). After initial handoff, external code review surfaced four more real bugs — all fixed (commits `5e0c9d2`, `3630dd5`, `06200fa`, `502c0a4`):
1. **OAuth refresh tokens never stored** (Phase 1 latent): `_store_refresh_token` extracted to module-public `store_refresh_token`; both Google + Microsoft callbacks now persist the JTI before returning. Test covers callback → `/auth/refresh` round-trip.
2. **OAuth callback never marked store authenticated** (Phase 2 introduced): `setTokens` now sets `isAuthenticated: true`. Verified safe for the refresh-interceptor caller.
3. **Stripe webhook idempotency could permanently drop failed events** (Phase 1 latent): `apply_subscription_event` now adds StripeEvent + runs handler + commits atomically; handler exception → rollback → Stripe retries. Inner `_handle_*` commits removed.
4. **Missing `/account/billing` and `/account/billing/select-plan` pages** (Phase 2 oversight): all the new billing CTAs (TrialPill, UpgradePrompt, NextStepCard, SetupChecklist) linked to non-existent routes. Built BillingPage (subscription summary + Manage-billing → portal session) and SelectPlanPage (plan cards + checkout flow).
**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.
- **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.
- **`SelectPlanPage` seat input doesn't validate against plan `max_seats`.** Stripe Checkout will reject if exceeded; nicer UX would cap client-side.
- **`BillingPage` Manage-billing button always enabled** — clicking with no `stripe_customer_id` falls back to a toast. By design; could disable + tooltip if billing-state payload were extended with that flag.
## Environment notes (carry-forward)
- Code-server LXC now standardizes on Python 3.12. `.python-version` pins 3.12.13 to match the Docker image; native backend work should use `backend/venv` built from that interpreter. `pytest --version` and `alembic --version` pass natively when `DEBUG=true SECRET_KEY=ci-test-secret-key-not-for-production` are supplied to avoid local `.env` validation failures.
- 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.