Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
## 2024-05-18 - Concurrent Fetching with Owner IDs in Share Links
**Learning:** In the share system, endpoints processing a share code typically suffer from waterfall latency by first loading target entity details (like a project) and then querying its associated elements (like project lists) using the entity's owner ID (`userId`). However, the `shareLink` object already contains the `userId` field (representing the owner's ID).
**Action:** Always leverage the existing owner's ID within `shareLink` to bypass sequential dependencies and fetch parent entities (like projects) concurrently with their child elements (like user lists) using `Promise.all`.

## 2024-07-06 - In-Memory Derivation of Data Subsets
**Learning:** When fetching both a global collection (e.g., all lists for a user) and a subset of that collection (e.g., lists associated with a specific project) simultaneously on the frontend, making two separate API requests causes redundant database queries and network latency.
**Action:** Always derive the subset in-memory using array filtering (e.g., `allLists.filter(list => list.projectId === projectId)`) from the globally fetched collection, eliminating the redundant subset API request entirely.
23 changes: 11 additions & 12 deletions src/app/projects/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,16 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
try {
setIsLoading(true);

// OPTIMIZATION: Execute independent network requests and JSON parsing concurrently
// using Promise.all. This prevents a 3-step waterfall, reducing Time to First Byte
// (TTFB) and overall load time significantly on this detail page.
const [projectRes, listsRes, allListsRes] = await Promise.all([
// OPTIMIZATION: Instead of making separate network requests for a subset of lists
// (project lists) and the global collection (all lists), we only fetch the global
// collection. We then derive the project lists in-memory, eliminating a redundant API request.
const [projectRes, allListsRes] = await Promise.all([
fetch(`/api/projects/${projectId}`),
fetch(`/api/projects/${projectId}/lists`),
fetch("/api/lists"),
]);

const [projectResult, listsResult, allListsResult] = await Promise.all([
const [projectResult, allListsResult] = await Promise.all([
projectRes.json(),
listsRes.json(),
allListsRes.json(),
]);

Expand All @@ -81,12 +79,13 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
setEditName(projectResult.data.name);
setEditDescription(projectResult.data.description || "");

if (listsResult.success) {
setLists(listsResult.data);
}

if (allListsResult.success) {
setAllLists(allListsResult.data);
const allFetchedLists = allListsResult.data as List[];
setAllLists(allFetchedLists);

// Derive the subset of lists that belong to this project
const projectLists = allFetchedLists.filter((list) => list.projectId === projectId);
setLists(projectLists);
}
} catch (err) {
console.error("Error fetching project:", err);
Expand Down
Loading