fix(knowledge-fs): isolate document detail route state

This commit is contained in:
Stephen Zhou 2026-09-01 19:32:24 +08:00
parent a7a16c1335
commit 4dba1bc22e
No known key found for this signature in database
3 changed files with 80 additions and 10 deletions

View File

@ -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(
<>
<NuqsTestingAdapter searchParams="?revision=3&chunk=first">
<DocumentDetailPage documentId="document-1" knowledgeSpaceId="space-1" />
</NuqsTestingAdapter>
<NuqsTestingAdapter searchParams="?revision=3&chunk=target">
<DocumentDetailPage documentId="document-1" knowledgeSpaceId="space-1" />
</NuqsTestingAdapter>
</>,
)
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

View File

@ -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 (
<ScopeProvider
key={`${knowledgeSpaceId}:${documentId}`}
atoms={[
[documentDetailDocumentIdAtom, documentId],
[documentDetailKnowledgeSpaceIdAtom, knowledgeSpaceId],
[documentDetailRequestedChunkIdAtom, documentLocation.chunk],
[documentDetailRequestedRevisionAtom, documentLocation.revision],
...documentWorkflowScopedAtoms,
]}
name="DocumentDetailPage"
>
<DocumentDetailLocationBridge
chunkId={documentLocation.chunk}
revision={documentLocation.revision}
>
{children}
</DocumentDetailLocationBridge>
</ScopeProvider>
)
return children
}

View File

@ -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)