Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Architecture of a Terminal-Themed CV Site

This site started as three JavaScript files and a question at 3 AM: "Can you turn my PDF resume into a website?" What came back was 5,620 lines of vanilla JS that worked -- until it didn't. Every new feature tangled with every other feature. Navigation raced with scroll spy. The TOC fought with the headings panel. Copy feedback timers leaked. And there was no way to test any of it without opening a browser.

The fix was not a framework. It was 15 state machines -- pure TypeScript functions with zero DOM dependencies, zero side effects, and zero coupling between them. Each one small enough to read in five minutes. Each one testable in milliseconds. Together, they run every interactive behavior on this site.

This series walks through the extraction, from the simplest toggle to the most complex scroll orchestration, then covers the TypeScript migration that made the interfaces explicit and the testing strategy that keeps them honest.


Before and After

Metric Before (vanilla JS) After (TypeScript state machines)
Files with behavior logic 3 (app.js, markdown-renderer.js, theme-switcher.js) 15 state machines + 3 wiring files
Largest file ~3,500 lines 163 lines (TocScrollMachine)
DOM references in logic Everywhere Zero (callback injection)
Unit tests 0 27 test files, 200+ test cases
Coverage (src/lib/) N/A 98% statements, 95% branches
Test execution time Manual only 600ms (unit), ~30s (E2E)

Part I: Three Files and Five Thousand Lines

The original architecture and why it broke. State encoded as booleans, race conditions on rapid clicks, DOM coupling that made unit testing impossible. The "before" picture that motivated everything that follows.

Part II: The Pattern -- Factory, Closures, Callbacks

The architecture applied to the four simplest machines: FontSizeManager, AccentPaletteMachine, CopyFeedbackMachine, and SidebarResizeMachine. Factory functions, closure state, callback injection, guard clauses, and pure helpers. The template that every machine follows.

Part III: Navigation and Interaction Machines

SpaNavMachine and PageLoadMachine -- the two machines that orchestrate every page transition. Plus TourStateMachine, ScrollSpyMachine, ZoomPanState, and KeyboardNavState. Navigation classification, the generation counter pattern, composition at the wiring layer, and three pattern variations.

Part IV: The TOC Machine Cluster -- When Simple Isn't Enough

Five machines that power the table of contents. From the 30-line TocTooltipMachine to the 8-state TocScrollMachine -- the most complex machine in the project. Geometry injection, timer injection, user scroll locking, and the wiring diagram that connects them all.

Part V: From Vanilla JS to TypeScript

The migration that made it all explicit. tsconfig choices, esbuild for browser bundles, tsx for scripts, the IIFE output format, and concrete bugs that strict mode caught. Two build pipelines: browser (esbuild) and Node.js (tsx).

Part VI: Testing Pure State Machines

98% statement coverage with zero DOM dependencies. Unit tests with Vitest, property-based tests with fast-check, E2E tests with Playwright, coverage thresholds as quality gates, and the @FeatureTest / @Implements decorator system that links tests to requirements.


How to Read This Series

If you want the architecture: Start with Part I for motivation, then Part II for the pattern. Parts III and IV apply it at increasing complexity.

If you want the migration story: Jump to Part V (TypeScript) and Part VI (testing). They reference earlier parts but stand alone.

If you're here for the TOC complexity: Part IV is the climax. But read Part II first -- it introduces the vocabulary (factory function, callback injection, guard clause, geometry injection) that Part IV uses heavily.


The Machine Catalog

All 15 state machines, sorted by complexity:

Machine States Lines Part Pattern
TocBreadcrumbMachine 43 IV Pure functions
FontSizeManager 5 steps 47 II Factory + closure
AccentPaletteMachine 2 91 II Factory + closure
TocTooltipMachine 3 101 IV Factory + closure
PageLoadMachine 6 103 III Factory + closure
KeyboardNavState 105 III Pure functions
CopyFeedbackMachine 4 105 II Factory + closure
TourStateMachine 4 110 III Factory + closure
TocExpandMachine 2/section 111 IV Factory + closure
SidebarResizeMachine 2 114 II Factory + closure
ScrollSpyMachine 116 III Pure functions
ZoomPanState 2 78 III Immutable reducer
HeadingsPanelMachine 5 151 IV Factory + closure
TocScrollMachine 8 163 IV Factory + closure
SpaNavMachine 5 192 III Factory + closure

Key Concepts by Part

This table maps each concept to the part where it's introduced:

Concept Introduced in Used throughout
Factory function pattern Part II (FontSizeManager) All machines
Closure state Part II (FontSizeManager) 11 of 15 machines
Callback injection Part II (AccentPaletteMachine) 11 of 15 machines
Guard clauses Part II (CopyFeedbackMachine) All machines
Timer injection Part II (CopyFeedbackMachine) CopyFeedback, TocTooltip
Pure helper functions Part II (SidebarResizeMachine) Most machines
Navigation classification Part III (SpaNavMachine) SPA navigation
Generation counter Part III (PageLoadMachine) Page load staleness
Immutable reducer Part III (ZoomPanState) Zoom/pan overlays
Pure function pattern Part III (ScrollSpyMachine) ScrollSpy, Breadcrumbs, Keyboard
Modal priority routing Part III (KeyboardNavState) Escape chain
Geometry injection Part IV (HeadingsPanelMachine) HeadingsPanel, TocScroll
CSS animation as state Part IV (HeadingsPanelMachine) HeadingsPanel, SpaNav
User scroll locking Part IV (TocScrollMachine) TOC scroll orchestration
Wiring layer composition Part IV (all 5 TOC machines) All machines
Discriminated unions Part V (TypeScript) All state types
IIFE bundling Part V (esbuild) Browser delivery
Coverage thresholds Part VI (Vitest) Quality gates
Property-based testing Part VI (fast-check) Invariant verification
Requirements decorators Part VI (@FeatureTest) Compliance tracing