repo-map.md was 173 KB (~43k tokens), too long to be part of every new
agent's context. The full gotcha text moves verbatim to context/gotchas.md
and the per-file inventory (with the old Wasm/CLI/Testing sections) to
context/file-inventory.md; the map keeps a one-or-two-line digest per
gotcha under the same numbers, so every `gotcha N` citation still resolves.
The doc-site manifest publishes the two new pages, and `gotchas` gets the
same gotcha-N anchors as `repo-map`.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Opus review of 46c8a967: a new-version file had nowhere to declare
its stored measures. The ## Category: _Measure section stays and lists
them with displays and ids; its header declares nothing. Clause 6 says
input text means files and command-language text, distinct from
interactive typing. The sort note says plain string order, not numeric.
The user approved the amendment on PR #326 (2026-10-09), so its Status,
the accepted ADR's pointer, core.md section 20 and the doc-site title
no longer say proposed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
On the user's instruction an ADR states only what its decision changes,
and mentions an existing convention only where it conflicts. The accepted
ADR 007 is restored to main's text plus a one-line pointer, so merging
this PR keeps it; the amendment is context/adr/007-stable-identities-
amendment-1.md (280 lines, from 784), with a table of the accepted rules
it replaces. The doc site lists the new file, and core.md section 20
points at it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
A two-level numeric tower: exact decimals (rust_decimal) by default, inexact f64 by Scheme's contagion rule. Measured against f64, bigdecimal and num-rational, and against ADR 009's exact f64 accumulator. Registered on the doc site.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
An in-house DBSP core as a problem-agnostic crate, plugged in behind a
trait in improvise-core, with the eager evaluator kept as the oracle.
Adds examples/retraction_drift.rs, which measures f64 retraction drift
against the eager fold and the exact decimal sum.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
The build job failed on the PR head: build-doc-site.mjs refuses a context/
doc that is neither in the manifest nor a named omission. core.md §18,
landed with km71, still called this ADR 005.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
The `build` job failed, correctly: `scripts/build-doc-site.mjs` requires every
`context/**/*.md` to be either in the SECTIONS manifest or a named omission
(improvise-res9's cure), and the two files this branch added were neither.
Both are now published, beside the TUI in each section.
While there: `context/code/doc-site.md` asserted 'the seven
`context/code/*.md` documents' when there were already twelve, and '501
entries' when the checker reports 801. Both were stale before this branch and
this branch makes them staler, so they are de-numbered rather than re-asserted
— a count in prose that nothing checks is a fact with no owner, and the shape
that stops drifting is not having one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Three findings, all correct, all applied.
1. A contextual selection face cannot be resolved by `resolve(Face)`. Clause 3
fixed that signature while the vocabulary described one `selection` face
"parameterised by the surface's accent" — the same Face cannot yield the
grid's Cyan and the formula panel's Yellow. Resolved the way clause 2
already works: `selection` is a BASE face and each surface has a derived
one. A context argument was rejected explicitly, because it is a second
input three front ends would each have to supply correctly, which is the
three-derivations hazard one level down.
2. `Indexed` could not be "a detail". The ADR defined Colour as exactly
Default | Named | Rgb, promised the two existing Indexed greys would still
render exactly, and said the shipped theme used only the first two. Folding
an index into Rgb changes its meaning AND breaks that rule. `Indexed(u8)` is
now a fourth arm, the browser resolves 16-255 through the xterm cube and
ramp, and the shipped-theme rule is restated over the line that actually
matters: palette-relative versus absolute. The shipped theme contains no
`Rgb`. The rejected-alternatives entry now also says that dropping `Rgb`
entirely is a coherent version of this ADR, since that would make the
regression unrepresentable rather than defaulted-against.
3. "Build all bundles" was failing: the ADR was neither published nor a named
omission. Added to SECTIONS in scripts/build-doc-site.mjs. The checked-in
site/docs output is deliberately NOT regenerated here — assemble.sh builds
into its own output directory, and site/ is a periodic snapshot rather than
a per-commit artifact.
Also corrects the hex count: nine stray values in style.css, six light and
three dark, not six.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
The `build` job fails on main and on every branch merged from it:
doc-site: context/code/pr-review.md is neither published nor a named omission
NOT a merge loss -- scripts/build-doc-site.mjs in this branch is
byte-identical to main's, and neither lists the file. PR #96 added
context/code/pr-review.md without a manifest line, which is exactly the
gate improvise-res9 added: a doc added without a line publishes nothing
and says nothing, so the generator refuses.
Omitted rather than published, and the line is cloud-agents.md's rather
than tooling.md's. tooling.md explains checkers a reader of the code will
meet; pr-review.md is about THIS REPOSITORY'S PROCESS -- where a Codex
review appears, how to tell it from our own agents' comments, why absence
is not approval. Nobody visiting a website is about to read a PR thread.
Both homes updated, because an omission is restated beside the manifest
AND in doc-site.md section 2, and the prose there counted them ("Neither
is referenced...") so it went stale at three.
This strays into scripts/, which is Wave N's ground. Flagged in the
hand-back; it is a one-line data entry either way if the orchestrator
would rather publish it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Both blockers from the adversarial review of #83, both in the mode this PR
rewrote, both producing a pass while the gate did not do what its prose said.
1. cargo_test_names reduced every listed path to its last segment. Six leaf
names collide in this workspace and two are cited in evidence, so a heading was
satisfiable by a test in another module answering another question -- renaming
ui::project::tests::a_load_that_fails_is_not_a_rebase, the one its entry
describes, left the checker at exit 0 with the ui::host namesake answering for
it. Paths are kept whole; a bare leaf resolves only when unique, and an
ambiguous one is REFUSED with both candidates named. _RUST_NAME accepts `::`, so
a qualified heading is a claim rather than prose, and a citation is matched on
the leaf so qualifying a heading cannot orphan its own test. The two colliding
entries are qualified. Neither half suffices alone: rename one of a colliding
pair and the leaf becomes unique again, which is why qualification is the fix
and the refusal is what forces it.
2. --rust-tests called cargo_test_names with one argument, so the --ignored
subtraction never ran, and it printed a byte-identical success line. It now
requires --rust-tests-ignored (one alone exits 2) and the line names its source.
Also, from the same review: a missing context/test-evidence/ exited 0 while 170
citations pointed at nothing (early return, now gone); the doc-site "runs last"
check accepted a trailing `echo` that merely NAMED the generator, so it asks for
an invocation; bundle_freedom gains an arm for a transitive `import "../app.js"`,
which reaches the bundle while naming nothing forbidden; and two prose overclaims
are corrected -- the prose-heading count is a convenience rather than a guard
(git diff is the mechanism), and the ratio rule says "above the run MEDIAN",
which is defensible where "above the others" is false against second place.
scripts/check-ci-graph.py makes claim 6's rule a gate: pages-deploy must `needs`
every other job in ci.yml. The rule was stated in the last commit and a
zz-new-check: job added during review was still ungated with nothing noticing.
README now states the rule and lists no jobs, the second copy being what went
stale twice.
Every fault re-injected and observed; figures in context/test-evidence/tooling.md
rather than only here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
The manifest only ever grows, so a doc added without a line in it published
nothing and said nothing: build green, link check green, doc simply absent.
The generator now walks context/**/*.md plus docs/design-notes.md, subtracts the
manifest, and fails unless the remainder is exactly the OMITTED map -- checked
in BOTH directions, because an entry naming a file nobody can find asserts a
reason for nothing.
Filed as latent; it was live. context/code/kb-tool.md had been unpublished,
unargued and unnoticed since it was added, while doc-site.md's own table says
context/code/*.md is published. It is published now, with context/code/tooling.md
and context/test-evidence/tooling.md: 23 pages to 26.
Second gate, same file: the generator reads web/assemble.sh and refuses unless
its own invocation is that script's last command. Its outward-link check resolves
../index.html and ../app/ against the filesystem, so a line appended below it
would make that check pass against a half-built tree.
Both run at module scope, before any page is generated, so a manifest error is
answered before the 48 outward-link failures a bare run reports -- a missing doc
is actionable wherever you ran the generator, and the two are unrelated
diagnoses.
Three faults injected and observed; evidence in context/test-evidence/tooling.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
`::before` content participates in accessible-name computation, so the hamburger
glyph was part of the toggle's name and a screen reader would read it out. The
CSS alt-text form `content: "\2630 " / ""` suppresses the glyph from the name
while still painting it.
Taken from the reviewer's optional polish, and verified here rather than
accepted: at 390 the checkbox's accessible name is exactly "Pages" and its
`checked` state flips false -> true on Space, so the collapsed/expanded state is
announced rather than merely the control's existence. The glyph still renders --
computed `::before` content is present and the label lays out at 358px. At 1280
there is still no checkbox in the tree at all.
This is the same principle the previous commit wrote down, one step further:
focusability is necessary and not sufficient, because a control a screen reader
can reach but cannot name, or whose state it cannot read, is still broken. The
instrument that observes that is the accessible name and the checked flag, not
the tab order that was enough for the last fix.
Firefox ignores the alt-text form and falls back to reading the content, which
is exactly today's behaviour, so the change is an improvement in Chromium and
WebKit and a no-op elsewhere.
Gates: 1378 rust, 26 node --test, 16 playwright (3 skipped, webkit OPFS), all
three check-comment-runs modes, 3462 site links / 0 broken, 0 nested anchors,
0 of 23 pages overflow at 320/390/1280, nav operability unchanged at 390/900/
901/1280.
improvise-djoo
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A regression the previous commit introduced, found in review. The checkbox
driving the collapsed nav carried `hidden`, which is `display:none`, which
removes it from the tab order AND from the accessibility tree. A <label> is
not focusable on its own -- the checkbox it labels is what carries focus -- so
at <=900px the navigation could be clicked and reached no other way, and the
accessibility snapshot contained neither the checkbox nor any of the 25 links.
The toggle is now visually hidden but focusable at narrow widths, and
display:none at wide ones. Keeping display:none on the wide side matters:
merely un-hiding it puts focus on an invisible control whose label is
display:none, which is its own wart.
Measured at 390, 900, 901 and 1280, because the regression lives on one side
of a media query. At 390 and 900 the tab order is brand -> navtoggle, the
toggle is focusable, the a11y tree carries a checkbox node, and Space takes
visible nav links 1 -> 26. At 901 and 1280 the toggle is display:none, not
focusable, absent from the a11y tree, and tab runs straight through the 26
links -- desktop identical to before. 0 of 23 pages scroll horizontally at
320, 390 and 1280, so the absolutely-positioned 1px input introduces nothing.
WHAT IS WORTH MORE THAN THE FIX, and why doc-site.md gains a paragraph rather
than the code gaining a comment: my probe for the previous commit counted
VISIBLE nav links. That answers "does operating the toggle reveal the links" --
correct and complete for a mouse -- and is silent on whether any non-mouse
route exists. It could not have seen this, however sharp the visibility
instrument was. Last round's lesson was that a measurement which cannot
distinguish the two states is not a measurement; this round adds the half it
was missing: a measurement of the wrong PROPERTY is not a measurement either.
checkVisibility fixed the visibility instrument; it does not turn a visibility
count into an operability check. The instrument here is tab order, .focus()
and the accessibility snapshot.
Gates: 1378 rust, 26 node --test, 16 playwright (3 skipped, webkit OPFS), all
three check-comment-runs modes, 3462 site links / 0 broken, 0 nested anchors,
0 of 23 pages overflow at 320/390/1280.
improvise-djoo
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 2 of review. Three notes and the mobile-nav call.
THE NAV, and the correction that prompted it. I had called the 25-link
sidebar wall pre-existing and declined to fix it. That was wrong: the entire
CSS is new in this PR, so the wall ships for the first time here and it is
mine. Every phone page opened on ~1500px of nav before any content.
The mechanism is a checkbox and not <details>, and the reason is measured
rather than assumed. <details> was the obvious control, and it collapses
correctly on a phone -- but the sidebar layout has to force it OPEN, and a
closed <details> hides its content through ::details-content, which `display`
cannot override. Chromium showed the desktop sidebar collapsed to one link
with its toggle hidden: unusable. So the hiding is ours, in a media query,
where overriding it is just cascade.
Measured at both widths: 390px shows 1 link and the toggle, with the h1 at
189px instead of ~1500; 1280px shows all 26 links with no toggle and the h1
at 40px, unchanged; and the phone toggle really expands 1 -> 26 on click.
Note the first probe was wrong -- getBoundingClientRect reports a height for
content-visibility-hidden children, so it read 26 visible in both states and
would have passed the broken version. checkVisibility() is what separates them.
The same argument applies to the table of contents, which is equally new
here: repo-map opened on 1397px of TOC on a phone. Capped at 45vh so it
scrolls; 1397 -> 924, desktop identical at 652.
THE HEADER COMMENT. The generator stopped running standalone when the link
check began resolving ../index.html and ../app/ on disk, and its header still
advertised a default OUT_DIR that now exits 1. That is gotcha 48's third
paragraph exactly -- a fact stated in two places and updated in one, with the
copy that was updated being doc-site.md and the copy someone about to run the
script actually reads being the header. It now says so, keeps the strictness
(the hole it replaced was counting those links as checked while skipping
them), and records the other thing the reorder changed: the DIRECTION of a
failure, from an obviously-broken tree with no app/ to a complete-looking one
with a partial docs/.
And one documented subset: a \| inside a code span in a table cell keeps its
backslash where GFM would unescape it. Splitting is suppressed inside code
spans, so the escape is never needed there and never consumed. No corpus
instance; recorded as a decision rather than left to be rediscovered.
Gates: 1378 rust, 26 node --test, 16 playwright (3 skipped, webkit OPFS), all
three check-comment-runs modes, 3460 site links / 0 broken, 0 nested anchors,
0 of 23 pages overflow at 390 and 1280.
improvise-djoo
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review of PR #62 found four live defects and four smaller ones. The most
valuable is the first, because of what it says about the rest.
A TOC entry wrapping a heading whose rendered text already contained a link
emitted <a href="#anchor"><a href="github...">...</a></a>. The parser closes
the outer anchor early, so the entry became an empty unclickable stub and
clicking "src/draw.rs" in the TOC navigated to GitHub. 19 occurrences over 10
pages. Both hrefs resolved perfectly, so the link check, the whole-site link
check and a screenshot pass all missed it -- it answers "does every href
resolve" and this is not that question. So the fix is two things: the TOC keeps
a tag-free variant of each heading, AND a new check asserts no <a> appears
inside another <a>. Proved by reverting the first half: 66 nested anchors
reported, assembly exits 1. Clicking that TOC entry now lands on #src-draw-rs.
esc() escaped & < > but not the quote it was interpolated into title="..."
with. Five shipped bead titles contain one, and Chromium parsed the resulting
tag as having three junk attributes. Now escapes " and ' as well. Today's
corpus being friendly is not a reason to interpolate tracker text unescaped.
Seven pages overflowed at 390px by up to 92%. The reviewer attributed it to the
TOC; the TOC was real but not sufficient, and the first fix measured clean by
getBoundingClientRect while the page still overflowed, because the heading text
overflows its box without widening it. Only per-element scrollWidth names the
culprit: hundreds of snake_case test names as headings. overflow-wrap on the
headings and on .toc a, plus dropping display:table from .tickets, which had
been cancelling the mobile overflow-x guard. 0 of 23 pages overflow at 390 and
1280 now, measured.
table() split rows on every pipe before inline parsing, so `|Time Period|` --
this repo's own quoting syntax, in a corpus whose subject is that syntax -- was
shredded into extra columns. A row that still parses, so no column count could
see it. Now walked, respecting code spans and \|. Proved with a fixture: 9 tds
with the fix, 12 with the old splitter reinstated.
The four smaller ones: the link check counted 48 links it never verified behind
a comment promising they were "checked below" when there was no below -- those
are the links leaving the docs tree, and they are now resolved against the
filesystem, which is why assemble.sh runs the generator LAST, once its siblings
exist. The gotcha jump bar's hardcoded 48 is derived from the emitted anchors,
because it failed closed on deletion and silently on addition, and the list only
grows. context/code/doc-site.md cited improvise-xxxx and so broke the audit the
surrounding paragraph describes; the repo-wide audit is now 189 ids, all
resolving. And the Conventions paragraph's own example path now goes through
inline(), so the sentence demonstrates the linking instead of asserting it.
The ticket index keeps all 180 rows, open bugs and titles included, by the
user's ruling: the data is already public in .beads/issues.jsonl, the page only
makes it legible, and filtering it while the lede links the raw JSONL would be
theatre.
Gates: 1378 rust tests, 26 node --test, 16 playwright (3 skipped, webkit OPFS),
all three check-comment-runs modes, 3460 site links / 0 broken, 0 nested
anchors, 189 bead ids all resolving.
improvise-djoo
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two ways the path linker could emit a URL that resolves by luck or not at all,
both found by reading the generated links back rather than by the link check,
which only sees the site's own pages.
A code span naming a directory -- `crates/gp-tree/`, `context/test-evidence`,
and about a dozen more -- was linked under /blob/. GitHub redirects that to
/tree/ today, so it worked, but the correctness of ~30 links rested on a
redirect nobody here controls. The kind is now read off the filesystem at
generation time, which is the same source the existence check already uses,
and the trailing slash is dropped.
And a code span of punctuation alone passed the path guard: `.` is matched by
both the character-class test and the "contains a slash or a dot" test, exists
on disk, and is a directory, so it became a link to the repository root from
the middle of a sentence. A path now has to contain at least one alphanumeric.
Verified with a 40-hex token: 195 source links, zero of them `blob/main`, no
blob link ending in a slash, no tree link naming a file, and no link to `.`.
improvise-djoo
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The landing page is the site root and the app is /app/; the prose now sits
at /docs/, assembled by the same web/assemble.sh the Pages job and the
browser harness both run, so what ships and what is tested are one tree.
Published: design-notes, design-principles, repo-map, the three ADRs, the
seven context/code/ arguments, and the 501 test-evidence entries as their
own clearly-labelled section. Not published: context/plan.md, which is an
agent prompt about a launch rather than a description of the program, and
context/cloud-agents.md, which is addressed to nobody who visits a website.
Neither is referenced by a published document outside a code fence, so
leaving them out dangles nothing. The reasoning is context/code/doc-site.md,
which is itself published.
Two things the corpus needed that a generator would not have given it. The
~190 improvise-xxxx citations resolve to a generated ticket index built from
the already-public .beads/issues.jsonl rather than to an invented URL that
would 404; an id the export does not yet carry stays plain text. And a code
span that is wholly a repo path links to GitHub at the assembled ref, but
only when the file exists on disk, so a stale citation degrades to text.
The generator link-checks its own output and exits non-zero on a break, and
assemble.sh runs under set -eu, so a dead cross-reference fails the assembly
rather than shipping. It earned that on its first two runs: it caught a
fence-close regex comparing a three-character marker to a one-character
string, which swallowed every document from its first code block onward, and
a list parser that ended the gotcha list at the first blank line, emitting 1
of the 48 anchors the jump strip links to. Proved by injection afterwards --
renaming the anchors reports 48 missing anchors, pointing the ticket links at
an ungenerated page reports 763 missing pages.
No framework, no node_modules for the site, no build step beyond one
zero-dependency node script, matching the landing page's posture.
improvise-djoo
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>