import type {
  DocumentProcessingTask,
  LogicalDocument,
  Source,
} from '@dify/contracts/knowledge-fs/types.gen'
import { hashKey } from '@tanstack/react-query'
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { renderWithNuqs as render } from '@/test/nuqs-testing'
import { DocumentsPage } from '../documents-page'
import { TaskEventObserver } from '../task-event-observer'

type InfiniteOptions = {
  enabled?: boolean
  getNextPageParam: (lastPage: { nextCursor?: string }) => string | undefined
  input: (pageParam: string | null) => unknown
  initialPageParam: string | null
  queryKind: 'documents' | 'sources' | 'tasks'
  queryKey?: readonly unknown[]
  refetchInterval?: (query: { state: { data?: unknown } }) => false | number
}

const documentsQuery = vi.hoisted(() => ({
  data: undefined as
    | { pages: Array<{ items: LogicalDocument[]; nextCursor?: string }> }
    | undefined,
  error: null as unknown,
  fetchNextPage: vi.fn(),
  hasNextPage: false,
  isFetchNextPageError: false,
  isFetching: false,
  isFetchingNextPage: false,
  isPending: false,
  isRefetching: false,
  refetch: vi.fn(),
}))

const tasksQuery = vi.hoisted(() => ({
  data: undefined as
    | { pages: Array<{ items: DocumentProcessingTask[]; nextCursor?: string }> }
    | undefined,
  dataUpdatedAt: 0,
  dataUpdateCount: 0,
  error: null as unknown,
  fetchNextPage: vi.fn(),
  hasNextPage: false,
  isFetchNextPageError: false,
  isFetching: false,
  isFetchingNextPage: false,
  isPending: false,
  refetch: vi.fn(),
}))

const sourcesQuery = vi.hoisted(() => ({
  data: undefined as { pages: Array<{ items: Source[]; nextCursor?: string }> } | undefined,
  error: null as unknown,
  fetchNextPage: vi.fn(),
  hasNextPage: false,
  isFetchNextPageError: false,
  isFetching: false,
  isFetchingNextPage: false,
  isPending: false,
  refetch: vi.fn(),
}))

const cancelMutation = vi.hoisted(() => ({ mutateAsync: vi.fn() }))
const retryMutation = vi.hoisted(() => ({ mutateAsync: vi.fn() }))
const reindexMutation = vi.hoisted(() => ({ mutateAsync: vi.fn() }))
const uploadMutation = vi.hoisted(() => ({ mutateAsync: vi.fn() }))
const bulkUploadMutation = vi.hoisted(() => ({ mutateAsync: vi.fn() }))
const queryCacheListeners = vi.hoisted(
  () =>
    new Set<
      (event: { action: { type: string }; query: { queryHash: string }; type: string }) => void
    >(),
)
const queryClient = vi.hoisted(() => ({
  cancelQueries: vi.fn(),
  getQueryCache: vi.fn(() => ({
    subscribe: (
      listener: (event: {
        action: { type: string }
        query: { queryHash: string }
        type: string
      }) => void,
    ) => {
      queryCacheListeners.add(listener)
      return () => queryCacheListeners.delete(listener)
    },
  })),
  getQueryState: vi.fn(() => ({ dataUpdateCount: tasksQuery.dataUpdateCount })),
  invalidateQueries: vi.fn(),
}))
const streamProcessingTaskEvents = vi.hoisted(() => vi.fn())
const getTaskSnapshot = vi.hoisted(() => vi.fn())
const permissionStateMock = vi.hoisted(() => ({
  datasetAtom: Symbol('datasetDefaultPermissionKeysAtom'),
  datasetKeys: ['dataset.acl.edit'],
  error: null as unknown,
  errorAtom: Symbol('workspacePermissionKeysErrorAtom'),
  fetching: false,
  fetchingAtom: Symbol('workspacePermissionKeysFetchingAtom'),
  loading: false,
  loadingAtom: Symbol('workspacePermissionKeysLoadingAtom'),
  retry: vi.fn(),
  retryAtom: Symbol('retryWorkspacePermissionKeysAtom'),
  refreshAfterDenial: vi.fn(),
  refreshAfterDenialAtom: Symbol('refreshWorkspacePermissionKeysAfterMutationDenialAtom'),
}))
const toastMock = vi.hoisted(() => ({
  error: vi.fn(),
  info: vi.fn(),
  success: vi.fn(),
  warning: vi.fn(),
}))

vi.mock('@/context/permission-state', () => ({
  datasetDefaultPermissionKeysAtom: permissionStateMock.datasetAtom,
  refreshWorkspacePermissionKeysAfterMutationDenialAtom: permissionStateMock.refreshAfterDenialAtom,
  retryWorkspacePermissionKeysAtom: permissionStateMock.retryAtom,
  workspacePermissionKeysErrorAtom: permissionStateMock.errorAtom,
  workspacePermissionKeysFetchingAtom: permissionStateMock.fetchingAtom,
  workspacePermissionKeysLoadingAtom: permissionStateMock.loadingAtom,
}))

vi.mock('jotai', async (importOriginal) => {
  const original = await importOriginal<typeof import('jotai')>()
  return {
    ...original,
    useAtomValue: (atom: unknown) => {
      if (atom === permissionStateMock.datasetAtom) return permissionStateMock.datasetKeys
      if (atom === permissionStateMock.errorAtom) return permissionStateMock.error
      if (atom === permissionStateMock.fetchingAtom) return permissionStateMock.fetching
      if (atom === permissionStateMock.loadingAtom) return permissionStateMock.loading
      return original.useAtomValue(atom as Parameters<typeof original.useAtomValue>[0])
    },
    useSetAtom: (atom: unknown) =>
      atom === permissionStateMock.retryAtom
        ? permissionStateMock.retry
        : atom === permissionStateMock.refreshAfterDenialAtom
          ? permissionStateMock.refreshAfterDenial
          : original.useSetAtom(atom as Parameters<typeof original.useSetAtom>[0]),
  }
})

vi.mock('@langgenius/dify-ui/toast', () => ({ toast: toastMock }))

const documentsInfiniteOptions = vi.hoisted(() =>
  vi.fn((options: Omit<InfiniteOptions, 'queryKind'>) => ({ ...options, queryKind: 'documents' })),
)
const tasksInfiniteOptions = vi.hoisted(() =>
  vi.fn((options: Omit<InfiniteOptions, 'queryKind'>) => ({
    ...options,
    queryKey: ['knowledge-fs', 'tasks'],
    queryKind: 'tasks',
  })),
)
const sourcesInfiniteOptions = vi.hoisted(() =>
  vi.fn((options: Omit<InfiniteOptions, 'queryKind'>) => ({ ...options, queryKind: 'sources' })),
)

vi.mock('@tanstack/react-query', async (importOriginal) => {
  const original = await importOriginal<typeof import('@tanstack/react-query')>()
  return {
    ...original,
    useInfiniteQuery: (options: InfiniteOptions) => {
      if (options.queryKind === 'documents') return documentsQuery
      if (options.queryKind === 'sources') return sourcesQuery
      return tasksQuery
    },
    useMutation: (options: {
      mutationKind: 'bulk-upload' | 'cancel' | 'reindex' | 'retry' | 'upload'
    }) => {
      if (options.mutationKind === 'cancel') return cancelMutation
      if (options.mutationKind === 'retry') return retryMutation
      if (options.mutationKind === 'reindex') return reindexMutation
      if (options.mutationKind === 'bulk-upload') return bulkUploadMutation
      return uploadMutation
    },
    useQueryClient: () => queryClient,
  }
})

vi.mock('@/service/client', () => ({
  consoleClient: {
    knowledgeFs: {
      getKnowledgeSpacesByIdDocumentsByDocumentIdProcessingTasksByTaskId: getTaskSnapshot,
    },
  },
  consoleQuery: {
    knowledgeFs: {
      deleteKnowledgeSpacesByIdDocumentsByDocumentIdProcessingTasksByTaskId: {
        mutationOptions: () => ({ mutationKind: 'cancel' }),
      },
      getKnowledgeSpacesByIdLogicalDocuments: {
        infiniteOptions: documentsInfiniteOptions,
        key: () => ['knowledge-fs', 'documents'],
      },
      getKnowledgeSpacesByIdProcessingTasks: {
        infiniteOptions: tasksInfiniteOptions,
        key: () => ['knowledge-fs', 'tasks'],
      },
      getKnowledgeSpacesByIdSources: {
        infiniteOptions: sourcesInfiniteOptions,
        key: () => ['knowledge-fs', 'sources'],
      },
      postKnowledgeSpacesByIdDocuments: {
        mutationOptions: () => ({ mutationKind: 'upload' }),
      },
      postKnowledgeSpacesByIdDocumentsBulk: {
        mutationOptions: () => ({ mutationKind: 'bulk-upload' }),
      },
      postKnowledgeSpacesByIdDocumentsBulkReindex: {
        mutationOptions: () => ({ mutationKind: 'reindex' }),
      },
      postKnowledgeSpacesByIdDocumentsByDocumentIdProcessingTasksByTaskIdRetry: {
        mutationOptions: () => ({ mutationKind: 'retry' }),
      },
    },
  },
}))

vi.mock('../services/processing-task-events', () => ({ streamProcessingTaskEvents }))

const document = (overrides: Partial<LogicalDocument> = {}): LogicalDocument => ({
  active: {
    activatedAt: '2026-07-20T10:01:00Z',
    contentHash: 'hash',
    createdAt: '2026-07-20T10:00:00Z',
    documentAssetId: 'asset-1',
    documentAssetVersion: 1,
    documentId: 'document-1',
    knowledgeSpaceId: 'space-1',
    mimeType: 'application/pdf',
    revision: 2,
    sizeBytes: 1024,
    state: 'active',
  },
  activeRevision: 2,
  createdAt: '2026-07-20T10:00:00Z',
  id: 'document-1',
  knowledgeSpaceId: 'space-1',
  rowVersion: 1,
  sourceId: 'source-1',
  status: 'ready',
  title: 'sso-enterprise.pdf',
  updatedAt: '2026-07-20T10:01:00Z',
  userMetadata: { sourceName: 'Notion support SOP' },
  ...overrides,
})

const task = (overrides: Partial<DocumentProcessingTask> = {}): DocumentProcessingTask => ({
  createdAt: '2026-07-20T10:00:00Z',
  documentId: 'document-1',
  documentRevision: 2,
  id: 'task-1',
  knowledgeSpaceId: 'space-1',
  progressPercent: 45,
  stage: 'parsed',
  state: 'running',
  updatedAt: '2026-07-20T10:01:00Z',
  ...overrides,
})

function streamFailedTaskThenWait(taskId: string) {
  let streamCount = 0
  streamProcessingTaskEvents.mockImplementation(async function* () {
    streamCount += 1
    if (streamCount === 1) {
      yield {
        data: {
          progressPercent: 80,
          stage: 'parsed' as const,
          state: 'failed' as const,
          updatedAt: '2026-07-20T10:03:00Z',
        },
        event: 'progress' as const,
        id: `${taskId}:2026-07-20T10:03:00Z`,
      }
      yield {
        data: { errorCode: 'PARSER_FAILED', state: 'failed' as const },
        event: 'terminal' as const,
        id: `${taskId}:terminal`,
      }
      return
    }
    await new Promise<void>(() => {})
  })
}

function notifyTaskQuerySuccess() {
  for (const listener of queryCacheListeners)
    listener({
      action: { type: 'success' },
      query: { queryHash: hashKey(['knowledge-fs', 'tasks']) },
      type: 'updated',
    })
}

const source = (overrides: Partial<Source> = {}): Source => ({
  createdAt: '2026-07-20T10:00:00Z',
  id: 'source-1',
  knowledgeSpaceId: 'space-1',
  metadata: {},
  name: 'Notion support SOP',
  status: 'active',
  type: 'connector',
  updatedAt: '2026-07-20T10:00:00Z',
  uri: 'notion://support',
  ...overrides,
})

describe('DocumentsPage', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    queryCacheListeners.clear()
    queryClient.cancelQueries.mockResolvedValue(undefined)
    queryClient.invalidateQueries.mockResolvedValue(undefined)
    documentsQuery.data = { pages: [{ items: [] }] }
    documentsQuery.error = null
    documentsQuery.hasNextPage = false
    documentsQuery.isFetchNextPageError = false
    documentsQuery.isFetching = false
    documentsQuery.isFetchingNextPage = false
    documentsQuery.isPending = false
    documentsQuery.isRefetching = false
    documentsQuery.refetch.mockResolvedValue({ error: null })
    tasksQuery.data = { pages: [{ items: [] }] }
    tasksQuery.dataUpdatedAt = 0
    tasksQuery.dataUpdateCount = 0
    tasksQuery.error = null
    tasksQuery.hasNextPage = false
    tasksQuery.isFetchNextPageError = false
    tasksQuery.isFetching = false
    tasksQuery.isFetchingNextPage = false
    tasksQuery.isPending = false
    tasksQuery.refetch.mockResolvedValue({ error: null })
    sourcesQuery.data = { pages: [{ items: [source()] }] }
    sourcesQuery.error = null
    sourcesQuery.hasNextPage = false
    sourcesQuery.isFetchNextPageError = false
    sourcesQuery.isFetching = false
    sourcesQuery.isFetchingNextPage = false
    sourcesQuery.isPending = false
    sourcesQuery.refetch.mockResolvedValue({ error: null })
    permissionStateMock.datasetKeys = ['dataset.acl.edit']
    permissionStateMock.error = null
    permissionStateMock.fetching = false
    permissionStateMock.loading = false
    permissionStateMock.retry.mockResolvedValue({
      data: {
        dataset: { default_permission_keys: ['dataset.acl.edit'] },
      },
      error: null,
    })
    permissionStateMock.refreshAfterDenial.mockResolvedValue({
      data: {
        dataset: { default_permission_keys: ['dataset.acl.edit'] },
      },
      error: null,
    })
    getTaskSnapshot.mockResolvedValue(task({ state: 'succeeded' }))
    cancelMutation.mutateAsync.mockResolvedValue(task({ state: 'canceled' }))
    retryMutation.mutateAsync.mockResolvedValue(task({ state: 'queued' }))
    reindexMutation.mutateAsync.mockResolvedValue({
      bulkJobId: 'reindex-1',
      items: [
        {
          asset: {
            createdAt: '2026-07-20T10:00:00Z',
            filename: 'one.pdf',
            id: 'asset-1',
            knowledgeSpaceId: 'space-1',
            mimeType: 'application/pdf',
            objectKey: 'documents/one.pdf',
            parserStatus: 'pending',
            sha256: 'sha',
            sizeBytes: 1024,
            version: 1,
          },
          compilationJob: { id: 'job-1', stage: 'queued' },
          status: 'queued',
          statusUrl: '/tasks/job-1',
        },
      ],
      total: 1,
    })
    uploadMutation.mutateAsync.mockResolvedValue({
      asset: {
        createdAt: '2026-07-20T10:00:00Z',
        filename: 'one.md',
        id: 'asset-1',
        knowledgeSpaceId: 'space-1',
        mimeType: 'text/markdown',
        objectKey: 'documents/one.md',
        parserStatus: 'pending',
        sha256: 'sha',
        sizeBytes: 3,
        version: 1,
      },
      compilationJob: { id: 'job-1', stage: 'queued' },
      documentRevision: 1,
      logicalDocument: { id: 'document-1', revision: 1 },
      logicalDocumentId: 'document-1',
      statusUrl: '/tasks/job-1',
    })
    bulkUploadMutation.mutateAsync.mockResolvedValue({
      accepted: 2,
      bulkJobId: 'upload-1',
      excluded: 0,
      items: [],
      total: 2,
    })
    streamProcessingTaskEvents.mockImplementation(async function* () {})
  })

  it('loads real logical documents and processing tasks through generated contracts', () => {
    documentsQuery.isPending = true
    tasksQuery.isPending = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const documentOptions = documentsInfiniteOptions.mock.lastCall?.[0]
    const taskOptions = tasksInfiniteOptions.mock.lastCall?.[0]
    const sourceOptions = sourcesInfiniteOptions.mock.lastCall?.[0]
    expect(documentOptions?.input(null)).toEqual({
      params: { id: 'space-1' },
      query: { limit: 50 },
    })
    expect(documentOptions?.input('next')).toEqual({
      params: { id: 'space-1' },
      query: { cursor: 'next', limit: 50 },
    })
    expect(documentOptions?.getNextPageParam({ nextCursor: 'next' })).toBe('next')
    expect(taskOptions?.input(null)).toEqual({
      params: { id: 'space-1' },
      query: { limit: 100 },
    })
    expect(taskOptions?.getNextPageParam({ nextCursor: 'next' })).toBe('next')
    expect(sourceOptions?.input(null)).toEqual({
      params: { id: 'space-1' },
      query: { limit: 100 },
    })
    expect(sourceOptions?.getNextPageParam({ nextCursor: 'next' })).toBe('next')
    expect(screen.getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument()
  })

  it('restores document search and status filters from the URL', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [
        {
          items: [
            document({ id: 'ready-document', title: 'Ready handbook.pdf' }),
            document({ id: 'failed-document', title: 'Failed report.pdf' }),
          ],
        },
      ],
    }
    tasksQuery.data = {
      pages: [
        {
          items: [task({ documentId: 'failed-document', id: 'failed-task', state: 'failed' })],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />, {
      searchParams: '?query=report&status=failed',
    })

    const metadata = screen.getByRole('button', { name: 'dataset.newKnowledge.metadata' })
    expect(metadata).toBeEnabled()
    await user.click(metadata)
    expect(toastMock.info).toHaveBeenCalledWith('dataset.newKnowledge.filtersUnavailable')
    const rowActions = screen.getByRole('button', {
      name: /dataset\.newKnowledge\.documentActions/,
    })
    expect(rowActions).toBeEnabled()
    await user.click(rowActions)
    await user.click(
      await screen.findByRole('menuitem', { name: 'dataset.newKnowledge.downloadDocuments' }),
    )
    expect(toastMock.info).toHaveBeenCalledWith('dataset.newKnowledge.documentActionsUnavailable')

    expect(screen.getByRole('searchbox')).toHaveValue('report')
    expect(screen.getByRole('combobox')).toHaveValue('failed')
    expect(screen.getByText('Failed report.pdf')).toBeInTheDocument()
    expect(screen.queryByText('Ready handbook.pdf')).not.toBeInTheDocument()
  })

  it('renders revisions, sources, stable cursor-ordered rows, and every designed status', () => {
    documentsQuery.data = {
      pages: [
        {
          items: [document({ id: 'ready', title: 'Ready.pdf', userMetadata: {} })],
        },
        {
          items: [
            document({
              active: null,
              activeRevision: undefined,
              id: 'queued',
              status: 'pending',
              title: 'Queued.docx',
              updatedAt: '2026-07-20T10:05:00Z',
            }),
            document({ id: 'failed', status: 'failed', title: 'Failed.html' }),
            document({
              id: 'disabled',
              sourceId: 'disabled-source',
              title: 'Disabled.xlsx',
              userMetadata: { sourceName: 'Archive' },
            }),
            document({ id: 'processing', status: 'pending', title: 'Processing.md' }),
          ],
        },
      ],
    }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({ documentId: 'processing', id: 'processing-task' }),
            task({ documentId: 'queued', id: 'queued-task', state: 'queued' }),
          ],
        },
      ],
    }
    sourcesQuery.data = {
      pages: [
        {
          items: [source(), source({ id: 'disabled-source', name: 'Archive', status: 'disabled' })],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const rows = screen.getAllByRole('row').slice(1)
    expect(within(rows[0]!).getByText('Ready.pdf')).toBeInTheDocument()
    expect(screen.getByRole('link', { name: 'Ready.pdf' })).toHaveAttribute(
      'href',
      '/datasets/new/space-1/documents/ready',
    )
    expect(screen.getAllByText('Notion support SOP').length).toBeGreaterThan(0)
    expect(screen.getAllByText('v2').length).toBeGreaterThan(0)
    for (const status of ['ready', 'queued', 'processing', 'failed', 'disabled'])
      expect(
        screen.getAllByText(`dataset.newKnowledge.documentStatus.${status}`).length,
      ).toBeGreaterThan(0)
    expect(screen.getByText('dataset.newKnowledge.lastReadyRevisionHint')).toBeInTheDocument()
  })

  it('treats a document with an unresolved source as unavailable for writes', () => {
    documentsQuery.data = {
      pages: [{ items: [document({ sourceId: 'missing-source', title: 'Orphaned.pdf' })] }],
    }
    sourcesQuery.data = { pages: [{ items: [] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getAllByText('dataset.newKnowledge.documentStatus.disabled')).toHaveLength(2)
    expect(screen.getByRole('checkbox', { name: 'Orphaned.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
  })

  it('renders the designed empty state with an available upload action', () => {
    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const emptyState = screen.getByText('dataset.newKnowledge.documentsEmptyTitle').parentElement
    expect(emptyState).not.toBeNull()
    expect(screen.getByText('dataset.newKnowledge.documentsEmptyDescription')).toBeInTheDocument()
    expect(screen.getByText('dataset.newKnowledge.documentsDropHint')).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' })).toBeEnabled()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.tasks' }),
    ).not.toBeInTheDocument()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.metadata' }),
    ).not.toBeInTheDocument()
    const dataTransfer = { dropEffect: 'none' }
    const dragOver = new Event('dragover', { bubbles: true, cancelable: true })
    Object.defineProperty(dragOver, 'dataTransfer', { value: dataTransfer })
    expect(fireEvent(emptyState!, dragOver)).toBe(false)
    expect(dataTransfer.dropEffect).toBe('copy')
  })

  it('removes the empty-state drop affordance when uploads are unavailable', () => {
    permissionStateMock.datasetKeys = ['dataset.acl.readonly']

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const emptyState = screen.getByText('dataset.newKnowledge.documentsEmptyTitle').parentElement
    expect(emptyState).not.toBeNull()
    expect(screen.queryByText('dataset.newKnowledge.documentsDropHint')).not.toBeInTheDocument()
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' }),
    ).toHaveAttribute('aria-describedby', 'documents-readonly-reason')
    const dataTransfer = {
      dropEffect: 'copy',
      files: [new File(['one'], 'one.md', { type: 'text/markdown' })],
    }
    const dragOver = new Event('dragover', { bubbles: true, cancelable: true })
    Object.defineProperty(dragOver, 'dataTransfer', { value: dataTransfer })
    expect(fireEvent(emptyState!, dragOver)).toBe(false)
    expect(dataTransfer.dropEffect).toBe('none')
    expect(
      fireEvent.drop(emptyState!, {
        dataTransfer,
      }),
    ).toBe(false)
    expect(uploadMutation.mutateAsync).not.toHaveBeenCalled()
  })

  it('keeps processing tasks reachable while the document list is empty', async () => {
    const user = userEvent.setup()
    tasksQuery.data = { pages: [{ items: [task({ id: 'orphaned-running-task' })] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(screen.getByRole('dialog')).toBeInTheDocument()
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
  })

  it('keeps every write action unavailable for read-only users', async () => {
    const user = userEvent.setup()
    permissionStateMock.datasetKeys = ['dataset.acl.readonly']
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByText('dataset.newKnowledge.documentPermissionRestricted')).toBeVisible()
    expect(screen.queryByLabelText('dataset.newKnowledge.uploadDocuments')).not.toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' })).toBeDisabled()
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-describedby',
      'documents-readonly-reason',
    )
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).not.toBeInTheDocument()
    expect(
      within(screen.getByRole('dialog')).getByText(
        'dataset.newKnowledge.documentPermissionRestricted',
      ),
    ).toBeInTheDocument()
  })

  it('keeps permission lookup failures distinct from read-only access and retries them', async () => {
    const user = userEvent.setup()
    permissionStateMock.error = new Error('permission service unavailable')
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('alert')).toHaveTextContent('dataset.newKnowledge.permissionLoadFailed')
    expect(
      screen.queryByText('dataset.newKnowledge.documentPermissionRestricted'),
    ).not.toBeInTheDocument()
    const addDocument = screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' })
    expect(addDocument).toBeDisabled()
    expect(addDocument).toHaveAccessibleDescription('dataset.newKnowledge.permissionLoadFailed')
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(within(screen.getByRole('dialog')).getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.permissionLoadFailed',
    )
    await user.click(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.permissionLoadFailed',
      }),
    )
    expect(permissionStateMock.retry).toHaveBeenCalledOnce()
  })

  it('explains pending permission checks inside the task drawer', async () => {
    const user = userEvent.setup()
    permissionStateMock.loading = true
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const pendingPermission = screen.getByText('dataset.newKnowledge.permission · common.loading')
    expect(pendingPermission).toHaveTextContent('dataset.newKnowledge.permission · common.loading')
    expect(pendingPermission).toHaveAttribute('role', 'status')
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' }),
    ).toHaveAttribute('aria-describedby', 'documents-permission-pending')
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-describedby',
      'documents-permission-pending',
    )
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )

    expect(
      within(screen.getByRole('dialog')).getByText(
        'dataset.newKnowledge.permission · common.loading',
      ),
    ).toBeInTheDocument()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).not.toBeInTheDocument()
  })

  it('uploads one or multiple files through the pinned document contracts', async () => {
    const user = userEvent.setup()
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const input = screen.getByLabelText('dataset.newKnowledge.uploadDocuments')
    expect(input).toHaveAttribute('hidden')
    expect(input).toHaveAttribute('tabindex', '-1')
    expect(input).toHaveAttribute(
      'accept',
      '.csv,.doc,.docx,.epub,.htm,.html,.json,.jsonl,.md,.pdf,.ppt,.pptx,.rtf,.text,.txt,.xls,.xlsx',
    )

    await user.upload(input, new File(['one'], 'one.md', { type: 'text/markdown' }))
    expect(uploadMutation.mutateAsync).toHaveBeenCalledWith({
      body: { file: expect.any(File) },
      params: { id: 'space-1' },
    })

    await user.upload(input, [
      new File(['two'], 'two.md', { type: 'text/markdown' }),
      new File(['three'], 'three.txt', { type: 'text/plain' }),
    ])
    expect(bulkUploadMutation.mutateAsync).toHaveBeenCalledWith({
      body: { files: [expect.any(File), expect.any(File)] },
      params: { id: 'space-1' },
    })
    expect(queryClient.invalidateQueries).toHaveBeenCalled()
    const documentInvalidation = queryClient.invalidateQueries.mock.calls.find(
      ([options]) => options.queryKey[1] === 'documents',
    )?.[0]
    expect(
      documentInvalidation?.predicate({
        queryKey: [
          ['console', 'knowledgeFs', 'getKnowledgeSpacesByIdLogicalDocuments'],
          { input: { params: { id: 'space-1' } }, type: 'infinite' },
        ],
      }),
    ).toBe(true)
    expect(
      documentInvalidation?.predicate({
        queryKey: [
          ['console', 'knowledgeFs', 'getKnowledgeSpacesByIdLogicalDocuments'],
          { input: { params: { id: 'space-2' } }, type: 'infinite' },
        ],
      }),
    ).toBe(false)
  })

  it('excludes unsupported files locally while uploading valid files', async () => {
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const validFile = new File(['one'], 'one.md', { type: 'text/markdown' })
    const unsupportedFile = new File(['two'], 'two.exe', {
      type: 'application/octet-stream',
    })

    fireEvent.change(screen.getByLabelText('dataset.newKnowledge.uploadDocuments'), {
      target: { files: [validFile, unsupportedFile] },
    })

    await waitFor(() =>
      expect(uploadMutation.mutateAsync).toHaveBeenCalledWith({
        body: { file: validFile },
        params: { id: 'space-1' },
      }),
    )
    expect(bulkUploadMutation.mutateAsync).not.toHaveBeenCalled()
    expect(toastMock.warning).toHaveBeenCalledWith(
      'dataset.newKnowledge.documentUploadPartial:{"accepted":1,"details":"two.exe (dataset.newKnowledge.documentUploadExclusion.fileType)","excluded":1}',
    )
  })

  it('rejects oversized files before invoking an upload contract', async () => {
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const oversizedFile = new File(['one'], 'oversized.pdf', { type: 'application/pdf' })
    Object.defineProperty(oversizedFile, 'size', { value: 16 * 1024 * 1024 })

    fireEvent.change(screen.getByLabelText('dataset.newKnowledge.uploadDocuments'), {
      target: { files: [oversizedFile] },
    })

    expect(uploadMutation.mutateAsync).not.toHaveBeenCalled()
    expect(bulkUploadMutation.mutateAsync).not.toHaveBeenCalled()
    expect(toastMock.error).toHaveBeenCalledWith(
      'dataset.newKnowledge.documentUploadRejected:{"details":"oversized.pdf (dataset.newKnowledge.documentUploadExclusion.fileSize)"}',
    )
  })

  it('reports partial and fully excluded bulk uploads from the contract result', async () => {
    const user = userEvent.setup()
    bulkUploadMutation.mutateAsync
      .mockResolvedValueOnce({
        accepted: 1,
        bulkJobId: 'upload-partial',
        excluded: 1,
        items: [
          {
            filename: 'too-large.pdf',
            index: 1,
            mimeType: 'application/pdf',
            reason: 'file_too_large',
            sizeBytes: 10_000,
            status: 'excluded',
          },
        ],
        total: 2,
      })
      .mockResolvedValueOnce({
        accepted: 0,
        bulkJobId: 'upload-rejected',
        excluded: 2,
        items: [
          {
            filename: 'one.md',
            index: 0,
            mimeType: 'text/markdown',
            reason: 'quota_exceeded',
            sizeBytes: 3,
            status: 'excluded',
          },
          {
            filename: 'two.md',
            index: 1,
            mimeType: 'text/markdown',
            reason: 'quota_exceeded',
            sizeBytes: 3,
            status: 'excluded',
          },
        ],
        total: 2,
      })
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const input = screen.getByLabelText('dataset.newKnowledge.uploadDocuments')

    await user.upload(input, [
      new File(['one'], 'one.md', { type: 'text/markdown' }),
      new File(['large'], 'too-large.pdf', { type: 'application/pdf' }),
    ])
    expect(toastMock.warning).toHaveBeenCalledWith(
      'dataset.newKnowledge.documentUploadPartial:{"accepted":1,"details":"too-large.pdf (dataset.newKnowledge.documentUploadExclusion.fileSize)","excluded":1}',
    )

    queryClient.invalidateQueries.mockClear()
    await user.upload(input, [
      new File(['one'], 'one.md', { type: 'text/markdown' }),
      new File(['two'], 'two.md', { type: 'text/markdown' }),
    ])
    expect(toastMock.error).toHaveBeenCalledWith(
      'dataset.newKnowledge.documentUploadRejected:{"details":"one.md (dataset.newKnowledge.documentUploadExclusion.quota); two.md (dataset.newKnowledge.documentUploadExclusion.quota)"}',
    )
    expect(queryClient.invalidateQueries).not.toHaveBeenCalled()
  })

  it('distinguishes a permission failure and exposes retry for other load errors', async () => {
    const user = userEvent.setup()
    documentsQuery.data = undefined
    documentsQuery.error = { status: 403 }

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(screen.getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.documentsPermissionDescription',
    )
    expect(screen.queryByRole('button', { name: 'common.operation.retry' })).not.toBeInTheDocument()

    documentsQuery.error = new Error('temporary')
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
      }),
    )
    expect(documentsQuery.refetch).toHaveBeenCalledOnce()
  })

  it('reports a failed background document refresh while preserving cached rows', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' }))

    documentsQuery.error = new Error('background refresh failed')
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByText('sso-enterprise.pdf')).toBeInTheDocument()
    expect(screen.getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.documentsErrorDescription',
    )
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    const actions = screen.getByRole('group', {
      name: 'dataset.newKnowledge.bulkDocumentActions',
    })
    expect(
      within(actions).getByRole('button', {
        name: 'dataset.newKnowledge.reindexDocuments',
      }),
    ).toHaveAttribute('aria-describedby', 'document-reindex-unavailable')
    expect(
      within(actions).getByText(
        'dataset.newKnowledge.reindexDocuments · dataset.newKnowledge.documentsErrorDescription',
      ),
    ).toBeVisible()
    await user.click(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
      }),
    )
    expect(documentsQuery.refetch).toHaveBeenCalledOnce()
  })

  it('blocks cached document content when a background refresh loses permission', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.error = { status: 403 }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.documentsPermissionDescription',
    )
    expect(screen.queryByText('sso-enterprise.pdf')).not.toBeInTheDocument()
    expect(screen.queryByLabelText('dataset.newKnowledge.uploadDocuments')).not.toBeInTheDocument()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.tasks' }),
    ).not.toBeInTheDocument()
  })

  it('keeps cached refresh retries busy while their queries are fetching', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.error = new Error('document refresh failed')
    documentsQuery.isRefetching = true
    tasksQuery.error = new Error('task refresh failed')
    tasksQuery.isFetching = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
      }),
    ).toHaveAttribute('aria-disabled', 'true')
    expect(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
      }),
    ).toHaveAttribute('aria-disabled', 'true')
  })

  it('keeps permission retries busy while permission keys are refetching', () => {
    permissionStateMock.error = new Error('permission refresh failed')
    permissionStateMock.fetching = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.permissionLoadFailed',
      }),
    ).toHaveAttribute('aria-disabled', 'true')
  })

  it('does not disable a failed dependency retry for an unrelated background refresh', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.error = new Error('task refresh failed')
    sourcesQuery.isFetching = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
      }),
    ).not.toHaveAttribute('aria-disabled')
  })

  it('keeps a blocking dependency retry stable while its first page refetches', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = undefined
    tasksQuery.error = new Error('task first page failed')
    sourcesQuery.error = new Error('source background refresh failed')
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const retry = screen.getByRole('button', {
      name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
    })

    await user.click(retry)
    expect(tasksQuery.refetch).toHaveBeenCalledOnce()
    tasksQuery.error = null
    tasksQuery.isFetching = true
    tasksQuery.isPending = true
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(retry).toBeInTheDocument()
    expect(retry).toHaveFocus()
    expect(retry).toHaveAttribute('aria-disabled', 'true')

    tasksQuery.data = { pages: [{ items: [] }] }
    tasksQuery.isFetching = false
    tasksQuery.isPending = false
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(screen.getByText('sso-enterprise.pdf')).toBeInTheDocument()
    await waitFor(() =>
      expect(
        screen.getByRole('button', {
          name: 'common.operation.retry · dataset.newKnowledge.sourcesErrorDescription',
        }),
      ).toHaveFocus(),
    )

    sourcesQuery.error = null
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: 'dataset.newKnowledge.documents' })).toHaveFocus(),
    )
  })

  it('closes a task drawer permanently and restores focus when document permission is revoked', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(screen.getByRole('dialog')).toBeInTheDocument()

    documentsQuery.error = { status: 403 }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
    const permissionAlert = screen.getByRole('alert')
    expect(permissionAlert).toHaveFocus()
    expect(tasksInfiniteOptions.mock.lastCall?.[0].enabled).toBe(false)
    expect(sourcesInfiniteOptions.mock.lastCall?.[0].enabled).toBe(false)
    expect(queryClient.cancelQueries).toHaveBeenCalledWith({
      predicate: expect.any(Function),
      queryKey: ['knowledge-fs', 'tasks'],
    })
    expect(queryClient.cancelQueries).toHaveBeenCalledWith({
      predicate: expect.any(Function),
      queryKey: ['knowledge-fs', 'sources'],
    })
    const taskCancellation = queryClient.cancelQueries.mock.calls.find(
      ([options]) => options.queryKey[1] === 'tasks',
    )?.[0]
    expect(
      taskCancellation?.predicate({
        queryKey: [
          ['console', 'knowledgeFs', 'getKnowledgeSpacesByIdProcessingTasks'],
          { input: { params: { id: 'space-1' } }, type: 'infinite' },
        ],
      }),
    ).toBe(true)
    expect(
      taskCancellation?.predicate({
        queryKey: [
          ['console', 'knowledgeFs', 'getKnowledgeSpacesByIdProcessingTasks'],
          { input: { params: { id: 'space-2' } }, type: 'infinite' },
        ],
      }),
    ).toBe(false)

    documentsQuery.error = null
    tasksQuery.refetch.mockClear()
    sourcesQuery.refetch.mockClear()
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
    expect(tasksQuery.refetch).toHaveBeenCalledWith({ cancelRefetch: false })
    expect(sourcesQuery.refetch).toHaveBeenCalledWith({ cancelRefetch: false })
  })

  it('does not repeat the child query that already recovered from permission denial', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [] }] }
    tasksQuery.error = { status: 403 }
    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)

    tasksQuery.error = null
    tasksQuery.refetch.mockClear()
    sourcesQuery.refetch.mockClear()
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(tasksQuery.refetch).not.toHaveBeenCalled()
    expect(sourcesQuery.refetch).toHaveBeenCalledWith({ cancelRefetch: false })
  })

  it('moves focus from document controls to the permission alert after dynamic revocation', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const searchbox = screen.getByRole('searchbox', {
      name: 'dataset.newKnowledge.searchDocuments',
    })
    await user.click(searchbox)
    expect(searchbox).toHaveFocus()

    documentsQuery.error = { status: 403 }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(screen.getByRole('alert')).toHaveFocus())

    documentsQuery.error = null
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: 'dataset.newKnowledge.documents' })).toHaveFocus(),
    )
  })

  it('moves focus from bulk actions to the permission alert after dynamic revocation', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' }))
    const reindex = within(
      screen.getByRole('group', { name: 'dataset.newKnowledge.bulkDocumentActions' }),
    ).getByRole('button', { name: 'dataset.newKnowledge.reindexDocuments' })
    act(() => reindex.focus())

    documentsQuery.error = { status: 403 }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(screen.getByRole('alert')).toHaveFocus())
  })

  it('stops background pagination and failed-task polling while document permission is denied', async () => {
    vi.useFakeTimers()
    documentsQuery.data = {
      pages: [{ items: [document({ sourceId: 'unresolved-source' })], nextCursor: 'next' }],
    }
    documentsQuery.error = { status: 403 }
    documentsQuery.hasNextPage = true
    tasksQuery.data = {
      pages: [{ items: [task({ state: 'failed' })], nextCursor: 'task-next' }],
    }
    tasksQuery.hasNextPage = true
    sourcesQuery.data = { pages: [{ items: [], nextCursor: 'source-next' }] }
    sourcesQuery.hasNextPage = true

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />, {
      searchParams: '?query=sso',
    })
    try {
      await act(async () => vi.advanceTimersByTime(10_000))

      expect(documentsQuery.fetchNextPage).not.toHaveBeenCalled()
      expect(tasksQuery.fetchNextPage).not.toHaveBeenCalled()
      expect(sourcesQuery.fetchNextPage).not.toHaveBeenCalled()
      expect(getTaskSnapshot).not.toHaveBeenCalled()
      expect(streamProcessingTaskEvents).not.toHaveBeenCalled()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('filters and searches documents while continuing through cursor pages', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document({ title: 'Product handbook.pdf' })], nextCursor: 'next' }],
    }
    documentsQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.type(
      screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchDocuments' }),
      'later page',
    )

    expect(documentsQuery.fetchNextPage).toHaveBeenCalledOnce()
    expect(screen.queryByText('dataset.newKnowledge.noMatchingDocuments')).not.toBeInTheDocument()
    expect(screen.getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument()
    expect(screen.getByText('dataset.newKnowledge.partialDocumentResults')).toBeInTheDocument()
  })

  it('blocks selection until filtered cursor pages are complete', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document({ title: 'Product handbook.pdf' })], nextCursor: 'next' }],
    }
    documentsQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.type(
      screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchDocuments' }),
      'product',
    )

    expect(screen.getByRole('checkbox', { name: 'Product handbook.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    expect(
      screen.getByRole('checkbox', { name: 'dataset.newKnowledge.selectAllDocuments' }),
    ).toHaveAttribute('aria-disabled', 'true')
  })

  it('surfaces dependent source pagination failures and retries the failed page', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    sourcesQuery.isFetchNextPageError = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.sourcesErrorDescription',
    )
    await user.click(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.sourcesErrorDescription',
      }),
    )
    expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
    const documentRow = screen.getByRole('row', { name: /sso-enterprise\.pdf/ })
    expect(
      within(documentRow).getByText('dataset.newKnowledge.documentStatus.ready'),
    ).toBeInTheDocument()
    expect(screen.getByRole('combobox')).toBeEnabled()
  })

  it('retries a failed task cursor page from the task drawer', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.isFetchNextPageError = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }))
    await user.click(
      within(screen.getByRole('dialog')).getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
      }),
    )

    expect(tasksQuery.fetchNextPage).toHaveBeenCalledOnce()
    expect(tasksQuery.refetch).not.toHaveBeenCalled()
  })

  it('returns focus to the drawer close button after a query retry succeeds', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.error = new Error('task refresh failed')
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }))
    const panel = screen.getByRole('dialog')
    await user.click(
      within(panel).getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
      }),
    )

    tasksQuery.error = null
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() =>
      expect(within(panel).getByRole('button', { name: 'common.operation.close' })).toHaveFocus(),
    )
  })

  it('moves focus between drawer query retries as each error recovers', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document()], nextCursor: 'document-next' }],
    }
    documentsQuery.hasNextPage = true
    documentsQuery.isFetchNextPageError = true
    tasksQuery.data = {
      pages: [{ items: [task({ documentId: 'missing-document' })] }],
    }
    tasksQuery.error = new Error('task refresh failed')
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    const panel = screen.getByRole('dialog')
    await user.click(
      within(panel).getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
      }),
    )

    tasksQuery.error = null
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    const documentRetry = within(panel).getByRole('button', {
      name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
    })
    await waitFor(() => expect(documentRetry).toHaveFocus())
    await user.click(documentRetry)

    documentsQuery.isFetchNextPageError = false
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(within(panel).getByRole('button', { name: 'common.operation.close' })).toHaveFocus(),
    )
  })

  it('continues remote task pagination from an empty cached drawer after the automatic cap', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: Array.from({ length: 20 }, () => ({
        items: [],
        nextCursor: 'next',
      })),
    }
    tasksQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const taskTrigger = screen.getByRole('button', {
      name: 'dataset.newKnowledge.tasksWithAttention:{"count":0} · dataset.newKnowledge.taskHistoryIncomplete',
    })
    expect(taskTrigger).toHaveTextContent('0+')
    await user.click(taskTrigger)
    expect(
      within(screen.getByRole('dialog')).queryByText('dataset.newKnowledge.noBackgroundTasks'),
    ).not.toBeInTheDocument()
    await user.click(
      within(screen.getByRole('dialog')).getByRole('button', {
        name: 'dataset.newKnowledge.loadMore',
      }),
    )

    expect(tasksQuery.fetchNextPage).toHaveBeenCalledOnce()
  })

  it('loads document titles for tasks whose documents are on a later cursor page', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: Array.from({ length: 20 }, (_, index) => ({
        items: index === 0 ? [document()] : [],
        nextCursor: 'next',
      })),
    }
    documentsQuery.hasNextPage = true
    tasksQuery.data = {
      pages: [
        {
          items: [task({ documentId: 'later-document', id: 'later-task', state: 'succeeded' })],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }))

    const panel = screen.getByRole('dialog')
    expect(within(panel).queryByText('later-document')).not.toBeInTheDocument()
    expect(
      within(panel).getByText(
        'dataset.newKnowledge.processDocument:{"name":"dataset.newKnowledge.documentColumn"}',
      ),
    ).toBeInTheDocument()
    await user.click(within(panel).getByRole('button', { name: 'dataset.newKnowledge.loadMore' }))
    expect(documentsQuery.fetchNextPage).toHaveBeenCalledOnce()
  })

  it('defers task title pagination until the task drawer is opened', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document()], nextCursor: 'next' }],
    }
    documentsQuery.hasNextPage = true
    tasksQuery.data = {
      pages: [
        {
          items: [task({ documentId: 'later-document', id: 'later-task', state: 'succeeded' })],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(documentsQuery.fetchNextPage).not.toHaveBeenCalled()

    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }))
    await waitFor(() => expect(documentsQuery.fetchNextPage).toHaveBeenCalledOnce())
  })

  it('reports and retries task title pagination failures inside the drawer', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document()], nextCursor: 'next' }],
    }
    documentsQuery.hasNextPage = true
    documentsQuery.isFetchNextPageError = true
    tasksQuery.data = {
      pages: [
        {
          items: [task({ documentId: 'later-document', id: 'later-task', state: 'succeeded' })],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }))

    const panel = screen.getByRole('dialog')
    expect(within(panel).getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.documentsErrorDescription',
    )
    await user.click(
      within(panel).getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
      }),
    )
    expect(documentsQuery.fetchNextPage).toHaveBeenCalledOnce()
  })

  it('keeps an unresolved source pending when a background source refresh fails', () => {
    documentsQuery.data = {
      pages: [{ items: [document({ sourceId: 'unresolved-source' })] }],
    }
    sourcesQuery.error = new Error('source refresh failed')

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const documentRow = screen.getByRole('row', { name: /sso-enterprise\.pdf/ })
    expect(
      within(documentRow).queryByText('dataset.newKnowledge.documentStatus.disabled'),
    ).not.toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
  })

  it('keeps loaded task actions available when background task pagination fails', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }
    tasksQuery.error = new Error('poll failed')
    tasksQuery.isFetchingNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByText('sso-enterprise.pdf')).toBeInTheDocument()
    expect(screen.getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.tasksErrorDescription',
    )
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    await user.click(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
      }),
    )
    expect(tasksQuery.refetch).toHaveBeenCalledOnce()
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
  })

  it('reports and retries cached task refresh failures from the document empty state', async () => {
    const user = userEvent.setup()
    tasksQuery.error = new Error('task refresh failed')

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.tasksErrorDescription',
    )
    await user.click(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.tasksErrorDescription',
      }),
    )
    expect(tasksQuery.refetch).toHaveBeenCalledOnce()
  })

  it('renders document identity while dependency cursor pages continue loading', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [], nextCursor: 'task-next' }] }
    tasksQuery.hasNextPage = true
    sourcesQuery.data = { pages: [{ items: [], nextCursor: 'source-next' }] }
    sourcesQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByText('sso-enterprise.pdf')).toBeInTheDocument()
    expect(screen.getByRole('combobox')).toBeDisabled()
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    expect(tasksQuery.fetchNextPage).toHaveBeenCalledOnce()
    expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
  })

  it('keeps document status and selection pending until the first task page arrives', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = undefined
    tasksQuery.isPending = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const documentRow = screen.getByRole('row', { name: /sso-enterprise\.pdf/ })
    expect(
      within(documentRow).queryByText('dataset.newKnowledge.documentStatus.ready'),
    ).not.toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    expect(screen.getByRole('region', { name: 'dataset.newKnowledge.documents' })).toHaveAttribute(
      'aria-busy',
      'true',
    )
  })

  it('keeps document state pending while the next task cursor page is loading', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [], nextCursor: 'next' }] }
    tasksQuery.hasNextPage = true
    tasksQuery.isFetchingNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const documentRow = screen.getByRole('row', { name: /sso-enterprise\.pdf/ })
    expect(
      within(documentRow).queryByText('dataset.newKnowledge.documentStatus.ready'),
    ).not.toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
  })

  it('keeps selection disabled after a filtered document page fails', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document({ title: 'Product handbook.pdf' })], nextCursor: 'next' }],
    }
    documentsQuery.hasNextPage = true
    documentsQuery.isFetchNextPageError = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.type(
      screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchDocuments' }),
      'product',
    )

    expect(screen.getByRole('checkbox', { name: 'Product handbook.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    expect(
      screen.getByRole('checkbox', { name: 'dataset.newKnowledge.selectAllDocuments' }),
    ).toHaveAttribute('aria-disabled', 'true')
    await user.click(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
      }),
    )
    expect(documentsQuery.fetchNextPage).toHaveBeenCalledOnce()
  })

  it('keeps a failed document page retry busy while the next page is fetching', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document()], nextCursor: 'next' }],
    }
    documentsQuery.hasNextPage = true
    documentsQuery.isFetchNextPageError = true
    documentsQuery.isFetchingNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.type(
      screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchDocuments' }),
      'sso',
    )

    expect(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
      }),
    ).toHaveAttribute('aria-disabled', 'true')
  })

  it('does not disable a document page retry for unrelated dependency pagination', () => {
    documentsQuery.data = { pages: [{ items: [document()], nextCursor: 'next' }] }
    documentsQuery.hasNextPage = true
    documentsQuery.isFetchNextPageError = true
    tasksQuery.data = {
      pages: Array.from({ length: 20 }, () => ({ items: [], nextCursor: 'task-next' })),
    }
    tasksQuery.hasNextPage = true
    tasksQuery.isFetchingNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(
      screen.getByRole('button', {
        name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
      }),
    ).not.toHaveAttribute('aria-disabled')
  })

  it('moves pagination focus from load more to retry and then to final results', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()], nextCursor: 'next' }] }
    documentsQuery.hasNextPage = true
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.loadMore' }))

    documentsQuery.isFetchNextPageError = true
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    const retry = screen.getByRole('button', {
      name: 'common.operation.retry · dataset.newKnowledge.documentsErrorDescription',
    })
    await waitFor(() => expect(retry).toHaveFocus())
    await user.click(retry)

    documentsQuery.data = {
      pages: [
        { items: [document()] },
        { items: [document({ id: 'last-document', title: 'Last.pdf' })] },
      ],
    }
    documentsQuery.hasNextPage = false
    documentsQuery.isFetchNextPageError = false
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(screen.getByRole('table').parentElement).toHaveFocus())
  })

  it('re-indexes selected documents and disables backend-dependent actions', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [{ items: [document({ id: 'one', title: 'One.pdf' }), document({ id: 'two' })] }],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'One.pdf' }))

    const actions = screen.getByRole('group', {
      name: 'dataset.newKnowledge.bulkDocumentActions',
    })
    expect(
      screen.getByRole('heading', { name: 'dataset.newKnowledge.documents' }).closest('section'),
    ).toHaveClass('pb-[calc(7rem+env(safe-area-inset-bottom,0px))]')
    expect(
      within(actions).getByText('dataset.newKnowledge.documentsSelected:{"count":1}'),
    ).toBeInTheDocument()
    const reindex = within(actions).getByRole('button', {
      name: 'dataset.newKnowledge.reindexDocuments',
    })
    expect(reindex).toBeEnabled()
    const orderedActions = within(actions).getAllByRole('button')
    expect(orderedActions[0]).toHaveAccessibleName('dataset.newKnowledge.reindexDocuments')
    expect(orderedActions[1]).toHaveAccessibleName('dataset.newKnowledge.clearDocumentSelection')
    expect(actions.firstElementChild).toBe(orderedActions[0])
    expect(orderedActions[1]!.nextElementSibling).toHaveTextContent(
      'dataset.newKnowledge.documentsSelected:{"count":1}',
    )
    expect(
      within(actions).getByText(
        'dataset.newKnowledge.downloadDocuments / dataset.newKnowledge.deleteDocuments · dataset.cornerLabel.unavailable',
      ),
    ).toBeVisible()
    const download = within(actions).getByRole('button', {
      name: 'dataset.newKnowledge.downloadDocuments',
    })
    const remove = within(actions).getByRole('button', {
      name: 'dataset.newKnowledge.deleteDocuments',
    })
    expect(download).toBeDisabled()
    expect(remove).toBeDisabled()
    expect(toastMock.info).not.toHaveBeenCalled()
    await user.dblClick(reindex)
    expect(reindexMutation.mutateAsync).toHaveBeenCalledOnce()
    expect(reindexMutation.mutateAsync).toHaveBeenCalledWith({
      body: { documentIds: ['one'] },
      params: { id: 'space-1' },
    })
    await waitFor(() =>
      expect(screen.getByRole('heading', { name: 'dataset.newKnowledge.documents' })).toHaveFocus(),
    )
  })

  it('explains why re-indexing becomes unavailable while results refresh', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' }))

    tasksQuery.data = undefined
    tasksQuery.isPending = true
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    const actions = screen.getByRole('group', {
      name: 'dataset.newKnowledge.bulkDocumentActions',
    })
    expect(
      within(actions).getByRole('button', { name: 'dataset.newKnowledge.reindexDocuments' }),
    ).toHaveAttribute('aria-describedby', 'document-reindex-unavailable')
    expect(
      within(actions).getByText('dataset.newKnowledge.reindexDocuments · common.loading'),
    ).toBeVisible()

    tasksQuery.data = { pages: [{ items: [] }] }
    tasksQuery.isPending = false
    sourcesQuery.data = { pages: [{ items: [] }] }
    sourcesQuery.error = new Error('source refresh failed')
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(
      within(actions).getByText(
        'dataset.newKnowledge.reindexDocuments · dataset.newKnowledge.sourcesErrorDescription',
      ),
    ).toBeVisible()
  })

  it('keeps missing documents selected after a partial bulk re-index result', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [
        {
          items: [
            document({ id: 'one', title: 'One.pdf' }),
            document({ id: 'missing', title: 'Missing.pdf' }),
          ],
        },
      ],
    }
    reindexMutation.mutateAsync.mockResolvedValue({
      bulkJobId: 'reindex-partial',
      items: [
        {
          asset: {
            createdAt: '2026-07-20T10:00:00Z',
            filename: 'one.pdf',
            id: 'asset-1',
            knowledgeSpaceId: 'space-1',
            mimeType: 'application/pdf',
            objectKey: 'documents/one.pdf',
            parserStatus: 'pending',
            sha256: 'sha',
            sizeBytes: 1024,
            version: 1,
          },
          compilationJob: { id: 'job-1', stage: 'queued' },
          status: 'queued',
          statusUrl: '/tasks/job-1',
        },
        { documentId: 'missing', status: 'not_found' },
      ],
      total: 2,
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'One.pdf' }))
    await user.click(screen.getByRole('checkbox', { name: 'Missing.pdf' }))
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.reindexDocuments' }))

    expect(screen.getByRole('checkbox', { name: 'One.pdf' })).not.toBeChecked()
    expect(screen.getByRole('checkbox', { name: 'Missing.pdf' })).toBeChecked()
    expect(toastMock.warning).toHaveBeenCalledWith(
      'dataset.newKnowledge.documentsReindexPartial:{"missing":1,"queued":1}',
    )
  })

  it('clears stale selection and refreshes after every re-index target is missing', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({ id: 'missing' })] }] }
    reindexMutation.mutateAsync.mockResolvedValue({
      bulkJobId: 'reindex-missing',
      items: [{ documentId: 'missing', status: 'not_found' }],
      total: 1,
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' }))
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.reindexDocuments' }))

    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).not.toBeChecked()
    expect(
      screen.queryByRole('group', { name: 'dataset.newKnowledge.bulkDocumentActions' }),
    ).not.toBeInTheDocument()
    expect(queryClient.invalidateQueries).toHaveBeenCalled()
    expect(toastMock.error).toHaveBeenCalledWith(
      'dataset.newKnowledge.documentsReindexPartial:{"missing":1,"queued":0}',
    )
  })

  it('counts only attention tasks and opens the designed task panel', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({ id: 'running' }),
            task({ id: 'failed', state: 'failed', errorMessage: 'Parser failed' }),
            task({ id: 'done', state: 'succeeded' }),
          ],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    const trigger = screen.getByRole('button', {
      name: 'dataset.newKnowledge.tasksWithAttention:{"count":2}',
    })
    expect(trigger).toHaveTextContent('2')
    expect(trigger).toHaveAttribute('data-has-error', 'true')
    await user.click(trigger)
    const panel = screen.getByRole('dialog', { name: 'dataset.newKnowledge.backgroundTasks' })
    expect(
      within(panel).getByText('dataset.newKnowledge.backgroundTasksDescription'),
    ).toBeInTheDocument()
    expect(within(panel).getByText('Parser failed')).toBeInTheDocument()
    expect(
      within(panel).getByText((_, element) =>
        Boolean(
          element?.tagName === 'P' &&
          element.textContent?.includes('dataset.newKnowledge.processingTaskState.running') &&
          element.textContent.includes('·'),
        ),
      ),
    ).toBeInTheDocument()
  })

  it('gives duplicate task actions distinct accessible names', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [
        {
          items: [
            document({ id: 'document-a', title: 'Alpha.pdf' }),
            document({ id: 'document-b', title: 'Beta.pdf' }),
          ],
        },
      ],
    }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({ documentId: 'document-a', id: 'task-a' }),
            task({ documentId: 'document-b', id: 'task-b' }),
          ],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":2}',
      }),
    )

    expect(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.interruptTask · Alpha.pdf · task-a',
      }),
    ).toBeInTheDocument()
    expect(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.interruptTask · Beta.pdf · task-b',
      }),
    ).toBeInTheDocument()
  })

  it('shows task loading instead of an empty result while the task query is pending', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }))

    tasksQuery.isPending = true
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    const panel = screen.getByRole('dialog', { name: 'dataset.newKnowledge.backgroundTasks' })
    expect(within(panel).getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument()
    expect(
      within(panel).queryByText('dataset.newKnowledge.noBackgroundTasks'),
    ).not.toBeInTheDocument()
  })

  it('prevents duplicate interrupt requests and refreshes both resources', async () => {
    const user = userEvent.setup()
    let resolveCancel: ((value: DocumentProcessingTask) => void) | undefined
    cancelMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveCancel = resolve
        }),
    )
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'running' })] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    const interrupt = screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' })
    await user.dblClick(interrupt)

    expect(cancelMutation.mutateAsync).toHaveBeenCalledOnce()
    expect(cancelMutation.mutateAsync).toHaveBeenCalledWith({
      params: { documentId: 'document-1', id: 'space-1', taskId: 'running' },
    })
    await act(async () => resolveCancel?.(task({ id: 'running', state: 'canceled' })))
    expect(queryClient.invalidateQueries).toHaveBeenCalled()
    expect(screen.getByRole('button', { name: 'common.operation.close' })).toHaveFocus()
  })

  it('locks cancel and retry together while either task mutation is pending', async () => {
    const user = userEvent.setup()
    let resolveCancel: ((value: DocumentProcessingTask) => void) | undefined
    cancelMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveCancel = resolve
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'shared-task' })] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }))

    tasksQuery.data = {
      pages: [{ items: [task({ id: 'shared-task', state: 'failed' })] }],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    const retry = screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' })
    expect(retry).toHaveAttribute('aria-disabled', 'true')
    await user.click(retry)
    expect(retryMutation.mutateAsync).not.toHaveBeenCalled()

    await act(async () => resolveCancel?.(task({ id: 'shared-task', state: 'canceled' })))
  })

  it('retries a failed task once and reports mutation errors in the panel', async () => {
    const user = userEvent.setup()
    retryMutation.mutateAsync.mockRejectedValueOnce(new Error('retry failed'))
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'failed', state: 'failed' })] }] }

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))

    expect(retryMutation.mutateAsync).toHaveBeenCalledOnce()
    expect(await screen.findByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.taskActionFailed',
    )

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'failed',
              state: 'running',
            }),
          ],
        },
      ],
    }
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(screen.queryByText('dataset.newKnowledge.taskActionFailed')).not.toBeInTheDocument()
  })

  it('does not restore an action failure after its drawer cycle closes', async () => {
    const user = userEvent.setup()
    let rejectCancel: ((reason?: unknown) => void) | undefined
    cancelMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((_resolve, reject) => {
          rejectCancel = reject
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }))
    await user.click(screen.getByRole('button', { name: 'common.operation.close' }))
    await act(async () => rejectCancel?.(new Error('cancel failed after close')))

    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(screen.queryByText('dataset.newKnowledge.taskActionFailed')).not.toBeInTheDocument()
  })

  it('does not move focus when an action from an older drawer cycle succeeds', async () => {
    const user = userEvent.setup()
    let resolveCancel: ((value: DocumentProcessingTask) => void) | undefined
    cancelMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveCancel = resolve
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'old-action' })] }] }

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }))
    await user.click(screen.getByRole('button', { name: 'common.operation.close' }))

    tasksQuery.data = {
      pages: [{ items: [task({ id: 'old-action' }), task({ id: 'new-action' })] }],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":2}',
      }),
    )
    const nextAction = screen.getByRole('button', {
      name: 'dataset.newKnowledge.interruptTask · sso-enterprise.pdf · new-action',
    })
    act(() => nextAction.focus())
    expect(nextAction).toHaveFocus()

    await act(async () => resolveCancel?.(task({ id: 'old-action', state: 'canceled' })))
    expect(nextAction).toHaveFocus()
  })

  it('does not move focus when the user leaves a pending task action', async () => {
    const user = userEvent.setup()
    let resolveCancel: ((value: DocumentProcessingTask) => void) | undefined
    cancelMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveCancel = resolve
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'pending-action' }), task({ id: 'focus-target' })] }],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":2}',
      }),
    )
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.interruptTask · sso-enterprise.pdf · pending-action',
      }),
    )
    const nextAction = screen.getByRole('button', {
      name: 'dataset.newKnowledge.interruptTask · sso-enterprise.pdf · focus-target',
    })
    act(() => nextAction.focus())

    await act(async () => resolveCancel?.(task({ id: 'pending-action', state: 'canceled' })))
    expect(nextAction).toHaveFocus()
  })

  it('ignores a delayed action success after the task lifecycle advances', async () => {
    const user = userEvent.setup()
    let resolveRetry: ((value: DocumentProcessingTask) => void) | undefined
    retryMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveRetry = resolve
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'advanced-success', state: 'failed' })] }] }

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))

    tasksQuery.data = {
      pages: [{ items: [task({ id: 'advanced-success', state: 'running' })] }],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await act(async () => resolveRetry?.(task({ id: 'advanced-success', state: 'queued' })))

    expect(
      screen.getByText(/dataset\.newKnowledge\.processingTaskState\.running/),
    ).toBeInTheDocument()
  })

  it('ignores a delayed task action after document permission is revoked', async () => {
    const user = userEvent.setup()
    let resolveCancel: ((value: DocumentProcessingTask) => void) | undefined
    cancelMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveCancel = resolve
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'revoked-action' })] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }))

    queryClient.invalidateQueries.mockClear()
    documentsQuery.error = { status: 403 }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await act(async () => resolveCancel?.(task({ id: 'revoked-action', state: 'canceled' })))
    expect(queryClient.invalidateQueries).not.toHaveBeenCalled()

    documentsQuery.error = null
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    ).toBeInTheDocument()
  })

  it('keeps a successful task action across a transient permission query failure', async () => {
    const user = userEvent.setup()
    let resolveCancel: ((value: DocumentProcessingTask) => void) | undefined
    cancelMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveCancel = resolve
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'permission-query-action' })] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }))

    permissionStateMock.error = new Error('permission query unavailable')
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    queryClient.invalidateQueries.mockClear()
    await act(async () =>
      resolveCancel?.(task({ id: 'permission-query-action', state: 'canceled' })),
    )

    expect(queryClient.invalidateQueries).toHaveBeenCalled()
    expect(
      screen.getByText(/dataset\.newKnowledge\.processingTaskState\.canceled/),
    ).toBeInTheDocument()
  })

  it('drops a delayed action failure after the task lifecycle advances', async () => {
    const user = userEvent.setup()
    let rejectRetry: ((reason?: unknown) => void) | undefined
    retryMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((_resolve, reject) => {
          rejectRetry = reject
        }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'advanced', state: 'failed' })] }] }

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))

    tasksQuery.data = { pages: [{ items: [task({ id: 'advanced', state: 'running' })] }] }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await act(async () => rejectRetry?.(new Error('late retry failure')))
    tasksQuery.data = { pages: [{ items: [task({ id: 'advanced', state: 'failed' })] }] }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.queryByText('dataset.newKnowledge.taskActionFailed')).not.toBeInTheDocument()
  })

  it('does not offer retry for a canceled task', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'canceled', state: 'canceled' })] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasks',
      }),
    )

    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.retryTask' }),
    ).not.toBeInTheDocument()
  })

  it('clears an old failure across retry and shows the next terminal error', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({ id: 'retried-failure', state: 'failed', errorMessage: 'Old parser error' }),
          ],
        },
      ],
    }
    retryMutation.mutateAsync.mockResolvedValue(
      task({
        id: 'retried-failure',
        progressPercent: 0,
        state: 'queued',
        updatedAt: '2026-07-20T10:02:00Z',
      }),
    )
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: {
          progressPercent: 20,
          stage: 'parsed' as const,
          state: 'failed' as const,
          updatedAt: '2026-07-20T10:03:00Z',
        },
        event: 'progress' as const,
        id: 'retried-failure:2026-07-20T10:03:00Z',
      }
      yield {
        data: { errorCode: 'NEW_PARSER_ERROR', state: 'failed' as const },
        event: 'terminal' as const,
        id: 'retried-failure:terminal',
      }
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))

    await waitFor(() => expect(screen.queryByText('Old parser error')).not.toBeInTheDocument())
    expect(await screen.findByText('NEW_PARSER_ERROR')).toBeInTheDocument()
  })

  it('ignores a delayed retry response older than the current failed list snapshot', async () => {
    const user = userEvent.setup()
    let resolveRetry: ((snapshot: DocumentProcessingTask) => void) | undefined
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              errorCode: 'OLD_FAILURE',
              id: 'delayed-local-retry',
              state: 'failed',
              updatedAt: '2026-07-20T10:01:00Z',
            }),
          ],
        },
      ],
    }
    retryMutation.mutateAsync.mockReturnValue(
      new Promise<DocumentProcessingTask>((resolve) => {
        resolveRetry = resolve
      }),
    )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              errorCode: 'NEWER_FAILURE',
              id: 'delayed-local-retry',
              state: 'failed',
              updatedAt: '2026-07-20T10:03:00Z',
            }),
          ],
        },
      ],
    }
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await act(async () =>
      resolveRetry?.(
        task({
          id: 'delayed-local-retry',
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:02:00Z',
        }),
      ),
    )

    expect(await screen.findByText('NEWER_FAILURE')).toBeInTheDocument()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).not.toBeInTheDocument()
  })

  it('accepts an exact active retry snapshot with the terminal timestamp', async () => {
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'same-time-retry' })] }] }
    getTaskSnapshot.mockResolvedValue(
      task({
        id: 'same-time-retry',
        progressPercent: 0,
        state: 'dispatch_pending',
        updatedAt: '2026-07-20T10:03:00Z',
      }),
    )
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        yield {
          data: {
            progressPercent: 80,
            stage: 'parsed' as const,
            state: 'failed' as const,
            updatedAt: '2026-07-20T10:03:00Z',
          },
          event: 'progress' as const,
          id: 'same-time-retry:2026-07-20T10:03:00Z',
        }
        yield {
          data: { errorCode: 'PARSER_FAILED', state: 'failed' as const },
          event: 'terminal' as const,
          id: 'same-time-retry:terminal',
        }
        return
      }
      await new Promise<void>(() => {})
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() =>
      expect(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      ).toHaveTextContent('1'),
    )
    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2))
    expect(getTaskSnapshot).toHaveBeenCalledWith(
      {
        params: { documentId: 'document-1', id: 'space-1', taskId: 'same-time-retry' },
      },
      expect.objectContaining({ signal: expect.any(AbortSignal) }),
    )
  })

  it('restarts an aborted equal-timestamp reconciliation after permission returns', async () => {
    const signals: AbortSignal[] = []
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'permission-reconciliation' })] }] }
    getTaskSnapshot.mockImplementation(
      (_input: unknown, options: { signal: AbortSignal }) =>
        new Promise<DocumentProcessingTask>((_resolve, reject) => {
          signals.push(options.signal)
          options.signal.addEventListener(
            'abort',
            () => reject(new DOMException('Aborted', 'AbortError')),
            { once: true },
          )
        }),
    )
    streamFailedTaskThenWait('permission-reconciliation')

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'permission-reconciliation', updatedAt: '2026-07-20T10:03:00Z' })],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledTimes(2))

    documentsQuery.error = { status: 403 }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(signals[1]?.aborted).toBe(true))
    documentsQuery.error = null
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledTimes(3))
    expect(signals[2]?.aborted).toBe(false)
  })

  it('restores complete terminal error details from an equal list snapshot', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'listed-terminal-error' })] }] }
    getTaskSnapshot.mockRejectedValue(new Response(null, { status: 403 }))
    streamFailedTaskThenWait('listed-terminal-error')

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      await screen.findByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(await screen.findByText('PARSER_FAILED')).toBeInTheDocument()

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              errorCode: 'PARSER_FAILED',
              errorMessage: 'Parser failed at line 400 with complete server context',
              id: 'listed-terminal-error',
              state: 'failed',
              updatedAt: '2026-07-20T10:03:00Z',
            }),
          ],
        },
      ],
    }
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    const error = await screen.findByText('Parser failed at line 400 with complete server context')
    expect(error).toHaveClass('whitespace-pre-wrap', 'wrap-break-word')
    expect(error).not.toHaveClass('truncate')
  })

  it('hides cached documents and closes tasks when terminal reconciliation loses read access', async () => {
    const user = userEvent.setup()
    let releaseStream!: () => void
    const streamGate = new Promise<void>((resolve) => {
      releaseStream = resolve
    })
    let resolveDocumentsRefetch!: (result: { error: null }) => void
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.refetch.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveDocumentsRefetch = resolve
        }),
    )
    tasksQuery.data = { pages: [{ items: [task({ id: 'forbidden-reconciliation' })] }] }
    getTaskSnapshot.mockRejectedValue(new Response(null, { status: 403 }))
    streamProcessingTaskEvents.mockImplementation(async function* () {
      await streamGate
      yield {
        data: {
          progressPercent: 80,
          stage: 'parsed' as const,
          state: 'failed' as const,
          updatedAt: '2026-07-20T10:03:00Z',
        },
        event: 'progress' as const,
        id: 'forbidden-reconciliation:progress',
      }
      yield {
        data: { errorCode: 'PARSER_FAILED', state: 'failed' as const },
        event: 'terminal' as const,
        id: 'forbidden-reconciliation:terminal',
      }
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(screen.getByRole('dialog')).toBeInTheDocument()

    await act(async () => releaseStream())

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())
    await waitFor(() =>
      expect(screen.getByRole('alert')).toHaveTextContent(
        'dataset.newKnowledge.documentsPermissionDescription',
      ),
    )
    expect(screen.queryByText('sso-enterprise.pdf')).not.toBeInTheDocument()
    expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
    expect(documentsQuery.refetch).toHaveBeenCalledWith({ cancelRefetch: true })

    await act(async () => resolveDocumentsRefetch({ error: null }))
    expect(await screen.findByText('sso-enterprise.pdf')).toBeInTheDocument()
    expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
  })

  it('blocks failed polling for a task version denied during terminal reconciliation', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'denied-terminal-version' })] }] }
    getTaskSnapshot.mockRejectedValue(new Response(null, { status: 403 }))
    streamFailedTaskThenWait('denied-terminal-version')

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('hides cached documents and closes tasks when the task event stream loses read access', async () => {
    const user = userEvent.setup()
    let rejectStream!: () => void
    const streamGate = new Promise<void>((_resolve, reject) => {
      rejectStream = () => reject(new Response(null, { status: 403 }))
    })
    let resolveDocumentsRefetch!: (result: { error: null }) => void
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.refetch.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveDocumentsRefetch = resolve
        }),
    )
    tasksQuery.data = { pages: [{ items: [task({ id: 'forbidden-stream' })] }] }
    streamProcessingTaskEvents.mockImplementation(async function* () {
      await streamGate
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(screen.getByRole('dialog')).toBeInTheDocument()

    await act(async () => rejectStream())

    await waitFor(() =>
      expect(screen.getByRole('alert')).toHaveTextContent(
        'dataset.newKnowledge.documentsPermissionDescription',
      ),
    )
    expect(screen.queryByText('sso-enterprise.pdf')).not.toBeInTheDocument()
    expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
    expect(documentsQuery.refetch).toHaveBeenCalledWith({ cancelRefetch: true })

    await act(async () => resolveDocumentsRefetch({ error: null }))
    expect(await screen.findByText('sso-enterprise.pdf')).toBeInTheDocument()
    expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
  })

  it('does not restart a denied stream version while the task list lags behind progress', async () => {
    let resolveDocumentsRefetch!: (result: { error: null }) => void
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.refetch.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveDocumentsRefetch = resolve
        }),
    )
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'lagging-denied-stream', updatedAt: '2026-07-20T10:01:00Z' })],
        },
      ],
    }
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: {
          progressPercent: 50,
          stage: 'parsed' as const,
          state: 'running' as const,
          updatedAt: '2026-07-20T10:02:00Z',
        },
        event: 'progress' as const,
        id: 'lagging-denied-stream:version-2',
      }
      throw new Response(null, { status: 403 })
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(screen.getByRole('alert')).toHaveTextContent(
        'dataset.newKnowledge.documentsPermissionDescription',
      ),
    )
    expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()

    await act(async () => resolveDocumentsRefetch({ error: null }))
    expect(await screen.findByText('sso-enterprise.pdf')).toBeInTheDocument()
    await act(async () => {})
    expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
  })

  it('observes a same-version retry after the task list confirms a denied stream terminal', async () => {
    let resolveDocumentsRefetch!: (result: { error: null }) => void
    const deniedVersion = '2026-07-20T10:02:00Z'
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.refetch.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveDocumentsRefetch = resolve
        }),
    )
    tasksQuery.dataUpdateCount = 1
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'same-version-terminal-retry' })],
        },
      ],
    }
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        yield {
          data: {
            progressPercent: 50,
            stage: 'parsed' as const,
            state: 'running' as const,
            updatedAt: deniedVersion,
          },
          event: 'progress' as const,
          id: 'same-version-terminal-retry:version-2',
        }
        throw new Response(null, { status: 403 })
      }
      await new Promise<void>(() => {})
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(documentsQuery.refetch).toHaveBeenCalled())
    await act(async () => resolveDocumentsRefetch({ error: null }))

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'same-version-terminal-retry',
              state: 'failed',
              updatedAt: deniedVersion,
            }),
          ],
        },
      ],
    }
    tasksQuery.dataUpdateCount = 2
    act(() => notifyTaskQuerySuccess())
    await act(async () => {})
    expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'same-version-terminal-retry',
              state: 'running',
              updatedAt: deniedVersion,
            }),
          ],
        },
      ],
    }
    tasksQuery.dataUpdateCount = 3
    act(() => notifyTaskQuerySuccess())

    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2))
  })

  it('restarts an active override after a structurally shared terminal confirmation', async () => {
    const user = userEvent.setup()
    let resolveDocumentsRefetch!: (result: { error: null }) => void
    const taskVersion = '2026-07-20T10:02:00Z'
    const sharedTerminalData = {
      pages: [
        {
          items: [
            task({
              id: 'shared-terminal-confirmation',
              state: 'failed',
              updatedAt: taskVersion,
            }),
          ],
        },
      ],
    }
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.refetch.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveDocumentsRefetch = resolve
        }),
    )
    tasksQuery.data = sharedTerminalData
    tasksQuery.dataUpdateCount = 1
    retryMutation.mutateAsync.mockResolvedValue(
      task({
        id: 'shared-terminal-confirmation',
        state: 'dispatch_pending',
        updatedAt: taskVersion,
      }),
    )
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) throw new Response(null, { status: 403 })
      await new Promise<void>(() => {})
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))
    await waitFor(() => expect(documentsQuery.refetch).toHaveBeenCalled())
    expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
    await act(async () => resolveDocumentsRefetch({ error: null }))

    tasksQuery.data = sharedTerminalData
    tasksQuery.dataUpdateCount = 2
    act(() => notifyTaskQuerySuccess())

    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2))
  })

  it('single-flights backed-off recovery polling until a denied stream gets a newer version', async () => {
    vi.useFakeTimers()
    let resolveDocumentsRefetch!: (result: { error: null }) => void
    let resolveTaskRefetch!: (result: { error: null }) => void
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.refetch.mockImplementation(
      () =>
        new Promise((resolve) => {
          resolveDocumentsRefetch = resolve
        }),
    )
    tasksQuery.dataUpdateCount = 1
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'recovering-stream', updatedAt: '2026-07-20T10:01:00Z' })],
        },
      ],
    }
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        yield {
          data: {
            progressPercent: 50,
            stage: 'parsed' as const,
            state: 'running' as const,
            updatedAt: '2026-07-20T10:02:00Z',
          },
          event: 'progress' as const,
          id: 'recovering-stream:version-2',
        }
        throw new Response(null, { status: 403 })
      }
      await new Promise<void>(() => {})
    })

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
      await act(async () => resolveDocumentsRefetch({ error: null }))
      tasksQuery.refetch.mockClear()
      tasksQuery.data = {
        pages: [
          {
            items: [task({ id: 'recovering-stream', updatedAt: '2026-07-20T10:02:00Z' })],
          },
        ],
      }
      tasksQuery.dataUpdateCount = 2
      act(() => notifyTaskQuerySuccess())
      await act(async () => {})
      tasksQuery.refetch
        .mockImplementationOnce(
          () =>
            new Promise((resolve) => {
              resolveTaskRefetch = resolve
            }),
        )
        .mockResolvedValue({ error: null })

      await act(async () => vi.advanceTimersByTime(5000))
      expect(tasksQuery.refetch).toHaveBeenCalledWith({ cancelRefetch: false })
      expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
      await act(async () => vi.advanceTimersByTime(120000))
      expect(tasksQuery.refetch).toHaveBeenCalledOnce()

      await act(async () => resolveTaskRefetch({ error: null }))
      for (const delay of [10000, 20000, 30000, 30000, 30000, 30000])
        await act(async () => vi.advanceTimersByTime(delay))
      expect(tasksQuery.refetch).toHaveBeenCalledTimes(7)
      expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()

      tasksQuery.data = {
        pages: [
          {
            items: [task({ id: 'recovering-stream', updatedAt: '2026-07-20T10:03:00Z' })],
          },
        ],
      }
      tasksQuery.dataUpdateCount = 3
      act(() => notifyTaskQuerySuccess())
      await act(async () => {})

      expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2)
      tasksQuery.refetch.mockClear()
      await act(async () => vi.advanceTimersByTime(60000))
      expect(tasksQuery.refetch).not.toHaveBeenCalled()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('focuses the permission alert when an auxiliary retry becomes a document denial', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    documentsQuery.refetch
      .mockResolvedValueOnce({ error: new Error('verification unavailable') })
      .mockImplementationOnce(() => new Promise(() => {}))
    tasksQuery.data = { pages: [{ items: [task({ id: 'retry-document-denial' })] }] }
    streamProcessingTaskEvents.mockImplementation(async function* () {
      throw new Response(null, { status: 403 })
    })

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const retry = await screen.findByRole('button', {
      name: 'common.operation.retry · dataset.newKnowledge.documentsPermissionDescription',
    })
    await user.click(retry)

    documentsQuery.error = { status: 403 }
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(screen.getByRole('alert')).toHaveFocus())
    expect(screen.queryByText('sso-enterprise.pdf')).not.toBeInTheDocument()
  })

  it('replaces an in-flight terminal reconciliation and aborts it on unmount', async () => {
    vi.useFakeTimers()
    const signals: AbortSignal[] = []
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'coalesced-reconciliation' })] }] }
    getTaskSnapshot.mockImplementation(
      (_input: unknown, options: { signal: AbortSignal }) =>
        new Promise<DocumentProcessingTask>((_resolve, reject) => {
          signals.push(options.signal)
          options.signal.addEventListener(
            'abort',
            () => reject(new DOMException('Aborted', 'AbortError')),
            { once: true },
          )
        }),
    )
    streamFailedTaskThenWait('coalesced-reconciliation')

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      tasksQuery.data = {
        pages: [
          {
            items: [
              task({
                id: 'coalesced-reconciliation',
                state: 'dispatch_pending',
                updatedAt: '2026-07-20T10:03:00Z',
              }),
            ],
          },
        ],
      }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => {})

      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
      expect(signals[0]?.aborted).toBe(true)
      expect(signals[1]?.aborted).toBe(false)
      rendered.unmount()
      expect(signals[1]?.aborted).toBe(true)
      await act(async () => vi.advanceTimersByTime(60000))
      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('retries a terminal reconciliation after its request deadline', async () => {
    vi.useFakeTimers()
    const signals: AbortSignal[] = []
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'timed-reconciliation' })] }] }
    getTaskSnapshot
      .mockImplementationOnce(
        (_input: unknown, options: { signal: AbortSignal }) =>
          new Promise<DocumentProcessingTask>((_resolve, reject) => {
            signals.push(options.signal)
            options.signal.addEventListener(
              'abort',
              () => reject(new DOMException('Aborted', 'AbortError')),
              { once: true },
            )
          }),
      )
      .mockResolvedValueOnce(
        task({
          errorCode: 'TERMINAL_CONFIRMED',
          id: 'timed-reconciliation',
          state: 'failed',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )
    streamFailedTaskThenWait('timed-reconciliation')

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      await act(async () => vi.advanceTimersByTime(3000))
      expect(signals[0]?.aborted).toBe(true)
      await act(async () => vi.advanceTimersByTime(1000))

      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('replaces an older reconciliation backoff with the latest generation retry', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'latest-reconciliation' })] }] }
    getTaskSnapshot
      .mockRejectedValueOnce(new Error('first snapshot unavailable'))
      .mockRejectedValueOnce(new Error('second snapshot unavailable'))
      .mockResolvedValueOnce(
        task({
          id: 'latest-reconciliation',
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )
    streamFailedTaskThenWait('latest-reconciliation')

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      tasksQuery.data = {
        pages: [
          {
            items: [task({ id: 'latest-reconciliation', updatedAt: '2026-07-20T10:03:00Z' })],
          },
        ],
      }
      tasksQuery.dataUpdateCount += 1
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => {})
      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)

      await act(async () => vi.advanceTimersByTime(1000))
      expect(getTaskSnapshot).toHaveBeenCalledTimes(3)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('accepts a later active list snapshot after exact reconciliation saw the terminal state', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'later-same-time-retry' })] }] }
    getTaskSnapshot
      .mockResolvedValueOnce(
        task({
          errorCode: 'PARSER_FAILED',
          id: 'later-same-time-retry',
          state: 'failed',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )
      .mockResolvedValueOnce(
        task({
          id: 'later-same-time-retry',
          progressPercent: 0,
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )
    streamFailedTaskThenWait('later-same-time-retry')

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      await screen.findByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(await screen.findByText('PARSER_FAILED')).toBeInTheDocument()
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'later-same-time-retry',
              progressPercent: 0,
              state: 'dispatch_pending',
              updatedAt: '2026-07-20T10:03:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledTimes(2))
    expect(
      await screen.findByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
    expect(screen.queryByText('PARSER_FAILED')).not.toBeInTheDocument()
    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2))
  })

  it('uses a later active list snapshot when exact reconciliation fails', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'failed-reconciliation-retry' })] }] }
    getTaskSnapshot.mockRejectedValueOnce(new Error('snapshot unavailable')).mockResolvedValueOnce(
      task({
        id: 'failed-reconciliation-retry',
        progressPercent: 0,
        state: 'dispatch_pending',
        updatedAt: '2026-07-20T10:03:00Z',
      }),
    )
    streamFailedTaskThenWait('failed-reconciliation-retry')

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      await screen.findByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(await screen.findByText('PARSER_FAILED')).toBeInTheDocument()
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'failed-reconciliation-retry',
              progressPercent: 0,
              state: 'dispatch_pending',
              updatedAt: '2026-07-20T10:03:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledTimes(2))
    expect(
      await screen.findByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
    expect(screen.queryByText('PARSER_FAILED')).not.toBeInTheDocument()
    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2))
  })

  it('does not treat a stale active row from a completed cursor page as a retry', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'stale-cursor-active', updatedAt: '2026-07-20T10:03:00Z' })],
        },
      ],
    }
    getTaskSnapshot.mockResolvedValue(
      task({
        errorCode: 'PARSER_FAILED',
        id: 'stale-cursor-active',
        state: 'failed',
        updatedAt: '2026-07-20T10:03:00Z',
      }),
    )
    streamFailedTaskThenWait('stale-cursor-active')

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      await screen.findByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(await screen.findByText('PARSER_FAILED')).toBeInTheDocument()
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())

    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'stale-cursor-active', updatedAt: '2026-07-20T10:03:00Z' })],
        },
        { items: [task({ id: 'another-page', state: 'succeeded' })] },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledTimes(2))
    expect(screen.getByText('PARSER_FAILED')).toBeInTheDocument()
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }),
    ).toBeInTheDocument()
    expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
  })

  it('ignores a terminal reconciliation that resolves after a local retry', async () => {
    const user = userEvent.setup()
    let resolveSnapshot: ((snapshot: DocumentProcessingTask) => void) | undefined
    let reconciliationSignal: AbortSignal | undefined
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'local-retry-race' })] }] }
    getTaskSnapshot.mockImplementation(
      (_input: unknown, options: { signal: AbortSignal }) =>
        new Promise<DocumentProcessingTask>((resolve) => {
          reconciliationSignal = options.signal
          resolveSnapshot = resolve
        }),
    )
    retryMutation.mutateAsync.mockResolvedValue(
      task({
        id: 'local-retry-race',
        progressPercent: 0,
        state: 'dispatch_pending',
        updatedAt: '2026-07-20T10:03:00Z',
      }),
    )
    streamFailedTaskThenWait('local-retry-race')

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      await screen.findByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(await screen.findByText('PARSER_FAILED')).toBeInTheDocument()
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))
    expect(reconciliationSignal?.aborted).toBe(true)
    expect(
      await screen.findByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()

    await act(async () => {
      resolveSnapshot?.(
        task({
          errorCode: 'PARSER_FAILED',
          id: 'local-retry-race',
          state: 'failed',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )
    })

    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
    expect(screen.queryByText('PARSER_FAILED')).not.toBeInTheDocument()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.retryTask' }),
    ).not.toBeInTheDocument()
  })

  it('consumes the terminal error after final progress without duplicate side effects', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'failed-stream' })] }] }
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: {
          progressPercent: 80,
          stage: 'parsed' as const,
          state: 'failed' as const,
          updatedAt: '2026-07-20T10:03:00Z',
        },
        event: 'progress' as const,
        id: 'failed-stream:2026-07-20T10:03:00Z',
      }
      yield {
        data: { errorCode: 'PARSER_FAILED', state: 'failed' as const },
        event: 'terminal' as const,
        id: 'failed-stream:terminal',
      }
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      await screen.findByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )

    expect(await screen.findByText('PARSER_FAILED')).toBeInTheDocument()
    expect(toastMock.error).toHaveBeenCalledTimes(1)
    expect(queryClient.invalidateQueries).toHaveBeenCalledOnce()
  })

  it('applies task events and clears the attention badge after completion', async () => {
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'running' })] }] }
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        yield {
          data: {
            progressPercent: 100,
            stage: 'published' as const,
            state: 'succeeded' as const,
            updatedAt: '2026-07-20T10:06:00Z',
          },
          event: 'progress' as const,
          id: 'running:2026-07-20T10:06:00Z',
        }
        yield {
          data: { state: 'succeeded' as const },
          event: 'terminal' as const,
          id: 'running:terminal',
        }
        return
      }
      await new Promise<void>(() => {})
    })

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() =>
      expect(
        screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }),
      ).not.toHaveTextContent('1'),
    )
    expect(streamProcessingTaskEvents).toHaveBeenCalledWith(
      expect.objectContaining({
        documentId: 'document-1',
        knowledgeSpaceId: 'space-1',
        taskId: 'running',
      }),
    )
    expect(queryClient.invalidateQueries).toHaveBeenCalled()

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'running',
              progressPercent: 90,
              state: 'running',
              updatedAt: '2026-07-20T10:05:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }),
    ).not.toHaveTextContent('1')

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'running',
              progressPercent: 100,
              state: 'succeeded',
              updatedAt: '2026-07-20T10:06:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(
        screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }),
      ).not.toHaveTextContent('1'),
    )

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'running',
              progressPercent: 0,
              state: 'queued',
              updatedAt: '2026-07-20T10:07:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      ).toHaveTextContent('1'),
    )
  })

  it('accepts an external retry newer than the terminal event version', async () => {
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'externally-retried' })] }] }
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        yield {
          data: {
            progressPercent: 100,
            stage: 'published' as const,
            state: 'succeeded' as const,
            updatedAt: '2026-07-20T10:04:00Z',
          },
          event: 'progress' as const,
          id: 'externally-retried:2026-07-20T10:04:00Z',
        }
        yield {
          data: { state: 'succeeded' as const },
          event: 'terminal' as const,
          id: 'externally-retried:terminal',
        }
        return
      }
      await new Promise<void>(() => {})
    })

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() =>
      expect(
        screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }),
      ).not.toHaveTextContent('1'),
    )

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'externally-retried',
              progressPercent: 0,
              state: 'queued',
              updatedAt: '2026-07-20T10:05:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      ).toHaveTextContent('1'),
    )
    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2), {
      timeout: 2000,
    })
  })

  it('ignores a stale terminal event after polling observes an external retry', async () => {
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'retry-race' })] }] }
    let releaseTerminal: (() => void) | undefined
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        await new Promise<void>((resolve) => {
          releaseTerminal = resolve
        })
        yield {
          data: {
            progressPercent: 100,
            stage: 'published' as const,
            state: 'succeeded' as const,
            updatedAt: '2026-07-20T10:03:00Z',
          },
          event: 'progress' as const,
          id: 'retry-race:progress',
        }
        yield {
          data: { state: 'succeeded' as const },
          event: 'terminal' as const,
          id: 'retry-race:terminal',
        }
        return
      }
      await new Promise<void>(() => {})
    })

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(releaseTerminal).toBeTypeOf('function'))

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'retry-race',
              progressPercent: 0,
              state: 'queued',
              updatedAt: '2026-07-20T10:05:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await act(async () => releaseTerminal?.())
    await waitFor(() =>
      expect(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      ).toHaveTextContent('1'),
    )
    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2), {
      timeout: 2000,
    })
  })

  it('keeps one event stream and its resume cursor when polling updates task versions', async () => {
    let releaseStream: (() => void) | undefined
    streamProcessingTaskEvents.mockImplementation(async function* () {
      await new Promise<void>((resolve) => {
        releaseStream = resolve
      })
    })
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'streaming' })] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledOnce())

    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'streaming', updatedAt: '2026-07-20T10:02:00Z' })],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()

    await act(async () => releaseStream?.())
  })

  it('keeps task event progress monotonic and yields to a newer server snapshot', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document({})] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'running' })] }] }
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: {
          progressPercent: 80,
          stage: 'nodes_generated' as const,
          state: 'running' as const,
          updatedAt: '2026-07-20T10:03:00Z',
        },
        event: 'progress' as const,
        id: 'running:newer',
      }
      yield {
        data: {
          progressPercent: 20,
          stage: 'parsed' as const,
          state: 'running' as const,
          updatedAt: '2026-07-20T10:02:00Z',
        },
        event: 'progress' as const,
        id: 'running:stale',
      }
    })

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await waitFor(() =>
      expect(
        screen.getByText(/dataset\.newKnowledge\.processingTaskState\.running.*"progress":80/),
      ).toBeInTheDocument(),
    )
    await act(async () => {})
    expect(
      screen.queryByText(/dataset\.newKnowledge\.processingTaskState\.running.*"progress":20/),
    ).not.toBeInTheDocument()
    await user.click(screen.getByRole('button', { name: 'common.operation.close' }))

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'running',
              progressPercent: 100,
              state: 'succeeded',
              updatedAt: '2026-07-20T10:04:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' })).toBeInTheDocument()
  })

  it('lets an equal-timestamp terminal list row replace an active stream override', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'equal-terminal', state: 'queued' })] }] }
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: {
          progressPercent: 60,
          stage: 'parsed' as const,
          state: 'running' as const,
          updatedAt: '2026-07-20T10:01:00Z',
        },
        event: 'progress' as const,
        id: 'equal-terminal:running',
      }
      await new Promise<void>(() => {})
    })

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledOnce())
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'equal-terminal', state: 'failed' })] }],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )

    expect(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' })).toBeEnabled()
  })

  it('moves focus to the drawer close button when an external update removes a task action', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'external-terminal' })] }] }
    streamProcessingTaskEvents.mockImplementation(async function* () {
      await new Promise<void>(() => {})
    })
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    const action = screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' })
    act(() => action.focus())

    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'external-terminal',
              state: 'succeeded',
              updatedAt: '2026-07-20T10:02:00Z',
            }),
          ],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() =>
      expect(screen.getByRole('button', { name: 'common.operation.close' })).toHaveFocus(),
    )
  })

  it('limits rendered task history while retaining an active retry for an old document', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const history = Array.from({ length: 150 }, (_, index) =>
      task({
        id: `history-${index}`,
        state: 'succeeded',
        updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
      }),
    )
    history[0] = task({
      createdAt: '2026-07-01T10:00:00Z',
      id: 'old-active-retry',
      state: 'running',
      updatedAt: '2026-07-01T10:00:00Z',
    })
    history[1] = task({
      createdAt: '2026-07-01T10:01:00Z',
      id: 'old-failed-retry',
      state: 'failed',
      updatedAt: '2026-08-01T10:00:00Z',
    })
    tasksQuery.data = { pages: [{ items: history }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":2}',
      }),
    )

    expect(screen.getAllByRole('listitem')).toHaveLength(100)
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }),
    ).toBeInTheDocument()
  })

  it('keeps a failed task actionable when one hundred active tasks fill the drawer', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            ...Array.from({ length: 100 }, (_, index) =>
              task({
                id: `active-${index}`,
                updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
              }),
            ),
            task({
              id: 'failed-outside-active-limit',
              state: 'failed',
              updatedAt: '2026-07-01T10:00:00Z',
            }),
          ],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":101}',
      }),
    )

    expect(screen.getAllByRole('listitem')).toHaveLength(100)
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }),
    ).toBeInTheDocument()
  })

  it('keeps an active task actionable when one hundred failed tasks fill the drawer', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            ...Array.from({ length: 100 }, (_, index) =>
              task({
                id: `failed-${index}`,
                state: 'failed',
                updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
              }),
            ),
            task({
              id: 'active-outside-failed-limit',
              state: 'running',
              updatedAt: '2026-07-01T10:00:00Z',
            }),
          ],
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":101}',
      }),
    )

    expect(screen.getAllByRole('listitem')).toHaveLength(100)
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
    await user.click(
      within(screen.getByRole('dialog')).getByRole('button', {
        name: 'dataset.newKnowledge.loadMore',
      }),
    )
    expect(screen.getAllByRole('listitem')).toHaveLength(101)
  })

  it('moves focus to the drawer close button when the final task batch is revealed', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: Array.from({ length: 101 }, (_, index) =>
            task({
              id: `history-${index}`,
              state: 'succeeded',
              updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
            }),
          ),
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.tasks' }))
    await user.click(
      within(screen.getByRole('dialog')).getByRole('button', {
        name: 'dataset.newKnowledge.loadMore',
      }),
    )

    await waitFor(() =>
      expect(screen.getByRole('button', { name: 'common.operation.close' })).toHaveFocus(),
    )
  })

  it('preserves focus when the user leaves a pending final-page control', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const firstPage = [
      task({ id: 'active-focus-target' }),
      ...Array.from({ length: 99 }, (_, index) =>
        task({
          id: `history-${index}`,
          state: 'succeeded',
          updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
        }),
      ),
    ]
    tasksQuery.data = { pages: [{ items: firstPage, nextCursor: 'final-page' }] }
    tasksQuery.hasNextPage = true

    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1} · dataset.newKnowledge.taskHistoryIncomplete',
      }),
    )
    await user.click(
      within(screen.getByRole('dialog')).getByRole('button', {
        name: 'dataset.newKnowledge.loadMore',
      }),
    )
    const action = screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' })
    act(() => action.focus())

    tasksQuery.data = {
      pages: [{ items: firstPage }, { items: [task({ id: 'last-task', state: 'succeeded' })] }],
    }
    tasksQuery.hasNextPage = false
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(action).toHaveFocus()
  })

  it('rotates a bounded task event stream pool without polling every cursor page', async () => {
    vi.useFakeTimers()
    streamProcessingTaskEvents.mockImplementation(async function* () {
      await new Promise<void>(() => {})
    })
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: Array.from({ length: 20 }, (_, index) =>
            task({
              id: `active-${index}`,
              updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
            }),
          ),
        },
      ],
    }

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(6)
      await act(async () => vi.advanceTimersByTime(5000))
      expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(12)
      const taskOptions = tasksInfiniteOptions.mock.lastCall?.[0]
      expect(taskOptions?.refetchInterval).toBeUndefined()
      expect(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":20}',
        }),
      ).toBeInTheDocument()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('resumes a task event stream from its cursor after rotation', async () => {
    vi.useFakeTimers()
    const resumeCalls: Array<{ lastEventId?: string; taskId: string }> = []
    streamProcessingTaskEvents.mockImplementation(async function* ({
      lastEventId,
      taskId,
    }: {
      lastEventId?: string
      taskId: string
    }) {
      resumeCalls.push({ lastEventId, taskId })
      if (taskId === 'active-0' && !lastEventId) {
        yield {
          data: {
            progressPercent: 50,
            stage: 'parsed' as const,
            state: 'running' as const,
            updatedAt: '2026-07-20T10:30:00Z',
          },
          event: 'progress' as const,
          id: 'active-0:cursor',
        }
      }
      await new Promise<void>(() => {})
    })
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: Array.from({ length: 20 }, (_, index) =>
            task({
              id: `active-${index}`,
              updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
            }),
          ),
        },
      ],
    }

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      for (let index = 0; index < 3; index += 1) {
        await act(async () => vi.advanceTimersByTime(5000))
        await act(async () => {})
      }

      expect(resumeCalls.filter(({ taskId }) => taskId === 'active-0')).toEqual([
        { lastEventId: undefined, taskId: 'active-0' },
        { lastEventId: 'active-0:cursor', taskId: 'active-0' },
      ])
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('drops an event cursor after the task list reports a terminal state', async () => {
    const streamCalls: Array<{ lastEventId?: string; taskId: string }> = []
    streamProcessingTaskEvents.mockImplementation(async function* ({
      lastEventId,
      taskId,
    }: {
      lastEventId?: string
      taskId: string
    }) {
      streamCalls.push({ lastEventId, taskId })
      if (streamCalls.length === 1) {
        yield {
          data: {
            progressPercent: 60,
            stage: 'parsed' as const,
            state: 'running' as const,
            updatedAt: '2026-07-20T10:02:00Z',
          },
          event: 'progress' as const,
          id: 'restarted:old-cursor',
        }
      }
      await new Promise<void>(() => {})
    })
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'restarted' })] }] }
    const { rerender } = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(streamCalls).toHaveLength(1))

    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'restarted', state: 'failed', updatedAt: '2026-07-20T10:03:00Z' })],
        },
      ],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'restarted', updatedAt: '2026-07-20T10:04:00Z' })] }],
    }
    rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(streamCalls).toHaveLength(2))
    expect(streamCalls).toEqual([
      { lastEventId: undefined, taskId: 'restarted' },
      { lastEventId: undefined, taskId: 'restarted' },
    ])
  })

  it('keeps a retry cursor while the base task list still has the old terminal row', async () => {
    vi.useFakeTimers()
    const retryStreamCalls: Array<string | undefined> = []
    streamProcessingTaskEvents.mockImplementation(async function* ({
      lastEventId,
      taskId,
    }: {
      lastEventId?: string
      taskId: string
    }) {
      if (taskId === 'active-5') {
        retryStreamCalls.push(lastEventId)
        if (!lastEventId) {
          yield {
            data: {
              progressPercent: 55,
              stage: 'parsed' as const,
              state: 'running' as const,
              updatedAt: '2026-07-20T10:02:00Z',
            },
            event: 'progress' as const,
            id: 'active-5:retry-cursor',
          }
        }
      }
      await new Promise<void>(() => {})
    })
    retryMutation.mutateAsync.mockResolvedValue(
      task({ id: 'active-5', state: 'queued', updatedAt: '2026-07-20T10:01:00Z' }),
    )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            ...Array.from({ length: 5 }, (_, index) => task({ id: `active-${index}` })),
            task({ id: 'active-5', state: 'failed' }),
            task({ id: 'active-6' }),
          ],
        },
      ],
    }

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      fireEvent.click(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":7}',
        }),
      )
      fireEvent.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))
      await act(async () => {})
      expect(retryStreamCalls).toEqual([undefined])

      await act(async () => vi.advanceTimersByTime(5000))
      await act(async () => vi.advanceTimersByTime(5000))
      await act(async () => {})

      expect(retryStreamCalls).toEqual([undefined, 'active-5:retry-cursor'])
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('keeps the latest event cursor when the observer effect restarts', async () => {
    const streamCursors: Array<string | undefined> = []
    streamProcessingTaskEvents.mockImplementation(async function* ({
      lastEventId,
    }: {
      lastEventId?: string
    }) {
      streamCursors.push(lastEventId)
      if (streamCursors.length === 1) {
        yield {
          data: {
            progressPercent: 50,
            stage: 'parsed' as const,
            state: 'running' as const,
            updatedAt: '2026-07-20T10:02:00Z',
          },
          event: 'progress' as const,
          id: 'task-1:cursor',
        }
      }
      await new Promise<void>(() => {})
    })
    const onLastEventIdChange = vi.fn()
    const firstOnEvent = vi.fn(() => true)
    const rendered = render(
      <TaskEventObserver
        documentId="document-1"
        knowledgeSpaceId="space-1"
        onEvent={firstOnEvent}
        onLastEventIdChange={onLastEventIdChange}
        onPermissionDenied={vi.fn()}
        taskId="task-1"
        taskVersion="2026-07-20T10:01:00Z"
      />,
    )

    await act(async () => {})
    expect(onLastEventIdChange).toHaveBeenCalledWith('task-1', 'task-1:cursor')

    rendered.rerender(
      <TaskEventObserver
        documentId="document-1"
        knowledgeSpaceId="space-1"
        lastEventId="task-1:cursor"
        onEvent={vi.fn(() => true)}
        onLastEventIdChange={onLastEventIdChange}
        onPermissionDenied={vi.fn()}
        taskId="task-1"
        taskVersion="2026-07-20T10:01:00Z"
      />,
    )
    await act(async () => {})

    expect(streamCursors).toEqual([undefined, 'task-1:cursor'])
  })

  it('does not retain a reconnect timer for an already-aborted observer', async () => {
    vi.useFakeTimers()
    let finishStream!: () => void
    streamProcessingTaskEvents.mockImplementation(async function* () {
      await new Promise<void>((resolve) => {
        finishStream = resolve
      })
    })
    const rendered = render(
      <TaskEventObserver
        documentId="document-1"
        knowledgeSpaceId="space-1"
        onEvent={vi.fn(() => true)}
        onLastEventIdChange={vi.fn()}
        onPermissionDenied={vi.fn()}
        taskId="task-1"
        taskVersion="2026-07-20T10:01:00Z"
      />,
    )
    try {
      await act(async () => {})
      rendered.unmount()
      await act(async () => finishStream())

      expect(vi.getTimerCount()).toBe(0)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('blocks the latest streamed task version when a reconnect loses permission', async () => {
    vi.useFakeTimers()
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        yield {
          data: {
            progressPercent: 50,
            stage: 'parsed' as const,
            state: 'running' as const,
            updatedAt: '2026-07-20T10:02:00Z',
          },
          event: 'progress' as const,
          id: 'task-1:version-2',
        }
        return
      }
      throw new Response(null, { status: 403 })
    })
    const onPermissionDenied = vi.fn()
    const rendered = render(
      <TaskEventObserver
        documentId="document-1"
        knowledgeSpaceId="space-1"
        onEvent={vi.fn(() => true)}
        onLastEventIdChange={vi.fn()}
        onPermissionDenied={onPermissionDenied}
        taskId="task-1"
        taskVersion="2026-07-20T10:01:00Z"
      />,
    )

    try {
      await act(async () => {})
      await act(async () => vi.advanceTimersByTime(1000))

      expect(onPermissionDenied).toHaveBeenCalledWith('task-1', '2026-07-20T10:02:00Z')
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('keeps an accepted task version after rejecting a stale event before a denied reconnect', async () => {
    vi.useFakeTimers()
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) {
        for (const updatedAt of ['2026-07-20T10:02:00Z', '2026-07-20T10:00:00Z']) {
          yield {
            data: {
              progressPercent: 50,
              stage: 'parsed' as const,
              state: 'running' as const,
              updatedAt,
            },
            event: 'progress' as const,
            id: `task-1:${updatedAt}`,
          }
        }
        return
      }
      throw new Response(null, { status: 403 })
    })
    const onPermissionDenied = vi.fn()
    const rendered = render(
      <TaskEventObserver
        documentId="document-1"
        knowledgeSpaceId="space-1"
        onEvent={vi.fn().mockReturnValueOnce(true).mockReturnValueOnce(false)}
        onLastEventIdChange={vi.fn()}
        onPermissionDenied={onPermissionDenied}
        taskId="task-1"
        taskVersion="2026-07-20T10:01:00Z"
      />,
    )

    try {
      await act(async () => {})
      await act(async () => vi.advanceTimersByTime(1000))

      expect(onPermissionDenied).toHaveBeenCalledWith('task-1', '2026-07-20T10:02:00Z')
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('syncs a newer task prop before a reconnect is denied', async () => {
    vi.useFakeTimers()
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) return
      throw new Response(null, { status: 403 })
    })
    const onPermissionDenied = vi.fn()
    const rendered = render(
      <TaskEventObserver
        documentId="document-1"
        knowledgeSpaceId="space-1"
        onEvent={vi.fn(() => true)}
        onLastEventIdChange={vi.fn()}
        onPermissionDenied={onPermissionDenied}
        taskId="task-1"
        taskVersion="2026-07-20T10:01:00Z"
      />,
    )

    try {
      await act(async () => {})
      rendered.rerender(
        <TaskEventObserver
          documentId="document-1"
          knowledgeSpaceId="space-1"
          onEvent={vi.fn(() => true)}
          onLastEventIdChange={vi.fn()}
          onPermissionDenied={onPermissionDenied}
          taskId="task-1"
          taskVersion="2026-07-20T10:03:00Z"
        />,
      )
      rendered.rerender(
        <TaskEventObserver
          documentId="document-1"
          knowledgeSpaceId="space-1"
          onEvent={vi.fn(() => true)}
          onLastEventIdChange={vi.fn()}
          onPermissionDenied={onPermissionDenied}
          taskId="task-1"
          taskVersion="2026-07-20T10:02:00Z"
        />,
      )
      await act(async () => vi.advanceTimersByTime(1000))

      expect(onPermissionDenied).toHaveBeenCalledWith('task-1', '2026-07-20T10:03:00Z')
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('uses a newer task prop for a terminal-only reconnect', async () => {
    vi.useFakeTimers()
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      if (streamCount === 1) return
      yield {
        data: { errorCode: 'PARSER_FAILED', state: 'failed' as const },
        event: 'terminal' as const,
        id: 'task-1:terminal',
      }
    })
    const onEvent = vi.fn(() => true)
    const rendered = render(
      <TaskEventObserver
        documentId="document-1"
        knowledgeSpaceId="space-1"
        onEvent={onEvent}
        onLastEventIdChange={vi.fn()}
        onPermissionDenied={vi.fn()}
        taskId="task-1"
        taskVersion="2026-07-20T10:01:00Z"
      />,
    )

    try {
      await act(async () => {})
      rendered.rerender(
        <TaskEventObserver
          documentId="document-1"
          knowledgeSpaceId="space-1"
          onEvent={onEvent}
          onLastEventIdChange={vi.fn()}
          onPermissionDenied={vi.fn()}
          taskId="task-1"
          taskVersion="2026-07-20T10:03:00Z"
        />,
      )
      rendered.rerender(
        <TaskEventObserver
          documentId="document-1"
          knowledgeSpaceId="space-1"
          onEvent={onEvent}
          onLastEventIdChange={vi.fn()}
          onPermissionDenied={vi.fn()}
          taskId="task-1"
          taskVersion="2026-07-20T10:02:00Z"
        />,
      )
      await act(async () => vi.advanceTimersByTime(1000))

      expect(onEvent).toHaveBeenCalledWith(
        'task-1',
        '2026-07-20T10:03:00Z',
        expect.objectContaining({ event: 'terminal' }),
      )
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('rejects stale active progress and backs off repeated stale reconnects', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'stale-active-progress',
              state: 'queued',
              updatedAt: '2026-07-20T10:05:00Z',
            }),
          ],
        },
      ],
    }
    let streamCount = 0
    streamProcessingTaskEvents.mockImplementation(async function* () {
      streamCount += 1
      yield {
        data: {
          progressPercent: streamCount < 3 ? 40 : 60,
          stage: 'parsed' as const,
          state: 'running' as const,
          updatedAt: streamCount < 3 ? '2026-07-20T10:04:00Z' : '2026-07-20T10:06:00Z',
        },
        event: 'progress' as const,
        id: `stale-active-progress:${streamCount}`,
      }
      await new Promise<void>(() => {})
    })

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
      expect(
        within(screen.getByRole('row', { name: /sso-enterprise\.pdf/ })).getByText(
          'dataset.newKnowledge.documentStatus.queued',
        ),
      ).toBeInTheDocument()

      await act(async () => vi.advanceTimersByTime(1000))
      expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2)
      await act(async () => vi.advanceTimersByTime(1000))
      expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(2)
      await act(async () => vi.advanceTimersByTime(1000))
      expect(streamProcessingTaskEvents).toHaveBeenCalledTimes(3)
      expect(
        within(screen.getByRole('row', { name: /sso-enterprise\.pdf/ })).getByText(
          'dataset.newKnowledge.documentStatus.processing',
        ),
      ).toBeInTheDocument()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('keeps rotation membership stable when progress updates task timestamps', async () => {
    vi.useFakeTimers()
    const callsByTask = new Map<string, number>()
    streamProcessingTaskEvents.mockImplementation(async function* ({ taskId }: { taskId: string }) {
      callsByTask.set(taskId, (callsByTask.get(taskId) ?? 0) + 1)
      yield {
        data: {
          progressPercent: 50,
          stage: 'parsed' as const,
          state: 'running' as const,
          updatedAt: '2026-07-20T12:00:00Z',
        },
        event: 'progress' as const,
        id: `${taskId}:progress`,
      }
      await new Promise<void>(() => {})
    })
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: Array.from({ length: 12 }, (_, index) =>
            task({
              createdAt: new Date(Date.UTC(2026, 6, 20, 9, index)).toISOString(),
              id: `stable-${index}`,
              updatedAt: new Date(Date.UTC(2026, 6, 20, 10, index)).toISOString(),
            }),
          ),
        },
      ],
    }

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      await act(async () => vi.advanceTimersByTime(5000))
      await act(async () => {})

      expect(callsByTask).toHaveLength(12)
      expect([...callsByTask.values()]).toEqual(Array.from({ length: 12 }, () => 1))
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('preserves the latest progress version when a rotated stream returns terminal-only', async () => {
    vi.useFakeTimers()
    const streamCounts = new Map<string, number>()
    streamProcessingTaskEvents.mockImplementation(async function* ({ taskId }: { taskId: string }) {
      const streamCount = (streamCounts.get(taskId) ?? 0) + 1
      streamCounts.set(taskId, streamCount)
      if (taskId === 'rotated-terminal' && streamCount === 1) {
        yield {
          data: {
            progressPercent: 70,
            stage: 'nodes_generated' as const,
            state: 'running' as const,
            updatedAt: '2026-07-20T10:05:00Z',
          },
          event: 'progress' as const,
          id: 'rotated-terminal:progress',
        }
      } else if (taskId === 'rotated-terminal') {
        yield {
          data: { errorCode: 'ROTATED_FAILURE', state: 'failed' as const },
          event: 'terminal' as const,
          id: 'rotated-terminal:terminal',
        }
        return
      }
      await new Promise<void>(() => {})
    })
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({ id: 'rotated-terminal', updatedAt: '2026-07-20T10:01:00Z' }),
            ...Array.from({ length: 12 }, (_, index) =>
              task({ id: `rotation-peer-${index}`, updatedAt: '2026-07-20T10:01:00Z' }),
            ),
          ],
        },
      ],
    }
    getTaskSnapshot.mockResolvedValue(
      task({
        errorCode: 'ROTATED_FAILURE',
        id: 'rotated-terminal',
        state: 'failed',
        updatedAt: '2026-07-20T10:05:00Z',
      }),
    )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => {})
      await act(async () => vi.advanceTimersByTime(5000))
      await act(async () => vi.advanceTimersByTime(5000))
      await act(async () => {})

      expect(streamCounts.get('rotated-terminal')).toBe(2)
      expect(toastMock.error).toHaveBeenCalledWith('dataset.newKnowledge.taskFailedNotification')
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('relies on bounded reconnect backoff instead of invalidating queries on stream closure', async () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'closed-stream' })] }] }
    streamProcessingTaskEvents.mockImplementation(async function* () {})

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalledOnce())
    expect(queryClient.invalidateQueries).not.toHaveBeenCalled()
  })

  it('polls failed tasks through a bounded exact-snapshot pool for external retries', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'external-retry', state: 'failed' })] }],
    }
    getTaskSnapshot.mockResolvedValue(
      task({
        id: 'external-retry',
        progressPercent: 0,
        state: 'dispatch_pending',
        updatedAt: '2026-07-20T10:03:00Z',
      }),
    )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()
      await act(async () => {})
      expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('keeps one failed-task poll in flight and ignores it after a local retry', async () => {
    vi.useFakeTimers()
    let resolvePoll: ((snapshot: DocumentProcessingTask) => void) | undefined
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'slow-failed-poll', state: 'failed' })] }],
    }
    getTaskSnapshot.mockReturnValue(
      new Promise<DocumentProcessingTask>((resolve) => {
        resolvePoll = resolve
      }),
    )
    retryMutation.mutateAsync.mockResolvedValue(
      task({
        id: 'slow-failed-poll',
        progressPercent: 0,
        state: 'dispatch_pending',
        updatedAt: '2026-07-20T10:03:00Z',
      }),
    )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      fireEvent.click(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      )
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()
      fireEvent.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))
      await act(async () => {})
      expect(
        screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
      ).toBeInTheDocument()

      await act(async () => {
        resolvePoll?.(
          task({
            errorCode: 'OLD_FAILURE',
            id: 'slow-failed-poll',
            state: 'failed',
            updatedAt: '2026-07-20T10:01:00Z',
          }),
        )
      })

      expect(
        screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
      ).toBeInTheDocument()
      expect(screen.queryByText('OLD_FAILURE')).not.toBeInTheDocument()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('ignores a delayed permanent poll rejection after a local retry', async () => {
    vi.useFakeTimers()
    let rejectPoll: ((error: unknown) => void) | undefined
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'rejected-failed-poll', state: 'failed' })] }],
    }
    getTaskSnapshot
      .mockReturnValueOnce(
        new Promise<DocumentProcessingTask>((_resolve, reject) => {
          rejectPoll = reject
        }),
      )
      .mockResolvedValueOnce(
        task({
          id: 'rejected-failed-poll',
          progressPercent: 0,
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:05:00Z',
        }),
      )
    retryMutation.mutateAsync.mockResolvedValue(
      task({
        id: 'rejected-failed-poll',
        progressPercent: 0,
        state: 'dispatch_pending',
        updatedAt: '2026-07-20T10:03:00Z',
      }),
    )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      fireEvent.click(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      )
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()
      fireEvent.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))
      await act(async () => {})
      await act(async () => rejectPoll?.(new Response(null, { status: 403 })))

      tasksQuery.data = {
        pages: [
          {
            items: [
              task({
                id: 'rejected-failed-poll',
                state: 'failed',
                updatedAt: '2026-07-20T10:04:00Z',
              }),
            ],
          },
        ],
      }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => vi.advanceTimersByTime(5000))

      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('does not repoll the same failed snapshot after document read recovery', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'forbidden-failed-poll', state: 'failed' })] }],
    }
    getTaskSnapshot.mockRejectedValue(new Response(null, { status: 403 }))

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()
      tasksQuery.data = {
        pages: [{ items: [task({ id: 'forbidden-failed-poll', state: 'failed' })] }],
      }
      tasksQuery.dataUpdateCount += 1
      act(() => notifyTaskQuerySuccess())
      await act(async () => vi.advanceTimersByTime(60000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('accepts a same-version active list lifecycle after a failed poll is denied', async () => {
    vi.useFakeTimers()
    const taskVersion = '2026-07-20T10:01:00Z'
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.dataUpdateCount = 1
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({ id: 'denied-failed-poll-retry', state: 'failed', updatedAt: taskVersion }),
          ],
        },
      ],
    }
    getTaskSnapshot.mockRejectedValueOnce(new Response(null, { status: 403 }))
    streamProcessingTaskEvents.mockImplementation(async function* () {
      await new Promise<void>(() => {})
    })

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      tasksQuery.data = {
        pages: [
          {
            items: [
              task({ id: 'denied-failed-poll-retry', state: 'running', updatedAt: taskVersion }),
            ],
          },
        ],
      }
      tasksQuery.dataUpdateCount = 2
      act(() => notifyTaskQuerySuccess())
      await act(async () => {})

      expect(streamProcessingTaskEvents).toHaveBeenCalledOnce()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('advances failed-task polling when a snapshot request never settles', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: Array.from({ length: 7 }, (_, index) =>
            task({ id: `failed-${index}`, state: 'failed' }),
          ),
        },
      ],
    }
    getTaskSnapshot.mockImplementation((input: { params: { taskId: string } }) => {
      if (input.params.taskId !== 'failed-0') return new Promise<DocumentProcessingTask>(() => {})
      return Promise.resolve(
        task({
          id: 'failed-0',
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:02:00Z',
        }),
      )
    })

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledTimes(6)
      await act(async () => vi.advanceTimersByTime(3000))
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledTimes(12)
      expect(getTaskSnapshot).toHaveBeenCalledWith(
        expect.objectContaining({ params: expect.objectContaining({ taskId: 'failed-0' }) }),
        expect.anything(),
      )
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('forgets permanent poll failures after a task leaves the cursor result', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'removed-failed-task', state: 'failed' })] }],
    }
    getTaskSnapshot.mockRejectedValue(new Response(null, { status: 403 }))

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      tasksQuery.data = { pages: [{ items: [] }] }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => {})
      tasksQuery.data = {
        pages: [{ items: [task({ id: 'removed-failed-task', state: 'failed' })] }],
      }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => vi.advanceTimersByTime(5000))

      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('ignores an older active snapshot while polling a newer failed task', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              errorCode: 'LATEST_FAILURE',
              id: 'stale-active-poll',
              state: 'failed',
              updatedAt: '2026-07-20T10:03:00.123789Z',
            }),
          ],
        },
      ],
    }
    getTaskSnapshot.mockResolvedValue(
      task({
        id: 'stale-active-poll',
        state: 'dispatch_pending',
        updatedAt: '2026-07-20T10:03:00.123456Z',
      }),
    )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()
      expect(streamProcessingTaskEvents).not.toHaveBeenCalled()
      fireEvent.click(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      )
      expect(screen.getByText('LATEST_FAILURE')).toBeInTheDocument()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('accepts a terminal-only stream after an exact snapshot observes an external retry', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              errorCode: 'OLD_FAILURE',
              id: 'terminal-only-external-retry',
              state: 'failed',
              updatedAt: '2026-07-20T10:01:00Z',
            }),
          ],
        },
      ],
    }
    getTaskSnapshot
      .mockResolvedValueOnce(
        task({
          id: 'terminal-only-external-retry',
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )
      .mockResolvedValueOnce(
        task({
          errorCode: 'NEW_FAILURE',
          id: 'terminal-only-external-retry',
          state: 'failed',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: { errorCode: 'NEW_FAILURE', state: 'failed' as const },
        event: 'terminal' as const,
        id: 'terminal-only-external-retry:terminal',
      }
    })

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      await act(async () => {})
      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
      fireEvent.click(
        screen.getByRole('button', {
          name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
        }),
      )
      expect(screen.getByText('NEW_FAILURE')).toBeInTheDocument()
      expect(
        screen.queryByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
      ).not.toBeInTheDocument()
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('unblocks failed-task polling after an active list snapshot without a terminal pin', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'list-retry-after-block', state: 'failed' })] }],
    }
    getTaskSnapshot
      .mockRejectedValueOnce(new Response(null, { status: 403 }))
      .mockResolvedValueOnce(
        task({
          id: 'list-retry-after-block',
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:05:00Z',
        }),
      )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      tasksQuery.data = {
        pages: [
          {
            items: [
              task({
                id: 'list-retry-after-block',
                state: 'dispatch_pending',
                updatedAt: '2026-07-20T10:03:00Z',
              }),
            ],
          },
        ],
      }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => {})
      tasksQuery.data = {
        pages: [
          {
            items: [
              task({
                id: 'list-retry-after-block',
                state: 'failed',
                updatedAt: '2026-07-20T10:04:00Z',
              }),
            ],
          },
        ],
      }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => vi.advanceTimersByTime(5000))

      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('retries exact polling when a permanently blocked failed task advances version', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [
            task({
              id: 'new-failed-generation',
              state: 'failed',
              updatedAt: '2026-07-20T10:01:00Z',
            }),
          ],
        },
      ],
    }
    getTaskSnapshot
      .mockRejectedValueOnce(new Response(null, { status: 403 }))
      .mockResolvedValueOnce(
        task({
          id: 'new-failed-generation',
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:03:00Z',
        }),
      )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      tasksQuery.data = {
        pages: [
          {
            items: [
              task({
                id: 'new-failed-generation',
                state: 'failed',
                updatedAt: '2026-07-20T10:02:00Z',
              }),
            ],
          },
        ],
      }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => vi.advanceTimersByTime(5000))

      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('aborts an in-flight failed-task poll on unmount', async () => {
    vi.useFakeTimers()
    let pollSignal: AbortSignal | undefined
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [{ items: [task({ id: 'aborted-failed-poll', state: 'failed' })] }],
    }
    getTaskSnapshot.mockImplementation(
      (_input: unknown, options: { signal: AbortSignal }) =>
        new Promise<DocumentProcessingTask>((_resolve, reject) => {
          pollSignal = options.signal
          options.signal.addEventListener(
            'abort',
            () => reject(new DOMException('Aborted', 'AbortError')),
            { once: true },
          )
        }),
    )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(pollSignal?.aborted).toBe(false)
      rendered.unmount()
      expect(pollSignal?.aborted).toBe(true)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('renders large document results in bounded batches', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [
        {
          items: Array.from({ length: 150 }, (_, index) =>
            document({ id: `document-${index}`, title: `Document ${index}` }),
          ),
        },
      ],
    }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getAllByRole('row')).toHaveLength(101)
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.loadMore' }))
    expect(screen.getAllByRole('row')).toHaveLength(151)
    expect(screen.getByRole('table').parentElement).toHaveFocus()
  })

  it('eagerly exhausts task and source cursor pages for accurate labels', () => {
    documentsQuery.data = {
      pages: [{ items: [document({ sourceId: 'source-on-later-page' })] }],
    }
    tasksQuery.data = { pages: [{ items: [], nextCursor: 'next' }] }
    tasksQuery.hasNextPage = true
    sourcesQuery.data = { pages: [{ items: [], nextCursor: 'source-next' }] }
    sourcesQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(tasksQuery.fetchNextPage).toHaveBeenCalledOnce()
    expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
  })

  it('keeps filtered results partial while task-dependent statuses have more pages', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: Array.from({ length: 20 }, () => ({ items: [], nextCursor: 'next' })),
    }
    tasksQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.selectOptions(screen.getByRole('combobox'), 'failed')

    expect(screen.queryByText('dataset.newKnowledge.noMatchingDocuments')).not.toBeInTheDocument()
    expect(screen.getByText('dataset.newKnowledge.partialDocumentResults')).toBeInTheDocument()
  })

  it('keeps source-name searches partial while unresolved sources have more pages', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: [
        {
          items: [document({ sourceId: 'later-source', userMetadata: {} })],
        },
      ],
    }
    sourcesQuery.data = {
      pages: Array.from({ length: 20 }, () => ({ items: [], nextCursor: 'next' })),
    }
    sourcesQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.type(
      screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchDocuments' }),
      'source title from next page',
    )

    expect(screen.queryByText('dataset.newKnowledge.noMatchingDocuments')).not.toBeInTheDocument()
    expect(screen.getByText('dataset.newKnowledge.partialDocumentResults')).toBeInTheDocument()
  })

  it('bounds automatic cursor exhaustion and leaves all further loading explicit', async () => {
    const user = userEvent.setup()
    documentsQuery.data = {
      pages: Array.from({ length: 20 }, (_, index) => ({
        items: index === 0 ? [document({ sourceId: 'source-on-later-page' })] : [],
        nextCursor: 'next',
      })),
    }
    documentsQuery.hasNextPage = true
    tasksQuery.data = {
      pages: Array.from({ length: 20 }, () => ({ items: [], nextCursor: 'next' })),
    }
    tasksQuery.hasNextPage = true
    sourcesQuery.data = {
      pages: Array.from({ length: 20 }, (_, index) => ({
        items: index === 0 ? [source()] : [],
        nextCursor: 'next',
      })),
    }
    sourcesQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.type(
      screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchDocuments' }),
      'sso',
    )

    expect(documentsQuery.fetchNextPage).not.toHaveBeenCalled()
    expect(screen.getByText('dataset.newKnowledge.partialDocumentResults')).toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-describedby',
      'partial-document-results',
    )
    const loadMore = screen.getByRole('button', { name: 'dataset.newKnowledge.loadMore' })
    expect(loadMore).toBeInTheDocument()
    await user.clear(screen.getByRole('searchbox'))
    await user.type(screen.getByRole('searchbox'), 'missing')
    expect(screen.queryByText('dataset.newKnowledge.noMatchingDocuments')).not.toBeInTheDocument()
    expect(screen.getByText('dataset.newKnowledge.partialDocumentResults')).toBeInTheDocument()
    await user.click(loadMore)
    expect(documentsQuery.fetchNextPage).toHaveBeenCalledOnce()
    expect(tasksQuery.fetchNextPage).toHaveBeenCalledOnce()
    expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
  })

  it('keeps explicit result pagination busy while a dependency page is loading', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: Array.from({ length: 20 }, () => ({ items: [], nextCursor: 'next' })),
    }
    tasksQuery.hasNextPage = true
    tasksQuery.isFetchingNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('button', { name: 'dataset.newKnowledge.loadMore' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
  })

  it('keeps known documents actionable while older task and source pages remain', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: Array.from({ length: 20 }, () => ({ items: [], nextCursor: 'next' })),
    }
    tasksQuery.hasNextPage = true
    sourcesQuery.data = {
      pages: Array.from({ length: 20 }, (_, index) => ({
        items: index === 0 ? [source()] : [],
        nextCursor: 'next',
      })),
    }
    sourcesQuery.hasNextPage = true

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).not.toHaveAttribute(
      'aria-disabled',
    )
    expect(
      within(screen.getByRole('row', { name: /sso-enterprise\.pdf/ })).getByText(
        'dataset.newKnowledge.documentStatus.ready',
      ),
    ).toBeInTheDocument()
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.loadMore' }),
    ).toBeInTheDocument()
  })

  it('locks cached documents when the task query returns a permission denial', () => {
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }
    tasksQuery.error = { status: 403 }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    expect(screen.getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.documentsPermissionTitle',
    )
    expect(screen.queryByText('sso-enterprise.pdf')).not.toBeInTheDocument()
    expect(screen.queryByLabelText('dataset.newKnowledge.uploadDocuments')).not.toBeInTheDocument()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}' }),
    ).not.toBeInTheDocument()
  })

  it('retires a failed task action before background invalidation settles', async () => {
    const user = userEvent.setup()
    retryMutation.mutateAsync.mockRejectedValueOnce(new Error('retry failed'))
    queryClient.invalidateQueries.mockImplementation(() => new Promise<void>(() => {}))
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task({ id: 'failed', state: 'failed' })] }] }

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))

    expect(await screen.findByText('dataset.newKnowledge.taskActionFailed')).toBeInTheDocument()
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }),
    ).not.toHaveAttribute('aria-disabled', 'true')
  })

  it('trusts same-version stream progress after a local retry', async () => {
    const user = userEvent.setup()
    const retryVersion = '2026-07-20T10:01:00Z'
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'same-version-retry', state: 'failed', updatedAt: retryVersion })],
        },
      ],
    }
    retryMutation.mutateAsync.mockResolvedValue(
      task({ id: 'same-version-retry', state: 'queued', updatedAt: retryVersion }),
    )
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: {
          progressPercent: 20,
          stage: 'parsed' as const,
          state: 'running' as const,
          updatedAt: retryVersion,
        },
        event: 'progress' as const,
        id: 'same-version-retry:running',
      }
      await new Promise<void>(() => {})
    })

    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))

    await waitFor(() => expect(streamProcessingTaskEvents).toHaveBeenCalled())
    expect(
      await screen.findByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'dataset.newKnowledge.retryTask' })).toBeNull()
  })

  it('retries a permission-blocked failed-task poll after permission returns', async () => {
    vi.useFakeTimers()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'permission-blocked-poll', state: 'failed' })],
        },
      ],
    }
    getTaskSnapshot
      .mockRejectedValueOnce(new Response(null, { status: 403 }))
      .mockResolvedValueOnce(
        task({
          id: 'permission-blocked-poll',
          state: 'dispatch_pending',
          updatedAt: '2026-07-20T10:02:00Z',
        }),
      )

    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    try {
      await act(async () => vi.advanceTimersByTime(5000))
      expect(getTaskSnapshot).toHaveBeenCalledOnce()

      documentsQuery.error = { status: 403 }
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      documentsQuery.error = null
      rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
      await act(async () => vi.advanceTimersByTime(5000))

      expect(getTaskSnapshot).toHaveBeenCalledTimes(2)
    } finally {
      rendered.unmount()
      vi.useRealTimers()
    }
  })

  it('restores document focus when a focused bulk toolbar is cleared', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' }))
    await user.click(
      screen.getByRole('button', { name: 'dataset.newKnowledge.clearDocumentSelection' }),
    )

    await waitFor(() =>
      expect(screen.getByRole('heading', { name: 'dataset.newKnowledge.documents' })).toHaveFocus(),
    )
    rendered.unmount()
  })

  it('keeps upload and re-index actions focusable and unavailable while pending', async () => {
    const user = userEvent.setup()
    uploadMutation.mutateAsync.mockImplementation(() => new Promise(() => {}))
    const emptyPage = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.upload(
      screen.getByLabelText('dataset.newKnowledge.uploadDocuments'),
      new File(['one'], 'one.md', { type: 'text/markdown' }),
    )
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' }),
    ).toHaveAttribute('aria-disabled', 'true')
    emptyPage.unmount()

    reindexMutation.mutateAsync.mockImplementation(() => new Promise(() => {}))
    documentsQuery.data = { pages: [{ items: [document()] }] }
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' }))
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.reindexDocuments' }))
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.reindexDocuments' }),
    ).toHaveAttribute('aria-disabled', 'true')
  })

  it('locks uploads after a write mutation reveals revoked permission', async () => {
    const user = userEvent.setup()
    uploadMutation.mutateAsync.mockRejectedValueOnce(new Response(null, { status: 403 }))
    permissionStateMock.refreshAfterDenial.mockResolvedValueOnce({
      data: { dataset: { default_permission_keys: ['dataset.acl.readonly'] } },
      error: null,
    })
    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    const addDocument = screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' })
    await user.click(addDocument)
    expect(addDocument).toHaveFocus()

    await user.upload(
      screen.getByLabelText('dataset.newKnowledge.uploadDocuments'),
      new File(['one'], 'one.md', { type: 'text/markdown' }),
    )

    await waitFor(() => expect(permissionStateMock.refreshAfterDenial).toHaveBeenCalledOnce())
    expect(toastMock.error).not.toHaveBeenCalledWith('dataset.newKnowledge.documentUploadFailed')
    const restriction = screen.getByText('dataset.newKnowledge.documentPermissionRestricted')
    expect(restriction).toBeInTheDocument()
    expect(restriction).toHaveAttribute('role', 'status')
    expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' })).toBeDisabled()
    expect(screen.getByRole('heading', { name: 'dataset.newKnowledge.documents' })).toHaveFocus()

    permissionStateMock.datasetKeys = ['dataset.acl.readonly']
    permissionStateMock.fetching = true
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    permissionStateMock.fetching = false
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' })).toBeDisabled()

    permissionStateMock.datasetKeys = ['dataset.acl.edit']
    permissionStateMock.fetching = true
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    permissionStateMock.fetching = false
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() =>
      expect(
        screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' }),
      ).toBeEnabled(),
    )
  })

  it('preserves a newer focus target when an upload later loses write permission', async () => {
    const user = userEvent.setup()
    let rejectUpload!: (error: unknown) => void
    uploadMutation.mutateAsync.mockImplementation(
      () =>
        new Promise((_resolve, reject) => {
          rejectUpload = reject
        }),
    )
    permissionStateMock.refreshAfterDenial.mockResolvedValueOnce({
      data: { dataset: { default_permission_keys: ['dataset.acl.readonly'] } },
      error: null,
    })
    tasksQuery.data = { pages: [{ items: [task()] }] }
    render(<DocumentsPage knowledgeSpaceId="space-1" />)

    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addDocument' }))
    await user.upload(
      screen.getByLabelText('dataset.newKnowledge.uploadDocuments'),
      new File(['one'], 'one.md', { type: 'text/markdown' }),
    )
    const tasksButton = screen.getByRole('button', {
      name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
    })
    tasksButton.focus()
    expect(tasksButton).toHaveFocus()

    await act(async () => rejectUpload(new Response(null, { status: 403 })))
    await waitFor(() => expect(permissionStateMock.refreshAfterDenial).toHaveBeenCalledOnce())

    expect(tasksButton).toHaveFocus()
  })

  it('locks re-indexing after a write mutation reveals revoked permission', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    reindexMutation.mutateAsync.mockRejectedValueOnce(new Response(null, { status: 403 }))
    permissionStateMock.refreshAfterDenial.mockResolvedValueOnce({
      data: { dataset: { default_permission_keys: ['dataset.acl.readonly'] } },
      error: null,
    })
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' }))
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.reindexDocuments' }))

    await waitFor(() => expect(permissionStateMock.refreshAfterDenial).toHaveBeenCalledOnce())
    expect(toastMock.error).not.toHaveBeenCalledWith('dataset.newKnowledge.documentsReindexFailed')
    expect(
      screen.queryByRole('group', { name: 'dataset.newKnowledge.bulkDocumentActions' }),
    ).not.toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'sso-enterprise.pdf' })).toHaveAttribute(
      'aria-disabled',
      'true',
    )
    expect(screen.getByRole('heading', { name: 'dataset.newKnowledge.documents' })).toHaveFocus()
  })

  it('locks task actions after a write mutation reveals revoked permission', async () => {
    const user = userEvent.setup()
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }
    cancelMutation.mutateAsync.mockRejectedValueOnce(new Response(null, { status: 403 }))
    permissionStateMock.refreshAfterDenial.mockResolvedValueOnce({
      data: { dataset: { default_permission_keys: ['dataset.acl.readonly'] } },
      error: null,
    })
    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }))

    await waitFor(() => expect(permissionStateMock.refreshAfterDenial).toHaveBeenCalledOnce())
    expect(
      within(screen.getByRole('dialog')).getByText(
        'dataset.newKnowledge.documentPermissionRestricted',
      ),
    ).toBeInTheDocument()
    expect(screen.queryByText('dataset.newKnowledge.taskActionFailed')).not.toBeInTheDocument()
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).not.toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'common.operation.close' })).toHaveFocus()

    permissionStateMock.error = new Error('permission refresh failed')
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)
    const dialog = screen.getByRole('dialog')
    expect(
      within(dialog).getByText('dataset.newKnowledge.documentPermissionRestricted'),
    ).toBeInTheDocument()
    expect(within(dialog).getByRole('alert')).toHaveTextContent(
      'dataset.newKnowledge.permissionLoadFailed',
    )
  })

  it('does not let an older permission refresh release the latest write lock', async () => {
    const user = userEvent.setup()
    let rejectUpload: ((reason: unknown) => void) | undefined
    let rejectCancel: ((reason: unknown) => void) | undefined
    let resolveFirstRefresh: ((value: unknown) => void) | undefined
    let resolveSecondRefresh: ((value: unknown) => void) | undefined
    uploadMutation.mutateAsync.mockImplementationOnce(
      () =>
        new Promise((_resolve, reject) => {
          rejectUpload = reject
        }),
    )
    cancelMutation.mutateAsync.mockImplementationOnce(
      () =>
        new Promise((_resolve, reject) => {
          rejectCancel = reject
        }),
    )
    permissionStateMock.refreshAfterDenial
      .mockImplementationOnce(
        () =>
          new Promise((resolve) => {
            resolveFirstRefresh = resolve
          }),
      )
      .mockImplementationOnce(
        () =>
          new Promise((resolve) => {
            resolveSecondRefresh = resolve
          }),
      )
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = { pages: [{ items: [task()] }] }
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.interruptTask' }))
    fireEvent.change(screen.getByLabelText('dataset.newKnowledge.uploadDocuments'), {
      target: { files: [new File(['one'], 'one.md', { type: 'text/markdown' })] },
    })
    expect(cancelMutation.mutateAsync).toHaveBeenCalledOnce()
    expect(uploadMutation.mutateAsync).toHaveBeenCalledOnce()

    await act(async () => rejectUpload?.(new Response(null, { status: 403 })))
    await waitFor(() => expect(permissionStateMock.refreshAfterDenial).toHaveBeenCalledOnce())
    await act(async () => rejectCancel?.(new Response(null, { status: 403 })))
    await waitFor(() => expect(permissionStateMock.refreshAfterDenial).toHaveBeenCalledTimes(2))

    await act(async () =>
      resolveFirstRefresh?.({
        data: { dataset: { default_permission_keys: ['dataset.acl.edit'] } },
        error: null,
      }),
    )
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).not.toBeInTheDocument()

    await act(async () =>
      resolveSecondRefresh?.({
        data: { dataset: { default_permission_keys: ['dataset.acl.readonly'] } },
        error: null,
      }),
    )
    expect(
      screen.queryByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).not.toBeInTheDocument()
  })

  it('observes a structurally shared same-version retry when response timestamps collide', async () => {
    const user = userEvent.setup()
    const taskVersion = '2026-07-20T10:03:00Z'
    const sharedTaskData = {
      pages: [{ items: [task({ id: 'shared-active-retry', updatedAt: taskVersion })] }],
    }
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = sharedTaskData
    tasksQuery.dataUpdatedAt = 100
    tasksQuery.dataUpdateCount = 1
    getTaskSnapshot
      .mockRejectedValueOnce(new Error('snapshot unavailable'))
      .mockResolvedValueOnce(
        task({ id: 'shared-active-retry', state: 'running', updatedAt: taskVersion }),
      )
    streamFailedTaskThenWait('shared-active-retry')
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(await screen.findByText('PARSER_FAILED')).toBeInTheDocument()
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())

    tasksQuery.data = sharedTaskData
    tasksQuery.dataUpdatedAt = 100
    tasksQuery.dataUpdateCount = 2
    act(() => notifyTaskQuerySuccess())

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledTimes(2))
    expect(
      await screen.findByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
  })

  it('rechecks a same-version active lifecycle after a denied terminal snapshot', async () => {
    const user = userEvent.setup()
    const taskVersion = '2026-07-20T10:03:00Z'
    const sharedTaskData = {
      pages: [{ items: [task({ id: 'denied-active-retry', updatedAt: taskVersion })] }],
    }
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = sharedTaskData
    tasksQuery.dataUpdatedAt = 100
    tasksQuery.dataUpdateCount = 1
    getTaskSnapshot
      .mockRejectedValueOnce(new Response(null, { status: 403 }))
      .mockResolvedValueOnce(
        task({ id: 'denied-active-retry', state: 'running', updatedAt: taskVersion }),
      )
    streamFailedTaskThenWait('denied-active-retry')
    render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledOnce())

    tasksQuery.data = sharedTaskData
    tasksQuery.dataUpdatedAt = 100
    tasksQuery.dataUpdateCount = 2
    act(() => notifyTaskQuerySuccess())

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalledTimes(2))
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    expect(
      await screen.findByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()
  })

  it('reconciles a same-version terminal list after trusted local retry progress', async () => {
    const user = userEvent.setup()
    const taskVersion = '2026-07-20T10:01:00Z'
    documentsQuery.data = { pages: [{ items: [document()] }] }
    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'trusted-terminal', state: 'failed', updatedAt: taskVersion })],
        },
      ],
    }
    retryMutation.mutateAsync.mockResolvedValue(
      task({ id: 'trusted-terminal', state: 'queued', updatedAt: taskVersion }),
    )
    getTaskSnapshot.mockResolvedValue(
      task({
        errorCode: 'TERMINAL_CONFIRMED',
        id: 'trusted-terminal',
        state: 'failed',
        updatedAt: taskVersion,
      }),
    )
    streamProcessingTaskEvents.mockImplementation(async function* () {
      yield {
        data: {
          progressPercent: 30,
          stage: 'parsed' as const,
          state: 'running' as const,
          updatedAt: taskVersion,
        },
        event: 'progress' as const,
        id: 'trusted-terminal:running',
      }
      await new Promise<void>(() => {})
    })
    const rendered = render(<DocumentsPage knowledgeSpaceId="space-1" />)
    await user.click(
      screen.getByRole('button', {
        name: 'dataset.newKnowledge.tasksWithAttention:{"count":1}',
      }),
    )
    await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }))
    expect(
      await screen.findByRole('button', { name: 'dataset.newKnowledge.interruptTask' }),
    ).toBeInTheDocument()

    tasksQuery.data = {
      pages: [
        {
          items: [task({ id: 'trusted-terminal', state: 'failed', updatedAt: taskVersion })],
        },
      ],
    }
    tasksQuery.dataUpdatedAt = 100
    rendered.rerender(<DocumentsPage knowledgeSpaceId="space-1" />)

    await waitFor(() => expect(getTaskSnapshot).toHaveBeenCalled())
    expect(await screen.findByText('TERMINAL_CONFIRMED')).toBeInTheDocument()
    expect(
      screen.getByRole('button', { name: 'dataset.newKnowledge.retryTask' }),
    ).toBeInTheDocument()
  })
})
