Files
ironclaw/tests
Henry Park 37669e6ec8 fix(web): prevent browser crash from timer leaks, DOM growth, SSE buffer (#2406) (#2441)
* fix(web): prevent browser crash from timer leaks, DOM growth, SSE buffer (#2406)

Extended sessions with heavy bot interactions caused Chrome's "Pages
Unresponsive" dialog due to accumulated browser resources that were
never cleaned up.

Fixes:
- Add cleanupConnectionState() to clear leaked setInterval/setTimeout
  timers on SSE reconnect, tab visibility change, and page unload
- Cap DOM at 200 message nodes via pruneOldMessages() with streaming-
  aware pruning (skips data-streaming elements, called at turn
  boundaries and after loadHistory)
- Cap jobEvents Map at 50 entries with LRU eviction (excludes current
  job from eviction scan)
- Increase SSE broadcast buffer from 256 to 1024 (configurable via
  SSE_BROADCAST_BUFFER env var, with zero-guard to prevent panic)

Closes #2406

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): address PR #2433 review — move SSE buffer to GatewayConfig, fix E2E timer test

Move SSE_BROADCAST_BUFFER env var from direct std::env::var() in sse.rs
to GatewayConfig in config/channels.rs, following the convention that all
gateway env vars flow through structured config. Add MAX_BROADCAST_BUFFER
(65,536) clamp to prevent OOM from misconfiguration.

Fix E2E timer leak test to install setInterval monkey-patch via
page.add_init_script() before navigation so initialization timers are
tracked. Add test_dom_resource_limits.py to E2E CLAUDE.md scenario table.

Add unit test for buffer config parsing, zero-rejection, and clamp.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): address review — clean gatewayStatusInterval, prune user msgs, use constants

- Add gatewayStatusInterval to cleanupConnectionState() so it is cleared
  on reconnect/tab-hide/unload; add guard in startGatewayStatusPolling()
  to prevent double-start; restart polling on tab visibility restore
- Call pruneOldMessages() after addMessage('user', ...) in sendMessage()
  so DOM stays bounded even during rapid user input
- Replace hardcoded broadcast_buffer: 1024 with DEFAULT_BROADCAST_BUFFER
  in all test construction sites (5 occurrences across 4 files)
- Document in from_sender() doc comment why broadcast_buffer is absent
- Tighten E2E timer leak assertion from baseline+1 to baseline

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): address PR #2441 review — prune/timer/assert/doc fixes

- Remove pruneOldMessages() from loadHistory() pagination path to avoid
  immediately evicting just-prepended older messages
- Move MAX_DOM_MESSAGES constant to top-level constants block
- Add _loadThreadsTimer to cleanupConnectionState() for consistency
- Add assert!(broadcast_buffer > 0) to SseManager constructor with
  panic doc (tokio broadcast channel requires capacity > 0)
- Use Set-based interval tracking in E2E test to prevent counter
  underflow from double-clear
- Update CLAUDE.md broadcast buffer docs (256 → 1024, SSE_BROADCAST_BUFFER)

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): remove assert! from SseManager to pass no-panics CI check

Replace assert!(broadcast_buffer > 0) with a doc comment noting the
precondition. GatewayConfig already rejects 0 at the config layer.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): address ilblackdragon review — correctness, e2e tests, docs (#2406)

Correctness:
- pruneOldMessages: clean up orphaned leading time-separators after pruning
- jobEvents LRU: replace O(n) scan with O(1) Map insertion-order eviction
- Document degenerate all-streaming under-prune case

Playwright e2e tests:
- Tab hide/restore: no duplicate gateway status polling intervals
- DOM cap + streaming: 260 elements prune to ≤200, streaming preserved, no orphan separators
- jobEvents bounded: 60 jobs stay capped at ≤50 via LRU eviction
- Fix assertion selector to match pruneOldMessages superset, tighten lower bound

Rust:
- Unit test: SseManager buffer size parameter actually controls lag behavior
- Document MAX_BROADCAST_BUFFER memory impact (65K×100×200B ≈ 1.3 GB)
- Move "capacity baked into tx" comment from from_sender to rebuild_state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): protect currentJobId from LRU eviction in jobEvents map (#2441)

The O(1) LRU eviction skips the job that just received an event (moved
to end via delete+set), but did not protect the job the user is actively
viewing in the detail panel (currentJobId). If the user views a quiet
job while 50+ other jobs fire events, the viewed job's events would be
evicted and the activity tab would appear empty.

Add a currentJobId guard to the eviction loop and a Playwright e2e test
that verifies the actively-viewed job survives LRU pressure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(e2e): add real-flow Playwright tests for DOM resource limits (#2406)

Add 4 E2E tests that exercise pruning and timer cleanup through actual
UI interactions (mock LLM round-trips, real SSE reconnects) instead of
page.evaluate() injection. Also fix the existing timer leak test which
failed due to execution context destruction from add_init_script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: serrrfirat <f@nuff.tech>
2026-04-14 10:25:42 -07:00
..