The hero explained the architecture before saying why anyone would care:
'task files in stage directories', 'stdlib-only', 'narrates' are words
that mean something once you already use bench. Every line now maps to
something the audience has actually hit — agents outrunning the person
supposed to be tracking them.
- Hero: 'Run a team of coding agents. Stay in control.' The old headline
keeps its job as the brand line, in the footer of every layout.
- A new section between hero and doors: three sentences a reader has
said out loud, each answered by a mechanism rather than a promise.
Deliberately unboxed — the doors below are the page's one grid of
cards, and repeating that shape would make the eye work twice.
- The six doors lead with the outcome and keep one mechanism as the
evidence for it, which is the inversion of what they did before.
- Title and description follow, since a pasted url is the same pitch in
another form.
Two lines that were quietly false are fixed too: the strip's comment
still said this site counts nothing (Fathom counts pageviews now), and
'no per-seat pricing' would have tripped the fiction guard in
tests/test_site_landing.py, which forbids the word outright.
Two changes to what the live site sends.
Analytics: Fathom's tag goes in every template's <head>, deferred. It is
cookieless and collects nothing about a person, so no consent banner —
but it is a third party, so the CSP names cdn.usefathom.com for script
and connect rather than opening the door generally, and the tests that
said 'no script at all' now say 'no script this site depends on, and no
origin nobody chose'.
Caching: the host concatenates a header two matching rules both set
rather than overriding, so /* and /static/* each setting Cache-Control
sent 'max-age=0, must-revalidate, max-age=31536000, immutable' on the
stylesheet — first max-age wins, and the year-long cache never happened.
Measured on the live site, which is where the from-memory assumption in
task 32 said to check it. Now /static/* is the only rule that sets it
and HTML takes the host's revalidating default; the post-deploy checks
in site/README.md verify both ends.
article.html: 34 added the lede under the h1, 35 added the folded
contents strip in the same place. Both belong, and site.css settles the
order — its lede adjacency names .menu-contents + p, so the strip sits
between the title and the lede.
Two things the merge exposed rather than caused:
- $toc and $sidebar were spelled inside article.html's own comments,
and string.Template substitutes there too, so every page carried a
second copy of both rails inside a comment (28 toc links for 7
headings). Doubled to $$toc / $$sidebar, as the template contract
already said to.
- test_site_pages assumed one rendering of each rail. Both are now
written twice by design, so the tests assert every rendering agrees
with the page instead of assuming there is one.
The card file keeps main's location: the board owns a card's stage, and
main had already moved 35 to review/.
The design is drawn at a fixed 1180px with three-column articles and a
two-column hero, and the site's first traffic is a link pasted into a
chat and opened on a handset. This is what happens below that width —
three max-width steps, and nothing that takes effect at or above the
width the design defines.
The two columns an article loses come back as <details> strips: "On
this page" folds under the title at 1080px, where the gutter goes, and
the section nav becomes a menu under the masthead at 760px, where the
sidebar goes. Both are filled from the same $sidebar and $toc as the
columns they replace, so a folded copy cannot say something the column
does not — and one menu that opens and closes needs no script, which
keeps the site's promise of shipping none.
The rest is what the acceptance asks for: a table becomes its own
scroller with an edge shadow that appears only while there is more to
the right; a token that cannot break, breaks; fenced code keeps
scrolling inside its own block; the hero and title steps come down
without leaving Zilla Slab; everything a finger has to hit is --tap
(44px) tall; and the masthead, footer and hero actions wrap instead of
widening the page.
The one thing that would have changed desktop is guarded rather than
noticed later: the contents strip is a sibling between the h1 and the
body's first paragraph even when display:none, so the lede rule names
it too.
tests/test_site_responsive.py mechanises all four promises, including
the one that matters most — every media query is a max-width below the
design.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fills the middle of the site. Nine routes, every body a heading slice of
AGENTS.md or README.md, and the article layout given the furniture the
design calls for.
The manifest gains the two concepts nothing covered: /concepts/task-files/
(the header format, from AGENTS.md's own section) and /concepts/adapters/
(the adapter summary, which is the other half of the three-layer law).
/concepts/stages/ now runs through "Moving a task", because the five
directories and moving between them are one idea.
The layout:
- A lede under the title — the one sentence an article authors, taken
from the manifest's `description` or an explicit `lede` where the two
want different words. A slice starts mid-document; a reader arriving
from the nav is owed a line saying what they are looking at.
- Prev/next at the foot, walking the sidebar's own order so the arrows
and the rail cannot disagree. Pages with no section (the landing page,
the 404) are not on the flow.
- "Edit this page on GitHub" anchors to the section the page was cut
from, built from the same `from` heading the slice starts at.
Two bugs the new pages found:
- string.Template substitutes inside HTML comments, so a comment naming
the body placeholder emitted the whole body twice and closed itself
early on the first `-->` in it.
- Promotion could produce a second <h1>. A slice that deliberately runs
past its own section carries headings at the `from` level, and those
promoted to h1 on a page that already had one. Promotion now stops at
h2, where they read as peers — which is what putting them on one page
said in the first place.
tests/test_site_pages.py covers the furniture on the real built site:
the routes, the layout, the sidebar marking one page, the contents list
being exactly the body's own h2s in order, the prev/next chain end to
end, the edit link's anchor, the six landing-page doors, and a table, a
fenced block and a nested list surviving the renderer. The scratch-repo
helper now copies every file a slice links to, since the builder checks
those exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things the build owed the host before the site could have an
address, all of them visible in site/dist/ rather than in a deploy
script.
A 404 page. Cloudflare's not-found handling serves a literal 404.html
from the root of the assets directory, so a route may now name an .html
file instead of ending in "/" — the one that does is /404.html, a normal
manifest entry with its own layout. It is the site's design and the
site's nav, with a way back to the landing page, a noindex and no
canonical. A null section keeps it off the nav it renders.
site/root/, copied verbatim to the TOP of the build the way static/ is
copied to a subdirectory of it. _headers is what it holds; the host
reads that file from the root and nowhere else. A file in root/ that a
route would also write stops the build rather than letting one silently
win.
Stamped urls for the stylesheet and the icon: the templates link them
through $stylesheet and $icon, which carry a ?v=<hash> of the file's own
contents. Nothing in static/ is renamed, so the tree and the /static/*
glob stay as they were — but the url changes when the file does, which
is what will make a year-long immutable cache safe.
Tests ask the builder where a route lands rather than keeping their own
copy of the rule.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bench has no public page. site/ is the machinery for one: a manifest,
a generator, two layouts from the turn-2 docs design, and the Daylight
half of the board's own token set.
Nothing here is transcribed. Every page body is a heading slice of a
file that already documents bench, and site/pages.json is the only
place that mapping is written down — the IA is editorial judgement, so
it lives in one readable file rather than in a directory convention.
The point is the failure: a from/to heading the source no longer
contains stops the build naming the route, the file and the heading,
and every page renders before the output directory is touched, so a
drifting rebuild leaves the last good build standing instead of
replacing it with an empty one. Repo-relative links come out as site
routes or GitHub links; a dead one stops the build too.
Rendering is markdown-it-py, pinned in site/requirements.txt. The
stdlib-only law binds manager/core/ — the tool people install — and
site/ is neither shipped nor installed, so a hand-rolled parser would
have been risk spent in the wrong place on a document that leans on
fenced code, nested lists and inline code throughout.
Templates are string.Template: $name substitutes and a literal dollar
doubles, because str.format's braces collide with every line of CSS.
Fonts are self-hosted and same-origin: no page bench serves may need a
request to a third party to be read. fetch-fonts.py downloads the seven
woff2 files once; until they are there the build says which are missing
and the pages fall back to the system stack.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>