- scripts/build.mjs resolves exceljs/dist/exceljs.min.js and fflate first,
before tsc and before rm -rf dist/web/public. A tree whose node_modules
predate those devDependencies (pulled but never ran npm install) used to
fail in prepare-spreadsheet-assets.mjs with the live dist assets already
deleted, so the running server served an index.html whose hashed files
were gone. It now exits 1 with "run `npm install` first", nothing touched.
test/spreadsheet-assets.test.ts pins the order, that the list covers every
require.resolve in the prepare script, and runs a relocated copy of the
build to prove the exit and message.
- CLAUDE.md: the header visibility rule's stock desktop default now lists
Tiles (1180px and wider), which ships ON on desktop.
- docs/wiki/Agent-CLIs.md: "Before 1.36.0" becomes "Before 1.40.0" (four
places); 1.36.0 never ships.
- docs/wiki/Home.md: the "Everything in the manual" index lists Tile Grid
and Custom Model Endpoints, matching the sidebar. test/wiki-home-index
fails when a sidebar page is missing from that index.
- docs/wiki/Tile-Grid.md: the Tiles default is off on tablets too since the
touch-primary default landed, not only on phones.
- docs/browser-testing-guide.md: the fixed port table and new WebServer(PORT)
snippet give way to the port-0 pattern (new WebServer(0, false, true),
server.boundPort) that test/test-ports-guard.test.ts enforces; the
examples that opened localhost:3000, the live instance, use BASE_URL.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Admission checked ZIP entry names as stored, but JSZip (inside ExcelJS)
resolves `.`, `..` and empty segments on load, and ExcelJS strips one
leading `/` and matches worksheets with an unanchored pattern. Names like
`/xl/worksheets/sheet1.xml` or `xl/worksheets/sheet1.xml.x` skipped every
counter. Admission now computes the name ExcelJS will see for each entry,
refuses two entries that resolve to the same name, keys the rebuilt
archive on it, and picks the worksheet/styles counters from it.
ExcelJS's DefinedNames model setter expands every range into one object
per cell. The preview never shows defined names, so the worker stubs
`_definedNames.model` before load.
Pin fflate to 0.8.3 (GHSA-px8p-9vwx-vf98) and refresh
SPREADSHEET_ASSET_VERSION.
- Normalize the value shapes ExcelJS loads before formatting: Date cells are
formatted from their serial (UTC), so they no longer render as a local-time
string a day early at negative UTC offsets; rich text joins its runs,
hyperlinks show their text, error values show the error, and formula and
shared-formula results (including error results) recurse. Excel serials are
rounded to whole milliseconds so 00:05 no longer shows as 00:04.
- sendTile() skips hidden rows and columns, and at the 2500-cell cap returns a
truncated tile with a warning instead of failing the whole preview.
- ExcelJS now parses a STORE-only archive rebuilt from exactly the entries
admitXlsx() inflated and counted, never the fetched bytes. Admission walks
local headers while JSZip reads the central directory, so overlapping
entries could show the two readers different sheets. A duplicate local
entry name is refused. The theme fallback reads the admitted entry too.
- Row and column headings take their size from the same axis math as cells.
- Document the admission, worker-only loading and SPREADSHEET_ASSET_VERSION
rules in architecture-invariants, and list .xlsx in the attachments panel
help and the `codeman attach` error text (built from the accepted list).
xlsx files were download-only. Add a read-only, virtualized preview (sheet
tabs, number formats, merges, theme colours) parsed entirely in a browser
Web Worker with exceljs and fflate, loaded only when a spreadsheet is
opened. The workbook is checked against ZIP-bomb, entry and cell limits
before exceljs loads; cell text is written with textContent, formulas are
never evaluated and nothing referenced by the workbook is fetched. On the
server xlsx only joins the existing allowlist and classification, with a
10 MB cap on ?preview=true. xls and ods stay download-only.