ikat 0.12.0

Weave Markdown into camera-ready LaTeX: mermaid-to-TikZ, pgfplots figures, publisher templates
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# ikat roadmap

Milestone detail stays here, done or not; the version-by-version
shipping record lives in `docs/CHANGELOG.md`.

Status 2026-10-27 (0.12.0, on `main`): through M7.10b done —
TeX-calibrated display-math width (M7.8b), MCP schema fidelity +
annotations + instructions + bundled skills + installer + registry
(M7.9/M7.9b), help verb + registry JSON + bare-invocation help
(M7.9c), full author front matter + opt-in collapse (M7.10/M7.10b),
POSIX load_preset fix — 173 cargo + 104 pytest green (104 passed,
4 skipped), hand and auto showcases byte-identical (7 pages,
0 overfulls), bench cache downloadable via `examples/bench_fetch.py`,
site asset = the auto-layout build. Shipped: tag `v0.11.0` pushed,
PyPI wheels (3.10-3.15 + PyPy, via Trusted Publishing) and the
crates.io crate published — first release from merged `main`.
0.11.1: the `tectonic` cargo feature is now Linux-gated
(`cfg(target_os = "linux")` — `--features tectonic` resolves on
every target, no-op where the C deps can't build) and CI on
ubuntu exercises the embedded engine via `cargo test --features
tectonic`; Windows keeps the binary cascade. 0.11.2: texres as
the third engine (binary cascade, `--engine texres`). 0.12.0:
docs + site engine-story sync (no code change over 0.11.2).
Open: none. See the TeXres watch item below for the next engine
step.

Acceptance rules for every phase: `cargo test` + `pytest` green,
`maturin develop` warning-free, demo + showcase paper still build.

## Phase 1 — Dogfood the paper (pipeline parity) ✅ DONE 2026-10-06

All compute in Rust (`src/esc.rs`, `src/table.rs`, `src/doc.rs`);
Python (`python/ikat/document.py`) only carries strings over the
PyO3 boundary. Golden test `tests/test_paper.py` rebuilds
`paper-2026-10-05.tex` **byte-identical** to `build-paper.py` output
(4 diagrams, 9 tables, 50-entry `.bib` derivative identical too).
Strictness upgrade kept: dangling citation keys fail the build.

Goal: ikat rebuilds `paper-2026-10-05.tex` from the draft markdown.
This is the fastest hardener: the paper's converter edge cases become
ikat's test suite.

- **M1.1 tables.** Pipe tables → `tabularx` with per-table colspec
  (`Xcl` appendix style), caption registry, `TABLE` numbering.
  Port from `build-paper.py::table_block`.
- **M1.2 citations.** `[key]`, `[author: key]`, `;`-groups, prose
  fallback — with keyset validation against the staged `.bib` so a
  dangling key fails the build instead of printing raw.
- **M1.3 inline.** Unicode→LaTeX map, `$…$` math protection,
  code/emphasis, IEEE section-number stripping.
- **M1.4 document writer.** Preamble from `ikat.toml` (class, options,
  columns, margins), float assembly in source order, `.bbl` passthrough.

*Acceptance:* ikat output diffed against the checked-in `.tex`
shows only intended improvements; resulting PDF compiles with
0 undefined citations.

## Phase 2 — Data plots live (matplotlib out) ✅ DONE 2026-10-08

- **M2.1 presets.** ✅ DONE 2026-10-08 (Phase A.1+A.2, 0.2.2+0.2.3)
  — `src/preset.rs` + `load_preset`: benchmark JSON → emitters
  (series names, min/max-validated whiskers, refline, footnote,
  legend key); CLI `--preset` + MCP `preset_json` wired with the
  surfaces synchronized in the same commits; skills updated.
- **M2.2 regeneration.** ✅ DONE 2026-10-08 (Phase A.3, 0.2.4)
  — `examples/regen/`: 4 fixtures transcribed from
  `benchmarks/results`, TikZ snapshots (CI byte-gate), standalone
  PDFs, pixel scores pinned in `ACCEPTED.md` with reviewed deltas
  (degenerate Fig-A whiskers, symmetric max-errors, linear Fig-D
  x, below-legend/45° touch, refline edge clip). Outputs stay in
  `regen/out/` — staging into `paper/figs/` is the paper track's
  call.
- **M2.3 local pgfplots.** ✅ DONE 2026-10-06 — tlnet `pgfplots.tar.xz`
  extracted into the TinyTeX texmf tree (`tlmgr` GPG is broken on
  this mirror chain); `tex_requirements()` auto-detects TikZ/pgfplots
  needs from the woven body so preambles stay minimal.
- **M2.4 legend positioning.** ✅ DONE 2026-10-07 (keyword;
  `auto` resolver follows) — `plot::LegendPos`: `auto` (the
  default) tries the inside corners in top-left, top-right,
  bottom-right, bottom-left order, testing each against the drawn
  data — polyline + error-bar segments for lines, whole-slot bar
  rects to the whisker top (log-mapped for log-y) plus the refline
  — on visual ranges that overestimate pgfplots' padding, and falls
  back to the under-axis row when every corner is occupied.
  Inside corners cannot touch axis labels (labels live outside the
  axis box); the below row clears tick labels by construction.
  Explicit `below`, four corners, and `outside-right` place it by
  hand; unknown words are `ValueError`s naming the set. Threaded
  Rust core → PyO3 (keyword-with-default, backward compatible) →
  MCP tools; showcase figs prove it (LOC → top-right, growth →
  top-left).
- **M2.5 emitter polish.** ✅ DONE 2026-10-07 — mermaid edge
  labels get `fill=white` knockout (diagonal edges no longer
  strike through `column`/`wide`); barchart x ticks rotate 45°;
  lineplot pins `xmin`/`xmax` to the data (no pre-first-point gap).
  All three are structural emitter changes, each with a Rust test.

*Acceptance:* `paper/figs/` producible with no matplotlib import
anywhere in the loop.

## Phase 3 — Figures advanced ✅ DONE 2026-10-08

- **M3.1 standalone export.** ✅ DONE 2026-10-08 (Phase B,
  0.3.1+0.4.0) — `python/ikat/standalone.py` (`wrap_standalone`
  + `compile_standalone`), `ikat standalone` (exactly-one-fence
  rule), MCP `standalone_figure` (returns source, workdir rule),
  `tikz_needed`/`plots_needed` predicates, four paper-diagram
  proofs with ledger scores in `examples/regen/ACCEPTED.md`.
- **M3.2 sequence subset**, **M3.3 state subset** ✅ DONE
  2026-10-08 (Phase C, 0.4.1+0.4.2+0.5.0) — `src/sequence.rs`
  (columns in declaration order, alt/else/opt boxes) +
  `src/state.rs` (shared placer, composite boxes) behind the same
  `flowchart_to_tikz` entry; `emit.rs` holds the shared TikZ
  vocabulary; showcase Figures 2+3 are the proofs.
- **M3.4 layout.** ✅ DONE 2026-10-08 (Phase D.1, 0.5.1) —
  `src/layout.rs` (legend geometry reused: `segs_cross`,
  `seg_hits_rect`, `cluster_box`), longest-path layering
  (multi-parent = max+1), `subgraph ID [title]`…`end` clusters
  (nesting errors), edge router (clear segments byte-identical,
  crossings reroute via pushed midpoint, exhaustion names the
  edge); showcase Figure 1 re-laid and reviewed.
- **D.2 heading guards.** ✅ DONE 2026-10-08 (0.5.2) —
  `[typography]` (`keep_with_next = true` default, `min_lines =
  2`): `\clubpenalty`/`\widowpenalty` 10000 +
  `\needspace{min_lines+1\baselineskip}` before sections;
  needspace auto-added (generated/override/skeleton paths);
  `titlesec` under IEEE rejected by `check_titlesec` +
  `ikat check`; main paper `.tex` regenerated (61 added guard
  lines, 0 removed) and recompiled clean.

*Acceptance:* the paper's TikZ diagrams (corpus: the four
`paper/figs/tikz/fig-*.tex` sources — branch-tree, ledger-flow,
read-questions, stratum) regenerate from their mermaid fences with
no hand-tuning.

## Phase 4 — Harden & release ✅ DONE 2026-10-08

- **M4.1 errors.** ✅ DONE 2026-10-08 (Phase F, 0.7.1 → gate
  0.8.0) — `src/error.rs::Error { msg, line, col, echo }` at
every fail-fast site (61 `Err`s audited: mermaid/sequence/state
statements + edge targets + empty diagrams, router exhaustion,
subgraph/composite errors, ragged plot/preset series, dangling
keys, template/skeleton tokens, titlesec, float attrs, legend
words, refdefs). Manuscript lines via `md.rs` original-number
blocks (fence bodies `line + row`), head-file lines for
templates, line 0 (no line part) for JSON/TOML data errors;
carets where the grammar knows the token (char columns,
indent-exact). Python `document.with_file` upgrades to
`file:line:` (head-path routing for template errors); CLI
prints verbatim; MCP returns the text. 118 cargo + 75 pytest
(16 + 6 new error tests); golden untouched (`ebe698db`).
- **M4.2 CLI.** ✅ DONE 2026-10-07 — `ikat build/weave/check/templates/template/flowchart/version`
  (`python/ikat/cli.py`, `ikat` console script): same operations as
  the API, `--spec` BuildSpec JSON, `--ensure-packages` gate.
- **M4.8 MCP server.** ✅ DONE 2026-10-07 — `python -m
  ikat.mcp_server` (FastMCP stdio, `ikat[mcp]` extra): nine tools
  mirroring the CLI/API map; `compile_pdf` deliberately excluded.
- **M4.2b CI compile check.** ✅ DONE — `.github/workflows/ci.yml`:
  `test` runs cargo + pytest on ubuntu/windows/macos-latest;
  `tex` (ubuntu) installs real TeX Live via apt, builds the demo
  and the showcase paper, and fails on undefined citations or
  control sequences. (Docker image dropped: runner TeX Live is
  the same backend verification with less machinery; the tectonic
  crate-embedding check rides along once M4.2b exists.)
- **M4.5 ensure-packages helper.** ✅ DONE 2026-10-06 — the
  texliveonfly trick: `src/texenv.rs` scans any preamble into
  `(probe file, tlmgr package)` needs, `python/ikat/texenv.py`
  probes via one `kpsewhich` call and `tlmgr install`s what's
  missing; `compile_pdf(..., ensure_packages=True)` gates the
  build on it.
- **M4.6 user templates.** ✅ DONE 2026-10-06 — `[template]`
  `preamble_file` replaces the generated head, `preamble_append`
  adds lines before `\\begin{document}`; Rust validates the
  replacement carries a class + every package the body needs.
- **M4.7 template library.** ✅ DONE 2026-10-07 — nine shipped
  heads (arXiv, article 1/2/3-col, IEEE, ACM, LNCS, Elsevier, APS)
  with `{{{title}}}`/`{{{author}}}`/`{{{thanks}}}` tokens,
  per-template presets (bib_style + toml), `abstract_before_maketitle`
  hoist for top-matter classes, all nine compile-proven locally.
- **M4.3 claim the names.** ✅ READY 2026-10-07 — both `ikat`
  names verified free; `cargo publish --dry-run` green; release
  wheel proven in a clean venv (9 heads + skeleton, weave ok);
  `v0.1.0` tagged. Ships with two commands once the tokens exist:
  `cargo login` + `cargo publish` (crates.io), `maturin publish`
  or `twine upload dist/*` (PyPI). Until then: install from the
  GitHub repo (`pip install git+https://github.com/opticsWolf/ikat`).
- **M4.4 self-hosting.** ✅ DONE 2026-10-07 — the paper builds with
  ikat itself (`ikat weave paper/paper-2026-10-05.md --spec
  paper/ikat-spec.json --bib paper/refs.bib`, byte-identical);
  `build-paper.py` retired (kept in git history, not in the tree); new
  `--bib` flag derives `bib_keys` from the `.bib` (no frozen
  derived data, no drift).
- **M4.9 tectonic engine (prototype).** ✅ DONE 2026-10-07 —
  `compile_pdf(engine="tectonic")` cascades embedded binding
  (`tectonic` cargo feature, off by default) → `TECTONIC_EXE`/PATH
  binary; `tex_for_tectonic` drops the inputenc line; woven doc
  with TikZ+table compiled to a content-verified PDF. Tectonic
  *crate* embedding was Linux-only from the start (C deps via
  pkg-config) and is now enforced in code: since 0.11.1 the
  dependency is `cfg(target_os = "linux")`-gated and CI on ubuntu
  compiles the embed (`cargo test --features tectonic`); empty `bib_name` now emits no bibliography lines (tectonic auto-runs
  BibTeX and dies on empty `\bibliography{}`).
  What M4.9 did NOT cover is itemized in M4.10.
- **M4.10 tectonic hardening.** ✅ DONE 2026-10-08 (Phase G,
  0.8.1 → gate 0.9.0) — (1) CI `tex` job installs the pinned
  0.17.0 binary (checksum-verified linux-gnu tarball) and builds
  demo + showcase with `--engine tectonic` (new flag on both
  builders, default pdflatex); (2) showcase (10 pp) AND main
  paper (22 pp) verified under tectonic, 0 undefined citations
  both engines, engine deltas (all font-encoding class: TU/OTF
  vs OT1/Type1 extraction, quote glyphs, hyphenation points)
  ledgered in `ACCEPTED.md` — no content delta, nothing filed;
  (3) Windows-embedding limitation in README (binary cascade is
  the supported path; embedding stays Linux-CI by design).
  Found in passing: tectonic swallows the XeTeX log without
  `--keep-logs` (undefined citations would pass silently) —
  `_compile_tectonic` now passes it and writes the transcript
  beside the kept log; the gate grep covers both. Red-then-green
  proven locally with the exact gate patterns (broken citation
  → 2 matches; clean → 0).

## Phase 5 — Floats, skeletons, parsing (spec: docs/FORMAT-DRAFT.md) ✅ DONE 2026-10-08

- **M5.1 float formatting.** ✅ DONE 2026-10-07 — per-element
  `span/pos/width/captionpos` attrs (`%% table {...}` for tables,
  `plot_attrs` registry for plots) + `[floats]` fractions/counters/
  `barrier_sections` + `table*` + `picture` kind; packages
auto-added or template-validated; compile-proven to PDF.
- **M5.2 level-3 skeleton.** ✅ DONE 2026-10-07 — `[template]`
  `skeleton` with `{{body}}`/`{{bibliography}}`/`{{abstract}}`
  token contract (comment-aware counting/rendering, unknown-token
  errors, exclusion vs `preamble_file`), shipped
  `skeleton-plain.tex`, compile-proven to PDF.
- **M5.3 Markdown AST spike.** ✅ SPIKE DONE 2026-10-07 —
  pulldown-cmark 0.13.4 event-diff vs the hand scanner: all four
  required properties hold (verbatim info strings, identical
  headings, matching table shapes, aligned paras modulo the
  `%%` directive line, which stays pre-processing either way).
  Switch viable, deferred to its own golden-parity milestone.
- **M5.4 pulldown-cmark switch.** ✅ DONE 2026-10-08 (Phase E,
  0.6.1 → gate 0.7.0) — `src/md.rs` production mapping +
  `convert()` on `MdBlock`s. Acceptance met: empty diff on the
  harness (141+35+3 corpus blocks + latent synthetics), golden
  paper byte-identical (`ebe698db`, 0 edits to the golden file),
  98 cargo + 69 pytest green, all proofs build. One consumption
  bug found mid-switch (blank-separated Paras merged — `convert`
  now flushes between consecutive `Para` blocks); one orphaned
  `#[cfg(test)]` on `mod mermaid` found via a failed
  `maturin develop` (probe-cleanup scar, fixed). `esc.rs`
  untouched, as the spike predicted.

## Phase 6 — Showcase paper ✅ DONE 2026-10-07

`examples/ikat-paper/` builds a 6-page two-column paper about ikat
itself (`build.py` + `ikat.toml` + `skel.tex` + `refs.bib`): two
inline mermaid diagrams (wide pipeline, column float-resolution),
two pgfplots figures (LOC bars from `wc -l`, test-growth line from
`git grep` history — no invented numbers), four tables, citations
via BibTeX, math, unicode. It is the release demo AND the
constructive proof that every md feature composes in one document.
Side fixes landed here and covered by tests: mermaid `[scale=]`
attribute splice (Rust + Python), lineplot `+- (0,0)` zero-error
emission. The shipped `IEEEtran.bst` is CTAN v1.14 (a 404-page
copy found in the texmf tree was replaced and the file staged
locally with an HTML guard).

*Acceptance:* `build.py` runs green, PDF is 6 pages with 2
diagrams + 2 plots + 4 tables and 0 undefined citations.

## Phase H — Auto-layout (M7.1) → 0.10.0

Advisory auto-layouter: `ikat layout` measures every float and
suggests `span`/`pos`/`width` with reasons; `--apply` rewrites
fence attrs. No pagination simulation (only LaTeX knows text
flow) — classification by measured size, LaTeX still places.
Estimators: TikZ coord scan + `node_box` margins (diagrams),
pgfplots defaults (plots), row-pitch + cell widths (tables);
pictures/external PDFs keep hand attrs. Policy: `page_frac`
pre-empt, section-lead bonus, wide ratio, crowding floor from
placed node labels. Surfaces in the same commits: Rust core +
`layout.py` (`suggest`/`apply`/`report`) + `ikat layout` CLI +
` suggest_layout` MCP tool + QUICKREF/skills/ARCHITECTURE.
Co-agent B1 bench (`examples/bench-layout/`, untracked scratch)
drives `suggest_layout` over 10 PLOS papers: v2 distributions
reproduce exactly under the final engine; showcase engine-vs-hand
diff is 2 hunks (Fig 2 `0.57` vs hand `0.58`, Table 3 `force` vs
`barrier`). E1 closed as expected (PLOS is single-column; the
1-col run puts everything in-flow).

*Acceptance (M7 gate):* `cargo test` + `pytest` green
(135 + 86), demo + showcase + regen + standalone + heads proofs
build, golden byte-identical, auto-applied showcase copy builds
8pp with 0 overfull and the hand float distribution.

## M7.2 — Top-fit + queue pressure → 0.10.2

Wide floats taller than the page-top budget (`top_frac` 0.7 ×
text height, the LaTeX `topfraction`/`dbltopfraction` default)
take `wide`+`page` instead of `wide`+`top`: `[t]` cannot legally
hold them, so they (and the queue behind them) pile at the
document end. Column tables taller than the text height go
`page` (`[H]` would blast through). The third and later `wide`
floats since a section heading earn a spread-the-fences note.
Proven on the full-export corpus: psych-maia's 41-row table left
the p24–26 end pile for an in-section float page (9 tables now
p9–p21 beside their prose, 26pp→25pp), 0 stranded headings
across all 10 papers (D.2 needspace guards hold). Exporter
`pic_attrs` mirrors the budget for precompiled pictures.

## M7.3 — Refs barrier (opt-in) → 0.10.3

`[floats] barrier_before_refs` (default off, so default weaves
stay byte-identical): a `\FloatBarrier` ahead of
`\bibliography` in all three preamble paths. End-queued floats
otherwise spill into/past `References` — bio-medsea's Figs 7-14
landed on pp37-42 after the reference list started (p27) because
10 wide figure tops saturated and no barrier contained them. With
`barrier_sections` + refs barrier (corpus weave policy), bio's
tables sit inside Discussion beside their citing prose (TABLE IV
under the `(Table 4)` paragraph, p26) and nothing crosses
`References`. Proven over the full 10-paper re-export, 0
unmatched, 0 stranded headings; golden byte-parity holds
(default off).

## M7.4 — Tail page + no empty columns → 0.10.4

`[layout] tail_lines` (default 10): a `wide`+`top` float with
fewer body prose lines remaining takes a dedicated `page`. Two
birds: no downstream tops are left (it would pile at the document
end), and a still-pending float meeting `\FloatBarrier` ejects
the column — placeins issues `\clearpage` in the first column,
stranding a half-empty column behind it. That eject was the
corpus's void source: section barriers (since reverted for the
corpus) met pending wides and blanked ~17 right columns
(chem p2, bio p17/p22/p33). Float pages never strand columns:
text flows past them, and the refs barrier meets empty defer
lists. Skipped in prose-free manuscripts so B1 stub pins stay
exact. Corpus result: 0 true mid-body voids across all 10 papers
(the 3 remaining flags are final-text-column end geometry + one
normal column break); nothing crosses `References`.

## M7.5 — Table compaction ladder (opt-in) → 0.10.5

`[tables] compact_tables` (default off, so the frozen paper and
all default weaves stay byte-identical): the widest unbreakable
token per table drives `Fit` (`\small`) → `Compact`
(`\footnotesize` + 3pt padding) → `Rotate` (`sidewaystable(*)` in
`\scriptsize` + 2pt padding against `\textheight`). Column
tables that fail promote to `wide` before rotating. Prose labels
never trigger it (they wrap); only unbreakable numeric cells do.
Corpus proof: econ-tax 441 → 25 overfulls (max 2.9pt, invisible),
the 13-column tables rotate readably; psych 200 → 19, bio 38 → 7,
earth 17 → 1. The engine notes always measure the shared ladder
and name the opt-in when it is off; `rotating` joins the strict
package contract (auto-added to generated heads, named for
skeletons/overrides).

## M7.6 — Cell stacking (PLOS-original style) → 0.10.6

Stacks estimate/SE cells before deciding: `0.080***(0.015)`
renders as estimate over `(0.015)` (`\newline` in the `X` cell,
via a split mark that survives `inline()` escaping). Only
trailing digit-bearing paren groups split — stars stay with the
estimate, empty heads and prose notes stay single-line. The
shared `decide_table` (weaver + engine notes) stacks first, then
re-decides: econ's 7-col tables drop from `Compact` to `Fit` at
full size, the 13-col tables rotate roomy. Result: econ 25 → **0**
overfulls (was 441), corpus total 441 → 47 with all remainders
in prose (flattened formulas) or ≤3pt. Prose labels never trigger
(they wrap); the knob stays default-off so the frozen paper and
all default weaves hold byte-identical.

## M7.7 — Honest portrait limits (Tiny rung + pin height) → 0.10.7

`Tiny`: a fourth ladder rung — `\scriptsize` + 2pt padding but
still portrait — for dense regression tables between `Compact`
and `Rotate` (relative type size matches the originals' 7.5pt on
their wider measure). A narrow-aware token ruler (digits/punct at
0.85) replaces the flat 0.16/char. A hyphenation discount was tried
and reverted the same session: isolation renders proved TeX never
breaks the words (IEEEtran sets `\hyphenpenalty=750` — measured
750 in the class), and the discount printed 16 overlapping tables;
every whitespace run binds.
Pin-height promotion: stacked rows double a table's height, so a
column table taller than the in-flow budget now floats wide + top
instead of overflowing (`\vbox too high`) — the width ladder
approves the column, the height vetoes the pin. Tail threshold
recalibrated 10 → 6 paragraphs (a column of downstream text; the
paragraph unit had made 10 three times stricter than the JATS-line
calibration) and the exporter's tail counter counts paragraphs,
fixing chem Fig 5's lone float page (10pp → 8pp). Corpus: econ
0 overfulls with 8/10 tables portrait; the two true 13-col tables stay
rotated — `globalization`-class words need 2.08cm against a
1.37cm share, mathematically unportraitable at IEEE widths (the
originals need 19cm + 7.5pt for the same).

## M7.8 — Honest math (MathML rebuild + auto-stretch) → 0.10.8

The bench exporter flattened 511 real MathML equations into symbol soup
(fractions lost their bar, 993 glyphs became ?, 70 overfulls to 494pt).
A structural MathML-to-LaTeX converter (mml.py) reconstructs single
breakable spans: sub/sup/fractions/sums/limits/roots/matrices/cases,
empty-base prescripts, JATS sub/sup merging, three publisher-misencoded
glyphs mapped by role (time renders as zeta, upright beta, k-index).
Core weaves whole-paragraph double-dollar spans to display math:
wider than the column takes a cuted strip; wider than the full measure
breaks BEFORE top-level relations into left-aligned rows (matrix
spans take one strip per piece instead). A glyph-aware width
estimator (TeX glue,
script extension, banked matrices, depth-matched envs, nested-aware
row splits) decides. math-hilfer-2024: 70 to 0 hbox overfulls;
econ/physics formulas restored from dropped to rendered.
## M7.8b — Calibrated display-math width (probe, don't guess) → 0.10.10

The M7.8 estimator's guessed constants (0.16–0.35 cm/char) mis-sized
caligraphic, big-operator, and script-heavy spans: the median
estimate ran 1.26× TeX truth (range 1.09–1.50), classifying 6 of
math-hilfer's 100 display spans into full-width strips that TeX
fits in one column. The replacement (`src/mathwidth.rs`) is probed,
not guessed: glyph widths at the three math styles (10/7/5 pt) come
from TeX `\sbox` measurements (examples/auto-proof/
math_probe_widths.py → `glyphs_raw.json` → math_emit_table.py
generates the Rust table); the structural rules are measured too —
class glue per the TeXbook table with previous-atom tracking (thin
before Open/Close, thick both sides of a relation, none in script
styles), stacked sub/sup = max + one `\scriptspace`, fraction
children descending display→text→script, column-aligned matrix
width (sum of per-column maxima + 10pt gaps + bracket pairs), and
one thin pad inside radicals set over an Inner. Unit tests pin ~40
TeX-measured composites within 0.01 cm. Result: math-hilfer 0
disagreements, 0 hbox + 0 vbox overfulls, strips 66 → 59, aligned
24 → 15, golden byte-parity and all regen snapshots hold.

## M7.9 — MCP schema fidelity + bundled skills (gossamer-audit transfer) → 0.10.11

An audit of the gossamer MCP surface (registry-driven, drift-tested,
but with lossy schemas) transferred here: ikat's FastMCP tools now
carry per-parameter descriptions and enum constraints in the
inputSchema (`legend` renders as a real enum; previously every
parameter was an undescribed bare type), every tool declares MCP
annotations (readOnly/destructive/idempotent/openWorld — all tools
are local, network-free, read-only computation except
`ensure_tex_packages`, the one tlmgr-mutating tool), and the server
sets `instructions` describing the weave-here-compile-via-CLI
doctrine. The four agent skills moved into the package
(`python/ikat/skills/`, ships in the wheel) and are served as
resources `skill://ikat*` so MCP-only hosts can read them; drift
tests pin every taught CLI command, MCP tool, and API name to the
live surfaces, frontmatter name/description validity included. The
CLI reconfigures stdio to UTF-8 so unicode error echoes cannot crash
a cp1252 console.

## M7.9b — Tool registry + skill installer (gossamer transfer, tranche 2) → 0.10.12

The second gossamer-learning tranche: the 14 MCP tools became DATA.
`python/ikat/registry.py` holds `TOOL_REGISTRY` — name, description,
callable, parameter specs (type, default, description, legend enum),
return type, and the annotation flags — and generates all three
consumptions from it: the FastMCP server (schemas/docstrings/
annotations are built from the spec, so the MCP surface cannot drift),
`llm_tool_definitions()` (OpenAI-style function-calling definitions,
full fidelity), and `execute_tool(name, arguments)` (drive the whole
surface from a dict, no MCP installed). The composites
(`weave_document`, `standalone_figure`, `suggest_layout`, …) moved
into the registry; `mcp_server.py` is now a thin renderer. And ikat
gained gossamer's skill installer: `ikat skills list | show NAME |
install [--dest DIR…] [--project] [--skill NAME…] [--force]` copies
the bundled packs into Claude Code, OpenCode, pi, and Agent Skills
directories (skip-unless-`--force`; the drift tests exercise the
whole lifecycle).

## M7.9c — `ikat help [COMMAND]` + bare-invocation help → 0.10.13

Argparse help as a verb: `ikat help` prints the command list (the
fifteenth subcommand), `ikat help CMD` prints that command's flags —
the git convention, so agents can discover the surface without the
`--help` global-flag form. Unknown command names name the valid set.
`ikat help --json` dumps the agent-tool registry (all 14 MCP tools,
full schemas via `llm_tool_definitions()`) — the MCP surface is
discoverable from the CLI too. And bare `ikat` prints help with exit
0 instead of an argparse error (gossamer errors on both; ikat
previously errored on bare).

The drift tests now pin FLAGS, not just command names: every
optional flag of every subcommand (nested `skills install` flags
included) must appear in the skill's fenced syntax, and invented
flags fail — this audit caught `ikat weave` never teaching
`--bib`.

## M7.10 — Full author front matter → 0.10.14

The 10-paper exports showed "Coll et al." with no institutes while
the originals carry 39 authors + 26 affiliations: the bench exporter
collapsed JATS front matter to one surname (its md had no channel
for authors), and the engine modeled authors as one scalar.
Amended end to end:

- `BuildSpec` gains `authors` / `affiliations` / `author_affils`
  (0-based affiliation indices per author); when `authors` is empty
  every spec is byte-identical to before.
- `author_front` (doc.rs) composes one wrapped `\parbox` — names
  with numeric `\textsuperscript` marks over the institute rows —
  because the IEEEtran author blocks are tabular rows that never
  wrap (a 39-name list clips at the margin) and a parbox nested
  inside them breaks the row alignment; raw `&` in institute names
  is escaped via `esc_text`. Out-of-range marks fail fast naming
  the count.
- CLI: repeatable `--author` / `--affiliation` / `--author-affil
  A:N[,N…]` (1-based) on `build`/`weave`; MCP/API ride the existing
  `spec_json`.
- The bench exporter reads every contrib + aff with xref marks —
  the re-exported corpus now shows the complete front matter
  (verified against the originals on page 1).

## M7.10b — Opt-in author collapse (`authors_max`) → 0.10.15

The deliberate "first N + et al." form, previously unavailable:
`BuildSpec.authors_max` (CLI `--authors-max N` on `build`/`weave`)
composes the first N marked names + ", et al." (IEEE style) when the
list is longer; the full affiliation block stays, so everything
shown remains true. 0 (default) never collapses; negative fails
fast; `max >= len` is a no-op. The exporter does NOT use it — the
bench corpus keeps complete front matter.

## Engine era — release, embed, third engine (0.11.0 → 0.12.0)

- **0.11.0 — first release from merged `main`** (2026-10-26):
  POSIX `load_preset` fix (a >NAME_MAX string probed as a path
  raises ENAMETOOLONG on POSIX — Windows answered False, which is
  why only the Linux CI tripped); the handwoven showcase ports
  the engine's winning attrs → hand == auto byte-identical
  (7 pages, 0 overfulls); bench cache downloadable via
  `examples/bench_fetch.py` (cache never committed); tag pushed,
  PyPI wheels (3.10–3.15 + PyPy, Trusted Publishing) + crates.io
  crate live.
- **0.11.1 — the tectonic embed is Linux-gated in code**: the
  optional dependency under `cfg(target_os = "linux")`, module +
  exports gated with it — `--features tectonic` resolves on every
  target (a no-op where the C deps cannot build) and ubuntu CI
  compiles the embedded engine; the Python cascade falls back to
  the binary elsewhere.
- **0.11.2 — texres, the third engine**: `engine="texres"` runs
  the one-binary Rust TeX distribution (LaTeX, packages, fonts,
  BibTeX bundled; all passes to convergence; nothing to install);
  proven on the demo, the showcase, and the CLI with zero TeX
  installed.
- **0.12.0 — docs/site engine-story sync**: README install
  section + TeX-tier hints, complete module map, the site's PDF
  story + TeX-boundary note name all three engines, Pages deploys
  gate on an absolute-link check.

## Watch: TeXres — a Rust TeX distribution (2026-10-27)

Survey question: "is there an alternative in Rust to tectonic?"
Answer: yes, one serious candidate — **TeXres** (github.com/leoliu0/texres,
v0.7.8 2026-10-23, MIT/Apache-2.0): a TeX distribution in a single
Rust executable (pdfLaTeX/XeLaTeX/LuaLaTeX + LaTeX formats + package
archive + fonts + BibTeX/Biber, fully offline, output CI-compared
against TeX Live 2026).

**Empirically verified on the showcase** (not just README claims):
portable Windows build (810 MB), the woven `ikat-paper.tex` (IEEEtran
conference class + TikZ + pgfplots + precompiled PDF figures +
`\bibliography` with staged `ikat-refs.bib`) compiled with zero other
software installed — 7 pages (same as pdflatex), References resolved
in IEEE style, IEEEtran typography correct, line-anchored errors with
source snippets. Found `IEEEtran.bst` in its own archive.

Also surveyed, all lesser: `texforge` (workflow wrapper that installs
tectonic underneath), `tex_engine` crate (real Rust engine framework,
GPL, research-grade), `rustatex` (own language, toy), Typst (mature
pure Rust, but a different language — an emission target, not an
engine swap).

**Why it matters for ikat:**

1. ~~`engine="texres"` slots into the binary cascade~~ **DONE in
   0.11.2**: `compile_pdf(engine="texres")` + `--engine texres` on
   `build`/`standalone` — `TEXRES_EXE`/PATH probe, stdout transcript
   kept beside the PDF for the CI gate, `ensure_packages` a no-op
   (everything bundles). Locally proven end-to-end on the demo and
   the showcase sources.
2. The strategic prize: texres ships a **C library and a WASM module**
   that compile documents in memory — a C embedding WITHOUT the
   pkg-config/system-lib chain that makes the `tectonic` cargo
   feature Linux-only. The credible endgame for an embedded engine on
   Windows; could replace the tectonic embed entirely. **Embed
   feasibility (checked 2026-10-27):** upstream publishes NO crate
   to crates.io (`libtex` absent); a git-dependency on their
   workspace would work, but the data is baked into the binary
   (that is why the exe is 810 MB) — an ikat embed would balloon
   the extension module to ~800 MB, far past PyPI's 100 MB upload
   cap. So: embed = source-build/offline-deployment option only,
   never a wheel; the cascade slot is the shippable form.
2. The strategic prize: texres ships a **C library and a WASM module**
   that compile documents in memory — a C embedding WITHOUT the
   pkg-config/system-lib chain that makes the `tectonic` cargo
   feature Linux-only. The credible endgame for an embedded engine on
   Windows; could replace the tectonic embed entirely.

**Why watch, not switch:** 810 MB per platform (the distribution is
the product, vs ~30 MB tectonic binary + cached bundles), v0.7.x
maturity, single maintainer. Revisit when a Windows/CI pain point or
the WASM/C-library embedding justifies the work.

## Non-goals

Full mermaid coverage (deliberately a strict subset), WYSIWYG,
bibliography management (`.bib` stays the source of truth),
reference-manager integration.

## Ordering

Nothing is queued — every planned phase and milestone is done
(see the sections above for each one's record). New work lands as
M7.x-style milestones with their own gate commits and version
bumps (the 0.10.x pattern); the candidate directions are in the
TeXres watch item and the Tracked-elsewhere list. Numbering has
no 1.0 planned.

## Tracked elsewhere (not ikat work)

- **Paper track** (`paper/`): pending `[NOT YET MEASURED]`
  benches, `0.19.0` tag + decision-register permalink, fig
  regeneration via ikat plots (M2.2's consumer), `arxiv-submit/`
  refresh and submission.
- **okfgraph upstream**: `--target chunks` segfault, parallel-CLI
  DB lock, phantom `image_count`, INFO-on-stdout breaking
  `--json`, no `okf delete`.
- **Repo hygiene**: Macrame_docs has no remote; local commits
  (incl. `paper/ikat-spec.json`) need a backup remote.