diff --git a/src/app/api/documents/route.ts b/src/app/api/documents/route.ts
index 1cdc2f6..fa34474 100644
--- a/src/app/api/documents/route.ts
+++ b/src/app/api/documents/route.ts
@@ -67,8 +67,6 @@ export async function GET(req: NextRequest) {
size: number;
lastModified: number;
filePath: string;
- parseState: string | null;
- parsedJsonKey: string | null;
}>;
const results: BaseDocument[] = rows.map((doc) => {
@@ -79,8 +77,6 @@ export async function GET(req: NextRequest) {
size: Number(doc.size),
lastModified: Number(doc.lastModified),
type,
- parseStatus: null,
- parsedJsonKey: null,
scope: 'user',
};
});
diff --git a/src/components/doclist/views/GalleryView.tsx b/src/components/doclist/views/GalleryView.tsx
index 0bc609e..6d2bf6a 100644
--- a/src/components/doclist/views/GalleryView.tsx
+++ b/src/components/doclist/views/GalleryView.tsx
@@ -28,14 +28,6 @@ function formatDateTime(value: number | undefined): string {
});
}
-function formatParseStatus(status: DocumentListDocument['parseStatus']): string {
- if (!status) return 'N/A';
- if (status === 'pending') return 'Pending';
- if (status === 'running') return 'Running';
- if (status === 'ready') return 'Ready';
- return 'Failed';
-}
-
function KindIcon({ doc, className }: { doc: DocumentListDocument; className?: string }) {
if (doc.type === 'pdf') return ;
if (doc.type === 'epub') return ;
@@ -244,12 +236,6 @@ export function GalleryView({
>
)}
- {activeDoc.type === 'pdf' && (
- <>
-
Parse status
- {formatParseStatus(activeDoc.parseStatus)}
- >
- )}
) : (
diff --git a/src/types/documents.ts b/src/types/documents.ts
index ac870d5..98802b5 100644
--- a/src/types/documents.ts
+++ b/src/types/documents.ts
@@ -7,8 +7,6 @@ export interface BaseDocument {
lastModified: number;
recentlyOpenedAt?: number;
type: DocumentType;
- parseStatus?: 'pending' | 'running' | 'ready' | 'failed' | null;
- parsedJsonKey?: string | null;
scope?: 'user';
folderId?: string;
}
diff --git a/tests/unit/pdf-parse-client-lifecycle.vitest.spec.ts b/tests/unit/pdf-parse-client-lifecycle.vitest.spec.ts
new file mode 100644
index 0000000..f661970
--- /dev/null
+++ b/tests/unit/pdf-parse-client-lifecycle.vitest.spec.ts
@@ -0,0 +1,169 @@
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+type SnapshotListener = (event: Event) => void;
+
+class MockMessageEvent extends Event {
+ data: string;
+
+ constructor(type: string, init: { data: string }) {
+ super(type);
+ this.data = init.data;
+ }
+}
+
+class MockEventSource {
+ static instances: MockEventSource[] = [];
+
+ readonly url: string;
+ readonly listeners = new Map();
+ closed = false;
+
+ constructor(url: string) {
+ this.url = url;
+ MockEventSource.instances.push(this);
+ }
+
+ addEventListener(type: string, listener: SnapshotListener): void {
+ const arr = this.listeners.get(type) ?? [];
+ arr.push(listener);
+ this.listeners.set(type, arr);
+ }
+
+ close(): void {
+ this.closed = true;
+ }
+
+ emit(type: string, payload: unknown): void {
+ const event = new MockMessageEvent(type, {
+ data: JSON.stringify(payload),
+ });
+ for (const listener of this.listeners.get(type) ?? []) {
+ listener(event);
+ }
+ }
+}
+
+describe('PDF parse client lifecycle', () => {
+ const originalFetch = global.fetch;
+ const originalEventSource = global.EventSource;
+ const originalMessageEvent = global.MessageEvent;
+
+ beforeEach(() => {
+ MockEventSource.instances = [];
+
+ const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = String(input);
+ const method = init?.method ?? 'GET';
+
+ if (url.endsWith('/api/documents/doc-1/parsed') && method === 'GET') {
+ fetchMock.getCount = (fetchMock.getCount ?? 0) + 1;
+ if (fetchMock.getCount === 1) {
+ return new Response(JSON.stringify({
+ parseStatus: 'pending',
+ parseProgress: null,
+ opId: null,
+ }), {
+ status: 409,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ return new Response(JSON.stringify({
+ documentId: 'doc-1',
+ pages: [],
+ }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ if (url.endsWith('/api/documents/doc-1/parsed') && method === 'POST') {
+ return new Response(JSON.stringify({
+ parseStatus: 'pending',
+ parseProgress: null,
+ opId: 'op-1',
+ }), {
+ status: 202,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ throw new Error(`Unexpected fetch: ${method} ${url}`);
+ }) as typeof fetch & { getCount?: number };
+
+ global.fetch = fetchMock;
+ global.EventSource = MockEventSource as unknown as typeof EventSource;
+ global.MessageEvent = MockMessageEvent as unknown as typeof MessageEvent;
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ global.EventSource = originalEventSource;
+ global.MessageEvent = originalMessageEvent;
+ });
+
+ test('follows not-ready -> ensure op -> SSE snapshots -> ready artifact', async () => {
+ const {
+ ParsedPdfNotReadyError,
+ ensureParsedPdfDocumentOperation,
+ getParsedPdfDocument,
+ subscribeParsedPdfDocumentEvents,
+ } = await import('../../src/lib/client/api/documents');
+
+ let initialError: unknown;
+ try {
+ await getParsedPdfDocument('doc-1');
+ } catch (error) {
+ initialError = error;
+ }
+
+ expect(initialError).toBeInstanceOf(ParsedPdfNotReadyError);
+ expect((initialError as InstanceType).parseStatus).toBe('pending');
+
+ const ensured = await ensureParsedPdfDocumentOperation('doc-1');
+ expect(ensured).toMatchObject({
+ parseStatus: 'pending',
+ opId: 'op-1',
+ });
+
+ const snapshots: Array<{ parseStatus: string; opId?: string | null }> = [];
+ const unsubscribe = subscribeParsedPdfDocumentEvents('doc-1', { opId: 'op-1' }, {
+ onSnapshot: (snapshot) => {
+ snapshots.push({
+ parseStatus: snapshot.parseStatus,
+ opId: snapshot.opId,
+ });
+ },
+ });
+
+ expect(MockEventSource.instances).toHaveLength(1);
+ expect(MockEventSource.instances[0]?.url).toBe('/api/documents/doc-1/parsed/events?opId=op-1');
+
+ MockEventSource.instances[0]?.emit('snapshot', {
+ eventId: 1,
+ snapshot: {
+ opId: 'op-1',
+ status: 'running',
+ progress: { totalPages: 5, pagesParsed: 2, currentPage: 3, phase: 'infer' },
+ },
+ });
+ MockEventSource.instances[0]?.emit('snapshot', {
+ eventId: 2,
+ snapshot: {
+ opId: 'op-1',
+ status: 'succeeded',
+ },
+ });
+
+ expect(snapshots).toEqual([
+ { parseStatus: 'running', opId: 'op-1' },
+ { parseStatus: 'ready', opId: 'op-1' },
+ ]);
+
+ unsubscribe();
+ expect(MockEventSource.instances[0]?.closed).toBe(true);
+
+ const parsed = await getParsedPdfDocument('doc-1');
+ expect(parsed).toMatchObject({ documentId: 'doc-1' });
+ });
+});
diff --git a/tests/unit/pdf-parsed-events-route-legacy-backfill.vitest.spec.ts b/tests/unit/pdf-parsed-events-route-worker-proxy.vitest.spec.ts
similarity index 98%
rename from tests/unit/pdf-parsed-events-route-legacy-backfill.vitest.spec.ts
rename to tests/unit/pdf-parsed-events-route-worker-proxy.vitest.spec.ts
index c86f679..87fd852 100644
--- a/tests/unit/pdf-parsed-events-route-legacy-backfill.vitest.spec.ts
+++ b/tests/unit/pdf-parsed-events-route-worker-proxy.vitest.spec.ts
@@ -57,7 +57,7 @@ vi.mock('@/lib/server/logger', () => ({
})),
}));
-describe('GET /api/documents/[id]/parsed/events worker proxy', () => {
+describe('GET /api/documents/[id]/parsed/events worker event proxy', () => {
const originalFetch = global.fetch;
beforeEach(() => {
diff --git a/tests/unit/pdf-parsed-route-legacy-backfill.vitest.spec.ts b/tests/unit/pdf-parsed-route-worker-flow.vitest.spec.ts
similarity index 98%
rename from tests/unit/pdf-parsed-route-legacy-backfill.vitest.spec.ts
rename to tests/unit/pdf-parsed-route-worker-flow.vitest.spec.ts
index ae1e6d3..13fa85b 100644
--- a/tests/unit/pdf-parsed-route-legacy-backfill.vitest.spec.ts
+++ b/tests/unit/pdf-parsed-route-worker-flow.vitest.spec.ts
@@ -76,7 +76,7 @@ vi.mock('@/lib/server/logger', () => ({
})),
}));
-describe('GET/POST /api/documents/[id]/parsed worker-owned flow', () => {
+describe('GET/POST /api/documents/[id]/parsed worker flow', () => {
beforeEach(() => {
hoisted.db = {
select: vi.fn(() => ({