Packages

Google Sheets from Elixir, as values, with a small database on a tab and the same code over .xlsx files.

Current section

Files

Jump to
sheetshow CHANGELOG.md
Raw

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&apos;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.