feat: switch all tables to native Convex pagination#451
Conversation
Replace cursor-based queries with Convex native .paginate() and useCachedPaginatedQuery for both the customers and executions tables. This enables automatic multi-page subscription management, server-side filtering via compound indexes, and an end-of-list indicator in the data table. Adds a guard for missing teamId in team_members query.
Extend native pagination to approvals, audit logs, conversations, documents, products, and vendors — replacing TanStack DB collections with server-side paginated queries and infinite scroll loading.
b1e5b96 to
2cd6c8c
Compare
📝 WalkthroughWalkthroughThis pull request implements a comprehensive pagination system across multiple features in the platform. It introduces paginated query hooks ( Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
services/platform/app/features/documents/components/documents-client.tsx (1)
101-124:⚠️ Potential issue | 🟠 MajorClient-side filters now operate on a partial dataset.
Because
filteredResultsderives frompaginatedResult.resultsonly, search/team/folder filters no longer cover the full dataset. This can hide matches that exist on later pages and may prematurely show an “end of list” state. Consider pushing these filters intolistDocumentsPaginated(preferred) or, when filters are active, auto‑loading pages until exhausted before showing “no results.”
🤖 Fix all issues with AI agents
In `@services/platform/app/components/ui/data-table/data-table.tsx`:
- Around line 557-561: Summary: The status text currently uses a non-semantic
<p> with role="status"; replace it with a semantic element. Replace the <p ...
role="status"> that renders {t('pagination.noMore')} in data-table.tsx with a
semantic <output> (keep the existing className and text content) and remove the
explicit role attribute (or retain aria-live="polite" if you need live-region
behavior). Ensure the element is properly closed and that any tests or styles
targeting the original tag still work after the change.
In `@services/platform/app/features/automations/executions/executions-client.tsx`:
- Around line 137-154: When isSearching is true we should avoid running the
paginated query; update the call site in executions-client.tsx (where
paginatedResult is created via useListExecutions) to pass 'skip' instead of the
normal args when isSearching is true, and then change the useListExecutions
signature in queries.ts to accept ListExecutionsArgs | 'skip' (inside
useListExecutions detect the 'skip' sentinel and call useCachedPaginatedQuery
with the 'skip' key and a default initialNumItems, otherwise destructure
initialNumItems and forward the real args as before), ensuring
useSearchExecution remains unchanged.
In `@services/platform/app/features/customers/components/customers-table.tsx`:
- Around line 18-23: The PaginatedResult interface is duplicated; extract it
into a shared exported type (e.g., export interface PaginatedResult { results:
Customer[]; status: 'LoadingFirstPage' | 'CanLoadMore' | 'LoadingMore' |
'Exhausted'; loadMore: (numItems: number) => void; isLoading: boolean; }) in a
new shared types module and update usages to import that shared PaginatedResult
in customers-table.tsx as well as audit-log-table.tsx and
conversations-list.tsx; ensure the shared type is exported and replace the local
interface declarations with imports so all components reference the same type.
- Around line 165-174: The current client-side search configured in useListPage
(search: { fields: ['name','email','externalId'], placeholder: searchPlaceholder
}) only filters loaded pages and will miss matches on un-fetched pages; fix by
making search server-side: thread the search term from the useListPage state
into the paginated data loader used to fetch rows (the function that loads pages
for this customers table / the backend query that returns paginated customers)
so the backend receives a search param and returns filtered pages, update the
paginated query handler to accept and apply that search param (or alternatively
document this limitation in the UI or implement auto-loading of additional pages
when a search term is entered); also ensure handleClearFilters and filterConfigs
continue to reset the search state.
In `@services/platform/app/features/documents/components/documents-client.tsx`:
- Around line 154-157: The previewDocument calculation currently only searches
filteredResults and returns null for deep links when the target docId isn't on
the current page; update the logic in the previewDocument useMemo to, when docId
is set but not found in filteredResults, either (a) call the existing loadMore
function repeatedly (or trigger pagination) until an item with id === docId
appears in filteredResults, or (b) perform a separate fetch-by-id query to load
that single document and return it; ensure you reference previewDocument,
filteredResults, docId, and loadMore in your changes and keep the useMemo
dependency array consistent.
In `@services/platform/app/features/products/components/product-table.tsx`:
- Around line 18-23: The PaginatedResult interface is duplicated; extract it
into a shared types file (e.g., pagination.ts) as a generic export like export
interface PaginatedResult<T> { results: T[]; status: 'LoadingFirstPage' |
'CanLoadMore' | 'LoadingMore' | 'Exhausted'; loadMore: (numItems: number) =>
void; isLoading: boolean; } and replace the local PaginatedResult declaration in
components (including the one in product-table.tsx) with an import of
PaginatedResult<T>, updating usages to PaginatedResult<Product> (or appropriate
type) so all tables (products, vendors, etc.) use the single shared type.
In
`@services/platform/app/features/settings/audit-logs/components/audit-log-table.tsx`:
- Around line 73-80: The category filter options array in AuditLogTable
currently uses hardcoded English labels; replace each label with i18n
translations using the existing t function (e.g., options: [{ value: 'auth',
label: t('logs.audit.categories.auth') }, ...]) so they match the filter title
which uses t('logs.audit.columns.category'). Update the options array in the
audit-log-table.tsx component (where the variable/options for the category
filter is defined) to call t(...) for each label key (create sensible keys like
logs.audit.categories.auth, member, data, integration, workflow, security,
admin) and ensure t is in scope (import/useTranslation or use the component's
existing t) before returning the options.
In `@services/platform/convex/approvals/queries.ts`:
- Around line 14-31: The handler in listApprovalsPaginated currently throws when
getAuthUserIdentity returns null; change it to return an empty pagination result
instead (matching listApprovalsByOrganization and
getPendingIntegrationApprovalsForThread) rather than throwing. Concretely, check
the result of getAuthUserIdentity(ctx) and if falsy return the empty pagination
shape used elsewhere (e.g. { items: [], nextCursor: null }) and avoid calling
listApprovalsPaginatedHelper or getOrganizationMember for unauthenticated
callers; keep the rest of the function flow the same for authenticated users.
In `@services/platform/convex/documents/list_documents_paginated.test.ts`:
- Around line 108-141: The test is asserting result.page entries use an id field
but the fixture docs use _id; ensure the mapping done by transformDocumentsBatch
is relied upon or made explicit: either (A) update the test setup to mock
transformDocumentsBatch (or its dependency) so that _id is mapped to id, or (B)
assert against the transformed shape by calling transformDocumentsBatch on the
docs before asserting, or at minimum add a clarifying comment. Locate references
to listDocumentsPaginated, transformDocumentsBatch, and ctx.storage.getUrl in
the test and adjust the mock/expectations so the test does not depend implicitly
on an unmocked transformation.
The example messages table reads `search.page` for client-side pagination, but the route had no validateSearch schema, causing a TypeScript error.
Summary
paginationOptsValidator-based paginationlist_*_paginatedserver queries with tests for each domainDataTableinfinite scroll component to show an end-of-list indicator when all data is loadedTest plan
list_*_paginated.test.ts)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Performance