#400·OfficeCLI

xlsx: get /<Sheet> reports freeze from topLeftCell, ignoring xSplit/ySplit

Author: upstream-rayCreated Sep 14, 2026Updated Sep 14, 2026

Summary

officecli get <file> /<SheetName> reports the freeze property by echoing the <pane> element's topLeftCell attribute instead of deriving it from xSplit / ySplit. Whenever a workbook was saved while scrolled away from the frozen boundary — the normal state of any real spreadsheet — the reported freeze position is wrong.

Writing is correct; only the readback is affected.

Environment

  • officecli 1.0.149 (win-x64), Windows 11
  • Reproduced on a file authored by Excel 16.0 and on a file created by officecli itself

Reproduction

Self-contained, no external file needed:

bash
officecli create repro.xlsx
officecli set repro.xlsx /Sheet1/A1 --prop value="header"
officecli set repro.xlsx /Sheet1 --prop freeze=A2
officecli close repro.xlsx

officecli get repro.xlsx /Sheet1
#   /Sheet1 (sheet) freeze=A2                                  <- correct
officecli raw repro.xlsx /Sheet1 | grep -o '<x:pane[^>]*>'
#   <x:pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/>

# Simulate what Excel writes when the sheet is saved while scrolled down.
# Only topLeftCell changes; ySplit="1" is untouched, so the freeze is still row 1.
officecli raw-set repro.xlsx /Sheet1 --xpath "//x:pane" --action setattr --xml 'topLeftCell="A50"'
officecli close repro.xlsx

officecli get repro.xlsx /Sheet1
#   /Sheet1 (sheet) freeze=A50                                 <- WRONG, should still be A2
officecli raw repro.xlsx /Sheet1 | grep -o '<x:pane[^>]*>'
#   <x:pane ySplit="1" topLeftCell="A50" activePane="bottomLeft" state="frozen"/>

Expected vs actual

<pane> Excel's actual freeze get /Sheet1 reports
ySplit="1" topLeftCell="A2" A2 freeze=A2
ySplit="1" topLeftCell="A50" A2 freeze=A50
ySplit="1" topLeftCell="A329" A2 freeze=A329

The third row is from a real 369-row workbook saved by Excel with the cursor near the bottom. It reports freeze=A329 while the sheet is, and always was, frozen at A2.

Root cause

topLeftCell is the scroll position of the scrolled pane — it drifts every time the user saves while scrolled. The freeze boundary is defined solely by xSplit / ySplit. They coincide only in a freshly created file, which is why round-tripping through officecli alone looks correct and hides the bug.

Correct derivation:

freezeCell = columnLetter(xSplit + 1) + (ySplit + 1)

with absent attributes treated as 0. Also worth guarding state: state="split" is a split pane, not a freeze, and state="frozenSplit" differs again.

The write path is already correct

set freeze=A5   ->  <x:pane ySplit="4"            topLeftCell="A5"  activePane="bottomLeft"  state="frozen"/>
set freeze=B2   ->  <x:pane xSplit="1" ySplit="1" topLeftCell="B2"  activePane="bottomRight" state="frozen"/>
set freeze=C10  ->  <x:pane xSplit="2" ySplit="9" topLeftCell="C10" activePane="bottomRight" state="frozen"/>

So the fix is confined to the getter.

Impact

The readback is silently and plausibly wrong — it returns a valid-looking cell reference, so there is no error to notice. An agent inspecting a workbook will report a wrong freeze configuration with full confidence, and a round-trip that reads freeze and writes it back would move a correct freeze pane to wherever the file happened to be scrolled.