Skip to content

feat(OUT-3702): fetch assignees via SWR in layout instead of SSR per page#1209

Open
priosshrsth wants to merge 16 commits intofeature/c1-optimizationfrom
anit/out-3702-optimize-assignee-fetcher-to-be-once-per-session
Open

feat(OUT-3702): fetch assignees via SWR in layout instead of SSR per page#1209
priosshrsth wants to merge 16 commits intofeature/c1-optimizationfrom
anit/out-3702-optimize-assignee-fetcher-to-be-once-per-session

Conversation

@priosshrsth
Copy link
Copy Markdown
Collaborator

@priosshrsth priosshrsth commented May 8, 2026

OUT-3702 — mirrors the workspace fetcher pattern, plus a scoped server-side cache for getClients.

Summary

  • New AssigneesFetcher (client) mounted once in the root layout. Fires SWR against /api/users or /api/users/client based on tokenPayload. Dispatches the existing setAssigneeList action — no new redux state.
  • Removed per-page <AssigneeFetcher> from home, client, detail; removed inline getAssigneeList from manage-templates. Deleted the now-unused AssigneeFetcher.tsx.
  • Layout fetcher also writes to IndexedDB so AssigneeCacheGetter (still per-page) keeps pre-painting on cold load.

Why

/api/users fans out to 5 Copilot calls. It was firing on every navigation across 4 pages. After this: 1 fetch per session. The scoped getClients cache eliminates the heaviest of the 5 fan-out calls for the allow-listed workspace.

Test plan

  • Home, client, detail, manage-templates render with assignees populated after first paint
  • Network tab: home → detail → manage-templates fires /api/users once
  • Real CU on /client hits /api/users/client
  • Preview-mode (CU page with IU token) hits /api/users
2026-05-08.18-48-08.mov
2026-05-08.18-31-51.mov

🤖 Generated with Claude Code

priosshrsth and others added 6 commits May 6, 2026 15:02
Add a layout-level WorkspaceFetcher that uses SWR to populate
authDetails.workspace from /api/workspace on the client. SSR pages
((home), detail, client, manage-templates, manage-templates/[id]) no
longer block on copilot.getWorkspace() per request — they let the
Redux store hydrate from the cached client fetch (60s deduping).

Server-side route uses React cache() via getMemoizedWorkspace for
per-request memoization. Workspace consumers (Sidebar, AppBridges,
HeaderBreadcrumbs, TaskBoard) now read portalUrl from the store via
selectAuthDetails instead of taking it as a prop.

Add tsc script (tsc --noEmit) to package.json for type-only checks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…page

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 8, 2026

OUT-3702

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tasks-app Ready Ready Preview, Comment May 8, 2026 0:37am

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR replaces four per-page SSR AssigneeFetcher calls with a single client-side SWR AssigneesFetcher mounted once in the root layout, eliminating redundant /api/users fan-out fetches on navigation. It also adds a scoped unstable_cache path for getClients on a single allow-listed workspace.

  • New AssigneesFetcher: client component in the root layout; guards with assignee.length === 0 (Redux state), dispatches setAssigneeList, and writes to IndexedDB so returning users get an immediate pre-paint via the per-page AssigneeCacheGetter on home/client/detail.
  • Removed SSR fetchers: AssigneeFetcher.tsx deleted; home, client, detail, and manage-templates pages no longer call getAssigneeList server-side.
  • manage-templates gap: Unlike the other three pages, manage-templates received no AssigneeCacheGetter to compensate for the removed SSR fetch \u2014 direct deep-links on cold load will show an empty assignee dropdown until SWR resolves.

Confidence Score: 4/5

Safe to merge with one regression: cold deep-links to manage-templates will show an empty assignee dropdown until the layout SWR resolves.

The consolidation from four SSR fetchers to one layout-level SWR component is sound, but manage-templates lost its server-side assignee data without gaining the AssigneeCacheGetter fallback that the other pages have. On a direct cold-load to /manage-templates, the assignee dropdown will be blank until the client-side fetch completes — a visible regression for users following deep-links.

src/app/manage-templates/page.tsx — needs an AssigneeCacheGetter (with the correct lookupKey) to match the pre-paint behaviour of the other pages.

Important Files Changed

Filename Overview
src/app/_fetchers/AssigneesFetcher.tsx New client-side SWR fetcher mounted once in the root layout; replaces per-page SSR fetchers. Correctly guards with assignee.length === 0 (Redux state) and writes to IndexedDB after each successful fetch.
src/app/manage-templates/page.tsx Removed SSR assignee fetch and assignee prop from ClientSideStateUpdate, but did not add AssigneeCacheGetter — cold deep-links to this page will render with an empty assignee dropdown until SWR resolves.
src/app/layout.tsx Mounts AssigneesFetcher alongside WorkspaceFetcher in the root layout inside a Suspense boundary; no structural issues.
src/app/_fetchers/AssigneeFetcher.tsx Deleted SSR fetcher; no longer referenced from any page.
src/app/(home)/page.tsx Removed AssigneeFetcher import and Suspense wrapper; AssigneeCacheGetter is retained for IndexedDB pre-paint.
src/app/client/page.tsx Removed AssigneeFetcher; AssigneeCacheGetter retained; no issues.
src/app/detail/[task_id]/[user_type]/page.tsx Removed AssigneeFetcher; AssigneeCacheGetter retained; no issues.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant RootLayout
    participant AssigneesFetcher
    participant AssigneeCacheGetter
    participant IndexedDB
    participant Redux
    participant API

    Browser->>RootLayout: Navigate to any page
    RootLayout->>AssigneesFetcher: mount (once per session)
    RootLayout->>AssigneeCacheGetter: mount (home/client/detail only)

    AssigneeCacheGetter->>IndexedDB: getAssignees(lookupKey)
    IndexedDB-->>AssigneeCacheGetter: cached assignees
    AssigneeCacheGetter->>Redux: setAssigneeList (pre-paint)

    Note over AssigneesFetcher: tokenPayload arrives via Redux
    AssigneesFetcher->>API: SWR /api/users or /api/users/client
    API-->>AssigneesFetcher: users or clients
    AssigneesFetcher->>Redux: setAssigneeList (fresh data)
    AssigneesFetcher->>IndexedDB: setAssignees(lookupKey, combined)

    Note over Browser: manage-templates cold deep-link
    Browser->>RootLayout: Navigate to /manage-templates
    Note over AssigneeCacheGetter: NOT mounted on manage-templates
    AssigneesFetcher->>API: SWR /api/users
    Note over Browser: Assignee dropdown empty until SWR resolves
Loading

Comments Outside Diff (1)

  1. src/app/manage-templates/page.tsx, line 49-79 (link)

    P1 Missing AssigneeCacheGetter leaves manage-templates cold-load with empty assignees

    The SSR getAssigneeList call was removed, but no AssigneeCacheGetter was added to this page. The home, client, and detail pages all mount AssigneeCacheGetter so returning users get an immediate pre-paint from IndexedDB while the layout-level SWR resolves. Manage-templates has no such fallback — on a cold deep-link directly to /manage-templates or /manage-templates/{id}, the assignee dropdown will be empty until the client-side SWR round-trip completes. The test plan item "Deep-link /manage-templates/{id} shows assignee dropdown without first visiting the list" specifically covers this scenario and it will fail for users who haven't pre-warmed the IndexedDB cache via another page.

Reviews (2): Last reviewed commit: "fix(OUT-3702): address Greptile review" | Re-trigger Greptile

Comment thread src/app/_fetchers/AssigneesFetcher.tsx
Comment thread src/app/_fetchers/AssigneesFetcher.tsx Outdated
Restore the AssigneeCacheSetter side-effect that was nested inside the
deleted AssigneeFetcher. AssigneeCacheGetter still reads on cold load
per page; without a Setter, IDB never refreshed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 8, 2026

Deployment failed with the following error:

Deploying Serverless Functions to multiple regions is restricted to the Pro and Enterprise plans.

Learn More: https://vercel.link/multiple-function-regions

…it/out-3702-optimize-assignee-fetcher-to-be-once-per-session

# Conflicts:
#	src/app/layout.tsx
#	src/app/manage-templates/page.tsx
@priosshrsth priosshrsth force-pushed the anit/out-3702-optimize-assignee-fetcher-to-be-once-per-session branch from fbdd3b3 to 9c2b703 Compare May 8, 2026 08:09
@priosshrsth priosshrsth changed the base branch from workspace-fetch-optimization to feature/c1-optimization May 8, 2026 08:11
- replace module-level hasFetched flag with Redux-state guard (assignee.length === 0)
  so HMR / store resets trigger refetch correctly
- IDB write via setAssigneesIDB was already present
@priosshrsth
Copy link
Copy Markdown
Collaborator Author

@greptileai

…s-app into anit/out-3702-optimize-assignee-fetcher-to-be-once-per-session
…s-app into anit/out-3702-optimize-assignee-fetcher-to-be-once-per-session
…s-app into anit/out-3702-optimize-assignee-fetcher-to-be-once-per-session
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.

1 participant