feat: bring desktop Studio UI and local playback to web - #2312
richiemcilroy wants to merge 660 commits into
Conversation
|
hey @greptileai, please re-review the PR |
Comments Outside DiffThese findings could not be posted inline.
|
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
… project is published
…ear it once frames move
…ding status at a time
… keep scrub frames drawing while media loads
…hich scrubbing reuses
|
hey @greptileai, please re-review the PR |
…s camera catches up
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
…ton and keep the comments rail always open
|
hey @greptileai, please re-review the PR |
| }).pipe(runPromise); | ||
| } catch (error) { | ||
| console.error( | ||
| `[link-preview] Failed to delete old preview image for ${videoId}:`, |
| bytes = new Uint8Array(await response.arrayBuffer()); | ||
| } catch (error) { | ||
| console.error( | ||
| `[video/link-preview] Failed to read preview image for ${rawVideoId}:`, |
| document.querySelector<HTMLVideoElement>("[data-edit-video] video") ?? | ||
| document.querySelector<HTMLVideoElement>("video"); | ||
|
|
||
| export function LinkPreviewDialog({ |
There was a problem hiding this comment.
The new dialog is exported but never rendered or opened by the share page. Although preview metadata and the save action are implemented, owners have no way to choose a title, description, or image through the UI, so they cannot use the feature.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/s/[videoId]/_components/link-preview/LinkPreviewDialog.tsx
Line: 100
Comment:
**Preview editor is unreachable**
The new dialog is exported but never rendered or opened by the share page. Although preview metadata and the save action are implemented, owners have no way to choose a title, description, or image through the UI, so they cannot use the feature.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| "Cache-Control": !anonymous | ||
| ? "private, max-age=300" | ||
| : current | ||
| ? "public, max-age=3600, s-maxage=86400, stale-while-revalidate=604800" | ||
| : "public, max-age=60, s-maxage=60", |
There was a problem hiding this comment.
When an anonymous visitor requests a public video's custom preview image, this response allows shared caches to keep it for 24 hours. If the owner then makes the video private, the image URL does not change, so a cache can continue serving the image without reaching the route's access check. How this was verified: The image route checks viewing permission at the origin but issues a 24-hour shared-cache response keyed only by the image version.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/api/video/link-preview/route.ts
Line: 104-108
Comment:
**Private images remain cached**
When an anonymous visitor requests a public video's custom preview image, this response allows shared caches to keep it for 24 hours. If the owner then makes the video private, the image URL does not change, so a cache can continue serving the image without reaching the route's access check. **How this was verified:** The image route checks viewing permission at the origin but issues a 24-hour shared-cache response keyed only by the image version.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| await writeLinkPreview(videoId, user.id, null); | ||
| if (existing?.image) await deleteImage(videoId, existing.image.key); |
There was a problem hiding this comment.
Concurrent updates delete saved image
A reset and a text-only save can both read the same existing image. If the reset clears the preview first, the save can restore a reference to that image, after which the reset deletes it. The saved preview then points to a missing image because deletion does not check what the database currently references.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/videos/link-preview.ts
Line: 187-188
Comment:
**Concurrent updates delete saved image**
A reset and a text-only save can both read the same existing image. If the reset clears the preview first, the save can restore a reference to that image, after which the reset deletes it. The saved preview then points to a missing image because deletion does not check what the database currently references.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| image = await Effect.gen(function* () { | ||
| const maybeVideo = yield* Effect.flatMap(Videos, (videos) => | ||
| videos.getByIdForViewing(videoId), | ||
| ); | ||
| if (Option.isNone(maybeVideo)) return null; | ||
| const [video] = maybeVideo.value; | ||
| const stored = readLinkPreview( | ||
| Option.getOrNull(video.metadata), | ||
| video.id, | ||
| )?.image; | ||
| if (!stored) return null; | ||
| // Paused while the owner doesn't have Cap Pro. | ||
| if (!(yield* Effect.promise(() => ownerServesLinkPreview(video.ownerId)))) | ||
| return null; | ||
|
|
||
| const [bucket] = yield* S3Buckets.getBucketAccess(Option.none()); | ||
| const url = yield* bucket.getInternalSignedObjectUrl(stored.key, { | ||
| expiresIn: 60, | ||
| }); | ||
| return { url, key: stored.key, contentType: stored.contentType }; | ||
| }).pipe(provideOptionalAuth, runPromise); |
There was a problem hiding this comment.
Route bypasses required API pattern
This new API route defines an ad-hoc Next.js handler and calls runPromise inside it. The repository requires routes under apps/web/app/api/* to use the HttpApi class/group/endpoint pattern and export a handler through apiToHandler(ApiLive). That requirement must be satisfied before merging.
Context Used: CLAUDE.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/api/video/link-preview/route.ts
Line: 41-61
Comment:
**Route bypasses required API pattern**
This new API route defines an ad-hoc Next.js handler and calls `runPromise` inside it. The repository requires routes under `apps/web/app/api/*` to use the `HttpApi` class/group/endpoint pattern and export a handler through `apiToHandler(ApiLive)`. That requirement must be satisfied before merging.
**Context Used:** CLAUDE.md ([source](https://github.com/capsoftware/cap/blob/main/CLAUDE.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| /** | ||
| * Sets a Cap's link preview. Form fields: `videoId`, `title`, `description`, | ||
| * an optional `image` file to replace the image, and `removeImage=1` to drop | ||
| * it. Blank text fields fall back to the defaults. | ||
| */ |
There was a problem hiding this comment.
Comment repeats action behavior
This new JSDoc restates the save action's form fields and blank-field behavior. The repository requires comments to preserve non-obvious context rather than narrate adjacent code; the new image-card and preview-state descriptions follow the same pattern. This requirement must be satisfied before merging.
Context Used: AGENTS.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/videos/link-preview.ts
Line: 94-98
Comment:
**Comment repeats action behavior**
This new JSDoc restates the save action's form fields and blank-field behavior. The repository requires comments to preserve non-obvious context rather than narrate adjacent code; the new image-card and preview-state descriptions follow the same pattern. This requirement must be satisfied before merging.
**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
hey @greptileai, please re-review the PR |
| editorState.timeline.audioPicker === null && | ||
| editorState.timeline.audioReplace === null; | ||
| props.sheet?.setOpen(!showing); | ||
| // Clear any active selection first |
There was a problem hiding this comment.
Comment Narrates Selection Clearing
“Clear any active selection first” only describes the check and update immediately below it. The repository requires comments to preserve non-obvious context, not narrate ordinary control flow. This requirement must be satisfied before merging.
Context Used: AGENTS.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/editor/ConfigSidebar.tsx
Line: 774
Comment:
**Comment Narrates Selection Clearing**
“Clear any active selection first” only describes the check and update immediately below it. The repository requires comments to preserve non-obvious context, not narrate ordinary control flow. This requirement must be satisfied before merging.
**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Brings the desktop Studio editor to the web as a pilot, with a browser recorder built around it. Recordings keep screen, camera, microphone and system audio as separate tracks, open straight into the editor, and can be styled, extended with more clips, exported locally or saved back to the same share link.
Studio is gated by
CAP_WEB_EDITOR_STUDIO_ENABLED=enabledand the pilot account (isWebStudioEnabledForEmail). Other accounts keep the existing editor. No database migration.Recorder
Opening the editor
<video>(which downloads a whole fragmented MP4), the waveform streams the mic once and is cached, and decoded frames go straight to the renderer. Sources up to 4K decode with WebCodecs.Editing
Save, export and import
Rendering new recordings
Validation
Known gaps
The PR does not appear safe to merge while the previously reported private-image caching and concurrent image-deletion defects remain unresolved.
Findings
Fix with agent prompt
Summary
The latest changes connect the link-preview editor to the share page and adjust its mobile layout.
Reviews (268) · Last reviewed commit: "feat(web): open the link preview dialog ..."