[PR #32339] refactor(web): migrate document list query state to nuqs #33679

Open
opened 2026-02-21 20:53:42 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/langgenius/dify/pull/32339

State: open
Merged: No


Problem

  1. URL query state was managed through scattered, hand-written parse/serialize logic (router.push + URLSearchParams) across modules, with inconsistent constraints.
  2. The Documents list had a dual-source-of-truth issue: URL query state and local component state (sorting/filtering/pagination) coexisted and were synchronized via useEffect, which increased coupling and drift risk.
  3. High-risk query fields (category, tab, action, sort, status) were not consistently constrained, so invalid values could leak into UI flows.
  4. nuqs test coverage was inconsistent: many tests asserted only UI outcomes, but not URL semantics (history, default clearing, parser fallback behavior).

Decision

  1. Standardize on a parser-first nuqs approach using parseAsStringLiteral / parseAsStringEnum / createParser + useQueryState / useQueryStates.
  2. Make URL query params the single source of truth for Document List state:
    • page/limit/keyword/status/sort are fully query-derived.
    • Sorting is remote-driven and limited to server-sortable fields (created_at, hit_count).
  3. Remove sync-style local side-effect state where possible:
    • Drop local polling state + effect in favor of TanStack Query refetchInterval callback logic.
    • Remove local sortedDocuments and render server-returned data directly.
  4. Unify test infrastructure with web/test/nuqs-testing.tsx (renderWithNuqs / renderHookWithNuqs) and assert URL updates via onUrlUpdate.
  5. Improve semantics and navigation context:
    • Replace non-semantic interactive icons with semantic button usage.
    • Preserve list query params when navigating list -> detail -> back.

Model

1) Query State Model (Parser Contract)

  • Documents: page/limit/keyword/status/sort
  • Apps: category
  • Tools: category
  • Plugin Page: tab
  • Account Settings modal: action + tab
  • Marketplace SSR params: category/q/tags

Each field now has explicit parse/serialize + default/fallback behavior. Invalid values are constrained at parser boundaries, and default-value clearing follows nuqs semantics.

2) Update Strategy Model (History & URL policy)

  • High-frequency input (keyword): history: 'replace' + throttled URL updates.
  • Discrete user actions (pagination, limit, status, sort): history: 'push'.
  • Full reset: replace to default query set.

3) Data & Side-Effect Model (Server-State First)

  • List data is owned by TanStack Query.
  • Polling policy moved from local timer state/effects to declarative refetchInterval(query) => number | false:
    • Keep polling for transient filters (queuing/indexing/paused) or when non-terminal documents exist.
    • Stop polling otherwise.

Impact

Behavior

  1. Document List sorting is consistently remote-driven: only created_at / hit_count are sortable; no local name/word_count sorting.
  2. Detail page back navigation restores the original list context (pagination/filter/sort/keyword).
  3. Invalid query inputs are safely normalized by parsers (e.g. invalid sort/status/category/tab/action).
  4. Query-driven behavior is now stricter and more predictable in Account Settings / Plugin / Tools / Apps flows.

Codebase & Maintainability

  1. Removes large amounts of manual URL parsing/string-building and effect-based state synchronization.
  2. useDocumentList now accepts full React Query refetchInterval typing (UseQueryOptions<DocumentListResponse>['refetchInterval']) for dynamic polling policies.
  3. Legacy ESLint suppressions tied to older patterns were removed.

Testing & Docs

  1. nuqs testing is standardized across hooks and integration tests.
  2. web/docs/test.md now includes explicit nuqs testing best practices (including partial mocking and onUrlUpdate assertions).
  3. Apps / Documents / Plugins / Tools / Modal-context tests were aligned with the new query-state model.

Test plan

  • cd web && pnpm vitest app/components/datasets/documents/hooks/__tests__/use-document-list-query-state.spec.tsx
  • cd web && pnpm vitest app/components/datasets/documents/hooks/__tests__/use-documents-page-state.spec.ts
  • cd web && pnpm vitest app/components/datasets/documents/__tests__/index.spec.tsx
  • cd web && pnpm vitest __tests__/datasets/document-management.test.tsx
  • cd web && pnpm vitest __tests__/tools/tool-browsing-and-filtering.test.tsx
  • cd web && pnpm type-check:tsgo
**Original Pull Request:** https://github.com/langgenius/dify/pull/32339 **State:** open **Merged:** No --- ## Problem 1. URL query state was managed through scattered, hand-written parse/serialize logic (`router.push` + `URLSearchParams`) across modules, with inconsistent constraints. 2. The Documents list had a dual-source-of-truth issue: URL query state and local component state (sorting/filtering/pagination) coexisted and were synchronized via `useEffect`, which increased coupling and drift risk. 3. High-risk query fields (`category`, `tab`, `action`, `sort`, `status`) were not consistently constrained, so invalid values could leak into UI flows. 4. `nuqs` test coverage was inconsistent: many tests asserted only UI outcomes, but not URL semantics (`history`, default clearing, parser fallback behavior). ## Decision 1. Standardize on a parser-first `nuqs` approach using `parseAsStringLiteral` / `parseAsStringEnum` / `createParser` + `useQueryState` / `useQueryStates`. 2. Make URL query params the single source of truth for Document List state: - `page/limit/keyword/status/sort` are fully query-derived. - Sorting is remote-driven and limited to server-sortable fields (`created_at`, `hit_count`). 3. Remove sync-style local side-effect state where possible: - Drop local polling state + effect in favor of TanStack Query `refetchInterval` callback logic. - Remove local `sortedDocuments` and render server-returned data directly. 4. Unify test infrastructure with `web/test/nuqs-testing.tsx` (`renderWithNuqs` / `renderHookWithNuqs`) and assert URL updates via `onUrlUpdate`. 5. Improve semantics and navigation context: - Replace non-semantic interactive icons with semantic `button` usage. - Preserve list query params when navigating list -> detail -> back. ## Model ### 1) Query State Model (Parser Contract) - Documents: `page/limit/keyword/status/sort` - Apps: `category` - Tools: `category` - Plugin Page: `tab` - Account Settings modal: `action + tab` - Marketplace SSR params: `category/q/tags` Each field now has explicit parse/serialize + default/fallback behavior. Invalid values are constrained at parser boundaries, and default-value clearing follows `nuqs` semantics. ### 2) Update Strategy Model (History & URL policy) - High-frequency input (`keyword`): `history: 'replace'` + throttled URL updates. - Discrete user actions (pagination, limit, status, sort): `history: 'push'`. - Full reset: `replace` to default query set. ### 3) Data & Side-Effect Model (Server-State First) - List data is owned by TanStack Query. - Polling policy moved from local timer state/effects to declarative `refetchInterval(query) => number | false`: - Keep polling for transient filters (`queuing/indexing/paused`) or when non-terminal documents exist. - Stop polling otherwise. ## Impact ### Behavior 1. Document List sorting is consistently remote-driven: only `created_at` / `hit_count` are sortable; no local name/word_count sorting. 2. Detail page back navigation restores the original list context (pagination/filter/sort/keyword). 3. Invalid query inputs are safely normalized by parsers (e.g. invalid sort/status/category/tab/action). 4. Query-driven behavior is now stricter and more predictable in Account Settings / Plugin / Tools / Apps flows. ### Codebase & Maintainability 1. Removes large amounts of manual URL parsing/string-building and effect-based state synchronization. 2. `useDocumentList` now accepts full React Query `refetchInterval` typing (`UseQueryOptions<DocumentListResponse>['refetchInterval']`) for dynamic polling policies. 3. Legacy ESLint suppressions tied to older patterns were removed. ### Testing & Docs 1. `nuqs` testing is standardized across hooks and integration tests. 2. `web/docs/test.md` now includes explicit `nuqs` testing best practices (including partial mocking and `onUrlUpdate` assertions). 3. Apps / Documents / Plugins / Tools / Modal-context tests were aligned with the new query-state model. ## Test plan - [x] `cd web && pnpm vitest app/components/datasets/documents/hooks/__tests__/use-document-list-query-state.spec.tsx` - [x] `cd web && pnpm vitest app/components/datasets/documents/hooks/__tests__/use-documents-page-state.spec.ts` - [x] `cd web && pnpm vitest app/components/datasets/documents/__tests__/index.spec.tsx` - [x] `cd web && pnpm vitest __tests__/datasets/document-management.test.tsx` - [x] `cd web && pnpm vitest __tests__/tools/tool-browsing-and-filtering.test.tsx` - [x] `cd web && pnpm type-check:tsgo`
yindo added the pull-request label 2026-02-21 20:53:42 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#33679