`{"sheet": null, "cols": "Data!B:B"}` was refused as naming a sheet other than `sheet`,
while `target_sheet` and the cell parameters read a null `sheet` as absent. The prefix now
decides, as it does when `sheet` is left out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`style: {fill: "none"}` (or "auto") went through the colour pass and came out as a solid
fill in the automatic colour. `home.fillColor` reads the same word as no fill, so the rule
style does too now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
data.validation took any `type`, `operator` or `errorStyle` and fell back
to a default: a typo such as `type: "lst"` saved an "any value" rule
(`type="none"`), which validates nothing, and an unknown operator became
`between`. Each is now an error that lists the accepted values; `any`
stays a type, and the operator aliases (`greaterThan`…) still work.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
data.validation {type: "list", formula1: "Open,In progress,Closed"}, which is
how Excel's Source box takes a list, worked inside GridCraft but was saved as
<formula1>Open,In progress,Closed</formula1>. In a file an inline list is a
string literal, "Open,In progress,Closed" (what Excel writes and what our own
reader expects); unquoted, formula1 reads as a formula of names.
- validation quotes such a source when the rule is created, doubling inner
quotes, using the test list_items already used to tell a literal list from
a reference or a name (now is_literal_list). A source typed with = (a
formula), a reference, a name, a sheet-qualified range and an already quoted
list are stored as before.
- list_items reads a quoted list as a string literal: "" inside stands for
one quote, so an item with a quote reads back the same after the change and
from a file.
Creating the rule is the one place that still knows whether the user typed =,
so the XLSX writer is left alone: it can't tell OFFSET(...) entered as a
formula from a literal list, and a rule read from a file is already quoted.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
home.conditionalFormat deserialized `style` straight into the model's
Style, so a programmatic caller had to write colours as {"Rgb": 16773836}:
"#FFF2CC", which format_range, colorScale and dataBar take, was refused
with a serde message naming internal types. A key the struct doesn't have
(`style: {preset: …}`, `fillColor`) was dropped, which saved a rule with an
empty format that matches and shows nothing. And a cellIs `value` already
written as a string literal ("\"Closed\"", as in the file and openpyxl)
was quoted again into the text "Closed" with its quotes, so it never
matched.
`style` colours (font, fill fg/bg, borders) may now be "#RRGGBB",
{theme, tint} or "auto", and `fill` may be a colour alone (a solid fill);
a fill colour with no pattern is solid. Unknown keys and a non-object
`style` are refused. A cellIs value that is one string literal is kept.
The model's own shape keeps working.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
home.rowHeight and home.columnWidth take pixels at 96 dpi, the model's unit
(header drags and the Row Height dialog send pixels), but their parameter docs
said points. An agent asking for a 30 pt row got `ht="22.5"` in the file, and
the range check accepted 500 while its message said "between 0 and 409 points".
- The params docs say pixels: `height: 20 (pixels) | points: 15`,
`width: 64 (pixels) | chars: 8.43`, and `{width: 64 (pixels)}` for
home.defaultWidth. Pixel values behave as before.
- home.rowHeight takes `points`, Excel's unit (Row Height dialog, `ht` in the
file), as home.columnWidth already takes `chars`.
- Range errors give the limit in the unit passed: 546 pixels (409.5 points),
409.5 points, 1800 pixels, 255 characters. `chars` is checked against 255,
Excel's limit and the widest column a file holds (it went to 256.4 through
the pixel limit).
- The model's size comments say pixels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
view.freezePanes, home.rowHeight, home.columnWidth, the AutoFit, Hide and
Unhide row/column commands and home.defaultWidth always changed the active
sheet: a `sheet` parameter was silently ignored, so a programmatic caller
laying out a sheet that isn't active changed the wrong one, and `Sheet!`
prefixes on `rows`/`cols` were refused as "not a range".
They now take `sheet` (name or index) like the commands that already use
target_sheet, accept a `Sheet!` prefix on `rows`/`cols` (and on Freeze
Panes' `cell`, which was refused), and refuse a prefix that contradicts
`sheet`. The selection belongs to the active sheet, so another sheet needs
explicit lines or a cell. Freeze Top Row, Freeze First Column and Unfreeze
Panes take `sheet` too. Without `sheet`, nothing changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Business Central's "Open in Excel" exports write booleans as
<c><f>TRUE()</f><v>1</v></c> with no t="b", so the cached value reads back
as the number 1 and the calc oracle reported every such cell as a
`number → bool` mismatch. A cached 1 or 0 now matches GridCraft's TRUE or
FALSE, and the cells are counted apart (`boolAsNumber` in --json, a note in
the text report) so a real number/logical disagreement stays visible.
Co-Authored-By: Claude Opus 5.5 <[email protected]>