Repository navigation
fix(ui): keep the selected rule and wait for the initial load in E2E - #863
Conversation
namesReady stayed true for one render after a rule switch, so the new rule's statements rendered with raw ids before the lookup names resolved. Key the gate on the exact content object that was warmed. Fixes #862
Teakowa
left a comment
There was a problem hiding this comment.
Verdict: changes requested (round 1). The race diagnosis is plausible, but the fix removes the stale-promise guard and adds a new gate-stuck-closed bug.
1. Out-of-order warm resolution leaves the body hidden — ui/src/lib/RuleView.svelte:32.
The old generation check dropped results from superseded effect runs. The new .then(() => { warmedFor = content }) is unconditional. App.svelte:174 renders one un-keyed RuleView, so the effect re-runs on every switch.
Scenario: open rule A (cold, lookups pending), then click rule B whose identities are all cached. B's warm resolves first and sets warmedFor = B. A's slower warm then resolves and sets warmedFor = A. Now warmedFor !== ruleState.content for B, and namesReady stays false: B never renders, with no further effect run to recover. Fix: guard the assignment, e.g. if (content === ruleState.content) warmedFor = content, or keep a cancel flag from the effect cleanup. Add a UI test with two controllable warm promises resolved in reverse order.
2. Fixes #862 should be Refs #862.
The PR says the openRule(4, 'after') timeout was not reproduced. The issue's stated root cause (serial server latency) is rejected by the PR itself. The PR addresses only the first failure signature (run 39409313917). Use Refs, and keep #862 open for the timeout, or split it into a separate issue. The issue's ask to bound the latency cliff is untouched.
3. Evidence is thin. Before: 1 of 5 failed. After: 6 of 6 passed. That cannot separate a fix from noise at a flake rate near 20%. I did not rerun the E2E (it needs the debug build). Please give ≥20 loaded runs on each of main and the branch, or a deterministic reproduction (delay lookup in the harness so a switch between ready rules shows the stale frame).
Non-blocking: the ENOTEMPTY teardown failure in the E2E profile cleanup is a separate flake. File it as its own issue.
No timeout was widened and no test was weakened, which is good. The scope is one file, and no docs change is needed.
|
Complementary to #864, which fixes the two traced causes (selection clobber in |
load() assigned the first rule as selected after every per-rule read finished, overwriting a click made while rules were still loading. Select before the reads start and keep an existing selection. The E2E read rule 0 content as soon as the list rendered. Wait for the first rule body before the first check. Refs #862
Teakowa
left a comment
There was a problem hiding this comment.
Approve (round 2). The corrected diagnosis holds up on reading the code.
- Cause confirmed by inspection. On
main,load()ended withselected = rules[0]?.summary.idafterPromise.allof the per-rule reads. A click during loading was overwritten. That explains thesetuptitle timeout and the rule-0 body (Set Global Variable(score, 0)) in run 39409313917. - Fix is a root-cause fix.
selected ??= …runs before the reads start and the trailing overwrite is gone.load()is called once, so??=has no stale-selection path. The E2E now waits for the first rule body, a real signal. No timeout is widened and no check is weakened. - Scope. Two files, no Rust. The
RuleView.sveltechange is fully reverted; net diff againstmainis zero for it.Refs #862is correct, since theopenRule(4, 'after')timeout is unconfirmed. Keep #862 open or split it. - CI. UI job SUCCESS. The other jobs were skipped by path detection.
Not verified by me: I did not rerun the E2E or the delayed-server repro (1 s delay, main 0/5 vs branch 10/10). I rely on the author's numbers and the code reading. The loaded 20-run comparison on the final branch is also missing. Non-blocking: file the ENOTEMPTY teardown flake (about a third of runs) as its own issue.
Add four classes: unconditional async writes (#829/#863, escalated as wright#868), Connection: close dropping pipelined requests (#827→#864), E2E asserting before awaited state (#829→#863/#865), and dependency-bump footprint growth (#866). Co-authored-by: Teakowa <27560638+Teakowa@users.noreply.github.com>
Refs #862
Root cause (corrected)
The serial-server hypothesis is not supported: locally
rulescalls take 1–5 ms andlookupcalls about 20 ms in the debug build. The flake is a client race plus an E2E that does not wait for the data it asserts on.App.svelteload()setselected = rules[0]only after every per-rule read finished. A click made while rules were still loading was overwritten, so the view returned to rule 0 (off). This gives theSet Global Variable(score, 0)only body and therule-title === "setup"timeout.e2e.mjsasserted on rule 0 (the disabled marker and its body) as soon as the list rendered, before the per-rule reads landed.Changes
ui/src/App.svelte: select the first rule before the per-rule reads start and keep an existing selection.ui/scripts/e2e.mjs: wait for the first rule body before the first check. No timeout changed.RuleView.sveltechange is reverted in this branch (net diff againstmainis zero for that file). It was not the cause and, as the review showed, could leave a rule body hidden.Evidence
Debug build,
ui/scripts/e2e.mjs, Brave. Delays are temporary local patches inui.rsand are not committed.main0 of 5 runs passed (4 checks failed each run); this branch 10 of 10 passed all 16 checks.lookup:main2 of 20 runs had check failures; the earlierRuleView-only change still had 2 of 20 failures (rule-title === "setup"timeout, three content checks). Not rerun for the final branch with this delay.main1 of 5 failed; the earlierRuleView-only change was 6 of 6.ENOTEMPTYerror removing the browser profile directory after all 16 checks passed. Separate teardown race, not fixed here.npm run check,npm test: pass. No Rust changed.Not confirmed
mainvs the final branch was not done; the delay runs above are the deterministic substitute.