Overview
Yarashi is an AI career intelligence SaaS platform targeting the Australian skilled-migrant job market. It is built as a solo project by Kevin Delaney under The Agile Foundry (ABN 60 303 086 105), operating from Sydney (Rouse Hill/Box Hill, NSW).
Critical rule: NEVER modify app/platform/page.jsx by downloading or rewriting from Claude outputs. Only surgical Python in-place string replacements, or upload the current deployed file for Claude to work on. Full file replace has broken the platform multiple times.
Platform Status
Tech Stack
| Layer | Technology | Notes |
|---|---|---|
| Framework | Next.js 14.2.29 | App Router. 20 CVEs — upgrade to 16 blocked on staging environment |
| Frontend | React, Tailwind CSS | Single 13,000-line page.jsx — component split is priority work |
| Database | Supabase (PostgreSQL) | sdhmresojnwuvtlyoohs.supabase.co |
| Auth | Supabase Auth | Email/password. TOTP MFA enabled at project level |
| AI | Anthropic Claude API | claude-sonnet-4-5, claude-haiku-4-5. 30-day data retention (ON). PII stripping active. |
| Billing | Stripe | Webhook live. Customer portal live. 9 plan keys. |
| Job APIs | Adzuna + Jooble | Dual feed. posted_at captured. 2-week scoring cutoff active. |
| Resend | Transactional email. Welcome email route live. | |
| Hosting | Vercel Pro | Auto-deploy from main branch. CSP headers active. |
| DNS | VentraIP | yarashi.com.au, agilefoundry.com.au, celticboxingcoach.com |
| Monitoring | UptimeRobot (free) | 3 monitors: yarashi.com.au, /platform, project.yarashi.com.au |
Key File Paths
| File | Purpose |
|---|---|
| app/platform/page.jsx | Entire platform UI — 13,000+ lines. DO NOT replace wholesale. |
| lib/stripe.ts | Stripe client, PLANS definition, getOrCreateStripeCustomer |
| lib/rateLimit.ts | Rate limiting, ADMIN_BYPASS_IDS (admin UUID only) |
| lib/piiStrip.ts | PII stripping utility — applied to all AI routes |
| lib/supabase.ts | Supabase admin client (service role) |
| app/api/webhook/route.ts | Stripe webhook handler with signature verification |
| app/api/sync-jobs/route.ts | Manual job sync — CRON_SECRET protected |
| app/api/cron/sync-jobs/route.ts | Automated job feed — Vercel cron 08:00 UTC daily |
| app/api/cron/data-retention/route.ts | Data cleanup — Vercel cron 02:00 UTC daily |
| app/privacy/page.jsx | Privacy Policy — Anthropic subprocessor, PII stripping, ADM disclosure |
| app/terms/page.jsx | Terms of Service — LinkedIn data warrant |
| vercel.json | Function timeouts, cron schedules, CSP security headers |
Architecture
Dual Job Search System
JobFeed (passive): Vercel cron runs daily at 08:00 UTC. Fetches from Adzuna + Jooble per user saved role preferences. Stores to job_feed table. Auto-scores jobs posted within 2 weeks using one batched Claude call per user. Max 20 jobs per user per sync.
JobIntelligence (active): User-triggered search at runtime. Also fetches from Adzuna + Jooble. Uses role_alias as query, not all keywords joined. These two systems are intentionally complementary — the June 2026 consolidation plan was retired.
Billing Flow
- User clicks upgrade →
/api/stripe/checkoutcreates Stripe Checkout Session withmetadata: { userId, plan } - User completes payment on Stripe-hosted page
- Stripe fires
checkout.session.completedto/api/webhook - Webhook verifies signature, reads metadata, calls
updateUserPlan()→ writes toprofiles.planin Supabase - On cancellation:
customer.subscription.deletedfires →revertToFree()
PII Stripping Flow
Before CV text is sent to the Anthropic API, lib/piiStrip.ts strips: name (first line), email, phone, address, postcode, LinkedIn URL. These are replaced with placeholders like [NAME], [EMAIL], [PHONE]. Applied to: cover-letter, build-cv, skills-gap, interview-prep, linkedin-optimiser, match-cv routes.
Rate Limiting
Server-side rate limiting in lib/rateLimit.ts. Per-tool limits per plan tier defined in lib/stripe.ts PLANS object. Admin account (ADMIN_BYPASS_IDS) bypasses all limits. Currently one entry: Kevin's admin UUID.
Supabase Cron Functions
| Function | Schedule | Purpose |
|---|---|---|
| /api/cron/sync-jobs | 08:00 UTC daily | Job feed refresh + auto-scoring for new jobs ≤14 days old |
| /api/cron/data-retention | 02:00 UTC daily | Enforce retention policy, schedule inactive account deletion |
Sprint Status
Sprint 0 — Security & Foundation (13/14)
| Card | Title | Status |
|---|---|---|
| s0-1 | Stripe portal route filename | Done |
| s0-2 | Staging environment | Done 22 Jun |
| s0-3 | AuthScreen hydration fix | Done 22 Jun (parked) |
| s0-4 | Verify Job Search/Role Match state safety | Done |
| s0-5 | Capture posted_at from Adzuna/Jooble | Done |
| s0-6 | 2-week auto-scoring cutoff in cron | Done |
| s0-7 | Deprecated meta tag + localStorage key | Done |
| s0-8 | Supabase account 2FA | Done |
| s0-9 | 2FA in project board auth flow | Done |
| s0-10 | Secure /api/sync-jobs | Done |
| s0-11 | Stripe webhook handler | Done |
| s0-12 | Rotate CRON_SECRET | Done |
| s0-13 | SUPABASE_SERVICE_ROLE_KEY confirmed clean | Done |
| s0-14 | ADMIN_BYPASS_IDS verified | Done |
Sprint 1 — Quick Wins (11/13 done, 10 code items remaining)
| Card | Title | Status |
|---|---|---|
| s0-24 | lib/locationResolver.ts deployed | Done 21 Jun |
| s1-1 | Build lib/ai.ts model assignment layer | Pending |
| s1-2 | Confirm and downgrade model assignments | Pending |
| s1-7 | npm audit | Blocked on staging |
| s1-8 | CSP and security headers | Done |
| s1-9 | Auth brute-force limits verified | Done |
| s1-10 | Anthropic API logging audited | Done |
| s1-11 | Data retention route verified | Done |
| s1-12 | PII stripping before Claude API calls | Done |
| s1-15 | Privacy Policy — Anthropic subprocessor | Done |
| s1-18 | LinkedIn data warrant in ToS | Done |
| s1-19 | ADM disclosure for Privacy Act 2024 | Done |
| s1-20 | Jooble commercial terms reviewed | Done |
| s1-23 | Uptime monitoring | Done |
| s1-25 | Beta exit criteria document | Done |
20 June 2026 Session
Security Changes
- Stripe webhook — replaced 4-line stub with full handler: signature verification, plan updates on checkout/subscription events, revert-to-free on cancellation
- Stripe portal route — renamed from
route (31).tstoroute.tsso Next.js can see it - /api/sync-jobs — added CRON_SECRET Bearer token auth to GET and POST
- CRON_SECRET rotated — new value in Vercel only
- Supabase account 2FA — TOTP MFA enabled at app.supabase.com
- CSP headers — added to vercel.json covering all routes
- PII stripping —
lib/piiStrip.tscreated and applied to 6 AI routes
Legal & Compliance
- Privacy Policy: Anthropic 30-day data retention disclosed
- Privacy Policy: Full ADM disclosure (5 tools) for Privacy Act 2024 — ahead of Dec 2026 deadline
- Privacy Policy: PII stripping disclosed
- Terms of Service: LinkedIn PDF upload warrant added
- Jooble commercial terms reviewed — clean, no restrictions
Project Board
- Full-page TOTP 2FA auth gate — board hidden until signed in
- Sign out button in topbar
- Nav links across all three pages (board ↔ risk register ↔ beta exit ↔ handover)
- Card IDs visible on every card
- Status visual treatment — done/in progress/won't do with completion dates
- Beta exit criteria live at project.yarashi.com.au/beta-exit
- Fixed pullFromSupabase bug — card data was accidentally embedded inside function body
Open Issues
AuthScreen hydration errors #418/#423/#425 — still firing in production. Root cause: success state (line 10943 in page.jsx) uses typeof window === 'undefined' check that evaluates differently SSR vs client. Fix pattern known (initialize to '', move URL check to useEffect). Dedicated session required — s0-3.
CVTailorModule Regenerate button ReferenceError — bare job variable out of scope in the Regenerate handler. All users are affected — cannot regenerate CV tailoring results.
Manual CV Rewrite silent failure — create-manual succeeds, build-cv never fires. Correlated with hydration errors.
Next.js CVEs — 20 vulnerabilities in Next.js 14 (high + moderate). Fix requires 14→16 upgrade (breaking change). Blocked on s0-2 staging environment. Do not run npm audit fix --force on production.
page.jsx component split — 13,000+ lines. The largest single technical debt item. Must be split before any significant new UI work. This is the prerequisite for adding PII stripping UI badge, 2FA option, and other user-facing improvements cleanly.
Adzuna commercial use — must confirm in writing with Adzuna before first paying customer. Beta risk accepted.
locationResolver.ts deployed - verify next sync — Jooble was returning US results (Jamestown ND) because pref.location was null. Canonical library deployed commit c87f9af with Country-first format across 57 locations. Verify next cron sync returns Sydney NSW roles only.
saved_jobs cleanup done 21 Jun — 100+ duplicate test entries removed via SQL. Real saved jobs from 13 June intact. No dedup constraint exists yet - monitor per-user counts.
Project board static HTML rule — docs deploy dd4da22 caused 404 on entire board by adding Next.js folders to static HTML repo. Reverted. Rule enforced: yarashi-project is static HTML only, no app/ or pages/ directories ever.
Admin new-user notification email (23 Jun 2026) — /api/email/admin-notify/route.ts created. Fires non-blocking from app/api/auth/signup/route.ts alongside welcome email on every new signup. Sends branded HTML email to kevin@theagilefoundry.es with user name, email, plan, total user count, AEST timestamp, and Supabase quick link. Commits: 98ebcf6 (route), 050df04 (signup patch). Tested and confirmed working.
Signout crash fixed (23 Jun 2026) — handleSignOut was clearing authUser to null and wiping all agf_ localStorage keys but not redirecting — React tried to re-render the entire platform with null state, causing error #300 which cascaded into #418/#423/#425. Fix: handleSignOut is now async, calls /api/auth/signout to invalidate Supabase session server-side, then hard redirects to yarashi.com.au. Commit: 5a4c36f.
Signout error flash fixed (23 Jun 2026) — After the crash fix, a 2-second error flash remained because React still attempted one re-render before the navigation completed. Fix: isSigningOut useRef added, setMounted(false) called at the very start of handleSignOut — React immediately renders the loading screen instead of the platform, error boundary never triggered. Commit: bf95f1f.
Onboarding screen re-appearing on every login (23 Jun 2026) — handleSignOut wiped all agf_ keys including agf_onboarding_seen. On next login, profile fetch hadn't completed before the onboarding check fired, so it showed regardless of onboarding_complete in Supabase. Fix: agf_onboarding_seen, agf_lang, agf_country, agf_ip_detected now preserved across signout — these are user preferences, not session data.
Protocol breach recorded (23 Jun 2026) — Three fixes (signout crash, signout flash, admin-notify) were pushed directly to main before staging was tested. Staging was 4 commits behind main at session end. Root cause: pressure to fix live crash overrode the staging-first rule. Corrected by merging main into staging (fast-forward). New non-negotiable protocol documented in Next Steps. Will not happen again.
page.jsx split plan locked (23 Jun 2026) — Full 25-session execution plan documented in handover. Triple-checked against live 13,205-line file. War room conducted with three AI engineers (network, security, data). 5 risks identified, 5 protocol additions agreed. Unanimous go — Session 1 clear to execute. See split plan section below.
— staging branch created in agf-job-match. Vercel auto-deploys previews. staging.yarashi.com.au CNAME added via VentraIP DNS -> cname.vercel-dns.com. Closes R-006 (no staging). All future feature work develops on staging first. For Next.js major upgrades, a full Supabase staging DB will also be needed (s6-40)./api/account/delete merged to main (23 Jun 2026) — POST schedules deletion (deletion_requested_at + deletion_scheduled_at = now+30d). GET returns status. DELETE cancels during grace period. Supabase migration complete. Both UI buttons wired. Built on staging, tested, merged to main. Closes s0-19 and bug-6 beta exit criterion.
React hydration errors eliminated (22 Jun 2026) — 13 commits to fix #418/#423/#425. Root cause: useState lazy initialisers reading localStorage during SSR. All fixed across page.jsx, AuthScreen, JobFeed, AdminFeatureBoard, RolePrefsPanel, MyHub. force-dynamic added. Landing page @import font broken fixed. Profile 14% flash fixed.
Hydration errors #418/#423/#425 - PARKED — After 13 commits eliminating all identifiable localStorage SSR mismatches, errors persist. Root chunk fd9d1056 is React itself (react-dom 18.3.0-canary). The errors are coming from React's hydration reconciler detecting tree differences. Decision: park this, move forward. Will be resolved as part of Next.js 14->15->16 upgrade which switches to React 19 with a redesigned hydration system. Does not affect platform functionality.
pending: userPlan + jobs lazy inits — Two final lazy initialisers found in main component (lines 11508 + 11594). Fixed in page_final.jsx. Deploy pending. These may be the last source of #425 errors.
CV Tailor create-manual -> build-cv fixed — commit 2d0ae06. forceNew=true on manual job generate button bypasses localStorage cache. Previously cached result from same job title caused early return before build-cv fired.
Decision Log
| Decision | Rationale | Date |
|---|---|---|
| Anthropic 30-day retention left ON | Beta, no paying customers. ZDR requires support request. Revisit before first paying customer. | 20 Jun |
| npm audit fix deferred | Next.js 14→16 is breaking change. Must go through staging first. | 20 Jun |
| Sentry moved to backlog | Kevin's preference. Must-have in beta exit criteria before first paying customer. | 20 Jun |
| Jooble terms accepted | Clean, no commercial restrictions. No action ever needed. | 20 Jun |
| Platform 2FA deferred | Consumer product — mandatory is too much friction. Optional is right for job search tool. | 20 Jun |
| PII stripping UI badge deferred | Adding to 13,000-line page.jsx is inadvisable. Build during component split. | 20 Jun |
| Dual job system retained | JobFeed (passive cron) and JobIntelligence (active search) are complementary. June consolidation plan retired. | Jun |
| AuthScreen hydration deferred | Sticky issue — reserved for dedicated session. s0-3. | Jun |
Commit Log -- 20-22 June 2026
Config & Credentials
Never commit secrets to the repo. All keys live in Vercel → yarashi-project → Settings → Environment Variables.
Required Environment Variables
| Variable | Where | Notes |
|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Vercel | Public — safe in frontend code |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Vercel | Public — safe in frontend code |
| SUPABASE_SERVICE_ROLE_KEY | Vercel only | Bypasses all RLS — never commit |
| ANTHROPIC_API_KEY | Vercel only | — |
| STRIPE_SECRET_KEY | Vercel only | — |
| STRIPE_WEBHOOK_SECRET | Vercel only | whsec_… from Stripe dashboard |
| CRON_SECRET | Vercel only | Rotated 20 June 2026. Auth for /api/sync-jobs |
| ADZUNA_APP_ID | Vercel only | — |
| ADZUNA_APP_KEY | Vercel only | — |
| JOOBLE_API_KEY | Vercel only | — |
| RESEND_API_KEY | Vercel only | — |
| NEXT_PUBLIC_APP_URL | Vercel | https://yarashi.com.au |
Supabase RPC Functions Required
| Function | Called by | Status |
|---|---|---|
| enforce_data_retention() | data-retention cron | Confirmed exists |
| schedule_inactive_account_deletion() | data-retention cron | Confirmed exists |
Stripe Webhook
Endpoint: https://www.yarashi.com.au/api/webhook
Events: checkout.session.completed, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted
Next Steps
Immediate — do these first
- Platform baseline COMPLETE (12 Jul 2026) — split (25/25), Next 15 + React 19, Next 16.2.10 (Turbopack, proxy.ts) ALL LIVE IN PRODUCTION. Stripe latent build bug fixed (lazy-init). s0-27/s0-28 confirmed NOT fixed by React 19 — real fixes now Batch 1. EXECUTION BATCHES (locked 12 Jul): B1 Stabilise (~17e): s0 bug cluster incl. signout + onboarding real fixes, dead-code s0-29..34, lint-in-CI, SupportBot extraction s8-5. B2 Protect (~30e): s1 security/cost core, s11-7/8/9 cost framework + cache double-billing + instrumentation, s12-4 sign-up gate, s6-15 2FA, s1-22 Sentry, s6-13 uptime. B3 AI cost engine (~22e): s1-26/27/28/29 cache + CV versioning, s1-1 lib/ai.ts, s1-3 Haiku eval, s6-4 prompt caching. B4 UX & trust (~30e): s2 accessibility/onboarding/mobile/observability + s6-9/10/17. B5 Oracle & CV Maestro (s3, 83e): decisions s3-2/3 first, phases 1-7, renderer, vault, destination engine, taxonomy, market intel, roadmap, learning navigator. B6 Engagement & growth (~60e): s4 alerts/digest + s11 health score, timeline, free match hook, mobile-first. B7 Platform expansion (~90e): s7 companion, s10 inbox, s12 benchmarking/company intel/cost dashboard. B8 Strategic bets (~100e): s13 autonomous agent + employer product, s6 China/i18n/team plan, s9 AU-specific. CLOSED AS SUPERSEDED: s5-6, s5-8, s5-9, s6-1 (~42e). DUPLICATES MERGED: s6-12->s1-22, s6-14->s2-12, s4-7->s1-13. Effective remaining ~585e.
- Next.js 14 → 15 → 16 upgrade — After split complete. Staging only. Build s6-40 (full Supabase staging DB) before starting. React 19 will resolve the hydration-related crashes at the root.
- Bug fixes — Signout crash, onboarding flash, and all other outstanding bugs — after Next.js upgrade. Many are caused by the same hydration/null-state pattern that React 19 redesigned. Fixing in a 13,000-line monolithic file before upgrade is high risk and fixes may not survive the upgrade.
Decision logged 23 Jun 2026. The signout crash and onboarding flash bugs are deliberately deferred. They are caused by the same hydration/localStorage pattern that Next.js 15+ and React 19 address at the framework level. Attempting to fix them now in the monolithic file adds risk with low durability. Fix after upgrade.
Protocol additions from war room (mandatory before Session 1)
- P-01 — Encoding check every session: After every git diff, before git push — scan for â€" /  / . Any hit: abort, restore via git checkout, rewrite in Python with encoding='utf-8' newline='\n'.
- P-02 — Session 7 atomic commit: Stage all four non-contiguous Shared.jsx blocks locally. Single git add -A and one commit. No partial pushes.
- P-03 — Before Session 13: Grep agf_ localStorage keys across CVMatchEngine, CVTailorModule, InterviewPrep, CoverLetterBuilder. Confirm no key name divergence.
- P-04 — Session 24: Post-extraction sign in as non-admin, confirm admin panel inaccessible before committing.
- P-05 — Before Sprint 8: Build s6-40 full Supabase staging DB before any Next.js upgrade session.
Session protocol — non-negotiable from 23 Jun 2026
Staging first, always. No exceptions — not for crash fixes, not for one-liners, not under pressure. git checkout staging is the first command of every session.
Check diff before every commit. git diff after every patch, read it fully, confirm only intended lines changed before git add.
Encoding check on every diff. Scan for corruption before committing.
One change per commit. No bundling multiple fixes. Each fix is its own commit.
Verify branch sync after every session. End every session with git log staging..main --oneline. If not empty, sync before closing.
Test checklist on staging before merging to main. Page loads, sign in works, changed feature works, no new console errors, sign out clean.
Before First Paying Customer (unchanged)
- Fix CVTailorModule Regenerate ReferenceError
- Install Sentry — currently blind on production errors
- Confirm Adzuna commercial use in writing
- Test Stripe billing end-to-end with a real test card
- Upgrade Next.js 14 → 15 → 16 (after split complete)
See Beta Exit Criteria for the full checklist.
page.jsx Component Split — Full Execution Plan
Non-negotiable rules. Staging branch only — never work directly on main. One extraction per session — no combining. Zero logic changes — pure file reorganisation only. Always run git diff before every push. Never replace page.jsx with a full file copy — surgical edits only.
Why this order matters. Dependencies flow downward — a file can only be extracted after everything it calls already exists as a standalone import. Utils go first (no JSX, no React, nothing can break). Shared UI components go second (used by multiple modules). Feature modules go last, ordered by their own cross-dependencies. The shell is cleaned up in the final session once every import resolves.
Import alias. Use @/ throughout — already confirmed working in the live file (@/components/JobFeed etc). Maps to the app/ root. New paths: @/utils/colors, @/components/ui/Toast etc. Never use relative paths like ../../utils.
Pre-Session Checklist (run before starting any session)
# 1. Confirm you are on staging — never on main git branch # 2. Pull latest staging git pull origin staging # 3. Get current live line count (shifts after each session — never trust the plan's numbers) (Get-Content app\platform\page.jsx).Count # 4. Grep for the function you are about to extract to get its real current line Select-String -Path app\platform\page.jsx -Pattern "^function ComponentName|^const ComponentName" # 5. Verify staging.yarashi.com.au is live and the previous session's module still works # Open browser, sign in, click through the module extracted last session
Per-Session Protocol (repeat exactly, every session)
1. git checkout staging 2. git pull origin staging 3. CREATE the new file — copy extracted lines verbatim, add export keywords, add required imports at top 4. ADD the import line at the top of page.jsx (after existing imports) 5. DELETE the extracted block from page.jsx (PowerShell str_replace — never manual edit) 6. git diff app/platform/page.jsx ← verify ONLY the deleted block and new import line changed 7. git diff app/[new-file-path] ← verify new file matches extracted content exactly 8. git add -A 9. git commit -m "refactor: extract [Name] to [filepath] (split session N)" 10. git push origin staging 11. Open staging.yarashi.com.au — run the Session Test Checklist below 12. IF all tests pass: git checkout main && git merge staging && git push origin main 13. IF any test fails: git revert HEAD on staging, investigate, do NOT merge to main
Session Test Checklist (run on staging after every push)
| Test | How to verify | Blocks merge? |
|---|---|---|
| Page loads | staging.yarashi.com.au loads without white screen or console error storm | YES |
| Sign in works | Sign in with celticboxingcoach@gmail.com test account — platform loads | YES |
| Extracted module renders | Click to the module extracted this session — it displays correctly | YES |
| No new console errors | F12 → Console — compare to pre-session baseline. Zero new errors. | YES |
| One AI call completes | Run any AI tool (Skills Gap or Job Analyser) — result returns successfully | YES |
| Auth flow intact | Sign out, sign back in — session restores correctly | YES |
| No hydration errors beyond baseline | Hard refresh (Ctrl+Shift+R), check console — no new hydration warnings | YES |
Known Issues to Correct in Session 1
Stray semicolon on line 52. A lone ; exists on line 52 after useColors. Delete it when extracting colors.js. It is dead syntax and should not be copied to the new file.
Verified Dependency Map (from triple-check 23 Jun 2026)
Every dependency below was verified against the live file with comment lines stripped. False positives from adjacent comment blocks have been removed.
| Definition | Real dependencies (verified) |
|---|---|
| C / useColors | (none — root of the tree) |
| safeFetch / aiFetch | (none — pure async functions) |
| saveAnalysisToProfile | setCachedAnalysis |
| loadSavedAnalysis | safeFetch, getCachedAnalysis, setCachedAnalysis |
| withRetry | (none) |
| CV_LIMIT / JOB_LIMITS / isPlanX / getLocalPlan / checkJobLimit / incrementJobLimit / recordJobUsage | (none — pure functions) |
| JobLimitGate | checkJobLimit — also needs React import (uses useState + useEffect) |
| InfoBtn / HELP_CONTENT | C |
| Tag / Pill / MatchRing / Stat / MiniBar / SOURCE / SourceTag | C (SOURCE is self-referencing within the group) |
| FLabel / CopyBtn / Field | C |
| LogoSVG | (none — pure SVG) |
| YarashiMark / LogoWatermark | C, LogoSVG |
| DashCard / DashSectionTitle / DashPill | (none — pure JSX) |
| Chips / LocationPicker / ListField / SectionHead / F | C — Chips also used by SkillsGapAnalyser, must be in Shared.jsx not ProfileModule |
| AddToKanbanBtn / SaveToProfileBtn | (none — uses hardcoded colours, no C dependency) |
| UsageBadge | checkJobLimit (from utils/plan) |
| ToastContainer / showToast | (none) |
| SkeletonCard | (none) |
| LoadingScreen | LogoSVG, SkeletonCard |
| GlobalStyles | (none) |
| NewUserChecklist | C, useColors |
| Dashboard | saveAnalysisToProfile, loadSavedAnalysis, LogoSVG, printDashboardReport |
| CVUpload | (none — uses hardcoded colours) |
| PLAN_DATA / PricingModal | isPlanStarter, isPlanPro, isPlanSuperPro, LogoSVG |
| CVMaestroModule | C, useColors, recordJobUsage, JobLimitGate, showToast |
| CVTailorModule | C, useColors, safeFetch, saveAnalysisToProfile, getCachedAnalysis, setCachedAnalysis, LogoSVG, printToPDF, showToast, autoSaveToTracker — must extract AFTER ApplicationTracker |
| SupportBot | C, LogoSVG |
| JobAnalyserScoreRing / JobAnalyser | C, useColors, saveAnalysisToProfile, setCachedAnalysis, printJobAnalysis, LoadingScreen, withRetry |
| JobIntelligence | C, safeFetch, JobLimitGate, LogoSVG, LOC_GROUPS, LOC_OPTS, DATE_OPTS, SALARY_OPTS, AddToKanbanBtn — LOC_GROUPS etc move WITH this module |
| CVMatchEngine | C, safeFetch, aiFetch, saveAnalysisToProfile, getCachedAnalysis, setCachedAnalysis, CV_LIMIT, recordJobUsage, buildFilename, exportRecommendationsPDF, exportRecommendationsWord, exportMatchPDF, exportMatchWord, Tag, showToast, SaveToProfileBtn, autoSaveToTracker — must extract AFTER ApplicationTracker |
| InterviewPrep | C, safeFetch, saveAnalysisToProfile, getCachedAnalysis, setCachedAnalysis, recordJobUsage, JobLimitGate, Tag, LogoSVG, printInterviewPrep, showToast, AddToKanbanBtn, SaveToProfileBtn, autoSaveToTracker — must extract AFTER ApplicationTracker |
| TRACKER_COLS / INTERVIEW_TYPES / EMPTY_APP / autoSaveToTracker / useTracker / ApplicationTracker | C, Tag, Field, TRACKER_COLS, INTERVIEW_TYPES, EMPTY_APP, useTracker — autoSaveToTracker needs EMPTY_APP so they move together |
| CoverLetterBuilder | C, aiFetch, getCachedAnalysis, setCachedAnalysis, recordJobUsage, JobLimitGate, exportWord, Tag, printCoverLetter, withRetry, AddToKanbanBtn, SaveToProfileBtn, autoSaveToTracker — must extract AFTER ApplicationTracker |
| ProfileModule + sub-components | C, safeFetch, showToast, ALL_INDUSTRIES, TEMPLATE_TYPES, dbRowToProfile, EMPTY_PROFILE, F, LocationPicker, Chips, ListField, SectionHead — Chips is shared, imports from Shared.jsx |
| SkillsGapAnalyser | C, safeFetch, aiFetch, saveAnalysisToProfile, setCachedAnalysis, recordJobUsage, InfoBtn, HELP_CONTENT, Tag, printSkillsGap, showToast, UsageBadge, Chips, STATUS_COLORS, PRIORITY_ORDER |
| LinkedInOptimiser | C, safeFetch, aiFetch, saveAnalysisToProfile, getCachedAnalysis, setCachedAnalysis, recordJobUsage, JobLimitGate, CopyBtn, printLinkedIn |
| AdminPanel | C, Tag, Stat |
| OnboardingGuide | C — confirmed dead code, never called anywhere in the file |
| SplashGrabWidget / AuthScreen / JobsWrapper | LogoSVG, LogoWatermark, SplashGrabWidget (internal) |
The 25 Sessions — Definitive Extraction Order
Line numbers are from the file as of 23 Jun 2026. Grep for function name at session start — numbers shift after each extraction.
| S | Target file | Lines | What moves | Key constraint |
|---|---|---|---|---|
| Phase 1 — Pure utils (no JSX, zero render risk) | ||||
| 1 | app/utils/colors.js | 15–54 | C object, useColors. Delete stray ; on line 52. | Nothing depends on this yet — safest possible first move |
| 2 | app/utils/fetch.js | 55–152 + 1278–1305 | safeFetch, aiFetch, saveAnalysisToProfile, loadSavedAnalysis, getCachedAnalysis, setCachedAnalysis, withRetry | Two non-contiguous blocks into one file. withRetry is at line 1278 — grab it in this session. |
| 3 | app/utils/plan.js | 153–222 | CV_LIMIT, JOB_LIMITS, isPlanStarter, isPlanPro, isPlanSuperPro, getLocalPlan, checkJobLimit, incrementJobLimit, recordJobUsage, JobLimitGate | plan.js needs import React, { useState, useEffect } from 'react' — JobLimitGate is a React component |
| 4 | app/utils/exports.js | 361–574 + 877–999 | useCVCount, buildFilename, exportPDF, exportWord, exportRecommendationsPDF, exportRecommendationsWord, exportMatchPDF, exportMatchWord, printToPDF, printCoverLetter, printInterviewPrep, printSkillsGap, printDashboardReport, printJobAnalysis, printLinkedIn, useIsMobile | Two non-contiguous blocks. printToPDF is at 877 — all print functions depend on it so they all move together. |
| Phase 2 — Shared UI (used by multiple modules — must exist before feature modules move) | ||||
| 5 | app/components/ui/Toast.jsx | 1065–1132 | ToastContainer, showToast | No deps. Standalone. showToast is called everywhere — page.jsx keeps it in scope via import until each module is extracted. |
| 6 | app/components/ui/Logo.jsx | 816–851 | LogoSVG, YarashiMark, LogoWatermark | Depends on C (Session 1). LogoSVG is pure SVG — no React hooks. |
| 7 | app/components/ui/Shared.jsx | 575–646 + 793–876 + 1133–1274 + 2101–2187 | Tag, Pill, MatchRing, Stat, MiniBar, SOURCE, SourceTag, FLabel, CopyBtn, Field, DashCard, DashSectionTitle, DashPill, SkeletonCard, LoadingScreen, GlobalStyles, AddToKanbanBtn, SaveToProfileBtn, Chips, LocationPicker, ListField, SectionHead, F | FOUR non-contiguous blocks into one file. Chips and LocationPicker normally sit inside ProfileModule range but are moved here because SkillsGapAnalyser uses Chips independently. CopyBtn moves here because LinkedInOptimiser uses it. LoadingScreen depends on LogoSVG and SkeletonCard — both in this file. |
| 8 | app/components/ui/InfoBtn.jsx | 223–360 | InfoBtn, HELP_CONTENT | Depends on C. Used by SkillsGapAnalyser — must exist before Session 19. |
| 9 | app/components/ui/CVUpload.jsx | 2203–2450 | CVUpload modal | No colour dependencies — uses hardcoded values. Self-contained. |
| 10 | app/components/ui/PricingModal.jsx | 2451–2863 | PLAN_DATA, PricingModal | Depends on isPlanX (Session 3) and LogoSVG (Session 6). |
| Phase 3 — Feature modules (ordered by dependency chain) | ||||
| 11 | app/components/NewUserChecklist.jsx | 1306–1518 | NewUserChecklist | Depends on C and useColors only. Simple. Used directly by the shell. |
| 12 | app/components/Dashboard.jsx | 1519–2000 | Dashboard | Depends on saveAnalysisToProfile, loadSavedAnalysis (Session 2), LogoSVG (Session 6), printDashboardReport (Session 4). |
| 13 | app/components/ApplicationTracker.jsx | 7005–7577 | TRACKER_COLS, INTERVIEW_TYPES, EMPTY_APP, autoSaveToTracker, useTracker, ApplicationTracker | Must happen before Sessions 14, 15, 17, 18, 23 — all call autoSaveToTracker. autoSaveToTracker and EMPTY_APP are coupled and move together. Export both ApplicationTracker (default) and autoSaveToTracker (named). |
| 14 | app/components/CVMatchEngine.jsx | 5759–6447 | CVMatchEngine | Depends on autoSaveToTracker (Session 13). Must happen before Sessions 15 and 16 — both render CVMatchEngine. |
| 15 | app/components/JobAnalyser.jsx | 4312–4967 | JobAnalyserScoreRing, JobAnalyser | Depends on LoadingScreen (Session 7), withRetry (Session 2). Note: references to "JobAnalyser" in CVTailor/InterviewPrep/CoverLetterBuilder are localStorage key names in comments only — not component imports. |
| 16 | app/components/JobIntelligence.jsx | 4968–5758 + LOC constants 2001–2100 | LOC_GROUPS, LOC_OPTS, DATE_OPTS, SALARY_OPTS, CONTRACT_OPTS, WORKTYPE_OPTS, SENIORITY_OPTS, JobIntelligence | LOC_GROUPS and all filter constants only used by JobIntelligence — move with it. Two non-contiguous blocks. Depends on CVMatchEngine (Session 14). |
| 17 | app/components/InterviewPrep.jsx | 6448–7004 | InterviewPrep | Depends on autoSaveToTracker (Session 13), AddToKanbanBtn + SaveToProfileBtn (Session 7), Tag (Session 7). |
| 18 | app/components/CoverLetterBuilder.jsx | 7578–8060 | COVER_TONES, COVER_LENGTHS, CoverLetterBuilder | Depends on autoSaveToTracker (Session 13), aiFetch (Session 2), exportWord (Session 4), withRetry (Session 2). |
| 19 | app/components/CVMaestroModule.jsx | 2864–3335 | CVMaestroModule | Depends on C, useColors, recordJobUsage (Session 3), JobLimitGate (Session 3), showToast (Session 5). No autoSaveToTracker — can go before Session 13 but simpler to do here. |
| 20 | app/components/CVTailorModule.jsx | 3336–4101 | CVTailorModule | Must be after Session 13 — calls autoSaveToTracker. Also depends on safeFetch (S2), getCachedAnalysis/setCachedAnalysis (S2), printToPDF (S4), LogoSVG (S6), showToast (S5). |
| 21 | app/components/ProfileModule.jsx | 8061–9426 | LOCATIONS_BY_REGION, AU_CITIES, ALL_INDUSTRIES, EMP_TYPES, WORK_TYPES, CLEARANCES, EXP_OPTS, SENIORITY, NOTICE_OPTS, TEMPLATE_TYPES, dbRowToProfile, EMPTY_PROFILE, ProfileModule. Sub-components F, SectionHead already in Shared.jsx. | Largest single extraction (1,071 lines of ProfileModule + constants). Chips, LocationPicker, ListField, F, SectionHead are already in Shared.jsx by this point — import from there. Export ProfileModule (default) and dbRowToProfile (named — used by shell). |
| 22 | app/components/SkillsGapAnalyser.jsx | 9427–10304 | STATUS_COLORS, PRIORITY_ORDER, UsageBadge, SkillsGapAnalyser | UsageBadge (line 2188, 15 lines) also moves here — only used by SkillsGapAnalyser. Depends on InfoBtn (S8), HELP_CONTENT (S8), Chips (S7), Tag (S7). Import Chips from @/components/ui/Shared. |
| 23 | app/components/LinkedInOptimiser.jsx | 10305–10691 | LinkedInOptimiser | Depends on CopyBtn (Session 7). CopyBtn is in Shared.jsx — import from @/components/ui/Shared. |
| 24 | app/components/AuthScreen.jsx | 10692–11529 | AdminPanel, SplashGrabWidget, AuthScreen, JobsWrapper, OnboardingGuide | OnboardingGuide is confirmed dead code (never called) — move with AuthScreen to clean it out of page.jsx. AdminPanel depends on Tag and Stat from Shared.jsx. |
| 25 | app/platform/page.jsx (shell cleanup) | 11530–13205 | AgileFoundryPlatform shell — reduce from 1,676 lines to ~300 | By this session page.jsx contains only imports + the shell function. Clean up the import block, remove any dead imports, verify all 29 module imports resolve. Full end-to-end test of every module before final merge. |
Final File Structure After Session 25
app/
platform/
page.jsx (~300 lines — shell + imports only)
MyHub.jsx (already separate — untouched)
utils/
colors.js (54 lines)
fetch.js (includes withRetry — ~130 lines)
plan.js (includes JobLimitGate — ~70 lines)
exports.js (~220 lines)
components/
ui/
Toast.jsx (68 lines)
Logo.jsx (59 lines)
Shared.jsx (~350 lines — Tag, Pill, MatchRing, Stat, MiniBar,
SourceTag, FLabel, CopyBtn, Field, DashCard,
DashSectionTitle, DashPill, SkeletonCard,
LoadingScreen, GlobalStyles, AddToKanbanBtn,
SaveToProfileBtn, Chips, LocationPicker,
ListField, SectionHead, F)
InfoBtn.jsx (138 lines)
CVUpload.jsx (248 lines)
PricingModal.jsx (~413 lines)
NewUserChecklist.jsx (213 lines)
Dashboard.jsx (482 lines)
ApplicationTracker.jsx (573 lines — exports autoSaveToTracker)
CVMatchEngine.jsx (689 lines)
JobAnalyser.jsx (631 lines)
JobIntelligence.jsx (~891 lines — includes LOC constants)
InterviewPrep.jsx (557 lines)
CoverLetterBuilder.jsx (483 lines)
CVMaestroModule.jsx (472 lines)
CVTailorModule.jsx (766 lines)
ProfileModule.jsx (~1,100 lines — exports dbRowToProfile)
SkillsGapAnalyser.jsx (~885 lines — includes UsageBadge)
LinkedInOptimiser.jsx (387 lines)
AuthScreen.jsx (~860 lines — includes AdminPanel, SplashGrabWidget,
JobsWrapper, OnboardingGuide)
(already external — untouched)
components/JobFeed.jsx
components/MyMatches.jsx
components/JobActionHub.jsx
components/OnboardingFlow.jsx
components/RolePrefsPanel.jsx
components/AdminFeatureBoard.jsx
Shell Import Block After Session 25
These are the exact imports the shell will need, verified from the live dependency check.
'use client';
export const dynamic = 'force-dynamic';
// Already external
import JobFeed from '@/components/JobFeed';
import MyMatches from '@/components/MyMatches';
import JobActionHub from '@/components/JobActionHub';
import OnboardingFlow, { GettingStarted } from '@/components/OnboardingFlow';
import RolePrefsPanel from '@/components/RolePrefsPanel';
import AdminFeatureBoard from '@/components/AdminFeatureBoard';
import MyHub from './MyHub';
import React, { useState, useMemo, useCallback, useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
// Extracted in split sessions
import { C } from '@/utils/colors';
import { safeFetch, setCachedAnalysis } from '@/utils/fetch';
import { isPlanPro, useCVCount } from '@/utils/plan';
import { LogoSVG, YarashiMark } from '@/components/ui/Logo';
import { ToastContainer, showToast } from '@/components/ui/Toast';
import { LoadingScreen, GlobalStyles } from '@/components/ui/Shared';
import CVUpload from '@/components/ui/CVUpload';
import { PricingModal } from '@/components/ui/PricingModal';
import NewUserChecklist from '@/components/NewUserChecklist';
import Dashboard from '@/components/Dashboard';
import ApplicationTracker from '@/components/ApplicationTracker';
import CVMatchEngine from '@/components/CVMatchEngine';
import { JobAnalyser } from '@/components/JobAnalyser';
import JobIntelligence from '@/components/JobIntelligence';
import InterviewPrep from '@/components/InterviewPrep';
import CoverLetterBuilder from '@/components/CoverLetterBuilder';
import CVMaestroModule from '@/components/CVMaestroModule';
import CVTailorModule from '@/components/CVTailorModule';
import ProfileModule, { dbRowToProfile } from '@/components/ProfileModule';
import SkillsGapAnalyser from '@/components/SkillsGapAnalyser';
import LinkedInOptimiser from '@/components/LinkedInOptimiser';
import { AdminPanel, AuthScreen, JobsWrapper } from '@/components/AuthScreen';
import SupportBot from '@/components/SupportBot';
SupportBot — Separate File
SupportBot (lines 4102–4311, 210 lines) was not assigned a session in the table above because it sits between CVTailorModule and JobAnalyser in the file and has no dependency on autoSaveToTracker. Extract it in its own session between Session 20 (CVTailorModule) and Session 21 (ProfileModule), or bundle with Session 20. It depends only on C and LogoSVG.
Decision Log — Split Plan
| Decision | Rationale |
|---|---|
| Chips moves to Shared.jsx not ProfileModule | SkillsGapAnalyser uses Chips independently. If left in ProfileModule, SkillsGapAnalyser would create a circular-style dependency on a feature module. |
| autoSaveToTracker stays with ApplicationTracker | It spreads EMPTY_APP which is a tracker-specific constant. Cannot cleanly separate into fetch utils without bringing tracker constants with it. |
| LOC_GROUPS and filter constants move with JobIntelligence | Only used by JobIntelligence. No justification for a shared constants file. |
| UsageBadge moves with SkillsGapAnalyser | Only called by SkillsGapAnalyser. No reason to put it in Shared.jsx. |
| OnboardingGuide bundled with AuthScreen | Confirmed dead code — never called anywhere. Moves to get it out of page.jsx. Can be deleted or wired up later. |
| CopyBtn goes in Shared.jsx not Logo.jsx | LinkedInOptimiser uses CopyBtn. Logo.jsx is for brand assets only. |
| JobLimitGate stays in utils/plan.js | It is tightly coupled to checkJobLimit and the limit functions. Only 7 lines. Needs a React import but that is a minor cost for keeping related logic together. |
| plan.js imports React | JobLimitGate uses useState + useEffect. Confirmed in live file. |
| withRetry moves with fetch.js in Session 2 | It is a fetch retry utility. Same file is the right home even though it is at line 1278 — non-contiguous blocks are acceptable for a utility grouping this clear. |
| CVTailorModule after ApplicationTracker (Session 20, not earlier) | CVTailorModule calls autoSaveToTracker at line 3498. autoSaveToTracker does not exist as a standalone import until Session 13. |