Skip to content

fix(feed): kill /feed blink + parallel loading (was: CLA + feed perf) - #786

Merged
tolstenko merged 7 commits into
developfrom
fix/feed-blink-perf
Oct 11, 2026
Merged

tolstenko merged 7 commits into
developfrom
fix/feed-blink-perf

Conversation

@tolstenko

Copy link
Copy Markdown
Contributor

What

Fixes the /feed blink and slow first load:

  • Blink: feed tabs, post tag chips, and trending-tag links pointed at /?tab=…, which redirects signed-in users to /feed?… — every click was 2 full page loads. All link surfaces now soft-navigate /feed directly.
  • Blank flash on cold load: a skeleton (nested Suspense inside SocialShell, per Next docs — route-level loading.tsx would flash on every searchParams-only tab switch) replaces the inherited empty [locale]/loading.tsx blank.
  • Slow first screen: SocialFeed ran 3 serial query rounds (feed → rails → story-author fan-out). Now one parallel Promise.all; story-author profiles chain off stories inside the batch. Measured: full-render time median 1.71s → 0.54s (−68%) (evidence in .omo/evidence/task-6-feed-blink-perf.md).
  • Layout blocking: (social)/layout.tsx awaited the notifications summary before children — now streamed via Suspense slot with a failure fallback (a throw could 500 every social page).

Infinite scroll already existed — now pinned by tests (identity reset, e2e no-remount + scroll pagination).

Verification

  • 108/108 component tests (vitest), 3/3 e2e (E2E_RUN=1 Playwright: tab-switch no-remount, infinite scroll, smoke)
  • Failure-probe: reverting a tab href made the new tests fail (they detect the regression)
  • Manual QA: 6/6 HTTP scenarios pass (details in .omo/evidence/f3-manual-qa.txt)

Known trade-offs

  • Sidebar notification badge chips dropped in the social shell (header bell keeps notifications; badge URLs never matched social nav — see review notes)
  • No API changes, no new deps, no route restructuring

Note: branch history includes 2 CLA-related commits (ff120b8023 #711 revert-ish, 8fd81a3520) that were already on this branch's base — they ride along; the 7 feed commits are 5f97da5..2710591.

Copilot AI balanced review requested due to automatic review settings October 10, 2026 20:31
@codacy-production

codacy-production Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 97 complexity · 8 duplication

Metric Results
Complexity 97
Duplication 8

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Perceptual hashing is silently disabled, and the pagination E2E probe uses a mismatched page size.

2 open findings
What changed in this PR

Fixes /feed navigation flashes and loading waterfalls, while also carrying CLA setup and ImageSharp removal.

Changes:

  • Routes feed links directly to /feed and adds loading/pagination regressions tests.
  • Parallelizes feed data and streams notifications via Suspense.
  • Adds CLA configuration and removes local perceptual hashing.
File Description
e2e/​feed.spec.ts Adds feed navigation and pagination E2E tests.
CLA.md Adds contributor license agreement.
apps/​web/​src/​components/​feed/​social-shell.tsx Adds Suspense and parallel loading.
apps/​web/​src/​components/​feed/​social-shell.test.tsx Tests streaming, parallelism, and failures.
apps/​web/​src/​components/​feed/​social-rail.tsx Routes tags directly to /feed.
apps/​web/​src/​components/​feed/​social-rail.test.tsx Verifies tag destinations.
apps/​web/​src/​components/​feed/​social-navigation.test.tsx Updates expected feed links.
apps/​web/​src/​components/​feed/​social-feed-tabs.tsx Routes tabs directly to /feed.
apps/​web/​src/​components/​feed/​social-app-shell.tsx Accepts streamed notification menu.
apps/​web/​src/​components/​feed/​post-card.tsx Updates tag-chip destinations.
apps/​web/​src/​components/​feed/​post-card.test.tsx Tests tag-chip links.
apps/​web/​src/​components/​feed/​infinite-post-feed.test.tsx Tests identity reset and deduplication.
apps/​web/​src/​components/​feed/​feed-skeleton.tsx Adds feed loading skeleton.
apps/​web/​src/​components/​feed/​feed-skeleton.test.tsx Tests skeleton structure.
apps/​web/​src/​app/​[locale]/​(social)/​notifications-slot.tsx Streams notification summary safely.
apps/​web/​src/​app/​[locale]/​(social)/​notifications-slot.test.tsx Tests notification fallback behavior.
apps/​web/​src/​app/​[locale]/​(social)/​layout.tsx Removes notifications from critical path.
apps/​web/​src/​app/​[locale]/​(social)/​layout.test.tsx Tests layout streaming and authentication.
apps/​api/​Source/​Modules/​GameGuild.Assets/​Services/​AssetUploadService.cs Updates image-parser guidance.
apps/​api/​Source/​Modules/​GameGuild.Assets/​GameGuild.Assets.csproj Removes ImageSharp dependency.
apps/​api/​Source/​Modules/​GameGuild.Assets/​Deduplication/​DeduplicationService.cs Removes local perceptual hashing.
apps/​api/​Directory.Packages.props Removes centralized ImageSharp version.
apps/​api/​Directory.Build.props Removes ImageSharp warning suppressions.
.omo/​evidence/​task-6-feed-blink-perf.md Records performance measurements.
.clabot Configures CLA enforcement.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +103 to +105
_logger.LogDebug("Perceptual hashing is delegated to the Openinary media pipeline; skipping local computation");
await Task.CompletedTask;
return null;
Comment thread e2e/feed.spec.ts
const probePage = await context.newPage();
let paginates = false;
try {
const first = await probePage.request.get(`${WEB_ORIGIN}/api/social/feed?scope=for-you&take=10`);
…d loads

Shipped the nested-Suspense option, not a route loading.tsx: per
node_modules/next/dist/docs (loading.js + linking-and-navigating), a
route-level loading.tsx is prefetched and shown on every dynamic
navigation to the segment, and /feed tab links are searchParams-only
soft navs to the same segment — the skeleton would flash on every tab
switch. Instead SocialShell now wraps the async feed body in
<Suspense fallback={<FeedSkeleton />}> so cold loads stream the
skeleton while layout chrome stays mounted; tab switches keep the
shell.
- social-shell.tsx: replace post-render primaryError reassignment with
  promise-chained result object ({failed, feed}) read after await
- feed-skeleton.test.tsx: drop unused cleanup import
@tolstenko
tolstenko force-pushed the fix/feed-blink-perf branch from af2a1f3 to 37fbd64 Compare October 11, 2026 03:36
@tolstenko
tolstenko merged commit afe1611 into develop Oct 11, 2026
24 checks passed
@tolstenko
tolstenko deleted the fix/feed-blink-perf branch October 11, 2026 04:07
mathrmartins pushed a commit that referenced this pull request Oct 11, 2026
…#786)

* fix(feed): point feed tabs, post tag chips, and trending tags at /feed instead of the redirecting root

* feat(feed): add /feed loading skeleton to kill the blank flash on cold loads

Shipped the nested-Suspense option, not a route loading.tsx: per
node_modules/next/dist/docs (loading.js + linking-and-navigating), a
route-level loading.tsx is prefetched and shown on every dynamic
navigation to the segment, and /feed tab links are searchParams-only
soft navs to the same segment — the skeleton would flash on every tab
switch. Instead SocialShell now wraps the async feed body in
<Suspense fallback={<FeedSkeleton />}> so cold loads stream the
skeleton while layout chrome stays mounted; tab switches keep the
shell.

* test(feed): pin tab and trending-tag hrefs to /feed contract

* perf(feed): run all SocialFeed feed and rail queries in one parallel round

* perf(feed): stream dashboard notifications out of the social layout critical path with a failure fallback

* test(feed): pin /feed soft-nav no-remount, identity reset, and infinite-scroll pagination

* fix(feed): resolve react-hooks/immutability + unused import lint errors

- social-shell.tsx: replace post-render primaryError reassignment with
  promise-chained result object ({failed, feed}) read after await
- feed-skeleton.test.tsx: drop unused cleanup import
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants