Skip to content

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 14 claims, 1 is 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 ​

#RiskCapabilityClaimAutomated?
1πŸ”΄ highDrawables & media β†’ Inline image placement in an indented columnan inline image sits in the text column of an indented paragraph, not at the page marginmanual
2πŸ”΄ highKeynote β†’ Slide copies in a Keynote-saved deck (per-slide components)Keynote SAVES a deck in which this library added or duplicated a slide of a Keynote-saved, per-slide-component deck.manual
3🟠 mediumDrawables & media β†’ Floating (non-inline) drawable placementa drawable copied into a page's floating list is placed and rendered by Pagesmanual
4🟠 mediumKeynote β†’ Pictures on slides (addImage)Keynote draws a picture this library added to a slide, at the size and position asked.manual
5🟠 mediumNumbers & tables β†’ Add and remove tables on a sheetthe app keeps a library-minted registration, so a cross-table reference to the clone computes on first openmanual
6🟠 mediumNumbers & tables β†’ Conditional formatting: apply an existing rule set to more cellsre-pointing a cell's conditional-style key makes Numbers apply that rule set to itmanual
7🟠 mediumNumbers & tables β†’ Formula function namesThe function-index table is incomplete, and every unnamed id is visible rather than guessed.test:e2e
8🟠 mediumNumbers & tables β†’ Formula reading (AST rendered to text)Rendered formula text matches what the app shows in its formula bar.manual
9🟠 mediumNumbers & 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
10🟠 mediumNumbers & tables β†’ Table styling (banded rows, grid strokes, visibility)Banded rows, grid strokes and the visibility toggles render as set.manual
11🟑 lowNumbers & tables β†’ Categories: enable or disable groupingflipping is_enabled makes Numbers group or ungroup the rowsmanual
12🟑 lowNumbers & tables β†’ Conditional formatting rulesthe second conditional id in a cell record (COND_RULE_STYLE_ID) is a cache the app rewrites, so preserving it verbatim is enoughmanual
13🟑 lowText & 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
14🟑 lowText & 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. Slide copies in a Keynote-saved deck (per-slide components) ​

Risk if wrong: πŸ”΄ high
Group: Keynote
Status in the matrix: βœ… read + write

Claim. Keynote SAVES a deck in which this library added or duplicated a slide of a Keynote-saved, per-slide-component deck.

Why the suite cannot settle it. A Keynote-saved deck keeps each slide in a Slide-<id> component, and a field report measured the squatting copy's failure precisely: the deck opens and renders, and saving fails β€” "Dokumentet kunne ikke gemmes automatisk", AppleScript save error -10000 β€” with a clean bisect (text edits save; duplicateSlide alone fails) and an app-side control (Keynote-made duplicates edited by this library save fine). The copy now arrives in a fresh component mirroring the donor's registration. Two deliberate differences from the app's own shape remain, each accepted in a measured save but never exercised together: no per-object UUID map entries, and one object-level row for the master where the app writes a component-level row alone.

How to settle it. Load a deck Keynote has saved (per-slide Slide-<id> components in Index/), duplicateSlide any slide, save, open in Keynote, edit something small, and let it save (autosave or ⌘S). The failure mode of the old shape was at SAVE, not open β€” a deck that opens and renders proves nothing yet.

3. 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

4. Pictures on slides (addImage) ​

Risk if wrong: 🟠 medium
Group: Keynote
Status in the matrix: βœ… read + write

Claim. Keynote draws a picture this library added to a slide, at the size and position asked.

Why the suite cannot settle it. The archive matches the corpus's 22 slide images field for field β€” parent, wrap, aspect lock, stand-ins, style, component placement β€” and a field report's hand-built adaptation of the same recipe rendered. But an image with a well-formed archive and any one wrong linkage is the invisible-control failure class: valid, complete, never drawn. Only the app can say it paints.

How to settle it. addImage a PNG onto a fresh deck's first slide, open in Keynote: the picture must show centered at its intrinsic size, selectable and movable like an inserted one. A blank slide means the linkage failed somewhere the audit cannot see; say which slide you see.

5. Add and remove tables on a sheet ​

Risk if wrong: 🟠 medium
Group: Numbers & tables
Status in the matrix: βœ… read + write

Claim. the app keeps a library-minted registration, so a cross-table reference to the clone computes on first open

Why the suite cannot settle it. the clone itself is first-class β€” two rounds opened it, showed its cells, and the checker's own formula referencing it by name computed β€” but both rounds the app discarded the written identity and re-registered, tombstoning the library's stored references into ref errors. The mint now enrolls the full owner family at all three engine sites (archives, tracker list, owner map, internals past the map's max); whether that is the whole acceptance test only an open can say.

How to settle it. npm run demos -- out, open demo-06-formler.numbers. F-03: the C cell under the rung shows 5 and, tapped, the formula bar shows =CrossCheck::B3; F-06 likewise 8 with =NoCalc::B2; CrossCheck's top row 7. A bare number with no formula behind it means the app again reduced the reference to its cached value, and the next site to write is the engine's uuid_reference_map, populated in the round-five return by the checker's own typed twins.

6. 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

7. 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:e2e on a Mac with the app installed.

8. 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).

9. 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.

10. 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.

11. 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

12. 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

13. 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.

14. 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 ​

42 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.

βœ… Cell display formats (number, currency, percentage, date, duration, text, boolean) ​

Was claimed. A format we write makes Numbers display the value the way the inspector would.

Why it needed an app. 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.

Outcome. Confirmed in Numbers β€” demo-05. Currency (kr., two decimals, record type 10 with the full format tail), percentage, number decimals, date and duration all display as the inspector states them, and the checkbox draws once written as the app writes it β€” format 263 plus the record's control id plus the control-spec entry, the trio the returned one-delta seed measured (Β»Checkboks vistΒ«). Custom formats remain read-only

βœ… Cell styling (fill, four borders, padding, alignment, wrap) ​

Was claimed. A cell style we create is picked up by the app and rendered, and the style table stays consistent.

Why it needed an app. 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.

Outcome. Confirmed in Numbers β€” demo-05, four rounds. Fill, padding and vertical alignment drew from the first round; borders drew once they moved to the stroke sidecar with the grid brought to the table's size (Β»2pt terrakotta hele vejen rundtΒ«); the centred merge confirmed horizontal alignment riding the cell's text style. The person's own app-written border in a returned round matched our runs byte for byte

βœ… 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: axes, legend, gridlines ​

Was claimed. Pages draws the chart without the gridlines this library switched off.

Why it needed an app. 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.

Outcome. Confirmed in Pages β€” demo-04 pass. The value axis's horizontal gridlines were switched off by the library and the app drew the chart without them, with the library-rewritten data and renamed series drawn as written

βœ… 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

βœ… Chart data editing (values, names, series, categories) ​

Was claimed. a series added or removed here leaves the chart's styling on the right series

Why it needed an app. styling is applied at render time from arrays indexed by series position; the suite proves the indexes shift, not what the app draws

Outcome. Confirmed in Pages β€” demo-04 pass, whole. Library-rewritten data (two series by four categories), renamed series and categories, all drawn as written in the corpus document's own column chart. The remove-a-middle-series case is untested, but the id map and sparse arrays the demo exercised are the same machinery

βœ… 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.

βœ… Conditional formatting: authoring new rules ​

Was claimed. a rule set this library authors draws its fills the moment Numbers opens the document

Why it needed an app. The first demo round proved the halfway state: rules written without ledger records showed correctly in the inspector and never evaluated β€” no fill until a covered cell was deleted and re-typed, at which point the app registered exactly the re-typed cells (olekristensen-v26.3-demo07-rules-returned.numbers carries that aftermath). This is the opposite behaviour of cell formulas, which the engine recomputes on open with no tracker write at all β€” so the ledger cannot be assumed either way; each owner kind had to be measured.

Outcome. Confirmed on open, and the app keeps our ledger. The rebuilt demo drew every fill with no cell touched β€” the rung that asks for missing cells came back without one β€” and the app's save preserved all seven library-written records intact (olekristensen-v26.3-demo07-rules-round2.numbers). The reviewer's remaining notes (numbers still left-aligned, but only where no rule matched, plus the slider and stepper) isolated a separate omission: value cells were written without their type's default format, which every app cell carries β€” fixed on the cell-writing row.

βœ… 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

βœ… Drawable shadows (enabled, angle, offset, blur, opacity) ​

Was claimed. A shadow we parameterise renders with the geometry we set, the enabled flag gates it, the contact and curved types draw as their kind β€” and the app survives editing our shadow in its own inspector.

Why it needed an app. Angle, offset and blur radius are rendering parameters, and the type and enabled fields are pure app behaviour. The first round proved rendering (S-01–S-07, S-09, S-10 confirmed) and found the harder half: re-enabling the disabled shadow through the app's popup aborted Pages whole β€” the archive rendered but asserted under edit, missing the type field all 929 corpus shadows carry, on an override style shaped like no app file's. Both are rewritten to the measured shape; the toggle is the remaining check.

Outcome. Confirmed in Pages, whole β€” Β»det virkedeΒ«. Every parameter renders as written (both angle checks on the calibrated scale, offset, blur, opacity, colour, the disabled state, contact and curved types), and the seven-field rewrite survived the app's own popup where the six-field archive aborted it. The toggle wrote the app's fresh preset over our archive β€” stored angle 90/inspector 270Β°, offset 2, blur 5, 50 % β€” and the round's returned file is a corpus fixture carrying the popup preset, the contact sub-archive and the first curvedShadow sub-archive

βœ… Drawable style (fill, stroke, opacity, shadow, reflection) ​

Was claimed. a reflection this library writes renders as a fading mirror below the drawable

Why it needed an app. reflection is a single opacity float on the style archive; the suite proves it round-trips, not that the app draws the mirror

Outcome. Confirmed in Pages β€” Β»ja, det virkerΒ«. The single reflection float mirrors the square below itself; demo-11 S-11, second round

βœ… 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

βœ… Filters: enable, disable, combining mode ​

Was claimed. enabling a filter set makes Numbers apply its rules

Why it needed an app. 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

Outcome. Confirmed in Numbers, both directions. The demo built on that very document β€” filter set disabled by this library β€” opened with all ten data rows visible, and re-enabling the filter through the app's own panel hid the non-matching rows (Β»Da jeg slog dem til fungerede detΒ«). A library-flipped flag is one the app honours and can flip back.

βœ… 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 text written by the library renders in the page-wide field, with the alignment its storage's paragraph style states.

Why it needed an app. Two demo rounds measured the model whole: modern Pages draws one page-wide header field bound to storage slot 1 β€” of SPALTE-A/B/C only B appeared, left-aligned at the page edge, while Β»Sektion 1Β« in the same slot rendered centred under its donor's centring style β€” and slots 0/2 are the legacy three-field layout's outer slots, whose mode switch no candidate byte survived (document 49, settings 13, section 28 all refuted). The same rounds verified the master-cloning fix (section 3's own header) and fields, bookmarks, footnotes, comments and placeholders silently.

Outcome. Confirmed on the third round (2026-08-11, Pages macOS): demo-02 settled whole. Three rounds, each converting a fault into a model: round one found the shared masters and the undrawn empty-slot writes, round two measured the page-wide slot-1 field and the style-borne alignment, round three rendered the stated expectations. Sections, headers and footers with live page numbers, the date field, bookmark, footnote, comment, both placeholder behaviours and the cloned section masters all render as written.

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

βœ… Image cropping (set, move, remove a mask) ​

Was claimed. a mask this library writes crops the way Apple's does, and the app's mask editor opens it

Why it needed an app. the crop is a rendering result; the suite proves the geometry and path round-trip, not what appears on the page. The editor is the harder half: rendering has been confirmed for three rounds while double-click editing kept refusing, and each round has closed one measured difference (full drawable super, window space, size semantics, traced_path, the modern super with lock pair and stand-in title/caption)

Outcome. Confirmed in Pages, whole β€” Β»der Γ₯bnede den!Β« The crop renders in both arrangements; the mask editor engages on the floating cropped copy and refuses the in-flow one, which is the app's behavior for images in the text flow, not a property of the file. The crop-delta seed corroborates from the other side: the app's own crop over a library-inserted image produced the same shape setCrop writes, field for field

βœ… 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. A border authored by this library draws in Pages: complete stroke, border_positions and deprecated_borders together.

Why it needed an app. The positions bitmask is settled app knowledge, but every rung that drew a border had the app author the stroke. Two rounds of demo-01 T-10 found two faults under each other: an abbreviated stroke read as Β«IngenΒ» (fixed β€” the writer states the 167-of-167 corpus shape), and with the stroke honoured the side toggles stayed unselected β€” the inspector keys on deprecated_borders, the historical enum the app writes beside the bitmask on every bordered corpus style. The writer now states both.

Outcome. Confirmed on the third round (2026-08-10, Pages macOS): all three border lines drew. Two faults sat under each other, each named by in-document feedback β€” the abbreviated stroke read as Β«IngenΒ» (round one), then honoured colours/type/width with the side toggles unselected (round two), which identified deprecated_borders as the field the toggles key on. The rendered gap between text and horizontal rules is the app's default β€” neither we nor the app's own authored border styles write a rule offset. Settled as part of demo-01 whole: all fourteen checks, character formatting through decimal tabs.

βœ… Paragraph rule offset (text-to-border distance) ​

Was claimed. A positive ruleOffset moves the border rules away from the text.

Why it needed an app. The negative direction is app-measured β€” βˆ’12 rendered the rules overlapping the paragraph β€” and zero is the app's own stated default. But every non-zero corpus value is negative, so outward movement is implied by symmetry, never shown; and the inspector displayed βˆ’2 for the stored βˆ’12, so the control and the archive are not the same scale, or the display clamps.

Outcome. Confirmed (2026-08-12, Pages macOS): +12 renders the wider gap, and the two inspector readings calibrate the scale β€” stored 0 displays 6 pt, stored +12 displays 18 pt, so the stored value is relative to the 6 pt default and the inspector shows the absolute offset. Demo-01 settled whole, all fifteen checks.

βœ… 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.

βœ… Sheets (add, duplicate, rename, move, remove) ​

Was claimed. Numbers opens a document whose sheets we added, duplicated, renamed or reordered.

Why it needed an app. 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.

Outcome. The structural half is confirmed; where the document opens was the surprise. A library-added sheet (cloned, renamed, moved first) opened alongside the original with both tabs named as written and the cloned table's cells intact. But the app opened on the other tab: tab order does not pick the active sheet β€” the UI state's stored sheet selections do, and the demo had left them pointing at the donor sheet. setActiveSheet re-points them; one later round opened on the re-pointed sheet and a still later round did not, so the re-pointed selections are necessary at most, not sufficient β€” the deciding state is an open question again.

βœ… 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.

βœ… Table cell writing (text, number, date, bool, duration) ​

Was claimed. Numbers, Pages and Keynote open a package whose cells we rewrote, and display the values we wrote.

Why it needed an app. 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.

Outcome. Right-alignment confirmed on open. Three review rounds triangulated the mechanism β€” automatic alignment is the absence of the per-cell text style, and the template's do-nothing style (alignment: 0) was the left-pin β€” and the round after the writer began dropping that style came back all green, every number right-aligned with no cell touched, resaved clean by the app. One write path had missed the drop: setFormula without a cached value left formula results left-pinned beside right-aligned plain values, named cell by cell in the next returned demo β€” the drop now runs on every formula write, with app verification riding the next demo-06 round.

βœ… 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.

MIT licensed. Independently made β€” not by Apple in California. Not affiliated with or endorsed by Apple Inc.