Whole-branch review — Claude Fable 5.1 (AGENTS.md §Review)
Read as one diff, origin/main...origin/fix/r1-grid-context-menu (af36460), in a scratch worktree; read the vendored jspreadsheet-ce 5.0.2 contextMenuControls, defaultContextMenu and keyDownControls.
VERDICT: CHANGES-REQUIRED (one blocking finding, a one-line fix on the line this branch already touches)
What is right:
contextMenu: () => falsedoes fully disable the menu in the pinned version:contextMenuControlscallsconfig.contextMenu(...), and on=== falsereturns beforet.contextMenu.contextmenu.open(e, r)and beforee.preventDefault(), so the browser menu shows. It is a spreadsheet-level option and is placed at that level. (While a cell editor is open the library stillpreventDefaults the right-click; pre-existing, harmless.)tools/drive/contextmenu.mjsexercises the fix: passes on the branch in Chromium and WebKit; with main'sgrid.jsswapped in it fails with the stock menu open (["Delete selected rows\nAdd comments\n…Save as...\nCtrl + S\nAbout"]).- The compaction (
columnDrag: false, allowInsertRow: false, allowInsertColumn: falseon one line) is behaviour-neutral: same three keys, same values, same worksheet object. .jcontextmenurestatement kept,TestVendorPaintIsRestatedand the 300-line cap pass;make testgreen with the pinned rastrillo CLI.- Already routed through the app and not at risk: Ctrl+C/X/V (clipboard.js captures on document and
stopImmediatePropagations before the library's bubble listener), Delete/Backspace on cells (librarysetValue→onchange→write), Ctrl+Z/Y (ignoreHistory), Enter/Tab at the last row/column (allowInsertRow/Column: false), fill-handle drag (onchangeper cell), touch long-press (opens the editor only).
Findings:
- [blocking]
internal/sheets/static/grid.js:222— the same library actions the menu offered are still reachable from the keyboard, and one of them corrupts later writes.keyDownControls(46/8 = Delete/Backspace): with a row header selected (selectedRow, set by clicking the row number) it runsconfirm("Are you sure to delete the selected rows?")thendeleteRow(); with a column header selected,deleteColumn(). Both act on the library's own model with no app write. I drove it against the branch with the menu fixture: click row 2's header, Delete, accept → rows 100→99, column A reads["10","30",""], no POST. Then typing into what is now visible row 2 posted{"r":1,"c":0,…}— the server's row 2 (still "20"), while the person is looking at "30". Every edit after a local delete lands one row off, silently. This is B7's "Delete selected rows" bypass through a different gesture, so it belongs in this fix. AddallowDeleteRow: false, allowDeleteColumn: falseto the worksheet options (the line you compacted), and extendcontextmenu.mjs(or a sibling) to click a row header, press Delete and assert rows unchanged and no dialog/write.
- [nit, fold into 1] Ctrl/Cmd+S →
download()— "Save as…" by keyboard; producedSheet1.csvfrom the library's display model in my run (bypasseshandlers_export.go). Gated by the spreadsheet-levelallowExport; addallowExport: falsenext tocontextMenu. The app's own keydown handlers don't claim S, and the library's document listener is registered first.
- [nit, fold into 1]
rowDragdefaults on (0 != options.rowDrag→tbody.draggable), so dragging the left 6 px of a row header reorders rows locally viamoveRowwith no server call — the row-equivalent of thecolumnDrag: falsealready set. AddrowDrag: false. Not driven; read frommouseDownControls.
- [nit] The
sheets.csscomment says the menu "never opens"; true for the library menu. Fine.
With finding 1 addressed (and ideally 2–3 in the same line) this is an approve; re-request review after the push.