fix(client): clear a poll's own error once the poll recovers - #75
Closed
aniruddhaadak80 wants to merge 1 commit into
Closed
aniruddhaadak80 wants to merge 1 commit into
aniruddhaadak80 wants to merge 1 commit into
Conversation
The banner is fed by three things: the state poll, the capture poll, and the
actions the owner takes. Only the actions ever cleared it.
const [s, w] = await Promise.all([api('/state'), api('/workspace')]);
setState(s);
setWorkspace(w);
// no setError('')
So a failure the poll reported was latched for the life of the page. Restart
the server, or lose the network for five seconds, and the app comes back
visibly working - the sidebar, the task list and the chat all repaint from
the very request that succeeded - while a red banner still says it is broken.
The only way out was the Dismiss button, on an error that was no longer true.
The capture poll twenty lines below had the same shape and the same gap, so
it is fixed here too rather than leaving one copy of the bug behind.
Neither poll clears the banner unconditionally. They remember the message
they last failed with and clear only that, because the banner is shared: an
unconditional setError('') would take away the message from a failed action
the owner had not finished reading, one poll tick after it appeared. That is
the same rule SpaceWorkspace's own poll already follows by clearing on
success, and it is what afterPollSuccess states on its own so it can be
tested without a DOM.
Author
|
Closing this in favour of #73, which is the same fix and reached it first. I worked this from issue #51 independently and landed the same rule: a poll should clear the failure it raised, and nothing else, because the banner is shared with errors from actions the owner just took, and a blanket setError('') would take those away a tick after they appeared. I extracted it to a small module with unit tests for the same reason. Having now read #73, it is the better version of that idea and I would rather not compete with it:
No need to merge mine. |
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.
What this changes
The red banner in the app shell is fed by three things: the state poll, the capture poll, and the actions the owner takes. Only the actions ever cleared it.
The state poll (
src/client/App.tsx) looked like this:So once the poll failed, its message was latched for the life of the page. Restart the server, or lose the network for five seconds, and the app comes back visibly working — the sidebar, the task list and the chat all repaint from the very request that succeeded — while a red banner still says it is broken. The only way out is the Dismiss button, on an error that is no longer true.
The capture poll twenty lines below had the same shape and the same gap, so it is fixed here as well rather than leaving one copy of the bug behind.
Neither poll now clears the banner unconditionally. They remember the message they last failed with and clear only that. This matters because the banner is shared: a bare
setError('')on poll success would take away the message from a failed action the owner had not finished reading, one 3-second tick after it appeared.afterPollSuccessstates that rule on its own so it can be tested without a DOM.The sibling poller in
SpaceWorkspace.tsx:46already clears on success at the same 3-second cadence, which is why this reads as an oversight rather than a decision.Closes the behaviour reported in #51.
Why the logic is extracted rather than inlined
This repo's component tests render with
renderToStaticMarkup(tests/controls.test.tsx,tests/transcript.test.tsx), which never runs an effect — so auseEffect+setInterval+asyncpath cannot be asserted that way, and reaching for a DOM environment would mean adding test infrastructure this PR has no business adding. Extracting the one rule intosrc/client/poll-error.tsmatches how the client already factors out pure logic (mergePageSnapshotinSpaceWorkspace,PageChatRequestsinpage-chat-requests.ts, covered bytests/page-chat-request.test.ts).What I did not change, and why
The capture poll also has no in-flight guard, so when a response is slow the 3-second ticks overlap and an older capture can overwrite a newer one. That is a real defect, but it is a different root cause from the latched error and mixing them would make this harder to review.
ComputerPanel.tsx:40-120already solves it with abusyflag plus a revision counter. I left it alone deliberately rather than bundling it — happy to send it separately.Verification
Ran on a clean checkout of
mainwith dependencies installed:npm test— baseline before my change: 35 files, 164 tests, all passing. After: 36 files, 169 tests, all passing. The five new ones are intests/poll-error.test.tsand cover: clearing the poll's own error; leaving an action error alone; leaving an error alone when the poll has not failed; staying empty after a manual dismiss; and not clearing a different message that happens to be showing.src/client/poll-error.tsexisted,tests/poll-error.test.tsfailed to resolve its import, which is the honest version of "red" for an extracted rule.npm run typecheck— exit 0. This caught a real mistake: I had first writtenconst captureError = useRef<string>()inside theuseEffectbody, which is a Rules-of-Hooks violation, and thenuseRef<string>()with no argument, which React 19's types reject. Both are fixed; the refs are now declared at component top level.npm run lint— exit 0.npx tsc -p tsconfig.server.json --noEmit— exit 0, the production build's type half.Per CONTRIBUTING.md I also exercised the changed UI behaviour in a browser: I have not done that, and I want to be plain about it rather than imply otherwise. What I can say concretely is that the rule is unit-tested and that the change is confined to when an existing message is cleared — no request, response shape, or rendered text changes.
npm run check-formatis also worth a note: it reports ~105 files in my working tree, including files I never touched, becausecore.autocrlf=truechecks them out with CRLF while prettier expects LF. That is pre-existing and local, not something this PR introduces — the three files I touched contain zero CRLF pairs and pass the check individually. It looks like the same class of Windows issue #34 and PR #35 describe.