Updates HANDOFF.md, CURRENT_TASK.md, SESSION_LOG.md to reflect this
session's work: PR #164 ready for review (5 commits closing the last
self-serve cutover code blockers), Phase O manual-ops sequence as the
resume point, and the apex-DNS / Edge-HSTS issues open on the user's
side.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes that surfaced together:
1. LandingPage.tsx had `—` in a JSX attribute string — JSX attribute
strings don't process JS escape sequences, so the literal six-character
"—" was rendering in the browser tab title and OG description
instead of the intended em dash. Replaced with the literal em dash
character. Same pattern was previously valid because every other use
of `\u...` in the codebase is inside a JS string (regular `'...'`
string literal in TS, or `{...}` expression in JSX), where escapes
resolve at compile time. Verified by grep — LandingPage was the only
site with the bug.
2. PageMeta default fallback tagline was "Decision Tree Platform" — a
stale tagline from before the FlowPilot pivot. Updated to the current
"AI-Powered Troubleshooting for MSPs" (matches index.html and brand
positioning). Default branch is rarely hit since every page passes
a title, but cleaner.
While building, hit TS errors that revealed the prior taxonomy commit
(team -> enterprise + add starter) didn't propagate through the frontend.
Cleared all of them:
- types/account.ts, types/admin.ts: Subscription.plan, AdminAccountCreate.plan,
InviteCodeCreateRequest.assigned_plan literals updated to the new tax.
- types/billing.ts: dropped 'team' from CheckoutPlan (was hybrid old+new).
- admin/AccountsPage.tsx, admin/InviteCodesPage.tsx: state-type literals,
select onChange casts, and the visible <option> rows updated. PLAN_OPTIONS
in InviteCodesPage now has all four tiers with correct labels.
- AccountSettingsPage.tsx: `plan !== 'team'` -> `'enterprise'`, CheckoutButton
prop value too.
- subscription/CheckoutButton.tsx: prop type was 'pro' | 'team', updated
to 'starter' | 'pro' | 'enterprise' with matching planLabels.
Verified: tsc -b clean, lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls the public docs forward to match the current state of the repo. No
behavior changes — every edit is informational.
- CURRENT-STATE.md: bump date to 2026-05-07; add entries for PR #159 (Diátaxis
User Guides), #160 (sidebar IA + account redesign), #161 (self-serve Phase 1
backend), #162 (Phase 2 frontend cutover), #163 (seed users email-verified),
#164 (open: taxonomy + INTERNAL_TESTER_EMAILS allowlist). Refresh "What's
In Progress" and "What's Next" to reflect Phase O cutover as the active work.
- 03-DEVELOPMENT-ROADMAP.md: add a "Status as of 2026-05-07" preamble at the
top so the months-stale historical content underneath is clearly framed as
historical record. Replace stale "In Progress" rows (PR #114, ConnectWise
Advanced) with current ones (#164 cutover, external Director-of-Onboarding
validation calls). Add Phase O cutover checklist as the new near-term
priority section. Mark search-and-recall complete (shipped via Voyage AI
embeddings).
- README.md: replace `docker start patherly_postgres` (legacy container name)
with `docker compose -f docker-compose.dev.yml up -d`. Repath project tree
from `patherly/` to `resolutionflow/` and add `.ai/` + `scripts/` directories.
Replace `UI-DESIGN-SYSTEM.md` (superseded) with `DESIGN-SYSTEM.md` in the
documentation table; add `AGENTS.md`, `PROJECT_CONTEXT.md`, `PRODUCT.md`.
- DECISIONS.md: append entries for the two architectural decisions made today
— plan taxonomy reconciliation (rename team→enterprise, add starter) and
the INTERNAL_TESTER_EMAILS allowlist for self-serve soft cutover.
- .env.example: add INTERNAL_TESTER_EMAILS line (user edit, paired with the
backend allowlist that landed in the prior commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase O Task 46 needs internal validation of the full self-serve flow
against the prod backend before flipping SELF_SERVE_ENABLED public. This
adds the per-email allowlist that bypasses the global flag for specific
authenticated users.
- INTERNAL_TESTER_EMAILS: comma-separated list, parsed by a Pydantic
field_validator into a normalized lowercase list. Settings.is_internal_tester
and Settings.is_self_serve_active_for centralize the allowlist + global-flag
check; both endpoints below call the latter.
- New get_current_user_optional dep — best-effort auth that returns None
on missing/invalid token instead of 401. Used by /config/public so the
same endpoint serves anonymous public callers and authenticated allowlist
members.
- /config/public now accepts optional auth and returns self_serve_enabled=True
for authenticated allowlist members even when the global flag is off.
Anonymous callers always see the global flag.
- /auth/register replaces the SELF_SERVE_ENABLED check with the helper so a
registering email on the allowlist can join without an invite code.
Non-allowlist emails still 400 when self-serve is off.
- docker-compose.dev.yml passes SELF_SERVE_ENABLED + INTERNAL_TESTER_EMAILS
through; backend/.env.example documents both.
Tests cover: allowlisted authenticated user sees true, non-allowlisted
authenticated user sees the global flag, anonymous calls ignore the
allowlist, allowlisted email registers without invite code, non-allowlisted
email still blocked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The backend container had no Stripe env vars wired through compose, so
sync_stripe_plan_ids.py and any in-app Stripe calls would short-circuit
even when sk_test_ was set in the repo .env. Adds STRIPE_SECRET_KEY,
STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET pass-throughs.
Also flips REQUIRE_INVITE_CODE to false in the dev compose (matches the
working state on this machine — Phase 2 self-serve has been gating that
behavior on SELF_SERVE_ENABLED + the upcoming INTERNAL_TESTER_EMAILS
allowlist anyway).
Adds a repo-root .env.example documenting the variables compose itself
reads (REPO_ROOT, POSTGRES_PORT, secrets) — separate from
backend/.env.example which documents the backend service env.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The marketing surface (PricingPage, Stripe products) was wired for
"Starter / Pro / Enterprise" while the backend was on "free / pro / team",
leaving plan_billing unseeded and BillingPlan accepting a literal that
violated the FK to plan_limits.
This change:
- Migration 4ce3e594cb87: defensive UPDATE of any subscriptions on
plan='team' to 'enterprise' (dev has zero), renames the plan_limits
row team -> enterprise, inserts a starter row with caps interpolated
between free and pro (max_trees=10, sessions=75, ai=15/mo).
- Renames the plan tier across schemas (invite_code, billing, admin,
subscription comment), is_paid/has_pro_entitlement checks in the
Subscription model, admin/admin_dashboard plan validators, and the
frontend useSubscription isPaidPlan check. Resource visibility uses
the same string 'team' in a separate domain (Tree/StepLibrary
visibility) and is intentionally untouched.
- New backend/scripts/sync_stripe_plan_ids.py: idempotent upsert of
plan_billing rows from Stripe products by exact name match. Picks
the active monthly recurring price for tiers that have one; leaves
annual fields NULL by design. Works against test or live keys.
- Test fixture updates: conftest seeds the new taxonomy, the public
plans helper is a true upsert so tests can override max_users, and
team -> enterprise across test_admin_plan_limits and test_invite_plan.
Verified: 86/86 passing across the subscription/billing/plan/invite/
admin sweep; sync script run against test mode populates plan_billing
correctly for all three tiers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>