Skip to content

Say why a live-data or template cell refused an edit (#207) - #231

Merged
SuruchBoss merged 1 commit into
mainfrom
claude/new-session-m6mehm
Oct 5, 2026
Merged

SuruchBoss merged 1 commit into
mainfrom
claude/new-session-m6mehm

Conversation

@SuruchBoss

Copy link
Copy Markdown
Owner

What this changes

Closes #207 (blind test round 4, N7). Next in the queue after #227.

The bug: a live-data cell is read-only, rightly, but it refused in silence. startEdit returned early for a cell it cannot edit, so typing 999 opened no editor, the keys went nowhere, Enter moved the cursor to the next row, and the value was gone. The only explanation was the hover tooltip. A double-click did nothing. A template's locked structure went through the same return and behaved the same way.

The fix:

  • A note under the cell says which cell refused, why, and which button to press: "B2 เป็นข้อมูลสด พิมพ์ทับไม่ได้ — กด "เอาออก" ก่อนถ้าจะแก้เอง" / "B2 is live data and can't be typed over — press "Remove" first to edit it yourself". A template cell names "ปลดล็อกทั้งชีต" / "Unlock the sheet" instead. It is drawn the same way as the "tap again to type" label from [area:mobile] มือถือ: แตะเซลล์ครั้งเดียวแล้วพิมพ์ ข้อความหายเงียบ · On a phone, typing after one tap loses the text silently (R4) #171: CSS from a data-refused attribute, so the cell's text stays its value. It is amber, and it wraps at 14rem so it fits at 390.
  • It is announced once. More refused keystrokes on the same cell keep the note up a little longer without repeating it.
  • Every way in: typing, F2, a double-click, a second tap on a phone, a phone keystroke with no key name (keyCode 229), and Delete/Backspace on a live cell.
  • Enter and Escape put the note away and leave the cursor on the cell, as they dismiss Excel's own protected-cell message. This is the part that matters most. In the blind test the value vanished because 999 and Enter were typed in one go: the note would have come with the first key, then Enter took the cursor away and the note with it. Once the note is gone, Enter moves down as always.

For PO: the visible place, and #200

#207 asks for the refusal to be visible "in the same place #200 decides on". #200 has not decided yet, so I picked the place this grid already uses for a word about one cell: under the cell, as the #171 tap hint does. If #200 settles on something else, this note should follow it. The state is one refusal object with a cell and a text, so moving it is small.

Two things here go a little past the issue's wording. Please say if you want either taken out:

  • Template cells get the same note. They refused through the same canEdit check. The README's template section already promised that every way in is "refused with a reason", but typing was the one that wasn't. The second tap on a phone used to skip locked cells entirely. It now reaches startEdit and gets the note.
  • Enter/Escape are taken while the note is up. That costs one extra Enter for someone who double-clicked a live cell and then wants to move down. Excel costs the same with its dialog.

Reproduced on main first

Same steps as the issue: Live data → Try it: Live sales → Test → Save and add → Insert at A1. Then B2, 999, Enter.

main 5d28538 this PR
Typing 999 on B2 nothing on screen, nothing announced note under B2, announced once
Enter cursor to B3, value gone note put away, cursor stays on B2, value unchanged
Enter again cursor to B3
Delete on B2 nothing the same note, value unchanged
Double-click / second tap on a phone nothing the same note, no editor
A template's locked cell, typed into nothing "A1 เป็นโครงของแม่แบบ แก้ไม่ได้ — กด "ปลดล็อกทั้งชีต" ถ้าต้องการแก้"

How it was verified

  • npm run verify (all ten gates): all ten gates green on 6a46a2f (the current head), result below
  • New e2e flow "typing over a live cell says why nothing was saved, and Enter keeps the cursor on it ([area:live-data] พิมพ์ทับเซลล์ข้อมูลสดแล้วหายเงียบ ไม่มีข้อความบอก · Typing into a live-data cell is discarded with no message #207)". It places the sample source through the real form, then checks typing, Enter twice, Delete, Escape and a double-click on B2.
  • Re-checked against the old code: with main's SpreadsheetGrid.tsx, 7 of its checks fail: no note, nothing announced, Enter leaves B2, Delete and the double-click say nothing. "No editor opens" and "the value stays" pass on both, as they should.
  • New a11y state "live cell refused typing" at 390 and 1280 (119 → 121 checks), because a new thing on screen goes into OPENED_STATES.
  • Tried on a production build of this branch:
    • at 1280, typing in Thai and Delete in English;
    • at 390 with a touch screen: one tap selects with no tap hint, the second tap gives the note and no editor;
    • a protected .xlsx opened as a template: typing into A1 and a second tap at 390 both give the template note, and the field B1 still takes typing;
    • checked by eye. No page errors and no dialogs.
  • Thai line breaks: Thai breaks lines inside the quoted button name ("เอา|ออก"), so the Thai strings put U+2060 (word joiner) inside it. That keeps "เอาออก" and "ปลดล็อกทั้งชีต" on one line. Screen readers ignore it.
  • READMEs and AGENTS.md, both languages:
    • a live-data bullet;
    • a sentence in the template section;
    • the e2e flow count 57 → 58 and its list;
    • the a11y check count 119 → 121.
Gate Result
lint · check:readme · check:screens pass
check:deps every advisory fixed or accounted for
test 1990 passed (1979 by the README's static count)
check:mutants 32/32 killed
build · check:bundle 3800/4200 KB, largest chunk 934/1300 KB
check:a11y 121 checks passed
check:e2e 58 flows passed
  • No unit tests change. The refusal lives in the grid component, and only a browser shows the seam (the keyboard meeting a cell the grid will not open). The test count stays 1979, so picture 27 and the social card are untouched, and check:screens passes.

Anything reviewers should look at closely

  • Not covered here: the formula bar on a live cell is still a read-only input with only its tooltip. Paste and Ctrl+Enter fill over a live block are also unchanged. Both are other ways in, and I kept this PR to the grid's own editor. Happy to open a ticket if you want them done the same way.
  • startEdit is no longer a useCallback. Nothing listed it as a dependency, and it now reads the current note to decide whether to announce.
  • The branch: this is the session's branch claude/new-session-m6mehm. The work was written on a local branch from main 5d28538.

🤖 Generated with Claude Code

https://claude.ai/code/session_013pmWZsbmugfpqtYazrzfjj


Generated by Claude Code

Typing over a live-data cell opened no editor and said nothing: the keys
went nowhere, Enter moved the cursor on, and the value was gone with only a
hover tooltip to explain it. A template's locked structure did the same.

A refused edit now puts a short note under the cell (which cell, why, and
which button to press) and announces it once. It covers typing, F2, a
double-click, a second tap on a phone, a phone keystroke with no key name,
and Delete/Backspace on a live cell. While the note is up, Enter or Escape
puts it away and leaves the cursor on the cell, as Excel's protected-cell
message does, so typing and Enter in one go no longer carries the note away
before it is read.

Adds an e2e flow (fails 7 checks on main) and an a11y state at both widths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013pmWZsbmugfpqtYazrzfjj
@vercel

vercel Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
excel-to-go Ready Ready Preview Oct 4, 2026 5:22pm UTC

Copy link
Copy Markdown
Owner Author

PO review — merged at 6a46a2f (main 004a09d).

Verify (local, full): green — 1990 tests, mutants 32/32, a11y 121, e2e 58. CI 17/17 green on the same head.

Tried as a user on a production build (live sales sample, block A1:G6):

  • 1280 Thai: 999 + Enter on B2 → value unchanged, cursor stays on B2, note "B2 เป็นข้อมูลสด พิมพ์ทับไม่ได้ — กด "เอาออก" ก่อนถ้าจะแก้เอง" under the cell; it goes by itself after ~6 s, and then Enter moves down as usual. Header cell B1 refused the same way. An ordinary cell beside the block types, undoes, and types again normally. "เอาออก" then 999 → B2 takes it.
  • 1280 English: the English note on D2.
  • 390 touch: second tap on G2 (rightmost column of the block) explains instead of opening an editor; the note is shifted to stay on screen (left 139, width 224, viewport 390).
  • No page errors, no alerts. Screenshots checked by eye.

Decisions asked for:

  1. Template structure gets the same note — kept. Same canEdit path, same silence before, and a locked cell is as much a refused edit as a live one.
  2. Enter/Escape put the note away and leave the cursor — accepted. It is what Excel's protected-cell message does, and it is what stops "type, Enter" from carrying the note off before it is read.
  3. Under the cell, like [area:mobile] มือถือ: แตะเซลล์ครั้งเดียวแล้วพิมพ์ ข้อความหายเงียบ · On a phone, typing after one tap loses the text silently (R4) #171's tap hint — accepted, and it sets the pattern: [area:validation] ค่าที่ถูกกติกาปฏิเสธบอกแค่ screen reader คนที่มองจอไม่เห็นอะไรเลย · A refused value is only announced; nothing on screen says it was not saved #200 (a value refused by a validation rule) should use this same note rather than a new style.

Out of scope, followed up: the formula bar is readOnly on a live cell, so nothing appears to be typed — visible, not a loss. Pasting is different: a paste into a live cell is written, announced as pasted, then wiped by the next refresh. That is opened as #232 (sev:high) and joins the hold set.

Next in the queue: #226 → #232 → #224 → #230.


Generated by Claude Code

This branch was successfully deployed

1 active deployment
Preview — 6a46a2fe Deployed Oct 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants