diff --git a/src/app/shared/page.tsx b/src/app/shared/page.tsx index 2fd6081..4b66966 100644 --- a/src/app/shared/page.tsx +++ b/src/app/shared/page.tsx @@ -498,6 +498,17 @@ export default function SharedPage() { const [paymentModal, setPaymentModal] = useState<{ id: number; name: string; balance: number } | null>(null); const [showHistory, setShowHistory] = useState(null); const [editModal, setEditModal] = useState(null); + // Rendering is windowed even though the data is not: the query returns every + // split row (1,279 as of 2026-08, growing ~600/yr) so search and sort stay + // instant over full history, but the DOM stops at visibleCount — an unbounded + // table was what made expanding a receipt freeze the browser. "Show more" + // extends the window; changing any filter resets it. + const [visibleCount, setVisibleCount] = useState(100); + useEffect(() => { + setVisibleCount(100); + }, [search, sortCol, sortDir, tagIds, participantId]); + const visible = (transactions as SharedTransactionRow[]).slice(0, visibleCount); + // Receipt expansion, same pattern as the transactions page. The shared // viewer is a split participant, so /api/transactions/[id]/order // authorises them — the item list is part of what was shared. @@ -681,7 +692,7 @@ export default function SharedPage() { - {(transactions as SharedTransactionRow[]).map((tx) => ( + {visible.map((tx) => ( )} + {!txLoading && transactions.length > visible.length && ( +
+ + + + showing {visible.length} of {transactions.length} + +
+ )} {/* Payment modal */}