fix(web): picking an agent on /new no longer crashes the tree (CL-8523) - #916
Merged
Merged
Conversation
Closing, skipping or finishing the tour only persisted the seen flag; the Joyride instance kept running for the rest of the session, holding two containers appended to document.body and an overlay above the app. Dismissal now also stops the tour so it unmounts.
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 was wrong
FirstRunTourread its run flag once and never wrote it back:Closing, skipping or finishing the tour called
markTourSeen(userId)— so itwould not replay on the next visit — but
runstayedtrue, so the Joyrideinstance stayed mounted and running for the whole session.
Verified against the running dev stack (fresh load of
/new, tour's Closeclicked):
localStorageflag: set (…first-run-tour-seen:<user>=true)document.bodychildren:#root,#react-joyride-portal,#react-joyride-step-1.react-joyride__beacon: present,.react-joyride__tooltip: presentSo a "closed" tour is in fact an advanced-to-step-2 tour. Its overlay sits
above the page — browser automation refused clicks on the prompt textarea and
on
+ Add agentwith "covered bydiv.react-joyride__overlayinsidediv#react-joyride-portal" — and withspotlightClicks(added in CL-8521)Joyride also keeps a
windowmousemovelistener that re-renders thatoverlay portal on every mouse move.
Those two
document.bodycontainers are the only DOM in the app that iscreated, appended and removed outside React's tree (
JoyridePortalappends/removes its own
div, andreact-floater's portal appends itscontainer from inside
render()), and the containers are torn down andrecreated as the step index changes (
#react-joyride-step-1→#react-joyride-step-3). A React commit that has to remove children from acontainer that non-React code already detached is exactly what produces the
NotFoundError: Failed to execute 'removeChild' on 'Node'cascade up toAppErrorBoundary, and it can only happen while Joyride is still mounted —which, before this change, was always.
The fix
Dismissal now stops the tour as well as remembering it, so
FirstRunTourreturns
nulland Joyride, its two body containers and its listeners areunmounted the moment the person closes it. That also fixes the separate,
directly observed bug that the tour overlay keeps intercepting clicks on the
app after Close.
No new
useEffect, notry/catcharound React, no new tests (the change isa one-line state flip; there is no red/green test that is not coverage
theater).
Not verified
The dev stack serves
main's working tree, not this worktree, so the fixitself was verified by reasoning plus
tsc,lint,fmt,buildandbun test ./src(595 pass). I could not reproduce theremoveChildstorm itself through headless automation: I created a
Scribeagent and ranthe exact sequence (hard load of
/new→ tour Close → type a prompt →+ Add agent→ clickScribe) several times, including with a real hover,with the step-2 tooltip open, after forcing the tour onto a step whose target
does not exist on
/new, and with a flood of syntheticmousemoveeventsaround the click — the console stayed clean every time. The post-Close state
above is reproduced reliably, and is the precondition for the reported crash;
whether some further real-mouse timing is also required could not be
established headlessly.