Skip to content

feat: switch all tables to native Convex pagination#451

Merged
larryro merged 8 commits into
mainfrom
feat/native-pagination-customers-executions
Feb 13, 2026
Merged

feat: switch all tables to native Convex pagination#451
larryro merged 8 commits into
mainfrom
feat/native-pagination-customers-executions

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Feb 13, 2026

Summary

  • Switches all data tables (customers, executions, approvals, conversations, documents, products, vendors, audit logs) from client-side pagination to native Convex paginationOptsValidator-based pagination
  • Adds dedicated list_*_paginated server queries with tests for each domain
  • Updates the DataTable infinite scroll component to show an end-of-list indicator when all data is loaded

Test plan

  • Verify each table loads initial page and loads more on scroll
  • Confirm end-of-list indicator appears when all rows are loaded
  • Check that search/filter still works correctly on paginated tables
  • Run new backend pagination tests (list_*_paginated.test.ts)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced infinite scroll functionality with improved loading states and end-of-list indicators across list views.
    • Paginated data loading for approvals, conversations, customers, documents, products, vendors, and other list-based features.
    • Improved search experience with separate search and browse modes.
  • Bug Fixes

    • Refined loading state handling for paginated lists to provide clearer user feedback.
  • Performance

    • Optimized large list rendering through pagination, reducing initial load times and improving responsiveness.

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.
@larryro larryro force-pushed the feat/native-pagination-customers-executions branch from b1e5b96 to 2cd6c8c Compare February 13, 2026 12:34
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

This pull request implements a comprehensive pagination system across multiple features in the platform. It introduces paginated query hooks (useListXyzPaginated) for approvals, conversations, customers, products, vendors, audit-logs, and documents, with corresponding backend Convex query implementations. Components consuming raw data arrays are refactored to accept a PaginatedResult object containing results, status, loadMore function, and loading state. The infinite scroll UI in data-table is enhanced with explicit end-of-list rendering. The approval update hook is simplified to delegate directly to API mutations without collection-based logic. Routes are updated to use the new paginated hooks and pass pagination state through component hierarchies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (46 files):

⚔️ services/crawler/pyproject.toml (content)
⚔️ services/crawler/uv.lock (content)
⚔️ services/graph-db/pyproject.toml (content)
⚔️ services/graph-db/uv.lock (content)
⚔️ services/operator/pyproject.toml (content)
⚔️ services/operator/uv.lock (content)
⚔️ services/platform/app/components/ui/data-table/data-table.tsx (content)
⚔️ services/platform/app/features/approvals/components/approvals-client.tsx (content)
⚔️ services/platform/app/features/approvals/hooks/mutations.ts (content)
⚔️ services/platform/app/features/approvals/hooks/queries.ts (content)
⚔️ services/platform/app/features/automations/executions/executions-client.tsx (content)
⚔️ services/platform/app/features/automations/hooks/queries.ts (content)
⚔️ services/platform/app/features/chat/components/integration-approval-card.tsx (content)
⚔️ services/platform/app/features/chat/components/workflow-creation-approval-card.tsx (content)
⚔️ services/platform/app/features/conversations/components/conversations-client.tsx (content)
⚔️ services/platform/app/features/conversations/components/conversations-list.tsx (content)
⚔️ services/platform/app/features/conversations/hooks/queries.ts (content)
⚔️ services/platform/app/features/customers/components/customers-table.tsx (content)
⚔️ services/platform/app/features/customers/hooks/queries.ts (content)
⚔️ services/platform/app/features/documents/components/documents-client.tsx (content)
⚔️ services/platform/app/features/documents/hooks/queries.ts (content)
⚔️ services/platform/app/features/products/components/product-table.tsx (content)
⚔️ services/platform/app/features/products/hooks/queries.ts (content)
⚔️ services/platform/app/features/settings/audit-logs/components/audit-log-table.tsx (content)
⚔️ services/platform/app/features/settings/audit-logs/hooks/queries.ts (content)
⚔️ services/platform/app/features/vendors/components/vendors-table.tsx (content)
⚔️ services/platform/app/features/vendors/hooks/queries.ts (content)
⚔️ services/platform/app/routes/dashboard/$id/_knowledge/customers.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/_knowledge/products.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/_knowledge/vendors.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/approvals/$status.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/conversations/$status.tsx (content)
⚔️ services/platform/app/routes/dashboard/$id/settings/logs.tsx (content)
⚔️ services/platform/convex/_generated/api.d.ts (content)
⚔️ services/platform/convex/approvals/queries.ts (content)
⚔️ services/platform/convex/approvals/schema.ts (content)
⚔️ services/platform/convex/audit_logs/queries.ts (content)
⚔️ services/platform/convex/conversations/queries.ts (content)
⚔️ services/platform/convex/customers/queries.ts (content)
⚔️ services/platform/convex/documents/queries.ts (content)
⚔️ services/platform/convex/products/queries.ts (content)
⚔️ services/platform/convex/team_members/queries.ts (content)
⚔️ services/platform/convex/vendors/queries.ts (content)
⚔️ services/platform/convex/wf_executions/queries.ts (content)
⚔️ services/rag/pyproject.toml (content)
⚔️ services/rag/uv.lock (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: switch all tables to native Convex pagination' clearly and concisely describes the primary change: migrating all data tables to use native Convex pagination instead of previous pagination approaches.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/native-pagination-customers-executions
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feat/native-pagination-customers-executions
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Client-side filters now operate on a partial dataset.

Because filteredResults derives from paginatedResult.results only, 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 into listDocumentsPaginated (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.

Comment thread services/platform/app/components/ui/data-table/data-table.tsx Outdated
Comment thread services/platform/app/features/customers/components/customers-table.tsx Outdated
Comment thread services/platform/app/features/products/components/product-table.tsx Outdated
Comment thread services/platform/app/features/settings/audit-logs/components/audit-log-table.tsx Outdated
Comment thread services/platform/convex/approvals/queries.ts
Comment thread services/platform/convex/documents/list_documents_paginated.test.ts
@larryro larryro merged commit 7877e3c into main Feb 13, 2026
15 checks passed
@larryro larryro deleted the feat/native-pagination-customers-executions branch February 13, 2026 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant