Pre-1.0: a minor version may rename or remove. When it does, the migration is one line here.
0.1.2 (2026-09-18)
Fixes from an external review of 0.1.1. Correctness, and a more honest account of what the concurrency story does and does not cover.
.xlsxreads that lost data. An ISO-date cell (t="d", from a workbook saved withiso_dates) read as nil and, since it read as nothing, was erased on the next write; it now reads as aDateorNaiveDateTime. An elapsed duration ([h]:mm:ss) read as aTime, throwing away whole days (36 hours became 12); it keeps its number and its format now. A float smaller than1.0e-15(such as1.0e-20) was written as0; the shortest round-tripping form is written instead. An error cell (#DIV/0!) with no formula crashed any write to its sheet; the error now reads into the cell's metadata, the same place a formula's error goes, so the write succeeds. (Writing such a cell back verbatim is still to come; an untouched error cell rewrites empty for now.).xlsxwrites that corrupted the file. A shared-string table spelled with a namespace prefix (<x:sst>) silently dropped a newly added string, so the cell read back nil; mutations are namespace-aware now. RemovingcalcChain.xmlleft its relationship and content-type override behind, pointing at a part that was gone; both go with it.- A local write no longer loosens a file's permissions. Replacing a
0600file produced a0644one, because the temporary file took the process umask. The destination's mode is now preserved, a new file is created0600, and the temporary file is created exclusively. Sheetshow.Table.compact/1no longer deletes a live row when a tombstone shares its id after a refresh: an ambiguous tombstone is left for a fresh read to resolve.- Reserved and colliding columns are refused.
Sheetshow.Table.new/2andSheetshow.Log.new/2reject a schema with anidordeletedcolumn, or two columns that differ only by case or spacing; a tab whose header names a needed column twice reads as%Sheetshow.Error{reason: :duplicate_column}rather than silently taking the last. Sheetshow.Schema.cast/2turns a serial number too large to convert into a:casterror, rather than raising.- WebDAV: a
PUTthat returns noETagleaves the version:unknowninstead of adopting one from a follow-upHEAD, which could be a racing writer's. - Google: a batch that adds a tab and then deletes it in one plan no longer leaves the tab in the remembered metadata; adds and deletes are applied in order.
- What Sheetshow can promise, corrected. A conditional-write store makes each
run/2on a file atomic against a racing writer, but does not yet tie aTablewrite to the snapshot it was planned against, becauserun/2re-reads the file at execution. The guide, theSheetshow.Tabledocs and the README said this gap was closed; they now say it is not, and the README's retry note carries the same caveat the guide always has.
0.1.1 (2026-09-17)
Fixes from a QA pass over 0.1.0, all in the .xlsx codec unless said otherwise:
- A column of formulas Excel filled down (one
<f t="shared">and pointers under it) read as empty formulas and was written back as<f></f>, taking the formulas out of the file. Each cell now reads as the shared formula moved to it. - Conditional formatting's fonts and fills (under
<dxfs>) were counted with the lists a cell indexes into, so a new style pointed past the end of<fonts>. - Deleting a sheet whose name a writer had escaped its own way (
Q1's, as LibreOffice does) left the<sheet>element behind, pointing at a part that was gone. - Adding a string rewrote the shared string table from the text alone, flattening rich text in every other cell of the workbook. The table is spliced now.
- A worksheet whose elements carry a namespace prefix (
<x:sheetData>) is read but refused a write, rather than written back with twosheetDataelements. Sheetshow.Table.refresh/2: an id the snapshot had read twice, one of whose rows had since gone, came back as two rows on one sheet row with the flag cleared. Both stay flagged until a fresh read.Sheetshow.records/3raised onheader: false.- A colour such as
"#FF8800\n"passedSheetshow.Style.validate/1and failed in the Google encoder; a sheet name or reference with a trailing newline parsed. Sheetshow.Schema.cast/2: a:jsoncolumn accepts a number or boolean a person typed, and a:booleancolumn accepts1.0and0.0.- Every function that takes options now refuses an option it does not know, as
Sheetshow.plan/2already did: the layout builders,Sheetshow.Client.new/2,Sheetshow.Workbook.xlsx/2,Sheetshow.Token,Sheetshow.Log.Event.new/2,Sheetshow.Table.insert/2anddelete/2,Sheetshow.OAuth,Sheetshow.ServiceAccount.assertion/2,Sheetshow.UserAccount.new/3andSheetshow.Store.webdav/2.
0.1.0 (2026-09-16)
The first release. What is in it:
- Cells and layout.
Sheetshow.Cell,Sheetshow.Coord,Sheetshow.RangeandSheetshow.A1, giving 0-indexed coordinates, inclusive ranges, quoted sheet names and open-ended ranges, withSheetshow.Value(numbers, text, booleans, formulas, dates and times as serial numbers) andSheetshow.Style(a plain map). Layout onSheetshow:row,col,rows,records,to_rows,stack,beside,pad_below,pad_right,shift,put_sheet,put_style,max_rowandmax_col. - Plans.
Sheetshow.plan/2turns cells into backend-neutral ops (AddSheet,DeleteSheet,PutCells,AppendRows,DeleteRows,SetDimensions), onePutCellsper run so a write never clears a neighbour, andSheetshow.run/2carries a plan out in one request.Sheetshow.read_cells/2reads cells with everything about them;read_rows/2reads computed values, one or several ranges in one request. - Backends.
Sheetshow.Workbookover Google Sheets (Sheetshow.Google, OTP's:httpc, TLS verified explicitly, the quota as:rate_limited), over an in-memory spreadsheet that doubles as the test double, and over.xlsxfiles, read one tab at a time and written back with every untouched part copied across compressed, on a local file or a WebDAV server, whereIf-Matchmakes a write conditional.Sheetshow.Backend.capabilities/1says what each promises. - A database on a tab.
Sheetshow.Schemaas a keyword list;Sheetshow.Log, an append-only tab whose state is the fold over its rows, with a cursor read and a view tab;Sheetshow.Table, mutable rows found again by id, with soft delete,refresh/2and a planner that orders hard deletes bottom-up;Sheetshow.ULIDfor client-generated ids. - Credentials.
Sheetshow.ServiceAccountandSheetshow.UserAccount, both reduced to oneSheetshow.Token;Sheetshow.OAuthbuilds the consent URL and reads the answer, with PKCE,access_type=offlineandprompt=consentas the defaults. - Guides that run. Two quick starts, against Google and against an
.xlsxfile; setting up Google; a cookbook; the case for Sheets instead of Postgres; and what Sheetshow can promise. Everyelixirblock in them is executed by the test suite. A cheatsheet generated from the compiled modules, andusage-rules.mdfor an agent's context. - No dependencies, no processes, no macros.