Code guide: read, run, change, explain
One installation, thirteen runnable pages
Use Node 24 LTS, version 24.15.0 or newer within the 24.x line. This bundle was checked with Node 24.19.0 and npm 11.9.0. From the course root, run npm ci, then npm run dev. Keep that terminal running and open http://127.0.0.1:5173/demos.html. If Vite reports another port because 5173 is occupied, use the URL it prints. Start a second terminal for checks. Do not open TSX entry pages with file://.
The root package and lockfile serve all ten checkpoints and both capstone versions. demos.html — download the course ZIP (Extract it and follow the included setup instructions for demos.html.) is the thirteenth page, an index. There is no need to install inside each checkpoint. Stop Vite with Ctrl+C.
Direct checkpoint URL pattern: http://127.0.0.1:5173/checkpoints/01/ through /checkpoints/10/. Capstone URLs: /capstone/starter/ and /capstone/reference/.
Commands and what each proves
npm run typecheck: strict checks across teaching code, starter code, tests and configuration. It does not execute behavior.npm test: the completed teaching/reference test suite intests/. Passing this does not mean the independent starter is finished.npm run test:watch: reference tests in watch mode.npm run test:capstone: learner acceptance checks importing only the starter's own functions and component. These intentionally fail at the beginning. They cover M1–M3 and storage parsing in M4, not the whole rubric. Add your own tests for later milestones.npm run build: type-check, then build demo app entry points intodist/.npm run preview: serve the production demo build, normally athttp://127.0.0.1:4173/demos.html.npm run check: type-check, reference tests and production build. This deliberately excludes initially red learner acceptance tests.
dist/ contains the runnable demo index, ten checkpoints, capstone starter/reference and fixtures. It does not contain the course reading pages. A production build is not a deployment, and a preview server is a local acceptance tool. Hash routes such as /checkpoints/10/#/events/garden-morning do not need a server rewrite for detail paths. The document and its assets still need to be served over HTTP.
Read source in this order
checkpoints/01/index.html— download the course ZIP (Extract it and follow the included setup instructions forcheckpoints/01/index.html.) andmain.tsx: the root element, module script,createRoot, Strict Mode and typed TSX values. This entry is intentionally self-contained.checkpoints/02/main.tsx: explicit props, card composition, conditional places wording and stable IDs.shared/mount.tsxremoves repeated mounting boilerplate. The root lookup checksnullinstead of asserting it away.shared/domain.ts,shared/fixtures.tsand checkpoint 03: event shape, category union, optional description,unknowninput and field-by-field validation. Editpublic/fixtures/events.jsonto experiment with the imported boundary in checkpoint 03; restore valid data afterward.checkpoints/04/main.tsxandshared/components.tsx: query/category state, labeled controls, a clear-search action that preserves category, typed callbacks, and visible results calculated during render.checkpoints/05/main.tsxandtoggleSavedindomain.ts: ID-only state, functional immutable updates and stable list keys. When a saved-only card disappears, focus returns to a stable heading.shared/PracticeForm.tsxand checkpoint 06: exact fictional participantSample learner, one to four practice places, controlled state, typed event handlers, named error results and a real form reset event. Error focus is requested after attributes commit. That small browser-synchronization effect previews lesson 07; changing fields does not trigger repeated focus jumps.shared/storage.ts,shared/useSavedEvents.tsand checkpoint 07: JSON validation, lazy initialization, external synchronization and failure warnings. Use Clear saved events to persist an empty course selection without touching other apps' storage.shared/useEvents.tsand checkpoint 08: discriminated loading/error/success state, checked HTTP response, unknown JSON validation, AbortController cleanup, an active guard for stale results, and retry attempts.shared/ExplorerRoutes.tsxand checkpoint 09: declarative HashRouter composition, links, params, missing events, wildcard routes, saved screen, title/focus synchronization and an event-keyed form. Checkpoint 09 uses in-memory fixture data and does not enable the form. The form is enabled in 10.shared/IntegratedExplorer.tsx, checkpoint 10 andtests/: one composition owns a selectable local fixture and connects validated fetching, stored IDs, URL screens and synthetic form behavior.FixtureControls.tsxstays mounted through loading/errors so another fixture can recover the page. The Saved screen includes a clear action with a stable heading focus target. The capstone reference intentionally composes these same completed modules rather than maintaining a second copy.
Checkpoints are focused increments, not ten complete cumulative copies. Checkpoint 06 isolates the form; 07 isolates persistence; 08 isolates async loading. Shared source means an edit can affect several checkpoints. Use a copy or version control for experiments and read imports before changing a shared file. This is not a TypeScript utility-type or advanced React-pattern course: a few library types and the parser's type predicates are explained at the boundaries where they are needed.
Honest data and state policies
The six fixture IDs, categories, locations and dates continue the earlier fictional Harbor Together project. Fixtures are static examples, not live availability. places is displayed as fictional capacity; the practice form does not reserve or reduce it and can be inspected even on a full example event.
External event data must be an array with nonempty fields, known categories, real ISO calendar dates, nonnegative integer places, safe slug IDs and no duplicate IDs. HTML-shaped text stays text through JSX; the project never uses dangerouslySetInnerHTML.
Saved data uses only the key harbor-together:react-course:saved:v1. Valid slug strings are deduplicated; non-arrays or invalid members are rejected. Unknown but syntactically valid saved IDs are retained because a later/different data response might contain them. They do not create phantom cards or inflate the visible count. The app does not automatically delete them or implement cross-tab synchronization. Filters belong to the current list screen and reset when that screen unmounts, including navigation to another screen or a fixture reload; saved IDs persist above those screens. Fixture selection survives screen navigation but returns to the normal fixture on a full page reload.
A failed initial read or parse falls back to an empty in-memory selection with a warning. Mounting does not overwrite that original value. An explicit save, unsave or clear starts writing the current selection; failed writes keep the UI usable in this tab and disclose that persistence failed. Clearing writes [], not localStorage.clear().
The form accepts only Sample learner and the strings 1, 2, 3, or 4. It has no real registration endpoint, email field or personal-data persistence. Native constraints document intent; noValidate means the custom checks own the submit path. Success says that nothing was sent or saved. Editing clears stale success; reset clears controlled values, error state and confirmation and returns focus to the participant field.
Async fixture experiments
Checkpoint 08 and the integrated checkpoint/reference offer valid, empty, malformed, wrong-shape, missing and HTML-shaped local fixtures. All requests stay on the local server. A missing path may return a real HTTP error or a dev-server HTML fallback with status 200; either path is a handled error, but the error wording differs. HTTP error handling is tested explicitly with a 503 response, separately from malformed JSON.
Use browser network throttling to observe loading, then quickly change fixtures or leave the page. Cleanup aborts the previous request; the active flag also prevents late results from committing when a mock or source ignores abort. Retry changes attempt, one of the effect dependencies. No timeout or artificial delay is built into the lesson app.
Tests and remaining acceptance
Reference tests cover data boundaries, filters, identity, safe text, forms, storage read/write failures, no overwrite on failed initial reads, explicit clear, async HTTP/JSON/network failures, abort/stale results, Strict Mode effect cleanup, retry, obsolete late rejection, rapid fixture switching, clear-search category preservation, history, missing routes, saved-screen clear/focus, direct detail-to-detail reset and integrated hash navigation.
DOM tests do not establish pixel layout, browser compatibility, screen-reader announcements or beginner pacing. See the QA report and actual browser evidence for the exact checked scope. Still perform keyboard-only and screen-reader journeys, mobile-device input checks, a second browser check and a beginner pilot before treating this as classroom-validated material.
Version evidence
qa/dependency-evidence.json records the npm registry versions, integrity fields and relevant engine/peer metadata retrieved on 2026-10-14. The package pins direct dependencies exactly and commits package-lock.json. Current verified pins include React 19.3.0, React Router 8.4.0, TypeScript 7.0.2, Vite 8.3.4 and Vitest 5.0.4. This is a dated reproducible teaching baseline, not a promise that those are the latest versions forever.