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 0 claims, 0 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? |
|---|
Settled
67 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.
✅ Add and remove tables on a sheet
Was claimed. the app keeps a library-minted registration, so a cross-table reference to the clone computes on first open
Why it needed an app. 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.
Outcome. Settled, Numbers 15.4 (M15.4-7051.0.79-2): once the re-mint also writes the table model's table_id string as the new base, the app's own save keeps both clones' identities (id strings and owner bases unchanged), formulas in the donor naming the clones read 5 and 8, a formula the checker typed naming the clone read 10, and the clone's own cross reference and =SUM(B) read 7 and 30. The probe in test/e2e/proofs.e2e.test.ts is on. A table added since takes the theme's first table preset, as Insert > Table does, instead of a copy of every preset style; Numbers 15.4 opened a document with two such tables and drew them in the theme's table style (its PDF export, no person).
✅ 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
✅ Builds: creating an animation
Was claimed. Keynote plays a build this library wrote from nothing.
Why it needed an app. The suite proves the archive matches the deck's builds field for field; a build the app silently drops looks exactly like one never written, so only playing the deck can say.
Outcome. Confirmed in Keynote 15.1.1. The checker opened seed-build.key, played it and wrote "It worked" as a comment on the slide (olekristensen-v26.0-mac-build-seed.key). The app kept the build and its chunk field for field, reseeded the random number and minted new chunk uids, and dropped the two references the library had declared on them: a build's drawable and a chunk's build are carried and never declared, which the container rule now states.
✅ Categories: defining a category by a column
Was claimed. Numbers draws a category this library defined from nothing, with the groups collapsed under their values.
Why it needed an app. The suite proves the archive matches the corpus shape field for field; whether the calc engine accepts a grouping whose internal formulas it did not register itself is the app's to say.
Outcome. Confirmed in Numbers 15.1. The checker opened seed-category.numbers and wrote "Works" as a comment on the sheet (olekristensen-v26.0-mac-category-seed.numbers). The app kept the group column, the tree and its node ids, re-minted the node uids, and dropped the donor's old root node, whose declaration the library had left on the archive; addCategory now retires it with the node.
✅ Categories: enable or disable grouping
Was claimed. flipping is_enabled makes Numbers group or ungroup the rows
Why it needed an app. the suite proves the flag round-trips and the tree survives, not that the app acts on it
Outcome. Settled, Numbers 15.4 (M15.4-7051.0.79-2), the checker's own save of Numbers rung 25: the table opened flat, the category was there to switch on and regrouped the rows, and switching it off again flattened them ("i turned it on and off - it worked"). In the bytes the app left the disabled definition untouched, re-sorted the rows into group order and kept that order when disabled, so a disabled definition's cached tree is stale by index; it also synced the inline copy under the table model's category_owner_deprecated to the flag, which setEnabled leaves as it was.
✅ 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. Pages 15.4's own export of rung P20 differs from its base's only inside the chart's box and on the page carrying the rung's appended paragraph, the gridlines absent.
✅ 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 rules
Was claimed. 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 it needed an app. 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
Outcome. Settled, Numbers 15.4 (M15.4-7051.0.79-2), the checker's own save of Numbers rung 26: the id does not track the match. Two sets, > 5 green on C2:C4 and = 4 yellow on C5, written with no id; after the checker typed 8 into C2 (now matching) and 7 into C5 (no longer matching), the app had given every cell on the first set 0 and the cell on the second set 15. It is a per-set value the app assigns, so preserving it verbatim is enough and reading it as the applied rule would be wrong.
✅ Conditional formatting: apply an existing rule set to more cells
Was claimed. re-pointing a cell's conditional-style key makes Numbers apply that rule set to it
Why it needed an app. the fixture suite proves the key changes and the file reloads, not that the app honours it — evaluation happens in the calc engine
Outcome. Numbers 15.4: the rule's fill is in the export of the one-cell document, and re-pointing a second matching cell to the set multiplies the count by between 1.6 and 2.6.
✅ 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.
✅ Dash presets (short-dashed, dashed, long-dashed, dotted strokes)
Was claimed. A paragraph border written with the "dotted" preset shows as dotted in the Format sidebar.
Why it needed an app. Pages showed None for a [4, 2] border that drew, and reads the "dashed" preset back as dashed (Pages rung P28, both as direct formatting and as a named style: "worked"). The dotted preset was the apps' dotted shape on shape outlines and chart gridlines; on a paragraph border the control showed None for it ("not showing up").
Outcome. Settled, Pages 15.4 (M15.4-7051.0.79-2), the checker's own save of Pages rung P30 with all eleven menu items applied: the menu's dotted item is [0.001, 2] with round caps and round joins, where ours had miter joins, the one byte the control did not match. The preset now writes the app's stroke archive byte for byte, as do dashed, short-dashed and long-dashed.
✅ 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
✅ Duration default format (a typed duration's stamp)
Was claimed. A duration typed through the library displays in Numbers as a duration typed in the app.
Why it needed an app. No plain duration cell exists in the corpus, so the default is the app's initialiser rather than a measured file; its rendering has not been seen.
Outcome. Numbers 15.4 (Danish UI) shows a 5400-second duration written to a blank document as '1t 30m': the short duration style in the UI's language, which a bare number or a text cell never gets.
✅ 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: adding a rule
Was claimed. Numbers opens a filter this library wrote with the right rows hidden, on the column the rule names.
Why it needed an app. The rule's bytes and the hidden states equal app-authored sets', on columns A, B and C; whether the app re-evaluates a rule it did not author is what only opening the file shows.
Outcome. Confirmed in Numbers 15.4 — only Pear and Train showed, so the rule applied to column C and the host is column 1. The checker wrote "It worked, but you did not size the first row to show all content, and you put a long instruction line there" (olekristensen-v26.0-mac-filter-seed.numbers): the seed had put its instruction in the table name, which the app shows on one unwrapped line. Seeds now keep names short and put the instruction in a wrapped first content row.
✅ 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.
✅ Floating (non-inline) drawable placement
Was claimed. a drawable copied into a page's floating list is placed and rendered by Pages
Why it needed an app. the suite proves the copy resolves, keeps its media and survives a save, not that the app lays it out
Outcome. Refuted for a text box, Pages 15.4: doc.addTextBox lands the box in the page's floating list and Pages lists it there with the asked position and size, but the PDF export carries neither its words nor any ink in its rectangle, with a top-level donor (gomap-v26.1-newest-writer) and a grouped one (patrickomatic-pages26-sections-masks) alike; Keynote's twin draws. Images are unmeasured.
✅ 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 function names
Was claimed. The function-index table is incomplete, and every unnamed id is visible rather than guessed.
Why it needed an app. 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.
Outcome. Complete for Numbers 15.1, read from the app itself. The registration code in the TSTables framework pairs every function name with its index; scripts/function-registry.ts reads both calling shapes it uses and harvest-functions.ts --binary merges the reading with the recorded document harvest, refusing any disagreement. All 270 harvested entries agree, 363 functions are named, and 296 is FIND.CASEINSENSITIVE, so the text-contains condition renders by name
✅ Formula reading (AST rendered to text)
Was claimed. Rendered formula text matches what the app shows in its formula bar.
Why it needed an app. 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.
Outcome. Numbers 15.4: 33 formulas of numbers-parser-v26.1-xlsx-lineage and olekristensen-v26.3-demo06-formulas-returned match the formula bar in references, ranges and operators. The one real difference, a reference to the formula's own table stored as a cross-table node and rendered CrossCheck::C3 where the app shows C3, is fixed. Function names and decimal literals read in the UI's language (MIDDEL, AFRUND, 3,7).
✅ 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.
✅ 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
✅ 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
✅ Inline image placement in an indented column
Was claimed. an inline image sits in the text column of an indented paragraph, not at the page margin
Why it needed an app. 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.
Outcome. Opened in Pages 15.4 and returned without a comment, which the seeds' protocol reads as as expected: the in-flow picture starts in the indented column and the page-placed one at the margin. A look, not a measurement; the returned file is not kept as a fixture.
✅ Master-page drawables
Was claimed. Pages repeats a drawable this library moved to the section master on every page.
Why it needed an app. The archive matches the app's own move field for field; whether the app draws it on every page only the app shows.
Outcome. Pages 15.4: moving the first section's graphic to the section master inks its rectangle on pages 2, 3, 8 and 16 of the sixteen-page section (0.57, 0.56, 0.13 and 0.13 of the rectangle more than the untouched export) and leaves page 17, the next section's first, unchanged.
✅ 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
✅ Pictures on slides (addImage)
Was claimed. Keynote draws a picture this library added to a slide, at the size and position asked.
Why it needed an app. 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.
Outcome. Keynote 15.4 reports one image on the blank deck's first slide at (100, 200), 300 by 150 points, as asked.
✅ 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) and page breaks
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.
✅ Shared style values (colour incl. P3, gradients, strokes, shadows, padding)
Was claimed. A Display-P3 colour we write renders as P3, and a dashed stroke renders with our dash lengths.
Why it needed an app. 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.
Outcome. Settled, Pages 15.4 (M15.4-7051.0.79-2), the checker's own saves of Pages rungs P25 and P27: two styles with the same channels, one tagged p3 and one srgb, drew as two greens ("the colourspace tagging works - it's two different greens"), and a [4, 2] paragraph border drew dashed ("the dashes look fine, as specified"). Found beside it: the Format sidebar shows None as the line style of that [4, 2] border, direct or named, while the app's own on the next line, [2, 2], shows: the sidebar knows only its own menu's dash shapes, now the presets.
✅ 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 background (read + set)
Was claimed. Keynote draws a background this library set as a variation of the layout's style.
Why it needed an app. The suite proves the archive matches the corpus's per-slide styles field for field; a style the app ignores looks like a slide left on its theme, so only the app can say.
Outcome. Keynote 15.4: the export of a blank deck whose first slide got a (0.1, 0.23, 0.16) background is more than half that colour at 18 dpi.
✅ Slide copies in a Keynote-saved deck (per-slide components)
Was claimed. Keynote SAVES a deck in which this library added or duplicated a slide of a Keynote-saved, per-slide-component deck.
Why it needed an app. 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.
Outcome. Keynote 15.4: olekristensen-v26.0-mac-authored.key (per-slide components) with duplicateSlide(0) and addSlide({ copyOf: 0 }) opened, reported two slides more, and saved on command without error; the library reloads the saved deck with the same count.
✅ 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).
✅ Slides from a layout (addSlide master)
Was claimed. Keynote opens a slide this library instantiated from a layout, drawn as that layout.
Why it needed an app. The suite proves the archive matches the corpus's content slides field for field; whether the app draws the copied placeholders in the layout's places, or repairs or refuses the slide, only the app can say.
Outcome. Keynote 15.4 reports the appended slide's base layout as the chosen master and its title as written.
✅ Slides: changing the layout (setLayout)
Was claimed. Keynote draws a slide this library moved to another layout as that layout.
Why it needed an app. The slide's field set and placeholders match the app's own change; whether the app draws the carried title in the new layout's place only the app shows.
Outcome. Keynote 15.4 reports the moved slide's base layout as Title & Bullets, its title and its body as written.
✅ 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.
✅ Table of contents (rules read + write, cached entries read)
Was claimed. Pages regenerates a TOC whose collection rules we changed, and honours the new rule set.
Why it needed an app. 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.
Outcome. Settled, Pages 15.4 (M15.4-7051.0.79-2), the checker's own save of Pages rung P26: with the Subtitle rule switched off, the TOC reopened without its twelve Subtitle headings, 49 entries where 61 were cached, and the style was unticked in the TOC settings ("PASS - the subtitles were not included in the TOC when the doc opened. And subtitle was unticked").
✅ Table structure (rows, columns, bands, sizes, freeze, repeat)
Was claimed. Changed band counts, freeze and repeating-header flags, row heights and column widths take effect.
Why it needed an app. 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.
Outcome. Numbers 15.4 reports header row count 2, footer row count 1, row 3 at 44 points and column 2 at 180 points, as set on a blank document. The freeze and repeat flags are not in the scripting dictionary and stay unmeasured.
✅ Table styling (banded rows, grid strokes, visibility)
Was claimed. Banded rows, grid strokes and the visibility toggles render as set.
Why it needed an app. 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.
Outcome. Banded rows, Numbers 15.4: a (0.6, 0.8, 1) band fill set on a blank document's table is in the PDF export (over 50 pixels of that colour at 36 dpi). Grid strokes and the visibility toggles are unmeasured.
✅ Tables from nothing on a page or a slide
Was claimed. Pages and Keynote draw a table grafted from the Numbers donor onto a page or a slide.
Why it needed an app. The donor is Apple-made, the anchoring and style wiring match the corpus field for field, and a table the app repairs or drops looks like one never placed.
Outcome. Confirmed in Pages 15.4 and Keynote 15.4. On the page the checker wrote "worked great" (olekristensen-v26.0-mac-table-seed.pages); on the slide "The table is there but you've not sized it to fit it's contents" (olekristensen-v26.0-mac-table-seed.key): the seed carried the Numbers donor's 20-point rows and 200-point columns onto a 1920-point slide, and the app keeps whatever sizes a file states. A new table now takes the app's own cell size in both dimensions — Keynote 432.25 by 129.8 in the layout's frame, Pages 120.386 by 22.73 — whatever its row and column count, and its geometry is the cells plus the border allowance.
✅ Text boxes: adding one
Was claimed. Keynote and Pages draw a text box this library copied and re-worded.
Why it needed an app. The copy matches the donor in every field but text, geometry and parent; a box the app silently drops looks exactly like one never written, so only opening the file can say.
Outcome. Confirmed in Keynote 15.4 — the checker wrote "works" on the slide (olekristensen-v26.0-mac-text-box-seed.key). Confirmed in Pages 15.4 on rung P21 with the app's own box authored beside it: ours drew, placed by a Letter-sized guess on a 396 by 612 page so that only "the tip of a W character" showed at the lower right corner; the app's box differs from ours in nothing but text, geometry and the page's text-box style. Placement is in page points inside the page, from pageSetup().
✅ 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.