From dc22aa0ff0d9f9102e04f4cef77201df4e574396 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Tue, 12 May 2026 01:25:19 -0400 Subject: [PATCH] docs(handoff): record PR #164/#165/#167 merges, EIN blocker, pending bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #164 (taxonomy + Stripe sync + allowlist) merged as 3f04911. PR #165 (legal/contact pages + MarketingFooter) merged as ba45cfe. PR #167 (create_site_admin.py bootstrap script) merged as e50a215. All code blockers for self-serve cutover are now on main. Site-admin bootstrap script verified end-to-end against prod via railway ssh (first prod super-admin row now exists). Stripe live-mode activation blocked on EIN — user applying via IRS.gov on 2026-05-13. Mailing-address decision: home address into Stripe's private business profile temporarily; public-facing ContactPage/PoliciesPage stays "available on request" until the P.O. Box arrives. Records a pending bug: user reported finding one but did not share details — planning to send a screenshot via the VS Code extension GUI in the next session. Next-session-first-action is updated to capture and triage that screenshot before resuming Phase O. Co-Authored-By: Claude Opus 4.7 --- .ai/CURRENT_TASK.md | 5 ++-- .ai/HANDOFF.md | 64 +++++++++++++++++++++++++++------------------ .ai/SESSION_LOG.md | 52 ++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 28 deletions(-) diff --git a/.ai/CURRENT_TASK.md b/.ai/CURRENT_TASK.md index b96a3b7d..5a66babe 100644 --- a/.ai/CURRENT_TASK.md +++ b/.ai/CURRENT_TASK.md @@ -1,10 +1,11 @@ # CURRENT_TASK.md -**Active task:** Phase O cutover for self-serve signup. PR #164 (`feat/billing-plan-taxonomy`) open in Gitea with 5 commits at head `2c9f5e9`, closing the last code blockers — plan taxonomy reconciliation (`team` → `enterprise`, add `starter`), `INTERNAL_TESTER_EMAILS` allowlist, `sync_stripe_plan_ids.py` script, page-title `—` JSX-escape bug fix, frontend taxonomy followups, doc refresh. After merge, only manual ops remain: Stripe Dashboard live-mode config, Railway prod env vars, internal validation pass, public flag flip. See `.ai/HANDOFF.md` for the resume point. +**Active task:** Phase O cutover for self-serve signup. All code blockers are now closed on `main`. Only user-side manual ops remain: apex DNS fix at Namecheap, Stripe Dashboard live-mode config (with the new `/contact` and `/policies` URLs surfaced in the business profile), Railway prod env vars, internal validation pass, public flag flip. See `.ai/HANDOFF.md` for the resume point. ## Recently shipped -- **2026-05-08 — PR #164 (open)** Plan taxonomy reconciliation + `INTERNAL_TESTER_EMAILS` allowlist + Stripe sync script + page-title fix + frontend taxonomy followups + doc refresh. 5 commits on `feat/billing-plan-taxonomy` from main (`dad5e1f`); HEAD `2c9f5e9`. Migration `4ce3e594cb87` renames `plan_limits.plan='team'` → `'enterprise'` and adds `starter` row (caps interpolated between free and pro: `max_trees=10`, `sessions=75`, `ai=15/mo`). Resource visibility (`Tree.visibility='team'`, `StepLibrary.visibility='team'`) is a separate domain and intentionally untouched. New `backend/scripts/sync_stripe_plan_ids.py` upserts `plan_billing` rows from Stripe products by exact name match — annual fields stay NULL by design (user explicitly skipping annual pricing for exit flexibility). `Settings.is_internal_tester` + `is_self_serve_active_for` centralize the allowlist + global-flag check; new `get_current_user_optional` dep; `/config/public` honors allowlist for authenticated callers; `/auth/register` allows allowlisted emails without invite code. LandingPage page-title bug — `—` inside JSX attribute strings was rendering as 6 literal characters in browser tabs; replaced with literal em dash. PageMeta default tagline updated from "Decision Tree Platform" to "AI-Powered Troubleshooting for MSPs". 86/86 passing across subscription/billing/plan/invite/admin sweep; tsc + lint clean. See `.ai/DECISIONS.md` for the two architectural entries (taxonomy reconciliation, allowlist). +- **2026-05-12 — PR #165** Legal/contact pages for Stripe site review. Squash-merged into main as `ba45cfe`. Three new SPA pages: `/policies` (consolidated Customer Policies — refunds, cancellation, U.S. legal/export restrictions, promotional terms; anchor IDs per subsection), `/contact` (phone (470) 949-4131, support/sales/billing/security inboxes, response-time SLAs), `/promotions` (stub satisfying Policies §6.2). New `MarketingFooter` component (`components/common/MarketingFooter.tsx`) extracted from inline landing footer; mounted on `/landing`, `/pricing`, `/contact-sales` so all four legal links (Privacy/Terms/Policies/Contact) are reachable from every marketing surface. Component reuses existing `landing-footer*` CSS — must be inside a `.landing-page` wrapper (documented in JSX comment). Privacy and Terms closing sections updated to point at `/contact` + `/policies` with correct per-area inboxes; stale `hello@` mailto removed everywhere. Mailing address left as TODO comments in both `ContactPage.tsx` and `PoliciesPage.tsx`, rendered publicly as "available on request" until P.O. Box is purchased. tsc + eslint clean. +- **2026-05-08 — PR #164** Plan taxonomy reconciliation + `INTERNAL_TESTER_EMAILS` allowlist + Stripe sync script + page-title fix + frontend taxonomy followups + doc refresh. 5 commits on `feat/billing-plan-taxonomy` from main (`dad5e1f`); HEAD `2c9f5e9`. Migration `4ce3e594cb87` renames `plan_limits.plan='team'` → `'enterprise'` and adds `starter` row (caps interpolated between free and pro: `max_trees=10`, `sessions=75`, `ai=15/mo`). Resource visibility (`Tree.visibility='team'`, `StepLibrary.visibility='team'`) is a separate domain and intentionally untouched. New `backend/scripts/sync_stripe_plan_ids.py` upserts `plan_billing` rows from Stripe products by exact name match — annual fields stay NULL by design (user explicitly skipping annual pricing for exit flexibility). `Settings.is_internal_tester` + `is_self_serve_active_for` centralize the allowlist + global-flag check; new `get_current_user_optional` dep; `/config/public` honors allowlist for authenticated callers; `/auth/register` allows allowlisted emails without invite code. LandingPage page-title bug — `—` inside JSX attribute strings was rendering as 6 literal characters in browser tabs; replaced with literal em dash. PageMeta default tagline updated from "Decision Tree Platform" to "AI-Powered Troubleshooting for MSPs". 86/86 passing across subscription/billing/plan/invite/admin sweep; tsc + lint clean. See `.ai/DECISIONS.md` for the two architectural entries (taxonomy reconciliation, allowlist). - **2026-05-06 — PR #163** Seed test users marked email-verified. Squash-merged into main as `dad5e1f`. - **2026-05-06 — PR #162** Self-serve signup Phase 2 (frontend cutover). 18 commits across Tasks 27–44 of the plan. Backend remainders + frontend billing foundation + auth surfaces (OAuth + accept-invite + verify-email) + welcome wizard + dashboard redesign (TrialPill, NextStepCard, unified checklist) + public surfaces (`/pricing`, `/contact-sales`) + beta-signup deprecation. Squash-merged into main as `f1be3ab`. Single alembic head was `c6cbfc534fad` (no new migrations in Phase 2; PR #164 adds `4ce3e594cb87`). - **2026-05-02 — PR #159** In-product User Guides rewrite. Merged into `main`. Replaced 15 feature-dump guides with 43 problem-oriented Diátaxis how-tos grouped under 10 categories. Dropped Maintenance Flows / AI Assistant / Flow Assist Sparkles (UI no longer exists). Renamed Step Library → Solutions Library. Authored 14 net-new how-tos for FlowPilot-era surfaces (tasklane keyboard flow, what-we-know, resolve, escalate, record-fix-outcome, post-docs-to-ticket, share-update, pause-and-leave, build-script-from-scratch, open-suggested-flow, pin-a-flow, invite-teammate, etc.). Schema additions: `category`, optional `relatedSlugs`; hub renders category sections; detail page renders related-guides footer. Fixed rendering bug where `**bold**` in `step.tip` rendered literally. Killed misleading "N sections" subtitle on guide cards. Browser-verified against engineer + owner login (sidebar labels, account sub-pages, pilot-screen header buttons, Tasks panel, integration form). Two unverified items intentionally deferred: change-teammate-role (requires non-owner test member to inspect role-change control) and detailed Resolve / Escalate modal contents (Resolve gated by 6 pending tasks in test data). tsc and Vite build clean. diff --git a/.ai/HANDOFF.md b/.ai/HANDOFF.md index 594a0ccc..2dfebda9 100644 --- a/.ai/HANDOFF.md +++ b/.ai/HANDOFF.md @@ -2,47 +2,59 @@ # HANDOFF.md -**Last updated:** 2026-05-08 +**Last updated:** 2026-05-12 -**Active task:** PR #164 (`feat/billing-plan-taxonomy`) open in Gitea with 5 commits at head `2c9f5e9`. Closes the last code blockers for self-serve cutover. After merge, only manual ops remain (Stripe live-mode Dashboard config, Railway prod env vars, internal validation, flag flip). PR #162 and #163 merged into main this session as squash commits `f1be3ab` and `dad5e1f`. +**Active task:** Phase O cutover for self-serve signup. All code blockers are closed on `main` (PR #164 `3f04911`, PR #165 `ba45cfe`, PR #167 `e50a215`). **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. + +**Bug pending capture (2026-05-12):** User reported finding a bug during the session but did not provide details — planning to send a screenshot via the VS Code extension GUI in the next session. Ask for the screenshot at session start, then triage. No further context yet. ## Where this session ended -PR #164 commits (oldest → newest): +PR #167 merged (`e50a215 Merge pull request '...create_site_admin.py...'`). Bootstrap script for the site-wide super-admin: `backend/scripts/create_site_admin.py`. Idempotent — creates or promotes a `super_admin` on any env. Reads `--email`, optionally `--send-reset` (mails the reset link) or `--print-reset` (prints to stdout) or `--promote-only`. Uses `ADMIN_DATABASE_URL` for BYPASSRLS. Verified end-to-end against the deployed Railway backend container by the user via `railway ssh` ("we're good now" confirmation, 2026-05-12 evening). Intended prod invocation when Stripe/EIN clears: `railway run python -m scripts.create_site_admin --email michael@resolutionflow.com --send-reset` from inside the backend container shell (not local Windows — local Python lacks the dep tree). -1. `ba36c47 feat(billing): reconcile plan taxonomy and add Stripe sync script` — migration `4ce3e594cb87` renames `plan_limits.plan='team'` → `'enterprise'` (defensive update of any subscriptions on the old slug; dev had zero), adds `starter` row with caps interpolated between free and pro. Code rename across schemas, `Subscription` paid-plan checks, admin endpoints, frontend `useSubscription`. Resource visibility (`Tree.visibility='team'`, `StepLibrary.visibility='team'`) is a separate domain and intentionally untouched. New `backend/scripts/sync_stripe_plan_ids.py` — idempotent upsert of `plan_billing` rows from Stripe products by exact name match, picks active monthly recurring price, leaves annual fields NULL by design. -2. `a628b24 chore(dev): pass STRIPE_* env to backend container` — wires `STRIPE_*` + `SELF_SERVE_ENABLED` + `INTERNAL_TESTER_EMAILS` through `docker-compose.dev.yml`. New repo-root `.env.example`. -3. `8494366 feat(billing): add INTERNAL_TESTER_EMAILS allowlist for self-serve soft cutover` — `Settings.is_internal_tester` + `is_self_serve_active_for`, new `get_current_user_optional` dep, `/config/public` honors allowlist for authenticated callers, `/auth/register` allows allowlisted emails without invite code. 5 regression tests in `test_config_public.py`. -4. `8649a4a docs: refresh CURRENT-STATE, ROADMAP, README, DECISIONS for self-serve cutover` — CURRENT-STATE bumped with PR #159–164 entries; ROADMAP got a "Status as of 2026-05-07" preamble (historical content preserved underneath); README fixed legacy `patherly_postgres` and `UI-DESIGN-SYSTEM.md` references; DECISIONS appended two entries. -5. `2c9f5e9 fix(frontend): page-title — escapes + propagate plan taxonomy through frontend types` — `LandingPage.tsx` had `—` (six literal characters) inside JSX attribute strings, rendering as literal text in browser tabs. Replaced with literal em dash. PageMeta default tagline updated from "Decision Tree Platform" (stale) to "AI-Powered Troubleshooting for MSPs". Fixed TS errors that surfaced from the previous taxonomy commit not propagating through frontend types — `types/{account,admin,billing}.ts`, `admin/{AccountsPage,InviteCodesPage}.tsx`, `AccountSettingsPage.tsx`, `subscription/CheckoutButton.tsx`. tsc -b clean, lint clean. +Earlier in the session, PR #165 squash-merged (`ba45cfe feat(legal): add /policies, /contact, /promotions pages + MarketingFooter (#165)`): -Stripe state (test mode via MCP, livemode=false): 3 active products (Starter $19.99/mo, Pro $29.99/mo, Enterprise no price); leftover Enterprise `$500/mo` test price archived (had to clear `default_price` on the product first); `plan_billing` populated for all three tiers in dev DB via `sync_stripe_plan_ids.py`. +- **New pages**, all SPA, matching existing `/privacy` and `/terms` pattern: `/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 existing `landing-footer*` CSS — must be rendered inside a `.landing-page` wrapper (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` + `/policies` and the correct inbox per area (`security@` and `support@` respectively). Stale `hello@resolutionflow.com` mailto removed everywhere. +- **Mailing address** left as TODO comments in `ContactPage.tsx` and `PoliciesPage.tsx` (one each). Rendered publicly as "available on request — email support@". Fill in when the P.O. Box is purchased. -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 handoff as "do not stage"). +`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. -Single alembic head: `4ce3e594cb87` after PR #164 merges (was `c6cbfc534fad`). +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 -1. Verify PR #164 CI green: - `curl -fsSL https://gitea.resolutionflow.com/api/v1/repos/chihlasm/resolutionflow/commits/2c9f5e9/status | python -m json.tool` -2. Squash-merge PR #164. -3. **Phase O manual ops** (after merge): - - Stripe Dashboard live-mode: 3 Products, 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. - - 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. - - Run sync against prod backend: `railway run python -m scripts.sync_stripe_plan_ids`. Verify `plan_billing` rows have `sk_live_*` price IDs. -4. Internal validation (Phase O Task 46): 9 scenarios with internal testers whose emails match `INTERNAL_TESTER_EMAILS`. -5. 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. +**First thing next session:** ask the user for the bug screenshot (mentioned at end of 2026-05-12 session — they were planning to send it via VS Code extension GUI). Triage that before resuming Phase O work. -## Open issues from this session (non-code, user-side) +After that — **Phase O manual ops, all user-side, all gated on EIN landing first:** + +1. **EIN application** (user, 2026-05-13 via IRS.gov). Without this, Stripe live-mode can't activate. +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; will 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 its site-verification step. +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 the new `create_site_admin.py` script (PR #167, on main as `e50a215`). Run from inside the backend container shell (not local Windows): `railway ssh --service=` then `python -m scripts.create_site_admin --email michael@resolutionflow.com --send-reset`. Reset email arrives at `michael@resolutionflow.com`, user clicks the link, sets a password, logs in as super_admin. Idempotent — safe to re-run. +6. **Sync Stripe → DB**: `railway run python -m scripts.sync_stripe_plan_ids` (or via `railway ssh` for the same reason as #5). 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 per SOA `dns1.registrar-servers.com.`). When `www` was reconfigured in Railway, the apex record got dropped from the zone. `www` works (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` (delete `resolutionflow.com` and `www.resolutionflow.com`) + `#dns` clear host cache + `#sockets` flush. Cert IS valid on the wire (proven by `curl --resolve` returning 200 OK from the user's box). +- **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 ("want to be able to exit if necessary without breaching any terms"). Schema columns (`annual_price_cents`, `stripe_annual_price_id`) preserved as nullable for future re-enable. `sync_stripe_plan_ids.py` leaves annual fields NULL. +- 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 from this session at `~/.gstack/projects/chihlasm-resolutionflow/abc-feat-self-serve-signup-phase-2-design-20260507-112020.md`. Captures the "documentation builder" thesis (cut branching Flows from pilot UI, focus on Day 1 onboarding checklist + 3 deep-capture procedures + Hudu/IT Glue/CW output). Pre-build assignment: 3 cold calls with external Directors of Onboarding before scoping the build. NOT yet adopted as roadmap — gated on the validation calls. -- Frontend lint shows 3 warnings in `coverage/` (auto-generated). Untouched. -- Backend env: `SALES_LEAD_RECIPIENT_EMAIL`. 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`. +- 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 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 — use for first prod admin, recovery, or promoting future teammates. Idempotent. Three modes: `--send-reset`, `--print-reset`, `--promote-only`. Run from inside the deployed backend container via `railway ssh`, not from local Windows (local Python lacks the dep tree). +- 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`. diff --git a/.ai/SESSION_LOG.md b/.ai/SESSION_LOG.md index 823781ec..aecec28e 100644 --- a/.ai/SESSION_LOG.md +++ b/.ai/SESSION_LOG.md @@ -12,6 +12,58 @@ --- +## 2026-05-12 ~06:30 UTC — Claude — PR #167 (site-admin bootstrap script) merged; bug pending capture + +**Accomplished:** + +- User reported being unable to log into prod with `admin@resolutionflow.example.com` — that's the dev seed email (`.example.com` is a documentation TLD), only present in dev. Prod has no admin user at all because `seed_test_users.py` doesn't run in prod, self-serve is still gated, and even when it flips on signup creates `owner` roles not `super_admin`. +- Designed and built `backend/scripts/create_site_admin.py` — idempotent CLI script for creating or promoting a site-wide super-admin on any environment. Three modes: `--send-reset` (mails reset link), `--print-reset` (stdout reset link), `--promote-only` (promote existing user without creating). Creates an `Account` first, then a `User` with `is_super_admin=true`, `account_role='owner'`, `email_verified_at` stamped at creation, `password_hash=NULL` (forces the reset flow on first login). Uses `ADMIN_DATABASE_URL` (BYPASSRLS) — required because `users` is RLS-enabled and the script has no tenant context at bootstrap. Reset token mints via existing `create_password_reset_token` helper, hashes JTI into `password_reset_tokens` row matching the `/auth/password/forgot` shape. +- Smoke-tested all three paths in the dev container before pushing: fresh create on a new email (Account + User + reset URL emitted), idempotent re-run on same email (SKIP message + new reset URL), `--promote-only` on a user with `password_hash=NULL` (promotes + issues reset). Cleaned up the dev test row + account afterwards. +- Initial bug: had `used: false` in the `password_reset_tokens` INSERT — actual column is `used_at` (nullable timestamp, NULL means "not used"). Fixed before pushing. +- PR #167 opened, CI green, squash-merged into main as `e50a215`. Remote branch `feat/site-admin-script` auto-deleted. +- User confirmed end-to-end success on prod via `railway ssh --service=` then `python -m scripts.create_site_admin ...` ("we're good now"). Specific service name not captured. First prod super-admin row now exists in the prod DB. +- Stripe live-mode activation block traced to EIN, not code (user does not yet have an EIN for ResolutionFlow, LLC). Applying via IRS.gov 2026-05-13. Mailing-address decision: home address into Stripe's **private** business profile temporarily so live-mode isn't blocked on the P.O. Box; public `ContactPage`/`PoliciesPage` stays "available on request". Stripe accepts address update later without re-verification. +- PR #166 (docs handoff for PR #164/#165 merges + EIN decision) still open from earlier in this same session — was never merged. This entry rebases the docs branch onto current main (which now includes PR #167) and adds the PR #167 narrative + bug-pending state so a fresh session has the full picture in one merge. +- User reported finding a bug in a UI surface but did not provide details — planning to send a screenshot via the VS Code extension GUI in the next session (CLI is unreliable for them). Next session: ask for the screenshot at session start, then triage. + +**Left for next session:** + +- Get the bug screenshot from the user, triage, fix or scope. +- Otherwise everything that was on the prior entry's left-for-next-session still stands: EIN application Tuesday 2026-05-13, then Stripe live-mode setup, apex DNS at Namecheap, Railway prod env vars, internal validation, flag flip. + +**Files touched (all merged to main via PR #167 squash `e50a215`):** `backend/scripts/create_site_admin.py` (new, ~270 lines including docstring). Plus `.ai/HANDOFF.md`, `.ai/SESSION_LOG.md` on `docs/handoff-pr-165-merge` (PR #166, awaiting merge). + +--- + +## 2026-05-12 05:30 UTC — Claude — PR #164 + #165 merged; Stripe activation reported blocked + +**Accomplished:** + +- Resumed from compacted context. Confirmed PR #164 (`feat/billing-plan-taxonomy`, head `2c9f5e9`) was already CI-green at session start and squash-merged into main as `3f04911` earlier in the session (occurred pre-compaction; reflected in the prior HANDOFF revision). Branch auto-deleted on remote. +- User raised the legal/contact pages question in conversation. Verified existing state of `frontend/src/pages/{PrivacyPage,TermsPage}.tsx` — both already contain real, dated content (last updated 2026-03-21) but are SPA-rendered. Discussed Stripe's site-review needs with the user and agreed to build a consolidated Customer Policies page plus a Contact page (now that the user has a business phone number) plus a Promotions stub to satisfy Policies §6.2 cross-reference. User authorized the work. +- Built PR #165 (`feat/stripe-legal-pages`, head `545b2ad`): + - **`/policies` — `frontend/src/pages/PoliciesPage.tsx`** (new). Consolidated Customer Policies doc, 8 sections with anchor IDs per subsection so Stripe (or a support email) can deep-link: customer service contact (with phone (470) 949-4131), return policy (n/a — SaaS), refund / dispute policy, cancellation policy, U.S. legal and export restrictions (Georgia governing law, OFAC / BIS compliance, sanctioned-jurisdiction exclusion), promotional terms (general + cross-ref to `/promotions`), changes-to-policies, relationship-to-other-agreements. Mailing address left as in-source `TODO` comment, rendered publicly as "available on request — email support@" until P.O. Box is purchased. + - **`/contact` — `frontend/src/pages/ContactPage.tsx`** (new). Phone **(470) 949-4131**, all four inboxes (`support@`, `sales@`, `billing@`, `security@`), response-time SLAs, mailing-address placeholder, link to `/contact-sales` for the lead-gen Calendly flow (distinct surface — kept both routes intentionally). + - **`/promotions` — `frontend/src/pages/PromotionsPage.tsx`** (new). One-paragraph stub stating no promotions currently active. Will be appended to when offers run; satisfies Policies §6.2's cross-reference. + - Routes wired in `frontend/src/router.tsx` as 3 new public lazy-loaded routes alongside existing `/privacy`, `/terms`, `/pricing`, `/contact-sales`. + - **`MarketingFooter` — `frontend/src/components/common/MarketingFooter.tsx`** (new, second commit). Extracted from the inline landing footer (26 lines → 1 line at the call site). Mounted on `/landing`, `/pricing`, `/contact-sales` so all four legal links (Privacy / Terms / Policies / Contact) are reachable from every marketing surface — including the page Stripe's reviewer spends the most time on (`/pricing`). Reuses existing `landing-footer*` CSS in `frontend/src/styles/landing.css` — must be rendered inside a `.landing-page` wrapper because `--lp-*` vars are scoped there (documented in a JSX comment). All three current call sites already wrap in `.landing-page`, so landing renders pixel-identically and the two new mount sites match. + - **Privacy and Terms closing sections** updated to point at `/contact` + `/policies` with correct per-area inboxes (`security@` for Privacy, `support@` for Terms). Stale `hello@resolutionflow.com` mailto removed everywhere. +- `tsc --project tsconfig.app.json --noEmit` clean, `eslint` clean. Local `vite build` and `tsc -b` blocked by root-owned `node_modules/.tmp` and `node_modules/.vite-temp` cache directories — CI rebuilds from a clean env and was green. +- PR #165 opened at `gitea.resolutionflow.com/chihlasm/resolutionflow/pulls/165`, CI passed, squash-merged into main as `ba45cfe`. Remote branch `feat/stripe-legal-pages` auto-deleted. +- User reports continued trouble activating Stripe live mode. After follow-up: the real blocker is the EIN — ResolutionFlow, LLC does not have one yet, and Stripe requires a tax ID before it will activate live mode. User is applying via IRS.gov on 2026-05-13. Updated HANDOFF.md to remove the earlier speculation list and record EIN as the named blocker, with the P.O. Box / mailing address called out as the likely-next blocker (Stripe live-mode also requires a business mailing address). Apex DNS at Namecheap is still pending but only matters after the business profile is accepted (site verification is a downstream step). +- Mailing-address decision: user is going with the home-address-temporarily approach for Stripe so live-mode isn't blocked on the P.O. Box. Home address goes into Stripe's **private** business profile only — the **public** `TODO: replace with full mailing address` in `ContactPage.tsx` and `PoliciesPage.tsx` stays as "available on request" until the P.O. Box is purchased. Stripe accepts updating the address later without re-verification, so swapping in the P.O. Box when it arrives is non-disruptive. + +**Left for next session:** + +- Check in on whether the EIN application went through and whether the P.O. Box / mailing address is sorted. Both are pure user-side ops; no code work to do until Stripe accepts the business profile. +- Once Stripe is activated: Stripe Dashboard live-mode product/price/webhook setup, Railway prod env vars, `railway run python -m scripts.sync_stripe_plan_ids` against prod, 9-scenario internal validation, flag flip. +- Apex DNS at Namecheap (still missing; only matters once Stripe runs its site-verification step). +- Mailing address TODO in `ContactPage.tsx` and `PoliciesPage.tsx` (one each) — fill in when P.O. Box is purchased. + +**Files touched (all merged to main via PR #165 squash `ba45cfe`):** `frontend/src/pages/ContactPage.tsx` (new), `frontend/src/pages/PoliciesPage.tsx` (new), `frontend/src/pages/PromotionsPage.tsx` (new), `frontend/src/components/common/MarketingFooter.tsx` (new), `frontend/src/router.tsx`, `frontend/src/pages/LandingPage.tsx`, `frontend/src/pages/PricingPage.tsx`, `frontend/src/pages/ContactSalesPage.tsx`, `frontend/src/pages/PrivacyPage.tsx`, `frontend/src/pages/TermsPage.tsx`. Plus `.ai/HANDOFF.md`, `.ai/CURRENT_TASK.md`, `.ai/SESSION_LOG.md` on the `docs/handoff-pr-165-merge` branch (this entry). + +--- + ## 2026-05-08 03:30 UTC — Claude — PR #164 self-serve cutover code blockers, doc refresh, page-title bug, DNS triage **Accomplished:**