Clicking a Base UI select entry found by text or by a title attribute is a
race. The text node exists one render before the popup finishes entering,
and until then the positioner still carries pointer-events: none, so
user-event refuses the click and the test throws. Querying by role only
matches once the popup is exposed to the accessibility tree, which is after
that window closes.
Route the 37 remaining select interactions through chooseSelectOption, which
does the role query. Instrumenting the converted files shows the text query
resolving while the popup was still pointer-blocked on 6 of 41 samples; the
role query was never blocked.
Seven files kept their text queries because their popup entries carry no
accessible role, so there is nothing to query by.
key_edit_view opened a select and then clicked the option it found by
title text or by raw text. Both queries match the moment the option
enters the DOM, which is one render before the popup finishes entering.
Until then the positioner still carries an inline pointer-events: none,
and user-event refuses to click through it.
That is a race, and a fast machine loses it. Five of the file's 84 tests
failed on every local run while CI stayed green, which is the worst shape
for a test to have: it is only ever red on the machine of whoever is
trying to change the code.
tests/test-utils.tsx already ships chooseSelectOption for exactly this.
It finds the option by role and waits for the positioner to release
pointer events before clicking. The five call sites now use it, and the
helper takes the direct user-event API as well as a setup() instance so
callers do not have to restructure to use it.
Five consecutive full-file runs pass where every previous run failed.
Also finishes this file's screen queries, which brings
prefer-screen-queries to its target of 18.
* chore(ui): upgrade the dashboard to React 19
Bumps react and react-dom from 18.3.1 to 19.2.8 with matching @types. Next 16 already required a React 19 peer, so this aligns the dashboard with what the framework expects and unblocks Base UI and shadcn work that assumes the React 19 ref model.
React 19 passes ref through as a regular prop, so the setup file's forwardRef tripwire and the ref-forwarding test's forwardRef case no longer describe real behavior; both now assert the React 19 contract instead. useRef<T>(null) now yields RefObject<T | null>, which is the one prop type MessageList had to widen.
* test(ui): wait for a Base UI select popup to open before clicking an option
The option lands in the DOM one render before the popup finishes entering, while its positioner still carries pointer-events: none, so clicking it throws. Waiting on the option's text alone was a race that React 19's flush timing loses, which is why four ToolPolicies cases went red on the bump.
chooseSelectOption in test-utils opens the trigger, finds the option by role, waits for it to stop being pointer-blocked, then clicks. It also replaces the last-match-by-text hack, which only worked because the popup happens to portal after the table.
* refactor(ui): replace hand-rolled query-param routing with nuqs
The dashboard carried five copies of the same pushState-based detail
routing hook plus a shared navigateWithParams helper, each with its own
plumbing test and a copy-pasted reactive useSearchParams mock in
component tests. nuqs provides the same shallow history-API routing
behind useQueryState/useQueryStates, so the key, team and org hooks are
deleted in favor of inline useQueryState at their single consumers,
while the models and logs hooks keep their interfaces but drop their
hand-rolled internals. Component tests now mount NuqsTestingAdapter
(via renderWithProviders or locally) instead of patching window.history,
and URL assertions go through onUrlUpdate spies that can additionally
distinguish push from replace, which the old window.location checks
could not
* test(ui): assert browser back closes the log drawer after in-drawer selection
Greptile flagged that the nuqs port of the switching-logs test stopped
at asserting emitted push and replace modes. The test now replays those
recorded modes against a history stack and performs the back step, so a
regression to push-on-select or broken URL-derived drawer state fails
the test instead of passing silently
The team settings guardrails dropdown always rendered the Global and
Other headers, so a proxy with no global guardrails showed an empty
Global heading above the list.