Skip to content

Commit b16b2e2

Browse files
DocAtPromptclaude
andcommitted
docs: make the orientation true again, for the next instance to inherit
The same pass this repository ran on 2026-07-25, and needed again: the last six commits moved things the documentation had not caught up with. Everything below was checked against a command's output, not against memory. **The specification did not know about `words:`** — nought occurrences. § 7.12 described a calendar whose own vocabulary was English and unchangeable, which stopped being true two commits ago. It now has a *Sprache des Blattes* section covering `words:`, `font:` on a document generator, and the Latin-1 boundary that makes the second necessary. This project's rule is to update the spec in the same breath as the code; that did not happen, and this is the repair. **§ 15's question 2 changed shape and now says so.** "Whether a font must be shipped" was theoretical while a missing glyph in a generator's text went unnoticed and a document could name no font. Both are fixed, so stage 2 is reachable everywhere and the question is now whether it is *reasonable to ask* — a decision for use, but one the user is finally shown rather than handed a box. **Decision 53** records the three changes as the one decision they were, in the order that is the actual lesson: refusing the silent failure before supplying the remedy left a window in which a Polish calendar was less usable than before. **Three stale numbers in `docs/CLAUDE.md`**, each verified against the tree: the specification is ~2800 lines and not ~2330; it covers thirteen generators and not ten; there are fifty-three implementation decisions and not fifty-one. Also checked and correct: 1231 tests, 19 presets (twelve generators, seven `lines` papers — the eighth `generator: lines` is a *section* inside `notebook-a4`), 18 examples, 45 modules, two tags, `0.11.1`. The Done table gains the row for everything after the audit, the module table names all three functions both page paths now share, and the release paragraph says two releases rather than one first tag. The habit about probes is rewritten with its real count: five times over these sessions I concluded something was unchecked, and five times the probe was wrong and the code was right — including twice while writing *this* commit's checks. 1231 tests green, ruff clean, every committed example byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 51b75c3 commit b16b2e2

3 files changed

Lines changed: 118 additions & 14 deletions

File tree

docs/CLAUDE.md

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ edge, so a tick now carries its position *and* its value separately. `unit` says
126126
| post-M9 `notebook` | the **second document generator**, and the first that composes blades (§ 7.13, decision 50): sections, each filled by an ordinary generator, with a linked contents page, opt-in dividers and a cover. The seam: a `DocumentPage` may carry a `Fill` (generator name + validated config) instead of marks, and **the handle calls the blade**`pages.document_page_marks` is the one function that says what is on a page, used by the writer, the capability pre-flight and the media check. A section is a definition in miniature, validated by that blade's own `config_model` with the loader's context (so `px`/`%w` resolve there too). Document bands are now laid out **per page**, so `{page}` counts and a per-page `{section}` names the section; `calendar-a4` stayed byte-identical. `page_layout.Page` extracted from `calendar_layout` and shared. Preset `notebook-a4`, example `15-notebook` |
127127
| post-M9 band colour | a header/footer `Band` takes `background` (a full-width strip, band height only) and `text_color`, both default off — drawn in `layout_band` so both the blade and document paths get them; resolves the title-page contrast (§ 8.9) |
128128
| pulled forward into M1 | format table, presets, `check`, overwrite protection, placeholders — the M1 acceptance criteria needed them |
129+
| 0.11.x — after the audit | **`tests/test_geometry_readback.py`**: every blade's geometry measured out of a finished PDF against numbers taken from the definition, never from the code — a hexagon's every edge exactly `size`, ring radii against the cumulative series, a mandala mapped onto itself by a twelfth of a turn, a perspective fan concurrent within a millimetre, `form`'s 8 mm ruling identical on A4 and A5, log positions against `math.log10`. **`check_page_furniture`**: the frame is checked on both page paths, not only drawn on both. **`check_text_glyphs`**: a generator's text is checked for missing glyphs (§ 12 point 13), which no code had ever done. **`words:` and `font:`** on the document generators, so every word on the sheet comes from the definition and there is a way out of Latin-1 for it (decision 53). **`labels: none`** accepted on `grid` and `polar`, the spelling § 7.10 documents |
129130
| 0.10.0 release-readiness | the audit above, in five commits: three degenerate values that hung or crashed instead of refusing; **`page_furniture`**, so the document path carries the page model (duplex, border, hole marks, ruler, stamp, background) instead of dropping it silently, plus three keys refused by name and a fourth (a multi-sheet blade in a notebook section) refused for now (decision 52); five messages made actionable and `--version` added; a ruler tick that ran off the edge, a ruler font that was never opened, an unreachable bookmark guard; and twenty-six stale comments |
130131

131132
### Not done
@@ -146,8 +147,9 @@ edge, so a tick now carries its position *and* its value separately. `unit` says
146147

147148
**Two more things wait, and neither of them is code:**
148149

149-
- ~~**PyPI.**~~ **Published on 2026-07-26 — `ctrlgrid 0.11.0`, MIT, Python ≥ 3.11,
150-
wheel and sdist, under the repository's first tag `v0.11.0`.** M1's first
150+
- ~~**PyPI.**~~ **Published on 2026-07-26 — `ctrlgrid`, MIT, Python ≥ 3.11, wheel
151+
and sdist. Two releases so far: `v0.11.0`, the repository's first tag ever, and
152+
`v0.11.1` for the metadata it should have carried.** M1's first
151153
acceptance criterion is met at last, and measured rather than assumed: `uvx
152154
ctrlgrid millimeter-a4 --pages 3 -o out.pdf` installs from the network and
153155
produces a sheet whose MediaBox reads 210.000 × 297.000 mm with a 1.000 mm line
@@ -234,7 +236,8 @@ of the five serious findings were there. That is where to look first.
234236
## Read this before writing anything
235237

236238
**The specification is the source of truth, and it is unusually complete**
237-
~2330 lines covering the architecture, all ten generators, the page model,
239+
~2800 lines covering the architecture, all thirteen generators — eleven blades
240+
and two documents — the page model,
238241
validation, milestones and the decisions that were explicitly rejected and why.
239242

240243
Most questions that come up while implementing are already answered there, with
@@ -251,7 +254,7 @@ work around it.
251254

252255
**Where the specification was genuinely silent**, the resolution is recorded in
253256
[`implementation-decisions.md`](implementation-decisions.md)
254-
fifty-one of them so far, each with the section it belongs to and the
257+
fifty-three of them so far, each with the section it belongs to and the
255258
reasoning. Read it before changing a default; several look arbitrary and are not.
256259

257260
## Language split
@@ -278,7 +281,7 @@ and knows nothing about margins.
278281
| `axes.py` | `AxisPeriod` — what the handle needs from a blade for § 8.3/§ 8.5 |
279282
| `model.py` | pydantic sections; `Section` base with `extra="forbid"` + `deferred` |
280283
| `loader.py` | YAML → `Document`; formats, presets, devices, name lists |
281-
| `pages.py` | `Geometry`, page loop, placeholders, `preflight`, `build`, `snap: pixel`, and **`page_furniture`** — the one function that says what the handle draws around a page's own marks, used by the blade path *and* the document path |
284+
| `pages.py` | `Geometry`, page loop, placeholders, `preflight`, `build`, `snap: pixel`, and the three functions both page paths share: **`page_furniture`** (what the handle draws around a page's own marks), **`check_page_furniture`** (whether it fits) and **`check_text_glyphs`** (whether the font can draw it). Each exists because the blade path had it and the document path did not |
282285
| `frame.py` | header/footer layout, border, background, hole marks, the edge ruler's marks and its fit check, stamp |
283286
| `labels.py` | counting patterns `n`/`a`/`A`, explicit lists (§ 7.10) |
284287
| `clip.py` | Liang–Barsky in exact rationals, shared by `perspective` and slanted `lines` |
@@ -356,7 +359,10 @@ the pre-flight, never while pages are being written (§ 12 point 13).
356359
- **One coherent block per commit**, message explaining the decisions rather
357360
than the diff. `git log` is where the open-question resolutions live.
358361
- Verify against a real PDF, not just unit tests — `tests/pdfread.py` reads
359-
geometry back out.
362+
geometry back out (lines, dash arrays, whole subpaths and fitted circles), and
363+
`tests/test_dimensional.py` plus `tests/test_geometry_readback.py` are where
364+
that is done for every blade. **Every expected number in them comes from the
365+
definition or from a sentence of the specification — never from the code.**
360366

361367
## Where to start
362368

@@ -394,8 +400,13 @@ this project runs on — claims are backed by a command's output, never asserted
394400

395401
## What to do next
396402

397-
**Nothing in the specification is unbuilt, and nothing from the 2026-07-25
398-
handover is left.** That handover
403+
**Nothing in the specification is unbuilt.** As of 2026-07-26 the tool is also
404+
*published* — the sessions since 0.9.0 audited it as a stranger meets it, read
405+
every blade's geometry back out of a finished PDF, and put it on PyPI. The user
406+
has said there is more to do and will say what; nothing below is a queue they
407+
have asked for, it is the state to start from.
408+
409+
Nothing from the 2026-07-25 handover is left either. That handover
399410
([`docs/superpowers/plans/2026-07-25-next-steps-handover.md`](superpowers/plans/2026-07-25-next-steps-handover.md))
400411
carries a *done* note on each of its phases saying what was built and what was
401412
decided against; the designs and plans behind the larger ones live beside it in
@@ -410,7 +421,7 @@ supply:
410421
|---|---|
411422
| a device | the empty `quirks` (decision 31), and the rM2 figures nobody has measured on the device — the Paper Pro is owner-checked, the rM2 is not |
412423
| a pair of scissors | print `examples/16-net-tray.yaml` and `box-tuck-a4` at 100 %, cut, fold. The tray checks the geometry; the tuck-top also checks the thickness rule. **No test can do this**, and until someone does, the tests only agree with themselves |
413-
| use | the three § 15 questions below, and 1.0.0 itself. 0.11.0 is *published*, so the DSL can now meet someone other than the test suite which is the one thing 1.0.0 has always been waiting for |
424+
| use | the three § 15 questions below, and 1.0.0 itself. The DSL can now meet someone other than the test suite, which is the one thing 1.0.0 has always been waiting for |
414425

415426
If a new feature *is* wanted, the recipe has not changed since M1: a design
416427
settled with the user first when there is a real fork in it, then a plan, then
@@ -431,11 +442,15 @@ Three habits earned their place the hard way and are worth keeping:
431442
the same centre. The third quantity, the grid, was the only one that could
432443
have contradicted it, and it was not looked at until the user did.
433444
- **A probe that does not fire proves nothing either — suspect the probe first.**
434-
During the release audit I twice concluded a check was missing, and twice the
435-
check was there and my test was wrong: a name list without `{name}` in any band
436-
draws no text, so the glyph check has nothing to refuse; and a pipeline's `$?`
437-
is `head`'s exit code, not the tool's. Both would have been reported as bugs.
438-
Before writing "X is not checked", make the probe fail on purpose.
445+
This happened *five* times over the release sessions, and not once was the code
446+
at fault: a name list without `{name}` in any band draws no text, so the glyph
447+
check had nothing to refuse; a pipeline's `$?` is `head`'s exit code, not the
448+
tool's; a `--seed` that never reached the loop made the maze booklet look like
449+
a regression; Vera has 256 glyphs and no Polish, so "a font file fixes it"
450+
failed for the fixture's reason and not the check's; and a grep for a claim in
451+
`docs/CLAUDE.md` missed it because the sentence wrapped across two lines.
452+
Every one of them was about to be written up as a bug. **Before writing "X is
453+
not checked", make the probe fail on purpose.**
439454

440455
A specific paper aeroplane was considered and **refused**: it is a drawing, not
441456
a structure, and § 2 rules out a drawing language.
@@ -465,3 +480,12 @@ than on a design answer — **q4 was put to the user in July 2026 while building
465480
the slanted families and deliberately left out**: the angle is what unlocks
466481
calligraphy guides and origami pre-creasing, the unit would be a convenience on
467482
top of it (§ 7.1).
483+
484+
**q2 changed shape on 2026-07-26 and is worth re-reading in § 15.** It used to be
485+
theoretical, because a missing glyph in a generator's text was never noticed: the
486+
check ran over the bands only, a Polish month name printed as a box, and a
487+
document generator took no font at all — so the documented remedy did not exist
488+
for it. Both are fixed (decision 53). Stage 2 is now *reachable* everywhere, and
489+
the question is no longer "does it work" but "is it reasonable to ask, or should
490+
a broad OFL font travel with the tool?". Still for use to answer — but now the
491+
user is shown the choice instead of a box.

docs/implementation-decisions.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,3 +1038,43 @@ redrew every maze because unrelated furniture shifted `page.index`. § 7.13 says
10381038
nothing about what a per-section sheet plan should mean, so it is refused until
10391039
it does rather than answered wrongly. Honouring it per section is a real design
10401040
question, not an oversight, and is left open.
1041+
1042+
1043+
## 53. The language of the sheet: the tool receives words, it does not know any (§ 7.12, § 7.8, § 10.3)
1044+
1045+
Three changes on 2026-07-26 that are one decision, and they came out of a
1046+
question the user asked rather than out of the audit: *can any Latin-script user
1047+
set this up freely now?* The answer was no, in three places.
1048+
1049+
**A generator's text was never checked for missing glyphs.** § 12 point 13 asks
1050+
the pre-flight to measure "Kopf-, Fuß- **und Beschriftungstext**" and then
1051+
whether every glyph is present; `missing_glyphs` was called from exactly two
1052+
sites, both of them the bands. So `months: [styczeń, …]` passed `check`,
1053+
reported a successful run, and put `stycze` and a box on the paper — the silent
1054+
almost-right of § 5.1, in the one place the tool had a query built to prevent it.
1055+
`check_text_glyphs` collects **characters as a set per font** over the marks —
1056+
page 0 for a blade, every page for a document (decision 49's reason: a month name
1057+
only appears on its own month's pages) — so a 386-page planner with tens of
1058+
thousands of glyphs is checked against a few dozen distinct ones and the run
1059+
still takes a second.
1060+
1061+
**The tool wrote its own words on the user's sheet.** § 7.12 had applied § 7.8 to
1062+
the *names* and not to the vocabulary, so a German calendar came out with
1063+
`Jänner` under `Index Year Month Notes`. `words:` names the nine, defaulting to
1064+
today's English so no definition moves. Rejected, and worth recording: shipping
1065+
month/weekday tables for common locales. It would be a translation layer by
1066+
another name (§ 3.4), it would need maintaining, and the very first user whose
1067+
language was not in it would be worse off than today.
1068+
1069+
**A document generator could not name a font**, which turned the first change
1070+
from a fix into a trap: refusing a character with no way to draw it is worse
1071+
advice than it looks. `CalendarConfig` and `NotebookConfig` take a `font:` like
1072+
every blade, reaching the pages through `page_layout.Page`, which now carries the
1073+
family token — so all seventeen call sites in `calendar_layout` and the
1074+
notebook's three follow without knowing they do. **One font per document**: there
1075+
is nothing on these pages that wants two, and the alternative (a font per view)
1076+
would be a typographic system nobody asked for.
1077+
1078+
The order these were done in is the lesson, not the code. Refusing the silent
1079+
failure first and supplying the remedy second left a window in which a Polish
1080+
calendar was *less* usable than before. Both belong in one step.

docs/pflichtenheft-vorlagengenerator.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,35 @@ auf dem Inhaltsverzeichnis, wird mitgezählt, wenn geprüft wird, ob die Seite
12301230
passt, und bleibt leer, wenn keine angegeben ist — der Kalender erfindet keine
12311231
Bedeutungen. Damit ist § 7.12 vollständig.
12321232

1233+
#### Die Sprache des Blattes (ergänzt 2026-07-26)
1234+
1235+
§ 7.8 entscheidet den Grundsatz — die Beschriftungen gehören zur Sprache des
1236+
Formulars, also nimmt das Werkzeug sie entgegen und erfindet sie nicht — und
1237+
§ 7.12 hatte ihn auf die **Namen** angewandt (`months`, `weekdays`, `label`)
1238+
und auf das eigene Vokabular nicht. Ein deutscher Kalender kam deshalb gemischt
1239+
heraus: `Jänner` unter einer englischen Navigationsleiste, daneben `Contents`,
1240+
`Full-year overview` und `Half-year 1`. Das sind Wörter des *Werkzeugs* auf dem
1241+
Blatt des Nutzers, und genau die schließt § 7.8 aus.
1242+
1243+
**`words:`** benennt sie: `index`, `year`, `month`, `week`, `notes` (die
1244+
Navigationsleiste), `contents`, `full_year_overview`, `half_year` und
1245+
`full_year`. Jede Vorgabe ist das Englisch, das bisher gedruckt wurde, also
1246+
ändert der Block keine bestehende Definition. Beim Notizbuch heißt das eine Wort
1247+
`contents_title`, und der Rücklink auf einem Trennblatt folgt ihm.
1248+
1249+
Das ist **keine Lokalisierung** (§ 3.4) und es wird weiterhin keine
1250+
Übersetzungstabelle mitgeliefert: Das Werkzeug nimmt Wörter entgegen und kennt
1251+
keine.
1252+
1253+
**`font:` an einem Dokument-Generator.** Die Grenze dahinter ist die Glyphen-
1254+
abdeckung (§ 10.3): Die Standardschriften reichen bis Latin-1, decken also
1255+
Deutsch, Französisch, Spanisch, Italienisch, Portugiesisch und die nordischen
1256+
Sprachen ab — aber nicht Polnisch, Tschechisch, Ungarisch, Türkisch, Rumänisch
1257+
oder Kroatisch. Deshalb nimmt ein Dokument-Generator wie jede Klinge eine eigene
1258+
Schrift entgegen, **eine für das ganze Dokument**; ohne sie lehnt die
1259+
Vorabprüfung ein Zeichen ab, das die Schrift nicht zeichnen kann (§ 12 Punkt 13),
1260+
statt ein Kästchen zu drucken.
1261+
12331262
### 7.13 `notebook` — verlinktes Notizbuch aus Abschnitten (Dokument-Generator)
12341263

12351264
Ein PDF, das mehrere Papiere trägt: vierzig gepunktete Seiten zum Journalen,
@@ -2666,6 +2695,17 @@ Bewusst noch offen:
26662695
Latin-1 ab. Zeigt sich, dass Namen mit `ł`, `ğ` oder `ő` häufig genug sind,
26672696
um Stufe 2 zur Pflicht zu machen, wäre eine mitgelieferte OFL-Schrift der
26682697
bequemere Weg. Erst nach Erfahrung entscheiden, nicht auf Verdacht.
2698+
2699+
**Der Boden dafür ist seit 2026-07-26 gelegt, die Frage bleibt offen.** Bis
2700+
dahin war sie theoretisch, weil ein fehlendes Zeichen in Generatortext gar
2701+
nicht auffiel: `missing_glyphs` lief nur über die Bänder, ein polnischer
2702+
Monatsname wurde still als Kästchen gedruckt, und ein Dokument-Generator nahm
2703+
überhaupt keine Schrift entgegen — die dokumentierte Abhilfe existierte für
2704+
ihn also nicht. Beides ist behoben (§ 7.12, § 12 Punkt 13). Damit ist Stufe 2
2705+
für jeden Fall *erreichbar*, und die Frage lautet jetzt sauber: ist sie
2706+
**zumutbar**, oder soll eine breite OFL-Schrift mitreisen? Das entscheidet
2707+
Gebrauch, nicht Verdacht — aber es entscheidet ihn jetzt an einer Stelle, wo
2708+
der Nutzer die Wahl überhaupt gezeigt bekommt.
26692709
3. ~~**`reportlab`-Lizenz** am Repository gegenprüfen (BSD-3-Clause erwartet).~~
26702710
**Geklärt (2026-07-24):** reportlab 5.0.0 steht unter **BSD-3-Clause** — der
26712711
Lizenztext (`reportlab-5.0.0.dist-info/licenses/LICENSE`) führt exakt die drei

0 commit comments

Comments
 (0)