feat(html): an editing mode for a sheet, and the locks it refuses on - #861
Open
andiwand wants to merge 3 commits into
Open
feat(html): an editing mode for a sheet, and the locks it refuses on#861andiwand wants to merge 3 commits into
andiwand wants to merge 3 commits into
Conversation
A sheet's editing is an overlay, so the page works out what it can and the markup states only what it cannot. The `<table>` gains `data-odr-sheet`, which is what an op names, and `data-odr-editable`, so `odr.editing.enable()` can refuse before the user clicks anything. A cell an edit would spoil gains `odr-locked` and `data-odr-lock` with the reason - `formula`, whose dependants would go stale, `shapes`, which an overlay would cover, and `rich`, whose markup a one-run write would throw away. A cell that can be written carries nothing, which is the whole point: the cost is on the locked cells only. `sheet-editing.js` holds `odr.editing` - `enable`, `disable`, `isEnabled`, `isEditable`, `lockAt` - and the three flat `odr.on*` callbacks. Codes share one space with `odr.onError`'s and are appended, never renumbered; the host maps a code to its own wording, since nothing here is localised, and the English message is for a developer who wires nothing. An identical refusal within two seconds is dropped by the page, so four taps on a locked cell are one snackbar. `spreadsheet.js` publishes `odr.sheet` beside it - `cellAt`, `positionOf`, `pinned`, `pin` - and the editor reads positions through it rather than walking the rows itself. A position is not a `cellIndex`: a sort moves the rows out of position order, so a row is named by the label it carries, and a merge writes no cell for the positions it covers, so a row's cells are walked with the rowspans above carried down. The map is built once, by the script that owns the pin, the raise and the sort - two copies of it would be two owners of all three. Decision 8 in the design. First step of the browser editor in `docs/design/spreadsheet-editing.md`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J325TWocZ4iXBjvKv2ZVZi
Every sheet states its index and editability, the locked cells say why, and `sheet-editing.js` is a new resource beside a `spreadsheet.js` that publishes `odr.sheet`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J325TWocZ4iXBjvKv2ZVZi
andiwand
force-pushed
the
feat/sheet-editing-mode
branch
from
September 8, 2026 05:55
48b24b9 to
4b52a1a
Compare
`odr.sheet` answers questions `odr_test` cannot ask - what a `td` is at a position - so two pages join the raise checks beside it. `positions.html` is a merged sheet as `translate_sheet` writes one, with no `td` where a span covers a position: a `colspan`, a `rowspan`, and a `rowspan` reaching past the last cell of the row below it. `sorting.html` asks the same questions after the sort control has moved every row. Both are shapes the walk that came before answered wrong - it misses 5 of the merged sheet's 20 positions and 4 of the 5 sorted rows, which is what the fixtures are cut to show. The report runner moves into `checks.js` for the three pages to share, and it now heads the list with a count. `serve` extracts `sheet_editing_js` too, since the locks are read through the same map.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
Step 1.1 of
docs/design/spreadsheet-editing.md— stage 0 is done, this opens the browser editor.What the markup states
A sheet's editing is an overlay, so the page works out what it can and the markup carries only what it cannot:
<table>data-odr-sheet="0"<table>data-odr-editable="true"enable()can refuse before the user clicks anything<td>odr-locked+data-odr-lock="…"Three lock reasons, per decision 3:
formula— overwriting it leaves every value computed from it stale.shapes— the cell's drawings are the cell, and an overlay would cover them.rich— a write replaces the cell's one run, so anything richer would be thrown away.repeatedis not among them: #858 made a repeated cell writable, so the lock the plan reserved for it is already gone.A cell that can be written carries nothing at all — the cost is on the locked cells only, not on the half million others.
What the scripts do
sheet-editing.jsholdsodr.editing(enable,disable,isEnabled,isEditable,lockAt) and the three flatodr.on*callbacks from decision 7.odr.onError's, appended and never renumbered — 1 iserrorIllegalEditNewLine, so these start at 2.spreadsheet.jspublishesodr.sheet—cellAt(column, row),positionOf(cell),pinned(),pin(position)— and the editor addresses cells through it (decision 8, added to the design in this PR). It is the map, not a walk:<tr>s, sobody.rows[row]stops being rowrowthe moment a column is sorted;translate_sheetwrites no<td>for a position a merge covers, so acolspan-only walk misreads every cell to the right of one;Positions are the ones an op names (decision 1); the ruler's
nth-childindex stays private to the script, and a position a merge covers answers with the cell covering it — the one the file states. Merged sheets still get no wash and no sort control, as before.The first two points were live bugs in the
cellAtthis PR previously carried insidesheet-editing.js: it indexedbody.rowsand walkedcolspanonly. Neither had shipped.One thing worth flagging
My first
richclassifier assumed a cell wraps its text in a paragraph, which is odf's shape — ooxml hangs the run under thecdirectly. That locked 92,690 xlsx cells that are perfectly plain. Now a single paragraph is unwrapped once, and the corpus reads: 30,906formula, 2,300rich, 36shapes. The remaining publicrichismultiline.xlsx, which genuinely holds several runs.Reference output
Regenerated and pinned — 355 output files (83 public, 272 private), plus
resources/sheet-editing.js, a new file, and theresources/spreadsheet.jsthat publishesodr.sheet. Every ods page changes, since every<table>gains the two attributes and the page gains a script.Gates: 0 of 355 changed files differ in visible text, and a fresh run against the new reference is 0 diffs in
output/andresources/.Checks
odr_test, sotest/browser/sheetgrows two pages beside the raise checks:positions.html(a merged sheet — acolspan, arowspan, and arowspanpast the row's last cell) andsorting.html(the same questions once the sort control has moved every row). 42 checks over the three pages, 0 failing. As a negative control the walk this PR replaced was run against the same fixtures: it misses 5 of the merged sheet's 20 positions and 4 of the 5 sorted rows, so the fixtures do discriminate.deadcode.DeadStoresit reports is pre-existing on main, identical code); gcc-15-Wall -Wextra -Werrorover four TUs. No C++ changed since —odr.sheetlives in a string literal, andfits_a_literalstill holds at 14580 of 16380.Next in the stack
1.2 the overlay editor, driving
odr.sheet.pinfor the arrow keys, 1.3 commit and row reflow (whosevisibleRight/cutOffjoinodr.sheet), 1.4 undo/redo andcommitted(), 1.5 the browser tests and the wasm example button.