Skip to content

all fixed#373

Merged
OlufunbiIK merged 1 commit intoOlufunbiIK:mainfrom
MadakiElisha:stabilize-overlay
Mar 29, 2026
Merged

all fixed#373
OlufunbiIK merged 1 commit intoOlufunbiIK:mainfrom
MadakiElisha:stabilize-overlay

Conversation

@MadakiElisha
Copy link
Copy Markdown
Contributor

@MadakiElisha MadakiElisha commented Mar 29, 2026

close #247

Summary by CodeRabbit

  • New Features

    • Tips are now batched together for a smoother, more cohesive display experience
  • Improvements

    • Enhanced accessibility with added semantic labels for screen readers
    • Improved leaderboard stability with deterministic ordering and better data validation
    • Performance optimizations to reduce unnecessary component re-renders

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 29, 2026

@MadakiElisha is attempting to deploy a commit to the olufunbiik's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

This PR implements tip batching in the live performance overlay to stabilize rapid updates, applies React.memo optimization to reduce unnecessary re-renders, extends the LeaderboardEntry type with stability fields, adds accessibility attributes, and re-enables comprehensive tests for batched behavior validation.

Changes

Cohort / File(s) Summary
Component Memoization
frontend/src/components/live-performance/HypeMeter.tsx, LiveLeaderboard.tsx, LiveTipAlert.tsx, SessionTicker.tsx
Wrapped default exports with React.memo() to skip re-renders when props are unchanged. LiveLeaderboard.tsx also added aria-label="Session leaderboard" to <ul> and updated list item keys to use supporterId or deterministic fallback for stability.
Tip Batching & Session Management
frontend/src/components/live-performance/LivePerformanceMode.tsx
Implemented batching for incoming tips via handleIncomingTip → queue → timed flushQueuedTips. Added exported TIP_BATCH_WINDOW_MS constant, refactored session initialization with useMemo(loadSessionState), adjusted hype decay logic with new constants, added refs for session-active and artistId tracking to prevent stale closures, and introduced helper functions (sanitizeLeaderboard, normalizeIncomingTip, applyTipBatch) for batching and data normalization.
Type Extensions & Configuration
frontend/src/components/live-performance/types.ts, frontend/vite.config.ts
Extended LeaderboardEntry interface with supporterId: string and sortOrder: number fields for deterministic ordering. Removed LivePerformanceMode.test.tsx from Vitest exclusion list to enable test execution.
Test Expansion
frontend/src/components/live-performance/LivePerformanceMode.test.tsx
Added within import and afterEach hook, imported TIP_BATCH_WINDOW_MS, refactored websocket test setup with helper functions (getTipHandler, emitTip), switched to fake timers for batching validation, and added new test for burst batching with leaderboard/feed ordering assertions. Updated assertions to account for batched behavior and changed privacy-mode check from masked amounts to "Hidden amount" text.
Accessibility
frontend/src/components/live-performance/LiveTipAlert.tsx
Added role="listitem" to outer <div> for assistive technology identification.

Sequence Diagram

sequenceDiagram
    participant WS as WebSocket
    participant LPM as LivePerformanceMode
    participant Queue as Tip Queue
    participant Timer as Batch Timer
    participant Batch as applyTipBatch
    participant State as Component State

    WS->>LPM: tip_received payload
    LPM->>LPM: normalizeIncomingTip()
    LPM->>Queue: queue tip
    LPM->>Timer: start batch timer (TIP_BATCH_WINDOW_MS)
    
    WS->>LPM: tip_received payload (rapid)
    LPM->>Queue: queue tip (accumulate)
    
    Timer->>LPM: TIP_BATCH_WINDOW_MS elapsed
    LPM->>Batch: flushQueuedTips()
    Batch->>Batch: aggregate counts/totals/leaderboard
    Batch->>State: apply batch updates at once
    State->>State: re-render once per batch
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly Related PRs

  • PR #170: Original addition of live-performance components; this PR makes iterative refinements to the same component suite with batching, memoization, and type extensions.

Poem

🐰 Tips cascade, but now we batch them tight,
No more fluttering leaderboards—smooth and light!
With memoized hops and sorted rows so keen,
The steadiest performance we've ever seen! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "all fixed" is vague and generic, providing no meaningful information about the changeset's primary purpose or scope. Use a descriptive title that reflects the main change, such as "Implement tip batching and memoization in live performance overlay" or "Stabilize live performance widgets with batching and re-render optimization".
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR addresses the core requirements of issue #247: implements batching for tips (TIP_BATCH_WINDOW_MS constant, flushQueuedTips logic), adds memoization to prevent unnecessary re-renders, includes comprehensive test coverage with timer-based batching assertions, and refines state update cadence.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #247 requirements: batching, memoization, test coverage, and stability improvements. No unrelated modifications detected in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/live-performance/LivePerformanceMode.tsx`:
- Around line 59-79: sanitizeLeaderboard currently synthesizes supporterId from
tipperName when supporterId is missing, which can split a single supporter into
multiple rows; change sanitizeLeaderboard (and the similar logic around the
other block handling entries) to treat a missing or non-string supporterId as a
storage-version mismatch and bail out by returning an empty leaderboard (or
otherwise triggering a rebuild/clear) instead of constructing an identity from
tipperName; ensure you reference the LeaderboardEntry shape and the fields
supporterId, tipperName, total, tipCount, and sortOrder when implementing this
check so the function rejects legacy rows rather than backfilling supporterId.
- Around line 121-123: The current supporterId logic in LivePerformanceMode.tsx
uses raw addresses or the shared 'anonymous' bucket which causes privacy and
leaderboard-merging issues; change supporterId to produce a non-reversible
stable key when a senderAddress exists (e.g., call a helper like
generateStableKey(truncateAddress(...) or the full senderAddress) that returns a
one-way hash) and when tip.isAnonymous or senderAddress is missing, fall back to
the unique tip identifier (tip.tipId) instead of the literal 'anonymous'; update
references to supporterId and add/import the stable-key helper (or use a crypto
hash) so persisted leaderboard identities are non-reversible and unique per tip
when no address is available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e49261a-f0b0-4cac-b43d-2cfcedf21454

📥 Commits

Reviewing files that changed from the base of the PR and between eef1b6d and a8a6469.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • frontend/src/components/live-performance/HypeMeter.tsx
  • frontend/src/components/live-performance/LiveLeaderboard.tsx
  • frontend/src/components/live-performance/LivePerformanceMode.test.tsx
  • frontend/src/components/live-performance/LivePerformanceMode.tsx
  • frontend/src/components/live-performance/LiveTipAlert.tsx
  • frontend/src/components/live-performance/SessionTicker.tsx
  • frontend/src/components/live-performance/types.ts
  • frontend/vite.config.ts
💤 Files with no reviewable changes (1)
  • frontend/vite.config.ts

Comment on lines +59 to +79
const sanitizeLeaderboard = (entries: unknown[]): LeaderboardEntry[] =>
entries.map((entry, index) => {
const candidate = entry as Partial<LeaderboardEntry>;
const tipperName =
typeof candidate.tipperName === 'string' && candidate.tipperName.trim()
? candidate.tipperName
: `Supporter ${index + 1}`;

return {
supporterId:
typeof candidate.supporterId === 'string' && candidate.supporterId.trim()
? candidate.supporterId
: tipperName,
tipperName,
total: typeof candidate.total === 'number' ? candidate.total : Number(candidate.total) || 0,
tipCount: typeof candidate.tipCount === 'number' ? candidate.tipCount : Number(candidate.tipCount) || 0,
sortOrder:
typeof candidate.sortOrder === 'number' && Number.isFinite(candidate.sortOrder)
? candidate.sortOrder
: index,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't migrate legacy leaderboard rows by inventing supporterId from the display label.

Older stored sessions won't have supporterId, so this path backfills the aggregation key from truncated tipperName. The next live tip from the same wallet will then key on senderAddress instead, which means the persisted row no longer matches and the leaderboard can split one supporter into two rows after a refresh. Treat missing supporterId as a storage-version miss and rebuild or clear the leaderboard instead of synthesizing identity from presentation text.

Also applies to: 96-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/live-performance/LivePerformanceMode.tsx` around
lines 59 - 79, sanitizeLeaderboard currently synthesizes supporterId from
tipperName when supporterId is missing, which can split a single supporter into
multiple rows; change sanitizeLeaderboard (and the similar logic around the
other block handling entries) to treat a missing or non-string supporterId as a
storage-version mismatch and bail out by returning an empty leaderboard (or
otherwise triggering a rebuild/clear) instead of constructing an identity from
tipperName; ensure you reference the LeaderboardEntry shape and the fields
supporterId, tipperName, total, tipCount, and sortOrder when implementing this
check so the function rejects legacy rows rather than backfilling supporterId.

Comment on lines +121 to +123
const tipperName = tip.isAnonymous ? 'Anonymous fan' : truncateAddress(tip.senderAddress || 'Guest fan', 5, 4);
const supporterId = tip.isAnonymous ? 'anonymous' : tip.senderAddress?.trim() || tipperName;
const createdAt = typeof tip.createdAt === 'string' ? tip.createdAt : new Date().toISOString();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

supporterId should not be a raw address or a shared 'anonymous' bucket.

This value is now the leaderboard identity and gets persisted with the session. For non-anonymous tips, that stores full wallet addresses in localStorage; for anonymous or missing-address tips, every event collapses into the same row because the key is always 'anonymous' or the same fallback. backend/src/websocket/websocket.gateway.ts:168-182 explicitly omits senderAddress for anonymous tips, so that merge will happen in production. Use a non-reversible stable key when an address exists, and fall back to tipId when it does not.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/live-performance/LivePerformanceMode.tsx` around
lines 121 - 123, The current supporterId logic in LivePerformanceMode.tsx uses
raw addresses or the shared 'anonymous' bucket which causes privacy and
leaderboard-merging issues; change supporterId to produce a non-reversible
stable key when a senderAddress exists (e.g., call a helper like
generateStableKey(truncateAddress(...) or the full senderAddress) that returns a
one-way hash) and when tip.isAnonymous or senderAddress is missing, fall back to
the unique tip identifier (tip.tipId) instead of the literal 'anonymous'; update
references to supporterId and add/import the stable-key helper (or use a crypto
hash) so persisted leaderboard identities are non-reversible and unique per tip
when no address is available.

@OlufunbiIK OlufunbiIK merged commit 4fdc1f4 into OlufunbiIK:main Mar 29, 2026
1 of 3 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.

Live Artist Performance Overlay Stability Pass

2 participants