Skip to content

Conversation

oscartbeaumont
Copy link
Member

@oscartbeaumont oscartbeaumont commented Sep 26, 2025

This PR aims to address the issues that plagued the original rollout and get this feature live finally.

Changes:

  • Disable progress reporting on UploadCapButton. This can be a separate PR and derisks the rollout.
  • Fix version header name mismatch with desktop that was disabling progress reporting
  • Instant mode progress is stuck at 0/0 - Was issue with local development env S3 ports not longer matching.
  • Sudio mode progress working???
  • Make buttons on cap card work while uploading
  • Deleting cap should remove progressa

Future:

  • More testing!!!!
  • Wiping the existing DB table
  • Enable feature flag in web by default

Summary by CodeRabbit

  • New Features

    • Dashboard cards now display in‑progress upload status with a dedicated overlay; Download/Duplicate are disabled during active uploads.
    • Optional upload progress tracking during video creation.
  • Bug Fixes

    • More reliable desktop upload progress (handles zero-size files, clamps values, reduces redundant updates).
    • Deleting a video also clears related upload records to prevent lingering progress states.
  • Style

    • Improved card layering (z-index) to prevent dropdown/thumbnail overlap.
  • Documentation

    • Added docs for the new upload progress option.

Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Desktop: refactors recording error handling, overhauls upload progress clamping/guards/logging, and changes auth header name. Web: adds optional supportsUploadProgress affecting DB insert, refactors CapCard rendering/guards, enhances desktop progress API validation/logging, and removes turbopack from dev script. Backend: deletes related videoUploads on video delete and removes an unused import.

Changes

Cohort / File(s) Summary
Desktop upload & recording flow
apps/desktop/src-tauri/src/recording.rs, apps/desktop/src-tauri/src/upload.rs, apps/desktop/src-tauri/src/web_api.rs
recording.rs: replace conditional match with awaited let, propagate errors with ?/map_err. upload.rs: clamp progress, zero-size guards, skip redundant updates, enhanced logging and error handling, refined finalize behavior. web_api.rs: header renamed to "X-Cap-Desktop-Version".
Web action: video create/upload URL
apps/web/actions/video/upload.ts
Adds optional supportsUploadProgress (default false). Inserts into videoUploads only when supportsUploadProgress is true.
Web API: desktop progress endpoint
apps/web/app/api/desktop/[...route]/video.ts
GET /create formatting tweak. POST /progress: input validation, detailed logging, explicit insert-vs-update handling, cleanup logging on completion, expanded error messages; flow unchanged.
Web UI: CapCard
apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
Adjust z-index, guard actions with hasActiveUpload, rework uploadProgress rendering into dedicated overlay, refine thumbnail class logic, minor interaction tweaks.
Web dev script
apps/web/package.json
Removes --turbopack from next dev script.
Backend videos repo
packages/web-backend/src/Videos/VideosRepo.ts, packages/web-backend/src/Videos/index.ts
VideosRepo: delete videos and related videoUploads in parallel via Promise.all. index.ts: remove unused S3BucketAccess import.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Desktop as Desktop App
  participant WebAPI as Web API (/api/desktop)
  participant DB as DB (videos, videoUploads, videoUploadProgress)

  rect rgba(230,245,255,0.6)
    note over Desktop,WebAPI: Create video and get upload URL
    Desktop->>WebAPI: POST /create (metadata)
    WebAPI->>DB: Insert video
    alt supportsUploadProgress = true
      WebAPI->>DB: Insert videoUploads row
    else
      note right of WebAPI: Skip videoUploads insert
    end
    WebAPI-->>Desktop: { uploadUrl, videoId }
  end

  rect rgba(240,255,240,0.6)
    note over Desktop,WebAPI: Upload with clamped progress
    loop For each chunk
      Desktop->>Desktop: Compute progress (clamp, guard total=0)
      Desktop->>WebAPI: POST /progress { uploaded, total } with X-Cap-Desktop-Version
      WebAPI->>WebAPI: Validate inputs, log
      WebAPI->>DB: Upsert/Update progress
      alt uploaded >= total and total > 0
        WebAPI->>DB: Cleanup progress row
        WebAPI-->>Desktop: Completed
      else
        WebAPI-->>Desktop: Progress updated
      end
    end
  end
Loading
sequenceDiagram
  autonumber
  participant Service as Videos Service
  participant DB as DB (videos, videoUploads)

  note over Service,DB: Delete video and related uploads
  Service->>DB: Execute Promise.all([delete videos, delete videoUploads])
  DB-->>Service: Deletions resolved
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Brendonovich

Poem

Thump-thump go my paws on keys so bright,
I clamp the bytes and track the flight.
Headers renamed, progress true,
Cards now shine with overlays new.
A hop, a bop, deletions clean—
Our uploads glide, so swift, so keen. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Upload progress improvements” accurately and succinctly captures the primary focus of the pull request, which is enhancing and fixing upload progress functionality across the application without including extraneous details or unrelated noise.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch upload-progress-fixes

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@oscartbeaumont oscartbeaumont marked this pull request as ready for review September 27, 2025 01:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx (2)

141-151: Use useEffectMutation instead of useMutation

Client mutations in apps/web must use useEffectMutation from @/lib/EffectRuntime; avoid direct useMutation. Replace deleteMutation to comply.

[Based on coding guidelines]

- const deleteMutation = useMutation({
-   mutationFn: async () => {
-     await onDelete?.();
-   },
-   onError: (error) => {
-     console.error("Error deleting cap:", error);
-   },
-   onSettled: () => {
-     setConfirmOpen(false);
-   },
- });
+ const deleteMutation = useEffectMutation({
+   mutationFn: () =>
+     Effect.tryPromise(async () => {
+       await onDelete?.();
+     }),
+   onError: (error) => {
+     console.error("Error deleting cap:", error);
+   },
+   onSettled: () => {
+     setConfirmOpen(false);
+   },
+ });

1-10: Add “use client” directive
Insert "use client"; as the very first line in apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx to enable React hooks under Next.js App Router.

🧹 Nitpick comments (3)
apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx (2)

538-547: Remove newly added inline comment

Inline comments in TS/TSX are disallowed in this codebase. Remove the commented-out class fragment.

[Based on coding guidelines]

-                  // uploadProgress && "opacity-30",

153-159: Avoid broad router.refresh in duplicate success

Prefer targeted cache updates (setQueryData/setQueriesData) for affected queries instead of router.refresh().

[Based on coding guidelines]

apps/desktop/src-tauri/src/recording.rs (1)

269-276: Avoid unnecessary String clone

You can move link directly into VideoUploadInfo; no need to clone.

- let link = app.make_app_url(format!("/s/{}", s3_config.id())).await;
- info!("Pre-created shareable link: {}", link);
+ let link = app.make_app_url(format!("/s/{}", s3_config.id())).await;
+ info!("Pre-created shareable link: {}", link);
   Some(VideoUploadInfo {
     id: s3_config.id().to_string(),
-    link: link.clone(),
+    link,
     config: s3_config,
   })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e53e810 and f2aa0c0.

📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/recording.rs (2 hunks)
  • apps/desktop/src-tauri/src/upload.rs (8 hunks)
  • apps/desktop/src-tauri/src/web_api.rs (1 hunks)
  • apps/web/actions/video/upload.ts (3 hunks)
  • apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx (5 hunks)
  • apps/web/app/api/desktop/[...route]/video.ts (3 hunks)
  • apps/web/package.json (1 hunks)
  • packages/web-backend/src/Videos/VideosRepo.ts (1 hunks)
  • packages/web-backend/src/Videos/index.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/web-backend/src/Videos/index.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • packages/web-backend/src/Videos/VideosRepo.ts
  • apps/desktop/src-tauri/src/web_api.rs
  • apps/web/actions/video/upload.ts
  • apps/desktop/src-tauri/src/upload.rs
  • apps/web/app/api/desktop/[...route]/video.ts
  • apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
  • apps/desktop/src-tauri/src/recording.rs
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • packages/web-backend/src/Videos/VideosRepo.ts
  • apps/web/actions/video/upload.ts
  • apps/web/app/api/desktop/[...route]/video.ts
  • apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • packages/web-backend/src/Videos/VideosRepo.ts
  • apps/web/actions/video/upload.ts
  • apps/web/app/api/desktop/[...route]/video.ts
  • apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • apps/desktop/src-tauri/src/web_api.rs
  • apps/desktop/src-tauri/src/upload.rs
  • apps/desktop/src-tauri/src/recording.rs
apps/web/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions; do not place AI calls elsewhere

Files:

  • apps/web/actions/video/upload.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/actions/video/upload.ts
  • apps/web/app/api/desktop/[...route]/video.ts
  • apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/actions/video/upload.ts
  • apps/web/app/api/desktop/[...route]/video.ts
  • apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/api/desktop/[...route]/video.ts
  • apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
🧬 Code graph analysis (6)
packages/web-backend/src/Videos/VideosRepo.ts (1)
packages/database/index.ts (1)
  • db (29-34)
apps/web/actions/video/upload.ts (3)
packages/utils/src/constants/plans.ts (1)
  • userIsPro (21-45)
packages/database/index.ts (1)
  • db (29-34)
packages/database/schema.ts (1)
  • videoUploads (648-654)
apps/desktop/src-tauri/src/upload.rs (2)
packages/web-domain/src/Video.ts (1)
  • UploadProgress (61-68)
apps/desktop/src/utils/tauri.ts (1)
  • UploadProgress (453-453)
apps/web/app/api/desktop/[...route]/video.ts (2)
packages/database/index.ts (1)
  • db (29-34)
packages/database/schema.ts (1)
  • videoUploads (648-654)
apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx (2)
packages/ui-solid/src/ProgressCircle.tsx (1)
  • ProgressCircle (55-112)
apps/web/components/VideoThumbnail.tsx (1)
  • VideoThumbnail (56-125)
apps/desktop/src-tauri/src/recording.rs (1)
apps/desktop/src-tauri/src/upload.rs (2)
  • create_or_get_video (443-505)
  • id (77-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vercel Agent Review
🔇 Additional comments (16)
apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx (6)

295-303: Z-index fix improves layering

Adding z-10 on the card container is correct and resolves overlay stacking issues.


310-319: Dropdown overlay stacking LGTM

Using z-20 for the action stack aligns the layering with other overlays.


361-400: Disable Download while uploading

Guarding the Download action when a cap has an active upload is correct and prevents broken flows.


414-423: Disable Duplicate while uploading

Disabling Duplicate during active uploads avoids duplicate data inconsistencies.


503-505: Prevent navigation while deleting

Preventing navigation on Link when isDeleting is set avoids mid-flight route changes. Good.


508-551: Upload-progress overlay rendering is solid

Dedicated overlay with failure/active cases is clear and avoids thumbnail flicker. No correctness issues spotted.

apps/desktop/src-tauri/src/recording.rs (1)

254-268: Error propagation on video pre-create is correct

map_err with error! then early return simplifies control flow and avoids silent failures.

apps/desktop/src-tauri/src/upload.rs (8)

138-156: Progress clamping and zero-total guards

Good defensive checks; prevents divide-by-zero and over-reporting.


157-190: Debounce/skip redundant updates and only send when valid

Aborting pending tasks and avoiding no-op updates reduces noisy writes. Immediate-send condition is correct.


203-216: Schedule delayed updates only with valid totals

Avoids server writes for unknown totals; appropriate.


220-255: Better observability on progress API errors

Status + body logging is helpful for diagnosing server-side failures.


304-319: Guarded channel progress with zero-total handling

Prevents NaN progress values on the UI channel when size is unknown.


816-825: Zero-size file guard in realtime upload

Correctly waits when recording hasn’t written data; errors if recording ended with empty file.


924-928: Early return on zero-size during chunk upload

Prevents invalid S3 part uploads and surfaces a clear error.


1141-1153: Post-chunk progress update and logging LGTM

Using current file_size for progress is acceptable; logs are useful during multipart.

apps/desktop/src-tauri/src/web_api.rs (1)

18-30: Header rename consistency confirmed

Both the desktop client (web_api.rs) and server routes (video.ts) now exclusively use "X-Cap-Desktop-Version" with no remaining "X-Desktop-Version" references.

Comment on lines +333 to +352
if (result.rowsAffected === 0) {
console.log("No existing progress record, inserting new one:", {
videoId: videoIdRaw,
uploaded,
total,
});
await db().insert(videoUploads).values({
videoId,
uploaded,
total,
updatedAt,
});
} else {
console.log("Updated existing progress record:", {
videoId: videoIdRaw,
uploaded,
total,
rowsAffected: result.rowsAffected,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix stale progress update handling when no rows are updated.

rowsAffected === 0 can mean “incoming update is stale” because of the lte(videoUploads.updatedAt, updatedAt) guard—not just “row missing”. In that case this new insert path will hit the table’s PK constraint and throw, breaking progress reporting. Guard the insert with an existence check (or similar strategy) so stale updates are ignored instead of crashing.

-			if (result.rowsAffected === 0) {
-				console.log("No existing progress record, inserting new one:", {
-					videoId: videoIdRaw,
-					uploaded,
-					total,
-				});
-				await db().insert(videoUploads).values({
-					videoId,
-					uploaded,
-					total,
-					updatedAt,
-				});
-			} else {
+			if (result.rowsAffected === 0) {
+				const [existingProgress] = await db()
+					.select({ updatedAt: videoUploads.updatedAt })
+					.from(videoUploads)
+					.where(eq(videoUploads.videoId, videoId))
+					.limit(1);
+
+				if (existingProgress) {
+					console.log("Ignoring stale progress update:", {
+						videoId: videoIdRaw,
+						uploaded,
+						total,
+						existingUpdatedAt: existingProgress.updatedAt,
+					});
+				} else {
+					console.log("No existing progress record, inserting new one:", {
+						videoId: videoIdRaw,
+						uploaded,
+						total,
+					});
+					await db().insert(videoUploads).values({
+						videoId,
+						uploaded,
+						total,
+						updatedAt,
+					});
+				}
+			} else {
				console.log("Updated existing progress record:", {
					videoId: videoIdRaw,
					uploaded,
🤖 Prompt for AI Agents
In apps/web/app/api/desktop/[...route]/video.ts around lines 333 to 352, the
code treats result.rowsAffected === 0 as “no existing record” but that can also
mean the incoming update was stale due to the lte(updatedAt) guard; attempting
to unconditionally insert then hits the PK constraint and throws. Change the
branch so that before inserting you verify existence (e.g., SELECT count/exists
WHERE videoId = ?), and only INSERT when the row truly does not exist; otherwise
treat rowsAffected === 0 as a stale/no-op and skip the insert. Optionally
replace the two-step check+insert with an upsert/INSERT ... ON CONFLICT DO
NOTHING to avoid the race and PK error.

@oscartbeaumont oscartbeaumont marked this pull request as draft September 29, 2025 06:58
@oscartbeaumont
Copy link
Member Author

I have merged this into #1092 as it benifits from it's other work.

@oscartbeaumont oscartbeaumont deleted the upload-progress-fixes branch September 29, 2025 07:07
oscartbeaumont added a commit that referenced this pull request Sep 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant