Skip to content

fix(guide): render a probe's open before its settle wait starts - #24249

Merged
KiroCrewCaptain merged 1 commit into
kirodotdev:mainfrom
IngridMorstrad:fix/find-by-name-probe-clock
Oct 26, 2026
Merged

KiroCrewCaptain merged 1 commit into
kirodotdev:mainfrom
IngridMorstrad:fix/find-by-name-probe-clock

Conversation

@IngridMorstrad

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: A guide's ui.find probe finds a control inside a popover, menu or local tab, and puts that container back, even on a busy machine.

On a loaded machine, the probe can open a registered popover, menu or local tab, decide that nothing is inside it, and leave it open on the person's page. CI caught it once as a flake in website/src/guide/findByName.test.tsx > probeForName > "opens a popover without its autofocus taking focus from the person" (run 40192491275, Frontend Tests (1): expected 'none' to be 'found'). That run was on #23937, which has no website/ changes.

Why it matters

When the guide points someone at a control inside a menu, a missed probe means the guide asks them to open the menu themselves, even though the guide could have found the control. A probe that leaves the menu open changes the page the person is looking at, and the probe's contract is that it never does that. The failure also shows up as a cross-PR frontend flake.

Not a goal

  • Changing the probe's time budget, settleMs, or the order it tries containers in.
  • Changing how the popover, dropdown-menu or tabs primitives register with the probe.

What changed (motivation → approach → change)

  • Symptom: on a slow runner the probe answers none for a control that is inside the popover.
  • Root cause: probeOnce opens a container through the primitive's own state (setState from outside React), then waits settleMs on a timer. React renders that update on its next scheduler task. When the settle timer comes due first, entry.isOpen() is still false, so the probe does not search. The restore then reads the open ref, which has not updated yet, and does nothing. The open lands afterwards and stays. The 1500 ms budget is not involved: the test has one container, and the budget is checked only before each open. The same failure reproduces with a frozen now.
  • Change: website/src/guide/findByName.ts runs each entry.open() and each restore() inside flushSync. The container has rendered before the settle wait starts, whatever order the timer and React's task run in. This covers all three primitives, because the probe is the only caller of a probe entry's open.

Backwards compatibility

Compatible: the probe opens and restores the same containers through the same calls, and the render now happens before the settle wait instead of during it. probeForName is only called from a timer callback (useFindProbe), never during a render or effect, so flushSync is valid there.

Removes nothing: no path, record, validator or key is removed.

Tests

Proof: not provable -- frontend test (prove.py runs Python only)

New test in website/src/guide/findByName.test.tsx: "finds inside %s and puts it back when its settle wait ends before the page renders". It runs once each for a popover, a menu and a local tab. Each settle wait ends at the next microtask instead of on a timer, so it always ends before React's render task. The test checks that the probe returns found and that the container is closed (or the original tab is selected again) afterwards. There are no sleeps, retries or real-clock waits.

Red/green evidence (manual):

Run origin/main product code This fix
New test, 11 separate vitest runs × 3 cases 33/33 failed with expected 'none' to be 'found' (the CI signature) 33/33 passed
Same interleaving with now: () => 0 (budget can never run out; scratch, not committed) failed, and the popover was still open 50 ms after the probe returned passed, popover closed
The flaky test's own page with real timers, no stubs: probe started in a setImmediate callback, and the runner stalls 20 ms before it returns to the event loop (scratch, not committed) 50/50 failed 50/50 passed
Whole findByName.test.tsx (75 tests), 6 vitest processes in parallel × 5 runs for load — 30/30 runs passed

Local gates: vitest run src/guide src/components/ui (11 files, 416 tests) passed. So did ChatPage.views.test.tsx and MembersPage.owners.test.tsx, the other suites that drive the guide. eslint is clean on both changed files, tsc -p tsconfig.app.json is clean, and test/test_flake_pattern_ratchet.py passed (60 tests).

Manual verification

N/A — unit coverage sufficient: the defect is the order of one timer and one React scheduler task. The new test pins that order, and a click-through cannot reliably reproduce it.

Screenshots / video

Why no screenshot: No layout or styling change; the probe opens and closes the same containers, only the render timing relative to its wait changes.

Related Issues

Closes #24248

Sibling sweep

Swept: git grep -nE "\.open\(\)|useProbeTarget\(" -- website/src
Sibling: website/src/components/ui/popover.tsx:51 -- not affected -- its probe open and restore now run inside the probe's flushSync at the single caller; covered by the "a popover" case
Sibling: website/src/components/ui/dropdown-menu.tsx:82 -- not affected -- same single caller; covered by the "a menu" case
Sibling: website/src/components/ui/tabs.tsx:148 -- not affected -- same single caller; covered by the "a local tab" case

Pattern harvest

Rule candidate: review-prompt
Pattern: "React state set from outside React (timer or async code), then a fixed setTimeout before reading the rendered DOM or a ref assigned during render: the render can land after the timer, so flush it with flushSync or wait for the commit"

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable) — N/A, no documented behaviour changes
  • No secrets, credentials, or internal references in the diff

Contribution License Agreement

Contributions are accepted under the project's Apache-2.0 license; see the Licensing section of CONTRIBUTING.md.

A guide's ui.find probe opens a registered popover, menu or local tab
through that primitive's own state, waits settleMs on a timer, searches
what the open revealed, then restores it. The open was a plain state
update from outside React, so it rendered on React's next scheduler
task. On a busy machine the settle timer can come due first. The probe
then saw the container still closed and concluded "none", and its
restore read the not-yet-updated open ref and did nothing, so the open
landed afterwards and stayed on the person's page. This is the one-off
CI failure of "opens a popover without its autofocus taking focus from
the person" (expected 'none' to be 'found').

Run each open and each restore inside flushSync, so the container has
rendered before the settle wait starts. A new test per primitive ends
the settle wait before React's task can run; all three fail on main
with the CI signature and pass with the fix.

Closes kirodotdev#24248
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running labels Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5, fork) — ✅ PASS

UX-level review of 3d62e6e9be87bc6e85503a46dd1c77cf97a6766e via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

UX-Verdict: PASS

Nothing to check. This is a render-timing fix inside the guide's probe and its test; no control, string or layout changes, so it needs no screenshot.

[UX-REVIEWED] 3d62e6e

Description read

[DESCRIPTION-READ] c09f63c9a3c862174757ca904a4891cc4c4f4c66b1270d31f02a5c4e249bbe31

That sha256 names everything this verdict read: the PR description, plus the 3 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5, fork) — ✅ PASS

Design-level review of 3d62e6e9be87bc6e85503a46dd1c77cf97a6766e via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

Nothing blocks: the fix addresses the root cause (the open and the restore now render before the settle wait starts), and its only caller runs from a timer, never from a render or an effect, so flushSync is valid there.

[DESIGN-REVIEWED] 3d62e6e

Description read

[DESCRIPTION-READ] d5ede72900695dcc45cd99fe42f609c32919a71ffc71d2b741efe8ffc11ce3d9

That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5, fork) — ✅ PASS

Premise-level review of 3d62e6e9be87bc6e85503a46dd1c77cf97a6766e via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

First-Principles-Verdict: PASS

Nothing blocks. This is a fix to the mechanism that causes the defect: the open and the restore now render before the settle wait starts. The fix is in the probe's only caller of entry.open(), so it covers the popover, the menu and the local tab.

What this change ships

Inventory (2 items) — 2 justified

Intent: a guide probe finds a control inside a popover, menu or local tab and closes it again, even on a slow machine. This is a FIX (#24248, plus a new test that fails on base).

  1. A probe on a busy machine now finds the control inside the container it opened and puts that container back, instead of answering none and leaving it open. — justified
  2. A new regression test runs a popover, a menu and a local tab, with each settle wait ending before React renders. — justified

[FIRST-PRINCIPLES-REVIEWED] 3d62e6e

Description read

[DESCRIPTION-READ] b6e79c515bd66fedce238da0a368e7f6e0c4ee8a8a525c3c60849b0d5e8412f9

That sha256 names the PR description this verdict read. Recompute it with .github/scripts/pr-description-capture.sh; if it differs, the description changed after this verdict was formed and any finding drawn from the description is unproven.

@github-actions

Copy link
Copy Markdown
Contributor

Opus 5.5 Review (fork) — ✅ no blocking findings

Reviewed 3d62e6e9be87bc6e85503a46dd1c77cf97a6766e via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 3d62e6e

@github-actions

Copy link
Copy Markdown
Contributor

GPT 6.1 Review (fork) — ✅ no blocking findings

Reviewed 3d62e6e9be87bc6e85503a46dd1c77cf97a6766e via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 3d62e6e

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Oct 26, 2026
@KiroCrewCaptain
KiroCrewCaptain enabled auto-merge (squash) October 26, 2026 07:06

@KiroCrewCaptain KiroCrewCaptain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, security checklist all-NO, no reviewer-lane findings, readiness signal fresh for this head SHA, declared category matches the diff. Category: the guide probe renders a container's open and restore synchronously before its settle wait (closes #24248), with tests.

@KiroCrewCaptain
KiroCrewCaptain merged commit 9f79a3c into kirodotdev:main Oct 26, 2026
104 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Guide find: a probe whose settle wait ends before the page renders misses the control and leaves the container open

2 participants