Repository navigation
Put a refused cell edit back in the builder, and stop text values at the field's length - #544
Conversation
…he field's length A value the server refused stayed in the Build tab's row, so every later save of that slot sent it again and was refused too, and a capacity change then showed "Saved" over the error. editCell now puts the row's refused cells back to their last saved values, keeping any cell edited again since. The slot editor's text input and a Short text day heading now stop at the field's maxLength. Fixes #514. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2W8jx8iaEzeQ6dUmiNqGD
Tophat: passedI ran 8501d5a locally with
I didn't test editing a cell again while the refused save was still in flight. That needs a slow server response to time it, and For the owner (UX): in the Build tab's slot editor, and in a day heading for a Short text field, typing or pasting now stops at the field's length (200 by default), so the "exceeds maxLength" error can't be reached by typing. If the server still refuses a value, the cell goes back to its last saved value and the error stays in the rail, instead of the bad value staying on screen and quietly blocking every later save of that slot. Compare Screenshots are in https://claude.ai/code/session_01QsKEfKk4ZKF46XNY88GWS4. Generated by Claude Code |
Keeps both Fixed entries in CHANGELOG.md (#514 and #240). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012NV65LfVJWkYDatbG9CV8D
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 1)
The PR does what #514 asks. useBuildState keeps each row's last server-accepted values in savedValues. Every path that learns what the server holds keeps it current: the first load, addRow (and so Duplicate), the refetch after a refused move, and saveRowValues on success, which editCell, moveRow and renameGroup all go through. When an editCell save is refused, rollBackRefused puts back only the cells of the refused request that differ from what was saved, skipping any cell the organizer has changed since (its value no longer matches what was sent) or that has a save waiting in the debounce, which covers the issue's "Watch for" item. renameGroup keeps its own rollback and now also updates savedValues through saveRowValues. setCapacity still sends only { capacity }, so after a rollback its "Saved" is true, because the row has nothing unsaved left. The slot editor's Short text input and a Short text day heading now take config.maxLength. Browser maxlength and zod's .max both count UTF-16 code units, so the limit the input enforces is the one the server checks. Each "Done when" item is done, and the four new hook tests fail without the fix, as the description says.
Blocking (0): none.
Optional (1):
src/components/build-shared/useBuildState.ts:626:savedValuestakes the snapshot of whichever PATCH of a row answers last, not the one the server applied last. If two saves of the same row are in flight together and answer out of order,savedValuescan hold an older snapshot, and a later refusal could roll a cell back to it. It needs two concurrent PATCHes of one row answering out of order and then a third that's refused, and the server's own order is just as uncertain, so it isn't worth a sequence counter here.
Earlier threads: none. Copilot couldn't review because its quota is used up.
Checks: CI is green on 1e88e8d (lint-type-test, e2e, CodeQL). The tophat passed on 8501d5a, and the only commit since is a merge of main, with no runtime change of the PR's own (the shared check prints 0). I read the full diff, the rest of useBuildState.ts (every caller of saveRowValues, renameGroup, setCapacity, the move and field paths) and the new tests. Nothing on the checklist applies: no service, route, schema, policy guard, help article or public page changed, and there's a CHANGELOG entry. The build page sits at 150.9 kB of its 151 kB budget, as the description says, and CI's budget check passes.
Why owner: pipeline:ux. In the Build tab, a Short text value in the slot editor or a Short text day heading now stops at the field's length (200 by default). If the server still refuses a value, the cell goes back to its last saved value and the error stays on the save rail. Compare the tophat's 544-base-refused and 544-base-after-capacity with 544-head-refused-rolled-back and 544-head-date-saved. Whichever of this PR and #542 merges second will need a small merge in saveRowValues.
https://claude.ai/code/session_01KSZnxXQzU2MnAp4VcCMySi
Generated by Claude Code
|
Approved by the owner at https://claude.ai/code/session_01Ga7vyEoy6hrHo2caTyqqAk Generated by Claude Code |
|
Not merged: the PR conflicts with https://claude.ai/code/session_01Ga7vyEoy6hrHo2caTyqqAk Generated by Claude Code |
Resolves the saveRowValues conflict with #542: the PATCH now builds its body with main's toTypedValues, then records savedValues as before. CHANGELOG keeps both sides' Fixed entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JHfJWDavp9zqKK5NLqRgk3
|
Merged
Ran on the merge: https://claude.ai/code/session_01JHfJWDavp9zqKK5NLqRgk3 Generated by Claude Code |
Tophat: passedRan 1824450 locally with
I didn't re-check the Short text day heading's For the owner (UX): same as at Screenshots are in https://claude.ai/code/session_0167febYbbeYTfJ9iZrawUkN. Generated by Claude Code |
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 2)
This round covers the merge from main at 1824450. The rest of the PR is the same as at 1e88e8d, which passed round 1 and which the owner approved. The merge puts saveRowValues and addRow on main's toTypedValues (#542). Both still record savedValues only after the server accepts the request. savedValues keeps the grid's string form (toStringValues / toBuildRow), so rollBackRefused compares strings with strings, as it did before. A Number field's value goes to the server as a number, but the snapshot and the rollback stay in the grid's own strings, so a refused row still goes back to what was on screen when it last saved. rollBackRefused, the maxLength props and the tests are unchanged since round 1.
Blocking (0): none.
Optional (1):
src/components/build-shared/useBuildState.ts:638: as in round 1,savedValueskeeps the snapshot from whichever PATCH of a row answers last. That only matters when two PATCHes of one row answer out of order and a third is then refused. Not worth a sequence counter.
Earlier threads: none. Copilot couldn't review because its quota was used up.
Checks: CI is green on 1824450 (lint-type-test, e2e, CodeQL). The tophat passed on this exact SHA, covering the merge: a refused value is put back, Number values save as numbers, Duplicate works, and the phone width was checked. I read the merged useBuildState.ts diff, toTypedValues / toStringValues / toBuildRow, and the field paths. No service, route, schema, policy guard, help article or public page changed, and there's a CHANGELOG entry under Fixed.
Why owner: pipeline:ux, and the approval at 1e88e8d doesn't carry over, because the merge resolved a conflict in useBuildState.ts. What people will see is the same as what you approved. On the Build tab, a Short text value in the slot editor or a Short text day heading stops at the field's length. If the server still refuses a value, the cell goes back to its last saved value and the error stays on the save rail. To compare, look at the latest tophat's 544-head-refused-rolled-back and 544-head-number-saved.
https://claude.ai/code/session_01SnCqCmJK8kEsGLAPmGZthC
Generated by Claude Code
The merge from main brought in #544, which put the builder page at 151.1 kB, over the 151 kB this branch had lowered it to. Main measures 151.2 kB under 152, and this branch comes in a little under main, so it keeps main's number rather than one the next merge can tip over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGRcD1xKZ6wL67VKSCbbVC
Fixes #514.
#542 (open) also touches
saveRowValues. It moves the number conversion out intotoTypedValues; this PR adds one line after the PATCH. Whichever lands second has a small merge to make.The problem
On the Build tab, when the server refused a value typed into a slot (most often a text value pasted past the field's 200-character limit), the builder kept showing it.
saveRowValuessends every value in the row, so every later edit to that slot was refused too. A capacity change sends only{ capacity }, so it saved and the rail switched to Saved, hiding the error. After a reload, the bad value and every later edit to that slot were gone.The fix
useBuildStatekeeps each row's values as the server last accepted them (savedValues). The first load, a new slot, the slot refetch after a refused move, and every successfulsaveRowValueskeep it current.editCellsave is refused,rollBackRefusedputs that row's changed cells back to their saved values, then the error shows as before. A cell the organizer edited again while the refused save was in flight keeps the newer value: it's skipped when its value no longer matches what was sent, or when it has a save still waiting in the debounce.renameGroupalready rolls back its own rows and is unchanged.Editablegains amaxLengthprop) now stop at the field'sconfig.maxLength, so the usual trigger can't be typed or pasted.Tests
useBuildState.test.ts, neweditCell when the server refuses a valueblock, against a fetch mock that refuses values over 200 characters: a refused edit puts the old value back, and the rail stays on the error. A later edit to another field in the row sends the saved value plus the new one, saves, and shows Saved. A rollback goes to the last value that saved, not the page's first load. An edit typed in the same cell while the refused save was in flight stays and saves. All four failed before the fix.SlotEditor.test.tsxandWysiwygGroup.test.tsx: a Short text input and a Short text heading carry the field'smaxLength. A date input has none.pnpm lint && pnpm typecheck && pnpm knip && pnpm test(1293 passed),pnpm build && pnpm budgets(the build page is at 150.9 kB of 151 kB), and the e2e specsorganizer,enum-picker,slot-row-layoutandreminder-fieldwith the container config (14 passed). No service, schema or route changed, sopnpm test:dbdoesn't apply.affected-articles.mjsfinds no help article touched.Generated by Claude Code