The Atlas A map of the system that builds this site: its parts, the territories that group them, and the writing about each. Every shape is a real part of the project rather than a diagram of an idea.
Each territory groups the parts that fail together. Read a part for what it does, and read on for how it is built. A mark on a shape names a role the grouping cannot show: some hold what outlives a request, some refuse what does not pass, and some are the one way in for a caller. A journey traces one job through the parts it touches, in order.
Journeys
A post gets published A ticket becomes a merge Someone searches An agent asks a question A reader switches to Markdown A publish becomes a deployment Someone sends feedback The instruction layer What an agent is told before it touches anything: the rules that arrive with the files they govern, the procedures it loads by name, and the record of what earlier sessions learned.
The gate What refuses work that skipped a step: hooks that decline the push, and five seams of tests inside one verify command.
Where words come from Three content sources behind one door, and the graph that turns documents into pages.
How a page becomes bytes Everything between a node in the graph and the bytes a reader receives: templates, derived renderings, the Markdown twin, the design tokens, and one screen effect.
What comes out The ways the site answers: read pages, search, an HTTP API, working demos, and developer documentation — every one a client of the same read interface.
Getting it live From a publish to a deployment: the build steps that assemble the site, the service that decides when to ship, and the checks that prove the deployment serves what was built.
RU
The rules 22 files · 1,002 lines
Path-scoped instructions that arrive in an agent’s context when it touches the files they govern.
Each rule is a Markdown file naming the paths it binds. The harness loads one when a matching file is read, so guidance costs attention only where it applies, and there is no central list to keep in sync.
Journeys through this part
SK
The skills 13 files · 1,525 lines
Procedures an agent loads by name for a whole job: landing a pull request, cutting a worktree, filing an issue.
A skill is a directory with one entry file, plus the references it draws on. A rule arrives on its own with the files it governs; a skill is chosen, and invoking it puts the procedure into context in place of improvisation.
Journeys through this part
RE
The record 173 files · 25,380 lines
The searchable memory of past decisions, bugs, and workflow lessons.
Solution documents carry fields a search can filter by — the module touched, the kind of problem. Decision records are archived, and the rules are their current statement.
Journeys through this part
GA
The guard 39 files · 6,480 lines
The hooks that refuse a commit to the main branch, a push nothing has reviewed, and a stop that left the board wrong.
Git hooks committed in the repository, plus session hooks that bind while an agent works. Each refusal prints the step it wants, so the block is itself an instruction rather than a dead end.
Journeys through this part
TS
The tests 189 files · 55,783 lines
Five seams of checks, from pure fixtures to a real browser, behind one command.
Each seam has its own configuration and answers only what it needs: fixtures alone, the built bytes, a browser, the service, or a live deployment. No one seam catches every kind of mistake, which is why there are five.
Journeys through this part
SA
Sanity 19 files · 4,919 lines
The Studio where the writing happens, and the client that reads it back.
Schemas define what an author can write, and generate the types the rest of the project is written against. One fetch projects every document type, stamps the corpus, and hands it to the loader.
What happens here
The author publishes in the Studio and the document is stored. For the author the job is done here.
A post gets published · step 1 The publish moves the content stamp, which changes whenever any document does. Nothing else reacts yet.
A publish becomes a deployment · step 1 Journeys through this part
A post gets published A publish becomes a deployment LO
The loader 5 files · 990 lines
The one door every document enters through, whatever its source.
Three sources — fixtures, Sanity, local Markdown — answer the same call, and every document is asserted field by field before anything downstream sees it. A malformed one fails here, naming the document, the field, and the source it came from.
Journeys through this part
GR
The graph 20 files · 2,053 lines
The derived map of every page, route, and cross-reference on the site.
A build walks the corpus, resolves every slug and reference into a node, and refuses one that names nothing. The route table and every emitted page read what it derived, so linking, search and routing never work out their own relationships.
What happens here
The graph places the post as a node: its route is assigned and its references become edges, and a reference naming nothing fails here.
A post gets published · step 3 The graph supplies the candidate nodes, routes and references already resolved.
Someone searches · step 3 The graph supplies the nodes the answer draws from, with their references already resolved.
An agent asks a question · step 4 Journeys through this part
A post gets published Someone searches An agent asks a question BL
The blocks 13 files · 2,124 lines
The closed set of block types a body may carry, each with both of its renderings.
One table pairs each block with its page component and its Markdown form. A block missing either half does not compile, so the two renderings cannot drift apart.
What happens here
Each block in the body is matched to its page component and its Markdown form, both confirmed before rendering begins.
A post gets published · step 4 Every block in the body already carries a Markdown form beside its page component, so no content lacks a twin.
A reader switches to Markdown · step 1 Journeys through this part
A post gets published A reader switches to Markdown TP
The templates 55 files · 3,533 lines
The components that turn a node in the graph into a page.
A registry maps each node kind to its template and, where one exists, its interactive surface — total over the route table, so a new kind fails to compile until its template exists.
What happens here
The template turns the node into the page a reader will open.
A post gets published · step 5 The results are rendered as a page on the server, so the answer arrives complete rather than fetched piece by piece.
Someone searches · step 5 The form renders outside the swapped region, so it works in every mode.
Someone sends feedback · step 1 Journeys through this part
A post gets published Someone searches Someone sends feedback RP
The representations 10 files · 1,240 lines
The derived renderings of the site: Markdown twins, the feed, social cards, the sitemap, and what robots read.
Each is a pure function of the graph, and golden files pin what every one of them emitted last time, so an unintended change reads as a diff in review rather than a surprise after release.
What happens here
The Markdown twin, the feed entry, the social card and the sitemap listing all derive from the same node, so none can disagree with the page.
A post gets published · step 6 The twin was derived at build time and embedded in the page, so it travels with the document.
A reader switches to Markdown · step 2 Journeys through this part
A post gets published A reader switches to Markdown MD
Markdown mode 1 files · 41 lines
The control that swaps a page for its own Markdown rendering.
The page embeds its twin at build time, so the control writes one attribute and fetches nothing.
Journeys through this part
A reader switches to Markdown TK
The tokens 2 files · 1,566 lines
The design system: the faces, colours, and spacing every surface draws from.
Custom properties in one theme block, which Tailwind reads. A component reaches for a token before it writes a rule.
Journeys through this part
A reader switches to Markdown CR
The CRT 1 files · 39 lines
The screen-curvature effect over the whole site.
An SVG displacement filter over the screen layers, which are siblings of the content and never its ancestors — no canvas, and no animation loop.
No journey passes through this part.
RA
The read API 12 files · 1,771 lines
The one interface every query surface is a client of.
The search page, the HTTP API, and the agent tools all call the same functions, so no two doors can answer the same question differently. Ranking weighs each document against the query, and the answer is a set of links rather than the content itself.
What happens here
The read API takes the query, through the same functions every other door calls.
Someone searches · step 2 Ranking happens in the read API, which returns the ordered answer.
Someone searches · step 4 The endpoint hands off to the read API, the identical functions the search page calls.
An agent asks a question · step 3 Ranking returns through the read API, so the program’s answer is made where a person’s would be.
An agent asks a question · step 5 Journeys through this part
Someone searches An agent asks a question QL
The query language 6 files · 1,692 lines
The small language a search box accepts.
A hand-written parser produces a shape the read API executes, and the grammar is tested operator by operator. A query too long or too deeply nested is refused rather than crashing.
Journeys through this part
AP
The HTTP API 38 files · 4,881 lines
The machine door: endpoints that answer an agent what the pages answer a person.
A route catalogue, a problem list, and a validator define each operation once; the reference documentation is generated from the same source, so it cannot fall behind the endpoints.
What happens here
The call lands on an endpoint generated from the same source as its documentation — described, validated, versioned with the site.
An agent asks a question · step 2 The endpoint returns the answer shaped as the catalogue promised.
An agent asks a question · step 6 The endpoint takes the submission and stores it — the same capability every channel reaches.
Someone sends feedback · step 2 Journeys through this part
An agent asks a question Someone sends feedback DE
The demos 21 files · 4,175 lines
Working things a person or an agent can use — this Atlas is one of them.
Each demo is a metadata row beside a component. The loader takes the rows, the registry takes the code, and a demo missing its component does not compile.
Journeys through this part
DO
The docs 16 files · 1,375 lines
The developer documentation, its API reference included.
The framework owns the bodies as authored Markdown; metadata rows beside them join each page to the graph, so documentation gains routes, search and a twin like any other page.
No journey passes through this part.
IN
The integrations 4 files · 534 lines
The build steps that fetch the corpus, generate the reference, and stamp the output.
Each runs inside the build and writes what a test seam later reads back.
Journeys through this part
A publish becomes a deployment DW
The deploy window 6 files · 359 lines
The service that decides when a publish becomes a deployment.
Its own process with its own build. It watches the content stamp and opens the window a deployment ships in, so a burst of edits becomes one release.
Journeys through this part
A publish becomes a deployment LV
The live checks 2 files · 1,697 lines
What proves a deployment serves what the build produced.
The live seam asks the deployed site itself and compares its answers against the build’s claims. On the commit path it runs against the held deployment before the domain moves; on the content path it reports after the fact, which is a stated trade.
Journeys through this part
A publish becomes a deployment A post gets published Turn a finished piece of writing into a public page, with no person running a build. Six parts take part, and none of them is the author.
SAThe author publishes in the Studio and the document is stored. For the author the job is done here. LOThe loader gathers the corpus through the single door and asserts every field, so nothing malformed reaches the build. GRThe graph places the post as a node: its route is assigned and its references become edges, and a reference naming nothing fails here. BLEach block in the body is matched to its page component and its Markdown form, both confirmed before rendering begins. TPThe template turns the node into the page a reader will open. RPThe Markdown twin, the feed entry, the social card and the sitemap listing all derive from the same node, so none can disagree with the page. A ticket becomes a merge Take a task from assigned to merged, with every quality step enforced rather than hoped for. Enforcement here is part of the work, not a wall at the end of it.
RUThe rules governing the files being touched arrive in context before the first edit. SKThe agent loads the procedure the job needs by name, trading improvisation for steps that have worked before. GAThe guard refuses a push nothing has reviewed. Here the refusal is expected traffic: it prints the missing step, and the agent supplies it. TSThe verify command runs the seams it holds — fixtures, the built bytes, a browser — each answering what the others cannot. REWhat the work taught is written into the record while it is fresh, so the next session starts further along. This journey runs in the development workflow, not in the site’s code, so no build can capture its wire.
Someone searches Answer one typed question with a ranked, rendered page. The route visits the read API twice: outward with the parsed query, and back with the ranked answer.
QLThe parser turns the typed text into a structured query — words, phrases, field filters — the site can execute. RAThe read API takes the query, through the same functions every other door calls. GRThe graph supplies the candidate nodes, routes and references already resolved. RARanking happens in the read API, which returns the ordered answer. TPThe results are rendered as a page on the server, so the answer arrives complete rather than fetched piece by piece. An agent asks a question Give a program what a person reading the page would get. Seven stops, the longest route on the map, and two of them are the same door — once entering, once leaving.
DEThe demo declares what a program may call: the tool surface is a published list any caller can read. APThe call lands on an endpoint generated from the same source as its documentation — described, validated, versioned with the site. RAThe endpoint hands off to the read API, the identical functions the search page calls. GRThe graph supplies the nodes the answer draws from, with their references already resolved. RARanking returns through the read API, so the program’s answer is made where a person’s would be. APThe endpoint returns the answer shaped as the catalogue promised. DEThe program receives the same substance a reader of the page would have. A reader switches to Markdown Swap a rendered page for its Markdown twin with no network activity. Four parts take part, and every one of them finished its work at build time.
BLEvery block in the body already carries a Markdown form beside its page component, so no content lacks a twin. RPThe twin was derived at build time and embedded in the page, so it travels with the document. MDThe control writes one attribute and the browser swaps the rendered layer for the embedded twin. Nothing is fetched. TKThe tokens restyle what the twin does not replace — headings, controls, spacing — so the swapped page still looks intentional. The switch happens in the reader’s browser; the build-side value is the twin, which the post journey carries.
A publish becomes a deployment A content change earns a deployment, decided by a service rather than a person. A publish alone triggers nothing.
SAThe publish moves the content stamp, which changes whenever any document does. Nothing else reacts yet. DWThe deploy window sees the stamp move and decides whether to ship now or hold for later edits. INThe build steps assemble the site against the new corpus: corpus fetched, reference generated, output stamped. LVThe live checks ask the deployed site itself and compare its answers against what the build recorded. This journey needs a deployment to answer, and a build has none to ask.
Someone sends feedback A message reaches Colin through whichever channel it was sent, ending at one capability. Two stops, the shortest route on the map.
TPThe form renders outside the swapped region, so it works in every mode. APThe endpoint takes the submission and stores it — the same capability every channel reaches. A submission ends at a network store, which an offline build cannot reach.
On the wire · SA → LO
The stored document, as the source holds it
{
"_id": "post.fixtures-before-the-module",
"_type": "post",
"title": "Fixtures before the module",
"publishDate": "2026-07-25",
"blocks": 1
} On the wire · LO → GR
What the one door admitted
{
"documents": 83,
"source": "sanity"
} On the wire · GR → BL
The node the graph resolved
{
"kind": "post",
"path": "/writing/fixtures-before-the-module",
"canonical": "https://lateano.com/writing/fixtures-before-the-module",
"title": "Fixtures before the module"
} On the wire · BL → TP
The body’s blocks, resolved against the closed table
{
"blocks": [
"block"
]
} On the wire · TP → RP
The derived Markdown twin, opening lines
---
title: "Fixtures before the module"
description: "Test data written after the code gets shaped by the code, and then it stops being evidence."
url: "https://lateano.com/writing/fixtures-before-the-module"
author: "Colin Lateano"
published: "2026-07-25"
tags:
- title: "How I work"
url: "https://lateano.com/tags/how-i-work"
---
# Fixtures before the module On the wire · QL → RA
The typed query "tool", parsed
{
"match": {
"text": "tool",
"in": "all"
}
} On the wire · RA → GR
What the read API may range over, which the graph decides
[
{
"collection": "writing",
"title": "Writing",
"path": "/writing",
"count": 27
},
{
"collection": "glossary",
"title": "Glossary",
"path": "/glossary",
"count": 4
},
{
"collection": "demos",
"title": "Demos",
"path": "/demos",
"count": 2
}
] On the wire · GR → RA
The answer coming back, first row of the ranking
{
"rows": 2,
"first": {
"path": "/demos/tool-surface",
"canonical": "https://lateano.com/demos/tool-surface",
"title": "Tool surface",
"description": "The tools the site’s MCP server publishes, and the arguments each one accepts.",
"collection": "demos",
"date": null,
"twin": "https://lateano.com/demos/tool-surface.md"
}
} On the wire · RA → TP
What the page renders for the top answer
{
"path": "/demos/tool-surface",
"canonical": "https://lateano.com/demos/tool-surface",
"title": "Tool surface",
"description": "The tools the site’s MCP server publishes, and the arguments each one accepts.",
"indexing": "index, follow, max-image-preview:large",
"collection": "demos",
"date": null,
"tags": [
"How I work"
],
"aliases": [],
"twin": "https://lateano.com/demos/tool-surface.md",
"outgoingReferences": [
{
"path": "/writing/the-tool-surface-behind-this-site",
"title": "The tool surface behind this site"
}
],
"referencingPosts": [],
"related": [
{
"path": "/demos/atlas",
"title": "The Atlas"
},
{
"path": "/glossary/deep-module",
"title": "Deep module"
},
{
"path": "/glossary/seam",
"title": "Seam"
},
{
"path": "/writing/a-glossary-that-links-itself",
"title": "A glossary that links itself"
},
{
"path": "/writing/fixtures-before-the-module",
"title": "Fixtures before the module"
},
{
"path": "/writing/the-interface-is-the-decision",
"title": "The interface is the decision"
},
{
"path": "/writing/what-i-got-wrong-about-validation",
"title": "What I got wrong about validation"
}
]
} On the wire · DE → AP
The declared tool an agent calls
{
"name": "search_content",
"title": "Search content",
"description": "Search posts, glossary terms and demos at once, best match first. Give it words to match, a collection, or a tag: a call that gives none of the three describes no question and is refused. Three shapes cover most questions — {\"match\":{\"text\":\"caching\",\"in\":\"all\"}} looks for a word in every field; {\"tag\":\"<a slug list_tags reported>\"} returns what carries one tag, and list_tags is where a real slug comes from; {\"collection\":\"glossary\",\"match\":{\"text\":\"cache\",\"in\":\"title\"}} narrows to one collection and one field. For one known slug, get_post answers directly; for the metadata of one known route, get_page_info does, and get_page_markdown reads what is written at one. Every answer reports matched beside its rows — how many documents the search found, whether or not they all fit. Where matched is larger than the rows returned, narrow the question with a collection, a tag, or a different field: twenty-five is the most any one search returns, and there is no page after it.",
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
},
"arguments": [
{
"name": "match",
"description": "What to look for. Omit it and a collection or a tag must narrow the search instead.",
"required": false,
"type": "{ text: string, in: title | body | description | aliases | tags | all }"
},
{
"name": "collection",
"description": "Narrow to one collection.",
"required": false,
"type": "writing | glossary | demos"
},
{
"name": "tag",
"description": "Narrow to one tag, by the slug list_tags reports.",
"required": false,
"type": "string"
},
{
"name": "limit",
"description": "How many results to return, from 1 to 25. Omitted, the search returns 10. A number outside that range is refused rather than reduced to fit.",
"required": false,
"type": "integer"
}
]
} On the wire · AP → RA
The read-API call the machine door makes
{
"match": {
"text": "tool",
"in": "all"
}
} On the wire · RA → GR
What the read API may range over, which the graph decides
[
{
"collection": "writing",
"title": "Writing",
"path": "/writing",
"count": 27
},
{
"collection": "glossary",
"title": "Glossary",
"path": "/glossary",
"count": 4
},
{
"collection": "demos",
"title": "Demos",
"path": "/demos",
"count": 2
}
] On the wire · GR → RA
The answer coming back, exactly as a reader would receive it
{
"rows": 2,
"first": {
"path": "/demos/tool-surface",
"canonical": "https://lateano.com/demos/tool-surface",
"title": "Tool surface",
"description": "The tools the site’s MCP server publishes, and the arguments each one accepts.",
"collection": "demos",
"date": null,
"twin": "https://lateano.com/demos/tool-surface.md"
}
} On the wire · RA → AP
The whole answer the read API returns, before the door shapes it
[
{
"results": [
{
"path": "/demos/tool-surface",
"canonical": "https://lateano.com/demos/tool-surface",
"title": "Tool surface",
"description": "The tools the site’s MCP server publishes, and the arguments each one accepts.",
"collection": "demos",
"date": null,
"twin": "https://lateano.com/demos/tool-surface.md"
},
{
"path": "/writing/the-tool-surface-behind-this-site",
"canonical": "https://lateano.com/writing/the-tool-surface-behind-this-site",
"title": "The tool surface behind this site",
"description": "Four read-only tools an agent can call while a reader has the page open, and why there are only four.",
"collection": "writing",
"date": "2026-07-22",
"twin": "https://lateano.com/writing/the-tool-surface-behind-this-site.md"
}
],
"matched": 2,
"facets": []
}
] On the wire · AP → DE
What the agent is finally handed
The MCP server composes this over a gateway that answers HTTP requests, and a build has none to call.