Skip to content

integration preview: daniel-parent queue merged on main (for the integration lane — do not merge directly) - #609

Closed
d-q222 wants to merge 203 commits into
mainfrom
agent/daniel-parent/integration-preview
Closed

d-q222 wants to merge 203 commits into
mainfrom
agent/daniel-parent/integration-preview

Conversation

@d-q222

@d-q222 d-q222 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

What

A draft integration preview for the integration lane (@GoKubar / kabir-claude). It is not for direct merging.

origin/main 36fe7ec3, plus all 43 open non-draft agent/daniel-parent/* PRs merged with git merge --no-ff, in dependency order.

  • No rebases, and vendor/ is untouched.
  • Conflicts were resolved minimally; generated docs were regenerated with render_supported_latex.sh, never hand-merged.
  • The notes are in docs/integration/daniel-parent-preview.md, reproduced below.

Branch agent/daniel-parent/integration-preview: origin/main 36fe7ec3 plus the 43 open non-draft
agent/daniel-parent/* PRs, merged with git merge --no-ff (no rebases, vendor/ untouched).
This is a preview for the integration lane (GoKubar / kabir-claude). Do not merge this branch directly.
Merge the individual PRs in the order below, and reuse these resolutions when a PR conflicts.

Recommended merge order

Stacks come from the PR bodies ("stacked on #N") and were confirmed by branch ancestry
(git merge-base --is-ancestor). A child PR's branch contains its parent, so merging the child also lands the parent.

  1. project-files: project-files: open rooted files non-blocking so a FIFO can't hang discovery #518project-files: probe, list, rename and unlink only through the pinned root (stacked on #518) #539 (stacked on project-files: open rooted files non-blocking so a FIFO can't hang discovery #518)
  2. pdf: pdf: exact route rounds colours to xcolor's 5 decimals and paints TikZ path_stroke/path_fill #527pdf: exact route paints TikZ opacity through pgf's /pgf@CA and /pgf@ca ExtGStates (stacked on #527) #533 (stacked on pdf: exact route rounds colours to xcolor's 5 decimals and paints TikZ path_stroke/path_fill #527) → pdf: an ellipsis's periods no longer make every period extract as "…" #546
  3. compiler + math-layout: compiler + math-layout: \tag and \text can contain math — a TextRun nucleus with nested $…$ (#441, PR 1 of 2) #470compiler + math-layout: amsmath \sideset with pdflatex geometry — a left-scripts slot (stacked on #470) #581 (stacked on compiler + math-layout: \tag and \text can contain math — a TextRun nucleus with nested $…$ (#441, PR 1 of 2) #470; 20 commits behind main)
  4. compiler: compiler: boxed arguments and letter \ps no longer leave phantom paragraph leadings #517, compiler: accept \tabcolsep, \arrayrulewidth, \doublerulesep, \extrarowheight assignments (#56) #526, compiler: \coloneqq is \vcentcolon\mkern-1.2mu= under mathtools (#468; needs render-pipeline re-pin) #529, compiler: a braced ] does not end lstlisting's options; \lstlistoflistings is listings' #542, compiler: 3x faster large-document compile — shaping memo, glyph-table index, lent cleveref config (#65) #535compiler: handle_line 1.4x faster on large documents — no placed-item copies, borrowed session output, pages serialised once (stacked on #535) #579 (stacked on compiler: 3x faster large-document compile — shaping memo, glyph-table index, lent cleveref config (#65) #535)
  5. compiler + tex-expansion: compiler: mutation fuzzer + 13 robustness fixes (panics, stack overflows, hangs, OOM) #577compiler, tex-expansion: bound diagnostic floods from runaway loops (stacked on #577) #592 (stacked on compiler: mutation fuzzer + 13 robustness fixes (panics, stack overflows, hangs, OOM) #577) → tex-expansion: incremental re-expansion equals a full run across step/output-limit stops (stacked on #592) #601 (stacked on compiler, tex-expansion: bound diagnostic floods from runaway loops (stacked on #577) #592), then compiler: em/ex follow the active font, \hspace keeps its trailing space, \global length prefixes #587
  6. compiler: compiler: page and paragraph control commands as penalty, discretionary and parameter nodes (#64) #568compiler: vertical \pagebreak is a bare penalty, amsmath \nobreakdash (#64; stacked on #568) #583 (stacked on compiler: page and paragraph control commands as penalty, discretionary and parameter nodes (#64) #568), then compiler: basic biblatex — \addbibresource, \printbibliography, \textcite/\parencite (numeric) #558
  7. math-layout: math-layout: TeX's math font kerns (make_ord §752) from TFM lig/kern programs; render-pipeline forwarding behind math-font-kerns #560math-layout: math ligatures in make_ord and kerns/ligatures inside math text runs; pipeline forwarding behind math-font-kerns (stacked on #560) #589 (stacked on math-layout: TeX's math font kerns (make_ord §752) from TFM lig/kern programs; render-pipeline forwarding behind math-font-kerns #560)
  8. render-pipeline: tests: un-ignore a resolved render-pipeline test, refresh a stale HW2 ignore reason #524, render-pipeline: tabular lengths keep their group scope, \small survives a float caption, @{} glue (#56) #525, render-pipeline: paint \prime with Latin Modern Math's unraised ssty prime; pin #278 geometry oracle #528, render-pipeline: mathtools' \vcentcolon family sits on the math axis (#468) #530 (needs compiler: \coloneqq is \vcentcolon\mkern-1.2mu= under mathtools (#468; needs render-pipeline re-pin) #529), render-pipeline: long arrows are pdfTeX joins; heading spaces after \normalfont are medium (#66) #536, render-pipeline: per-chapter figure/table numbers, object-stream PDF sizing, float.sty [H] (#69) #537 + render-pipeline: \chapter in an \include'd file is a chapter (book-include 5 -> 7 pages) #538render-pipeline: floats numbered in \include reading order, LoF/LoT and \ref agree (stacked on #537 and #538) #578 (stacked on both), render-pipeline: \lstlistoflistings, report chapter gaps in LoF/LoT, lists after the last material #543, render-pipeline: pdfLaTeX's UTF-8 input errors, and no font kerns around OT1 accents #545,
    render-pipeline: a macro body's font declarations apply to its arguments (min2-preamble-switches 419 bp -> 0.005 bp) #547render-pipeline: macro-body text takes the body's fonts; declaration groups add no italic correction (stacked on #547) #556 (stacked on render-pipeline: a macro body's font declarations apply to its arguments (min2-preamble-switches 419 bp -> 0.005 bp) #547), render-pipeline: explicit glue keeps the space factor (min-hrulefill 0.554 bp -> 0.004 bp) #548, render-pipeline: a heading after an empty contents list takes its \addvspace; no page break between two heads #580, render-pipeline: \check@icl italic correction and nested-list closing skips (display-placement 35/36) #596, render-pipeline: math minus, asterisk and phi extract as the right Unicode #597
  9. docs: docs: user guide matches current main #531, docs: design proposal — incremental cross-references (for Commander decision) #575 (a design proposal awaiting a Commander decision; it merges cleanly, so merge or skip it)
  10. cli / protocol / mac: cli: did-you-mean suggestions in output, and flashtex check --fix [--dry-run] with symlink-safe atomic writes #362, render-pipeline: opt-in display-list-v2-diagnostics capability carries suggestion on the v2 wire #358mac: consume display-list-v2-diagnostics — v2 suggestions reach Problems and Tab-to-fix (after #358) #363 (stacked on render-pipeline: opt-in display-list-v2-diagnostics capability carries suggestion on the v2 wire #358)

Skipped (drafts blocked on a vendor re-pin): #569, #584 (stacked on #568/#569), #599 (stacked on #584),
#582 (stacked on #581), #585 (stacked on #470/#581).

Conflicts and resolutions

12 of the 43 merges conflicted, in 23 files.

Already merged Incoming PR File Resolution
#533 (via #527) #546 crates/pdf/tests/v2.rs Both appended tests at the end of the file; kept both (#527/#533's TikZ path/alpha tests, then #546's ellipsis ToUnicode test).
main + #470 #581 (20 commits behind main) crates/compiler/supported/coverage.md, docs/user/compiler.md (+ supported-latex.json, apps/mac bundled copy) Generated artefacts; took one side, then regenerated with sh crates/compiler/scripts/render_supported_latex.sh.
#526 #587 crates/compiler/src/parser.rs Both add TABLE_LENGTHS and an is_table_length dispatch arm. Kept one const (#526's doc comment) and #587's arm, which passes #587's new global flag to length_assignment; #526's comment moved onto it. The duplicate two-argument arm was dropped (it would not compile against #587's signature).
#601 #587 crates/tex-expansion/src/expand.rs Checkpoint gained last_origin (#601) and quad_sp/x_height_sp (#587). Kept all three fields in the struct, in checkpoint() and in restore().
#601 #587 crates/tex-expansion/src/incremental.rs Checkpoint shift: kept #601's steps offset and last_origin, plus #587's quad_sp/x_height_sp. cargo check --tests passes for tex-expansion and compiler.
#587 #568 crates/compiler/src/parser.rs parse_project_with's P { .. } initialiser: kept #587's length_scopes/pending_global and #568's parameters/parameter_scopes/hyphenation. cargo check --tests passes.
#581/#587 #568 docs/user/compiler.md, crates/compiler/supported/{coverage.md,supported-latex.json}, apps/mac bundled supported-latex.json Generated; regenerated with the script.
#568 (merged, regenerated) #583 docs/user/compiler.md Generated section only; regenerated with the script.
#535 (char_table) #558 crates/compiler/src/lib.rs Adjacent module declarations; kept mod char_table; and pub mod biblatex;.
#542 #558 crates/compiler/src/vocabulary.rs command_package: kept #542's listings/minted split (lstlistoflistings is listings, listoflistings is minted) and #558's citeyear (natbib) and cite/parencite/textcite/autocite/nocite (biblatex) arms.
#583 (merged, regenerated) #558 docs/user/compiler.md Generated section only; regenerated with the script.
#581 #560 crates/math-layout/src/layout.rs Engine::atom: took #560's signature (new text_font_pair argument, doc comment) and kept #581's left_scripts early return to make_sideset at the top. All call sites already pass four arguments. cargo check --tests passes.
#537 + #578 #543 crates/render-pipeline/src/{toc.rs,lib.rs,adapter.rs}, plus a follow-on edit in listings.rs This is the one semantic conflict. #537/#578 give each figure/table entry its own number (floats::number) and merge list entries in reading order (Labels::reading_order), and they dropped the adapter's chapter_starts. #543 numbers LoF/LoT/LoL entries inside list_blocks from chapter_starts, adds listing entries (listed, nolol) and adds \addvspace chapter gaps. Resolution: FloatEntry keeps both number and listed. lib.rs keeps #578's numbering and reading order, then extends with #543's listing_entries. adapter.rs keeps #543's chapter_starts/chapter_gaps and its list_blocks call, but now pushes them as reading positions (reading_position(cmd_doc, cmd.start)), because chapters can come from \included documents after #538/#578. In list_blocks, figures and tables use f.number (#578); only LoL entries are counted with chapter_numbers (#543), over reading positions; the chapter-gap check compares reading positions. listings::numbers takes the reading order so \thelstlisting agrees with the LoL; its two unit tests pass &[], where the offset fallback applies. cargo check --tests passes.
#556 #596 crates/render-pipeline/src/adapter.rs Adjacent edits. #556 dropped font_declaration's italic-correction flag (declaration groups add no \/); #596 inserted text_command_argument_at (`
main (watch --timing) #362 docs/user/compiler.md (hand-written CLI synopsis, outside the generated section) Union: check gains #362's --fix and --dry-run, and watch keeps main's --timing. The generated section was re-rendered with the script.
main (structured labels/notes/help folding in from_compiler) #358 (43 commits behind main) crates/render-pipeline/src/display.rs #358's side had no change in either hunk; it predates main's folding. Kept main's sources/message folding. #358's v2-diagnostics capability code auto-merged around it. cargo check --tests passes.

After the last merge, re-running sh crates/compiler/scripts/render_supported_latex.sh produced no diff,
so the generated artefacts match the combined compiler.

Combined test results

Run on the merged tip with CARGO_BUILD_JOBS=4. The crate tests used the debug profile with cargo test --locked --no-fail-fast;
render-pipeline used cargo test --release --locked --no-fail-fast.

Crate Passed Failed Ignored
compiler 747 1 7
tex-expansion 75 0 0
project-files 97 0 0
pdf 123 0 0
math-layout 74 0 0
render-pipeline (release) 439 1 3

Failures caused by the combination

  1. compiler, tests/robustness.rs::nested_sub_parses_hit_tex_grouping_capacity_instead_of_the_stack, debug only.
    thread '...' has overflowed its stack / fatal runtime error: stack overflow, aborting.
  2. render-pipeline, tests/math_symbols.rs::common_math_glyph_runs_use_semantic_unicode.

No other failures.

Vendor re-pins needed after merging

render-pipeline builds against the frozen mirrors in crates/render-pipeline/vendor/; see vendor/VENDORING.md.
These PRs change crates that are mirrored there. Their pipeline effect appears only after re-pinning.

Mirror PRs to re-pin past Blocked pipeline work
vendor/compiler #470, #517, #525, #526, #529, #535, #542, #558, #568, #577, #579, #581, #583, #587, #592, #601 (#524 only touches a compiler test) drafts #569, #584, #599 (need #568/#583); #582, #585 (need #470/#581); #530's \coloneqq composition needs #529
vendor/math-layout #470, #560, #581, #589 #560/#589's math-font-kerns feature stays off until then; #582, #585
vendor/tex-expansion #577, #587, #592, #601 none
vendor/pdf #527, #533, #546 none
vendor/project-files #518, #539 none
bibliography none; no merged PR touches crates/bibliography (#558's biblatex is in crates/compiler) none

Why

Main has not moved for hours and about 40 daniel-parent PRs are queued, many of them stacked. This branch shows the integration lane:

  • which order merges cleanly;
  • how each conflict resolves;
  • what the combined tree's tests say before any individual merge.

Overlap check

  • gh pr list --state open --search "integration preview": no other preview PR.
  • coord.py claims: only GH-INTEGRATION-PREVIEW (daniel-parent), this task.
  • The 48 open daniel-parent PRs were listed. Heads were verified against git ls-remote before merging, and all matched.

Test results

compiler (debug):          747 passed; 1 failed; 7 ignored   (robustness::nested_sub_parses_hit_tex_grouping_capacity_instead_of_the_stack: stack overflow; passes with --release)
tex-expansion:             75 passed; 0 failed
project-files:             97 passed; 0 failed
pdf:                       123 passed; 0 failed
math-layout:               74 passed; 0 failed
render-pipeline --release: 439 passed; 1 failed; 3 ignored  (math_symbols::common_math_glyph_runs_use_semantic_unicode: #536 x #597)

Not done

🤖 Generated with Claude Code

https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

d-q222 and others added 30 commits September 13, 2026 21:26
…art 2)

`flashtex check`/`build`/`watch` print each diagnostic as a header, a
`--> file:line:col` pointer, the source line(s) under a line-number
gutter, carets under the byte span and a wrapped `= recovery:` line.

- `--diagnostics=full|short|json`: full is the new form; short is the
  unchanged one-line `line_text`; json is `--json`. The default is full
  when stderr is a terminal and short otherwise, so piped output and
  scripts see exactly what they did before.
- `--color=auto|always|never`; auto is on for a terminal unless NO_COLOR.
- Uses only the fields a diagnostic already has (code, message, span,
  recovery). Structured labels/help from #277 part (1) can be added to
  the renderer without changing this path.
- Spans: tabs expanded so carets line up, multi-byte text, empty spans
  and spans past the end of the text get one caret, spans longer than
  four lines show the first two and the last.

Tests: 7 renderer unit tests, one end-to-end test for the flags. The
pre-existing multi_file_project_resolves_inputs_from_the_project_root
failure reproduces on unmodified main 9bd4b02 and is not touched here.

Implementation-Agent: daniel-parent (mac-m5pro-dq222)
Commit-Executor: git, direct
Co-authored-by: d-q222 <279808976+d-q222@users.noreply.github.com>
Decode optional code/suggestion/labels/notes/help on RuntimeV1.Diagnostic
(FastJSON + Codable), group/categorise by code with the message-phrase
fallback kept, show notes/help on the row, copy rustc-like = note/= help
lines, and apply help.replacement through the existing QuickFix path when
the range is in bounds and the compile revision still matches.

Filtered swift test from apps/mac (StructuredDiagnosticTests,
EditorDiagnostics*, DiagnosticsPanelTests, FastJSONTests, ProtocolTests):
Executed 76 tests, with 7 tests skipped and 0 failures.

Next: full `swift test` from apps/mac, then push this lane branch.

Open questions: none in-scope; help.replacement with an explicit other-file
path hides Fix… (QuickFix.otherDocument), matching today's explanation edits.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
Add display::Diagnostic.suggestion (None at every constructor) and tests
that expect unknown_command/\alpha and unsupported_feature. from_compiler
still hardcodes code "compiler" and does not copy suggestion.

Ran: CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-diagfwd cargo test --manifest-path crates/render-pipeline/Cargo.toml --lib from_compiler_forwards
Result: test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 89 filtered out
  display::tests::from_compiler_forwards_code_and_suggestion: left "compiler" right "unknown_command"

Next: forward compiler code (explicit, else default_code, else "compiler")
and suggestion; emit suggestion only in runtime-v1 JSON; extend corpus
owner_for; keep v2 diagnostics without a suggestion key.

Open questions: none in-scope. vendor/, apps, protocol schema untouched.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
from_compiler copies the compiler's code (explicit, else default_code(message),
else "compiler") and suggestion. runtime-v1 JSON emits suggestion only when
set; display-list-v2 diagnostics are unchanged except the code value.
owner_for accepts the compiler code set; tikz_pipeline rejects those codes
too so a forwarded unknown_command cannot slip through as "not compiler".

Ran (CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-diagfwd):
- --lib from_compiler_forwards -- writer_matches_value_tree: test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 88 filtered out
- --lib write_json_matches_the_value_tree: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 89 filtered out
- --test compiler_diagnostic_forward: test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- --test tikz_pipeline: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- python3 -m unittest tools.real-world-corpus.test_run: Ran 6 tests in 0.008s OK

Next: full cargo test --manifest-path crates/render-pipeline/Cargo.toml control-vs-after.

Open questions: no in-scope golden embeds "code":"compiler" for a compiler
diagnostic (vendor/pdf fixture and apps/mac display-list fixture are out of
lane). docs/user/compiler.md still shows the old example.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
Add protocol/proposals/display-list-v2-diagnostics.md: capability accepted
only with display-list-v2; suggestion/labels/notes/help use the runtime-v1
shapes with v2 #/$defs/source; omit-when-empty; deltas carry the same keys;
without the cap bytes stay identical. Schema delta is text in the proposal
(frozen rendering-v2.schema.json untouched). labels/notes/help wait for a
vendor re-pin past #346; this lane serialises suggestion only.

Ran: none (docs-only).

Next: failing tests for negotiated vs not, compact vs JSON-tree, and a
suggestion-only delta.

Open questions: none in-scope. No vendor/, schema, apps, or contracts edits.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
CAP_DIAGNOSTICS and Wire.diagnostics / Capabilities.diagnostics, negotiated
only with display-list-v2. Writers still emit the frozen four-key diagnostic,
so capability-on output is byte-identical to capability-off and a suggestion
change does not change the delta header digest.

Ran (CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-diagfwd):
cargo test --manifest-path crates/render-pipeline/Cargo.toml --lib
  diagnostics_capability_gates_suggestion -- suggestion_change_is_hashed --
  negotiation_accepts_only_known
Result: test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 89 filtered out
- negotiation_accepts_only_known_requested_capabilities: ok
- diagnostics_capability_gates_suggestion_on_the_wire: left == right (no suggestion key)
- suggestion_change_is_hashed_and_deltaed_only_when_serialised: header_digest equal with cap on

Next: emit suggestion from write_diagnostics / diagnostic_json when the cap
is on and the value is Some; hash it only then.

Open questions: none. labels/notes/help still wait for #346 re-pin.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
write_diagnostics and diagnostic_json_wire add suggestion after sources
when Wire.diagnostics is set and the value is Some (BTreeMap order; never
null). Deltas use the same writer. header_digest hashes suggestion only
then, so a suggestion-only change produces a delta. Frozen writers stay
four-key. No labels/notes/help slots (vendor compiler has none until #346).

Ran (CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-diagfwd):
- --lib diagnostics_capability_gates_suggestion -- suggestion_change_is_hashed
  -- write_json_matches: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 89 filtered out
- --lib writer_matches_value_tree -- from_compiler_forwards --
  negotiation_accepts: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 89 filtered out
- --test compiler_diagnostic_forward: test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Next: full cargo test --manifest-path crates/render-pipeline/Cargo.toml.

Open questions: none in-scope.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
…g-forward' into agent/daniel-parent/cli-fix

Stacking base for an overnight daniel-parent lane (#277); announced on #2.

Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…d-diagnostics' into agent/daniel-parent/mac-v2-diagnostics

Stacking base for an overnight daniel-parent lane (#277); announced on #2.

Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
Add six cli.rs tests that pin GH-277 acceptance: rustc-style help for
\alpah, JSON suggestion, check --fix rewrite, --dry-run diff, suggestion
in an \input file, and --help mentioning --fix/--dry-run. Overlap and
hash-changed refusal will land with the apply module (need synthetic
edits; the compiler does not emit overlapping spans for \alpah).

Control (CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-clifix, base 5b7a88b):
- unittests: test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- --test cli: test result: FAILED. 12 passed; 1 failed (multi_file_project_resolves_inputs_from_the_project_root, fixture-dependent / PR #308)

New tests (same target dir):
- typo_alpah_full_output_has_a_help_block: FAILED (no "= help: did you mean `\alpha`?")
- typo_alpah_json_includes_suggestion: FAILED (suggestion key absent, left None)
- check_fix_rewrites_alpah_to_alpha / dry_run / input file: FAILED (exit 2, unknown option "--fix")
- check_help_mentions_fix_and_dry_run: FAILED (--help has no --fix)

Next: copy suggestion onto the CLI Diagnostic next to code.clone(), then
render the help block / short parenthetical / JSON key, then check --fix.

Open questions: none in-scope. Overlap/hash/symlink tests belong in the
apply module; they cannot be driven from the compiler's \alpah diagnostic.

Implementation-Agent: cursor-agent cursor-grok-4.6-high-fast
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
Add display-list-v2-diagnostics.json (frozen four-key diagnostic plus one
with suggestion/labels/notes/help), Codable slots on RenderingV2.Diagnostic,
and tests for fast==slow, setLiveV2 negotiation, document-runtime allowlist,
and Fix… from suggestion. Slow JSONDecoder already fills the slots; the
fast reader still skips them, so decode() (fast-first) drops the fields.

Ran:
- apps/mac swift test --filter RenderingV2Tests.testDisplayListV2DiagnosticsFixtureKeepsStructuredFields|…|V2ImageTests.testPaneRequestsImagesAlongsideV2…|EditorDiagnosticsQuickFixTests.testSuggestionReplacementBoundsAndHelpWins|testHelpReplacementBoundsAndRevisionGate
  Executed 5 tests, with 18 failures (0 unexpected) in 0.252 seconds
  testHelpReplacementBoundsAndRevisionGate passed; the four new assertions fail
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-macv2 cargo test --manifest-path crates/document-runtime/Cargo.toml --lib diagnostics_capability_is_accepted
  test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 20 filtered out
  unknown accepted capability display-list-v2-diagnostics

Next: fast diagnostic() reads the new keys; setLiveV2 requests the cap
with images; document-runtime allowlist mirrors images; canApplyHelpReplacement
covers suggestion.

Open questions: Mac compile path is WorkerClient / preview-controller, not
document-runtime directly; the allowlist is still the documented reference.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
CLI Diagnostic copies pipeline suggestion next to code.clone(). Full
renderer prints `= help: did you mean \`\\alpha\`?` plus a `+` gutter over
the replaced line; short appends `(did you mean \alpha?)`; JSON emits
`suggestion` only when set. `check --fix [--dry-run]` applies non-
overlapping back-to-front byte edits, refuses changed files (SHA-256),
symlinks and paths outside the project root, writes atomically with mode
preserved, then re-checks. Overlap/hash/symlink/mode covered by fix.rs
unit tests (the compiler does not emit overlapping \alpah spans).

Ran (CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-clifix):
- --bin flashtex: test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- full cargo test --manifest-path crates/flashtex-cli/Cargo.toml:
  unittests ok. 21 passed; --test cli FAILED. 18 passed; 1 failed
  (multi_file_project_resolves_inputs_from_the_project_root — same
  pre-existing control failure; all six new GH-277 tests passed)

Next: no in-scope follow-up; lane is ready for daniel-parent review (no PR).

Open questions: none. Known #308 fixture failure left untouched.

Implementation-Agent: cursor-agent cursor-grok-4.6-high-fast
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
Fast and slow readers fill suggestion/labels/notes/help (v2 nested source
on help.replacement). setLiveV2 asks for display-list-v2-diagnostics next
to images. document-runtime accepts that cap only with display-list-v2.
Fix… uses suggestion over sources[0] when help.replacement is absent.
Delta header digest hashes suggestion when present (producer rule).

Ran:
- apps/mac swift test --filter RenderingV2Tests|V2ImageTests.testPaneRequestsImagesAlongsideV2…|EditorDiagnosticsQuickFixTests|LayoutCapabilityTests|DiagnosticsPanelTests
  Executed 41 tests, with 1 test skipped and 0 failures (0 unexpected) in 1.172 seconds
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-macv2 cargo test --manifest-path crates/document-runtime/Cargo.toml --lib capability_is_accepted
  test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 19 filtered out

Next: filtered RenderingV2*/V2*/EditorDiagnostics*/DiagnosticsPanel*/LayoutNegotiation* then full swift test and cargo test document-runtime.

Open questions: Mac talks WorkerClient and preview-controller; document-runtime is the helper allowlist, not the direct-worker path.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
…g it

Review fix on the GH-277-cli-fix lane: collect_edits used
`end_byte.unwrap_or(start)`, so a diagnostic with a start but no end (or an
empty span) became an insertion at `start`. A compiler suggestion replaces
its span, so both ends are now required and the span must be non-empty.

cargo test -p flashtex-cli: bin 22/0 (+1: a_suggestion_without_a_full_span_is_not_turned_into_an_insertion);
tests/cli 18 passed, 1 failed: multi_file_project_resolves_inputs_from_the_project_root,
the same fixture-dependent failure as on the base 5b7a88b (replaced by #308).

Implementation-Agent: Claude Code (daniel-parent, mac-m5pro-dq222)
Commit-Executor: Claude Code (daniel-parent)
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…no re-derived fallback

Adopts the semantics of GoKubar's duplicate #370: the compiler's constructors
already set `code` via `default_code`, and a `None` is deliberate (request
validation), so re-deriving `default_code(message)` here could label a
diagnostic the compiler's own runtime-v1 reply leaves uncoded. Now
`d.code.map_or("compiler", ...)`.

--lib from_compiler 1/0 (test updated: uncoded stays "compiler");
compiler_diagnostic_forward 2/0 (tikz still unsupported_feature, set by the
compiler's constructor); tikz_pipeline 3/0; golden_v1 1/0.

Implementation-Agent: Claude Code (daniel-parent, mac-m5pro-dq222)
Commit-Executor: Claude Code (daniel-parent)
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…s and set diagnostics: false in flashtex-render's Wire

Conflict in crates/render-pipeline/src/bin/flashtex-render.rs: main (#344) made
--v2 honour --images; this branch added the Wire.diagnostics field. Resolved
as Wire { images: self.images, device_color, diagnostics: false }.
lib 97/0, compiler_diagnostic_forward 2/0, display_list_delta 3/0, cli_e2e 4/0.

Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…c-v2-diagnostics (main + inventory sync + flashtex-render Wire resolution)

Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…c-v2-diagnostics (main + inventory sync + flashtex-render Wire resolution)

Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
What changed:
- Added compiler regression tests for nested math in tags/text, text styles, unsupported commands, tag glue, and eqref labels.
- Added math-layout width/style-size regression tests.

What was run:
- test result: FAIL (expected): CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-tag cargo test --manifest-path crates/compiler/Cargo.toml --test text_runs
- test result: FAIL (expected): CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-tag cargo test --manifest-path crates/math-layout/Cargo.toml --test text_runs

Next step:
- Add the minimal text-run IR and parser/layout support.

Implementation-Agent: codex gpt-5.6-luna (max)

Commit-Executor: codex (this session)

Lane-Owner: daniel-parent (mac-m5pro-dq222)
Merged origin/main (2cf635b). #303 and #354 are on main; this lane no
longer needs its stacked pipeline-diag-forward display.rs code.

Conflict resolution:
- crates/flashtex-cli/src/main.rs: kept --fix/--dry-run and main's
  version_string (#420), collapse_repeats, and watch screen-clear. The
  post-fix re-check also uses collapse_repeats.
- crates/flashtex-cli/src/report.rs (add/add): kept main's structure
  (collapse_repeats) and ported this lane's `= help:` / `+` gutter
  suggestion rendering. Fold equality also compares suggestion.
- crates/render-pipeline/src/display.rs: took main's file. #354 already
  forwards compiler `code` exactly and runtime-v1 `suggestion`. This
  lane's default_code re-derivation was dropped (CLI reads suggestion
  from the pipeline Diagnostic; it does not need that re-derivation).

--fix still has no ambiguity policy. None existed; PR #444 is the
unique-closest-match rule, so the CLI applies whatever suggestion the
compiler emitted.

compile.rs auto-merged: suggestion copy plus main's missing-include
dedup. Tests in tests/cli.rs stayed at the end of the file.

Ran:
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-clifix cargo test --manifest-path crates/flashtex-cli/Cargo.toml
  unittests: test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  tests/cli.rs: test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  (multi_file_project_resolves_inputs_from_the_project_root now passes; #308 landed on main)
- cargo test --manifest-path crates/render-pipeline/Cargo.toml --lib
  test result: ok. 110 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Next: push this merge to origin/agent/daniel-parent/cli-fix.

Open questions: none in-scope. Ambiguous suggestions are compiler-side (#444, still open).

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
…ment

Conflict in crates/render-pipeline/src/display.rs: one hunk, the
Diagnostic.suggestion doc-comment. Kept HEAD's capability-gated wording
(serialised on display-list-v2 only when Wire.diagnostics is set). Took
main's #354 from_compiler (exact compiler code, in-memory suggestion) as
the auto-merged base; Wire.diagnostics / diagnostic_json_wire /
write_diagnostics gating came through from this branch.

Ran: git fetch origin && git merge origin/main. Tests not yet run.

Next: add a negotiated-case integration test next to #354's
"v2 omits suggestion" test; run render-pipeline lib + diagnostic/delta
tests; confirm remaining diff vs origin/main is only the capability.

Open questions: none on the conflict. document-runtime allowlist is out
of scope (#363).

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
compiler_diagnostic_forward.rs: keep typo_alpah_is_unknown_command_with_alpha_suggestion_in_v1_only
(v2 omits suggestion when the cap is off). Add negotiated_v2_diagnostics_emits_suggestion
through handle_line: capability off omits suggestion; with display-list-v2 +
display-list-v2-diagnostics the sibling carries suggestion and echoes the cap;
the cap alone is rejected. Proposal status no longer says stacked on #354.

Ran (CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-diagfwd):
- cargo test --manifest-path crates/render-pipeline/Cargo.toml --lib
  test result: ok. 112 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- cargo test --manifest-path crates/render-pipeline/Cargo.toml --test compiler_diagnostic_forward --test display_list_delta
  compiler_diagnostic_forward: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
  display_list_delta: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- python unittest tests/test_rendering_v2.py: Ran 23 tests in 0.037s OK

Next: push this lane branch.

Open questions: none. document-runtime allowlist remains #363.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds the --fix safety matrix. Two tests fail on 6abfbec, as intended:

- a_symlinked_parent_directory_is_refused: with root/link -> root/src
  and diagnostic path link/main.tex, apply reports issues=1, skipped=[],
  and rewrites src/main.tex. resolve_writable only lstats the leaf and
  canonicalises the parent, so the temp+rename follows the directory
  symlink.
- a_span_on_a_utf8_multibyte_boundary_is_refused: start=1,end=3 on
  éé (c3 a9 c3 a9) with an empty replacement leaves a valid é;
  from_utf8 accepts it and the file is written.

The rest of the matrix already passes: leaf symlink, ../ and absolute
paths, CRLF preservation, back-to-front non-overlapping edits,
overlapping skips, dry-run directory listing, JSON omitting the
suggestion key (extracted diagnostic_json so the assertion is
unconditional).

Ran:
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-clifix cargo test --manifest-path crates/flashtex-cli/Cargo.toml --bin flashtex -- a_symlinked_parent_directory_is_refused a_span_on_a_utf8_multibyte_boundary_is_refused
  test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 29 filtered out
- same binary filter for the other new tests
  test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 24 filtered out

Next: walk every path component with symlink_metadata and refuse
non-char-boundary spans before splicing.

Open questions: none.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
resolve_writable walked only the leaf and canonicalised the parent, so a
directory symlink (root/link -> root/src, diagnostic link/main.tex) still
took the temp+rename to src/main.tex. It now lstats every component from
the project root to the file and returns Skip::Symlink if any is a
symlink. `..` and absolute paths are still rejected before the walk.
The atomic temp+rename stays in that verified real directory.

apply_edits_back_to_front also refuses a span whose start or end is not
a UTF-8 char boundary, even when the spliced bytes happen to be valid
UTF-8.

Ran:
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-clifix cargo test --manifest-path crates/flashtex-cli/Cargo.toml --bin flashtex -- a_symlinked_parent_directory_is_refused a_span_on_a_utf8_multibyte_boundary_is_refused a_symlink_target a_parent_escape crlf_bytes non_overlapping overlapping dry_run_creates json_omits a_file_changed
  test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 21 filtered out

The two tests that failed on 83211e8 now pass.

Next: git fetch origin && git merge origin/main, then an end-to-end
test that an ambiguous typo (#444: igl/lph/igm) gets no suggestion and
--fix writes nothing, while \\alpah is still fixed.

Open questions: CLI links render-pipeline/vendor/compiler, not
crates/compiler. If origin/main's #444 did not re-pin the vendor, the
e2e test is a vendor-re-pin (out of scope) and will be reported.

Implementation-Agent: cursor-agent cursor-grok-4.6
Commit-Executor: cursor-agent
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Co-authored-by: Cursor <cursoragent@cursor.com>
…-v2-diagnostics capability carries suggestion on the v2 wire)

Conflicts resolved; see docs/integration/daniel-parent-preview.md.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…tics — v2 suggestions reach Problems and Tab-to-fix (after #358))

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
What changed:
- docs/integration/daniel-parent-preview.md: recommended merge order for the
  43 merged daniel-parent PRs, the 16 conflict resolutions, combined test
  results, the two failures caused by the combination, and the vendor re-pins.

What was run:
- compiler (debug): 747 passed, 1 failed (robustness stack overflow, debug only; passes in --release), 7 ignored
- tex-expansion: 75 passed; project-files: 97 passed; pdf: 123 passed; math-layout: 74 passed
- render-pipeline --release: 439 passed, 1 failed (math_symbols common_math_glyph_runs_use_semantic_unicode, #536 x #597), 3 ignored

Next step: the integration lane merges the individual PRs in the recorded order.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
What changed:
- `\addbibresource` and `\printbibliography` move out of `P::command` into
  `#[inline(never)]` helpers (`add_bib_resource`, `print_bibliography`);
  `biblatex_cite` is `#[inline(never)]` too.
- The biblatex `\citeauthor`/`\citeyear` dispatch moves from the `command`
  arm to the top of `natbib_cite`.
- Why: `P::command` is on the stack once per nested sub-parse. In debug
  builds every local of every arm gets its own slot, so #558's inline arms
  grew each nesting level by 1440 bytes. Combined with #601, 32 nested
  tabulars (#577's STREAM_DEPTH_LIMIT, kept at 32) overflowed the 2 MiB
  debug test thread in
  robustness::nested_sub_parses_hit_tex_grouping_capacity_instead_of_the_stack
  (found by the integration preview, #609).

What was run:
- Stack per nesting level (debug), measured with a scratch probe that prints
  the stack address in `parse_stream` at each depth (not committed):
  | construct | #601 | #601+#558 before | #601+#558 after |
  | tabular   | 57232 | 58672 | 57360 |
  | footnote  | 31424 | 32864 | 31552 |
  | colorbox  | 30928 | 32368 | 31056 |
  | rotatebox | 32048 | 33488 | 32176 |
  | uline     | 30336 | 31776 | 30464 |
- #601 + #558 (merge), debug, CARGO_BUILD_JOBS=4:
  cargo test --test robustness --test biblatex
  biblatex: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  robustness: test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 41.06s
- This branch, debug:
  biblatex: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  robustness: test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 40.81s
  natbib: test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  --lib cite: test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 331 filtered out; finished in 0.00s

Next step: merge into the integration preview (#609) and rerun there.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…er arms out of the recursive command frame)

What changed:
- Merges agent/daniel-parent/biblatex-basic at 16dd331 (no rebase, no conflicts).

What was run (this merge, CARGO_BUILD_JOBS=4):
- crates/compiler, debug: cargo test --locked --test robustness --test biblatex
  biblatex: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  robustness: test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 42.11s
- crates/render-pipeline, release: cargo test --release --locked --test math_symbols --test long_arrows
  long_arrows: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
  math_symbols: test result: FAILED. 15 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.05s
  (common_math_glyph_runs_use_semantic_unicode, the #536/#597 conflict, still open)

Next step: the #536/#597 long-arrow extraction decision.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…g-arrow blocker

What changed:
- Failure 1 (debug stack overflow): fixed on #558 (16dd331), with the measured per-level stack before/after, and a margin warning. This preview's frame is 65136 bytes per nested tabular.
- Failure 2 (#536/#597 long arrows): why the decided fix is blocked as specified. Empty-text clusters are invalid in display-list-v2, the join's pieces are in different runs, and the exact PDF route's per-glyph-id ToUnicode made real `=`/`−`/`→` extract as the arrow in a prototype.

What was run:
- Documentation only; the test lines are in the preceding merge commit.

Next step: the long-arrow decision from daniel-parent.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
@d-q222

d-q222 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

daniel-parent subagent (claude-opus-5): follow-up on the two combination failures.

1. Debug stack overflow: fixed on #558, merged here

Preview tip, debug:

biblatex:   test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
robustness: test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 42.11s

Margin warning: the test now passes, but only just.

2. Long-arrow extraction (#536 / #597): not implemented, blocked as specified

Preview tip, release:

long_arrows:  test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
math_symbols: test result: FAILED. 15 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.05s
  left:  "−∗∗∘⋅∖\u{338}=\u{338}∈↦=⇒−→↪ϕφμΔΩ∑∏∫∮⋅⋅⋅"
  right: "−∗∗∘⋅∖\u{338}=\u{338}∈↦⟹⟶↪ϕφμΔΩ∑∏∫∮⋅⋅⋅"

See the comment on #536 for why the decided fix cannot ship as specified, and the options.

@d-q222

d-q222 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

daniel-parent decisions on the preview's two combination failures:

  1. Debug stack overflow: fixed in compiler: basic biblatex — \addbibresource, \printbibliography, \textcite/\parencite (numeric) #558 (16dd331f) and merged into the preview. Headroom is thin (about 2.0MiB of 2MiB at 32 nested tabulars), so new lane GH-PARSER-STACK-FRAME will cut the parser's recursive frame at least 2×.
  2. Long-arrow extraction: option C. render-pipeline: math minus, asterisk and phi extract as the right Unicode #597's test will expect =⇒ / −→, which matches pdfTeX's own extraction for the two-piece arrows. Single-character extraction would need PDF /ActualText in crates/pdf, a possible later enhancement that isn't blocking.

What changed:
- The long arrows move out of common_math_glyph_runs_use_semantic_unicode
  into long_arrows_extract_as_their_drawn_pieces (option C from #536).
- A two-glyph join (#536's drawing) must extract as `=⇒` / `−→`, matching
  pdfTeX's own PDF (`x =⇒y −→z`, pdflatex + PyMuPDF). While main still draws
  one Latin Modern Math glyph, the text is the single `⟹` / `⟶`.
- A comment records that one `⟹` for two drawn glyphs needs PDF /ActualText
  marked content, a future crates/pdf change outside this PR.

What was run:
- math_symbols (debug): test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s

Next step:
- Merge into agent/daniel-parent/integration-preview and rerun math_symbols there.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…xtract as their drawn join pieces)

What changed:
- Merged origin/agent/daniel-parent/pdf-math-tounicode (5bf6d57) with --no-ff; no conflicts.
- Notes: failure 2 (math_symbols long arrows) is recorded as resolved by option C.

What was run:
- math_symbols (debug): test result: ok. 17 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.41s

Next step:
- GH-PARSER-STACK-FRAME for the debug stack margin warning.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
What changed:
- tools/visual-oracle/rank.py `norm` folds U+2212 MINUS SIGN into U+002D
  before alignment. pdfTeX's own PDFs extract a math minus as U+2212
  (PyMuPDF on pdflatex's 19-vmatrix.pdf: "= ad −bc"; 28-xrightarrow.pdf:
  "f−→B", "h←−"), which is what this branch now emits. The pinned references
  read "-" only because pdftext's glyph-name table maps `minus` to "-". The
  references and FlashTeX are both right; the word matcher was not.
- amsmath_corpus/oracle.py `regroup` orders coincident glyphs (the \relbar
  and arrow head of \xrightarrow) with the same fold, so `−→` sorts like the
  references' `-→`.
- test_rank.py: a minus sign aligns with a hyphen-minus.

What was run:
- amsmath oracle, this branch's flashtex-render: TOTAL 58/59 after the norm
  fold alone (28-xrightarrow order tie), TOTAL 59/59 within 0.5 bp with both
- amsmath oracle, main's flashtex-render (36fe7ec): TOTAL 59/59 within 0.5 bp
- amssymb oracle, main and this branch: TOTAL 38/39 within 0.5 bp (both;
  32-braces-narrow fails identically on main)
- tabular oracle, main and this branch: TOTAL 106/106 (words 0.5 bp, rules 0.1 bp)
- display-placement oracle, main and this branch: TOTAL 33/36 within 0.5 bp (identical rows)
- python3 -m unittest discover -s tools/visual-oracle -p 'test_*.py': Ran 14 tests, OK

Next step: merge into the integration preview (#609) and rerun there.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
What changed:
- rank.v2_words sliced the glyph run's Python str with the display list's
  cluster ranges, which are UTF-8 byte offsets. After the first non-ASCII
  character in a run (`∈`, and now this branch's U+2212 minus) every later
  glyph got its neighbour's text: `|f(x_k) − f(y_k)|` read as words `−f`,
  `(y` instead of `−`, `f(y`, and `x ∈ K` left an empty-text word. The
  sweep-2 "apparent regressions with identical pixels" (inline-math p1/p2,
  math-sheet p2, hw1 p1, conf-paper p2) were this. The amsmath oracle's own
  reader already encoded before slicing.
- test_rank.py: the v2 fixture helper writes byte offsets; a run with
  `−` and `∈` keeps its word text.

What was run (tools/visual-oracle/rank.py, all 22 real-world fixtures, same
flashtex-pdf-exact; aligned words summed over every page):
- old rank.py: main 36fe7ec 15285, preview 5d1eb06 15253
- this rank.py: main 15330, preview 15326
- pages whose aligned/within-0.5 counts differed main vs preview only
  through text (conf-paper p2, hw1 p1, inline-math p1/p2, math-sheet p1/p2,
  ps-calculus p2/p3, twelvept-plain p2, lecture-notes p2) are now identical
- amsmath 59/59, amssymb 38/39, tabular 106/106, display-placement 33/36
  (main) and 35/36 (preview): unchanged by this commit
- python3 -m unittest discover -s tools/visual-oracle -p 'test_*.py': OK

Next step: merge into the integration preview (#609).

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…p-2 regression verdicts

What changed:
- Merged origin/agent/daniel-parent/pdf-math-tounicode (d999108) with --no-ff; no conflicts.
  b283587: rank.norm folds U+2212 into "-", amsmath regroup orders with the same fold.
  d999108: rank.v2_words slices cluster byte ranges as bytes.
- Notes: new "Corpus sweep 2 regressions" section. Per regression: the cause PR found by
  rebuilding flashtex-render at each render-pipeline merge, the verdict against pdflatex's
  positions, and the oracle results. Also repaired the #556/#596 conflict-table row, which
  had swallowed the #536/#597 row.

What was run (this tip; render-pipeline unchanged by the merge, flashtex-render built at 5d1eb06):
- amsmath: TOTAL 59/59 within 0.5 bp
- amssymb: TOTAL 38/39 within 0.5 bp
- tabular: TOTAL 128/128 (words 0.5 bp, rules 0.1 bp)
- display-placement: TOTAL 35/36 within 0.5 bp
- tools/visual-oracle unit tests: Ran 15 tests, OK
- rank.py real-world: aligned words 15326 (15253 with the old rank.py)

Next step: comment the verdicts on #597, #596, #536 and #547.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
@d-q222

d-q222 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Sweep-2 regressions resolved (GH-PREVIEW-REGRESSIONS). Preview tip is now da03ac94.

Method: rebuilt flashtex-render at every render-pipeline merge on the first parent, then ran rank.py with the same flashtex-pdf-exact.

Regression Cause Verdict Fix
amsmath 59/59 → 57/59 #597 Oracle bug: pdflatex's PDF extracts U+2212 too #597 b2835879 + d9991088 (rank.py str/byte slicing bug), merged
enumitem-worksheet p2 +14710 px #596 Closer to pdflatex: exact once description[style=nextline] is out of the picture none
enumitem-worksheet p1 +1164 px #536 Closer to pdflatex: \implies join exact; a pre-existing +1.17 bp list line shift remains none
ps-calculus p1 +747 px #536 Closer to pdflatex: \longrightarrow join exact; a pre-existing +1.81 bp quad/text gap remains none
cv p1 +1350 px #547 Closer to pdflatex: x exact; pre-existing \\[-6pt] y error (re-pin) none

Oracles on da03ac94:

  • amsmath TOTAL 59/59 within 0.5 bp
  • amssymb TOTAL 38/39 (same as main)
  • tabular TOTAL 128/128
  • display-placement TOTAL 35/36
  • visual-oracle unit tests Ran 15 tests, OK
  • rank.py real-world, aligned words: 15326 (main 15330 with the same rank.py)

Details and evidence are in docs/integration/daniel-parent-preview.md, section "Corpus sweep 2 regressions".

(daniel-parent subagent, GH-PREVIEW-REGRESSIONS)

🤖 Generated with Claude Code

https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

GoKubar added a commit that referenced this pull request Sep 15, 2026
* compiler: implement \thepage via the \pageref late-binding path

\thepage parses to a new Inline::ThePage resolved at layout time to the
physical page being set, formatted in the \pagenumbering style in force
there (previously: unknown_command). \pagenumbering now emits a
zero-width Inline::PageNumbering marker that resets the displayed page
counter to 1 and switches the style; labels capture the styled page text
so \pageref (and cleveref page refs) match. Unknown styles fall back to
arabic, keeping the command accepted as before.

Implementation-Agent: muse-spark-1.3-contributor
Commit-Executor: muse-spark-1.3-contributor

* compiler: support \captionof{figure/table}[short]{text} outside floats

caption.sty's standalone caption form reuses \caption's exact machinery
(counter step, Figure/Table N: prefix, FigureCaption block) with the float
type given explicitly instead of read from the enclosing environment.
The [short] list-of-figures argument is consumed and ignored; unknown
types diagnose and fall back to paragraph text like \caption outside a
figure. Inventory entry plus regenerated supported-LaTeX artifacts.

Implementation-Agent: muse-spark-1.3-contributor

* compiler: parse \marginpar and set the note in the right margin (#505)

\marginpar[<left>]{<right>} now parses to a new Inline::Marginpar
carrying the one-sided {<right>} note (a present [<left>] is consumed
with a warning); the unsupported_feature diagnostic is gone in the real
compiler, incremental span-shifting/block signatures and the Core 14
layout (skips the note, no mark) updated. Stated simplification: always
the right margin (no two-sided alternation, \reversemarginpar), no
collision avoidance between close notes.

Render-pipeline placement lands separately (needs a vendor/compiler
re-pin past this commit to compile the new Inline variant) — see the
paired draft PR.

Implementation-Agent: muse-spark-1.3-contributor
Commit-Executor: daniel-muse-lead-2 (Claude Sonnet Muse supervisor; split
compiler-only files from muse/marginpar 3473c632/9c819976 for the
compiler-PR-first/pipeline-draft-second pattern)
Co-authored-by: d-q222 <279808976+d-q222@users.noreply.github.com>

* compiler: add basic biblatex support

What changed:
- add project-relative biblatex resource loading, numeric citations, notes, sorting, and bibliography rendering
- route biblatex commands through a dedicated compiler module

What was run:
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-biblatex cargo test --manifest-path crates/compiler/Cargo.toml --test biblatex -- --nocapture: 7 passed

Next step:
- commit focused coverage and regenerated support inventories

Lane-Owner: daniel-parent (mac-m5pro-dq222)
Implementation-Agent: codex gpt-5.6-luna (max)
Commit-Executor: codex (this session); trailers added by daniel-parent
Lane-Owner: daniel-parent (mac-m5pro-dq222)

* compiler: cover biblatex support

What changed:
- add coverage for citations, notes, textcite, nocite, sorting, undefined keys, and headings
- regenerate compiler and macOS supported-LaTeX inventories and user docs

What was run:
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-biblatex cargo test --manifest-path crates/compiler/Cargo.toml --test biblatex -- --nocapture: test result: ok. 7 passed; 0 failed
- sh crates/compiler/scripts/render_supported_latex.sh: completed

Next step:
- run the final full compiler test suite

Lane-Owner: daniel-parent (mac-m5pro-dq222)
Implementation-Agent: codex gpt-5.6-luna (max)
Commit-Executor: codex (this session); trailers added by daniel-parent
Lane-Owner: daniel-parent (mac-m5pro-dq222)

* compiler: route eqnarray/eqnarray* through multirow displays (#520)

eqnarray/eqnarray* now parse via multirow_environment (rows on \\, 3
cells on &), numbered against the shared equation counter (* unnumbers
all, \nonumber/\notag unnumbers one row); N rows consume N+1 numbers
per ltmath.dtx. Both envs moved from the unimplemented to the
implemented inventory.

Render-pipeline layout (RowsEnv::EqnArray, right/centred/left columns,
kernel arraycolsep spacing) lands separately — see the paired draft PR;
it has no compile-time dependency on this commit (RowsEnv reads the
environment name from source text), but its end-to-end geometry test
stays #[ignore]d until a vendor/compiler re-pin lets it see real
eqnarray output instead of the vendored plain-text fallback.

Implementation-Agent: muse-spark-1.3-contributor
Commit-Executor: daniel-muse-lead-2 (Claude Sonnet Muse supervisor; split
compiler-only files from muse/eqnarray for the compiler-PR-first/
pipeline-draft-second pattern)
Co-authored-by: d-q222 <279808976+d-q222@users.noreply.github.com>

* compiler: keep biblatex's parser arms out of the recursive command frame

What changed:
- `\addbibresource` and `\printbibliography` move out of `P::command` into
  `#[inline(never)]` helpers (`add_bib_resource`, `print_bibliography`);
  `biblatex_cite` is `#[inline(never)]` too.
- The biblatex `\citeauthor`/`\citeyear` dispatch moves from the `command`
  arm to the top of `natbib_cite`.
- Why: `P::command` is on the stack once per nested sub-parse. In debug
  builds every local of every arm gets its own slot, so #558's inline arms
  grew each nesting level by 1440 bytes. Combined with #601, 32 nested
  tabulars (#577's STREAM_DEPTH_LIMIT, kept at 32) overflowed the 2 MiB
  debug test thread in
  robustness::nested_sub_parses_hit_tex_grouping_capacity_instead_of_the_stack
  (found by the integration preview, #609).

What was run:
- Stack per nesting level (debug), measured with a scratch probe that prints
  the stack address in `parse_stream` at each depth (not committed):
  | construct | #601 | #601+#558 before | #601+#558 after |
  | tabular   | 57232 | 58672 | 57360 |
  | footnote  | 31424 | 32864 | 31552 |
  | colorbox  | 30928 | 32368 | 31056 |
  | rotatebox | 32048 | 33488 | 32176 |
  | uline     | 30336 | 31776 | 30464 |
- #601 + #558 (merge), debug, CARGO_BUILD_JOBS=4:
  cargo test --test robustness --test biblatex
  biblatex: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  robustness: test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 41.06s
- This branch, debug:
  biblatex: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  robustness: test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 40.81s
  natbib: test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  --lib cite: test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 331 filtered out; finished in 0.00s

Next step: merge into the integration preview (#609) and rerun there.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* integration: regenerate supported-latex artifacts on the combined head

crates/compiler/scripts/render_supported_latex.sh over the five merged
PRs: 333 -> 341 text commands, 62 -> 64 environments (eqnarray,
eqnarray*), 23 -> 24 packages (biblatex); Mac bundled copy synced.

Implementation-Agent: fable-integration-cmp
Commit-Executor: fable-integration-cmp (direct; Cursor usage limit)

* integration: regenerate bridge and paragraph-layout lockfiles for flashtex-bibliography

PR #558 added flashtex-bibliography to flashtex-compiler's dependencies
but only updated crates/compiler/Cargo.lock. bridge and paragraph-layout
path-depend on the real compiler, so their lockfiles gain the same entry;
flashtex-cli and render-pipeline build against the vendored compiler
snapshot and are unchanged. Verified CI-style with
cargo build --release --locked for all five crates.

Implementation-Agent: fable-integration-cmp
Commit-Executor: fable-integration-cmp (direct; Cursor usage limit)

---------

Co-authored-by: muse-spark-1.3-contributor <muse-contributor@flashtex.invalid>
Co-authored-by: d-q222 <279808976+d-q222@users.noreply.github.com>
GoKubar added a commit that referenced this pull request Sep 15, 2026
… (#646)

* render-pipeline: tabular lengths keep their group scope; \small survives a float caption; @{} glue (#56)

What changed:
- Table lengths (\tabcolsep, \arrayrulewidth, \doublerulesep, \extrarowheight,
  and longtable's) are read as seen at the table: only assignments whose
  group ({...}, \begingroup, an environment) is still open count, in every
  form (\setlength{\x}{v}, \setlength\x{v}, \addtolength, \x=v). The old
  scan took the last brace-form \setlength anywhere in the file.
- A float-level size declaration (\small before \caption) is carried into
  later content runs, as \@caption's \normalsize is inside its own group.
- @{...} material keeps glue at both ends (@{\hspace{1em}}, @{\quad}).
- \hspace{<n>em} is set as <n> quads of the current font, not the
  compiler's fixed-size em.
- 17 new tabular corpus fixtures (107-123) with pdflatex references.

What was run:
- oracle.py check: main 117/123, this branch 123/123 (words 0.5 bp, rules 0.1 bp)
- cargo test --release (render-pipeline): 79 test binaries ok, 0 failed
- tabular_oracle: test result: ok. 1 passed; 0 failed
- lib: test result: ok. 140 passed; 0 failed; 2 ignored

Next step: compiler PR accepting table length assignments without the
"not implemented here" warning / unknown \tabcolsep error.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: report/book number figures and tables within the chapter

What changed:
- floats::number follows report.cls/book.cls: \thefigure and \thetable are
  \ifnum\c@chapter>\z@\thechapter.\fi\@Arabic\c@figure, reset by
  \@addtoreset{figure}{chapter}. \chapter* and book's \chapter outside
  \mainmatter step nothing; \appendix zeroes the chapter counter and makes
  \thechapter \@alph. Captions and \ref both use the value.
- FloatSpec.number is the printed string ("1.2"), not a bare counter.
- New pdflatex-pinned fixtures in fixtures/float-numbering (report chapters
  with \chapter*, report appendix, book front/main/back matter, an article
  control) and tests/float_numbering_oracle.rs: caption label, page and
  position within 1 bp, and the resolved \ref line.

What was run:
- cargo test --test float_numbering_oracle: test result: ok. 1 passed; 0 failed
- same test on origin/main sources: 17 mismatches (e.g. "Figure 1:" for
  pdflatex's "Figure 1.1:", chapter two's first figure printed "Figure 3")
- cargo test --test floats_oracle: ok. 2 passed; float_body_oracle: ok. 1
  passed; float_notes_oracle: ok. 1 passed; toc_oracle: ok. 1 passed;
  footnotes_oracle: ok. 1 passed; --lib floats: ok. 4 passed

Next step: open the GH-69 PR with the object-stream PDF sizing commit.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: size PDF graphics whose page tree is in object streams

What changed:
- graphics::probe_pdf falls back to flashtex_pdf::reader when the byte scan
  fails on a PDF with /ObjStm (PDF 1.5 object streams, pdfTeX's default
  \pdfobjcompresslevel 2). Before, every such PDF was image_unavailable and
  took no space. CropBox/MediaBox/Rotate handling is shared (page_info).
- New fixtures/float-graphics: a 72 dpi PNG and two pdfTeX object-stream
  PDFs (one /Rotate 90), made by make_fixtures.py; oracle.py pins box size and
  position from pdflatex. tests/float_graphics_oracle.rs checks
  width=\linewidth, scale, keepaspectratio, angle before/after a size key,
  \textwidth multiples, and the PDFs, within 1 bp, with no image diagnostic.
- Unit test graphics::tests::pdf_object_streams_are_read.

What was run:
- cargo test --test float_graphics_oracle: test result: ok. 1 passed; 0 failed
- same test without the graphics.rs change: 7 mismatches
- cargo test --lib graphics: test result: ok. 8 passed; 0 failed

Next step: [H] from the float package, then the PR.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: float.sty [H] sets the box exactly where it stands

What changed:
- FloatSpec::exact_here: with \usepackage{float}, a vertical-mode [H]
  float is not a float (\@float@HH). paginate splices \float@endH's
  \vskip\intextsep, the box and \vskip\intextsep at its marker, with TeX's
  interline glue (\lineskip against the line above; the next line's glue
  against the box's zero depth). It is never deferred or counted.
- [H] in the middle of a paragraph or on a two-column figure* now has its own
  diagnostic and falls back to h. [H] without the package keeps its old one.
- packages: float is implemented by the pipeline. \floatstyle, \restylefloat
  and \newfloat are still diagnosed by the compiler.
- fixtures/floats: make_fixtures.py adds 11-float-h and
  12-h-passes-deferred. oracle.py takes fixture names, so only those two
  were pinned; the other 10 sources and references are byte-identical.
  floats_oracle expects 12 fixtures, plus a diagnostics test for [H].

What was run:
- cargo test --test floats_oracle: test result: ok. 3 passed; 0 failed
  (11-float-h 2/2 pages, 13 anchors, max 0.002 bp; 12-h-passes-deferred
  3/3 pages, 9 anchors, max 0.003 bp)
- same fixtures on the pre-change sources: 26 mismatches
- cargo test --lib: test result: ok. 138 passed; 0 failed; 2 ignored
- float_body_oracle, float_notes_oracle, footnotes_oracle, toc_oracle,
  multicol_oracle, twocolumn_title_oracle: ok, 1 passed each;
  graphics_draft_demo: ok. 3 passed; float_numbering_oracle: ok. 1 passed;
  float_graphics_oracle: ok. 1 passed

Next step: open the GH-69 PR and post the verification table on #69.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: \chapter in an \include'd file is a chapter

What changed:
The adapter read structural body commands (\chapter, \part, \appendix,
book matter, marks/page styles, \addcontentsline) from the entry
document's source only. A \chapter in chapters/one.tex was set as a plain
paragraph on the current page: no page clear, no number, no marks, no
contents line. Included documents now get the same scan; their commands
are laid out before that document's next unit, and any left after its
last unit are laid out when the entry document resumes. \maketitle,
\noindent, contents lists and nested \input stay entry-only.

Corpus extended/project-book-include (#42): 5 pages -> 7 (pdflatex 7).

What was run:
cargo test --release (render-pipeline): passed 377 failed 0 ignored 3
test result: ok. 2 passed; 0 failed (chapter_in_included_file)
test result: ok. 1 passed; 0 failed (input_order_oracle)
test result: ok. 1 passed; 0 failed (toc_oracle)
test result: ok. 20 passed; 0 failed (flashtex-cli cli)

Next step:
\include's own \clearpage before/after (article: pdflatex 3 pages, we
set 1) and the missing CONTENTS running head in book front matter are
separate follow-ups.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* pdf: an ellipsis's periods no longer make every period extract as "…"

What changed:
- v2 export reduced cluster text to ToUnicode by first use. The pipeline sets
  `\textellipsis` as three periods in one cluster whose text is "…", so the
  period glyph was mapped to U+2026 for the whole font: the ellipsis
  extracted as "………" and every later sentence-final period as "…"
  (measured on "em dash… ellipsis." in OT1 and T1+lmodern documents).
- Only a one-glyph cluster now names its glyph's text. A glyph seen only
  inside clusters of several glyphs takes the character the font's own cmap
  maps to it (new `TrueTypeFont::char_for_glyph`, lowest code point), and the
  cluster text only when the cmap has none. Ligatures (`fi`, one glyph)
  still extract as their cluster text.
- tests/v2.rs: `ellipsis_periods_keep_the_period_in_to_unicode` (fails on
  main with `{28: "a", 88: "…"}`).

What was run:
- cargo test --release --test v2
  test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- cargo test --release (crates/pdf, whole crate): 118 passed, 0 failed
- flashtex build --v2 + flashtex-pdf-exact from-v2 on the survey documents
  d4-punct-ot1 / d5-punct-t1-lm: "em dash……… ellipsis…" -> "em dash... ellipsis."

Next step:
- Re-pin crates/render-pipeline/vendor/pdf so `flashtex build` and Export
  PDF pick it up (vendor re-pins are the integration lane's).

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* math-layout: TeX's math font kerns (make_ord, tex.web §752) from the TFM lig/kern programs

What changed:
- tools/gen_cm_tfm.py emits each font's lig/kern program (lig_kern_starts,
  lig_kern, kerns) read from the real TFMs; cm_tfm.rs and ams_tfm.rs
  regenerated (additions only; existing char rows unchanged).
- tfm.rs: TfmFont::lig_kern walks the program as TeX does (first matching
  instruction; a ligature shadows a later kern).
- MathFontMetrics::ord_pair (default None) + CmMathMetrics impl: same-family
  check via symbol_slot, the scaled kern, and whether the font is a text font.
- layout.rs: make_ord appends the kern after an unscripted Ord character
  followed by an Ord..Punct character of the same family, and drops the
  italic correction in a text font (§755). Bins demoted by Rule 6 are not kerned.

What was run:
- cargo test --test math_font_kerns: test result: ok. 2 passed; 0 failed
- cargo test (math-layout): all suites ok (12, 4, 5, 28, 4, 2, 11 passed; 0 failed)

Next step: feature-gated forwarding in render-pipeline's TexMathMetrics, then HW1 and amsmath corpus measurement.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: an em is the quad of the font in force; table lengths follow macro invocations (#525 review)

What changed:
- `\hspace{<n>em}`, `\quad` and `\qquad` in running text are now set in
  quads of the font current where they are read. `Item::Quad` carries a
  style: the series/family come from the source declarations at the
  command, the size from the compiler's sizes of the text around it
  (`glue_size`). Before, the quad was the paragraph's base font, so
  `{\Large Ff\hspace{2em}Gg}` was 8.165 bp short and `{\bfseries ..}`
  2.989 bp short.
- The blank after `\hspace{..}`'s closing brace is an ordinary space.
  It was being skipped as if it followed a control word (3.3-3.9 bp).
- Size environments (`\begin{small}..\end{small}`, all ten sizes) give
  text, rules, kerns and tables their size unless a declaration made
  inside is still in force. A paragraph whose `\par` is read inside one
  gets that size's `\baselineskip`. The compiler's "environment is not
  implemented" warning for them is superseded.
- `length_at` applies the `\setlength`/`\addtolength`/`\<len>=` a user
  macro makes at the top level of its body (nested macros too, `#k`
  from braced arguments) at the invocation, with the invocation's group
  scope. An assignment that cannot be read (optional argument, unbraced
  argument) is left out and reported as a `table_limitation` warning.
- Corpus fixtures 124-126 (`\hspace{em}`/`\quad` under
  `\small`/`\normalsize`/`\Large`, `\bfseries` and size environments, in
  body text and tables, at 10/11/12pt), 127 (size-environment
  paragraphs) and 128 (macro `\setlength`/`\addtolength` before tables),
  with pdflatex references. Unit tests:
  table_lengths_follow_macro_invocations,
  size_environments_give_their_size_until_a_declaration,
  glue_takes_the_size_of_the_text_it_is_read_with.

What was run:
- python3 oracle.py check --render .../flashtex-render: TOTAL 128/128 (words 0.5 bp, rules 0.1 bp)
- cargo test --release (crates/render-pipeline): 79 test binaries, all "test result: ok", 0 failed
- lib: test result: ok. 143 passed; 0 failed; 2 ignored
- tests/tabular_oracle.rs: tabular_oracle: 128/128 fixtures match pdflatex; test result: ok. 1 passed; 0 failed

Next step: re-review of #525.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: math-font-kerns feature forwards math-layout's ord_pair (off until re-pin)

What changed:
- Cargo.toml: new off-by-default `math-font-kerns` feature; render-pipeline
  depends on vendor/math-layout (not crates/math-layout), which predates
  MathFontMetrics::ord_pair.
- mathtex.rs: TexMathMetrics::ord_pair asks the embedded CM programs for
  families 1-3 and the installed rm-lmr TFM for family 0.
- mathtext.rs: TextRunMetrics forwards, never kerning a \text handle.
- tests/math_font_kerns.rs (feature-gated): glyph origins of $r,a,b$,
  $f,P,Y.$, $f(x)$ $\Gamma,\Delta$ $x_1,x_2$ $V_{a}$ and HW1's display
  against pdflatex within 0.02 bp.

What was run (scratch copy of crates/render-pipeline with vendor/math-layout
replaced by this branch's crates/math-layout, feature on; vendor/ untouched):
- cargo test --release --features math-font-kerns --test math_font_kerns: test result: ok. 1 passed; 0 failed
- same test with ord_pair forced to None: fails, $r,a,b$ glyph 1 +0.608 bp, $f,P,Y.$ glyph 5 +3.633 bp
- amsmath corpus oracle.py check: TOTAL 59/59 within 0.5 bp (before and after)
- rank.py --only hw1: page 2 within 0.5 bp 203 -> 212; the 9 kern words (lines 560.9/585.4) from 0.60-0.61 bp to <=0.005 bp; no other word moved by more than 0.005 bp
- cargo build --release --bin flashtex-render against vendor/ (feature off): ok

Next step: re-pin vendor/math-layout (integration lane), then make math-font-kerns a default feature.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* math-layout: keep the ord_pair imports and impl clear of #470's lines

What changed: separate use lines for MathChar/OrdPair and CmMathMetrics::ord_pair moved after accent_sizes, so this branch merges cleanly with #470 (tag-textrun). No behaviour change.

What was run:
- cargo test (math-layout): every suite test result: ok, 0 failed

Next step: open the PR.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: number floats in the order the \include tree is read

What changed:
- adapter::reading_order walks \input/\include from the entry file
  (compiler lookup: path, then path.tex; the preamble's last \includeonly;
  cycles and unknown files read nothing) into ordered byte ranges.
- floats::number steps chapter/appendix/matter and the figure/table
  counters over those ranges, so an entry-file float after an \include
  whose file has its own \chapter gets 2.1, not 1.2. A float of a file
  never read (\includeonly-excluded) has no number, label, spec or .lof
  line.
- LoF/LoT entries take the float's own number (report pre-chapter float
  is "1", not "0.1") and merge in reading order.
- Float markers in paginate are placed by reading order, so an entry
  float after an included chapter follows that chapter's blocks.
- \include issues its \clearpage before the file and after it.
- Fixtures 05 (report, two files), 06 (book, \include + \input +
  appendix include), 07 (report, \includeonly) with pdflatex references;
  oracle.py handles multi-file projects; tests/include_float_lists.rs pins
  LoF/LoT/\ref against pdflatex's .lof/.lot/.aux.

What was run:
cargo test --release --test float_numbering_oracle
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo test --release --test include_float_lists
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo test --release --test toc_oracle
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo test --release --test floats_oracle
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo test --release --lib reading_order
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 140 filtered out

Next step: full crate run, then PR stacked on #537 and #538.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* math-layout: math ligatures in make_ord and kerns/ligatures inside text runs (tex.web §752-§753)

What changed:
- `OrdPair` carries the pair's ligature instruction (`OrdLigature { op, ch }`);
  `CmMathMetrics::ord_pair` reports cmr's `=:` ligatures (ff, fi, fl, ffi,
  ffl, and the quote/dash/Spanish marks) as Unicode text characters, and
  `text_glyph` boxes those characters from their OT1 slots.
- `layout`: the first pass runs `make_ord` over the list before boxing. A
  ligature rewrites the list as §753 does: `=:` merges the pair (the ligature
  takes the right atom's scripts), `=:|`/`|=:` replace one side, `|=:|`
  inserts, and the pair is retried unless the op is a `>` form. So
  `$\mathrm{f}\mathrm{i}$` is one fi glyph and `$\mathrm{f}\mathrm{i}^2$` is fi^2.
- `make_text` (`Nucleus::Text`, the multi-character `\mathrm{..}` and
  operator runs) applies the same program between its characters: the font
  kerns (`AV`, `Tr`, `Wa`) and ligatures (`ff`, `ffi`), last italic kept.
- tests/math_font_kerns.rs: 19 new widths pinned to pdfTeX `\wd0`, plus a
  synthetic program exercising every ligature form on both paths.

What was run:
cargo test --manifest-path crates/math-layout/Cargo.toml --test math_font_kerns
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo test --manifest-path crates/math-layout/Cargo.toml
test result: ok. 12 / 4 / 5 / 28 / 4 / 3 / 2 / 11 passed; 0 failed
before this change the new widths fail, e.g. $\mathrm{AV}$ 15.13890pt vs pdfTeX 14.02777pt

Next step: forward ligatures through render-pipeline's TexMathMetrics and
one-character \mathrm groups; measure the pipeline against pdflatex.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: math ligatures and one-character alphabet kerns behind math-font-kerns

What changed:
- `TexMathMetrics::ord_pair` reads family 0's instruction straight from the
  installed rm-lmr TFM program (`Tfm::pair_program`, no text-mode boundary
  handling) and reports its ligatures, so `make_ord` forms them;
  `text_glyph` boxes a formed ligature (U+FB01 ...) from its OT1 slot.
- A one-character math alphabet pair of the same alphabet
  (`\mathbf{T}\mathbf{o}`, `\mathit{A}\mathit{V}`) is kerned from that
  alphabet's text-font TFM. Ligatures there are not formed (no character
  addresses a ligature slot of those fonts).
- `typeset`: a one-letter/digit `\mathrm{X}` is math-layout's `TextChar`
  (TeX §1186), so it joins its neighbour. All behind `math-font-kerns`.
- math-layout `cm::ligature_char` shared by both providers.
- tests/math_ligatures.rs pins glyph origins against pdflatex (lmodern).

What was run (scratch copy, vendor/math-layout := this branch's crate):
cargo test --release --features math-font-kerns --test math_ligatures
test result: ok. 1 passed; 0 failed
  negative control on #560's overlay: fails, "$x\mathrm{A}\mathrm{V}x$: glyph 2 at x 140.215 bp, pdflatex 139.003 (+1.212)"
cargo test --release --no-fail-fast --features math-font-kerns (whole crate): 377 passed; 0 failed; 3 ignored
oracle.py check: TOTAL 59/59 within 0.5 bp (before and after, per-fixture lines identical)
rank.py --only hw1: page 2 within 0.5 bp 212 of 215, pages 1 and 3 unchanged (same as #560)
worktree, feature off: cargo check --release --tests ok

Next step: open the PR stacked on #560.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* math-layout: keep the new layout.rs imports clear of #470's lines

What changed: the OrdLigature/SizeClass/Cow imports move to a block of their
own, so #560's import lines stay byte-identical and #470 still merges cleanly.

What was run:
git merge-tree --write-tree HEAD origin/agent/daniel-parent/tag-textrun: no conflict
cargo test --manifest-path crates/math-layout/Cargo.toml
test result: ok. 12 / 4 / 5 / 28 / 4 / 3 / 2 / 11 passed; 0 failed

Next step: open the PR stacked on #560.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: a chapter whose body is only a float still clears its page

What changed:
The adapter lays an \include'd file's structural commands (\chapter, ...)
out before that file's next unit (paragraph, heading, rule, picture). A
file whose whole body is a captioned float has no unit, so its \chapter
(heading, page clear, number) was never laid out and the floats piled onto
one page ahead of later text. Likewise a trailing entry \chapter with only a
float after it was dropped: the end-of-document loop handled only marks,
page styles and \maketitle.
- An entry \input/\include consumed without a unit of the file it reads
  (found from the reading order) lays that file's commands out in place,
  then \include's closing \clearpage.
- A unit of an included file flushes up to the command that reads its
  file, not merely the next \input/\include.
- Included files read so far are finished whenever the next file is read,
  not only when the entry file resumes.
- The unit loop takes one last pass after the last unit, so commands after
  it (\chapter, \include, marks, \maketitle) go through the same code; the
  separate trailing loop is gone.
tests/include_float_only_chapters.rs pins pages, lines and baselines
against pdflatex for two and three float-only \include'd chapters, the same
under \includeonly (with and without trailing text), and single-file
float-only chapters.

What was run:
cargo test --release --test include_float_only_chapters
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
same test without the adapter change: test result: FAILED. 0 passed; 3 failed
chapter_in_included_file: test result: ok. 2 passed; 0 failed
float_body_oracle: test result: ok. 1 passed; 0 failed
float_graphics_oracle: test result: ok. 1 passed; 0 failed
float_notes_oracle: test result: ok. 1 passed; 0 failed
float_numbering_oracle: test result: ok. 1 passed; 0 failed
floats_oracle: test result: ok. 3 passed; 0 failed
include_float_lists: test result: ok. 1 passed; 0 failed
input_order_oracle: test result: ok. 1 passed; 0 failed
toc_oracle: test result: ok. 1 passed; 0 failed
cargo test --release -p flashtex-render-pipeline --no-fail-fast: passed 386, failed 0, ignored 3

Next step: a float right after a body \clearpage (no \include) still lands
on the old page; that break is carried by the next block's eject and is
left to the #569/#583/#584 page-break stack.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: \check@icl italic correction and nested list closing skips (display-placement 35/36)

What changed:
- An upright text font command (\textbf, \textup, \texttt, \emph in italic
  text, ...) and amsmath's \eqref (\textup{\tagform@{..}}) put the italic
  correction of the character before them under the interword space
  (latex.ltx \check@icl -> \maybe@ic -> \sw@slant). Fixture 17's `(2.1).`
  was 0.784 bp left (the `f` of "Ref", kern 0.7922 pt).
- A list's closing \addvspace\@topsepadd uses the \topsep of the level that
  closes (was always level 1), with that list's own \partopsep flag; lists
  closing together and a following \item's \addvspace\itemsep keep the
  larger natural skip instead of the sum. Fixture 20's `2. Outer again` was
  7.97 bp low.
- A paragraph right after \end{itemize}/\end{enumerate} with no blank line
  is not indented (\@endpe), as after center/quote.
- Pins: tests/text_command_left_italic_correction.rs,
  tests/nested_list_end_skips.rs (pdflatex numbers; all four fail on main
  by -0.791, +7.970, -1.993, +3.985 bp); display_placement.rs PASSING adds
  17 and 20; baseline-render-pipeline.json refreshed.

What was run:
- display-placement oracle.py check: TOTAL 35/36 within 0.5 bp (main: 33/36;
  15 still fails on the rich \tag, owned by #470/#585)
- amsmath_corpus oracle.py check: TOTAL 59/59 within 0.5 bp
- cargo test --release -p flashtex-render-pipeline (all targets): 81 test
  binaries, 379 passed, 0 failed, 3 ignored

Next step: fixture 15 lands with #585 plus the math ToUnicode lane.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: math minus, asterisk and phi extract as the right Unicode

What changed:
- Math '-' extracts as U+2212 MINUS SIGN and '*'/\ast as U+2217 ASTERISK
  OPERATOR (both were ASCII); \phi/\varphi now extract as U+03D5/U+03C6
  (they were swapped). Glyph selection and positions are unchanged.
- Tests assert semantically correct Unicode, not pdflatex parity: pdflatex
  without the cmap package extracts cmex/cmsy glyphs as artefacts
  (\sum -> P, \mapsto -> 7->), which are deliberately not copied.

What was run:
- render-pipeline math targets: 68 passed, 0 failed
- render-pipeline lib: 138 passed, 0 failed, 2 ignored
- crates/pdf: all suites passed; git diff --check: passed

Next step:
- independent review.

Implementation-Agent: codex gpt-5.6-luna (max)
Commit-Executor: daniel-parent (committed Codex's working tree; sandbox blocked its git index)
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: keep only the four semantic math extraction fixes

What changed:
- The previous commit accidentally shipped the first-pass table that copied
  pdflatex's cmex/cmsy extraction artefacts (\mapsto -> 7->, \Longrightarrow
  -> =>, \circ -> bullet, \setminus -> ASCII backslash, ...). This commit
  applies the corrected working tree: extraction_text only maps '-' ->
  U+2212, '*' -> U+2217 and swaps \phi/\varphi; every other glyph keeps
  main's text. Tests assert semantically correct Unicode.

What was run:
- render-pipeline math targets and lib (see next comment on #597)

Next step:
- re-run tests; update #597.

Implementation-Agent: codex gpt-5.6-luna (max)
Commit-Executor: daniel-parent (staging slip in the previous commit)
Lane-Owner: daniel-parent (mac-m5pro-dq222)
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: long arrows extract as their drawn join pieces

What changed:
- The long arrows move out of common_math_glyph_runs_use_semantic_unicode
  into long_arrows_extract_as_their_drawn_pieces (option C from #536).
- A two-glyph join (#536's drawing) must extract as `=⇒` / `−→`, matching
  pdfTeX's own PDF (`x =⇒y −→z`, pdflatex + PyMuPDF). While main still draws
  one Latin Modern Math glyph, the text is the single `⟹` / `⟶`.
- A comment records that one `⟹` for two drawn glyphs needs PDF /ActualText
  marked content, a future crates/pdf change outside this PR.

What was run:
- math_symbols (debug): test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s

Next step:
- Merge into agent/daniel-parent/integration-preview and rerun math_symbols there.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* oracles: a math minus sign aligns with the references' hyphen-minus

What changed:
- tools/visual-oracle/rank.py `norm` folds U+2212 MINUS SIGN into U+002D
  before alignment. pdfTeX's own PDFs extract a math minus as U+2212
  (PyMuPDF on pdflatex's 19-vmatrix.pdf: "= ad −bc"; 28-xrightarrow.pdf:
  "f−→B", "h←−"), which is what this branch now emits. The pinned references
  read "-" only because pdftext's glyph-name table maps `minus` to "-". The
  references and FlashTeX are both right; the word matcher was not.
- amsmath_corpus/oracle.py `regroup` orders coincident glyphs (the \relbar
  and arrow head of \xrightarrow) with the same fold, so `−→` sorts like the
  references' `-→`.
- test_rank.py: a minus sign aligns with a hyphen-minus.

What was run:
- amsmath oracle, this branch's flashtex-render: TOTAL 58/59 after the norm
  fold alone (28-xrightarrow order tie), TOTAL 59/59 within 0.5 bp with both
- amsmath oracle, main's flashtex-render (36fe7ec): TOTAL 59/59 within 0.5 bp
- amssymb oracle, main and this branch: TOTAL 38/39 within 0.5 bp (both;
  32-braces-narrow fails identically on main)
- tabular oracle, main and this branch: TOTAL 106/106 (words 0.5 bp, rules 0.1 bp)
- display-placement oracle, main and this branch: TOTAL 33/36 within 0.5 bp (identical rows)
- python3 -m unittest discover -s tools/visual-oracle -p 'test_*.py': Ran 14 tests, OK

Next step: merge into the integration preview (#609) and rerun there.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* visual-oracle: candidate word text slices cluster byte ranges as bytes

What changed:
- rank.v2_words sliced the glyph run's Python str with the display list's
  cluster ranges, which are UTF-8 byte offsets. After the first non-ASCII
  character in a run (`∈`, and now this branch's U+2212 minus) every later
  glyph got its neighbour's text: `|f(x_k) − f(y_k)|` read as words `−f`,
  `(y` instead of `−`, `f(y`, and `x ∈ K` left an empty-text word. The
  sweep-2 "apparent regressions with identical pixels" (inline-math p1/p2,
  math-sheet p2, hw1 p1, conf-paper p2) were this. The amsmath oracle's own
  reader already encoded before slicing.
- test_rank.py: the v2 fixture helper writes byte offsets; a run with
  `−` and `∈` keeps its word text.

What was run (tools/visual-oracle/rank.py, all 22 real-world fixtures, same
flashtex-pdf-exact; aligned words summed over every page):
- old rank.py: main 36fe7ec 15285, preview 5d1eb06 15253
- this rank.py: main 15330, preview 15326
- pages whose aligned/within-0.5 counts differed main vs preview only
  through text (conf-paper p2, hw1 p1, inline-math p1/p2, math-sheet p1/p2,
  ps-calculus p2/p3, twelvept-plain p2, lecture-notes p2) are now identical
- amsmath 59/59, amssymb 38/39, tabular 106/106, display-placement 33/36
  (main) and 35/36 (preview): unchanged by this commit
- python3 -m unittest discover -s tools/visual-oracle -p 'test_*.py': OK

Next step: merge into the integration preview (#609).

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: read each table length once per adapt call, not per table (#525 review, #623)

What changed:
- `length_at_checked` scanned the source before every table (a CmdScan of
  the prefix, plus a `group_open_between` rescan per assignment) for each
  of the four tabular lengths and the five longtable lengths. On a
  document with many tables that is quadratic or worse: 800 tables spent
  1.6 s in it. It is the same class of bug #623 fixed in
  `split_at_page_breaks`.
- Within an adapt call (`MacroDefsScope`) each length is now indexed once
  per document: `length_assignments` collects every `\setlength`/
  `\addtolength`/`\<name>=` and assigning macro invocation of the whole
  source once, `GroupTokens` lexes the group depth once (the tokens
  `group_open_between` counts, with the next-lower-depth token of each),
  and `LengthIndex` links each assignment to the last earlier one still in
  force at its end. A table binary-searches its byte offset and walks that
  chain past closed groups. The value and `unresolved` flag are folded in
  the same order as the scan, so the floats are bit-identical.
- Outside an adapt call, at a byte inside a control word, or for a document
  whose assignments do not form a chain (an assigning macro in another's
  argument, an assignment ending inside a comment), the old scan runs
  unchanged as `length_at_scan`.
- The indexes live in a new `lengths` field of `MacroDefsEntry`, placed
  before `index` so #623's `setlengths` field merges without conflict
  (`git merge-tree` of this commit with #623's head is clean).

What was run:
- `cargo test --release --lib -- table_length`: test result: ok. 5 passed; 0 failed
  - table_length_index_matches_the_prefix_scan: the indexed lookup equals
    the scan at every byte of 8 hand-written and 300 generated sources,
    for tabcolsep/arrayrulewidth/LTpre (both index and fallback paths hit).
  - table_lengths_scale_linearly_with_the_number_of_tables:
    before: 200 tables 41.2ms, 400 tables 242.7ms, 800 tables 1.637s (panicked: not linear)
    after:  200 tables 393µs, 400 tables 717µs, 800 tables 1.42ms
- flashtex-render end to end, generated doc, best of 3 (before -> after, v2 identical):
  200 tables 0.16s -> 0.08s; 400 tables 0.71s -> 0.22s; 800 tables 4.05s -> 0.73s
- v2 JSON + compile_result of all 128 tabular_corpus fixtures and 22
  fixtures/real-world documents, head 99defd5 vs this commit: `diff -rq` IDENTICAL (150/150)
- `oracle.py check`: TOTAL 128/128 (words 0.5 bp, rules 0.1 bp)
- `cargo test --release` (render-pipeline): exit 0, 79 test binaries, 383 passed; 0 failed; 3 ignored

Next step: merge #623 after #525 (or the reverse); the rest of the
remaining 800-table time is #623's `split_at_page_breaks` fix.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* integration: #639's layout_cache_keys test constructs #525's Item::Quad with its style field

Main's tests/layout_cache_keys.rs (#639) predates #525's Item::Quad
{ em, style }; the test's shared TextStyle fills the new field.

Implementation-Agent: fable-integration-rp
Commit-Executor: fable-integration-rp (direct; Cursor usage limit)

* integration: a file-tail float keeps its blank-line mask; include_float_lists expects #608's joined paragraph

Two #578 x #608 interactions on the combined head:

- floats::mask (#608) turned a float-only line into a % comment; for a
  float at the very tail of an \included file that left the paragraph
  open at EOF, and the compiler joined it with the entry document's
  next text, so #578's closing \clearpage landed after the merged
  paragraph (4 pages instead of pdflatex's 5). A float with nothing
  but blanks/comments after it now keeps the blank-line mask; entry
  documents are unaffected (\end{document} follows their floats).
- tests/include_float_lists.rs (#578) expected "Refs ..." on its own
  line; under #608 the [h] floats stay inside the paragraph (pdflatex
  behaviour), so the refs share the paragraph's line.

Implementation-Agent: fable-integration-rp
Commit-Executor: fable-integration-rp (direct; Cursor usage limit)

* integration: restore #543's entry-document chapter starts for \thelstlisting

The #578 delta removed the chapter_starts push together with the
chapter-based figure numbering it replaced, but #543's listing numbers
(list_blocks' Lol entries and listings::apply captions) still come from
chapter_numbers over those starts (toc_oracle fixture
37-report-lstlistoflistings).

Implementation-Agent: fable-integration-rp
Commit-Executor: fable-integration-rp (direct; Cursor usage limit)

---------

Co-authored-by: d-q222 <279808976+d-q222@users.noreply.github.com>
GoKubar added a commit that referenced this pull request Sep 15, 2026
… (#649)

* render-pipeline: long arrows are pdfTeX joins; heading spaces after \normalfont are medium

What changed:
- The six long arrows (and so \implies, \iff, \impliedby) are built as
  LaTeX builds them: one relation holding the two cmr/cmsy pieces with
  \joinrel's -3mu between them. They were Latin Modern Math's single
  U+27F5..U+27FA glyphs, 1.457em wide against pdfTeX's 1.611em. On HW1
  Problem 4(b) that shifted the centred display by +0.84/-0.84bp.
- In a directly written heading title, a space with \normalfont words on
  both sides uses the medium font's \fontdimen2 (3.91571pt at \large),
  not the head's bold 4.4989pt. On HW1 "Bonus Problem [1 bonus point]",
  "[1" was 1.15bp left of pdfTeX's position.
- New tests/long_arrows.rs pins pdfTeX's x-to-y distance for all nine
  commands. heading_macros.rs pins the Bonus heading's positions and
  checks the bold/medium spaces around {\normalfont B}. math_symbols.rs
  now expects the composed pieces instead of the fallback glyph.

What was run:
- cargo test --test long_arrows: test result: ok. 1 passed; 0 failed
- cargo test --test heading_macros: test result: ok. 5 passed; 0 failed
- cargo test --test math_symbols: test result: ok. 12 passed; 0 failed
- HW1 glyph diff against pdflatex (TeX Live 2026): lines over 0.5bp went
  from 6 to 2. The 2 left are math-mode TFM kerns (r+comma). 3 pages and
  4 diagnostics, both unchanged.

Next step: math-mode Ord kerns (tex.web §752) in crates/math-layout, then a
vendor re-pin by the integration lane.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* compiler: implement \obeylines as group-scoped forced line breaks (#588)

Each source newline lowers to the existing \\-primitive (Inline::LineBreak), scoped like the centering/ragged declarations; blank lines still end the paragraph and surrounding spaces are not preserved. Adds crates/compiler/tests/obeylines.rs and regenerates the supported-LaTeX inventory. Nothing under crates/render-pipeline/ touched.

Implementation-Agent: muse-spark-1.3-contributor

* render-pipeline: build \longmapsto from pieces like the other long arrows (GH-LONGMAPSTO-PIECES)

U+27FC explodes in symbol_atoms to the flag (U+2223, this compiler's \mid
glyph) backed up by its own advance (cmsy10 slot "6A is 5.00002mu, so -5mu
nets ~1e-5pt) plus the usual relbar + joinrel + rightarrow join, matching
pdfTeX's \mapstochar\longrightarrow: the x-to-y span is 29.9832pt,
bit-for-bit the \longrightarrow width. long_arrow_pieces/long_arrow now
take (glyph, kern-before-mu) lists so the 3-piece arrow shares the 2-piece
path. The new long_arrows.rs case spells U+27FC literally until
vendor/compiler is re-pinned past PR #603's ("longmapsto", U+27FC) row.

Implementation-Agent: muse-spark-1.3-contributor

* render-pipeline: align list labels with pdflatex

What changed:
- Preserve list-symbol weight and glyph identity from the compiler.
- Use Latin Modern font dimensions for list margins and honor left-extending label boxes.
- Add pdflatex-pinned itemize, enumerate, and description origin probes.

What was run:
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-itemlabel CARGO_BUILD_JOBS=4 cargo build --bin flashtex-render
- Focused render-pipeline list oracle tests
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-itemlabel CARGO_BUILD_JOBS=4 cargo test --lib

Next step:
- Parent lane can review and integrate this commit.

Implementation-Agent: codex gpt-5.6-luna (max)
Commit-Executor: codex (this session)
Lane-Owner: daniel-parent (mac-m5pro-dq222)

* pdf: add marked ActualText groups

What changed:
- Added typed exact-route marked-content operators with UTF-16BE ActualText encoding.
- Added optional v2 glyph_run actual_text grouping across font and BT/ET switches.
- Documented the field and added syntax, byte-identity, and PyMuPDF tests.

What was run:
- CARGO_TARGET_DIR=/Users/dqi26/flashtex/target-actualtext CARGO_BUILD_JOBS=4 cargo test --manifest-path crates/pdf/Cargo.toml --lib --tests
- git diff --cached --check

Next step:
- render-pipeline can populate actual_text after the vendor/pdf re-pin.

Implementation-Agent: codex gpt-5.6-luna (max)
Commit-Executor: codex (this session)
Lane-Owner: daniel-parent (mac-m5pro-dq222)

* render-pipeline: enumitem labelsep, itemindent, widest and register leftmargin follow \enit@calcleft

What changed:
- Read enumitem `labelsep=`, `itemindent=` and `widest=` per list level
  (itemize/enumerate). With `leftmargin=*` the margin is
  `\labelwidth + \labelsep - \itemindent` (new `ListMargin::WidestSep`);
  otherwise `labelsep` moves only the label and `itemindent` the first
  line and its label. `\labelsep` is inherited by nested lists.
- `leftmargin=\len` resolves a preamble `\setlength` or `\settowidth`
  register (new `ListMargin::TextWidth`).
- `ListGeom` gains `labelsep_pt` and `itemindent_pt` at the end.
- New pdflatex-pinned oracle `tests/enumitem_item_text.rs` (10/11/12pt).
- The enumitem-worksheet +1.17bp item-text shift from the #609 sweep is
  already gone on #611's head (it was `\leftmargin<i>` in a 12pt em rather
  than lmr12's 11.74994pt quad); rows a-c pin it.

What was run:
- cargo test --release --test enumitem_item_text: test result: ok. 1 passed; 0 failed
- Without the src change the same test fails on 37 rows (e.g. K12e text: 101.265 vs pdflatex 112.981).

Next step:
- Full render-pipeline release suite, then PR stacked on #611.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* render-pipeline: itemize TS1 symbols take tcrm's widths without lmodern

What changed:
- Without lmodern (OT1 or T1 body text), \textbullet, \textasteriskcentered
  and \textperiodcentered are TS1 tcrm, whose widths are the cmsy designs
  (0.499878/0.499878/0.27771 at 10pt), not ts1-lmr's 0.7778em bullet and
  period. Label boxes now take the tcrm advance (transcribed tcrm CHARWD at
  all 14 EC sizes, fonts::tcrm_symbol_width). The bullet and centred period
  are drawn with Latin Modern Math's U+2022/U+00B7, which are cmsy's glyphs
  (ink matches pdflatex's SFRM1000 to 0.03bp); the asterisk keeps LM Roman.
- leftmargin=* on itemize measures the class labels as \labelitemi..iv set
  them: the tcrm widths, and \labelitemii's en dash in bold (was regular,
  0.75pt short even with lmodern).
- Class \leftmargin<i> under T1 cmr use OT1 cmr's em (the class sets them
  before fontenc), fixing a 0.15bp shift at 11pt.
- New oracle tests/itemize_ts1_symbols.rs (OT1/T1/lmodern x 10/11/12pt,
  levels 1-4, default and leftmargin=*); enumitem_item_text now gates the
  K..j bullet x.

What was run:
- cargo test --release --no-fail-fast (render-pipeline): 82 binaries, 378 passed, 0 failed, 3 ignored
- itemize_ts1_symbols: test result: ok. 1 passed; 0 failed (78 mismatches before the src change)
- enumitem_item_text, enumitem_keys_oracle, itemize_label_offset, list_items, description_env, description_display: all ok

Next step: Times documents' TS1 symbols and class em are unmeasured.

Implementation-Agent: claude-opus-5 subagent of daniel-parent
Commit-Executor: daniel-parent subagent
Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz

* integration fix: math_symbols reads Page::items through #294's accessor

#294 replaced Page's items field with PageContent plus an items()
accessor; tests/math_symbols.rs still took the field, so the test
target failed to compile (E0615) and its 18 tests silently did not run.
Broken on main itself as well -- fixed there in #664.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5Hmwh5GHNTiAmHUJ1MZu

---------

Co-authored-by: d-q222 <279808976+d-q222@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@d-q222

d-q222 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Superseded: the integration lane merged and closed all 43 PRs in this preview's own recommended queue overnight (backlog 219 → ~105), so the preview branch no longer previews anything that is not already on main. Closing. Triage detail and what is still genuinely blocked (the vendor/compiler pin, 41 commits behind) is in #2 (comment) — daniel-parent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant