Spreadsheets for the TempleOS desktop (HolyC)
Plan only, no code. Written 2026-10-27 on task/templeos-shell. Companion: slides.md.
Basis: holyc-native-apps-plan.md, holyc-improvement-analysis.md,
aiwnios/README.md, desk3/01-core.HC, 02-ui.HC, 03-apps.HC, and the Kosmos OS sources in section 5.
Verification labels: (V) read or fetched today, (M) from memory, not checked.
1. Purpose and users#
A calculator-grade sheet for one user on the desktop: budgets, lists, small models, teaching examples, and quick checks of
numbers that arrive from the host. It must run identically on the x86 guest (QEMU) and on Aiwnios (native arm64, see
aiwnios/README.md), so the formula engine lives in HolyC, not on the host.
Sizing target: 10,000 rows by 52 columns, sparse; about 5,000 populated cells and 2,000 formulas in a typical file.
Out of scope: pivot tables, macros or scripts, real-time collaboration, data sets above one million cells, external data connections.
2. Feature inventory#
Sources: Microsoft's Excel function list (support.microsoft.com, about 514 entries, approximate); Google's Sheets function reference (support.google.com/docs/table/25273, about 520 functions in 17 categories, approximate); Apple's Numbers support page (support.apple.com/numbers: "hundreds of built-in functions", export to Excel, CSV and PDF); LibreOffice Calc (the Calc function list did not load; Calc details are from memory (M)). Effort: S under a day, M one to three days, L a week or more.
| Feature | Seen in | Priority | Effort |
|---|---|---|---|
| Grid, A1 references, selection, keyboard movement | all | P0 | M |
| Cell values: number, text, boolean, error | all | P0 | S |
Formulas: arithmetic, comparison, &, A1 refs, ranges |
all | P0 | M |
| Function set tier 1 (40 functions, section 4) | all | P0 | L |
| Recalculation on edit with dependency tracking and cycle errors | all | P0 | M |
| Number formats: General, fixed decimals, thousands, percent, ISO date | all | P0 | S |
| Copy and fill with relative references | all | P0 | M |
| Undo and redo (50 steps) | all | P0 | S |
| Cut, copy and paste inside the app | all | P0 | S |
| Several sheets: add, rename, delete; cross-sheet references | all | P1 | M |
| Column width, row height, freeze top row | all | P1 | S |
| Bold, italic, alignment, fill from the style palette | all | P1 | S |
| Charts: column, bar, line, pie | all | P1 | L |
| Find and replace; sort a range by one column | all | P1 | S |
| Function set tier 2 (25 functions) | all | P1 | M |
| Named ranges | Excel, Sheets, Calc | P2 | S |
| Conditional formatting (one rule: value above or below a limit) | all | P2 | M |
| Data validation (list) and autofilter | all | P2 | M |
| Printing and PDF export | all | P2 | M |
| Function set tier 3 (15 functions) | all | P2 | M |
| Pivot tables, macros, scripts, comments, live collaboration | Excel, Sheets, Numbers | out | n/a |
3. Architecture decision#
Options:
- A. Native engine. Grid, editing, parser, dependency graph, recalculation, number formatting and chart drawing in HolyC.
- B. Host engine. Every edit goes to a host service (IronCalc, HyperFormula, LibreOffice). Rejected for interactive use: each edit crosses the serial channel, whose speed the native-apps plan gates at 50 KB/s but whose round-trip latency is unmeasured.
- C. Hybrid. Native editing and recalculation; the host does file conversion, PDF output, and acts as the test oracle.
Recommendation: C. It is the only option that works offline, under QEMU and on Aiwnios with the same code.
| Runs natively (HolyC) | Runs on the host |
|---|---|
| Grid painting with dirty rectangles, scroll, selection, cell editing | XLSX and ODS read and write |
| Formula tokeniser, parser, evaluator, dependency graph | PDF output |
| Recalculation, error values, number formatting to text | Large-file conversion and bulk checks |
Chart drawing (lines via LineN, pie via DiskN, rectangles, text) |
Golden oracle runs (LibreOffice headless, IronCalc) |
Undo stack, native .KSS file read and write, CSV and TSV |
Font work: none needed, the guest uses ASCII fonts |
4. Formula engine#
Parser. A tokeniser (numbers, strings in double quotes, references with $, ranges, names, function calls, operators) feeds a Pratt
parser with Excel precedence. Excel binds unary minus tighter than ^, so -2^2 is 4; the test set checks this. Each formula compiles to a
flat postfix array of opcodes stored with the cell. Evaluation walks that array with a pointer, with no recursion for ordinary formulas.
Values. One struct per value: type tag, an F64 number, a string pointer into a small string pool, and an error code (#DIV/0!,
#VALUE!, #REF!, #NAME?, #N/A, #NUM!, #CYCLE!).
Dependencies and recalculation. Each cell keeps its precedents. Ranges are stored as rectangles, and a reverse index maps a rectangle to the
formulas that read it. An edit marks the forward closure dirty and evaluates it in topological order with a depth-first walk and an on-stack
flag; a cell reached again while on the stack gives #CYCLE!. Budget: full recalculation of 2,000 formulas under 30 ms on Aiwnios; under QEMU
the first measured figure sets the gate, with a fail threshold at 10 times the Aiwnios figure.
Numbers. Values are F64 (IEEE double), as Excel and Google Sheets are. Reasons:
- the goldens can match a Python reference computed in double;
- the performance concern is per-frame float, which the grid avoids by caching formatted text; per-cell float work is cheap (the
benchmark set in the improvement analysis measures about 67 ns per float multiply-add under TCG, so 20,000 operations is about 1.3 ms);
- an integer fast path handles SUM and COUNT when all operands are whole numbers.
Decimal fixed point (I64 scaled by 10^6) was rejected for the main path: it overflows at about 9.2 trillion and cannot represent division.
It is kept as a P2 option for a currency-only mode. Display rounds to 15 significant digits with integer digit extraction, not PrintF.
Known parity risk. TempleOS evaluates F64 in 80-bit x87; Aiwnios uses IEEE double. The aiwnios README records this as the reason some F64-derived pixels differ. Spreadsheet goldens therefore compare with a relative tolerance of 1e-12, and any value that differs at 1e-15 is reported, not failed.
Function tiers (80 in total).
- P0 (40): SUM AVERAGE MIN MAX COUNT COUNTA COUNTBLANK PRODUCT ROUND ROUNDUP ROUNDDOWN ABS INT MOD POWER SQRT IF IFERROR AND OR NOT ISBLANK ISNUMBER ISTEXT ISERROR CONCAT LEN LEFT RIGHT MID UPPER LOWER TRIM TEXT VLOOKUP HLOOKUP INDEX MATCH SUMIF COUNTIF.
- P1 (25): AVERAGEIF SUMIFS COUNTIFS IFS SWITCH TODAY NOW DATE YEAR MONTH DAY WEEKDAY EDATE PMT PV FV MEDIAN STDEV LN EXP LOG SUBSTITUTE FIND VALUE SUMPRODUCT.
- P2 (15): XLOOKUP FILTER SORT UNIQUE SEQUENCE RANDBETWEEN LET MAXIFS MINIFS TEXTJOIN CHOOSE OFFSET INDIRECT NETWORKDAYS REPT.
Semantics to match: ROUND rounds halves away from zero; dates use Excel's 1900 serial system, including its false 29 February 1900
(M, from Excel's documented behaviour); text comparison is case-insensitive. Excluded everywhere: WEBSERVICE, IMPORTDATA,
GOOGLEFINANCE, and any function that reads a file or the network. OFFSET and INDIRECT are volatile and stay P2.
Charts. Column, bar, line, pie in P1; scatter and area in P2. They read a range and draw with the existing primitives: Rect, RRFill,
Line/LineN for series lines, DiskN for pie slices (angles need trig, once per slice, acceptable). Labels use the 8-pixel and 16-pixel
fonts already loaded (F_SMALL, F_REG).
5. Data model and file format#
Reuse. Kosmos OS already defines a workbook: packages/doc-interop/src/types.ts (V) gives
WorkbookDoc { version, title, sheets: [{ id, name, cells: { "A1": { value, formula, format } } }] }, with format covering bold, italic,
strikethrough, alignment, a three-value number format (plain, currency, percent) and a fill. shared/sheetFormula.ts (V) is a small
TypeScript evaluator covering SUM, AVERAGE, MIN, MAX, COUNT and IF; it is useful as a behavioural reference, not as the engine.
Gaps versus this plan: no column widths, row heights, merged cells, freeze panes, charts, named ranges, validation or conditional formats, and a number format too coarse for decimals or dates.
Proposal. Version 2 of WorkbookDoc, additive only: cols, rows, freeze, charts, names, and numberFormat accepting an Excel-style
format code, with the old enum kept as an alias. Version 1 files keep loading in both apps. The Kosmos OS importers and the TempleOS app read
the same JSON on the host side.
Guest file. .KSS, one record per line, ASCII only, written by the host converter from WorkbookDoc:
KSS 1
SHEET Sheet1
W A 14
C A1 S Item
C B1 S Amount
C B2 N 1200
C B3 F =SUM(B2:B2)*1.25
FMT B2 #,##0.00Non-ASCII text is written as \uXXXX escapes and shown as ? on the guest. CSV and TSV are read and written natively, but only the
ASCII subset is round-tripped, and a leading = in imported text stays text.
Interop. The host converter reuses packages/doc-interop (V: CSV, TSV, ODS and XLSX, ESM TypeScript, runs under Node). The guest reaches it
through the KsExec pattern in 03b-browser (V: the bridge reads a request line and replies OK n c). Realistic subset: values, formulas as
text, multiple sheets, bold, italic, alignment, column widths, common number formats. Dropped with a warning: charts, pivot caches,
conditional formats, macros, external links (cached values kept). PDF: HTML rendered by LibreOffice headless (M) or Typst (M), in M7.
6. Open-source candidates#
Licences and versions checked today on GitHub, npm, crates.io and PyPI (V unless marked).
| Project | Licence and version | Verdict |
|---|---|---|
| IronCalc | MIT OR Apache-2.0; ironcalc 0.9.1 (crates.io, 2026-10-21); @ironcalc/wasm 0.9.1; README says "work in progress" and xlsx only |
Use as a host oracle for function results. Not in the guest. Young; do not depend on it for runtime. |
| HyperFormula | GPLv3 or a paid proprietary licence (README (V)); npm 3.5.0 is GPL-3.0-only (V) | Semantics reference only. Do not link: GPL, or buy a licence. Blocked by default. |
formula.js (@formulajs/formulajs) |
MIT; 4.6.1 (V) | Reference for each function's edge cases and test vectors. JavaScript, not portable to HolyC. |
| calamine | MIT; 0.37.0 (crates.io, 2026-10-18); reads xls, xlsx, xlsm, xlsb, ods; read-only (V) | Host reader if the converter is Rust. |
SheetJS CE (xlsx) |
Apache-2.0 (README (V)); npm 0.18.5 (V). Newer versions are on sheetjs.com, not checked | Second choice for a JavaScript host converter. Pro features are paid; CE is enough for this subset. |
| openpyxl | MIT; 3.1.5, uploaded 2024-06-28 (PyPI, V) | Python host writer for XLSX if the converter is Python. |
| LibreOffice Calc | MPL-2.0 (M); help version 26.8 (V) | Oracle for recalculation and PDF rendering, run as an external program, never linked. |
| Typst | Apache-2.0, Rust (V) | PDF typesetting option for printed sheets. Optional. |
doc-interop (Kosmos OS) |
Private package in this workspace; depends on exceljs, jszip, pptxgenjs (V) | Reuse as the host converter. Its FIDELITY matrix is the spec. |
7. Security and privacy#
- No macros, scripts or plug-ins. The formula language cannot reach the network or the file system.
- Opening a file never fetches anything. External links are shown as text. Embedded objects and OLE data are skipped.
- Imports are capped: 5 MB of input, 1,000,000 cell records, a limit on zip entries and on uncompressed size (a zip-bomb guard before any parsing).
- The converter reads and writes only the guest's
B:directory and a folder the host picks; arguments are validated and never interpolated into a shell line. It has no network access. - Export to a path, share, or send each need an explicit prompt in the UI.
- CSV formula injection: a leading
=in imported text is text. Export writes formulas as text and warns.
8. UI specification#
Layout follows the desk3 style: rounded window, toolbar strip with RRFill buttons, one line for the formula bar, a grid with a fixed header
row and column, sheet tabs at the bottom, an inspector on the right that opens with a key.
+-----------------------------------------------------------------------+
| Sheet Edit Insert Format Data Help [Share] [X] |
| [Save] [Undo] [Redo] | [B] [I] | [0.00] [%] [$] | [Chart] [fx] |
+-----------------------------------------------------------------------+
| B4 | =SUM(B2:B3) |
+-----+---------------+---------------+---------------+-----------------+
| | A | B | C | D |
| 1 | Item | Amount | Tax | Total |
| 2 | Rent | 1,200.00 | 0.00 | 1,200.00 |
| 3 | Food | 310.50 | 0.00 | 310.50 |
| 4 | | [1,510.50]| | |
+-----+---------------+---------------+---------------+-----------------+
| [Sheet1] [Sheet2] [+] | Inspector: Format |
| | Number: 0.00 (v) |
| | Bold [x] Fill: [16 swatches]|
+----------------------------------------------------------------------+Behaviour: arrows move the selection, Enter commits and moves down, Tab moves right, F2 edits in place, Esc cancels, a drag on the corner fills. Chart panel opens over the grid on request, and a chart is a movable object with a title.
Text: two sizes plus bold, the ASCII fonts already loaded (F_REG, F_BOLD, F_SMALL, F_BIG). Colours: the 16-slot palette of the active style
(ks_pal), not free colour, so the five styles stay consistent.
9. Framework needs and gaps#
Present (V): Button, Toggle, Radio, Slider, Check, hit regions per window and the Hit/HitAt routing, per-window layers and
dirty rectangles, Txt, TxtR, Wrap and TW, RRFill, Line, Disk, and IconLoad. The Notes app is a single text buffer with insert
and delete (NotesInsert, NotesDelete in 03-apps.HC): no selection, no caret movement, no undo.
Missing, in order of need:
- A text field with a caret, selection, and home, end and word movement.
- A virtualised grid scroll view with a fixed header, drawing only visible cells.
- An undo stack built on commands, shared with slides.
- An internal clipboard (cells and text); the guest has no system clipboard. The host clipboard is a P2 service.
- Key-up and modifier state. Aiwnios reports these; the x86 path needs checking before relying on Shift-click range selection.
- A context menu. The desktop has only the Kosmos menu.
- A number input for the inspector.
- Unicode: none in the guest. Non-ASCII renders as
?.
10. Shared infrastructure#
Shared with slides.md, built once: a chart module (sheets use it for ranges, slides for chart objects); the cell model as the
slide table model; Wrap and a width cache for text; a bounded undo queue; one KsExec converter protocol; the five styles' 16-slot palette
(ks_pal); and a 32-bit image cache, with PNG and JPEG decoded on the host until a HolyC decoder exists.
11. Milestones and gates#
| Milestone | Work | Gate (pass) |
|---|---|---|
| M0 | Grid paint, scroll, selection, cell store on both targets | 10,000-row sheet scrolls at 16 ms a frame on Aiwnios; QEMU figure recorded |
| M1 | Editing, tokeniser and parser for operators and refs | 40 parser cases pass; -2^2 = 4 |
| M2 | Dependency graph, recalculation, 40 P0 functions, cycles | 100 golden formula cases pass (below); recalc budget met |
| M3 | Native .KSS, CSV and TSV; host XLSX and ODS round trip |
10 sample workbooks round-trip with identical values |
| M4 | Charts (column, bar, line, pie) | 5 chart goldens match within the render tolerance |
| M5 | 25 P1 functions, sheets, freeze panes, sort, find | 100 further cases pass |
| M6 | Undo, paste, fill, number formats, column widths | scripted drive of 200 edits leaves no residue |
| M7 | PDF export, sharing and export prompts | PDF of a 3-page workbook matches the grid; prompt tests pass |
Test plan.
- Golden workbooks: 20 small workbooks; expected values from a Python reference (
decimalandfractionsfor exactness,floatfor IEEE). LibreOffice headless recalculation is a second oracle, IronCalc a third where it implements the function. Tolerance 1e-12 relative. - 100 formula cases as the M2 gate, a table of formula, inputs and expected value, generated and checked by the Python reference.
- Differential fuzzing: 10,000 random expressions compared between the HolyC engine and the Python reference.
- Render goldens: grid screenshots at 1440 by 900 with
golden.HCandpxdiff.py, on both targets. - Performance: the benchmark set in the improvement analysis (section 11), reported as median of three runs.
12. Risks#
- F64 cost under QEMU: a large recalculation may be slow. Mitigation: integer fast path, dirty-only recalculation, and a measured gate.
- Excel compatibility: function edge cases (ROUND, the 1900 date bug, text comparison) are where spreadsheet tools disagree. Mitigation: the golden cases encode the Excel answer and the LibreOffice answer is recorded separately.
- Scope creep: pivots and charts of every kind. Mitigation: the P-levels in section 2 are the cut line.
- ASCII only: non-English sheets display badly. Accept for version one; a Unicode font path is a separate project.
- Converter drift: the Kosmos OS model changes. Mitigation: the version 2 proposal is additive, and the converter has golden round trips.
13. Effort#
Estimate, not measured: M0 to M2 about 6 to 8 weeks of focused HolyC work; M3 to M7 about 6 weeks; total about 3 to 4 months for one developer, with the framework items in section 9 counted in M0 to M2. Lower bound if charts are cut to column and pie only.
14. Verified and from memory#
- Verified (V): the Kosmos OS sources in sections 5 and 6, the desk3 names, the aiwnios notes, the benchmark figures, the licences and versions in section 6, the Numbers summary, and the approximate Excel and Sheets counts.
- From memory (M): LibreOffice's licence and function list, Excel's 1900 date rule, ROUND's mode, Excel's unary-minus precedence,
exceljsandpptxgenjslicences. - Not checked: IronCalc's function coverage, calamine's Python bindings, SheetJS versions after 0.18.5.