Write one tableParaStyle entry per paragraph (#81) - #82
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Author — the crash was mine, and it's fixed. Thanks for catching it; a crash on open is a worse failure than the unstyled render this PR set out to fix. What happenedThe per-paragraph entries were the right idea. Writing them was not. For a repeat entry I set I saw the template's repeats reported "identifier 0" and generalized from that without checking how they actually serialize. The bytes are unambiguous: Reading the identifier back cannot distinguish them — absent and zeroed both report 0. Only The fix
I confirmed the new test fails against the crashing version before restoring the fix — a test that can't fail would have been worthless here, and the previous one proved that. Verification
Re-render, pleaseFresh deck at
Recorded in |
Item-level `.font`/`.fontSize`/`.foregroundColor` reached only the FIRST
paragraph of a multi-paragraph text box. Keynote rendered the rest at the
template default, silently — the archive stayed valid, so nothing failed
until a human looked at a slide.
`collapsedEntries` run-length collapsed adjacent identical paragraph formats
into one entry at offset 0. That reads like a reasonable optimization, but
Keynote treats each `tableParaStyle` entry as a paragraph boundary marker,
so collapsing left later paragraphs unstyled.
The fixture settles the intended shape. A human-authored 5-paragraph body
storage in `build_action_B.key` writes FIVE entries:
char=0 -> 2651127
char=15 -> 0
char=30 -> 0
char=47 -> 0
char=63 -> 0
The style rides entry 0; later boundaries carry identifier 0, meaning "same
as the preceding entry". The entry must exist even though it references no
record.
`paragraphEntries` emits one entry per paragraph, using identifier 0 for a
repeat. Fork records are still deduped — the dedupe is on records, not
entries. `applyParagraphStyles` filters identifier-0 entries out of the
header references, since 0 is not a record.
Two existing tests asserted the collapsing behavior; they encoded the bug, so
they now assert the per-paragraph shape instead. Added
`MultiParagraphFormattingTests` and a `multi_paragraph_formatting`
acceptance deck.
Everything uses THREE or more paragraphs deliberately: a two-paragraph case
exercises only the first and last and passes while interior paragraphs stay
broken. That is exactly how the per-span variant of this bug survived the
first #64 probe round.
`swift test` 70 green, `LINT_MODE=STRICT ./Scripts/lint.sh` exit 0.
Structural only — the render pass is the real gate.
Refs #81, #64, #66
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Leo's render pass crashed Keynote on open. My fault, and a worse failure than
the bug being fixed.
The per-paragraph entries were right; writing them was not. I set
`entry.object.identifier = 0` for a repeat, which MATERIALIZES a
present-but-empty `TSP.Reference`. Keynote resolves that to nil and crashes —
the same id-0 trap already recorded twice in agent-notes, for `RecordCloner`
remaps and for plain character spans. I read the template's repeats as
"identifier 0" and generalized from that without checking how they serialize.
The wire bytes are unambiguous:
template repeat: [08 0f] characterIndex only, no object field
what I wrote: [08 0f 12 00] present-but-empty reference
Reading the identifier back cannot tell them apart — absent and zeroed both
report 0. Only `hasObject` does.
## Changes
- `applyParagraphStyles` assigns `object` only for real identifiers
- `UUIDMapVerifier` gains **rule 7**: no storage object-attribute entry may
carry a present-but-empty reference, across `tableParaStyle`,
`tableCharStyle`, and `tableListStyle`. Every deck now passes through it,
so this class of crash cannot ship again from any code path.
- A regression test asserting `hasObject` is false and the entry serializes
to exactly `[08 04]`. Verified it FAILS against the crashing version —
asserting `identifier == 0` alone passes on both, which is precisely why
the original test missed it.
All 12 acceptance decks regenerate clean under rule 7. 71 tests green,
STRICT lint exit 0.
Refs #81
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0bc36b5 to
672eb1e
Compare
Rebase integration. #82 landed `MultiParagraphFormattingContent` and its tests while this branch was open; they use `TextColor`, which this branch renames to `Color`. Mechanical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes the bug behind both the #64 monospace failure and the dark-on-dark code panel in #78's render pass: item-level formatting reached only the first paragraph of a multi-paragraph text box.
Branches off
v0.1.xdirectly — independent of the #78 and #65 stack.Cause
collapsedEntriesrun-length collapsed adjacent identical paragraph formats into a singletableParaStyleentry at offset 0. That reads like a reasonable optimization, but Keynote treats each entry as a paragraph boundary marker, so collapsing left later paragraphs unstyled.The archive stayed valid and nothing errored — the defect was only visible by opening a slide, which is why it survived #37's render verification (that deck is one paragraph).
What the fixture says
A human-authored 5-paragraph body storage in
build_action_B.key:One entry per paragraph. The style rides entry 0; every later boundary carries identifier 0 = "same as the preceding entry". The entry must exist even though it references no record.
Fix
paragraphEntriesemits one entry per paragraph, writing identifier 0 where a format repeats. Fork records are still deduped — the dedupe is on records, not entries.applyParagraphStylesfilters identifier-0 entries out of the header references, since 0 isn't a record.Two existing tests changed
itemWideAlignmentanddedupeCollapsesEntriesasserted the collapsing behavior — they encoded the bug. Both now assert the per-paragraph shape. Flagging explicitly since changing a green test to make a fix pass deserves scrutiny; the fixture evidence above is why I'm confident the new assertion is the correct one.Everything uses three or more paragraphs
Deliberate. A two-paragraph case exercises only the first and last and passes while interior paragraphs stay broken — which is exactly how the per-span variant of this bug survived the first #64 probe round, where
iiiiand1111rendered correctly andMMMMdidn't.Verification
swift test— 70 green, including a newMultiParagraphFormattingTestssuiteLINT_MODE=STRICT ./Scripts/lint.sh— exit 0swift run AcceptanceDecks—multi_paragraph_formatting.keywrites and self-checksStructural only. This bug was invisible to structural checks by definition, so the render pass is the real gate.
Render checklist (yours)
Deck generated at
/Users/leo/Downloads/para-fix/multi_paragraph_formatting.key.iiiiwas.If this renders green, #66 unblocks and I'd also restore the multi-line code sample in #78's
background_fillslide 1.Fixes #81