CodeHerWay is a self-paced, browser-based platform that teaches women web development by building real projects — from <h1>Hi</h1> to a shipped React app — with an AI tutor, a live in-browser code editor, and auto-graded coding challenges. Designed and built by Jenna Zawaski, a Chicago-based frontend developer.
▶ Try the live demo · Reviewer start here · Testing & quality gates
For reviewers, in 60 seconds: CodeHerWay is a portfolio case study of a trust-centered learning platform — React 19 + TypeScript + Supabase (Auth + Postgres), deployed on Vercel. It is ready for guided portfolio review, not a production or credentialing product yet. Open the live demo, take the first lesson (no signup wall), then skim reviewer-start-here for the guided tour and proof commands.
Canonical repository notice: This repository is the active canonical CodeHerWay learning platform application for portfolio review. Archived/older repositories are historical references only and should not be treated as the current product.
CodeHerWay is an active frontend learning platform project and portfolio product focused on beginner-friendly coding education.
Start here for review: docs/reviewer-start-here.md
- Canonical active app:
itcodegirl/codeherway-platform - Portfolio posture: Ready for guided portfolio review today, not production-grade yet
- Archived repos: Historical context only; do not evaluate them as the current CodeHerWay product
- This repository root is the active canonical app for CodeHerWay.
- The project is usable for guided portfolio review.
- The project is not yet production-grade.
- The quality baseline currently includes lint, JS-source policy, Playwright script validation, Supabase static policy readiness, production build, bundle budget, lesson-label audit, lesson-format audit (locks new lessons to the structured shape), strict quiz audit, learning-content flow audit, curriculum coverage audit, unit tests, and Playwright smoke coverage.
- Recent audit hardening added stable resume coverage, learner-scoped local state, authenticated persistence boundary tests, clearer lesson/quiz/challenge semantics, mobile tool-sheet polish, and Supabase live-deployment readiness notes.
| Area | Current status | Proof / next gate |
|---|---|---|
| Public learner flow | Ready for portfolio review | npm run test:e2e:smoke:public, docs/reviewer-demo-script.md |
| Lesson structure | Ready for review | npm run audit:content, npm run audit:lesson-format, npm run audit:curriculum-coverage |
| Quiz integrity | Locally hardened, audit-enforced | npm run audit:quizzes |
| Authenticated persistence | Unit/static coverage exists; live CI needs secrets | npm run audit:auth-e2e, docs/authenticated-e2e-ci.md |
| Mobile usability | Covered by focused smoke and visual paths | tests/e2e/mobile-learning-smoke.spec.js, docs/mobile-qa-checklist.md |
| Accessibility | Covered by unit and smoke gates | npm run test:a11y, src/styles/lessonTasks.a11y.test.js |
| Performance | Budgeted and CI-audited; score claims need dated artifact | npm run audit:performance, docs/lighthouse-evidence.md |
| Production launch | Not production-grade yet; gated on launch hardening | docs/supabase-production-readiness.md, RELEASE_CHECKLIST.md |
Do not describe CodeHerWay as a production credentialing platform yet. The strongest current positioning is a trust-centered learning-platform case study with explicit production gates.
Progress sync: saved to your account. Lesson completions, quiz scores, bookmarks, notes, and the spaced-repetition review queue persist to Supabase and reload on any device you sign in to. The XP/streak/badge "gamification" layer that used to ride alongside this was retired entirely (PR #294) and its Supabase tables were dropped (PR #296); progress now tracks completion and review state, not reward points.
- Course browsing and lesson viewing UI for all twenty-six live course tracks, every one of which renders through
V2LessonStudio. - Single-device progress save/reload for the core learning flow, with same-browser retry/replay for failed writes.
- The XP/streak/badge reward layer (and its Supabase backend) has been retired: PR #294 removed all reward code, contexts, and services, and PR #296 dropped the backing tables and
award_reward_eventRPC from the live database. There is no reward backend or feature flag to enable. - Active lesson quiz coverage is complete for all eight courses the quiz audit treats as coverage-expected: HTML, CSS, JavaScript, React, Accessibility, TypeScript Foundations, React + TypeScript, and Next.js. (This set is not the same as the seven full-depth core courses listed further down — Accessibility has complete quiz coverage without yet meeting the full-depth evidence bar.)
- Quiz variant groups and legacy orphan quiz inventory are classified and monitored by the audit.
- Bookmarks and lesson notes in the active app.
- Progress Summary PDF flow that reflects current app progress and is explicitly not a verified credential.
- Mobile learning flow with sticky lesson navigation, topbar search, and a compact tools sheet.
- Public Playwright smoke coverage, including landing, auth, accessibility, visual snapshots, and first-lesson preview entry.
- Vercel + Vite build/deploy flow.
Current baseline checks:
npm run check(lint, JS-source policy, Playwright project-reference audit, Supabase static policy readiness, production build, bundle budget, lesson-label audit, lesson-format audit, strict quiz audit, learning-content flow audit, and unit tests)npm run buildnpm run lintnpm run typecheck(tsc --noEmitagainst all TypeScript source;strict: true; test files excluded)npm run check:js-source(source policy guard: enforces that generated reference files stay as.js)npm run check:supabase-sql-lint(source lint: required SQL/privacy statements present in the repo — not a live-DB check)npm run audit:e2e-scripts(Playwright project-reference guard)npm run audit:auth-e2e(authenticated E2E workflows keep preflight, secret wiring, and required signed-in smoke coverage)npm run audit:content(course/module/lesson/quiz/challenge content integrity guard)npm run audit:curriculum-coverage(report-only lesson-to-quiz/practice/project/rubric coverage map; see Curriculum Coverage Audit)npm run test(Vitest unit/component suite — passes on a fresh clone with no.envconfigured; the suite stubs theVITE_SUPABASE_*placeholders viavitest.config.jsso client-importing tests can evaluate)npm run audit:quizzesnpm run test:e2e(public smoke and first-lesson preview paths run by default)
Current test boundaries:
- Authenticated Playwright smoke checks are skipped locally when auth env credentials are not provided. CI runs authenticated learner coverage only when the full E2E Supabase secret set is configured; see Authenticated E2E CI setup.
- Local authenticated Playwright checks can read an ignored
.env.e2e.localfile copied from.env.e2e.example; CI still requires GitHub Secrets. - Playwright authenticated storage state is generated under
playwright/.auth/and intentionally ignored by Git. npm run audit:quizzesruns in strict mode by default and is the source of truth for quiz integrity drift. It fails on any unclassified orphan quizzes or unreviewed variant groups. All 14 intentional variant groups are locked; 49 legacy orphans are classified and non-blocking.npm run audit:contentfails on stale prerequisite IDs, missing bridge lesson IDs, and implicit cross-course lesson handoffs.npm run audit:curriculum-coverageis report-only by default. It summarizes remaining lesson quiz, practice, challenge/project evidence, and rubric-depth gaps without blocking unrelated PRs.npm run audit:lesson-formatfails when a lesson ships in a non-renderable legacy shape and is not inscripts/lesson-format-allowlist.json(the allowlist is now empty — the React course is normalized to v2 at assembly viasrc/data/react/migrateReactModulesToV2.ts, so it renders throughV2LessonStudiolike every other track). New course content must use a renderable lesson shape; intentional exceptions requirenode scripts/audit-lesson-format.mjs --updatein the same PR. See docs/handoff-deferred-risks.md (Risk A).npm run audit:e2e-scriptsfails when package scripts or smoke runners reference Playwright projects that do not exist inplaywright.config.js.- The XP/streak/badge reward backend was retired (PR #294) and its Supabase tables and RPC were dropped (PR #296); there is no longer a reward ledger, feature flag, or staging validation step in the test matrix.
- Supabase migration and privacy readiness details live in docs/supabase-production-readiness.md.
- Authenticated smoke checks are enabled in the suite, but they self-skip unless Supabase and learner test credentials are configured.
- Direct optimistic progress writes now use a same-browser retry queue with manual retry, reconnect retry, and next-session replay.
- Recoverable lesson route mutations for completion toggles and bookmarks now feed that same-browser retry queue when Supabase route actions fail with a recoverable write descriptor.
- Progress sync queue and replay outcomes emit privacy-safe analytics events when analytics is configured; event payloads avoid learner IDs, lesson keys, note content, and raw database messages.
- Non-recoverable route failures still surface advisory warnings where full replay/import is not implemented yet.
- Progress sync recovery details live in docs/progress-sync-recovery.md.
A learner who signs in on a second device sees their server-stored progress —
lesson completions, quiz scores, the spaced-repetition review queue, bookmarks,
and notes — because all of these persist to Supabase and are reloaded on sign-in
(fetchAllUserData in src/services/progressService.ts).
There is no longer an XP/streak/badge reward layer to reconcile across devices:
the gamification system and its Supabase backend were retired (PR #294), and the
backing tables and award_reward_event RPC were dropped from the live database
(PR #296). The VITE_REWARD_BACKEND_SYNC_ENABLED flag and the reward-event
ledger no longer exist, so cross-device persistence is simply about completion,
quiz, and review state following the learner — no reward totals are stored or
deduplicated.
See KNOWN_LIMITATIONS.md for the current limitation baseline, including learning identity hardening, quiz integrity follow-up, cross-device persistence scope, search coverage limits, and AI/security hardening scope.
See docs/repair-roadmap.md for the staged repair plan:
- Phase 1: Stabilize
- Phase 2: Clarify UX
- Phase 3: Strengthen Product Logic
- Phase 4: Portfolio Polish
- Phase 5: Launch Readiness
This project is intended to demonstrate:
- Frontend architecture and modular React app composition
- Product thinking for learning workflows
- Accessibility awareness and iterative UX hardening
- Learning-platform UX and retention-oriented interaction design
- Honest iteration discipline (audit -> staged repairs -> verification)
- Ability to assess and improve a real codebase under constraints
Reviewer shortcuts:
- Reviewer start: docs/reviewer-start-here.md
- Claim-to-code evidence map: docs/reviewer-evidence-map.md
- Reviewer demo script: docs/reviewer-demo-script.md
- Portfolio demo capture plan: docs/portfolio-demo-capture.md
- Branch and PR triage: docs/branch-triage.md
- Product story: docs/portfolio-case-study.md
- Audit stabilization summary: docs/audit-stabilization-summary.md
- UX trust/calmness notes: docs/ux-trust-calmness-notes.md
- Trust boundaries: docs/trust-boundaries.md
- Learner state model: docs/learner-state-model.md
- Roadmap acceptance criteria: docs/roadmap-acceptance-criteria.md
- Progress sync recovery: docs/progress-sync-recovery.md
- Architecture overview: docs/architecture.md
- Release checklist: RELEASE_CHECKLIST.md
- Known limitations: KNOWN_LIMITATIONS.md
- Screenshot capture guidance: docs/screenshots/README.md
This should be presented as a stabilized learning-platform case study, not as a finished production SaaS product.
Concrete shape of the project at the current commit:
- Twenty-six live course tracks, including seven coverage-gated core courses: HTML, CSS, JavaScript, React, TypeScript Foundations, React + TypeScript, and Next.js.
- Durable learner state (bookmarks, notes, and a spaced-repetition review queue) with same-browser retry support for direct progress writes. The former XP/streak/badge gamification layer and its Supabase reward backend were retired (PR #294) and the backing tables dropped from the live database (PR #296).
- A lazy-loaded Monaco editor split into multiple chunks via Vite manual chunking so it never enters the initial bundle.
- A top-level
ErrorBoundaryso a provider crash falls back to a visible retry/reload screen, not a blank page. - A Vitest unit/component suite of 2,100+ tests including accessibility integration tests (axe-core).
- A Playwright public smoke suite plus opt-in authenticated lesson, mobile, and visual paths.
Files most worth a look from a senior reviewer:
src/context/ProgressContext.tsx— the dual-layer optimistic + canonical persistence model. Owns progress state once and publishes it through ~8 narrow memoized context slices so consumers re-render only on their slice. The XP/badge/streak reward backend was retired (PR #294); what remains is the clean persistence model for completions, quiz scores, the SR review queue, bookmarks, and notes.src/services/srAlgorithm.ts— pure SM-2-style spaced-repetition scheduling (nextSRCardState) extracted fromProgressContext.updateSRCardso it's testable in isolation.src/services/progressWriteQueue.ts— same-browser retry queue for direct optimistic progress writes. Failed saves enqueue, replay on reconnect or next-session load, and expose a visible "Retry now" action without claiming universal cloud durability.src/components/learning/QuizView.tsx+src/hooks/useQuizSession.ts+src/components/learning/quiz/questionTypes.tsx— quiz engine split into a renderer registry + session hook.src/components/learning/CodeChallenge.tsx+src/hooks/useChallengeSession.ts+src/components/learning/challenge/— challenge engine split into the same shape (session hook + AI panel + preview-builder util). The session hook waits for the iframe'sonLoadbefore grading so DOM-based test assertions can land cleanly;src/data/html/challenges.ts(html-ch-1) is the demonstrated migration. Includes an explicit honesty note in the UI about what the auto-grader actually checks (seeKNOWN_LIMITATIONS.md).src/components/admin/LessonBuilder.tsx+src/hooks/useLessonBuilder.ts+src/components/admin/lesson-builder/— admin lesson-authoring tool split into a state hook, a pure codegen util, three view-tab components, and sharedLBField/ArrayFieldprimitives.src/services/aiService.ts—AIServiceErrorcarries a stablecodefromAI_ERROR_CODES, so callers switch on the code instead of regex-matching error message strings.src/hooks/useDocumentTitle.ts+src/utils/lessonNavCopy.ts+src/utils/savedPosition.ts+src/utils/learnerLocalStore.ts— small reusable primitives extracted out of the layout / context layer so the orchestrator components are composition glue rather than inline business logic. Each ships with a focused unit-test file.src/utils/helpers.ts— shared pure utilities:estimateReadingTime,getTodayString,getYesterdayString, and formatting helpers. Dependency-free and fully unit-tested; a good first stop for any time-aware or string-formatting logic in the app.src/utils/lessonToggle.ts—resolveLessonToggle(completedSet, stableKey, legacyKey)codifies the stable-first-then-legacy preference for the Complete lesson toggle. Migration-aware: an old learner whose DB row uses the legacy label-derived key gets that exact key toggled off, while new completions land on the stable key.src/hooks/useMarkLessonDone.ts+src/hooks/useToggleBookmark.ts— own the lesson-level mutation flows (complete lesson + bookmark toggle + bookmark removal). The bookmark file exports bothuseToggleBookmark(used by LessonView) anduseRemoveBookmark(used by BookmarksPanel), sharing one private submitter so the two surfaces can't drift. Each hook bundles the useFetcher mutation, sync-failure plumbing, optimistic toggle dispatch, and (for lesson completion) the min-feedback duration. Each ships with its own focused unit-test file.src/hooks/useTodayKey.ts— returns the current date as aYYYY-MM-DDUTC string and re-renders the consumer when the wall clock crosses midnight. Schedules a singlesetTimeoutto the next UTC midnight (more efficient than polling) and refreshes onvisibilitychangeto catch sleep/throttle gaps. Used by progress surfaces that need to know if "today" has changed without a page reload.src/hooks/usePrefersReducedData.ts— Data Saver /prefers-reduced-dataaware hook that gates the Monaco editor on desktop the same wayuseIsMobilealready gates it on phones.vite.config.js— Monaco manual-chunking strategy.
- React 19 + TypeScript + Vite
- Supabase Auth + Postgres
- Vercel static hosting + Vercel Functions
npm installCopy .env.example to .env:
cp .env.example .envRequired frontend values:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-supabase-anon-key- Create a Supabase project.
- Run
supabase-schema.sqlin Supabase SQL editor. - Run
npm run check:supabase-sql-lintto verify the required migration/privacy artifacts remain in source, thennpm run check:securityagainst the project to verify the live database's actual RLS/anon-wall state. - Apply the additive SQL files in
supabase/migrationsafter the base schema. Use Supabase Production Readiness for the current migration order. - Enable Email auth.
- Optionally enable Google/GitHub auth.
npm run devInstall Chromium once:
npm run test:e2e:installRun smoke tests:
npm run test:e2eOptional authenticated smoke coverage requires:
E2E_EMAIL=learner@example.com
E2E_PASSWORD=your-test-password
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-supabase-anon-keyConfigured in vercel.json:
- SPA rewrites — all paths serve
index.html - Security response headers (CSP, HSTS, X-Frame-Options, Permissions-Policy, COEP/COOP)
Serverless functions live in api/. The build command (npm run build) and
output directory (dist) are auto-detected by Vercel.
- Live deploy verification:
npm run check:production-deploy -- --url <production-url>
For release QA, use RELEASE_CHECKLIST.md.
Note:
docs/netlify-production-verification.mdis a historical reference from when this project ran on Netlify. Production is now on Vercel. Usenpm run check:production-deployand the Vercel dashboard for live deploy checks.