Conversation
When a PDF was imported via the Chrome extension, the workspace card
would flicker between reading and done states. Two bugs caused this:
1. persistOcrResults wrote BULK_ITEMS_PATCHED to the DB but never
broadcast it over Supabase Realtime, so connected clients only
learned of OCR completion via a manual refetch — not live. Fixed by
broadcasting the BULK_ITEMS_PATCHED event via service role key after
the DB write, mirroring the pattern the import route uses for
BULK_ITEMS_CREATED. The DB-assigned version is now returned from
appendWorkspaceEvent and included in the broadcast payload so the
client cache maintains correct event ordering.
2. refetchOnWindowFocus: 'always' caused a stale refetch (started
before OCR completed) to race the realtime broadcast and overwrite
done back to processing. Since both BULK_ITEMS_CREATED and
BULK_ITEMS_PATCHED are now broadcast server-side, the safety-net
refetch is unnecessary and has been disabled.
iMerge branch 'main' into main_chrome_ext_fix
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdded comprehensive development documentation (CLAUDE.md), implemented workspace folder retrieval and file import APIs with OCR triggering, enhanced realtime event broadcasting for OCR result persistence, improved workspace state loading with folder filtering, updated styling in layout components, and streamlined self-event filtering in realtime synchronization. Changes
Sequence DiagramssequenceDiagram
actor Client
participant ImportAPI as Import API<br/>(POST /import)
participant WorkspaceState as Workspace State<br/>(state-loader)
participant Database as Database<br/>(append_workspace_event)
participant RealtimeDB as Realtime<br/>(Supabase)
participant OCRStartAPI as OCR Start API<br/>(/api/ocr/start)
Client->>ImportAPI: POST files with metadata
ImportAPI->>ImportAPI: Validate files & payload
ImportAPI->>WorkspaceState: loadWorkspaceState(id)
WorkspaceState->>Database: Fetch latest snapshot & events
Database-->>WorkspaceState: Return current state
ImportAPI->>ImportAPI: Resolve name conflicts, build items
ImportAPI->>Database: appendBulkItemsWithRetry()
Database-->>ImportAPI: Return version
ImportAPI->>RealtimeDB: Broadcast workspace:id:events
RealtimeDB-->>ImportAPI: Acknowledge
ImportAPI->>OCRStartAPI: Trigger OCR for items
OCRStartAPI-->>ImportAPI: Return ocrRunId
ImportAPI-->>Client: { success, itemIds, ocrRunId }
sequenceDiagram
participant OCRStep as OCR Workflow Step<br/>(persist-results)
participant Database as Database<br/>(append_workspace_event)
participant RealtimeService as Realtime Service<br/>(Supabase)
participant Client as Connected Clients
OCRStep->>Database: appendWorkspaceEvent(OCR_COMPLETED)
Database-->>OCRStep: version (number)
OCRStep->>RealtimeService: Initialize client & send broadcast
RealtimeService->>RealtimeService: Publish on workspace:id:events
RealtimeService-->>Client: Deliver workspace_event payload
RealtimeService-->>OCRStep: Acknowledge
OCRStep->>OCRStep: Log any broadcast errors (non-blocking)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
2 issues found across 10 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/app/api/workspaces/[id]/import/route.ts">
<violation number="1" location="src/app/api/workspaces/[id]/import/route.ts:90">
P2: No upper bound on the number of files per import request. An unbounded `files` array can be abused to cause excessive CPU (duplicate-name resolution is quadratic), memory (large JSON payload), and database load. Add a reasonable maximum (e.g., 50 or 100 files) and return a 400 if exceeded.</violation>
<violation number="2" location="src/app/api/workspaces/[id]/import/route.ts:202">
P1: Self-fetching the same Next.js server (`/api/ocr/start`) is an anti-pattern that adds latency, wastes a connection, and can deadlock in serverless environments. Import and call the OCR start logic directly instead of making an HTTP round-trip to yourself. The manual cookie forwarding also makes this fragile — any auth middleware changes would silently break this path.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| let ocrRunId: string | null = null; | ||
| try { | ||
| const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; | ||
| const ocrResponse = await fetch(`${appUrl}/api/ocr/start`, { |
There was a problem hiding this comment.
P1: Self-fetching the same Next.js server (/api/ocr/start) is an anti-pattern that adds latency, wastes a connection, and can deadlock in serverless environments. Import and call the OCR start logic directly instead of making an HTTP round-trip to yourself. The manual cookie forwarding also makes this fragile — any auth middleware changes would silently break this path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/api/workspaces/[id]/import/route.ts, line 202:
<comment>Self-fetching the same Next.js server (`/api/ocr/start`) is an anti-pattern that adds latency, wastes a connection, and can deadlock in serverless environments. Import and call the OCR start logic directly instead of making an HTTP round-trip to yourself. The manual cookie forwarding also makes this fragile — any auth middleware changes would silently break this path.</comment>
<file context>
@@ -0,0 +1,224 @@
+ let ocrRunId: string | null = null;
+ try {
+ const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
+ const ocrResponse = await fetch(`${appUrl}/api/ocr/start`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json", cookie: request.headers.get("cookie") ?? "" },
</file context>
| const body = await request.json(); | ||
| const files: ImportFile[] = Array.isArray(body.files) ? body.files : []; | ||
|
|
||
| if (files.length === 0) { |
There was a problem hiding this comment.
P2: No upper bound on the number of files per import request. An unbounded files array can be abused to cause excessive CPU (duplicate-name resolution is quadratic), memory (large JSON payload), and database load. Add a reasonable maximum (e.g., 50 or 100 files) and return a 400 if exceeded.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/api/workspaces/[id]/import/route.ts, line 90:
<comment>No upper bound on the number of files per import request. An unbounded `files` array can be abused to cause excessive CPU (duplicate-name resolution is quadratic), memory (large JSON payload), and database load. Add a reasonable maximum (e.g., 50 or 100 files) and return a 400 if exceeded.</comment>
<file context>
@@ -0,0 +1,224 @@
+ const body = await request.json();
+ const files: ImportFile[] = Array.isArray(body.files) ? body.files : [];
+
+ if (files.length === 0) {
+ return NextResponse.json({ error: "At least one file is required" }, { status: 400 });
+ }
</file context>
There was a problem hiding this comment.
🧹 Nitpick comments (3)
CLAUDE.md (1)
71-71: Add language specifier to fenced code block.The code block starting at line 71 is missing a language specifier. For a path listing, use
textorplaintextto satisfy markdownlint and improve rendering consistency.Proposed fix
-``` +```text src/app/api/ # API routes (auth, chat, cards, pdf, youtube, deep-research)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CLAUDE.md` at line 71, In CLAUDE.md locate the fenced code block that contains the repository path listing (the block that begins with ``` and contains lines like "src/app/api/ # API routes (auth, chat, cards, pdf, youtube, deep-research)") and change the opening fence to include a language specifier (e.g., ```text or ```plaintext) so markdownlint passes and rendering is consistent.src/lib/workspace/state-loader.ts (1)
59-59: Consider defining a type for raw event query results.Multiple
anytypes are used for DB query results (lines 59, 78, 80, 99, 107). While the code safely maps toWorkspaceEventdownstream, defining an interface for the raw query shape would improve maintainability.Proposed type definition
interface RawEventRow { eventId: string; eventType: string; payload: unknown; timestamp: string | number; userId: string; userName: string | null; version: number; } // Then use: let eventsData: RawEventRow[] = [];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/workspace/state-loader.ts` at line 59, Define a concrete type for raw DB rows and replace the loose any[] usages: create an interface (e.g., RawEventRow with fields eventId, eventType, payload, timestamp, userId, userName, version) and change declarations like let eventsData: any[] to let eventsData: RawEventRow[]; update other any-typed query result variables in this file (the other occurrences around the mappings to WorkspaceEvent) to use RawEventRow so the mapping logic (in functions/methods that convert RawEventRow -> WorkspaceEvent) is strongly typed and safer to maintain.src/hooks/workspace/use-workspace-realtime.ts (1)
15-17: Stale interface documentation forcurrentUserId.The JSDoc comment says
currentUserIdis used "to filter out own events," but after this PR, self-filtering is handled by the Supabase channel config (broadcast: { self: false }), not bycurrentUserIdin the effect. The field is still destructured at line 46 but never used.Consider updating the comment or removing
currentUserIdfrom the interface if it's truly unused.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/workspace/use-workspace-realtime.ts` around lines 15 - 17, The JSDoc for WorkspaceRealtimeOptions incorrectly claims currentUserId is used to "filter out own events" but self-filtering is now done via the Supabase channel broadcast config; remove currentUserId from the WorkspaceRealtimeOptions interface and from any destructuring (the local variable currently being pulled into the effect), or if you prefer to keep the field for future use, update the JSDoc to reflect that it is unused by the effect and not relied on for self-filtering; ensure no unused variable warnings remain (check the destructuring site in useWorkspaceRealtime and the interface definition WorkspaceRealtimeOptions).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@CLAUDE.md`:
- Line 71: In CLAUDE.md locate the fenced code block that contains the
repository path listing (the block that begins with ``` and contains lines like
"src/app/api/ # API routes (auth, chat, cards, pdf, youtube,
deep-research)") and change the opening fence to include a language specifier
(e.g., ```text or ```plaintext) so markdownlint passes and rendering is
consistent.
In `@src/hooks/workspace/use-workspace-realtime.ts`:
- Around line 15-17: The JSDoc for WorkspaceRealtimeOptions incorrectly claims
currentUserId is used to "filter out own events" but self-filtering is now done
via the Supabase channel broadcast config; remove currentUserId from the
WorkspaceRealtimeOptions interface and from any destructuring (the local
variable currently being pulled into the effect), or if you prefer to keep the
field for future use, update the JSDoc to reflect that it is unused by the
effect and not relied on for self-filtering; ensure no unused variable warnings
remain (check the destructuring site in useWorkspaceRealtime and the interface
definition WorkspaceRealtimeOptions).
In `@src/lib/workspace/state-loader.ts`:
- Line 59: Define a concrete type for raw DB rows and replace the loose any[]
usages: create an interface (e.g., RawEventRow with fields eventId, eventType,
payload, timestamp, userId, userName, version) and change declarations like let
eventsData: any[] to let eventsData: RawEventRow[]; update other any-typed query
result variables in this file (the other occurrences around the mappings to
WorkspaceEvent) to use RawEventRow so the mapping logic (in functions/methods
that convert RawEventRow -> WorkspaceEvent) is strongly typed and safer to
maintain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ca343b6b-99ee-4b85-b780-b542e2cb1d67
📒 Files selected for processing (10)
CLAUDE.mdpublic/.well-known/workflow/v1/manifest.jsonsrc/app/api/workspaces/[id]/folders/route.tssrc/app/api/workspaces/[id]/import/route.tssrc/app/globals.csssrc/components/layout/DashboardLayout.tsxsrc/hooks/workspace/use-workspace-events.tssrc/hooks/workspace/use-workspace-realtime.tssrc/lib/workspace/state-loader.tssrc/workflows/ocr-dispatch/steps/persist-results.ts
💤 Files with no reviewable changes (1)
- src/app/globals.css
|
@coderabbitai run |
|
✅ Actions performedReview triggered.
|
Summary by CodeRabbit
Release Notes
New Features
Improvements
Documentation