# Numbers Viewer An Obsidian plugin that lets you **read** Apple `.numbers` spreadsheets without leaving your vault. Read-only: it never writes to, moves or modifies a `.numbers` file. It is not a spreadsheet editor — [Sheet Plus](https://community.obsidian.md/plugins/sheet-plus) and [Univer](https://github.com/dream-num/obsidian-univer) already cover that. The gap this plugin fills is reading Apple's proprietary format, which no other plugin interprets. ## Usage Click a `.numbers` file in your vault and it opens in its own view. Toggle between **Preview** — the thumbnail Apple already stores in the file, first sheet only — and **Tables**, which shows every sheet and table. Each table has buttons to copy its contents as Markdown or CSV. **Clipboard:** those two buttons are the only clipboard use. They write the table you clicked to the system clipboard via `navigator.clipboard.writeText`. The plugin never reads the clipboard, and never writes to it unless you click one of the buttons. ## Reading engines Tried in cascade; the view tells you which one it ended up using. 1. **Our own IWA parser** (Snappy + protobuf, a port of the read path of [numbers-parser](https://github.com/masaccio/numbers-parser)). The only engine that yields **real formats** — currency, percentage, dates — **per-cell colours**, **merged cells** and **each table's own name**. It reads 7 of the 9 files in the reference corpus. 2. **SheetJS**, as a safety net: it delivers values but no formats and no styles (measured: zero cells with `.z`, `.s` or `.w`). 3. **Apple's preview image**, when no engine can interpret the file. ## Development ```bash npm install npm run dev # watch build into test-vault/.obsidian/plugins/numbers-viewer/ npm test # node --test test/*.test.ts ``` Open `test-vault/` as a vault in Obsidian to test with hot reload. ### Releasing `.github/workflows/release.yml` builds, attests and publishes on a tag push. GitHub Actions is currently disabled on this account, so releases are cut locally instead: ```bash git tag 0.1.2 && git push origin 0.1.2 scripts/release.sh # build + checks only scripts/release.sh --publish # create/update the GitHub release ``` The script mirrors the workflow except for build provenance attestations, which need the Actions OIDC token and have no local equivalent. The workflow stays in place and takes over unchanged once Actions is available again. The test fixture `test/fixtures/sample.numbers` is **synthetic** — generated by `scripts/make-fixture.py` with numbers-parser, so no personal spreadsheet ships in this repository. Regenerate it with: ```bash pip install 'numbers-parser' python scripts/make-fixture.py ``` Styles in that script must be registered through `doc.add_style()`; a bare `Style()` only persists `bg_color`. One consequence of shipping a synthetic fixture: the **SheetJS fallback engine has no automated coverage**. SheetJS cannot read a file written by numbers-parser, and the only files it can read are real Numbers.app documents. Test that engine by hand against a real `.numbers` file when you change `src/numbers/via-sheetjs.ts`. ## Known limits Measured against 9 real `.numbers` files, with every cell diffed against `numbers-parser` as an oracle: **3,651 styled cells match exactly**, and of 1,303 numeric cells the only differences are two deliberate deviations, both documented below. - **A `.numbers` file saved as a package (a folder) instead of a single file.** Obsidian sees a folder and never even routes the click to this plugin, so there is no error to show — none of our code runs. The only remedy is to re-save from Numbers as a single file. - **Pre-BNC files (Numbers 5.x and earlier) are not read by the IWA parser.** That is old storage which the reference implementation rejects by design; it is not a bug that will be fixed. Two of the nine corpus files. SheetJS rescues one of them; the other stays on the preview image. - **Rounding follows Numbers.app, not the reference library.** A raw 2652.5 in a no-decimals currency format renders as `2.653`, which is what Numbers displays; `numbers-parser` truncates to `2652`. This affects 35 cells in the corpus and is intentional. - **Accounting formats with conditional rules** are supported. The real template — the one carrying the currency symbol — lives in the rule that matches the value, not in the base format. The **complete** format of the rule is used, not just its template string: the reference uses only the string and applies the base format's widths (an empty `#.#` stub), producing `36000, €` where Numbers shows `36.000,00 €`. - **Colours, bold, italic, alignment and merged cells are reproduced** from the file's real values. With real styles the table is painted on a light ground even when Obsidian is in a dark theme, because Apple's fills assume white paper. What is **not** reproduced: font family and size, borders, and vertical alignment. - **Merged cells** are read from the *formula store*, then from the *dependency archives*, then from `merge_region_map` — the same order of preference as the reference implementation. - **Formula text is not shown yet.** The IWA parser already has the infrastructure to reach `CalculationEngine.iwa`, but the AST decoder is not ported. The **computed value** is correct. - **Categories and footer rows** (Numbers' native row grouping) are not represented yet. Same situation: the infrastructure is there, the semantic step is not. - **Number separators are hard-coded to the Spanish convention** — `.` for thousands, `,` for decimals. The document locale is not read yet, so other locales will see their numbers grouped the Spanish way. - **Pop-up menus and star ratings:** no real example in the corpus to confirm how they behave. Tickboxes do render as ☑/☐. - **`[[wiki]]` links** inside a cell work: pattern recognition over the extracted text, independent of everything above. - **The preview image can lie.** It is a QuickLook thumbnail of the last save, and only of the first sheet. It is always labelled as a preview. - The format is proprietary and undocumented, so a Numbers update can break parsing of a given file. When that happens the view falls back to the preview image, or to a readable error message, rather than a blank panel — verified with a real file that SheetJS cannot parse (`"NUMBERS missing tile 24"`). - **No table virtualisation.** The Numbers tables observed are small (~170 rows at most). It would be added if a real table started to hurt. ## Protobuf schema `src/numbers/iwa/descset.generated.ts` embeds the full IWA schema (1,129 messages) extracted from numbers-parser. **It must be regenerated when Apple ships a Numbers release that changes the format** — see `scripts/extract-iwa-schema.py` and `scripts/embed-iwa-schema.mjs`. ## License MIT. See [LICENSE](LICENSE).