Conversation
…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
… pick up #327's Mac inventory sync (CI sync-supported-latex --check) Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…k up #327's Mac inventory sync (CI sync-supported-latex --check) Claude-Session: https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz
…c inventory sync 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
…act compiler code semantics 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>
…2-structured-diagnostics
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
|
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: Margin warning: the test now passes, but only just.
2. Long-arrow extraction (#536 / #597): not implemented, blocked as specifiedPreview tip, release: See the comment on #536 for why the decided fix cannot ship as specified, and the options. |
|
daniel-parent decisions on the preview's two combination failures:
|
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
|
Sweep-2 regressions resolved (GH-PREVIEW-REGRESSIONS). Preview tip is now Method: rebuilt
Oracles on
Details and evidence are in (daniel-parent subagent, GH-PREVIEW-REGRESSIONS) 🤖 Generated with Claude Code |
* 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>
… (#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>
… (#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>
|
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 |
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-draftagent/daniel-parent/*PRs merged withgit merge --no-ff, in dependency order.render_supported_latex.sh, never hand-merged.docs/integration/daniel-parent-preview.md, reproduced below.Branch
agent/daniel-parent/integration-preview: origin/main36fe7ec3plus the 43 open non-draftagent/daniel-parent/*PRs, merged withgit 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.]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) #535 → compiler: 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)render-pipeline: a macro body's font declarations apply to its arguments (min2-preamble-switches 419 bp -> 0.005 bp) #547 → render-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
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.
sh crates/compiler/scripts/render_supported_latex.sh.TABLE_LENGTHSand anis_table_lengthdispatch arm. Kept one const (#526's doc comment) and #587's arm, which passes #587's newglobalflag tolength_assignment; #526's comment moved onto it. The duplicate two-argument arm was dropped (it would not compile against #587's signature).Checkpointgainedlast_origin(#601) andquad_sp/x_height_sp(#587). Kept all three fields in the struct, incheckpoint()and inrestore().stepsoffset andlast_origin, plus #587'squad_sp/x_height_sp.cargo check --testspasses for tex-expansion and compiler.parse_project_with'sP { .. }initialiser: kept #587'slength_scopes/pending_globaland #568'sparameters/parameter_scopes/hyphenation.cargo check --testspasses.char_table)mod char_table;andpub mod biblatex;.command_package: kept #542's listings/minted split (lstlistoflistingsis listings,listoflistingsis minted) and #558'sciteyear(natbib) andcite/parencite/textcite/autocite/nocite(biblatex) arms.Engine::atom: took #560's signature (newtext_font_pairargument, doc comment) and kept #581'sleft_scriptsearly return tomake_sidesetat the top. All call sites already pass four arguments.cargo check --testspasses.number(floats::number) and merge list entries in reading order (Labels::reading_order), and they dropped the adapter'schapter_starts. #543 numbers LoF/LoT/LoL entries insidelist_blocksfromchapter_starts, adds listing entries (listed,nolol) and adds\addvspacechapter gaps. Resolution:FloatEntrykeeps bothnumberandlisted.lib.rskeeps #578's numbering and reading order, then extends with #543'slisting_entries.adapter.rskeeps #543'schapter_starts/chapter_gapsand itslist_blockscall, but now pushes them as reading positions (reading_position(cmd_doc, cmd.start)), because chapters can come from\included documents after #538/#578. Inlist_blocks, figures and tables usef.number(#578); only LoL entries are counted withchapter_numbers(#543), over reading positions; the chapter-gap check compares reading positions.listings::numberstakes the reading order so\thelstlistingagrees with the LoL; its two unit tests pass&[], where the offset fallback applies.cargo check --testspasses.font_declaration's italic-correction flag (declaration groups add no\/); #596 insertedtext_command_argument_at(`watch --timing)checkgains #362's--fixand--dry-run, andwatchkeeps main's--timing. The generated section was re-rendered with the script.from_compiler)sources/messagefolding. #358's v2-diagnostics capability code auto-merged around it.cargo check --testspasses.After the last merge, re-running
sh crates/compiler/scripts/render_supported_latex.shproduced 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 withcargo test --locked --no-fail-fast;render-pipeline used
cargo test --release --locked --no-fail-fast.Failures caused by the combination
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.--skip, the other 13 pass.--release, which is what CI runs.59311b2b) and overflows after compiler: basic biblatex — \addbibresource, \printbibliography, \textcite/\parencite (numeric) #558 (a1ce8b4c).\printbibliography,\addbibresourceand biblatex citation arms). compiler: mutation fuzzer + 13 robustness fixes (panics, stack overflows, hangs, OOM) #577 setSTREAM_DEPTH_LIMIT = 32to stay "below what a 2 MiB debug test thread holds", and the larger frames now exceed that.#[inline(never)]helpers; lowerSTREAM_DEPTH_LIMIT; or run the test on a thread with an explicit stack size.tests/math_symbols.rs::common_math_glyph_runs_use_semantic_unicode.left: "…↦=⇒−→↪…",right: "…↦⟹⟶↪…".\Longrightarrow/\longrightarrowas pdfTeX joins (=+⇒,−+→), so the math runs' text is the pieces. render-pipeline: math minus, asterisk and phi extract as the right Unicode #597 added this test expecting the semantic single characters⟹and⟶. Each PR is self-consistent; together their intents conflict.⟹), or change render-pipeline: math minus, asterisk and phi extract as the right Unicode #597's expectation to the join pieces.No other failures.
Vendor re-pins needed after merging
render-pipeline builds against the frozen mirrors in
crates/render-pipeline/vendor/; seevendor/VENDORING.md.These PRs change crates that are mirrored there. Their pipeline effect appears only after re-pinning.
vendor/compiler\coloneqqcomposition needs #529vendor/math-layoutmath-font-kernsfeature stays off until then; #582, #585vendor/tex-expansionvendor/pdfvendor/project-filescrates/bibliography(#558's biblatex is incrates/compiler)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:
Overlap check
gh pr list --state open --search "integration preview": no other preview PR.coord.py claims: onlyGH-INTEGRATION-PREVIEW(daniel-parent), this task.git ls-remotebefore merging, and all matched.Test results
Not done
🤖 Generated with Claude Code
https://claude.ai/code/session_012c9XLkHjePPGBuarrmE2mz