From 4dba1bc22e0beafd55d351b05d341f3138418af2 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 1 Sep 2026 19:32:24 +0800 Subject: [PATCH] fix(knowledge-fs): isolate document detail route state --- .../documents/detail/__tests__/page.spec.tsx | 34 +++++++++++++ .../documents/detail/state/boundary.tsx | 51 +++++++++++++++---- .../documents/detail/state/workflow.ts | 5 ++ 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/web/features/new-rag/documents/detail/__tests__/page.spec.tsx b/web/features/new-rag/documents/detail/__tests__/page.spec.tsx index 968f628b367..f53d265bf75 100644 --- a/web/features/new-rag/documents/detail/__tests__/page.spec.tsx +++ b/web/features/new-rag/documents/detail/__tests__/page.spec.tsx @@ -16,6 +16,7 @@ import { act, fireEvent, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import copy from 'copy-to-clipboard' import { createStore, Provider } from 'jotai' +import { NuqsTestingAdapter } from 'nuqs/adapters/testing' import { renderWithNuqs } from '@/test/nuqs-testing' import { DocumentDetailPage } from '../page' @@ -2555,6 +2556,39 @@ describe('DocumentDetailPage', () => { getBoundingClientRect.mockRestore() }) + it('isolates document route state while client navigation renders two route instances', () => { + chunksQuery.data = { + pages: [ + { + items: [ + chunk({ id: 'first', text: 'First chunk' }), + chunk({ id: 'target', ordinal: 2, text: 'Target chunk' }), + ], + }, + ], + } + + render( + <> + + + + + + + , + ) + + const firstChunkRows = screen.getAllByRole('treeitem', { name: 'First chunk' }) + const targetChunkRows = screen.getAllByRole('treeitem', { name: 'Target chunk' }) + expect(firstChunkRows).toHaveLength(2) + expect(targetChunkRows).toHaveLength(2) + expect(firstChunkRows[0]).toHaveAttribute('aria-selected', 'true') + expect(targetChunkRows[0]).toHaveAttribute('aria-selected', 'false') + expect(firstChunkRows[1]).toHaveAttribute('aria-selected', 'false') + expect(targetChunkRows[1]).toHaveAttribute('aria-selected', 'true') + }) + it('distinguishes missing, restricted, and retryable document failures', async () => { const user = userEvent.setup() documentQuery.data = undefined diff --git a/web/features/new-rag/documents/detail/state/boundary.tsx b/web/features/new-rag/documents/detail/state/boundary.tsx index c949999c91f..867380163dd 100644 --- a/web/features/new-rag/documents/detail/state/boundary.tsx +++ b/web/features/new-rag/documents/detail/state/boundary.tsx @@ -1,6 +1,7 @@ 'use client' import type { ReactNode } from 'react' +import { ScopeProvider } from 'jotai-scope' import { useHydrateAtoms } from 'jotai/utils' import { useQueryStates } from 'nuqs' import { @@ -10,6 +11,27 @@ import { documentDetailRequestedRevisionAtom, } from './inputs' import { documentDetailChunkParser, documentDetailRevisionParser } from './location' +import { documentWorkflowScopedAtoms } from './workflow' + +function DocumentDetailLocationBridge({ + children, + chunkId, + revision, +}: { + children: ReactNode + chunkId: string | null + revision: number | null +}) { + useHydrateAtoms( + [ + [documentDetailRequestedChunkIdAtom, chunkId], + [documentDetailRequestedRevisionAtom, revision], + ], + { dangerouslyForceHydrate: true }, + ) + + return children +} export function DocumentDetailStateBoundary({ children, @@ -25,15 +47,24 @@ export function DocumentDetailStateBoundary({ revision: documentDetailRevisionParser, }) - useHydrateAtoms( - [ - [documentDetailDocumentIdAtom, documentId], - [documentDetailKnowledgeSpaceIdAtom, knowledgeSpaceId], - [documentDetailRequestedChunkIdAtom, documentLocation.chunk], - [documentDetailRequestedRevisionAtom, documentLocation.revision], - ], - { dangerouslyForceHydrate: true }, + return ( + + + {children} + + ) - - return children } diff --git a/web/features/new-rag/documents/detail/state/workflow.ts b/web/features/new-rag/documents/detail/state/workflow.ts index 9f838f24bd5..dd3d29bdf01 100644 --- a/web/features/new-rag/documents/detail/state/workflow.ts +++ b/web/features/new-rag/documents/detail/state/workflow.ts @@ -67,6 +67,11 @@ function initialWorkflowState(identity: string): DocumentWorkflowState { const documentWorkflowStateAtom = atom(initialWorkflowState('')) +export const documentWorkflowScopedAtoms = [ + documentHasEditPermissionAtom, + documentWorkflowStateAtom, +] as const + function workflowState(get: Getter) { const identity = workflowIdentity(get) const state = get(documentWorkflowStateAtom)