sewt all limit #77

Merged
ahmed.mujtaba merged 2 commits from SQS_BROKER into main 2026-09-07 11:52:13 +00:00
2 changed files with 36 additions and 23 deletions

View File

@ -215,6 +215,18 @@ try {
hasSkeleton(view.html()) && rowCount(view.html()) === 0,
`skeleton=${hasSkeleton(view.html())} rows=${rowCount(view.html())}`,
)
const allFormFetch = requestsMatching('/sheet/form-data/fetch')
const allEmailFetch = requestsMatching('/inbox/all-applications').filter((u) => !u.includes('/count'))
check(
'All channel sends the UI page size, not the whole form_data table',
allFormFetch.some((u) => u.includes('limit=10')),
allFormFetch.slice(-1)[0] || 'no form-data fetch on All',
)
check(
'All channel emails send the same page size',
allEmailFetch.some((u) => u.includes('top=10')),
allEmailFetch.slice(-1)[0] || 'no email fetch on All',
)
// ---- frame 2: email home, sheet still travelling ------------------------
// THE REGRESSION: this frame used to render six placeholders on top of two

View File

@ -49,10 +49,10 @@ const PAGE_SIZE_MAX = 500
* This list is a local table, not a live feed. Applications only appear when
* the Sync worker writes them, and that path already invalidates
* qk.mailbox.all() the moment a run completes so refetching on every visit
* bought nothing and cost a full-width skeleton each time. The All channel
* makes that worse: it fetches BOTH sources unpaged, so the "loading" state a
* recruiter saw on re-entry was thousands of rows being re-downloaded to
* render the same ten.
* bought nothing and cost a full-width skeleton each time.
*
* List fetches always send the UI page size (10 / 50 / 100), including All
* omitting limit used to dump the whole form_data table into the browser.
*
* staleTime therefore covers a normal working stretch, and keepPreviousData
* means a tab switch, a page turn or a keystroke re-renders the rows already
@ -1010,22 +1010,22 @@ export default function Inbox() {
}, [deepOpen, deepKind, setSearchParams])
const isForms = channel === 'forms'
// Combined channel: both sources fetched UNPAGED (each endpoint reads a
// missing top/limit as no LIMIT), merged by date, and paged client-side
// per-source skip/top cannot compose into a correct global page.
// Combined channel: each source is fetched with the UI page size, then
// merged newest-first. Per-source pages are not a perfect global timeline,
// but omitting limit dumped the whole form_data table on open.
const isAllChannel = channel === 'all'
const channelTabs = isForms || isAllChannel ? FORM_TABS : TABS
const pageLimit = pageSize === 'all' ? undefined : pageSize
const tabFilter = TAB_FILTERS[tab] ?? {}
const formTabFilter = FORM_TAB_FILTERS[tab] ?? {}
const listParams = useMemo(() => ({
...tabFilter,
// 'all' page size and the All channel drop the param entirely the
// endpoint reads a missing top as unpaged.
top: pageSize === 'all' || isAllChannel ? undefined : pageSize,
skip: isAllChannel ? 0 : skip,
// Show-all omits top (no LIMIT). Otherwise send the pager size 10, 50, 100.
top: pageLimit,
skip: pageLimit == null ? 0 : skip,
...(search ? { search } : {}),
}), [tabFilter, skip, pageSize, search, isAllChannel])
}), [tabFilter, skip, pageLimit, search])
/**
* Sheet Forms only. On the All channel these rows are merged with email ones,
@ -1042,12 +1042,12 @@ export default function Inbox() {
const formParams = useMemo(() => ({
// All channel spans every sheet tab, not just the selected one.
sheet: isAllChannel ? undefined : (formSheet || undefined),
offset: isAllChannel ? 0 : skip,
limit: pageSize === 'all' || isAllChannel ? undefined : pageSize,
offset: pageLimit == null ? 0 : skip,
limit: pageLimit,
...formTabFilter,
...(search ? { search } : {}),
...linkFilters,
}), [formSheet, skip, pageSize, search, formTabFilter, isAllChannel, linkFilters])
}), [formSheet, skip, pageLimit, search, formTabFilter, isAllChannel, linkFilters])
const applicationsQuery = useQuery({
queryKey: qk.mailbox.applications(listParams),
@ -1135,8 +1135,7 @@ export default function Inbox() {
}
}, [isForms, formSheetsQuery.data, formSheet])
// All channel: both sources arrive unpaged; merge newest-first and let the
// pager slice the merged array below.
// All channel: one page from each source, merged newest-first.
const mergedRows = useMemo(() => {
if (!isAllChannel) return null
const emails = Array.isArray(applicationsQuery.data?.rows) ? applicationsQuery.data.rows : []
@ -1198,15 +1197,17 @@ export default function Inbox() {
const countsReady = isAllChannel
? countsQuery.isSuccess && formCountsQuery.isSuccess
: (isForms ? formCountsQuery.isSuccess : countsQuery.isSuccess)
const searchTotal = isAllChannel
? (Number(applicationsQuery.data?.total ?? 0) + Number(formQuery.data?.total ?? 0))
: (activeQuery.data?.total ?? 0)
const total = search
? (activeQuery.data?.total ?? 0)
? searchTotal
: (countsReady ? tabTotal : (poolTotal || (activeQuery.data?.total ?? 0)))
// 'all' = unpaged: the fetch omits top/limit (the endpoints treat a missing
// page size as no LIMIT), so the whole tab is one page.
const showAll = pageSize === 'all'
const pages = showAll ? 1 : Math.max(1, Math.ceil(total / pageSize))
const from = total ? skip + 1 : 0
const to = showAll ? total : Math.min(skip + pageSize, total)
const currentPage = showAll ? 1 : Math.min(Math.floor(skip / pageSize) + 1, pages)
useEffect(() => {
@ -1215,8 +1216,9 @@ export default function Inbox() {
setSkip(Math.max(0, Math.floor((total - 1) / pageSize) * pageSize))
}, [total, pageSize, skip, showAll])
// All channel pages the merged array client-side; single channels page on the server.
const list = isAllChannel && !showAll ? inbox.slice(skip, skip + pageSize) : inbox
// Server already applied skip/limit (or the whole tab when Show all).
const list = inbox
const to = showAll ? total : Math.min(skip + (isAllChannel ? list.length : pageSize), total)
// Mixed rows: the row's own kind picks the detail endpoint, not the channel.
const selectedKind = inbox.find((i) => sameInboxId(i.id, selectedId))?.kind
@ -1399,8 +1401,7 @@ export default function Inbox() {
/** Styled XLSX of the rows already loaded in this view the DB filtered
them when the list was fetched (channel, tab, search); no extra request.
Paged channels export the loaded page; the All channel and the "All"
page size hold the whole view, so those export everything. */
Paged channels export the loaded page; Show all exports the whole view. */
async function exportRows() {
const rows = inbox
if (!rows.length) {