End-to-End Verification
Objectives
By the end of this chapter, you should be able to:
- Run the full mechanical verification pass (
typecheck,lint,build,test) across the whole monorepo and know what each one actually catches that the others don’t - Walk every feature built across Modules 1-5 in one sitting, with a fresh
npm run db:migratedatabase, and confirm the finished app matches what each module promised - Recognize the specific cross-module interactions most likely to have quietly broken while building something else on top of them
๐ก Why this matters: Every module’s own “Try It” verified that module in isolation, right after building it. It’s never verified whether Module 5’s saved-locations feature still respects Module 4’s watch-region default, or whether Module 3’s theme system still applies cleanly to Module 5’s mobile bottom sheet. Five modules of features built in sequence is exactly where a regression hides โ this lesson is the first (and only) point in the course that looks at the whole thing at once.
The Mechanical Pass
Run these four, in this order, from the repo root:
npm run typecheck
npm run lint
npm run build
npm run test -w @skywatch/server
Each one catches a different class of mistake, and running only one is not a substitute for the others:
typecheckcatches a shape mismatch โ a field renamed inschema.tsbut not in the repo function that reads it, a store action whose signature changed in one caller but not another. It does not catch a value that’s the right type but the wrong number.lintcatches the mechanical stuff โ an unused import left over from a refactor (there are a few of these on purpose in this course, called out explicitly when they show up; anything not called out should be a real problem), a hook called conditionally, a missing dependency in auseEffectarray.buildcatches whattypecheckalone doesn’t: Next.js’s own production compilation, which is stricter about some patterns than the dev server is, and which would otherwise be the first time you discover a problem โ in production, after deploying.testcatches a behavior regression in the one place this course put automated tests โ the detection engine and geo helpers from the previous lesson. It does not catch anything else, because nothing else has tests; that’s a real, explicit limitation of this pass, not an oversight to gloss over.
If any of the four fail, fix it before moving on โ the manual pass below is much harder to interpret if you’re not sure whether a broken feature is a real bug or a symptom of something typecheck would have told you about in five seconds.
Fresh Database, Fresh Start
Run this pass against a database that’s been through every migration from scratch, not whatever state your dev database has accumulated โ that’s the only way to be sure Module 1 through Module 5’s migrations still apply cleanly, in order, on top of each other.
npm run db:down
docker volume rm skywatch_pg_data 2>/dev/null || true
npm run db:up
npm run db:migrate
npm run db:seed:airports
npm run dev
If db:migrate fails here, it’s telling you something a per-module “Try It” couldn’t: two migrations that individually look fine but conflict when applied in sequence (a column added by one migration that a later one assumes doesn’t exist yet, for instance). This is also the moment npm run db:seed:airports (Module 5, Lesson 1) needs to run before anything else, since the airport overlay has nothing to show without it.
The Verification Checklist
Work through this in order โ later items lean on earlier ones being confirmed working first, same as the modules themselves did.
Module 1 โ Data layer. psql $DATABASE_URL -c "\dt" shows all seven tables. watch_regions has exactly one row with is_default = true (the seeded JFK-area default from Module 2’s poller startup).
Module 2 โ Backend services. The server boots without errors and starts polling (watch the console for poll-cycle logs). GET /api/watch-regions returns the seeded default region. Open a raw WebSocket client against /ws and confirm a {"type":"connected",...} message arrives immediately, followed by {"type":"positions",...} roughly every 15 seconds.
Module 3 โ Frontend core and theming. The app loads, shows the map, and live aircraft markers appear within one poll cycle. Click a marker or a flight-list row and confirm the detail panel populates; click again elsewhere and confirm it can be deselected. Cycle the theme toggle through all four themes and confirm <html data-theme="..."> updates with no flash on reload, and confirm zero matches for the literal string dark: anywhere in apps/web/src.
Module 4 โ Watch regions and history. Add a second watch region, set it as default, reload, and confirm the map opens there. Pan away and confirm the RETURN TO WATCH REGION control appears and free-pan positions load. Select an aircraft inside a watch region, wait two or three poll cycles, and confirm a trail renders. Open history playback, confirm it loads, scrubs, and plays, and confirm live data resumes instantly on exit.
Module 5 โ Feature build-out. Toggle airports on/off and confirm the preference survives a reload. Zoom into a large airport and confirm a METAR badge appears. Trigger a test alert (psql pg_notify against squawk_alert, same technique as Module 2’s realtime-layer lesson) and confirm it plays a sound, shows a browser notification (if permission was granted), and appears in the header’s alert feed. Set a home location, then confirm a manually-inserted overflight_log row shows up on /overflights. Enter compare mode and confirm two regions render side by side, each with its own live traffic. Save a location, jump to it, and confirm it doesn’t affect the poller’s watch-region list. Capture a snapshot and confirm the PNG actually shows map tiles (not a blank background โ this is the crossOrigin fix from that lesson doing its job). Copy a share link, open it in a new tab, and confirm it restores the same view. Shrink the viewport below the md breakpoint and confirm the mobile bottom sheet replaces the desktop sidebar instead of leaving an empty gap.
Cross-cutting. Switch through all four themes again, this time on top of every panel opened above (watch regions, history playback, the alert feed, compare mode, the mobile sheet) โ confirm none of them have a hardcoded color that breaks in amber or blue. Reload the page mid-free-pan and confirm it doesn’t get stuck off the default region forever (free-pan is session state, not persisted โ reloading should return to the default). Resize between desktop and mobile widths a few times in a row and confirm nothing duplicates or disappears permanently.
Try It
- Run the full mechanical pass and the fresh-database bootstrap above. Fix anything that fails before continuing.
- Work through the entire verification checklist in one sitting, in order, without skipping ahead โ note anything that doesn’t match what its module promised.
- Pick one item from the “Cross-cutting” section and try to break it on purpose (for instance, open history playback, then immediately switch themes) โ confirm the app degrades sensibly rather than silently, even in a combination no single module’s own “Try It” ever exercised.
Recap
typecheck,lint,build, andtesteach catch a different, non-overlapping class of mistake โ running one is not a substitute for the other three.- A fresh database run through every migration in sequence is the only way to be confident Modules 1 through 5’s schema changes still compose correctly, rather than happening to work on a dev database that’s never been rebuilt from scratch.
- The checklist runs module by module on purpose, but the real value of this lesson is the “Cross-cutting” section at the end โ the combinations no single module’s own verification step ever had a reason to try.
Next lesson: what’s deliberately out of scope, and where the patterns you’ve learned take you next.