|
1 | 1 | "use client"; |
2 | | -import { startTransition, useEffect, useState } from "react"; |
| 2 | +import { useEffect, useState } from "react"; |
3 | 3 | import { pb, pbIsMisconfigured, PB_MISCONFIG_MESSAGE } from "../lib/pb"; |
4 | 4 | import type { StatusRow } from "../lib/live-status"; |
5 | 5 | import { upsertByKey } from "../lib/live-status"; |
@@ -144,34 +144,27 @@ export function useLiveStatus(dimension?: string): UseLiveStatusResult { |
144 | 144 | if (!alive || pendingByKey.size === 0) return; |
145 | 145 | const ops = Array.from(pendingByKey); |
146 | 146 | pendingByKey.clear(); |
147 | | - // SSE deltas drive a non-urgent visual update — flag the commit as a |
148 | | - // transition so React 19 can yield to user input (scroll, click, |
149 | | - // keyboard) while it walks the matrix tree. Without this, a large |
150 | | - // burst still renders synchronously and noticeably stalls the page |
151 | | - // even though the burst was coalesced into a single setRows call. |
152 | | - startTransition(() => { |
153 | | - setRows((prev) => { |
154 | | - let next = prev; |
155 | | - let mutated = false; |
156 | | - for (const [key, op] of ops) { |
157 | | - if (op.op === "delete") { |
158 | | - const idx = next.findIndex((r) => r.key === key); |
159 | | - if (idx === -1) continue; |
160 | | - if (!mutated) { |
161 | | - next = next.slice(); |
162 | | - mutated = true; |
163 | | - } |
164 | | - next.splice(idx, 1); |
165 | | - } else { |
166 | | - const candidate = upsertByKey(next, op.row); |
167 | | - if (candidate !== next) { |
168 | | - next = candidate; |
169 | | - mutated = true; |
170 | | - } |
| 147 | + setRows((prev) => { |
| 148 | + let next = prev; |
| 149 | + let mutated = false; |
| 150 | + for (const [key, op] of ops) { |
| 151 | + if (op.op === "delete") { |
| 152 | + const idx = next.findIndex((r) => r.key === key); |
| 153 | + if (idx === -1) continue; |
| 154 | + if (!mutated) { |
| 155 | + next = next.slice(); |
| 156 | + mutated = true; |
| 157 | + } |
| 158 | + next.splice(idx, 1); |
| 159 | + } else { |
| 160 | + const candidate = upsertByKey(next, op.row); |
| 161 | + if (candidate !== next) { |
| 162 | + next = candidate; |
| 163 | + mutated = true; |
171 | 164 | } |
172 | 165 | } |
173 | | - return mutated ? next : prev; |
174 | | - }); |
| 166 | + } |
| 167 | + return mutated ? next : prev; |
175 | 168 | }); |
176 | 169 | } |
177 | 170 |
|
@@ -239,60 +232,30 @@ export function useLiveStatus(dimension?: string): UseLiveStatusResult { |
239 | 232 | } |
240 | 233 |
|
241 | 234 | async function fetchInitial(): Promise<StatusRow[]> { |
242 | | - // Pull page 1 sequentially so we learn `totalItems` before deciding |
243 | | - // how many additional pages to issue. After that, the remaining |
244 | | - // pages are independent reads from the same collection — fire them |
245 | | - // in parallel so wall-clock latency tracks the slowest page rather |
246 | | - // than the sum. With 2000-row datasets paginated at 200/page that |
247 | | - // turns ~10 sequential round-trips into one round-trip per slot of |
248 | | - // network parallelism, which is the difference between a |
249 | | - // multi-second loading hitch and a single network frame. |
250 | | - const firstResp = await pb |
251 | | - .collection("status") |
252 | | - .getList<StatusRow>(1, INITIAL_PAGE_SIZE, { filter }); |
253 | | - if (!firstResp.items || firstResp.items.length === 0) return []; |
254 | | - |
255 | | - const total = Math.min(firstResp.totalItems, INITIAL_CAP); |
256 | | - if (firstResp.items.length >= total) { |
257 | | - return firstResp.items.slice(0, total); |
258 | | - } |
259 | | - |
260 | | - const lastPage = Math.ceil(total / INITIAL_PAGE_SIZE); |
261 | | - const pageRequests: Promise<StatusRow[]>[] = []; |
262 | | - for (let p = 2; p <= lastPage; p++) { |
263 | | - const perPage = Math.min( |
264 | | - INITIAL_PAGE_SIZE, |
265 | | - total - (p - 1) * INITIAL_PAGE_SIZE, |
266 | | - ); |
267 | | - pageRequests.push( |
268 | | - pb |
269 | | - .collection("status") |
270 | | - .getList<StatusRow>(p, perPage, { filter }) |
271 | | - .then((resp) => resp.items ?? []), |
272 | | - ); |
273 | | - } |
274 | | - const restPages = await Promise.all(pageRequests); |
275 | | - const collected = firstResp.items.slice(); |
276 | | - for (const items of restPages) { |
277 | | - collected.push(...items); |
278 | | - if (collected.length >= total) break; |
| 235 | + // Paginated fetch with a hard total cap. `getFullList({batch})` |
| 236 | + // would keep pulling every page of matching rows; we instead loop |
| 237 | + // `getList` and break once we hit INITIAL_CAP. |
| 238 | + const collected: StatusRow[] = []; |
| 239 | + let page = 1; |
| 240 | + while (collected.length < INITIAL_CAP) { |
| 241 | + const remaining = INITIAL_CAP - collected.length; |
| 242 | + const perPage = Math.min(INITIAL_PAGE_SIZE, remaining); |
| 243 | + const resp = await pb |
| 244 | + .collection("status") |
| 245 | + .getList<StatusRow>(page, perPage, { filter }); |
| 246 | + if (!resp.items || resp.items.length === 0) break; |
| 247 | + collected.push(...resp.items); |
| 248 | + if (collected.length >= resp.totalItems) break; |
| 249 | + page += 1; |
279 | 250 | } |
280 | | - return collected.length > total ? collected.slice(0, total) : collected; |
| 251 | + return collected; |
281 | 252 | } |
282 | 253 |
|
283 | 254 | async function connect(): Promise<void> { |
284 | 255 | try { |
285 | 256 | const initial = await fetchInitial(); |
286 | 257 | if (!alive) return; |
287 | | - // The first time real data lands, every cell in the matrix has to |
288 | | - // re-render (empty map → populated map invalidates per-key memo |
289 | | - // checks). That commit is a hundreds-of-cells walk; flag it as a |
290 | | - // transition so React can interleave user input. `setStatus` |
291 | | - // remains urgent so the "connecting → live" indicator flips |
292 | | - // immediately, before the heavy commit lands. |
293 | | - startTransition(() => { |
294 | | - setRows(initial); |
295 | | - }); |
| 258 | + setRows(initial); |
296 | 259 | setStatus("live"); |
297 | 260 | setError(null); |
298 | 261 | // Reset the reconnect counter on a SUCCESSFUL connection. This is |
|
0 commit comments