Claims we cannot prove offline β
Everything in the test suite proves this library agrees with its own reading of Apple's files: that it decodes what the apps wrote and re-encodes it byte for byte. That is a real guarantee, and it is not the same as proving the apps accept something we invented.
The claims below are the ones where the only authority is the application itself. Each says what is being claimed, why the offline suite structurally cannot settle it, and what would.
This file is generated. Claims live in manualProof blocks beside their capability in scripts/coverage-matrix.ts; run npm run coverage to regenerate. A test fails if it goes stale.
Where a claim can be settled by a repeatable procedure rather than a one-off look, that procedure lives in docs/BLOCKERS.md along with a ledger of what has actually been run and against which app version.
How much is already automated β
Of 21 claims, 2 are covered by npm run test:e2e, which drives the real apps through AppleScript on a Mac. The rest need a person to look at a rendered document, because the scripting dictionaries expose no way to ask.
The list β
| # | Risk | Capability | Claim | Automated? |
|---|---|---|---|---|
| 1 | π΄ high | Drawables & media β Inline image placement in an indented column | an inline image sits in the text column of an indented paragraph, not at the page margin | manual |
| 2 | π΄ high | Numbers & tables β Cell styling (fill, four borders, padding, alignment, wrap) | A cell style we create is picked up by the app and rendered, and the style table stays consistent. | manual |
| 3 | π΄ high | Numbers & tables β Sheets (add, duplicate, rename, move, remove) | Numbers opens a document whose sheets we added, duplicated, renamed or reordered. | manual |
| 4 | π΄ high | Numbers & tables β Table cell writing (text, number, date, bool, duration) | Numbers, Pages and Keynote open a package whose cells we rewrote, and display the values we wrote. | test:e2e |
| 5 | π medium | Drawables & media β Floating (non-inline) drawable placement | a drawable copied into a page's floating list is placed and rendered by Pages | manual |
| 6 | π medium | Drawables & media β Image cropping (set, move, remove a mask) | a mask this library writes crops the way Apple's does | manual |
| 7 | π medium | Numbers & tables β Add and remove tables on a sheet | a table added this way is editable in Numbers as a table, not just present in the file | manual |
| 8 | π medium | Numbers & tables β Cell display formats (number, currency, percentage, date, duration, text, boolean) | A format we write makes Numbers display the value the way the inspector would. | manual |
| 9 | π medium | Numbers & tables β Chart appearance: axes, legend, gridlines | Pages draws the chart without the gridlines this library switched off. | manual |
| 10 | π medium | Numbers & tables β Chart data editing (values, names, series, categories) | a series added or removed here leaves the chart's styling on the right series | manual |
| 11 | π medium | Numbers & tables β Conditional formatting: apply an existing rule set to more cells | re-pointing a cell's conditional-style key makes Numbers apply that rule set to it | manual |
| 12 | π medium | Numbers & tables β Filters: enable, disable, combining mode | enabling a filter set makes Numbers apply its rules | manual |
| 13 | π medium | Numbers & tables β Formula function names | The function-index table is incomplete, and every unnamed id is visible rather than guessed. | test:e2e |
| 14 | π medium | Numbers & tables β Formula reading (AST rendered to text) | Rendered formula text matches what the app shows in its formula bar. | manual |
| 15 | π medium | Numbers & tables β Table structure (rows, columns, bands, sizes, freeze, repeat) | Changed band counts, freeze and repeating-header flags, row heights and column widths take effect. | manual |
| 16 | π medium | Numbers & tables β Table styling (banded rows, grid strokes, visibility) | Banded rows, grid strokes and the visibility toggles render as set. | manual |
| 17 | π‘ low | Drawables & media β Drawable shadows (enabled, angle, offset, blur, opacity) | A shadow we enable or re-parameterise renders in the app with the geometry we set. | manual |
| 18 | π‘ low | Numbers & tables β Categories: enable or disable grouping | flipping is_enabled makes Numbers group or ungroup the rows | manual |
| 19 | π‘ low | Numbers & tables β Conditional formatting rules | the second conditional id in a cell record (COND_RULE_STYLE_ID) is a cache the app rewrites, so preserving it verbatim is enough | manual |
| 20 | π‘ low | Text & styles β Shared style values (colour incl. P3, gradients, strokes, shadows, padding) | A Display-P3 colour we write renders as P3, and a dashed stroke renders with our dash lengths. | manual |
| 21 | π‘ low | Text & styles β Table of contents (rules read + write, cached entries read) | Pages regenerates a TOC whose collection rules we changed, and honours the new rule set. | manual |
1. Inline image placement in an indented column β
Risk if wrong: π΄ high
Group: Drawables & media
Status in the matrix: β
read + write
Claim. an inline image sits in the text column of an indented paragraph, not at the page margin
Why the suite cannot settle it. Reported from a real build: in a template whose body styles are indented, an inserted picture drew from the page margin instead of the column, and the next paragraph flowed up beside it into the running footer. The drawable carried no exterior_text_wrap at all β the field every corpus inline image has β and geometry could not move it, because for an in-flow attachment the position is a cache the app recomputes.
How to settle it. npm run seeds -- out writes seed-inline-image.pages: two pictures in a paragraph indented well in from the margin, one in-flow and one page-placed, each labelled with what it should look like. The in-flow picture starting where its own paragraph starts is the pass.
2. Cell styling (fill, four borders, padding, alignment, wrap) β
Risk if wrong: π΄ high
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. A cell style we create is picked up by the app and rendered, and the style table stays consistent.
Why the suite cannot settle it. We add a TST.CellStyleArchive and a style-table entry, then point the cell record at the new key. Nothing offline proves the app resolves that key, nor that cloning a style without its name and identifier is acceptable. The scripting dictionary exposes no cell formatting, so even e2e cannot assert it.
How to settle it. Write a fill, four borders, padding and vertical alignment into a cell, open in Numbers, and compare against the same formatting applied by hand in the inspector. Then re-save from the app and diff our style object against what Numbers rewrote.
3. Sheets (add, duplicate, rename, move, remove) β
Risk if wrong: π΄ high
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. Numbers opens a document whose sheets we added, duplicated, renamed or reordered.
Why the suite cannot settle it. A sheet is valid only in the context of the calc engine and the document's own bookkeeping. Our copies reload and round-trip, but whether Numbers accepts a duplicated tab β and whether its formulas still resolve against the copy rather than the original β only the app can say.
How to settle it. Duplicate a sheet with formulas, rename and reorder, save, and open in Numbers: check the tab bar, that the copy's formulas point within the copy, and that editing one tab leaves the other alone.
4. Table cell writing (text, number, date, bool, duration) β
Risk if wrong: π΄ high
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. Numbers, Pages and Keynote open a package whose cells we rewrote, and display the values we wrote.
Why the suite cannot settle it. Every offline check is self-referential: our encoder round-trips through our decoder. Apple's reader is the only authority on whether the rebuilt row buffers, offset array, cell counts and legacy stubs are all acceptable together.
How to settle it. npm run test:e2e on a Mac β 'writes cells that Numbers itself reads back' asserts the app reports our text and number. Then open the file by hand and check the edited cells look normal (no red triangle, no reformatting) and that undo/redo behaves.
Already exercised by
npm run test:e2eon a Mac with the app installed.
5. Floating (non-inline) drawable placement β
Risk if wrong: π medium
Group: Drawables & media
Status in the matrix: β
read + write
Claim. a drawable copied into a page's floating list is placed and rendered by Pages
Why the suite cannot settle it. the suite proves the copy resolves, keeps its media and survives a save, not that the app lays it out
How to settle it. copy an image onto a page at a known position, open in Pages, and confirm it appears there and is independently editable from its source
6. Image cropping (set, move, remove a mask) β
Risk if wrong: π medium
Group: Drawables & media
Status in the matrix: β
read + write
Claim. a mask this library writes crops the way Apple's does
Why the suite cannot settle it. the crop is a rendering result; the suite proves the geometry and path round-trip, not what appears on the page
How to settle it. crop an image to a known rectangle, open in Pages, and confirm the visible region matches β then drag the image inside the mask and re-read to check the window is where this library says
7. Add and remove tables on a sheet β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. a table added this way is editable in Numbers as a table, not just present in the file
Why the suite cannot settle it. the suite proves it reloads with its own cells and a unique name, not that the app treats it as a first-class table
How to settle it. add a blank table, open in Numbers, type into it and reference it from a formula on another table
8. Cell display formats (number, currency, percentage, date, duration, text, boolean) β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. A format we write makes Numbers display the value the way the inspector would.
Why the suite cannot settle it. The type codes were established by correlating every format in the corpus against the flag that referenced it β strong evidence for the categories, but rendering is still the app's.
How to settle it. Write a currency, percentage and date format, open in Numbers, and compare each cell against the same format applied through the Cell inspector on an untouched copy.
9. Chart appearance: axes, legend, gridlines β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. Pages draws the chart without the gridlines this library switched off.
Why the suite cannot settle it. The archives round-trip and copy-on-write correctly, which is the file's side of the story; only the app can say the toggle changes what is drawn rather than being ignored.
How to settle it. npm run pages:docs -- <outDir>, open P20-chart-gridlines: the page states its own pass β the column chart should show no horizontal gridlines behind its bars.
10. Chart data editing (values, names, series, categories) β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. a series added or removed here leaves the chart's styling on the right series
Why the suite cannot settle it. styling is applied at render time from arrays indexed by series position; the suite proves the indexes shift, not what the app draws
How to settle it. take a chart with distinctly coloured series, remove the middle one, open in the app and confirm the remaining series keep their own colours rather than shifting
11. Conditional formatting: apply an existing rule set to more cells β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. re-pointing a cell's conditional-style key makes Numbers apply that rule set to it
Why the suite cannot settle it. the fixture suite proves the key changes and the file reloads, not that the app honours it β evaluation happens in the calc engine
How to settle it. open a document with two conditional rules, move a cell onto the other set with setConditionalStyleKey, open in Numbers and confirm the cell picks up the second rule's styling
12. Filters: enable, disable, combining mode β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. enabling a filter set makes Numbers apply its rules
Why the suite cannot settle it. the corpus now carries a populated, enabled set the app itself wrote, but a flag flipped by this library has never been reopened in the app β and hidden rows are recomputed there, not here
How to settle it. take olekristensen-v26.3-mac-filters.numbers, flip is_enabled off with this library, reopen and confirm all rows show
13. Formula function names β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β οΈ experimental
Claim. The function-index table is incomplete, and every unnamed id is visible rather than guessed.
Why the suite cannot settle it. AST_function_node_index is an index into an Apple-internal list that appears in no public schema. The corpus proves exactly one entry (168 = SUM, by arithmetic). Shipping a table of plausible-looking guesses would turn a visible gap into silent wrong answers.
How to settle it. Run node scripts/harvest-functions.ts --drive on a Mac β it writes ~300 candidate functions through Numbers and reads every index back in one pass, producing data/function-index.json and a generated table. Without a Mac to hand, --emit-sheet produces a file to open and save in Numbers by hand, then --ingest. Procedure in docs/BLOCKERS.md.
Already exercised by
npm run test:e2eon a Mac with the app installed.
14. Formula reading (AST rendered to text) β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: π read only
Claim. Rendered formula text matches what the app shows in its formula bar.
Why the suite cannot settle it. Operators, references and ranges are decoded structurally and check out against cached values, but the archive records no brackets and no function names, so the rendering is a reconstruction. Only the app can confirm the reconstruction reads the same.
How to settle it. Open libetonyek-pages5-extra-dir.pages in Pages and numbers-parser-v14.4-issue102.numbers in Numbers, click the formula cells, and compare the formula bar with cellFormula(). Expect =B2*C2 and =SUM(C3:K6).
15. Table structure (rows, columns, bands, sizes, freeze, repeat) β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. Changed band counts, freeze and repeating-header flags, row heights and column widths take effect.
Why the suite cannot settle it. These are presentation fields the offline suite can only verify it wrote and can read back. Whether the app agrees a header count is legal for a given table β and whether frozen or repeating headers need companion state we are not writing β only the app can say.
How to settle it. Set headerRows/footerRows plus freezeHeaderRows and repeatHeaderRows, open in Numbers, and check the header/footer controls in the inspector show what we set and that scrolling freezes correctly. For repeating headers, print to PDF from Pages and confirm the header repeats on page 2.
16. Table styling (banded rows, grid strokes, visibility) β
Risk if wrong: π medium
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. Banded rows, grid strokes and the visibility toggles render as set.
Why the suite cannot settle it. TableStylePropertiesArchive has separate strokes for the body grid and the outer border plus a set of visibility booleans; which combination the app honours for a given theme is a rendering question no archive inspection answers. Our 'body border' setter writes both the horizontal and vertical border strokes on the assumption the inspector's single control does the same.
How to settle it. Set bandedRows with a banded fill and a body grid stroke, open in Numbers, and compare against the same settings applied through the Table inspector on an untouched copy.
17. Drawable shadows (enabled, angle, offset, blur, opacity) β
Risk if wrong: π‘ low
Group: Drawables & media
Status in the matrix: β
read + write
Claim. A shadow we enable or re-parameterise renders in the app with the geometry we set.
Why the suite cannot settle it. Angle, offset and blur radius are rendering parameters. Fixtures prove we read Apple's values correctly and re-encode them identically, but not that a shadow we author from scratch on a shape that had none is picked up rather than ignored.
How to settle it. Enable a shadow at angle 90, offset 10, radius 20 on a shape, open in Keynote or Pages, and compare with the Shadow section of the Style inspector.
18. Categories: enable or disable grouping β
Risk if wrong: π‘ low
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. flipping is_enabled makes Numbers group or ungroup the rows
Why the suite cannot settle it. the suite proves the flag round-trips and the tree survives, not that the app acts on it
How to settle it. take a categorised table, disable it with setEnabled(false), open in Numbers and confirm the rows are flat and the category can be switched back on
19. Conditional formatting rules β
Risk if wrong: π‘ low
Group: Numbers & tables
Status in the matrix: β
read + write
Claim. the second conditional id in a cell record (COND_RULE_STYLE_ID) is a cache the app rewrites, so preserving it verbatim is enough
Why the suite cannot settle it. its value contradicts the obvious reading β every cell on a one-rule set carries 15 regardless of content, and cells on other sets carry 0, which is not a valid key in any of the table's lists
How to settle it. author two conditional rules, note the value on cells matching each, then change a cell's content so a different rule fires and re-read; if it tracks the match it is a live cache, if not it means something else
20. Shared style values (colour incl. P3, gradients, strokes, shadows, padding) β
Risk if wrong: π‘ low
Group: Text & styles
Status in the matrix: β
read + write
Claim. A Display-P3 colour we write renders as P3, and a dashed stroke renders with our dash lengths.
Why the suite cannot settle it. Colour space and dash patterns are rendering behaviour. We know 26.x files tag colours with rgbspace and that the dash array is repeated float, but not that a colour we author with space: 'p3' is treated as wide-gamut rather than reinterpreted.
How to settle it. Write a saturated P3 green and the same values as sRGB side by side, open on a P3 display, and confirm they differ. For dashes, write [4, 2] and compare against a 4/2 dash set in the inspector.
21. Table of contents (rules read + write, cached entries read) β
Risk if wrong: π‘ low
Group: Text & styles
Status in the matrix: β
read + write
Claim. Pages regenerates a TOC whose collection rules we changed, and honours the new rule set.
Why the suite cannot settle it. Rules are an instruction the app acts on at its next repagination. Nothing offline repaginates, so the change is visible in the archive but its effect is not.
How to settle it. Turn a heading style off in the TOC settings, save, open in Pages, and check the TOC drops those headings after it redraws.
Settled β
30 claims have been checked in the app and moved off the list above. The reasoning is kept, because it is what makes the result mean something; what changed is that it is no longer a request.
β Builds (animations): read and retime β
Was claimed. the build model reads a real animation correctly
Why it needed an app. not one of the eight decks in the corpus, spanning 2013 to 26.1, contains an animation
Outcome. Half confirmed, half refuted (2026-08-03, the first animated deck anywhere), then closed whole when the deck's bytes arrived. Confirmed: three builds survive authoring and resave, the slideβbuild graph reads correctly, and delivery stores English display strings ("All at Once", "By Paragraph") even under a Danish UI. Refuted: every database_* field read for effect and timing was absent from the app-authored builds β including one given 3 s duration and 1 s delay by hand β so modern Keynote packs effect and timing into animationAttributes. The returned deck settled that field as KN.AnimationAttributesArchive (in the vendored schema all along): effect strings in two schemes, duration/delay doubles, and per-chunk timing on staged delivery. The deck is now the fixture the readers are pinned against
β Categories: regrouping rows after an edit β
Was claimed. a row whose grouping value changed appears under its new group heading in Numbers, and per-group summaries β where a table has any β follow it
Why it needed an app. the offline check reads the tree this library just wrote, using the reader that shares its assumptions. Whether Numbers honours a rebuilt tree, or recomputes its own and ignores ours, is not visible from the file.
Outcome. Confirmed in Numbers β the move half. Bear appears under Fruit. The summaries half is untested and cannot be tested here: that fixture declares zero TST.ColumnAggregateArchive entries, so its group headings show no counts or totals at all, and there is nothing for a regroup to get wrong. regroupCategories does not touch aggregates, which is correct only if Numbers recomputes them; on a table that does declare a summary, moving a row between groups would change both groups' totals, and nothing here establishes whether ours would go stale. Needs a categorised fixture with a per-column summary, which this repository does not have
β Cell controls (checkbox, star rating, slider, stepper, pop-up menu) β
Was claimed. interaction_type 4 is the stepper and 5 the slider, rather than the other way round
Why it needed an app. the other three widgets identify themselves β a checkbox row holds FALSE/TRUE, a star row is bounded [0β¦5], a pop-up carries a chooser model. Stepper and slider store the identical field set, so nothing in a file separates them. The pairing rests on one slider whose bounds match a published test, plus elimination.
Outcome. Confirmed in Numbers. All four range and toggle widgets β checkbox, star rating, slider and stepper β were opened and each drew as its label said, so the 4/5 pairing is observed rather than inferred. This also settled the larger question underneath it: a control needs a format as well as a spec, and without one the cell renders its value and the widget never appears (FORMAT.md Β§14.7.1). That was invisible to every offline check and is why the widgets had never once been seen before this.
β Character properties (font, colour, highlight, underline, strike, caps, shadowβ¦) β
Was claimed. Clearing a property by writing its *_null flag reads as 'none', not as 'inherit'.
Why it needed an app. We infer that a set *_null flag with the value absent means an explicit clear. Fixtures show the encoding but never disambiguate it from plain absence, because both render the same whenever the parent sets nothing either.
Outcome. Partly settled, and it found a bug. Opening an authored document in Pages showed a character style applying its bold and ignoring its font_color β the word rendered black. Text colour comes from tsd_fill (field 46), not font_color (7); a style with only the latter is valid, round-trips, and does nothing visible (FORMAT.md). Both are now written, the fix is confirmed in Pages on a current-format document β the word renders bold and red β and test/pages-authored-shape.test.ts guards the pairing against the fixture corpus. The *_null question in the claim above is still open; what is settled is that an authored colour reaches the page
β Chart appearance: type and series colours β
Was claimed. a recoloured series shows the new colour, and only in the chart that was edited
Why it needed an app. the suite proves the archives and declarations are right, not that Numbers draws them
Outcome. Half confirmed in Numbers. The recoloured series drew red and the chart was otherwise correct β so the clone-and-repoint worked where it is observable: five other series kept their colours despite the shared archive. The cross-chart half is still unobserved, because the only chart fixture here has a single chart, and a copy-on-write that leaks would need a second chart to leak into. Same mechanism, so the risk stays low
β Comment creation and removal β
Was claimed. a comment this library creates is readable and attributed in the app
Why it needed an app. the suite proves the three archives and the highlight run round-trip; what an author must carry before the comment UI will draw at all took three app rounds
Outcome. Confirmed β "P08 Comment works" β on the third round, each round a distinct finding. Round one (Pages for iOS): unreadable placeholder β the comment carried no author where every corpus comment references one. Round two: with a name-only author, Pages crashed on open β both corpus authors carry the identical comment-yellow TSP.Color and explicit is_public_author = false, and the comment UI draws the author's tint; the corpus rosters also declare refs=[], and the round-one fix had made ours declare the author β the container rule reintroduced by our own repair. Round three, with the author byte-for-byte Apple's shape and the roster declaring nothing: readable and attributed.
β Date fields and bookmarks (read + create) β
Was claimed. a date field and a bookmark this library inserts are live in Pages, not literal text
Why it needed an app. both are attachments whose meaning comes from the app resolving them; the suite proves the archive and the anchor round-trip, not that the app treats them as fields
Outcome. Confirmed, and the bookmark half found a bug. The date field renders set to 1 January and is editable. The bookmark rung marked a 13-character phrase and Pages bookmarked one character β "the B character is a bookmark" β because the writer derived ranged from the name and wrote ranged=false over a 13-character run, a combination no corpus bookmark has. The corpus ties the flag to run length (true at 13 and 46 characters, false at exactly 1) with the name orthogonal, and Pages resolved our contradiction in the flag's favour. ranged now derives from the run, and the corrected form is confirmed: the re-emitted named bookmark with ranged=true spans its full 13-character phrase in Pages β a name-plus-range combination the corpus itself never shows, accepted by the app
β Edit cycle: open β edit β save β reopen β
Was claimed. Pages, Numbers and Keynote open a document this library has edited and saved.
Why it needed an app. The offline suite proves self-consistency: we read back what we wrote. Only the apps can say whether they accept it.
Outcome. Confirmed for all three apps. A current-format Pages document (file format 26.1.0) was edited, saved and opened with its formatting intact β appending a paragraph, applying character formatting, and applying a named paragraph style. Getting there took four separate defects, none of which any offline check could see, and each is now guarded: text colour must go in tsd_fill as well as font_color; a storage must not declare its stylesheet in object_references; paragraphs end at U+0004/U+0005/U+000C as well as U+000A but not at U+2028; and table_para_style is dense while the list and layout tables are sparse. Numbers is covered separately by the widget and regrouping checks β and directly on 2026-08-03, when the e2e suite's cell-write round-trip passed: Numbers opened a package whose cells we wrote and read our values back. Keynote joined the same day: the speaker-notes round-trip β our edit of the notes storage, opened and reported back by Keynote β passed on a current install, and Keynote also opened the deck whose transition we wrote
β Footnote creation and removal β
Was claimed. a footnote this library creates is numbered and laid out by Pages
Why it needed an app. numbering and layout are the app's; the suite proves the archives and anchors round-trip, and three app rounds proved what the archives must also carry
Outcome. Confirmed in Pages β "P09 pass" β after three rounds, each of which found a distinct defect class. Round one crashed the app: every newly created attribute table was seeded with an objectless entry at index 0, fatal in the point-anchored table_footnote/table_attachment where an entry is an object at a position (107 such tables in the corpus, zero objectless entries β the seed is now shape-aware). Round two rendered and numbered the note but drew the reference on the baseline: every corpus mark, body U+000E and note U+FFFC alike, is covered by one shared anonymous character style whose whole bag is superscript = 1, and we wrote none. Round three: the note renders small in Footnote style, the mark is a raised number, and the note storage carries the six attribute tables all 2676 corpus storages have.
β Formula writing (authoring an AST) β
Was claimed. Numbers recalculates a formula this library wrote β replaced or fresh β rather than trusting the stale dependency tracker beside it.
Why it needed an app. The calc engine keeps a per-cell dependency tracker (TSCE.FormulaOwnerDependenciesArchive lists exactly the formula cells, with precedent edges β measured on the issue102 fixture), and setFormula does not update it: a replaced formula keeps stale edges, and a fresh formula cell is missing from the tracker entirely. A same-text replace is proven byte-identical and needs no app check; whether the engine rebuilds the tracker on open, or trusts it, only Numbers can say.
Outcome. Confirmed β Numbers recomputes. The e2e recompute probe (2026-08-03, 17 of 17): a fresh formula written with a deliberately wrong cached value (=B2*2 cached as 999 over B2 = 100) opened in Numbers reporting 200 β the recomputed truth, not our cache. So the engine does not trust the per-cell dependency tracker setFormula leaves stale; it rebuilds on open, and no tracker write is needed for app correctness. The probe runs on every e2e pass (test/e2e/authoring.e2e.test.ts), so a future Numbers that starts trusting the tracker fails loudly. Bisect rungs 19β21 are superseded
β Headers & footers (3 columns Γ first/even/odd) β
Was claimed. header and footer text written into the section masters renders on the page
Why it needed an app. headers live on section page masters; only layout proves the storages are the ones drawn
Outcome. Confirmed in Pages β "P05 pass". Centre-column header and footer text written into every page-master variant renders in the page chrome.
β Hyperlinks β
Was claimed. a hyperlink this library inserts is live in the app
Why it needed an app. a link is a smartfield run plus a URL ref; the field makes it live, the Link style makes it look live, and only the app proves either
Outcome. Click confirmed β "P04 pass" β appearance was not, and is now written. The linked words were a live hyperlink and did not look like one: every native link run in the corpus is covered by the document's Link character style (identifier character-style-hyperlink, name "Link", bag exactly {underline: 1}), which every corpus template ships and insertLink never applied. It now applies it by default, with characterStyle: false to skip and an id or identifier to override; the underlined form is unverified in the app
β Inline image insertion β
Was claimed. an image this library inserts inline appears on the page at the size asked for
Why it needed an app. The shape audit found the archive incomplete in exactly the way a cell control with no format was β every omission optional, nothing offline objecting.
Outcome. Confirmed in Pages β "P11 pass". A 1x1 red PNG inserted inline and scaled to 72pt renders as a red square at the size asked, on the current-format base. This was the rung that had never been opened at all, and it shipped with four shape-audit fixes applied together: the theme's image-0-imageStyle reference (all 83 corpus images carry one), naturalSize alongside originalSize, flags/interpretsUntaggedImageDataAsGeneric, and the four attachment offset fields (101 of 101 corpus attachments). All four rode in one file, so which were necessary rather than merely corpus-true is not isolated β they are cheap, measured, and stay
β Merged cell ranges β
Was claimed. Numbers accepts a merge this library wrote, and shows it where we put it.
Why it needed an app. Recreating one of Apple's merges reproduces the whole file byte-for-byte, which is as far as offline proof reaches β a fresh merge additionally mints a ledger tile object, and whether the engine is satisfied with it is the app's call alone.
Outcome. Accepted and re-emitted (2026-08-03, 17 of 17). The e2e merge-survival test writes a fresh merge β ledger tile minted β into a fixture, has Numbers resave the entire package, and finds the merge intact in the app's own rewrite: the engine accepted our records and re-encoded them from its model. The visual half (one cell spanning, text intact) rides along with any future rung-06 glance, but acceptance is no longer in question
β New document from nothing (blank) β
Was claimed. Pages, Numbers and Keynote each open a blank() document and read our edits back.
Why it needed an app. The donors round-trip offline and take edits, but only the apps can say they accept a package whose previews are stripped.
Outcome. Confirmed in all three apps (2026-08-03, 17 of 17 β twice) β Pages reported our paragraph, Numbers our cell and recomputed formula, Keynote our presenter note, each from a preview-stripped blank(); the Basic White Keynote donor passed the same suite the day it replaced the 2018-era deck. The house restyle that followed (Palatino body, gray secondary, terracotta accent) is checked by the font read-backs the suite now carries: Pages and Keynote each report the typed text's font, so a donor whose styling the app ignores fails visibly
β Page numbers and page counts (insert, read, remove) β
Was claimed. a page-number attachment this library inserts renders as a live number
Why it needed an app. the value comes from pagination, which nothing here performs β the suite proves the archive and anchor round-trip, not what appears on the page
Outcome. Confirmed in Pages. A page number inserted into the body renders as a live number ("P06 pass"), a page count updates when a page is inserted, and a date field renders and is editable as a date. All on current-format documents
β Page setup (size, margins, orientation) β
Was claimed. page size and orientation this library writes are what Pages lays out
Why it needed an app. layout geometry is the app's; the fields could have been advisory
Outcome. Confirmed in Pages β "P10 pass". A rung written as corpus-exact A4 landscape (841.89 x 595.28 pt, orientation 1) renders as a page noticeably wider than tall. The first round was unjudgeable and taught the encoding: every corpus document stores its real geometry in the width/height fields β the one wide document is 2880x2304 with orientation 1 β so the flag is metadata and swapping the dimensions is what makes landscape
β Paragraph & character styles (by name, plus creation and editing) β
Was claimed. a paragraph style this library creates appears in the app's paragraph styles panel, so a person can reapply it
Why it needed an app. Nothing offline distinguishes a listed style from an unlisted one except by correlation with the corpus, and every correlation found so far has been necessary at best. Four rounds of guess-and-check is where guessing stops paying.
Outcome. Confirmed in Pages β "P15 works now". A created style applies as asked and appears in the paragraph styles panel, on the current-format ladder base. What it took, cumulatively: a super.name; a super.identifier plus a matching identifier_to_style_map entry; both property bags; and an entry in TSWP.ThemePresetsArchive.paragraph_style_presets β the theme list the panel reads. The earlier failures were real: the first three alone left the style applying but unlisted. One fine point went unrecorded: the confirming report did not itemise the density pair (P15b, bags copied from Body, against P15c, three properties), so whether a sparse property bag alone lists is not established β copyOf exists either way
β Paragraph background & borders (rule stroke + positions) β
Was claimed. border_positions 0/1/2/3/4 means none / top / bottom / top and bottom / all.
Why it needed an app. The old mapping was inferred from the inspector's five choices and the deprecated enum's shape; every value in the corpus was 0, 1 or 2, so nothing could contradict it offline.
Outcome. Refuted, and replaced with the measured truth: a bitmask with logical side bits. Three seed-borders rounds (2026-08-03, Pages, Danish UI): top/bottom/both/all carried 1, 2, 3, 15 β the union proving flags β red left-only wrote 4 and blue right-only 8 in LTR paragraphs, and the closing round put a green left-edge border on a genuinely RTL paragraph (direction written by this library, accepted by the app) and stored 8: the side bits are logical β 4 leading, 8 trailing β swapping visual sides with the paragraph's direction. The enum reading (ALL = 4) would have drawn one leading edge where a box was meant
β Paragraph writing direction (read + write) β
Was claimed. a paragraph this library sets to RTL renders right-to-left in Pages
Why it needed an app. rendering direction is editor behaviour nothing offline can observe
Outcome. Confirmed in the same round trip that taught the mechanism (2026-08-03, iOS Pages, T15.3 writer): the pair was copied from an app-flipped paragraph, and a library-written (1, 0) then survived the app's resave untouched with the paragraph behaving as RTL β its left-edge border control stored the trailing bit, which only an RTL paragraph does
β Placeholder text (list, fill, define) β
Was claimed. a span this library defines as placeholder behaves as one in Pages β a click selects the whole span and typing replaces it β and a filled placeholder behaves as plain text
Why it needed an app. the written archive is byte-shaped like the app's own, but tap-to-replace is editor behaviour nothing offline can observe
Outcome. Confirmed in full, through the native lifecycle (2026-08-03, iOS Pages, T15.3 writer, via seed-placeholder): one tap selected the library-defined span whole, typing replaced the entire span, and the returned resave shows the field consumed β exactly what the app does to its own placeholders. The filled line edited as plain text, so fillPlaceholder sheds the marking correctly. The round trip is also the project's first iOS-written artifact over library-authored bytes
β Placement (copy onto a page/slide/sheet, remove, reorder in z) β
Was claimed. A drawable we copied onto another page/slide/sheet appears there, at the geometry we set.
Why it needed an app. The three apps store the list differently β two lists in Keynote, one in Numbers, per-page wrapped entries in Pages β and each app decides for itself whether an object it owns is renderable. Reloading through this library proves the wiring, not the rendering.
Outcome. Confirmed in Pages for both placement shapes β "Both p19 work now". A drawable copied onto the page it already lived on, and onto a fresh page needing a new page group, both render. What it took beyond the page group: the copy must join the document-level TP.DrawablesZOrderArchive, the paint order β a drawable in a page group but absent from it does not draw at all, with no warning. Pages keeps paint order per document where Keynote and Numbers keep it in-container, so this is the one app where attach() alone was never enough. Keynote and Numbers placement is still unverified in-app
β Pop-up menu creation (TST.PopUpMenuModel) β
Was claimed. a TST.PopUpMenuModel built from the schema is one Numbers will open and draw
Why it needed an app. every other control was measured against a real one before being written. This one could not be, and the failure mode just demonstrated by cell controls is precisely a structure that is valid in every offline respect and still does not render β required fields present, reader agrees, app shows nothing. A menu has more surface for that than the others: it is two archives and a cross-object reference rather than one flag.
Outcome. Confirmed in Numbers, after the first attempt was quietly wrong. The model was accepted and the menu drew, but offered one fewer choice than it was given β the first. Three candidate readings of tsce_item[0] were written as three documents, and the decisive one was putting a copy of the selected value there: all choices came back, but the menu marked none of them current, so slot 0 is the None entry rather than a selection. It takes a bare NIL_TYPE, the choices start at index 1, and chooser_control_start_w_first governs only whether that entry is offered as a row (FORMAT.md Β§14.7.2). Text and numeric menus both verified.
β Presentation settings (mode, loop, autoplay delays, slide size) β
Was claimed. Keynote renders a deck whose canvas this library resized.
Why it needed an app. slideSize is one TSP.Size on the show; nothing else references it, so nothing offline can prove the app re-lays content out rather than ignoring or refusing the change.
Outcome. Confirmed β "K10 size was 4:3 on iPhone". The resized canvas renders 4:3 in Keynote for iOS, the second platform this ladder has been checked on (the Pages comment rung was the first). One TSP.Size on the show is all it takes, and the app re-lays out for it.
β Sections (read + insert) β
Was claimed. a section break this library inserts starts a new section on a new page
Why it needed an app. pagination is the app's; the table entry alone was well-formed, listed in the sidebar, and paginated nothing
Outcome. Confirmed in Pages β "P07 passed" β on the second round. The first check failed ("not on a new page") and taught the rule: all 28 section boundaries across the five multi-section fixtures put U+0004 where the previous paragraph's newline was, and we wrote only the table_section entry β Pages listed the section and kept the text flowing, because the table names a section and the character breaks the page. With insertSectionBreak swapping the terminator (same length, so every attribute-table index survives) and keeping the clone's name, the second paragraph renders on its own page.
β Skipped slides β
Was claimed. Keynote treats a slide this library marked skipped as skipped.
Why it needed an app. The write is one bool on the slide node. No corpus deck carries it true, so even the read side rests on the schema alone β this rung is the first evidence in either direction.
Outcome. Confirmed in Keynote β "K09 passed". One bool on the slide node, and the app honours it: the marked slide stayed out of the presentation. First evidence in either direction for this flag β no corpus deck carries it true.
β Slide management (add, duplicate, move, remove) β
Was claimed. Keynote opens a deck we added, duplicated, moved or removed slides in, and shows them in order.
Why it needed an app. A slide is only as valid as the graph around it β placeholders, builds, the master reference. The offline audit removed four defects before any Mac (an undeclared slide node, orphaned clones, undeclared guide storage, placeholders declaring their slide). The app then found a fifth the audit could not see: "K04 added an empty slide" β the add-without-content path stripped owned_drawables and drawables_z_order wholesale, and on decks that list their placeholders there (8 of 12 on the ladder base; 0 of 33 on another β which is why no ubiquity threshold fired), Keynote painted nothing, our written title included. The copy now keeps its cloned placeholders in whichever lists its source used.
Outcome. Confirmed in Keynote β "K05 passed / K06 passed / K07 passed", then "K02 and K04 pass now". Add, duplicate, remove and reorder all hold on the current-format base. It took two rounds of measurement: the offline audit removed four graph defects before any Mac (undeclared slide node, orphaned clones, undeclared guide storage, placeholders declaring their slide), and the app then exposed the fifth no threshold could see β stripping the paint-order lists on a deck that lists its placeholders there rendered the added slide empty. The copy now follows its source's own listing convention, and the app draws it.
β Slide placeholders (title, body, slide number) β read and fill β
Was claimed. Keynote shows placeholder text this library wrote, styled by the layout.
Why it needed an app. Placeholder text goes through the shared storage writer into a shape the layout styles. First round: the text half passed β "K00 - 03 passed", K01's title and K02's body both rendered β but K02's subtitle styling did not: "the textareas default style of subtitle was replaced by normal". Measured cause: the base's empty storage carries one paragraph entry at 0, the rebuild misread it (0 === 0) as a trailing terminator, and the refilled table gained an end-of-text entry Apple never writes β the app dropped the style run. Fixed; the paragraph entries are now exactly the paragraph starts.
Outcome. Confirmed in Keynote β "K02 and K04 pass now", after one round each way. K01's title passed first contact; K02's body text rendered but lost its subtitle styling, because filling the base's empty storage misread its one paragraph entry at 0 (0 === 0) as a trailing terminator and manufactured an end-of-text entry Apple never writes. With the entries restored to exactly the paragraph starts, the subtitle style survives the fill β the app confirmed it, and the corpus settled the bystander question on the way: object-less inherit entries are Apple's own majority convention (2379 of 3416).
β Speaker notes β
Was claimed. Keynote shows presenter notes this library wrote.
Why it needed an app. Notes reuse the shared text-storage writer, which is app-confirmed in Pages β but a NOTE-kind storage hangs off a KN.NoteArchive no Pages document has, and only Keynote can say the chain holds.
Outcome. Confirmed in Keynote β "K00 - 03 passed" on the first decks this library ever put in front of the app (current 26.1.0 base; K03 is the presenter-notes rung). The container layer, title and body placeholder writes, and the KN.NoteArchive chain all held on first contact β after the offline shape audit had already removed four defects no app ever saw.
β Transitions β
Was claimed. Keynote honours automatic advance written into the transition attributes.
Why it needed an app. The animationAttributes chain is where both auto-advance and named effects live. Auto-advance uses only corpus-verified fields (is_automatic, delay), so it is the half we can claim; a pass also proves the chain itself accepts our writes, which is the prerequisite for effects later.
Outcome. Confirmed in Keynote β "K08 passed". The deck advanced by itself after ~2 seconds on the current-format base, which also proves the animationAttributes chain accepts our writes β the prerequisite for named effects, which stay blocked on measuring a real effect string (the corpus knows only "none"; the animated.key ask in docs/BLOCKERS.md settles it).
Recording an outcome β
When a claim is checked by hand, do not delete its entry β add settled: to its manualProof block saying what was observed. The claim moves to the section above, keeping the reasoning that made it worth checking. If the check fails, that is a bug report with a reproduction already written.