ADR 0032 - Niche limits and workbook pass-through behaviors
- Status: accepted
- Date: 2026-05-09
- Spec target: XTL 0.1
- Affects: evaluation.md "Source Data Model" + "Cell Evaluation"; ADR-0021
Context
The spec audit pass surfaced four small under-specified shapes that share a theme: they're niche, but the corresponding behavior was never normatively documented. Bundling them here avoids opening four micro-ADRs for behaviors that mostly only need a paragraph.
-
String values longer than Excel's per-cell limit (32,767 chars) — what does xl3 do when source data, an evaluated expression, or a template literal produces a cell value that exceeds Excel's 32,767-char per-cell limit?
-
Source headers in merged cells —
__sources__headers in row 1 (or whereversource_tablepoints) can technically be inside merged ranges. What does xl3 read? -
Workbook / sheet properties (tab color, page setup, views, default row height) — how are these preserved from template to output?
-
Integer precision beyond 2^53 — JS numbers are IEEE 754 doubles; integers beyond 2^53 lose precision. What's the spec position?
Considered Options
Each axis has the same option shape (per existing audit-pass treatment):
A. Document existing behavior as the spec. B. Tighten with validation / fixture coverage. C. Defer entirely.
Decision
Adopt A for all four. Each is a niche case where the existing impl behavior is reasonable; this ADR makes it normative so future impl changes don't accidentally diverge.
#1 — String values longer than 32,767 chars
Position: implementation-defined; XTL does not enforce Excel's per-cell limit.
A cell value exceeding 32,767 characters is permitted by xl3 and written to OOXML as-is. The resulting workbook may fail to open in Excel (Excel rejects cells over the limit when reading), but xl3 does not pre-validate. Authors and hosts that need Excel compatibility must validate the cell length upstream.
Rationale: enforcing the limit at xl3 would require either truncation (silent data loss) or an error (forces every host to length-check before passing data in). Neither is universally correct; the conservative stance is "xl3 writes what you give it; Excel-compatibility validation is a host concern."
#2 — Source headers in merged cells
Position: implementation-defined; portable templates do NOT merge header cells.
When source_table points at a row whose cells are part of a
merged range, the value reported per individual cell is reader-
library-defined. ExcelJS (the reference impl's reader) returns the
merge value on every cell in the range. xl3 then hits the
existing xl3/source/duplicate-name check, since the same value
appears across multiple header cells.
Other libraries may return only the top-left cell's value with
others as empty, in which case xl3 would hit
xl3/source/missing-header instead.
Both error paths reject the template. The spec's normative position is: templates that merge header cells are not portable. Authors should remove merges from header rows.
#3 — Workbook and sheet properties
Position: preserved verbatim from template to output.
The reference impl's cloneWorksheet copies properties (which
includes tabColor, defaultRowHeight, defaultColWidth),
pageSetup, and views from the template sheet to each output
sheet. Other workbook-level properties (themes, defined names,
print areas) are similarly preserved by ExcelJS's workbook
copy/load cycle.
Authors who want a specific tab color, page orientation, or print area set it in the template; xl3 carries it through.
This pass-through preservation is normative — porters MUST preserve workbook and sheet properties unless an ADR specifically overrides them.
Fixture 120 pins tabColor preservation as the most user-visible example.
#4 — Integer precision beyond 2^53
Position: implementation-defined within IEEE 754 limits.
xl3's value model treats all numbers as IEEE 754 doubles (per
ADR-0017). Integers up to 2^53 (~9 quadrillion) are exact;
integers larger lose precision according to IEEE 754 rules. A
source cell with value 9007199254740993 (2^53 + 1) reads as
9007199254740992 because no double-precision representation
exists for the odd value.
xl3 does NOT detect or warn about precision loss. Authors who need
exact integer representation beyond 2^53 (rare in reporting:
financial IDs, very large counts) store values as strings in the
source and apply TEXT() if formatting is needed.
A future ADR may introduce a BIGINT value kind if real demand
emerges; until then, IEEE 754 doubles are the only numeric type.
Consequences
- Four niche behaviors are now spec-documented. Future impl changes can't silently diverge.
- One new conformance fixture (120) pins tabColor pass-through (the most testable and most user-visible).
- No new error codes. No impl change required.
- No impact on conformance pass rate.
References
- ADR-0002 — Output filename sanitization (similar "impl-defined-with-recommendation" pattern)
- ADR-0017 — Source value model (IEEE 754 numeric model)
- ADR-0021 — Implementation-defined boundaries (similar pattern for memory model, sync vs async, etc.)
- ADR-0022 — Excel version compatibility (the version-axis catalog this ADR complements with limit-axis catalog)