Part I: Why Typed Specifications
644 tests. All green. Ship it? But can you answer one question: is the search feature fully tested?
The Green Bar Illusion
Every developer knows the feeling. Tests pass. Coverage is high. The build is green. You merge with confidence.
Then a user reports that keyboard navigation in search results doesn't work on mobile. You check the test suite — there are 54 E2E tests, 235 unit tests, visual regression, accessibility audits. Surely one of them covers this?
You grep. You search. You read test names. Twenty minutes later, you still can't answer the question: which tests verify the search feature, and are all its acceptance criteria covered?
This is the green bar illusion. Passing tests prove that something works. They don't prove that everything you care about is verified.
The Gap
In most projects, three things exist in separate worlds:
- Requirements — in Jira, Notion, a Google Doc, or someone's head
- Tests — in test files, organized by technical concerns (unit/, e2e/, a11y/)
- The mapping — in a human's mental model, or a markdown table that drifted three sprints ago
This website — a terminal-styled CV with SPA navigation, scroll spy, search, mermaid diagrams, 8 accent color palettes, 4 theme modes, mobile viewports, and a full static build pipeline — has 20 distinct features and 112 acceptance criteria.
Before typed specifications, the connection between "what should the website do?" and "which tests verify it?" was a table in a blog post. Human-maintained. Drift-prone. Unenforceable.
What "Typed Specifications" Means
A typed specification system makes the connection between features and tests explicit, compiler-checked, and scannable:
- A feature is an abstract class. Not a string. Not a ticket.
- An acceptance criterion is an abstract method. Not a bullet point.
- A test declares which AC it verifies via a decorator. Not a comment.
- The compiler catches typos —
@Implements<NavigationFeature>('typo')is a type error. - A scanner cross-references features and tests, producing a coverage matrix.
- A quality gate fails the build if critical features aren't fully covered.
The end state looks like this:
── Feature Compliance Report ──
✓ NAV SPA Navigation + Deep Links 8/8 ACs (100%)
✓ SPY Scroll Spy 7/7 ACs (100%)
✓ SEARCH Search 5/5 ACs (100%)
✓ BUILD Static Build Pipeline 13/13 ACs (100%)
✓ A11Y Accessibility 5/5 ACs (100%)
...
────────────────────────────────────────────────────────────
Coverage: 112/112 ACs (100%)
Critical uncovered: 0
Quality gate: PASS ── Feature Compliance Report ──
✓ NAV SPA Navigation + Deep Links 8/8 ACs (100%)
✓ SPY Scroll Spy 7/7 ACs (100%)
✓ SEARCH Search 5/5 ACs (100%)
✓ BUILD Static Build Pipeline 13/13 ACs (100%)
✓ A11Y Accessibility 5/5 ACs (100%)
...
────────────────────────────────────────────────────────────
Coverage: 112/112 ACs (100%)
Critical uncovered: 0
Quality gate: PASSEvery feature. Every acceptance criterion. Every test linked. Machine-verified.
Why You Can't Just Drop It In
Here's the thing nobody tells you: you can't add typed specifications to a project that doesn't have the foundation for them.
You need:
- Features worth specifying — a hello-world doesn't need this. You need real complexity: 20 features, not 2.
- Deterministic output — if your app only runs in a browser and you can't pre-render it, you can't test it reliably at build time.
- Tests at multiple layers — typed specs link to tests. If you only have unit tests, you can only specify unit-testable behavior. Features like "scroll spy tracks headings" need E2E tests.
- A culture of testing — typed specs make the gap visible. If the team doesn't write tests, making the gap visible just creates noise.
This website went through four phases before typed specifications were possible:
Each phase was prerequisite to the next. The rest of this series walks through each one.
What You'll Learn
By the end of this series, you'll understand:
- Why each phase matters and what it unlocks (Parts II-III)
- How to define features as abstract classes with typed ACs (Part IV)
- How to link tests to features with three decorators (Part V)
- How to build a compliance scanner and quality gate (Part VI)
- What's left to improve and how to get there (Part VII)
The approach is language-agnostic in philosophy but implemented in TypeScript. The same pattern works in C# (with Roslyn source generators — see Requirements as Code in C#), Java (with annotations), or any language with a type system and decorators/attributes.
Next: Part II: Building the Foundation — why you need a SPA and static generation before you can specify anything.