# Capstone · Build and defend your own event explorer

## The client brief
The fictional Harbor Together team wants a local prototype that lets a learner explore community events, keep a browser-local shortlist, visit event details and check a synthetic practice form. It must remain understandable when data are empty, invalid or unavailable. The team will judge your explanation and failure handling, not only the happy-path appearance.

Allow a provisional 5–7 hours plus revision. This estimate is unpiloted. You have completed ten taught modules; now combine the concepts without copying the finished reference. All records and form values are fictional. No live submission, real contact details, payment, login or publication is permitted by this assignment.

## Start and preserve independence
Open capstone/starter/README.md. Work in capstone/starter/App.tsx, model.ts and new files you create within that folder. The supplied page renders and type-checks, but its model helpers contain TODOs. Use npm run test:capstone to run tests that import the starter only. They are expected to start red.

You may reuse the mount helper and stylesheet. Do not import the completed shared parser, loading hook, routing UI, form or reference app to satisfy the assignment. Read earlier lessons and checkpoint code when stuck, then close the example and implement your own version. Keep the separate reference closed until you have submitted your first complete attempt.

The supplied learner tests cover data parsing, combined filters, immutable ID toggling and stored-ID parsing. They do not grade the whole application. You must add UI/integration tests and complete the manual checklist for the remaining milestones.

## Milestone 1 · A trustworthy model
Implement parseEvents from unknown data. Require an array of records with stable slug-like IDs, nonempty title/location, one of the three known categories, a real YYYY-MM-DD calendar date and a nonnegative integer place count. Reject duplicate IDs. Treat [] as a valid dataset. Preserve the documented optional description when present and valid if you choose to support it.

Evidence: valid/empty success, wrong outer shape, bad category, negative/fractional capacity, impossible date and duplicate-ID rejection. Explain what TypeScript checks and what runtime parsing checks. Replace the starter's seed with validated fixture input when the parser is ready.

## Milestone 2 · State and derived results
Implement combined normalized search and category filtering without mutating inputs. The search must cover title and location. Store only independent control values. Derive the visible list and count. Include a useful zero-match message and a clear-search control that does not unexpectedly reset category.

Evidence: query alone, category alone, both together, padded/case-varied query, no matches and restoring all results. Explain the state owner and why no filter effect is needed.

## Milestone 3 · Identity and saved events
Implement immutable toggling of saved IDs and stable list keys. Show pressed state accessibly on named save buttons. A hidden-by-filter event must remain saved. A saved view should show current records for remembered IDs, with an explicit policy for stale IDs. Do not store duplicated event objects.

Evidence: save, unsave, filter away/back, reorder, saved-only results, clear and no mutation. When unsaving the last visible saved card removes the focused button, move focus to a meaningful remaining control or screen heading.

## Milestone 4 · Failure-tolerant persistence
Read only your namespaced course key. Parse and validate stored data, deduplicate IDs, and handle read or write failure without crashing. Synchronize current saved IDs deliberately. A failed initial read must not immediately overwrite stored data you could not inspect. Store no form values or personal details. Explain the browser-origin boundary and your reset policy.

Evidence: same-origin reload, corrupt data fallback, unavailable-storage behavior and continued in-memory use. Do not clear the entire origin's storage.

## Milestone 5 · Async data with recovery
Load the supplied local fixtures using fetch. Represent loading, error and success explicitly. Check HTTP status, parse JSON and validate records. Make empty success different from failure. Support retry. Cleanup must prevent obsolete success or error from overwriting the current selection after a rapid fixture change.

Evidence: normal, empty, malformed and wrong-shape fixtures; deterministic HTTP error; retry failure and recovery; B resolving before A; old A rejection after B succeeds. Use controlled requests in tests rather than an arbitrary sleep.

## Milestone 6 · Navigation, practice form and handoff
Add hash-based explore, saved and detail screens plus missing-route and missing-event recovery. Keep saved IDs shared across screens. Set meaningful titles and focus after navigation without overwriting the fragment route. Decide and document whether filters survive navigation.

Add a controlled practice form accepting only Sample learner and one to four practice places. It must never submit or store form data. Associate errors, focus the first invalid field after error markup is ready, clear stale success on edits, reset values and feedback, and state plainly that nothing was sent or booked. Switching directly from one event detail to another must not carry over an old event's practice confirmation.

Evidence: URL copy/refresh, Back/Forward, unknown routes/IDs, keyboard form journey, no outgoing form request, direct detail-to-detail reset, automated behavior tests, type-check and build. Add a handoff note with setup, decisions, known limits and manual acceptance results.

## Rubric · 30 points
- Data contract and runtime boundary: 5 points
- State ownership, filters and immutable identity: 5 points
- Persistence correctness and recovery: 4 points
- Async state, errors and obsolete-work protection: 5 points
- Routes, form truthfulness and accessibility: 6 points
- Tests, reproducible build and honest explanation: 5 points

For each category, full credit means all stated milestone behavior is demonstrated and explained. About half credit means a working happy path with significant gaps or a weak explanation. Zero means absent, copied without understanding, or contradicted by evidence. A suggested completion threshold is 24/30, with no missing safety gate below. This is a teaching rubric, not a validated certification standard.

## Non-negotiable completion gates
No real submission or personal-data storage. No bypass of validation using any or unchecked assertions at external boundaries. No reference imports in learner checks. No stale request replacing current data. No claim that unperformed browser or assistive-technology checks passed. A failed gate requires revision regardless of total score.

## Submit and defend
Submit your starter-folder source, added tests, evidence log and release note. Explain: why each state value exists; where unknown input becomes trusted; what cleanup protects; how keys differ from persistence; and what your passing tests do not prove. After your first submission, compare with the [separate reference walkthrough](walkthrough.html) and revise one design choice with a written reason.
