perf-agent: visibility gate VideoEventBuffer

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-13 20:44:59 +00:00
1 parent f8cd7369b5
commit bb39226ca5
11 files changed
+139 -2

No files matched your search

+4
View File
@@ -3,7 +3,11 @@
## Performance Bottlenecks
- **Unbounded Concurrency (P0):** `js/nostr/commentEvents.js` uses `Promise.all` for relay requests in `publishComment` and `listVideoComments`. This can saturate network connections if a user has many relays (20+), causing UI freeze or timeouts.
- **Remediation:** Apply `pMap` with `RELAY_BACKGROUND_CONCURRENCY` (3).
- **VideoEventBuffer (P1):** Flushes every 75ms on event ingest regardless of visibility.
- **Remediation:** Implemented visibility gating (2026-02-13).
- **WatchHistory (P2):** Republish loop has backoff but no visibility check.
## Documentation Gaps
- **CORS Configuration:** `content/docs/guides/upload-content.md` suggests `http://localhost:5500` for CORS, but the default dev port is `3000`.
- **Remediation:** Update port to `3000`.
- **Upload Limit:** 2GB (documented and code-verified).
+18
View File
@@ -0,0 +1,18 @@
# Context
## Goal
Improve application responsiveness by optimizing background tasks and ensuring documentation accuracy.
## Scope
- Analyze codebase for performance bottlenecks (timers, loops, concurrency).
- Fix: Visibility-gate `VideoEventBuffer` to prevent UI thrashing when hidden.
- Audit: Verify upload documentation matches implementation.
## Assumptions
- `document.hidden` is reliable.
- `VideoEventBuffer` is the primary feed ingestion path.
## Definition of Done
- `VideoEventBuffer` pauses UI updates when hidden.
- `daily-perf-report` generated.
- Lint passes.
+24
View File
@@ -0,0 +1,24 @@
# Daily Perf Report - 2026-02-13
## Summary
Implemented visibility gating for `VideoEventBuffer` to prevent UI thrashing when the tab is hidden. Audited upload documentation against implementation.
## P0/P1 Findings
- **P1 - VideoEventBuffer Thrashing**: The `VideoEventBuffer` was flushing events and triggering UI callbacks every 75ms regardless of visibility. This wasted CPU on layout/paint recalculations for hidden tabs.
- **Fix**: Modified `flush()` to buffer updates when `document.hidden` is true, and flush them in a batch upon `visibilitychange`.
- **P2 - Upload Limits**: Documentation states a 2GB limit due to client-side hashing. Verification confirmed `js/utils/torrentHash.js` reads the file for hashing, which is memory-bound in browsers. Docs are accurate.
## Metrics
- **Login Time**: (Not measured this run)
- **Queue Sizes**: (Not measured this run)
## PRs / Changes
- **VideoEventBuffer**: Added visibility gating.
## Blockers & Decisions
- **Decision**: Prioritized `VideoEventBuffer` fix over `WatchHistory` backoff as it affects the main feed loop directly.
- **Decision**: Accepted the 2GB upload limit documentation as accurate for the current architecture.
## Next Steps
- Investigate `WatchHistory` republish loop for similar visibility gating.
- Consider using streaming hashing (e.g., `crypto.subtle`) to lift the 2GB limit in the future.
@@ -0,0 +1,6 @@
# Decisions
## Visibility Gating for VideoEventBuffer
- **Problem**: `VideoEventBuffer` flushes every 75ms if events are arriving, triggering UI updates via `onVideo` callback even if the tab is hidden.
- **Solution**: Check `document.hidden` in `flush()`. If hidden, process events into internal state (`activeMap`) but skip `onVideo` and buffer the updated video objects. On `visibilitychange` (visible), flush the buffered video objects to UI.
- **Tradeoff**: Memory usage might increase slightly to hold pending video references, but CPU usage on main thread (layout/paint) will decrease significantly.
@@ -0,0 +1 @@
Started perf-agent run for 2026-02-13.
@@ -0,0 +1,6 @@
# Completed: perf-agent
**Timestamp:** 2026-02-13 20:45:00
**Agent:** perf-agent
**Summary:**
Implemented visibility gating for `VideoEventBuffer`. Now pauses UI updates (flushes) when the tab is hidden, accumulating changes to be applied in a batch when visibility is restored. This reduces background CPU usage and UI thrashing. Verified upload documentation against code (2GB limit is accurate). Generated daily perf report.
+26 -2
View File
@@ -21,6 +21,13 @@ export class VideoEventBuffer {
this.invalidEvents = [];
this.flushTimerId = null;
this.FLUSH_DEBOUNCE_MS = 75;
// Buffer updates while hidden to avoid UI thrashing
this.pendingVideos = [];
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
if (typeof document !== "undefined") {
document.addEventListener("visibilitychange", this.handleVisibilityChange);
}
}
/**
@@ -140,8 +147,12 @@ export class VideoEventBuffer {
}
if (updatedVideos.length > 0) {
// Trigger the callback once per batch to avoid UI thrashing
this.onVideo(updatedVideos);
if (typeof document !== "undefined" && document.hidden) {
this.pendingVideos.push(...updatedVideos);
} else {
// Trigger the callback once per batch to avoid UI thrashing
this.onVideo(updatedVideos);
}
// Fetch NIP-71 metadata (categorization tags) in the background for the whole batch
this.client.populateNip71MetadataForVideos(updatedVideos)
@@ -162,6 +173,16 @@ export class VideoEventBuffer {
this.client.saveLocalData("subscribeVideos:flush");
}
handleVisibilityChange() {
if (typeof document !== "undefined" && !document.hidden) {
if (this.pendingVideos.length > 0) {
const batch = this.pendingVideos;
this.pendingVideos = [];
this.onVideo(batch);
}
}
}
/**
* Called when the subscription reaches End-of-Stored-Events (EOSE).
* Triggers an immediate flush to ensure the UI is fully up-to-date.
@@ -188,6 +209,9 @@ export class VideoEventBuffer {
clearTimeout(this.flushTimerId);
this.flushTimerId = null;
}
if (typeof document !== "undefined") {
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
}
// Ensure any straggling events are flushed before tearing down.
this.flush();
}
+1
View File
@@ -0,0 +1 @@
{"hits": ["VideoEventBuffer flush loop", "WatchHistory republish loop", "ViewCounter debounce"]}
@@ -0,0 +1,8 @@
# Test Log
## Environment
- Agent: perf-agent
- Date: 2026-02-13
## Verification
- Lint: Passed
+39
View File
@@ -151,4 +151,43 @@ test('VideoEventBuffer', async (t) => {
assert.equal(buffer.buffer.length, 0); // Should have flushed
assert.equal(client.allEvents.size, 1);
});
await t.test('Visibility Gating', () => {
// Mock document
const originalDocument = global.document;
let visibilityHandler = null;
global.document = {
hidden: true,
addEventListener: (type, handler) => {
if (type === 'visibilitychange') visibilityHandler = handler;
},
removeEventListener: () => {}
};
const client = new MockClient();
let onVideoCalls = [];
const onVideo = (videos) => onVideoCalls.push(videos);
const buffer = new VideoEventBuffer(client, onVideo);
// Push event while hidden
buffer.push(validEvent);
buffer.scheduleFlush(true);
// Should NOT have called onVideo yet
assert.equal(onVideoCalls.length, 0);
// Should have buffered pending videos
assert.equal(buffer.pendingVideos.length, 1);
// Simulate visibility change
global.document.hidden = false;
assert.ok(visibilityHandler, "Listener attached");
visibilityHandler();
// Should flush now
assert.equal(onVideoCalls.length, 1);
assert.equal(buffer.pendingVideos.length, 0);
// Cleanup mock
global.document = originalDocument;
});
});
+6
View File
@@ -0,0 +1,6 @@
- [ ] Create context/logs
- [ ] Create INITIAL_BASELINE.md
- [ ] Save search hits
- [ ] Implement visibility gating in `VideoEventBuffer.js`
- [ ] Generate report
- [ ] Lint