Skip to content

Pre-major cleanup: composition for csv/markdown, bind the edit API everywhere, clear the cheap TODOs - #881

Merged
andiwand merged 6 commits into
mainfrom
chore/pre-major
Sep 10, 2026
Merged

Pre-major cleanup: composition for csv/markdown, bind the edit API everywhere, clear the cheap TODOs#881
andiwand merged 6 commits into
mainfrom
chore/pre-major

Conversation

@andiwand

@andiwand andiwand commented Sep 10, 2026

Copy link
Copy Markdown
Member

A survey of what is open before a new major version, with the small things done
directly.

is_savable() lied for a .csv and a .md

abstract::CsvFile and abstract::MarkdownFile derived from
abstract::TextFile, so is_text_file() answered true for both. Their
capability rows declare save = false, but TextFile::is_savable() asks only
whether the encoding decodes — so both reported savable, and html::translate
had to test them ahead of its text branch or a csv rendered as a line list.

Both now derive from abstract::DecodedFile and hold the text::TextFile
they already composed. Breaking: is_text_file() is false for a .csv and
a .md; the plain-text view is as_csv_file().text_file().

The capability test now asserts a text file's is_savable() against the
declared save. That is what caught this.

Every binding reaches the structural edit API

Document::remove, insert_text_before / _after, append_text,
split_paragraph, merge_paragraph_with_next, insert_paragraph_after and
TextFile::write_edited were C++ only. python, jni and apple take elements, as
their Document already does; element_by_id joins them, so the id
Element::identifier hands out resolves in all three rather than in python
alone.

wasm addresses the same operations by element id: nothing escapes that
binding as a handle, and the render already writes ids into the page as
data-odr-id.

CsvFile and MarkdownFile are bound in all three element bindings too, with
document() and text_file() — so the composition above costs a caller
nothing.

An apple method that returns nil for a legitimate answer must not also take
an error:: Swift imports the pair as throws and turns the nil into
nilError. elementWithIdentifier: is a plain nullable getter.

TODOs

Three were defects rather than notes:

  • text_set_content on an .xlsx run was a silent no-op. Every other
    engine that cannot write a run throws UnsupportedOperation; this one now
    does too, because the format declares edit and a caller had no other way to
    find out the write was dropped.
  • The pdf object parser skipped 4 or 5 bytes for null/true/false
    without reading them
    , so nXYZ parsed as null. expect_keyword checks
    them, without case, as the peek_ around them already allow.
  • The three oldms element adapters each held a const Document * that
    nothing read, kept alive by a [[maybe_unused]].

The 21 bare // TODO are down to none: each now states what is missing, so a
reader learns it without going to the format spec. Four design questions sitting
in headers are answered where they were asked.

Check pages and documentation

annotation/tests.html and viewport/tests.html kept their own check() and
printed no #summary. Both now load checks.js, which gains an optional
failure detail, a note() heading, and a guard against a throw in an event
listener. All nine pages pass: 14, 51, 10, 22, 8, 93, 45, 41, 17 checks.

Five public classes carried no doc comment. The README's editing paragraph named
the wrong formats, and document-editing.md named registry links that were
renamed before they landed.

Checks run

C++ 1684 passed; python 81; jni junit; apple 40 XCTest; wasm 46 node. The 14
touched TUs are clean under g++-15 -Wall -Wextra -Werror -fsyntax-only.

Left open, deliberately

  • A percentage fo:margin in odf is dropped. CSS takes % here with the
    same meaning, so passing it through would work — but it moves the reference
    output for every document that uses one.
  • AnchorType has no neutral value, so Frame::anchor_type() on a frame
    that does not exist answers as_char, which a real frame can also answer.
    Adding AnchorType::none is safe for the bindings only if appended, which
    reads wrong; it needs a decision rather than a quiet edit.
  • The remaining ~100 TODOs are format gaps in odf/ooxml/oldms/pdf decoding,
    each now stating what it is missing. The largest clusters are streaming
    decrypt/inflate, big-endian support, and pptx master pages and links.

andiwand and others added 6 commits September 10, 2026 18:16
`annotation` and `viewport` kept their own `check()` and printed no
`#summary`, so a headless run could not read them at a glance. Both now load
`checks.js`, which gains an optional failure `detail` and a `note()` heading.

`checks.js` also guards against a throw in an event listener: it does not stop
the script that dispatched it, so a page could pass over a live exception.

The README said editing is limited to odt, odp, odg, ods and docx. ods, pptx,
xlsx and txt all declare `edit` + `save` now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKFKbUVCYF2VhujdmjhhPW
`abstract::CsvFile` and `abstract::MarkdownFile` derived from
`abstract::TextFile`, so `is_text_file()` answered true for a `.csv` and a
`.md`. Both types declare `save = false`, but `TextFile::is_savable()` asks
only whether the encoding decodes - so both reported savable, and
`html::translate` had to test them ahead of its text branch.

Both now derive from `abstract::DecodedFile` and *hold* the `text::TextFile`
they already composed. `CsvFile::text_file()` and `MarkdownFile::text_file()`
hand out the plain-text view, so reading the bytes as text still needs no
reopening.

The capability test now asserts a text file's `is_savable()` against the
declared `save`, which is what caught this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKFKbUVCYF2VhujdmjhhPW
…e notes

`HtmlResource`, `HtmlView`, `HtmlService`, `LogFormat` and `TablePosition`
carried no doc comment; every other public class does.

`document-editing.md` still named the registry links `insert_child_after` /
`insert_child_before`, renamed to `insert_sibling_*` before they landed, and
its format table left `.xlsx` under "everything else — read-only".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKFKbUVCYF2VhujdmjhhPW
`Document::remove`, `insert_text_before` / `_after`, `append_text`,
`split_paragraph`, `merge_paragraph_with_next`, `insert_paragraph_after` and
`TextFile::write_edited` were C++ only. A host could drive them through the op
envelope, but not in process.

python, jni and apple take elements, as their `Document` already does.
`element_by_id` joins them, so the id `Element::identifier` hands out resolves
in all three rather than in python alone.

wasm addresses the same operations **by element id**: nothing escapes that
binding as a handle, and the render already writes ids into the page as
`data-odr-id`.

An apple method that returns `nil` for a legitimate answer must not also take
an `error:` - Swift imports the pair as `throws` and turns the `nil` into
`nilError`. `elementWithIdentifier:` is a plain nullable getter, like the rest
of the navigation.

Tests: python 80, jni junit, apple 40 XCTest, wasm 46 node.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKFKbUVCYF2VhujdmjhhPW
Three were defects rather than notes:

- `text_set_content` on an `.xlsx` run was a silent no-op. Every other engine
  that cannot write a run throws `UnsupportedOperation`; this one now does too,
  because the format declares `edit` and a caller had no other way to find out.
- The pdf object parser skipped 4 or 5 bytes for `null`/`true`/`false` without
  reading them, so `nXYZ` parsed as null. `expect_keyword` checks them, without
  case, as the `peek_` around them already allow.
- The three `oldms` element adapters each held a `const Document *` that
  nothing read, kept alive by a `[[maybe_unused]]`. Gone, with the parameter
  that fed it.

The 21 bare `// TODO` are down to none: each now states what is missing, so a
reader learns it without going to the format spec. `contents_reference` was
marked `// TODO remove` while four call sites use it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKFKbUVCYF2VhujdmjhhPW
…ywhere

Making them hold a text file rather than be one took `.text()` on a `.csv` and
a `.md` away from jni and apple, which had no `CsvFile` wrapper to reach it
through. Neither could get at the spreadsheet or prose view either, and python
had `CsvFile` but no `MarkdownFile`.

All three now expose both types with `document()` and `text_file()`, so the
composition costs a caller nothing.

Four design questions that had sat in headers as bare `TODO` are answered
where they were asked: `File::impl()` is the escape hatch every public wrapper
offers, `Frame::anchor_type` has no neutral value to report, the text file is
the last resort of `open_strategy`, and a walker builds its path rather than
holding one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKFKbUVCYF2VhujdmjhhPW
@andiwand andiwand changed the title Pre-major cleanup: composition over inheritance for csv/markdown, check-page and doc gaps Pre-major cleanup: composition for csv/markdown, bind the edit API everywhere, clear the cheap TODOs Sep 10, 2026
@andiwand
andiwand merged commit 39794b0 into main Sep 10, 2026
36 checks passed
@andiwand
andiwand deleted the chore/pre-major branch September 10, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant