fix(ui): keep the activity screen smooth during uploads - #56
Merged
Merged
Conversation
Issue #45: the Activity screen got laggy while an upload streamed events. Two root causes, both fixed UI-side: 1. Live-event ingestion now coalesces. `activity:new` events no longer mutate the reactive `liveEntries` (or `total`) per event - each arrives in its own task, so a burst re-rendered the table once per event. Events are appended to a non-reactive buffer and a single flush per animation frame applies the whole burst in ONE reactive update (newest-first, deduped via seenIds, capped to LIVE_TAIL_CAP). The lagged-reconcile path drains the buffer first so it stays consistent; the buffer eager-flushes at the cap so memory is bounded even when frames are throttled (backgrounded). No event is dropped or duped. 2. The rendered DOM is bounded. The table renders only the newest slice(0, ACTIVITY_RENDER_WINDOW) of the accumulated entries and grows the window on demand via the unified "load more" control, so the mounted row count never grows with the live tail. Table columns, :key, filters, and history pagination are unchanged. Tests: new batched-ingestion + render-window suites prove a burst is one reactive update each to entries and total, ordering/dedup/cap are preserved, the scheduler auto-flushes, and a mounted Activity.vue caps its rows at the window while the store holds more. Existing activity-store tests flush the buffer where they previously relied on synchronous live application. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZQh3ZfwtZsM6c5qnTuWZP
Contributor
Coverage
Gate: passed - no coverage regression (epsilon 0.1 pp). |
pmaxhogan
marked this pull request as ready for review
June 27, 2026 01:47
Addresses codex /codex review [P2] findings before merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MZQh3ZfwtZsM6c5qnTuWZP
pmaxhogan
enabled auto-merge (squash)
June 27, 2026 02:04
pmaxhogan
added a commit
that referenced
this pull request
Jun 27, 2026
🤖 I have created a release *beep* *boop* --- ## [0.4.0](v0.3.1...v0.4.0) (2026-06-27) ### Features * **ui:** global backup progress bar in the app header ([#55](#55)) ([b7513b5](b7513b5)) * **ui:** sticky restore action bar and virtualized large lists ([#54](#54)) ([4631225](4631225)) ### Bug Fixes * **ui:** keep the activity screen smooth during uploads ([#56](#56)) ([44f3853](44f3853)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Issue #45: the Activity screen became laggy while an upload was running (repro: configure a folder, click "run now", scroll). Two root causes, both fixed UI-only (no Rust / event-emission changes):
Coalesced live ingestion.
activity:newevents used to mutate the reactiveliveEntriesarray (and bump the reactivetotal) once per event. Each event arrives in its own event-loop task, so a high-rate burst re-rendered/diffed the whole table once per event. Events are now appended to a non-reactive buffer and a single coalesced flush per animation frame applies the whole burst in one reactive update (oneliveEntriesassignment + onetotalwrite). Newest-first ordering, theseenIdsdedup, theactivity:laggedreconcile path, and theLIVE_TAIL_CAPeviction are all preserved; no event is dropped or duplicated. The buffer drains on reconcile/unsubscribe and eager-flushes when it reaches the cap, so memory stays bounded even when frames are throttled (window backgrounded).Bounded rendered DOM. The table renders only the newest
slice(0, ACTIVITY_RENDER_WINDOW)(200) of the accumulated entries and grows the window on demand via a unified "load more" control, so the mounted row count never grows with the live tail (which can reach ~1000). Table columns,:key="entry.id", filters, and history pagination are unchanged. The render window collapses back on a filter change.Testing
All run from the worktree (
ui/):pnpm install --frozen-lockfile- okpnpm lint- okpnpm format:check- ok (ranpnpm formatonce onActivity.vue, then clean)pnpm test:unit- 216 passed (24 files).activity-store.test.tsnow has 34 tests (27 original + 7 new); newactivity-window.test.tsmounts the realActivity.vueand proves the mounted row count is capped at the window while the store holds more.pnpm build(vue-tsc --noEmit && vite build) - okNew tests prove: a burst of N live events is exactly one reactive update each to
entriesandtotal(viaflush: "sync"watchers); dedup, newest-first order, and the cap are preserved across a buffered burst; the scheduler auto-flushes on the next frame; the buffer drains on unsubscribe; and the windowed slice is bounded. No Rust touched, so cargo gates were not required.Closes #45
🤖 Generated with Claude Code
https://claude.ai/code/session_01MZQh3ZfwtZsM6c5qnTuWZP