Skip to content

fix: #172 follow-ups after #169 (analytics warm-up retry, drawer close contrast, IATA oldest test, RX fit race) - #175

Merged
dborup merged 13 commits into
masterfrom
codex/issue-172-followups
Oct 3, 2026
Merged

dborup merged 13 commits into
masterfrom
codex/issue-172-followups

Conversation

@dborup

@dborup dborup commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Summary

Four follow-ups after #169, as listed in #172. Each item has its own test commit and fix commit, except item 3, which is test only.

Relates to #172
Relates to #150

1. Analytics outlasts the server warm-up (item 5 of #150)

Since #145 the server answers 503 + Retry-After: 5 on /api/analytics/{rf,topology,channels} until its background load is done, for up to the 60 s force-open (cmd/server/analytics_warmup_1659.go). api() gave up after 6 attempts (about 30 s), so the page showed "Failed to load".

  • api() takes retry503: false for a caller that retries on its own. Its errors now carry the HTTP status, and a 503's valid Retry-After as retryAfterSeconds. Other callers keep the old 6-attempt loop.
  • loadAnalytics() retries a 503 on the server's interval while the next attempt starts within 120 s of the load's start, and shows a "still loading" status (role="status") meanwhile. Only after that does it show the error.
  • Like the distance tab (fix(analytics): treat lazy distance-index 202 responses as transient #120), each load and destroy() bump _loadGen and clear the single retry timer. A superseded load or a page the user has left never renders.
  • The retry-delay helper is now shared with the distance tab (_retryAfterDelayMs).

The 120 s cap is a constant. Per AGENTS.md rule 8, exposing it in the customizer is left for later.

Perf: this is not a hot path. During a warm-up, each open analytics page sends at most three requests every 5 s, for at most 120 s. Successful endpoints are cached by api() and are not fetched again.

2. Drawer close-button contrast in every preset

The close button used --nav-text-muted on --nav-bg2. That falls below 4.5:1 in forest/light (3.38), forest/dark (4.05), sunset/dark (3.03) and mono/dark (3.67).

The fix gives the button --nav-text, the same colour as the title next to it, which passes on that background in every preset and theme. It is one declaration on one control, so it is the least invasive option. Changing --nav-text-muted in the four presets would also recolour every inactive navbar link and drawer item, and it would not help an operator's own theme. Hover and focus are still visible through the hover background and the focus outline.

3. IATA throttle: oldest refresh after a partial sweep (test only)

TestIATADropThrottleNoResweepAfterPartialSweep does a partial sweep, refills the table, and sends 100 new regions before the next deadline. Exactly one sweep may happen in that window, and exactly one more at the deadline. No production change was needed.

4. RX Coverage: stale fitToObserver response

A fit request now gets a sequence number, like the coverage and leaderboard requests in #169, and it only applies while it is the latest fit (isLatestFit). A newer fit, a change of days and the All button each bump the sequence, so a stale response, or its error path, does nothing.

Tests

Item Test Master Branch
1 test-1659-analytics-warmup.js (#172 part, 5 tests) 5 red 12/12
2 test-nav-drawer-1064-e2e.js step (k), 8 presets × light/dark 4 red (25/29) 29/29
3 TestIATADropThrottleNoResweepAfterPartialSweep green (code was correct) green, also with -race
4 test-issue-124-rx-coverage-viewport.js 14a–c 3 red (13/16) 16/16

Mutants (each run in a scratch copy) and the test that catches each one:

  • analytics cap set to 30 s → the 45 s warm-up and the permanent-503 tests;
  • destroy() no longer cancels → the leaving-the-page test;
  • no generation check on success → the slow-superseded-load test;
  • the old api() retry loop kept → three tests;
  • close button on --text-muted → step (k), 10 combinations red;
  • t.oldest = oldest removed → the new IATA test (101 sweeps instead of 1);
  • the fit response checks only isLive → 14a–c;
  • setDays does not drop the fit → 14a;
  • All does not drop the fit → 14b.

The following runs were identical on master and branch:

  • test-frontend-helpers.js: 705 passed, plus the 2 known favStar failures.
  • test-analytics-channels-integration.js: the same single failure (channels.js sidebar link).
  • test-e2e-playwright.js against a local fixture server: stops fail-fast at "Version info lives on Perf dashboard".

scripts/check-xss-sinks.sh --diff origin/master and scripts/check-css-vars.js are clean. The fork guards in deploy.yml are unchanged: 9.

Browser validation (local server, scratch proxies)

  • Analytics: a proxy answered 503 + Retry-After: 5 for 45 s. On desktop and mobile the page showed "still loading", then the data at about 45.9 s, and never an error. Master showed "Failed to load" at about 30.8 s.
  • Drawer: forest/light, sunset/dark and mono/dark measure 6.39, 9.37 and 17.4:1 for the close button (screenshots taken).
  • RX Coverage: a proxy held the observer extent for 4 s. Switching days, choosing All or choosing another observer while that request was in flight never moved the map to the stale extent, on desktop or mobile. On master all six cases moved the map.

🤖 Generated with Claude Code

dborup and others added 7 commits October 2, 2026 18:13
#172)

Part #172 of test-1659-analytics-warmup.js runs the real app.js api() and
the real analytics page in one vm with a fake fetch and a fake clock. The
server answers 503 + Retry-After: 5 on rf/topology/channels until its
background load is done (up to the 60 s force-open).

- a 45 s warm-up must end with data, with a "still loading" state and
  no error on the way;
- a permanent 503 must keep retrying for at least 90 s and give up by
  about 120 s;
- destroy() during the warm-up must stop the retries and write nothing;
- a new load during the warm-up must leave one retry timer and render
  the data once.

Red on master: the error shows at 31 s (api() stops after 6 attempts),
retries keep running after destroy(), and three retry timers are pending.

Relates to #172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…172)

Since #145 the server answers 503 + Retry-After on rf/topology/channels
until its whole background load is done, for up to its 60 s force-open.
api() gave up after 6 attempts (about 30 s at Retry-After: 5) and the page
showed "Failed to load".

- api() takes retry503:false for a caller that retries on its own. Its
  errors now carry the HTTP status, and a 503's valid Retry-After as
  retryAfterSeconds. Other callers keep the old 6-attempt loop.
- loadAnalytics() retries a 503 on the server's interval while the next
  attempt starts within 120 s of the load's start, and shows a "still
  loading" status meanwhile; only then does it show the error.
- Like the distance tab (#120), each load and destroy() bump _loadGen and
  clear the one retry timer, so a superseded load or a left page never
  renders. The retry delay helper is shared with the distance tab.

Also adds a test that a slow response of a superseded load does not
render over the newer one (red on master; it was added here because a
mutant without the success-path generation check survived the first
four).

Relates to #172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…172)

Step (k) of test-nav-drawer-1064-e2e.js now walks every customizer
preset (read from the preset buttons) in the light and the dark theme,
checks that the preset's --nav-bg2 is applied, waits for the colour
transitions to settle, and requires 4.5:1 on the header background for
both the title and the close button (the close button used to need 3:1).

Red on master: the close button's --nav-text-muted measures 3.38:1
(forest/light), 4.05:1 (forest/dark), 3.03:1 (sunset/dark) and 3.67:1
(mono/dark).

Relates to #172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#169 put the drawer header on --nav-bg2. The close button kept
--nav-text-muted, which is under 4.5:1 on --nav-bg2 in four presets:
forest/light 3.38, forest/dark 4.05, sunset/dark 3.03, mono/dark 3.67.

The close button now uses --nav-text, the colour of the title beside
it, which passes on the same background in every preset and theme. This
is the least invasive fix: one declaration on one control. Changing the
four presets' --nav-text-muted instead would also recolour every
inactive navbar link and drawer item, and would not protect an
operator's own theme. Hover and focus stay visible through the hover
background and the focus outline.

Relates to #172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestIATADropThrottleNoResweepAfterPartialSweep fills the throttle table
with entries at +0 and +30m, lets a new region at +1h sweep out the +0
half, refills the table, and then sends 100 new regions between +1h and
+1h29m. None of them may sweep again: the oldest kept entry (+30m)
cannot expire before +1h30m, where exactly one more sweep must run.

Green on master, which is correct. Without `t.oldest = oldest` in
shouldWarn it fails with 101 sweeps instead of 1; every other IATA test
stays green with that line removed. Test only: no production change.

Relates to #172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests 14a-c in test-issue-124-rx-coverage-viewport.js open an
observer-only link, so fitToObserver() requests the observer's extent
for days=7, and keep that response pending. They then switch days to
30 (14a), pick All (14b) or pick another observer (14c), release the
stale response, and require that fitBounds is not called for it. 14c
also requires that the newer observer's extent still fits the map.

Red on master: in all three the stale extent calls fitBounds.

Relates to #172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fitToObserver() fetches the observer's extent and then calls fitBounds.
Only isLive(gen) guarded the response, so switching days, picking All or
picking another observer while it was in flight still moved the map to
the old request's extent. Same class of bug as item 3 of #150.

A fit request now takes a sequence number like coverage and leaderboard
(#169) and applies only while it is the latest fit (isLatestFit). A
newer fit, a days switch and All bump the sequence, so the stale
response (and its error path) does nothing.

Relates to #172

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dborup and others added 4 commits October 3, 2026 06:02
…clicks during warm-up (#172)

- api() shares one in-flight promise per path, so a retry503:false
  caller and a default caller get each other's behaviour (3 tests).
- Clicking a data tab while the analytics are still loading throws a
  TypeError instead of showing the loading state, and the warm-up
  retries overwrite a tab that fetches its own data (7 tests).
- Pin what was untested: a 500/404 fails at once without retries, the
  retry follows Retry-After clamped to 1..30 s, and the loading state
  is role="status" (6 tests, green on the current code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vwXk6z7Bcv1AmN2qd6WSZ
The tab-click tests ran the tabs on the overview's minimal stub, so a
correct guard still failed inside renderRF/renderTopology/... on data
those tabs never get from the server. They now use the CI fixture
server's real responses for the five shared endpoints (arrays cut to
two items, node names and keys anonymised) and check that the clicked
tab, not the overview, renders after the warm-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vwXk6z7Bcv1AmN2qd6WSZ
…me retry503 (#172)

_inflight was keyed on the path alone. A default caller that joined a
retry503:false request got its 503 after one attempt, and a
retry503:false caller that joined a retrying request waited out the
whole retry loop (up to ~31 s) without the cancellation analytics.js
relies on. The key now includes the flag; default callers keep the
bare path as their key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vwXk6z7Bcv1AmN2qd6WSZ
…ding (#172)

While the shared load had no data, a click on Overview, RF, Topology,
Channels, Hash Stats or Hash Issues rendered from an empty
_analyticsData and threw an uncaught TypeError (snrValues, ...). The
window is now up to 120 s instead of ~30 s.

renderTab() now shows the load's current status (loading, still
loading or the error) on those six tabs until the data is there, and
the load renders the selected tab when it is. The load writes its
status only while one of those tabs is shown, so its 503 retries no
longer overwrite a tab that fetches its own data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vwXk6z7Bcv1AmN2qd6WSZ
@dborup-agent

Copy link
Copy Markdown
Collaborator

Review feedback addressed (commits ae27ab35, 45d9e855, fe88e842, d7cd59b2)

  1. _inflight ignored retry503 (fe88e842). The in-flight key is now the path plus the flag. A default caller keeps the bare path, so it no longer joins a retry503:false request and gets its 503 after 1 s. A retry503:false caller no longer joins a retrying request and waits ~31 s. Tests: both directions, plus "same flag still shares one request" (test-1659-analytics-warmup.js).
  2. Test gaps (ae27ab35, test only; it passes on the existing code and catches the named mutants):
    • a 500 and a 404 show the error at once, with no retry and no pending timer (J19);
    • Retry-After 1 / 12 / 60 / abc → retry after 1 / 12 / 30 / 5 s, and the status text says so (J11b, J4);
    • the loading state is role="status" and not an alert (J8).
  3. Tab click during loading threw a TypeError (d7cd59b2). The six tabs that render from the shared load (Overview, RF, Topology, Channels, Hash Stats, Hash Issues) show the load's current status (loading, still loading or the error) until the data is there. The load then renders the selected tab. The load also writes its status only while one of those tabs is shown, so its 503 retries no longer overwrite a tab that fetches its own data (on 1c510d4 the Nodes and Roles tabs got overwritten). Tests: a click on each of the six tabs during a 45 s warm-up must not throw, must show the loading state, and must render that tab after the warm-up. Another test checks that a self-fetching tab is left alone. 45d9e855 gives these tests the real response shapes from the CI fixture server (test-fixtures/analytics-tabs-172.json: arrays cut to two items, names and keys anonymised).

Not changed: the drawer close-button hover colour. Measured on the hover background in 8 presets × light/dark, only --nav-text reaches 4.5:1 everywhere: --accent goes down to 1.14 and --nav-text-muted to 2.73. The hover background change stays the visible difference.

Mutants (each in a scratch copy)

Mutant Caught by
J13: _inflight keyed on the path alone the 3 in-flight tests
J19: no e.status === 503 check "a 500 / a 404 shows the error at once"
J11b: analytics ignores Retry-After (fixed 5 s) "Retry-After: 1 / 12 / 60"
J4: no err.retryAfterSeconds "Retry-After: 1 / 12 / 60"
J8: no role="status" "Retry-After: …" (4) and the 6 tab-click tests
tab click without the guard the 6 tab-click tests
status written over any tab "a tab that fetches its own data is not overwritten"

Runs

  • test-1659-analytics-warmup.js: 28/28 (was 12). On 1c510d4 the 10 new reproduction tests fail.
  • The 85 node test-*.js from the CI unit step: all green, the same as on 1c510d4.
  • 34 more app/analytics-related test-*.js: the same results as on 1c510d4. That includes the known failures: test-frontend-helpers.js 705 passed + 2 favStar, and test-analytics-channels-integration.js 1 failure.
  • check-xss-sinks.sh --diff origin/master: clean. check-css-vars.js: OK. Fork guards: 9, unchanged.

Browser (headless Chromium, local server, CI fixture; 503s mocked with page.route)

  • Warm-up 45 s with Retry-After: 5: the data appears at ~45.1 s with no error, on desktop and mobile. Warm-up 40 s with Retry-After: 45 (clamped to 30 s): the data appears at ~60 s (retries at 30 s and 60 s).
  • 20 tab clicks during a 30 s warm-up: 0 pageerrors on desktop and mobile. On 1c510d4 there were 7 (totalTransmissions, snrValues, hopDistribution, channels, distribution). RF, clicked last, renders by itself at ~30.2 s.
  • test-nav-drawer-1064-e2e.js: 29/29. test-e2e-playwright.js: 132/135 (3 skipped), the same as on 1c510d4.

🤖 Generated with Claude Code

https://claude.ai/code/session_017vwXk6z7Bcv1AmN2qd6WSZ

dborup and others added 2 commits October 3, 2026 08:05
…e synthetic (#172)

The fixture still had two real observer pubkeys from e2e-fixture.db as
object keys in topoData.perObserverReach. They are now synthetic 64-hex
keys like the other ids. The values, the key order and the shape are
unchanged. A scan of every key and value against nodes, observers,
transmissions (hash, from_pubkey, decoded_json) and neighbor_edges finds
no real ids or names left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TdKwzUPntKQHjQEKpMXefc
…he status, theme-refresh during a load (#172)

Re-review gaps in test-1659-analytics-warmup.js (34 tests, was 28):

- The self-fetching-tab test wrote its own marker after the click, so it
  passed even when renderTab skipped the tab's render function (guard
  without LOAD_TABS.has(tab)). It now runs for Route Patterns, Roles and
  Nodes and requires that tab's own fetch and its own output during the
  warm-up, and that the 503 retries leave the output alone.
- After a failed load and a region change, a data tab clicked during the
  new load must show "Loading analytics", not the old "Failed to load".
- theme-refresh on Overview and RF during the warm-up must not throw and
  must keep the loading state; the data renders after the warm-up. The
  page's window listener is captured and called like app.js does.
- A slow 500 of a superseded load must not be shown during the newer
  load (the gen check in the error path had no test).

Test only. On 1c510d4 the theme-refresh tests fail with the reported
TypeError (totalTransmissions, snrValues).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TdKwzUPntKQHjQEKpMXefc
@dborup-agent

Copy link
Copy Markdown
Collaborator

Review feedback addressed (commits 837abe98, 7ad6defd)

Test and fixture only. public/ and the other production code are unchanged since d7cd59b2.

  1. N1: the self-fetching-tab test proved nothing (7ad6defd). The test wrote its own marker after the click. It now runs for Route Patterns, Roles and Nodes. Each tab gets a small answer from its own endpoint. During the warm-up the test requires that tab's own fetch and its own output. After that it requires that the 503 retries leave the output alone. M8 (LOAD_TABS.has(tab) && removed) now fails all 3 of these tests.
  2. N2: no test for the status reset on a new load (7ad6defd). New test: a 500 shows "Failed to load", a region change starts a new load, and an RF tab click during that load must show "Loading analytics", not the old error. RF renders once the new load is done. M6 now fails this test.
  3. theme-refresh during a load (7ad6defd). The test environment captures the page's theme-refresh listener on window. Two new tests (Overview and RF) call it during the warm-up. They require no error, an unchanged role="status" loading state, and the data after the warm-up. On 1c510d48 they fail with the reported TypeError (totalTransmissions, snrValues). If the guard is removed, or moved into the click handler so that only the theme-refresh path is unguarded, both tests fail.
  4. N3: two real pubkeys in the fixture (837abe98). The two perObserverReach keys are now synthetic 64-hex keys, uppercase like the other observer ids. The values, the key order, the shape and the file size are unchanged. I checked every key and value against e2e-fixture.db: nodes.public_key, observers.id, transmissions.hash / from_pubkey / pubkeys in decoded_json, neighbor_edges.node_a/b, node and observer names, and the 64-hex keys in test-fixtures/*.sql. Real ids: 2 before, 0 after.
  5. Extra: the gen check in the error path had no test. New test: a slow 500 from a superseded load must not be shown during the newer load.

Mutants (each in a scratch copy, test-1659-analytics-warmup.js)

Mutant Caught by
M8: guard without LOAD_TABS.has(tab) "clicking subpaths / roles / nodes during the warm-up renders that tab…" (3)
M6: no _loadStatusHtml = LOADING_HTML on a new load "after a failed load, a data tab clicked during the next load shows Loading…"
renderTab guard removed the 6 data-tab click tests, the 2 theme-refresh tests, the M6 test
guard moved to the click handler (theme-refresh only unguarded) the 2 theme-refresh tests
_inflight keyed on the path alone the 3 in-flight tests
no e.status === 503 check "a 500 / a 404 shows the error at once", the M6 test
load ignores Retry-After (fixed 5 s) "Retry-After: 1 / 12 / 60"
no err.retryAfterSeconds in app.js "Retry-After: 1 / 12 / 60"
load status written over any tab the 3 self-fetching-tab tests
no role="status" "Retry-After: …" (4), 6 tab-click tests, 2 theme-refresh tests
no gen check after the responses "a slow response of a superseded load…"
no gen check in the error path "a slow error of a superseded load…" (new)
warm-up cap 30 s 12 tests, including "a warm-up longer than 30 s…"
destroy() keeps the retry timer "leaving the page during the warm-up…"

All 14 mutants are caught on the branch and on a local merge with origin/master. Against the old test file, M8, M6, "guard in the click handler" and "no gen check in the error path" survive.

Runs

  • test-1659-analytics-warmup.js: 34/34 (was 28).
  • On the branch, test-app-api-inflight-cleanup-rejection.js 8/8, test-warmup-banner.js 13/13 and test-issue-1375-scope-stats-fetch.js 9/9 pass. test-frontend-helpers.js fails 2 (favStar), and test-analytics-channels-integration.js fails 1, both already fixed on master.
  • On a local merge with origin/master (cbd562cd): all of the above are green, including test-frontend-helpers.js 707/707 and test-analytics-channels-integration.js 24/24.
  • Local full validation at 7ad6defd (Go with -race, the JS unit tests in the CI list, Playwright E2E on the CI fixture): 204 PASS, 0 FAIL.
  • Fork guards: 9 github.repository == 'Kpa-clawbot/CoreScope', unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TdKwzUPntKQHjQEKpMXefc

dborup commented Oct 3, 2026

Copy link
Copy Markdown
Owner Author

Review — CS-Cloud re-review PR#175 runde 3 — head 7ad6def

Dom: APPROVE with nits

Delta review of d7cd59b2..7ad6defd (2 commits, 2 files, +113/−15). Read-only: I read the code through git archive copies and ran everything in a scratch directory. The head was 7ad6defd07749c00d943226eea138b902b25df30 before and after the review (git ls-remote). Tags: [T] test, [A] analysis, [K] not checked.

Checks

Check Result
No changes in public/ Yes [T]. git diff --name-only d7cd59b2 7ad6defd lists no public/ file, and the public/ trees of both commits are identical. The round only changes test-1659-analytics-warmup.js and test-fixtures/analytics-tabs-172.json.
Fixture shape unchanged Yes [T]. The only change is the two topoData.perObserverReach keys. The shape is identical with those two keys compared by position, and no leaf value changed.
Fixture free of e2e-fixture.db pubkeys Yes [T]. The fixture has 22 distinct 64-hex strings. I extracted every 64-hex string from every text column of test-fixtures/e2e-fixture.db (258) and compared case-insensitively: 0 in common, as keys or as values. Searching the raw bytes of the DB file also finds none. The same scan on round 2 found 2 in common (the two old keys), so the fix did what it should. The DB file is unchanged.
M8 caught Yes [T]. See the table below.
M6 caught Yes [T].
Guard in the theme-refresh path caught Yes [T]. See the table below.
CI on 7ad6def Green [T]. I read the check runs through the GitHub API; the API reported 7ad6defd as the PR head. Go Build & Test: success. Playwright E2E Tests: success. Build & Publish Docker Image: success. Staging deploy, badges and release artifacts are skipped, as expected for a draft PR.

Test results [T]

  • test-1659-analytics-warmup.js: 34 passed, 0 failed (round 2: 28). I ran it 5 times with the same result; it uses a fake clock and a fake Date.
  • test-packet-filter.js 92/0, test-aging.js 19/0, test-app-api-inflight-cleanup-rejection.js PASS, test-warmup-banner.js 13/0, test-issue-124-rx-coverage-viewport.js 16/0, test-issue-120-distance-building.js 13/0.
  • test-frontend-helpers.js: 705 passed, 2 failed. These are the known favStar failures, identical on master.

Mutants [T]

Each mutant was applied to a scratch copy of the head and run against both the round-2 test and the round-3 test. "Survived" means the test file stayed green.

Mutant Round-2 test Round-3 test
M8a: the renderTab load-status guard applies to every tab, so own-data tabs show the status survived killed by clicking "subpaths"/"roles"/"nodes" during the warm-up renders that tab, and the retries leave it alone
M8b: the load status is written over a tab with its own data killed killed (same 3 tests)
M8c: subpaths added to LOAD_TABS killed killed (subpaths test)
M6: a new load does not reset the status to "Loading" survived killed by after a failed load, a data tab clicked during the next load shows "Loading", not the old error
T1: the renderTab guard is removed killed (6) killed (10), including both theme-refresh tests
T5: guard bypassed only on the theme-refresh path (the handler renders overview and rf without the guard) survived killed by theme-refresh on "overview"/"rf" during the warm-up keeps the loading state, then the data renders
T3: the theme-refresh listener is not registered survived killed (both theme-refresh tests)
T2: guard only for overview killed killed
T4: collisions missing from LOAD_TABS killed killed

So M8a, M6, T3 and T5 survived round 2 and are now killed. T1 and T2 were already killed by the tab-click tests, because they share the renderTab guard with the theme-refresh handler. T5 shows that the new theme-refresh tests catch a guard bypass that exists only on the theme path.

Nits

  1. [K] The fixture still holds 20 other 64-hex values (hop pubkeys, observer_ids and observers[].id). None is in e2e-fixture.db or anywhere else in the repository, which is what this round asked for. I cannot tell from the repository whether they came from a real network capture. If "fully synthetic" is meant strictly, they could be replaced the same way as the two keys. This is not blocking.
  2. [A] The round-3 tests add OWN_TABS and pageEnv options (realData, status, retryAfter) inside one 700-line test file. That is fine now. If more tabs are added later, moving the fake DOM and clock helpers to a shared test helper would help.

Not checked

  • [K] The browser behaviour of the round-3 tests. They run in a vm sandbox with a fake DOM, so they prove the logic and not the rendering. My earlier browser checks covered 1c510d48, not this head.
  • [K] The Go, CSS and rx-coverage changes of earlier rounds were not re-run on this head. None of those files changed in this round.

Generated by Claude Code

@dborup
dborup marked this pull request as ready for review October 3, 2026 09:56
@dborup
dborup merged commit 751f8fc into master Oct 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants