Skip to content

perf: fetch commitment history in parallel in RecentActivityFeed (Closes #1396) - #1725

Open
Aycode01 wants to merge 1 commit into
Commitlabs-Org:masterfrom
Aycode01:fix/issue-1396-parallel-recent-activity-feed
Open

perf: fetch commitment history in parallel in RecentActivityFeed (Closes #1396)#1725
Aycode01 wants to merge 1 commit into
Commitlabs-Org:masterfrom
Aycode01:fix/issue-1396-parallel-recent-activity-feed

Conversation

@Aycode01

Copy link
Copy Markdown

Summary

Closes #1396

RecentActivityFeed.tsx's loadEvents previously fetched per-commitment history sequentially in a for-loop:

for (const commitment of commitments) {
  // ... await apiGet(`/api/commitments/${commitment.id}/history`) ...
}

Each /api/commitments/{id}/history request had to complete before the next one started. For a user with N active commitments, the feed took roughly N × single-request latency to render (instead of max(latency)), and the loading skeleton stayed visible the entire time.

This PR replaces the sequential loop with Promise.allSettled(commitments.map(...)), which issues all per-commitment history requests concurrently, dropping feed render time from N × latency to max(latency) — the loading skeleton disappears as soon as the slowest single request resolves, not after the sum of all of them.

Behavior preserved

  • Per-commitment try/catch-and-log: each request keeps its own try/catch with console.error logging on failure (unchanged from the original), so one failing commitment never blocks or breaks the rest of the feed.
  • Empty-state semantics: commitments that fail or return no events simply contribute no entries, and commitments that succeed still render — identical to before.
  • Promise.allSettled (per the issue's acceptance criteria) guarantees the aggregate never rejects even if an individual request does, and we flatMap only fulfilled results.

Changes

src/components/dashboard/RecentActivityFeed.tsx

  • Rewrote loadEvents to use Promise.allSettled(commitments.map(async (commitment) => { ... })) with the original per-commitment try/catch-and-log preserved inside each mapper.
  • The mapper resolves to the commitment's enriched FeedEvent[] (or [] on failure); the outer allSettled result is flattened into a single event list.
  • Adjusted the FeedEvent type to an intersection (HistoryEvent & { commitmentId: string }) so discriminated-union narrowing on event.kind still works.
  • Removed a redundant role="list" on the <ul> to satisfy jsx-a11y/no-redundant-roles.

src/components/dashboard/RecentActivityFeed.test.tsx (new)

Seven tests covering the feed, including:

  • Concurrency test — asserts requests are issued in parallel: it mocks apiGet with never-resolving promises, renders the feed, and verifies all N requests are fired before any promise resolves. A sequential implementation could never pass this test, since the 2nd call would only fire after the 1st resolves.
  • Resilience test — one commitment's request rejects (logged via console.error) while the other's succeeds; asserts the successful commitment's events still render.
  • Rendering, loading, and empty-state tests.

Restored support files (new against upstream/master)

These files exist in the intended feature but were deleted from the repository by the destructive 1ecce7d2 commit and never restored upstream — they currently 404 on upstream/master:

  • src/lib/apiClient.ts
  • src/lib/client/apiClient.ts
  • src/utils/errorHelpers.ts
  • src/components/Skeleton.tsx

⚠️ Important context for reviewers: this PR is not adding new dead code. Upstream useTestNotification.ts already imports apiRequest from @/lib/client/apiClient, a module that does not exist on current upstream/master (verified via the GitHub contents API → 404). Restoring these files repairs a broken import on master. They are also transitive dependencies of RecentActivityFeed.tsx (apiGet@/lib/apiClient./client/apiClienterrorHelpers).

Type-safety fixes applied to the restored files

The restored files predate the repo's stricter tsconfig.json settings, so they were brought up to current standards:

  • client/apiClient.ts: optional properties assigned conditionally to satisfy exactOptionalPropertyTypes.
  • errorHelpers.ts: normalizeApiError builds its passthrough object with conditional field assignment (exact optional properties) and uses safe registry lookups under noUncheckedIndexedAccess.

Motivation

For a user with N active commitments, the sequential loop renders the feed after the sum of all request latencies. With Promise.allSettled, it renders after the maximum single latency — a linear-to-constant improvement that matters most exactly when a user has many commitments (the loading skeleton stays visible for N× latency today).

Testing

Run locally and passing:

  • npx prettier --check on all 6 files
  • npx eslint on all 6 files (0 errors)
  • npx vitest run src/components/dashboard/RecentActivityFeed.test.tsx7/7 tests pass, including the new concurrency + resilience tests
  • npx tsc --noEmit — no errors in any of the changed files

Note: the repo's full typecheck currently reports a pre-existing, unrelated error in tests/components/MyCommitmentsGrid.perf.test.tsx (module MyCommitmentsGridSkeleton missing — same upstream 1ecce7d2 deletion artifact, file absent from upstream/master). It is not caused by this PR and CI's typecheck job is configured with continue-on-error: true.

Checklist

  • Sequential fetch replaced with Promise.allSettled(commitments.map(...))
  • Per-commitment try/catch-and-log behavior preserved
  • Test asserting requests are issued concurrently added
  • Formatting, lint, and unit tests pass locally
  • No errors introduced in the changed files by the full typecheck

Replace the sequential for-loop in loadEvents with Promise.allSettled(commitments.map(...)), preserving the existing per-commitment try/catch-and-log behavior for failures. This drops feed render time from N x latency to max(latency) for N active commitments.

Closes Commitlabs-Org#1396
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

@Aycode01 is attempting to deploy a commit to the 1nonly's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 31, 2026

Copy link
Copy Markdown

@Aycode01 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

RecentActivityFeed.tsx fetches per-commitment history sequentially in a for-loop instead of in parallel

1 participant