Architecture & Technical Design¶
Last updated: July 17 2026 — Live access and route guidance now live in access-control.md and startupOS.md; this doc is the broader technical map, not the access source of truth.
Before making any money-/user-data change, consult
.github/copilot-instructions.md§3 — the change-ripple matrix. It lists every surface (pages + endpoints) that reads the same data, so you don't accidentally ship a change that breaks a sibling feature (e.g. adding a new "share" path when sharing already exists via/api/share-links+SharedView.tsx).Current live systems note: The unified Archetiq app runs on
https://www.archetiq.comandhttps://demo.archetiq.com. For access policy, role names, and live gates, use access-control.md. For deployment, use deployment.md.
Stack¶
| Layer | Technology |
|---|---|
| Frontend | React 19 + TypeScript (strict mode) + Vite 6 |
| Styling | Tailwind CSS 4 |
| Animation | motion/react 12 |
| Icons | lucide-react |
| Charts | Chart.js 4 + react-chartjs-2, Recharts |
| Routing | react-router-dom v7 |
| Auth | VPS JWT (issued by /api/auth/* endpoints; stored in localStorage) |
| Backend API | Express.js on Hostinger VPS (port 5002, PM2, Node 20) |
| Database | MongoDB (personality_db) on VPS |
| Payments | Stripe Checkout → VPS webhook handler (/api/stripe/*) |
| AI | Google Gemini (@google/genai) + Ollama (local LLM) |
| RAG | MongoDB lexical retrieval + Ollama summarization |
| PDF Export | html2canvas-pro + jsPDF |
Note: Firebase has been completely removed. Auth is handled by the VPS (
/api/auth/*endpoints issuing 30-day JWTs). All data is stored in MongoDB. Thesrc/api/folder holds the VPS HTTP client (client.ts), JWT auth helpers (auth.ts), a localStorage shim (config.ts), and the MongoDB data layer (db.ts).
AI & RAG Architecture¶
Ollama (VPS Docker)¶
Ollama runs as a Docker container on the VPS at 127.0.0.1:11434, exposed publicly via ollama.archetiq.com with Bearer token auth.
Installed models:
| Model | Size | Purpose |
|---|---|---|
gemma4:latest |
9.6 GB | Primary local LLM for summarization |
qwen2.5-coder:1.5b-base |
986 MB | Code/analysis tasks |
llama3.2:1b |
1.3 GB | Lightweight tasks |
Ollama client: server/src/utils/ollamaClient.js
- ollamaGenerate(prompt, timeoutMs) — generic text generation
- summariseCareerPatterns(patterns) — compresses career transition patterns into market intelligence
- ollamaIsAvailable() — health check
RAG Collections¶
| Collection | Purpose |
|---|---|
archetiq_knowledge_chunks |
Platform knowledge corpus (Personality-Test) |
startupos_framework_chunks |
NEW: StartupOS framework and module guides (for AI Mentor) |
archetiq_user_memory |
User episodic memory (RAG-005 pending) |
archetiq_team_memory |
Team episodic memory (RAG-006 pending) |
archetiq_rag_shadow_log |
Shadow mode logs with TTL retention |
Retrieval utility: server/src/utils/knowledge/retrieval.js
- getKnowledgeChunks(moduleType, limit, options) — BM25 lexical retrieval
- buildKnowledgeRetrievalSnippet(moduleType, limit, options) — builds context snippets for AI calls
RAG Implementation Status¶
| Phase | Status | Description |
|---|---|---|
| Phase 1 | ✅ Complete | Knowledge corpus ingest + retrieval utility |
| Phase 2 | ✅ Complete | Shadow mode (parallel to Gemini, logging only) |
| Phase 3 | 🚧 In Progress | Ollama primary + Gemini fallback rollout |
| Phase 4 | ⬜ Pending | User/team memory writes with PII sanitization |
Career Insights Ollama Loop¶
The career insights system uses a 3-tier Ollama learning loop:
- Tier 1 (on-the-fly): Ollama summarization of career patterns on request
- Tier 2 (cache): Weekly aggregate stored in
archetiq_user_memory - Tier 3 (cron):
careerInsightsCron.jsruns Sundays 02:30 UTC to pre-compute aggregates
Infrastructure¶
Browser
└─ HTTPS → archetiq.com (Nginx, Let's Encrypt SSL)
├─ /api/* → proxy → Express :5002 (API + Stripe webhook)
├─ /health → proxy → Express :5002
└─ /* → static files from /opt/personality-app/public/ (Vite dist)
- VPS: Hostinger, IP
76.13.211.100 - Static files:
/opt/personality-app/public/(Vitedist/uploaded via SCP) - Backend app dir:
/opt/personality-app/ - Process manager: PM2 (
personality-app, id 2) - MongoDB:
mongodb://localhost:27017/personality_db - DEPRECATED: The
startup.archetiq.comdeployment, its directory (/opt/startup-playbook), and its database (startupplaybook) are now obsolete and will be removed post-migration.
Application Structure¶
src/
pages/ — Full-page route components
// ... existing pages
startup/ — ARCH mission runtime (.tsx)
ArchMissionPage.tsx
components/ — Shared UI components
store/ — NEW: Zustand stores for StartupOS features
startupStore.ts
sprintStore.ts
aiMentorStore.ts
data/ — Static JSON and data helpers
startupArchMissionContract.ts — ARCH mission schema
// ... other directories
server/src
server.js — Express entry point
models/ — NEW: Mongoose schemas for all collections
User.js
Organisation.js
Startup.js
StartupJourney.js
Sprint.js
Cohort.js
// ... other existing models
middleware/ — auth.js, adminMiddleware, etc.
routes/ — One file per resource
// ... existing routes
startups.js — NEW
sprints.js — NEW
cohorts.js — NEW
lecturer.js — NEW
decisionLab.js — NEW
utils/ — Utilities and business logic
API Design¶
All backend routes are prefixed /api/. Auth is validated via VPS-issued JWT in the Authorization: Bearer <token> header (authMiddleware in middleware/auth.js).
Key route groups:
| Prefix | Description |
|---|---|
/api/auth |
Auth endpoints (no token required). POST /register (create account, returns JWT), POST /login (verify credentials, returns JWT), POST /forgot-password (sends SMTP reset email), POST /reset-password (sets new password via token), GET /me (own user doc, requires token) |
/api/users |
User profiles. GET /me (own), PUT /me (own update — plan/payment fields excluded), GET /:uid (self or admin), PUT /:uid (admin — general field update), PUT /:uid/assign-coach (org admin) |
/api/ai |
Gemini AI endpoints. POST /generate (structured JSON, 20/hr), POST /chat (multi-turn AiGuide, 30/hr per user), POST /draft-life-plan (Plan Assembly Engine — scopes: ikigai, vision-mission, pillars, all, field, capability-gap, one-pillar; builds context from user's Profile + Foundation + Life Wheel + Wellness + Money data + Life Path milestones from server/src/utils/careerPaths.json; rate-limited 10/hr per user shared with consistency/alignment checks). scope=one-pillar generates a single pillar goal object (~85% output token reduction vs scope=all). POST /check-ikigai-alignment — verifies coherence of all 4 Ikigai circles + Vision + Mission, returns { verdict, critique, suggestion? }. All endpoints use model gemini-2.5-flash. |
/api/admin |
Admin-only atomic operations. POST /change-plan — atomically updates plan, planExpiresAt, coachAppStatus, and the coaches collection in one DB round-trip. All plan/payment state changes must go through this endpoint — never call /api/users/:uid directly for plan changes. |
/api/responses |
Assessment responses |
/api/coaches |
Coach profiles. GET supports ?email= and ?referralCode= filters — must be applied as DB filters, not ignored. POST supports { upsert: true } for create-or-reactivate by email (used internally by /api/admin/change-plan). See access-control.md §8 for the April 2026 filter bug. |
/api/coach-tools |
Coach-only utility endpoints for org-selected coaches (orgRole === 'coach'). GET /manual streams the Archetiq demo PDF. POST /demo-reseed rebuilds demo org users, responses, and teams in a separate demo database (DEMO_MONGO_URI, fallback personality_demo_db). |
/api/health |
Native health sync. POST /sync — writes workout/sleep/steps data from Capacitor Health API (mobile only). GET /summary — returns user's last 7 days of health metrics. |
/api/organisations |
Orgs; POST /:id/activate-plan, POST /:id/deactivate-plan |
/api/cohorts |
Coach cohorts (CRUD) |
/api/teams |
Teams; POST /join, POST /leave, GET /:code/members |
/api/stripe |
Checkout, webhook, verify-session; fires agent commission to agents-service (port 5003) after payment |
/api/conversations |
Messaging. GET / — list user's conversations; POST / — create conversation (uses randomUUID() string _id); GET /:id/messages — paginated message list; POST /:id/messages — send message. Frontend polls conversations every 5 s and messages every 3 s. |
/api/notifications |
User notifications. GET / — list unread/recent. PUT /mark-read — mark all read. DELETE / — delete all notifications for current user (used by "Clear all" button in Layout). |
/api/share-links |
Report share tokens |
/api/promo-codes |
Promo code lookup + redeem |
/health |
Health check |
/api/startups |
NEW: Startup CRUD and workspace progress tracking. |
/api/startup-v2 |
Retired: legacy founder workspace route removed after ARCH cutover. |
/api/startup-arch |
NEW: ARCH mission runtime, migration, coach, outputs, and telemetry. |
/api/sprints |
NEW: Sprint and task management for startups. |
/api/cohorts |
UPDATED: Now used for Institution cohort management. |
/api/lecturer |
NEW: Endpoints for the Lecturer Dashboard. |
/api/decision-lab |
NEW: Endpoints for the Decision Intelligence Lab. |
Note: Compatibility comparison (
RelationshipLensPage.tsx,/compare) is computed entirely client-side bysrc/services/compatibilityEngine.ts— there is no/api/compareserver route.
User Journey (current, June 26 2026)¶
Navigation sections by plan:
Home — Dashboard (all users)
Discover — Assessment, My Report (all users)
Explore — Book Guide, Mind & Body, Money Plan (Explorer only)
Design — Career Path (/life-simulator + CareerFitLab) (Explorer only)
Master — Growth Loop, Chemistry Check (Explorer only)
Organisation — Org Dashboard, Team Map, Team Intelligence (Organisation plan only)
Journey gate chain (enforced in ProtectedRoute.tsx + journeyGates.ts):
requiresDiscovery = discoveryCompleted
requiresFoundations = discoveryCompleted AND financialPlan completed ← Life Simulator
requiresPayment = hasPaidAccess(profile) [false for organisation plan]
Routing¶
All routes defined in App.tsx (current, June 26 2026):
| Path | Component | Gate |
|---|---|---|
/ |
LandingPage |
Public |
/login, /register |
Login, Register |
Public |
/verify-email |
VerifyEmailPage |
Public |
/reset-password |
ResetPassword |
Public |
/pricing |
Pricing |
Public |
/about |
AboutPage |
Public |
/terms, /privacy |
TermsOfService, PrivacyPolicy |
Public |
/share/:token |
SharedView |
Public |
/payment/success |
PaymentSuccess |
Public |
/complete-profile |
CompleteProfile |
Auth |
/dashboard |
Dashboard |
Auth |
/questionnaire |
QuestionnairePage |
Auth |
/report |
MyReportPage |
Auth |
/archetype-reveal |
ArchetypeReveal |
Auth |
/profile |
Profile |
Auth |
/coach-portal |
CoachPortal |
Auth |
/book |
BookPage |
requiresBook |
/wellness-profile |
WellnessProfilePage |
requiresDiscovery + requiresPayment |
/financial-plan |
FinancialPlanPage |
requiresDiscovery + requiresPayment |
/life-simulator |
LifeSimulatorPage |
requiresFoundations + requiresPayment |
/growth-loop |
GrowthLoopPage |
requiresDiscovery + requiresPayment |
/chemistry |
ChemistryPage |
requiresChemistry (Explorer only) |
/team |
TeamReport |
requiresOrganisation |
/team-intelligence |
TeamIntelligencePage |
requiresOrganisation |
/organisation |
OrganisationDashboardPage |
requiresOrganisation |
/admin |
AdminDashboard |
Admin role only |
/org-dashboard |
Redirect → /organisation |
Legacy |
/health-dashboard |
Redirect → /wellness-profile?tab=health |
Legacy |
/life-insights |
Redirect → /wellness-profile?tab=health |
Legacy |
/relationship-lens |
Redirect → /chemistry?tab=personal |
Legacy |
/people-blueprint |
Redirect → /chemistry?tab=work |
Legacy |
requiresFoundations = Discovery completed + Money Plan completed (Life Simulator needs financialPlan data)
DELETED routes: /life-design, /life-design-legacy, /self-mastery, /compare, /corporate, /self-inquiry
Layout & Navigation¶
Layout.tsxrenders the sidebar navigation and (on mobile) a top bar.- Navigation groups (in order): Home → Discover (01) → Explore (02 — Health, Financial Stability, Career Path) → Design (03 — Life Design, Growth Loop) → Master (04) → Account → Coaching → Organisation → Platform Admin.
- “Explore” group appears after Discovery is completed. Contains Health, Financial Stability, and Career Path — Health and Financial Stability must both be completed before Life Design unlocks.
- “Design” group is always visible (Life Design and Growth Loop items show lock icons until prerequisites are met).
- Header hidden on desktop for authenticated inner pages — the top header bar is only shown on mobile/tablet to avoid double navigation chrome.
- Collapsible sidebar on inner pages (Report, Wellness, LifeDesign, Financial): users can collapse the sidebar to gain horizontal space. Collapse state is toggled per-session.
- Notification bell in the sidebar and top bar:
- Unread count badge.
- Clicking opens a fixed-position dropdown panel (avoids overflow/clipping issues).
- "Mark all read" button marks every notification read server-side and clears the badge.
- "Clear all" button calls
DELETE /api/notificationsto permanently remove all notifications for the user, then clears local state. - Notification links that reference a coach conversation include
?conv=<uuid>soMessages.tsxdeep-links to the correct thread.
State Management¶
The application uses a hybrid state management approach:
- UserProfileContext: A global context (src/contexts/UserProfileContext.tsx) polls GET /api/users/me every 30 seconds to provide a live-updating { user, profile } object. This is used for all authentication and access control logic.
- Zustand: For features migrated from StartupOS, a store/ directory contains Zustand stores (startupStore, canvasStore, etc.) to manage complex client-side state for the founder journey.
- Local State: Most individual components and pages manage their own UI state via useState / useEffect.
user is type AuthUser { uid: string; email: string } — not a Firebase User object. Auth state is restored from localStorage key personality_auth (written by client.ts:setStoredAuth) and kept live via a personality_auth_change CustomEvent listener. Previously Firebase onAuthStateChanged was used — replaced in April 2026.
Messages (Messages.tsx)¶
- Polling-based real-time messaging between coach and client.
- Conversations listed in a left panel; clicking one opens the thread.
- Active conversation set from
?conv=<id>URL param on load; if no param is present, the first conversation is auto-selected. - Conversations polled every 5 seconds; messages polled every 3 seconds.
- Conversation
_idvalues are string UUIDs (generated viarandomUUID()on the server), not MongoDB ObjectIds. All server-sidefindOnecalls use atoDbId()helper that handles both formats for backward compatibility.
Scoring Engine (src/utils/scoring.ts)¶
Pure functions only — no side effects, no Firebase calls.
calculateResult(answers)— core scoring function. Do not modify without full regression testing.smartOrderQuestions()— interleaves categories and alternates difficulty for engagement.isAssessmentStable(answers, questions)— early-completion signal.
Services Layer¶
| Service | File | Purpose |
|---|---|---|
| Insight Engine | insightEngine.ts |
Generates 4 InsightPanel objects (self, direction, decision, evolution) from archetype + force data. Fully rule-based — no AI calls. |
| Career Insight Engine | careerInsightEngine.ts |
Produces CareerEnhancement — personalised career context panels from profile fields. |
| Health Sync Service | healthSyncService.ts |
Syncs native health data from Capacitor Health API on mobile. On web this is a no-op. |
| Wellness Engine | wellnessEngine.ts |
Computes dosha scores and wellness profile recommendations from prakriti quiz answers. |
| Compatibility Engine | compatibilityEngine.ts |
Computes CompatibilityResult from two archetype+force pairs. |
| Corporate Engine | corporateEngine.ts |
computeCorporateGuide, computeTeamIntelligence, computePeerGroupIntelligence. Used by PeopleBlueprintPage, ChemistryPage, TeamIntelligencePage. |
| Payment Service | paymentService.ts |
Thin wrapper around VPS API (POST /api/stripe/create-checkout-session). |
DELETED services (do NOT recreate): ikigaiEngine.ts, selfInquiryService.ts, foundationDerivation.ts, selfMasteryData.ts, foundationSynthesisService.ts, visionGoalParser.ts, aiService.ts
Gamification Utility (src/utils/gamification.ts)¶
Pure functions — no Firebase calls, no side effects. Consumed by pages and UserProfileContext.
| Export | Purpose |
|---|---|
XP_REWARDS |
Map of action key → XP value (e.g. ASSESSMENT_COMPLETE: 500) |
BADGE_DEFINITIONS |
Map of badge ID → { label, icon, description } for all 9 badges |
computeLevel(xp) |
Returns 'Stardust' \| 'Comet' \| 'Nova' \| 'Supernova' based on thresholds |
levelProgress(xp) |
Returns 0–100 percentage within the current level band |
nextLevelThreshold(xp) |
Returns { level, threshold } for the next level, or null at Supernova |
computeStreak(habits, today?) |
Iterates habit completedDates backwards to compute consecutive-day count |
computeNewBadges(profile, ctx?) |
Returns array of newly earned badge IDs not already in profile.badges |
applyXp(profile, action) |
Returns updated { xp, level, badges } — does not write to DB |
computeBackfill(profile) |
One-time retroactive calculation for existing users: derives XP + level + badges + streak from completed stages |
Key Type Definitions (src/types/)¶
| Type | Purpose |
|---|---|
SectionDetail |
{ meaning, realLife, strength, growth } — used by all archetype section cards |
InsightReport |
Four InsightPanel objects returned by insightEngine.ts |
CareerEnhancement |
Personalised career context object from careerInsightEngine.ts |
CareerDecisionAIResult |
Full career AI output including optional roleSuitability?: { verdict, score, suited[], gaps[], summary } (in src/types/careerDecision.ts) |
CompatibilityResult |
Output from compatibilityEngine.ts: score, summary, strengths, challenges, dosList, dontsList, approachGuide, traitsToWatchOut, conflictFlashpoints |
MongoDB Data Model¶
users collection — UserProfile¶
Key fields:
uid, name, email, role ('user' | 'admin')
discoveryCompleted
plan ('free' | 'explorer' | 'coach' | 'organisation'), planExpiresAt (ISO string | null), stripeCustomerId
orgId (null = individual), orgRole ('member' | 'admin' | 'coach'), assignedCoachId
emailVerified (boolean), emailVerificationToken (string | null)
coachAppStatus ('approved_pending_payment' | 'payment_received' | undefined)
referredBy (coachId), referralCode (coaches only), teamId
careerLabAnswers: { ikigai, lifeCtx, intentionChoice, pathChoices[], suggestedRoles[], chosenRole, wizardStep, ... }
careerFitResult: { bestRole, targetIndustry, roleSuitability?, next90Days, savedAt, ... }
careerCommitment: { plan, goals[], committedAt }
growthLoop: { habits, weeklyReflections, monthlyReviews, goalKanban: Record<goalId, GoalKanbanCard> }
shareToken (for public share link)
xp?, level?, badges?, streakCount?, lastStreakDate?```
**Plan access logic** (`src/utils/journeyGates.ts` — `hasPaidAccess`):
- `role === 'admin'` → always true
- `plan === 'coach'` → always true
- `plan === 'explorer'` with no `planExpiresAt` → true (admin-granted permanent access)
- `plan === 'explorer'` with future `planExpiresAt` → true (active paid or org subscription)
- All other cases → false
### `responses` collection — Assessment Response
One document per user (retakes overwrite). Key fields:
```text
uid, answers, archetype, secondaryArchetype, force, scores,
confidenceScore, energyOrientation, profileType, createdAt, assessmentHistory[]
createdAt is used by the 6-month retake cooldown enforced in DiscoveryPage.tsx.
coaches + coachApplications collections — Coaching¶
coaches/{id}: name, email, title, bio, archetypes, modules, location, sessionType,
priceMin, priceMax, referralCode, status ('active' | 'inactive'), createdAt
coachApplications/{id}: name, email, archetype, message,
status ('pending' | 'approved_pending_payment' | 'payment_received' |
'approved' | 'rejected')
coachRequests/{id}: userId, coachId, status ('pending'|'proposed'|'accepted'|
'awaiting_payment'|'payment_submitted'|'active'), proposal fields
commissions/{id}: referralCode, coachId, clientUid, amount, createdAt
processedWebhooks/{sessionId}: processedAt (idempotency guard for Stripe events)
Coach application status lifecycle:
approved_pending_payment— admin pre-approved; user'scoachAppStatusset; payment gate shown in CoachPortalpayment_received— Stripe webhook confirmed payment; admin "Activate Coach" button appearsapproved— admin activated;coachesdoc created/reactivated;plan: 'coach'set on user;coachAppStatuscleared
organisations collection — Corporate & Institution Accounts¶
This collection now serves both corporate clients and the newly migrated university "Institutions". A new orgKind field is used to differentiate them.
_id, name, inviteCode, coachIds[],
plan ('explorer' | 'institution'), planExpiresAt,
seatLimit,
orgKind ('organisation' | 'institution'), // NEW
createdAt
startups collection (NEW)¶
Stores all data related to a founder's startup journey.
_id, owner (ref: User), name, tagline, industry,
members (ref: [User]),
progress: { persona: bool, ... }, // 27 boolean keys
evolutionStage, xp, badges
canvases collection (NEW)¶
Stores the data for each of the 26 canvases for a given startup.
sprints collection (NEW)¶
Stores sprint planning data for a startup.
_id, startup (ref: Startup), name, status,
startDate, endDate,
tasks: [{ title, status, assignee, ... }]
cohorts collection (UPDATED)¶
Now used for managing university cohorts.
_id, institution (ref: Organisation),
name, joinCode,
lecturerIds (ref: [User]),
studentIds (ref: [User])
Assessment Retake Cooldown¶
Users are locked from retaking the assessment for 6 months after their first submission.
- Check location:
DiscoveryPage.tsx—useEffecton mount readsresponses/{uid}.createdAt - Lock duration:
SIX_MONTHS_MS = 6 * 30 * 24 * 60 * 60 * 1000 - Locked UI: Shows the last assessment date, next unlock date, and a countdown in days
- Manual admin override: Edit
responses/{uid}.createdAtin MongoDB (via Admin > Users in AdminDashboard or direct MongoDB access) to any date 6+ months ago
Why 6 months: Personality archetypes are stable over months and years. Research shows meaningful shifts take at least 6 months of new life experience to register. Short re-tests mostly reflect mood, not real change.
Payment Flow (Stripe)¶
Explorer Plan (RM 59/year)¶
Client (Pricing.tsx)
↓ createCheckoutSession({ plan: 'explorer', userId, referralCode?, promoCodeId? })
VPS API: POST /api/stripe/create-checkout-session
↓ creates Stripe Checkout session (optionally applies coupon for discount codes)
↓ returns { url }
Browser → Stripe-hosted checkout page
↓ on success: redirects to /payment/success?session_id=...
Stripe Webhook (checkout.session.completed)
→ verifies signature, writes plan: 'explorer', planExpiresAt (+1 year) to users collection
→ increments usedCount + usedBy[] on promoCodes (if promo used)
→ records commission if referralCode present
Stripe Webhook (invoice.payment_succeeded, subscription_cycle)
→ extends planExpiresAt by 1 year
Stripe Webhook (customer.subscription.deleted)
→ sets plan: 'free', removes planExpiresAt
Coach Plan (RM 599 year 1 → RM 199/year renewal)¶
Coach plan uses a Stripe Subscription Schedule with two phases (RM 599 → RM 199). The webhook does not activate the portal directly — it requires admin review first.
CoachPortal (payment gate — only shown when coachAppStatus === 'approved_pending_payment')
↓ createCheckoutSession({ plan: 'coach', userId, ... })
VPS API: POST /api/stripe/create-checkout-session
↓ creates Stripe Checkout session at STRIPE_COACH_FIRST_PRICE_ID
↓ returns { url }
Browser → Stripe-hosted checkout
Stripe Webhook (checkout.session.completed)
→ sets coachAppStatus: 'payment_received' on users document
→ updates coachApplications document → status: 'payment_received'
→ creates SubscriptionSchedule (phase 1: RM 599, phase 2: RM 199 recurring)
→ does NOT set plan: 'coach' directly — admin must activate
Admin clicks "Activate Coach" in AdminDashboard → Coaches tab
→ creates coaches document, sets plan: 'coach', clears coachAppStatus
Stripe Webhook (customer.subscription.deleted)
→ sets plan: 'free', clears coachAppStatus, deactivates coaches document
PDF Export (MyReportPage.tsx)¶
Uses html2canvas-pro v2.0.2 + jsPDF v4.2.0. Each [data-pdf-section] element in the report renders as its own PDF page (image-based JPEG). Responsive Tailwind classes are inlined at capture time to ensure multi-column layouts render correctly regardless of viewport width.
html2canvas-pro v2 compatibility fix (May 2026): v2 added a Validator class that throws if canvas width/height ≤ 0 or > 32767. handleExportPDF passes { skipValidation: true } and clamps dimensions to Math.min(Math.max(dim, 1), 16383) to prevent both classes of error.
Data-Driven UI¶
No report or dashboard content is hardcoded. All text is pulled from:
src/data/framework.json— archetype sections, leadership profiles, learning profiles, financial datasrc/data/questions.json— assessment questionssrc/data/scoring.json— scoring weightssrc/data/careerPaths.json— 3-path career data (acceleration / mastery / contribution)src/data/powerMoves.json— per-archetype behavioural leverssrc/data/actionPlans.json— growth action planssrc/data/manifestos.json— path manifestossrc/data/swotTemplates.json— SWOT prompts for Strategic Audit step