From aa35a722ec3b46526558db2ef53c6327cda42e4d Mon Sep 17 00:00:00 2001 From: Chloe Lin Date: Mon, 23 Feb 2026 17:21:34 +0100 Subject: [PATCH 1/4] Add caching mechanism for initial batch of rules by author --- app/api/tina/rules-by-author/route.ts | 43 ++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/app/api/tina/rules-by-author/route.ts b/app/api/tina/rules-by-author/route.ts index 7be30a737..bb31a1955 100644 --- a/app/api/tina/rules-by-author/route.ts +++ b/app/api/tina/rules-by-author/route.ts @@ -1,6 +1,11 @@ +import { unstable_cache } from "next/cache"; import { NextRequest, NextResponse } from "next/server"; import client from "@/tina/__generated__/client"; +const FIRST_BATCH_SIZE = 20; +const FIRST_BATCH_CACHE_SECONDS = 60 * 60 * 24; +const FIRST_BATCH_STALE_WHILE_REVALIDATE_SECONDS = 60; + export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); @@ -13,17 +18,39 @@ export async function GET(request: NextRequest) { } const last = lastStr ? Number(lastStr) : undefined; + const isFirstBatch = before === undefined && (last === undefined || last === FIRST_BATCH_SIZE); + + const fetchRulesByAuthor = (a: string, l?: number, b?: string) => + client.queries.rulesByAuthor({ + authorTitle: a, + last: l, + before: b, + sort: "created", + }); + + const result = isFirstBatch + ? await unstable_cache( + async (a: string) => fetchRulesByAuthor(a, FIRST_BATCH_SIZE, undefined), + [`tina-rules-by-author-first-batch-${authorTitle}-${FIRST_BATCH_SIZE}`], + { revalidate: FIRST_BATCH_CACHE_SECONDS } + )(authorTitle) + : await fetchRulesByAuthor(authorTitle, last, before); - const result = await client.queries.rulesByAuthor({ - authorTitle, - last, - before, - sort: "created", - }); + const res = NextResponse.json(result, { status: 200 }); + + if (isFirstBatch) { + res.headers.set( + "Cache-Control", + `public, s-maxage=${FIRST_BATCH_CACHE_SECONDS}, stale-while-revalidate=${FIRST_BATCH_STALE_WHILE_REVALIDATE_SECONDS}` + ); + } - return NextResponse.json(result, { status: 200 }); + return res; } catch (error) { console.error("Error fetching rules by author from Tina:", error); - return NextResponse.json({ error: "Failed to fetch rules", details: error instanceof Error ? error.message : String(error) }, { status: 500 }); + return NextResponse.json( + { error: "Failed to fetch rules", details: error instanceof Error ? error.message : String(error) }, + { status: 500 } + ); } } From 2228ead52b8560e4849779c89ca269e84cb5a21e Mon Sep 17 00:00:00 2001 From: Chloe Lin Date: Mon, 23 Feb 2026 18:09:14 +0100 Subject: [PATCH 2/4] set page_size to 100 --- app/user/client-page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/user/client-page.tsx b/app/user/client-page.tsx index 839811f85..c7d527b40 100644 --- a/app/user/client-page.tsx +++ b/app/user/client-page.tsx @@ -35,7 +35,7 @@ export default function UserRulesClientPage({ ruleCount }) { const [githubError, setGithubError] = useState(null); const [currentPageAuthored, setCurrentPageAuthored] = useState(1); const [itemsPerPageAuthored, setItemsPerPageAuthored] = useState(20); - const FETCH_PAGE_SIZE = 20; + const FETCH_PAGE_SIZE = 100; const resolveAuthor = async (): Promise => { const res = await fetch(`./api/crm/employees?query=${encodeURIComponent(queryStringRulesAuthor)}`); From fbfcc145fa06dcd0af92a069433bd132642a7d9d Mon Sep 17 00:00:00 2001 From: Chloe Lin Date: Mon, 23 Feb 2026 18:57:14 +0100 Subject: [PATCH 3/4] revert page_size change --- app/user/client-page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/user/client-page.tsx b/app/user/client-page.tsx index c7d527b40..839811f85 100644 --- a/app/user/client-page.tsx +++ b/app/user/client-page.tsx @@ -35,7 +35,7 @@ export default function UserRulesClientPage({ ruleCount }) { const [githubError, setGithubError] = useState(null); const [currentPageAuthored, setCurrentPageAuthored] = useState(1); const [itemsPerPageAuthored, setItemsPerPageAuthored] = useState(20); - const FETCH_PAGE_SIZE = 100; + const FETCH_PAGE_SIZE = 20; const resolveAuthor = async (): Promise => { const res = await fetch(`./api/crm/employees?query=${encodeURIComponent(queryStringRulesAuthor)}`); From f8f535bacb7e827a3fb148d3c4c07a79fb1273c0 Mon Sep 17 00:00:00 2001 From: Chloe Lin Date: Tue, 24 Feb 2026 10:46:27 +0100 Subject: [PATCH 4/4] Adds development-specific caching logic and rule limits --- app/api/tina/rules-by-author/route.ts | 7 +++++-- app/user/client-page.tsx | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/api/tina/rules-by-author/route.ts b/app/api/tina/rules-by-author/route.ts index bb31a1955..8674f4ac4 100644 --- a/app/api/tina/rules-by-author/route.ts +++ b/app/api/tina/rules-by-author/route.ts @@ -19,6 +19,7 @@ export async function GET(request: NextRequest) { const last = lastStr ? Number(lastStr) : undefined; const isFirstBatch = before === undefined && (last === undefined || last === FIRST_BATCH_SIZE); + const isDev = process.env.NODE_ENV === "development"; const fetchRulesByAuthor = (a: string, l?: number, b?: string) => client.queries.rulesByAuthor({ @@ -28,7 +29,7 @@ export async function GET(request: NextRequest) { sort: "created", }); - const result = isFirstBatch + const result = isFirstBatch && !isDev ? await unstable_cache( async (a: string) => fetchRulesByAuthor(a, FIRST_BATCH_SIZE, undefined), [`tina-rules-by-author-first-batch-${authorTitle}-${FIRST_BATCH_SIZE}`], @@ -38,11 +39,13 @@ export async function GET(request: NextRequest) { const res = NextResponse.json(result, { status: 200 }); - if (isFirstBatch) { + if (isFirstBatch && !isDev) { res.headers.set( "Cache-Control", `public, s-maxage=${FIRST_BATCH_CACHE_SECONDS}, stale-while-revalidate=${FIRST_BATCH_STALE_WHILE_REVALIDATE_SECONDS}` ); + } else if (isDev) { + res.headers.set("Cache-Control", "no-store"); } return res; diff --git a/app/user/client-page.tsx b/app/user/client-page.tsx index 839811f85..3878e2a4a 100644 --- a/app/user/client-page.tsx +++ b/app/user/client-page.tsx @@ -111,12 +111,13 @@ export default function UserRulesClientPage({ ruleCount }) { let hasMore = true; let pageCount = 0; const MAX_PAGES = 100; // Safety limit to prevent infinite loops + const MAX_AUTHORED_RULES = 100; const allRulesFromTina: any[] = []; const seenKeys = new Set(); try { - while (hasMore && pageCount < MAX_PAGES) { + while (hasMore && pageCount < MAX_PAGES && allRulesFromTina.length < MAX_AUTHORED_RULES) { pageCount++; const params = new URLSearchParams(); @@ -152,6 +153,9 @@ export default function UserRulesClientPage({ ruleCount }) { }); allRulesFromTina.push(...batch); + if (allRulesFromTina.length > MAX_AUTHORED_RULES) { + allRulesFromTina.length = MAX_AUTHORED_RULES; + } // Render as soon as we have the first page. // Backend returns results sorted by created, so we can append without re-sorting.