fix(web): resolve the rollback baseline at failure time, not at issue

Review round 3 on #2091. The baseline was still captured when the write
went out, so overlapping toggles reproduced the original bug through the
new machinery: toggle 1 PUTs `true` while its reload is pending, toggle 2
reads the baseline and sees `false`, toggle 1 lands `true`, toggle 2
fails — and its catch pushed the `false` it had closed over, contradicting
both disk and the hook.

`resolve` now reads the record and the list live and takes no fallback,
returning `undefined` when neither has anything to say, so calling it in
the failure handler answers for the state at failure time. Both the
value being written and the rollback go through it, each at the moment
it is needed. The hook says explicitly not to hoist the result.

The regression test drives the exact order — issue 1, issue 2, land 1,
fail 2 — with deferred promises rather than timing, and fails on the
issue-time capture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-23 11:52:16 -04:00
commit ce29d8273d

View file

@ -3737,14 +3737,10 @@ function App() {
if (!current || activeServerId === undefined) return;
// Not `current.settings` directly: that entry only advances on a
// successful list read, so once one has failed it describes disk as it
// was *before* the writes made since. Roll back to the last write known
// to have landed while that is the fresher account (#2089).
const prevSettings = lastPersistedSettings.resolve(
activeServerId,
current.settings ?? EMPTY_SETTINGS,
);
// was *before* the writes made since. Build on the last write known to
// have landed while that is the fresher account (#2089).
const next: InspectorServerSettings = {
...prevSettings,
...(lastPersistedSettings.resolve(activeServerId) ?? EMPTY_SETTINGS),
paginatedLists: value,
};
inspectorClient?.setServerSettings(next);
@ -3787,13 +3783,18 @@ function App() {
.catch((err: unknown) => {
// Persist failed: revert the optimistic override and roll the live
// client setting back, so the UI and client reflect the value that's
// actually on disk rather than the failed edit (#1721). The override
// is set to the baseline rather than cleared: clearing it falls back
// to `persistedPaginatedLists`, read from the same `servers` entry
// that may be stale, which would show the same wrong value from the
// other direction (#2089).
setPaginatedListsOverride(prevSettings.paginatedLists ?? false);
inspectorClient?.setServerSettings(prevSettings);
// actually on disk rather than the failed edit (#1721).
//
// The baseline is resolved *here*, not captured when this write was
// issued: another toggle can land in between, and its value is what
// disk holds by the time this one fails. The override is set to that
// baseline rather than cleared, because clearing it falls back to
// `persistedPaginatedLists` — read from a `servers` entry that may be
// stale, showing the same wrong value from the other side (#2089).
const baseline =
lastPersistedSettings.resolve(activeServerId) ?? EMPTY_SETTINGS;
setPaginatedListsOverride(baseline.paginatedLists ?? false);
inspectorClient?.setServerSettings(baseline);
notifications.show({
title: "Failed to save pagination setting",
message: err instanceof Error ? err.message : String(err),