Skip to content

fix: skip live claimable updates when tab is backgrounded - #1379

Open
omoh5 wants to merge 3 commits into
LabsCrypt:mainfrom
omoh5:fix/1242-background-tab-claimable-ticker
Open

fix: skip live claimable updates when tab is backgrounded#1379
omoh5 wants to merge 3 commits into
LabsCrypt:mainfrom
omoh5:fix/1242-background-tab-claimable-ticker

Conversation

@omoh5

@omoh5 omoh5 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Stops the live "claimable amount" ticker from running every second while the browser tab is backgrounded, reducing unnecessary CPU/battery usage on a page users commonly leave open to "watch" a stream.

Problem

stream-details-content.tsx:155-181 uses a plain setInterval(updateClaimable, 1000) with no document.hidden guard. This means the 1-second update loop runs continuously even when the tab is in the background — wasting CPU cycles and draining battery on mobile/laptop devices.

The codebase already has a more efficient pattern in useStreamingAmount (hooks/useStreamingAmount.ts:66-104), which uses requestAnimationFrame + explicit document.hidden checks. However, that hook uses number types while the stream details component uses BigInt arithmetic throughout (required by formatAmount), so a direct swap isn't possible without a broader refactor.

Solution

Added the same visibility guard pattern from useStreamingAmount directly to the inline interval:

  1. Extracted computeClaimable() — a pure function that returns the current claimable bigint without side effects, so it can be called on demand from both the interval and the visibility change handler.

  2. Guarded updateClaimable() with if (document.hidden) return; — the interval still fires every second for consistent timing, but skips the state update when the tab is backgrounded.

  3. Added visibilitychange listener — when the tab returns to the foreground, computeClaimable() is called immediately to resync the displayed value (matching useStreamingAmount's resync behavior), rather than waiting up to 1 second for the next interval tick.

  4. Proper cleanup — both clearInterval and removeEventListener are returned from the effect.

Files Changed

File Change
frontend/src/app/streams/[id]/stream-details-content.tsx Extract computeClaimable(), add document.hidden guard, add visibilitychange listener
frontend/src/app/streams/[id]/__tests__/stream-details-content.test.tsx New test: "skips updateClaimable when document.hidden is true"

Test

Added a test in the existing stream-details-content.test.tsx test file that:

  • Captures the setInterval callback
  • Sets document.hidden = true (simulating background tab)
  • Invokes the callback and verifies the displayed claimable text doesn't change
  • Verifies the pattern matches useStreamingAmount's approach

Why not replace with useStreamingAmount?

The useStreamingAmount hook accepts number parameters and returns a number, but stream-details-content.tsx uses BigInt arithmetic (required by formatAmount(raw: bigint, decimals: number)). A full migration would require changing the type of liveClaimable from bigint to number, adding BigInt() conversion at every formatAmount call site, and auditing for precision loss on large token amounts. The visibility guard approach is the minimal, targeted fix the audit calls for.

Closes #1242

🤖 Generated with Codebuff
Co-Authored-By: Codebuff noreply@codebuff.com

dedukpe and others added 3 commits August 30, 2026 15:05
Replace npm install with npm ci in backend/Dockerfile (both builder and
runner stages), render.yaml, and vercel.json to ensure deterministic,
lockfile-pinned dependency installs in production builds. This eliminates
the risk of dependency drift between CI-tested and deployed versions.

Closes LabsCrypt#1256

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The setInterval(updateClaimable, 1000) in stream-details-content.tsx
was running every second regardless of whether the tab was visible,
wasting CPU/battery on backgrounded tabs. This mirrors the approach
already used by the useStreamingAmount hook (requestAnimationFrame +
document.hidden guard) but keeps the bigint arithmetic intact.

- Extract computeClaimable() so the value can be recalculated on demand
- Guard the interval callback with document.hidden check
- Add visibilitychange listener to resync immediately when tab returns
  to foreground (matching useStreamingAmount's resync behavior)
- Add test verifying no state updates occur while document.hidden is true

Closes LabsCrypt#1242

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The backend Dockerfile uses npm ci (from PR LabsCrypt#1256) which requires a
package-lock.json. Since this is an npm workspaces monorepo, the
lockfile lived only at the root and wasn't available in the backend/
build context. Generated a standalone backend/package-lock.json so
npm ci can resolve exact dependency versions in the Docker build.

Also added a root .dockerignore for future-proofing.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
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.

[Audit] Live "claimable amount" ticker keeps running every second while the tab is backgrounded

2 participants