> ## Documentation Index
> Fetch the complete documentation index at: https://cona.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination and url state

# Pagination & URL State (nuqs)

## Overview

Our data tables use URL-synchronized state via nuqs. Pagination should be stable: selecting rows or toggling non-paging controls must not reset the current page to 1.

This guide explains:

* How pagination parameters are modeled
* Which URL params trigger a page reset
* The `OMIT_FIELDS` list used to prevent unwanted resets
* Practical recommendations when adding new URL params

## Key URL Parameters

* `page`: current page (1-based)
* `size`: page size
* `sort`: sort field key
* `asc`: sort direction (boolean)
* `filter`: whether filtering UI is enabled (boolean)
* Additional dynamic filter params (e.g., `doc_date`, `status`, etc.)
* Selection and UI-only params (e.g., `selected`)

## Page Reset Rules

Changing actual filter values should reset to page 1 so users see results from the beginning. However, changes to purely presentational or ephemeral state (like selection) must not trigger a reset.

We implement this using an omit list during change detection.

### OMIT\_FIELDS

Source of truth in the webapp utility:

```19:28:apps/webapp/app/lib/utils/search-params-utils.ts theme={null}
const OMIT_FIELDS = [
  "page",
  "pageSize",
  "asc",
  "sort",
  "filter",
  "tab",
  // Do not reset page when selection changes
  "selected",
] as const;
```

Any differences between the previous and current processed params after omitting these fields are considered a “meaningful change” and will reset `page` to the default.

### Where Reset Happens

The reset to page 1 is applied in `processSearchParams` when a meaningful change is detected:

```52:60:apps/webapp/app/lib/utils/search-params-utils.ts theme={null}
const filteredProcessedParams = omit({ ...processedParams }, OMIT_FIELDS);

if (previousParsedParams && !isEqual(previousParsedParams, filteredProcessedParams)) {
  processedParams.page = DEFAULT_PAGE.toString();
}

previousParsedParams = filteredProcessedParams;
```

In addition, the pagination/UI hook `useQueryFiltersSync` resets `page` when any filter group (not omitted) changes, using a debounced effect to avoid jitter during typing:

```156:166:apps/webapp/app/hooks/use-query-filters-sync.ts theme={null}
if (JSON.stringify(prevFilters.current) !== JSON.stringify(filters)) {
  if (debounceTimeoutRef.current) clearTimeout(debounceTimeoutRef.current);
  debounceTimeoutRef.current = setTimeout(() => {
    setPage(DEFAULT_PAGE.toString());
    prevFilters.current = filters;
  }, DEBOUNCE_INTERVAL);
}
```

## What Should Not Reset Pagination

The following changes must not reset the page:

* Row selection changes: `selected`
* Sorting toggles: `sort`, `asc`
* Pagination controls themselves: `page`, `size`
* View toggles/tabs: `tab`
* UI-only flags that don’t change the dataset shape

Ensure these live in `OMIT_FIELDS` or otherwise do not affect the reset logic.

## What Should Reset Pagination

Any change that alters the dataset shape/order enough that page N might no longer contain the same items, for example:

* Adding/removing/changing filter values (e.g., `doc_date`, `status`)
* Toggling `filter` that enables filter mode and reveals active filters
* Changing object scoping parameters

These should not be in `OMIT_FIELDS` so that the system resets to page 1.

## Best Practices When Adding Params

1. Classify the parameter:
   * Data-affecting (should reset) vs. UI-only/ephemeral (should not reset)
2. If it should not reset, add its key to `OMIT_FIELDS` in `search-params-utils.ts`.
3. Keep pagination stable on selection and sort changes.
4. Debounce page resets on filter edits to avoid flicker.
5. Keep URL key mapping consistent (see `search-params-config` for `pageSize` ⇄ `size`).

## Example: Row Selection Should Not Reset

In AR Reconciliation, selecting a row updates the `selected` query param. Because `selected` is listed in `OMIT_FIELDS`, changing selection will not reset `page`.

## Troubleshooting

* Pagination resets unexpectedly: Verify the param is in `OMIT_FIELDS` if it is UI-only.
* Page doesn’t reset when filters change: Ensure those filter keys are NOT in `OMIT_FIELDS` and that they flow through `processSearchParams`.
* Jumping during typing: Confirm debounce is applied in `useQueryFiltersSync`.

***

By following these rules and maintaining `OMIT_FIELDS`, pagination remains predictable while filters and controls work as expected.
