Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
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.
- **`.xlsx` reads that lost data.** An ISO-date cell (`t="d"`, from a workbook
saved with `iso_dates`) read as nil and, since it read as nothing, was erased on
the next write; it now reads as a `Date` or `NaiveDateTime`. An elapsed duration
(`[h]:mm:ss`) read as a `Time`, throwing away whole days (36 hours became 12); it
keeps its number and its format now. A float smaller than `1.0e-15` (such as
`1.0e-20`) was written as `0`; 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.)
- **`.xlsx` writes 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. Removing `calcChain.xml` left
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 `0600`
file produced a `0644` one, because the temporary file took the process umask.
The destination's mode is now preserved, a new file is created `0600`, and the
temporary file is created exclusively.
- **`Sheetshow.Table.compact/1`** no 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/2` and
`Sheetshow.Log.new/2` reject a schema with an `id` or `deleted` column, 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/2`** turns a serial number too large to convert into a
`:cast` error, rather than raising.
- **WebDAV**: a `PUT` that returns no `ETag` leaves the version `:unknown` instead
of adopting one from a follow-up `HEAD`, 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/2` on a file atomic against a racing writer, but does not yet tie a `Table`
write to the snapshot it was planned against, because `run/2` re-reads the file
at execution. The guide, the `Sheetshow.Table` docs 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 two `sheetData` elements.
- `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/3` raised on `header: false`.
- A colour such as `"#FF8800\n"` passed `Sheetshow.Style.validate/1` and failed in
the Google encoder; a sheet name or reference with a trailing newline parsed.
- `Sheetshow.Schema.cast/2`: a `:json` column accepts a number or boolean a
person typed, and a `:boolean` column accepts `1.0` and `0.0`.
- Every function that takes options now refuses an option it does not know, as
`Sheetshow.plan/2` already did: the layout builders, `Sheetshow.Client.new/2`,
`Sheetshow.Workbook.xlsx/2`, `Sheetshow.Token`, `Sheetshow.Log.Event.new/2`,
`Sheetshow.Table.insert/2` and `delete/2`, `Sheetshow.OAuth`,
`Sheetshow.ServiceAccount.assertion/2`, `Sheetshow.UserAccount.new/3` and
`Sheetshow.Store.webdav/2`.
## 0.1.0 (2026-09-16)
The first release. What is in it:
- **Cells and layout.** `Sheetshow.Cell`, `Sheetshow.Coord`, `Sheetshow.Range` and
`Sheetshow.A1`, giving 0-indexed coordinates, inclusive ranges, quoted sheet
names and open-ended ranges, with `Sheetshow.Value` (numbers, text, booleans,
formulas, dates and times as serial numbers) and `Sheetshow.Style` (a plain
map). Layout on `Sheetshow`: `row`, `col`, `rows`, `records`,
`to_rows`, `stack`, `beside`, `pad_below`, `pad_right`, `shift`, `put_sheet`,
`put_style`, `max_row` and `max_col`.
- **Plans.** `Sheetshow.plan/2` turns cells into backend-neutral ops (`AddSheet`,
`DeleteSheet`, `PutCells`, `AppendRows`, `DeleteRows`, `SetDimensions`), one
`PutCells` per run so a write never clears a neighbour, and `Sheetshow.run/2`
carries a plan out in one request. `Sheetshow.read_cells/2` reads cells with
everything about them; `read_rows/2` reads computed values, one or several
ranges in one request.
- **Backends.** `Sheetshow.Workbook` over 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 `.xlsx` files,
read one tab at a time and written back with every untouched part copied across
compressed, on a local file or a WebDAV server, where `If-Match` makes a write
conditional. `Sheetshow.Backend.capabilities/1` says what each promises.
- **A database on a tab.** `Sheetshow.Schema` as 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/2` and a planner that orders hard deletes bottom-up; `Sheetshow.ULID`
for client-generated ids.
- **Credentials.** `Sheetshow.ServiceAccount` and `Sheetshow.UserAccount`, both
reduced to one `Sheetshow.Token`; `Sheetshow.OAuth` builds the consent URL and
reads the answer, with PKCE, `access_type=offline` and `prompt=consent` as the
defaults.
- **Guides that run.** Two quick starts, against Google and against an `.xlsx`
file; setting up Google; a cookbook; the case for Sheets instead of Postgres;
and what Sheetshow can promise. Every `elixir` block in them is executed by the
test suite. A cheatsheet generated from the compiled modules, and
`usage-rules.md` for an agent's context.
- No dependencies, no processes, no macros.