-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Updated project page search bar #571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Updated project page search bar #571
Conversation
@DeveloperAromal is attempting to deploy a commit to the OpenCut OSS Team on Vercel. A member of the Team first needs to authorize it. |
👷 Deploy request for appcut pending review.Visit the deploys page to approve it
|
WalkthroughPrimarily formatting and whitespace changes across web app files, plus deletion of apps/web/.env.example. Minor UI tweak adds a search icon within the Projects page search input. Migration metadata JSON formatting normalized. No functional logic, control flow, or public API changes detected in other files. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
apps/web/src/components/ui/input.tsx (1)
76-90
: Keyboard accessibility regression: clear button doesn’t work via keyboardThe clear action is wired to
onMouseDown
only. Because there’s noonClick
, pressing Enter/Space on the button won’t clear the input, which breaks keyboard and assistive tech activation. KeeponMouseDown
to preserve input focus, but addonClick
and a keyboard handler. Also, the Tailwind class!size-[0.85]
is invalid (missing unit) and will be ignored; use a supported size utility.- {showClear && ( + {showClear && ( <Button type="button" variant="text" size="icon" + onClick={(e) => { + // ensure keyboard and pointer activation both work + e.preventDefault(); + onClear?.(); + }} onMouseDown={(e) => { e.preventDefault(); onClear?.(); }} className="absolute right-0 top-0 h-full px-3 text-muted-foreground !opacity-100" aria-label="Clear input" > - <X className="!size-[0.85]" /> + <X + className="h-3.5 w-3.5" + aria-hidden="true" + /> </Button> - )} + )}apps/web/src/components/editor/preview-panel.tsx (4)
208-214
: Avoidany
forelement
in drag handler.You already import
TimelineElement
; use it to maintain type safety.- const handleTextMouseDown = ( + const handleTextMouseDown = ( e: React.MouseEvent<HTMLDivElement>, - element: any, + element: TimelineElement, trackId: string ) => {
742-754
: Replaceany
foractiveProject
inFullscreenPreview
props.Don’t use
any
. Derive the type from the store to avoid circular type imports.- activeProject: any; + activeProject: ReturnType<typeof useProjectStore>["activeProject"];
656-689
: Add accessible name and button type for media controls.The play/pause
Button
lacks an accessible label and explicittype
. Icons should be decorative.- <Button + <Button variant="text" size="icon" onClick={toggle} disabled={!hasAnyElements} className="h-auto p-0 text-foreground hover:text-foreground/80" + type="button" + title={isPlaying ? "Pause" : "Play"} + aria-label={isPlaying ? "Pause" : "Play"} > - {isPlaying ? ( - <Pause className="h-3 w-3" /> - ) : ( - <Play className="h-3 w-3" /> - )} + {isPlaying ? ( + <Pause className="h-3 w-3" aria-hidden="true" /> + ) : ( + <Play className="h-3 w-3" aria-hidden="true" /> + )} </Button>Similarly, add
type="button"
to the adjacent skip buttons to satisfy the “always include a type for button elements” rule.
837-849
: Same a11y fix for the non-fullscreen play/pause control.Mirror the
title
,aria-label
,type="button"
, andaria-hidden
icon changes here.- <Button + <Button variant="text" size="icon" onClick={toggle} disabled={!hasAnyElements} className="h-auto p-0" + type="button" + title={isPlaying ? "Pause" : "Play"} + aria-label={isPlaying ? "Pause" : "Play"} > - {isPlaying ? ( - <Pause className="h-3 w-3" /> - ) : ( - <Play className="h-3 w-3" /> - )} + {isPlaying ? ( + <Pause className="h-3 w-3" aria-hidden="true" /> + ) : ( + <Play className="h-3 w-3" aria-hidden="true" /> + )} </Button>
🧹 Nitpick comments (43)
apps/web/src/components/keyboard-shortcuts-help.tsx (5)
107-112
: Add explicit type="button" to all Button components.Per guidelines, always set a button type to avoid unintended form submissions and to stay consistent.
Apply:
@@ - <Button variant="text" size="sm" className="gap-2"> + <Button type="button" variant="text" size="sm" className="gap-2"> @@ - <Button size="sm" variant="destructive" onClick={resetToDefaults}> + <Button type="button" size="sm" variant="destructive" onClick={resetToDefaults}> @@ - <Button + <Button + type="button" variant="outline" size="sm"Also applies to: 151-154, 231-244
66-68
: Prefer for...of over Array.forEach as per project guidelines.Keeps style consistent and avoids callback allocations in hot paths.
- const oldKeys = getKeybindingsForAction(recordingShortcut.action); - oldKeys.forEach((key) => removeKeybinding(key)); + const oldKeys = getKeybindingsForAction(recordingShortcut.action); + for (const key of oldKeys) { + removeKeybinding(key); + }
169-177
: Comment doesn’t match the code’s behavior.The comment mentions lower/upper-case dedupe, but the code filters Cmd/Ctrl duplicates. Align the comment to avoid confusion.
- // Filter out lowercase duplicates for display - if both "j" and "J" exist, only show "J" + // Filter out platform-variant duplicates for display: + // if both "Cmd+..." and "Ctrl+..." exist, hide the "Cmd" entry when the "Ctrl" variant is present.
4-4
: Type-only import for ReactNode.Avoid referencing the React namespace and follow “import type” guidance.
-import { useEffect, useState } from "react"; +import { useEffect, useState, type ReactNode } from "react"; @@ - children: React.ReactNode; + children: ReactNode;Also applies to: 220-223
231-241
: Expose pressed state for assistive tech during recording.When a key “chip” initiates recording, reflect that toggle state via aria-pressed for better a11y.
<Button variant="outline" size="sm" className={`font-sans px-2 min-w-6 min-h-6 leading-none mr-1 hover:bg-opacity-80 ${ isRecording ? "border-primary bg-primary/10" : "border bg-accent/50" }`} onClick={handleClick} + aria-pressed={isRecording} title={ isRecording ? "Press any key combination..." : "Click to edit shortcut" }
apps/web/src/hooks/use-infinite-scroll.ts (1)
21-35
: Guard against bursty onLoadMore calls near the thresholdIf
isLoading
flips to true slightly afteronLoadMore()
starts (e.g., state set async), rapid scroll events at the bottom can still trigger multiple invocations in the same frame. Optional guard: coalesce calls with a micro lock or rAF throttle.Apply a minimal lock with a ref:
import { useRef, useCallback } from "react"; @@ export function useInfiniteScroll({ @@ }: UseInfiniteScrollOptions) { const scrollAreaRef = useRef<HTMLDivElement>(null); + const pendingRef = useRef(false); const handleScroll = useCallback( (event: React.UIEvent<HTMLDivElement>) => { if (!enabled) return; const { scrollTop, scrollHeight, clientHeight } = event.currentTarget; const nearBottom = scrollTop + clientHeight >= scrollHeight - threshold; - if (nearBottom && hasMore && !isLoading) { - onLoadMore(); + if (nearBottom && hasMore && !isLoading && !pendingRef.current) { + pendingRef.current = true; + // next tick lets isLoading update before more scroll events fire + queueMicrotask(() => { + onLoadMore(); + pendingRef.current = false; + }); } }, [onLoadMore, hasMore, isLoading, threshold, enabled] ); return { scrollAreaRef, handleScroll }; }apps/web/src/lib/editor-utils.ts (1)
15-21
: Validate aspectRatio input to avoid NaN/invalid valuesIf callers pass 0, negative, or non-finite values, the current math yields misleading results. Add a fast-path guard and normalize.
export function findBestCanvasPreset(aspectRatio: number): CanvasSize { - // Calculate aspect ratio for each preset and find the closest match + // Guard invalid ratios + if (!Number.isFinite(aspectRatio) || aspectRatio <= 0) { + // Default to 16:9 HD + return { width: 1920, height: 1080 }; + } + // Calculate aspect ratio for each preset and find the closest match let bestMatch = DEFAULT_CANVAS_PRESETS[0]; // Default to 16:9 HD let smallestDifference = Math.abs( aspectRatio - bestMatch.width / bestMatch.height );apps/web/src/components/ui/input.tsx (1)
92-108
: Tiny a11y polish: mirror click with key handler and hide decorative icons from SRButton elements fire
click
on Enter/Space, so behavior is already accessible. If you want strict alignment with the project’s “accompany onClick with key handler” guideline, add a minimalonKeyDown
. Also, mark iconsaria-hidden
to avoid double announcement since the button has a properaria-label
.{showPasswordToggle && ( <Button type="button" variant="text" size="icon" onClick={() => onShowPasswordChange?.(!showPassword)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onShowPasswordChange?.(!showPassword); + } + }} className={cn( "absolute top-0 h-full px-3 text-muted-foreground hover:text-foreground", showClear ? "right-10" : "right-0" )} aria-label={showPassword ? "Hide password" : "Show password"} > {showPassword ? ( - <Eye className="h-4 w-4" /> + <Eye className="h-4 w-4" aria-hidden="true" /> ) : ( - <EyeOff className="h-4 w-4" /> + <EyeOff className="h-4 w-4" aria-hidden="true" /> )} </Button> )}If the style guide still enforces “title elements on icons,” confirm the repo-wide approach. Current pattern (aria-label on the button + aria-hidden icons) is screen-reader friendly and avoids duplicate announcements. I can update to add SVG <title> if that’s the standardized convention here.
apps/web/src/hooks/use-sound-search.ts (1)
96-105
: Nit: build the request URL using URLSearchParams for consistency.You already use URLSearchParams in loadMore. Using the same approach here avoids double-encoding edge cases and keeps style consistent.
See the unified diff in the previous comment; it switches to URLSearchParams.
apps/web/src/components/ui/input-with-back.tsx (2)
75-81
: Icon accessibility: add a title or mark decorative.Our TSX rule requires a title for icons unless there’s adjacent text. The Search icon is decorative; add a title and hide it from AT to avoid clutter.
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> + <Search + className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" + title="Search" + aria-hidden="true" + focusable="false" + />
61-67
: Minor: replace inline styles with utility classes for consistency.Tailwind utilities can replace style={{ marginLeft: "0px", paddingLeft: "0px" }} for consistency with the codebase.
- <div - className="relative flex-1" - style={{ marginLeft: "0px", paddingLeft: "0px" }} - > + <div className="relative flex-1 ml-0 pl-0">apps/web/src/components/language-select.tsx (3)
96-117
: Icons need titles to meet TSX guideline.Add a title to icons; they’re not accompanied by adjacent text that names the icon itself. Keep them non-focusable and hidden from AT where purely decorative.
- <motion.button + <motion.button type="button" @@ > {!expanded ? ( <div className="flex items-center justify-between w-full" @@ <div className="flex items-center gap-2"> {selectedCountry === "auto" ? ( - <Globe className="!size-[1.05rem]" /> + <Globe className="!size-[1.05rem]" title="Globe" aria-hidden="true" focusable="false" /> ) : ( <ReactCountryFlag countryCode={selectedCountry} svg style={{ width: "1.05rem", height: "1.05rem" }} /> )}
159-166
: Chevron icon should have a title or be marked decorative.ChevronDown is decorative; add a title (per rule) and keep it aria-hidden to avoid redundancy.
- <ChevronDown className="text-muted-foreground size-4" /> + <ChevronDown className="text-muted-foreground size-4" title="Expand" aria-hidden="true" focusable="false" />
187-203
: Language list icons: add titles or mark decorative; verify ISO codes.For consistency, add title/aria-hidden to the Globe variant. Also, this component expects ISO 3166-1 alpha-2 country codes for ReactCountryFlag. Please verify upstream that provided codes conform (retrieved learning notes emphasize correct ISO codes).
- {language.code === "auto" ? ( - <Globe className="!size-[1.0rem]" /> + {language.code === "auto" ? ( + <Globe className="!size-[1.0rem]" title="Auto-detect" aria-hidden="true" focusable="false" /> ) : ( <ReactCountryFlag countryCode={language.code} svg style={{ width: "1.05rem", height: "1.05rem" }} /> )}If some entries are language codes (e.g., “EN”, “ES”) rather than countries, we should either map them to a representative country for flags or switch to BCP 47 tags and render labels without country flags.
apps/web/src/components/editor/layout-guide-overlay.tsx (1)
11-15
: Consider making the guide image decorative for screen readersIf this overlay is purely visual guidance, expose less noise to AT by using an empty alt and presentation semantics.
- <Image - src="/platform-guides/tiktok-blueprint.png" - alt="TikTok layout guide" + <Image + src="/platform-guides/tiktok-blueprint.png" + alt="" + role="presentation" className="absolute inset-0 w-full h-full object-contain" draggable={false} fill />apps/web/src/app/api/get-upload-url/route.ts (3)
69-75
: Bind Content-Type in the presigned request to reduce misuseCurrently the presigned PUT doesn't constrain headers, allowing uploads with arbitrary content types. Bind an expected
Content-Type
based on extension so the signature enforces it.- const { fileExtension } = validationResult.data; + const { fileExtension } = validationResult.data; + const contentTypeMap = { + wav: "audio/wav", + mp3: "audio/mpeg", + m4a: "audio/mp4", + flac: "audio/flac", + } as const; + const contentType = contentTypeMap[fileExtension]; @@ - const signed = await client.sign(new Request(url, { method: "PUT" }), { + const signed = await client.sign( + new Request(url, { + method: "PUT", + headers: { "Content-Type": contentType }, + }), + { aws: { signQuery: true }, - }); + } + );Optionally also set a
Content-Length
bound via upload policy or enforce size on subsequent processing.Also applies to: 86-95
35-47
: Replace console calls with the project’s logger or structured loggingGuidelines discourage
console.*
. Use a centralized logger or wrap logs behind env-based guards to avoid noisy prod logs.- console.error( + logger.error?.( "Missing environment variables:", JSON.stringify(transcriptionCheck.missingVars) ); @@ - console.error( + logger.error?.( "Invalid API response structure:", responseValidation.error ); @@ - console.error("Error generating upload URL:", error); + logger.error?.("Error generating upload URL:", { error });If no logger exists, consider a lightweight wrapper that no-ops in production.
Also applies to: 104-108, 116-127
25-31
: Centralize IP extraction for rate limiting to prevent spoofingI confirmed via grep that all API routes (get-upload-url, waitlist/export, transcribe, sounds/search) currently derive the client IP directly from the
"x-forwarded-for"
header without validation. Because this header can be spoofed unless your proxy is stripping untrusted values, I recommend extracting the IP in one place with a helper that:
- Reads the first entry of a trusted forward header chain
- Falls back to
request.ip
(when available) or"anonymous"
- Is used consistently across all routes
Example helper (e.g.
lib/getClientIp.ts
):import { NextRequest } from "next/server"; export function getClientIp(request: NextRequest): string { const header = request.headers.get("x-forwarded-for"); if (header) { // take the first IP in the list return header.split(",")[0].trim(); } // @ts-ignore next – NextRequest.ip may exist in your adapter return (request as any).ip ?? "anonymous"; }Then in each
route.ts
:- const ip = request.headers.get("x-forwarded-for") ?? "anonymous"; + const ip = getClientIp(request); const { success } = await baseRateLimit.limit(ip); if (!success) { return NextResponse.json({ error: "Too many requests" }, { status: 429 }); }Finally, ensure your Next.js/proxy setup is configured to trust only your upstream proxy (Vercel, Cloudflare, etc.) so that arbitrary clients can’t inject their own
x-forwarded-for
headers.apps/web/src/types/sounds.ts (1)
1-22
: Consider usingexport type
aliases instead ofinterface
to match repo guidelines.The coding guidelines encourage “Use
export type
for types” and “Useimport type
for types.” If this file is referenced widely, this can be a low-priority, incremental refactor.Also applies to: 24-34, 36-39
apps/web/src/components/export-button.tsx (2)
41-49
: Keyboard handling on a native<button>
is redundant and can cause double-activation.Native buttons already activate on Enter/Space. Handling
onKeyDown
here adds complexity and may trigger the action twice in some browsers.Apply this diff to simplify:
- <button - type="button" - className="flex items-center gap-1.5 bg-[#38BDF8] text-white rounded-md px-[0.12rem] py-[0.12rem] cursor-pointer hover:brightness-95 transition-all duration-200" - onClick={handleExport} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - handleExport(); - } - }} - > + <button + type="button" + className="flex items-center gap-1.5 bg-[#38BDF8] text-white rounded-md px-[0.12rem] py-[0.12rem] cursor-pointer hover:brightness-95 transition-all duration-200" + onClick={handleExport} + >
134-136
: Fix capitalization in user-facing copy.Capitalize “We’re” to avoid a visible grammar nit in the dialog.
- Export isn't ready yet. we're building a custom pipeline to make it - great. + Export isn't ready yet. We're building a custom pipeline to make it great.apps/web/src/app/api/sounds/search/route.ts (1)
93-95
: Harden client IP extraction for rate limiting.
x-forwarded-for
may contain a comma-separated list or be spoofed. Prefer first IP, thenx-real-ip
, thenrequest.ip
.- const ip = request.headers.get("x-forwarded-for") ?? "anonymous"; + const xff = request.headers.get("x-forwarded-for"); + const ip = + xff?.split(",")[0]?.trim() || + request.headers.get("x-real-ip") || + // Next.js may populate this in node runtime + // @ts-expect-error - .ip is not always available + (request as any).ip || + "anonymous";apps/web/src/components/ui/editable-timecode.tsx (1)
104-120
: Expose validation state to assistive tech.Add
aria-invalid
whenhasError
is true so screen readers can announce input errors.<input ref={inputRef} type="text" value={inputValue} onChange={handleInputChange} onKeyDown={handleKeyDown} onBlur={handleBlur} className={cn( "text-xs font-mono bg-transparent border-none outline-none", "focus:bg-background focus:border focus:border-primary focus:px-1 focus:rounded", "tabular-nums text-primary", hasError && "text-destructive focus:border-destructive", className )} + aria-invalid={hasError} style={{ width: `${formattedTime.length + 1}ch` }} placeholder={formattedTime} />
apps/web/src/app/api/transcribe/route.ts (4)
55-56
: Normalize X-Forwarded-For to first IP for rate limiting.Header can contain a comma-separated list. Using it raw could amplify or fragment rate limits.
- const ip = request.headers.get("x-forwarded-for") ?? "anonymous"; + const xff = request.headers.get("x-forwarded-for") ?? ""; + const ip = xff.split(",")[0]?.trim() || "anonymous";
57-57
: Remove unused variableorigin
.- const origin = request.headers.get("origin");
66-69
: Avoidconsole
in server code; use a logger or remove.Project guidelines prohibit
console
. Route currently logs missing envs.If there’s a logger (e.g.,
@/lib/logger
), replace with it; otherwise consider removing or gating underprocess.env.NODE_ENV === "development"
.- console.error( - "Missing environment variables:", - JSON.stringify(transcriptionCheck.missingVars) - ); + // logger.error("Missing environment variables", { missingVars: transcriptionCheck.missingVars });
123-141
: Error path usesconsole.error
; prefer centralized logging.Same guideline as above. Keep details but route through a logger to avoid direct console usage.
- console.error("Modal API error:", response.status, errorText); + // logger.error("Modal API error", { status: response.status, errorText });apps/web/src/app/api/waitlist/export/route.ts (1)
77-81
: Replaceconsole.error
with project logger (or remove).Adheres to the “don’t use console” rule and centralizes logs.
- console.error("Waitlist API error:", error); + // logger.error("Waitlist API error", { error });apps/web/src/components/editor/preview-panel.tsx (4)
368-376
: Left position calc is correct; consider extracting to a tiny helper for readability.The nested expression is accurate; a small helper like
toPercentX(x)
would reduce inline complexity.
378-386
: Top position calc mirrors left; same readability suggestion applies.
715-724
: Add explicit button type for expand controls.Prevents accidental submit behavior if this panel is ever used inside a form.
- <Button + <Button variant="text" size="icon" className="size-4! text-foreground/80 hover:text-foreground" onClick={onToggleExpanded} title="Exit fullscreen (Esc)" + type="button" >Apply similarly to the “Enter fullscreen” button.
Also applies to: 899-907
336-342
: Consider Next.js<Image />
over<img>
for optimization (optional).Guidelines advise against
<img>
in Next.js projects. If feasible for this preview context, migrate tonext/image
withfill
and appropriatesizes
. If not, keep as-is for correctness.Also applies to: 458-464
apps/web/src/lib/zk-encryption.ts (3)
24-31
: Optional: generate the AES key withsubtle.generateKey
instead of manual bytesUsing
subtle.generateKey
avoids handling raw key material before import and lets you control extractability/usages explicitly. If you still need raw bytes to persist the key, export them after encryption.Here’s what that would look like (outside the diffed lines, shown for clarity):
const cryptoKey = await webCrypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, // extractable so we can persist the key ["encrypt", "decrypt"] ); const exported = await webCrypto.subtle.exportKey("raw", cryptoKey); // ArrayBuffer // ...encrypt... return { encryptedData, key: exported, iv: iv.buffer };
52-59
: Avoid O(n²) string concatenation for large buffers in base64 encodeBuilding
binary
with+=
in a loop is quadratic and can be slow for large payloads. Concatenate in chunks to keep it linear.Apply this diff:
export function arrayBufferToBase64(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer); - let binary = ""; - for (let i = 0; i < bytes.byteLength; i++) { - binary += String.fromCharCode(bytes[i]); - } + let binary = ""; + const chunkSize = 0x8000; // 32KB chunks to avoid call-stack/arg limits + for (let i = 0; i < bytes.byteLength; i += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); + } return btoa(binary); }
15-31
: No SSR usage detected; guard is optionalI ran a repo-wide search for any imports of
zk-encryption
in server-only or non-browser code and found only one occurrence:
apps/web/src/components/editor/media-panel/views/captions.tsx:7
– a React component (client-side) importingencryptWithRandomKey
Since this utility isn’t currently used in any SSR or Node contexts, it won’t fail at runtime today. However, to future-proof and safely guard against accidental server-side usage or environments without
crypto.subtle
, you may still opt to apply the patch below.You can apply the following optional refactor to harden the function:
export async function encryptWithRandomKey( data: ArrayBuffer ): Promise<ZeroKnowledgeEncryptionResult> { + const webCrypto = globalThis.crypto; + if (!webCrypto?.subtle) { + throw new Error("Web Crypto API (crypto.subtle) is unavailable in this environment"); + } // Generate a truly random 256-bit key - const key = crypto.getRandomValues(new Uint8Array(32)); + const key = webCrypto.getRandomValues(new Uint8Array(32)); @@ - const cryptoKey = await crypto.subtle.importKey( + const cryptoKey = await webCrypto.subtle.importKey( @@ - const encryptedResult = await crypto.subtle.encrypt( + const encryptedResult = await webCrypto.subtle.encrypt(apps/web/src/components/editor/media-panel/views/stickers.tsx (3)
125-131
: Removeas any
on CSS custom propertiesThe two
as any
assertions violate our “Don’t use the any type” guideline. Use a typed style object that declares the CSS variables explicitly.Apply this refactor within the component:
- <div - className="grid gap-2" - style={{ - gridTemplateColumns: capSize - ? "repeat(auto-fill, minmax(var(--sticker-min, 96px), var(--sticker-max, 160px)))" - : "repeat(auto-fit, minmax(var(--sticker-min, 96px), 1fr))", - ["--sticker-min" as any]: "96px", - ...(capSize ? ({ ["--sticker-max"]: "160px" } as any) : {}), - }} - > + {(() => { + type GridVars = React.CSSProperties & Record<"--sticker-min" | "--sticker-max", string>; + const gridStyle: GridVars = { + gridTemplateColumns: capSize + ? "repeat(auto-fill, minmax(var(--sticker-min, 96px), var(--sticker-max, 160px)))" + : "repeat(auto-fit, minmax(var(--sticker-min, 96px), 1fr))", + "--sticker-min": "96px", + ...(capSize ? { "--sticker-max": "160px" } : { "--sticker-max": "160px" }), // keep key present for typing; value ignored if not used + }; + return ( + <div className="grid gap-2" style={gridStyle}> +``` (You can inline the closing tags accordingly.) --- `403-407`: **Add `type="button"` to native button** Per our JSX guidelines, always include a type for buttons. Defaulting to “submit” can cause accidental form submissions if this component is nested in a form. Apply this diff: ```diff - <button + <button + type="button" onClick={clearRecentStickers} className="ml-auto h-5 w-5 p-0 rounded hover:bg-accent flex items-center justify-center" >
336-344
: Preferfor...of
overArray.forEach
Guideline asks to favor
for...of
. This also plays nicer withawait
if you ever need it later.Apply this diff:
- if (currentCollection.categories) { - Object.values(currentCollection.categories).forEach((categoryIcons) => { - icons.push( - ...categoryIcons.map( - (name) => `${currentCollection.prefix}:${name}` - ) - ); - }); - } + if (currentCollection.categories) { + for (const categoryIcons of Object.values(currentCollection.categories)) { + icons.push( + ...categoryIcons.map((name) => `${currentCollection.prefix}:${name}`) + ); + } + }apps/web/src/stores/playback-store.ts (1)
40-42
: Avoidconsole
usage in storesOur guidelines discourage console logging. This warning is useful during development but should be routed through an app logger or removed.
Apply this diff to remove the console call (behavior is unchanged due to the DEFAULT_FPS fallback):
- if (!projectFps) - console.error( - "Project FPS is not set, assuming " + DEFAULT_FPS + "fps" - ); + // If FPS is not set, fallback to DEFAULT_FPS (no console logging per guidelines)If you prefer to keep a signal in dev, consider a dedicated logger utility rather than
console
.apps/web/src/components/ui/tooltip.tsx (1)
55-67
: Mark decorative SVG arrow as presentationalThis inline SVG is purely decorative. Add
aria-hidden
,focusable="false"
, androle="presentation"
so assistive tech ignores it without needing a<title>
.Apply this diff:
- {variant === "sidebar" && ( - <svg + {variant === "sidebar" && ( + <svg + aria-hidden="true" + focusable="false" + role="presentation" width="6" height="10" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg" className="absolute left-[-6px] top-1/2 -translate-y-1/2" >apps/web/src/stores/sounds-store.ts (2)
245-256
: Derive filename and MIME type from the response; sanitize name.Hardcoding
.mp3
andaudio/mpeg
risks incorrect file types if the preview is OGG/WAV/etc. Also sanitize the filename for filesystem safety.- const blob = await response.blob(); - const file = new File([blob], `${sound.name}.mp3`, { - type: "audio/mpeg", - }); + const blob = await response.blob(); + const contentType = response.headers.get("content-type") ?? "audio/mpeg"; + // naive extension mapping; extend as needed + const ext = contentType.includes("ogg") + ? "ogg" + : contentType.includes("wav") + ? "wav" + : contentType.includes("mpeg") + ? "mp3" + : "mp3"; + const safeName = sound.name.replace(/[^\w.-]+/g, "_").slice(0, 64); + const file = new File([blob], `${safeName}.${ext}`, { + type: contentType, + });
163-177
: Align saved-sounds state updates for consistency and UX.After save, you reload from storage; after remove, you mutate local state. Consider making both paths consistent (optimistic update + reconcile, or both pull-from-storage) to avoid flicker and reduce storage reads in rapid toggles.
Possible approach: optimistic update on save, then reconcile from storage in background (outside this range).
Also applies to: 183-194
apps/web/src/components/providers/global-prefetcher.ts (1)
21-68
: Abort in-flight fetch on unmount to free resources.Using an ignore flag avoids state updates but doesn’t cancel the network request. Add AbortController and pass a signal to fetch.
- let ignore = false; + let ignore = false; + const ac = new AbortController(); const prefetchTopSounds = async () => { try { if (!ignore) { setLoading(true); setError(null); } - const response = await fetch( - "/api/sounds/search?page_size=50&sort=downloads" - ); + const response = await fetch( + "/api/sounds/search?page_size=50&sort=downloads", + { signal: ac.signal } + ); if (!ignore) { if (!response.ok) { throw new Error(`Failed to fetch: ${response.status}`); }return () => { clearTimeout(timeoutId); - ignore = true; + ignore = true; + ac.abort(); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
bun.lock
is excluded by!**/*.lock
📒 Files selected for processing (38)
apps/web/.env.example
(0 hunks)apps/web/migrations/meta/0003_snapshot.json
(6 hunks)apps/web/migrations/meta/_journal.json
(1 hunks)apps/web/src/app/api/get-upload-url/route.ts
(1 hunks)apps/web/src/app/api/sounds/search/route.ts
(1 hunks)apps/web/src/app/api/transcribe/route.ts
(1 hunks)apps/web/src/app/api/waitlist/export/route.ts
(1 hunks)apps/web/src/app/editor/[project_id]/layout.tsx
(1 hunks)apps/web/src/app/projects/page.tsx
(1 hunks)apps/web/src/components/editor/layout-guide-overlay.tsx
(1 hunks)apps/web/src/components/editor/media-panel/views/base-view.tsx
(1 hunks)apps/web/src/components/editor/media-panel/views/sounds.tsx
(1 hunks)apps/web/src/components/editor/media-panel/views/stickers.tsx
(2 hunks)apps/web/src/components/editor/preview-panel.tsx
(2 hunks)apps/web/src/components/editor/properties-panel/text-properties.tsx
(1 hunks)apps/web/src/components/export-button.tsx
(1 hunks)apps/web/src/components/footer.tsx
(1 hunks)apps/web/src/components/icons.tsx
(2 hunks)apps/web/src/components/keyboard-shortcuts-help.tsx
(1 hunks)apps/web/src/components/language-select.tsx
(1 hunks)apps/web/src/components/panel-preset-selector.tsx
(1 hunks)apps/web/src/components/providers/global-prefetcher.ts
(1 hunks)apps/web/src/components/ui/editable-timecode.tsx
(1 hunks)apps/web/src/components/ui/input-with-back.tsx
(1 hunks)apps/web/src/components/ui/input.tsx
(1 hunks)apps/web/src/components/ui/tooltip.tsx
(4 hunks)apps/web/src/data/colors/syntax-ui.tsx
(1 hunks)apps/web/src/hooks/use-highlight-scroll.ts
(1 hunks)apps/web/src/hooks/use-infinite-scroll.ts
(1 hunks)apps/web/src/hooks/use-sound-search.ts
(1 hunks)apps/web/src/lib/editor-utils.ts
(1 hunks)apps/web/src/lib/iconify-api.ts
(0 hunks)apps/web/src/lib/schemas/waitlist.ts
(1 hunks)apps/web/src/lib/transcription-utils.ts
(1 hunks)apps/web/src/lib/zk-encryption.ts
(1 hunks)apps/web/src/stores/playback-store.ts
(1 hunks)apps/web/src/stores/sounds-store.ts
(1 hunks)apps/web/src/types/sounds.ts
(1 hunks)
💤 Files with no reviewable changes (2)
- apps/web/src/lib/iconify-api.ts
- apps/web/.env.example
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{jsx,tsx}
: Don't useaccessKey
attribute on any HTML element.
Don't setaria-hidden="true"
on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like<marquee>
or<blink>
.
Only use thescope
prop on<th>
elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assigntabIndex
to non-interactive HTML elements.
Don't use positive integers fortabIndex
property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include atitle
element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
AssigntabIndex
to non-interactive HTML elements witharia-activedescendant
.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include atype
attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden
).
Always include alang
attribute on the html element.
Always include atitle
attribute for iframe elements.
AccompanyonClick
with at least one of:onKeyUp
,onKeyDown
, oronKeyPress
.
AccompanyonMouseOver
/onMouseOut
withonFocus
/onBlur
.
Include caption tracks for audio and video elements.
Use semantic elements instead of role attributes in JSX.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*
) are valid.
Use valid, non-abstract ARIA roles for elements with...
Files:
apps/web/src/components/keyboard-shortcuts-help.tsx
apps/web/src/components/footer.tsx
apps/web/src/components/export-button.tsx
apps/web/src/components/editor/properties-panel/text-properties.tsx
apps/web/src/components/editor/media-panel/views/sounds.tsx
apps/web/src/app/projects/page.tsx
apps/web/src/components/ui/input.tsx
apps/web/src/components/editor/media-panel/views/stickers.tsx
apps/web/src/components/editor/media-panel/views/base-view.tsx
apps/web/src/components/editor/layout-guide-overlay.tsx
apps/web/src/app/editor/[project_id]/layout.tsx
apps/web/src/components/editor/preview-panel.tsx
apps/web/src/components/ui/input-with-back.tsx
apps/web/src/components/panel-preset-selector.tsx
apps/web/src/components/language-select.tsx
apps/web/src/data/colors/syntax-ui.tsx
apps/web/src/components/ui/editable-timecode.tsx
apps/web/src/components/ui/tooltip.tsx
apps/web/src/components/icons.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}
: Don't use TypeScript enums.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use TypeScript namespaces.
Don't use non-null assertions with the!
postfix operator.
Don't use parameter properties in class constructors.
Don't use user-defined types.
Useas const
instead of literal types and type annotations.
Use eitherT[]
orArray<T>
consistently.
Initialize each enum member value explicitly.
Useexport type
for types.
Useimport type
for types.
Make sure all enum members are literal values.
Don't use TypeScript const enum.
Don't declare empty interfaces.
Don't let variables evolve into any type through reassignments.
Don't use the any type.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use implicit any type on variable declarations.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use the TypeScript directive @ts-ignore.
Use consistent accessibility modifiers on class properties and methods.
Use function types instead of object types with call signatures.
Don't use void type outside of generic or return types.
**/*.{ts,tsx}
: Don't use primitive type aliases or misleading types
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Use either T[] or Array consistently
Don't use the any type
Files:
apps/web/src/components/keyboard-shortcuts-help.tsx
apps/web/src/hooks/use-sound-search.ts
apps/web/src/components/footer.tsx
apps/web/src/components/export-button.tsx
apps/web/src/components/editor/properties-panel/text-properties.tsx
apps/web/src/app/api/transcribe/route.ts
apps/web/src/types/sounds.ts
apps/web/src/components/editor/media-panel/views/sounds.tsx
apps/web/src/app/projects/page.tsx
apps/web/src/hooks/use-highlight-scroll.ts
apps/web/src/lib/zk-encryption.ts
apps/web/src/lib/schemas/waitlist.ts
apps/web/src/stores/sounds-store.ts
apps/web/src/stores/playback-store.ts
apps/web/src/components/ui/input.tsx
apps/web/src/components/editor/media-panel/views/stickers.tsx
apps/web/src/app/api/get-upload-url/route.ts
apps/web/src/components/editor/media-panel/views/base-view.tsx
apps/web/src/hooks/use-infinite-scroll.ts
apps/web/src/app/api/waitlist/export/route.ts
apps/web/src/components/editor/layout-guide-overlay.tsx
apps/web/src/app/editor/[project_id]/layout.tsx
apps/web/src/lib/transcription-utils.ts
apps/web/src/components/providers/global-prefetcher.ts
apps/web/src/app/api/sounds/search/route.ts
apps/web/src/components/editor/preview-panel.tsx
apps/web/src/components/ui/input-with-back.tsx
apps/web/src/components/panel-preset-selector.tsx
apps/web/src/components/language-select.tsx
apps/web/src/data/colors/syntax-ui.tsx
apps/web/src/components/ui/editable-timecode.tsx
apps/web/src/components/ui/tooltip.tsx
apps/web/src/lib/editor-utils.ts
apps/web/src/components/icons.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,ts,tsx}
: Don't use the return value of React.render.
Don't use consecutive spaces in regular expression literals.
Don't use thearguments
object.
Don't use primitive type aliases or misleading types.
Don't use the comma operator.
Don't write functions that exceed a given Cognitive Complexity score.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use uselessthis
aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names that aren't base 10 or use underscore separators.
Remove redundant terms from logical expressions.
Use while loops instead of...
Files:
apps/web/src/components/keyboard-shortcuts-help.tsx
apps/web/src/hooks/use-sound-search.ts
apps/web/src/components/footer.tsx
apps/web/src/components/export-button.tsx
apps/web/src/components/editor/properties-panel/text-properties.tsx
apps/web/src/app/api/transcribe/route.ts
apps/web/src/types/sounds.ts
apps/web/src/components/editor/media-panel/views/sounds.tsx
apps/web/src/app/projects/page.tsx
apps/web/src/hooks/use-highlight-scroll.ts
apps/web/src/lib/zk-encryption.ts
apps/web/src/lib/schemas/waitlist.ts
apps/web/src/stores/sounds-store.ts
apps/web/src/stores/playback-store.ts
apps/web/src/components/ui/input.tsx
apps/web/src/components/editor/media-panel/views/stickers.tsx
apps/web/src/app/api/get-upload-url/route.ts
apps/web/src/components/editor/media-panel/views/base-view.tsx
apps/web/src/hooks/use-infinite-scroll.ts
apps/web/src/app/api/waitlist/export/route.ts
apps/web/src/components/editor/layout-guide-overlay.tsx
apps/web/src/app/editor/[project_id]/layout.tsx
apps/web/src/lib/transcription-utils.ts
apps/web/src/components/providers/global-prefetcher.ts
apps/web/src/app/api/sounds/search/route.ts
apps/web/src/components/editor/preview-panel.tsx
apps/web/src/components/ui/input-with-back.tsx
apps/web/src/components/panel-preset-selector.tsx
apps/web/src/components/language-select.tsx
apps/web/src/data/colors/syntax-ui.tsx
apps/web/src/components/ui/editable-timecode.tsx
apps/web/src/components/ui/tooltip.tsx
apps/web/src/lib/editor-utils.ts
apps/web/src/components/icons.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{tsx,jsx}
: Always include a title element for icons unless there's text beside the icon
Always include a type attribute for button elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Don't import React itself
Don't define React components inside other components
Don't use both children and dangerouslySetInnerHTML on the same element
Don't insert comments as text nodes
Use <>...</> instead of ...
Files:
apps/web/src/components/keyboard-shortcuts-help.tsx
apps/web/src/components/footer.tsx
apps/web/src/components/export-button.tsx
apps/web/src/components/editor/properties-panel/text-properties.tsx
apps/web/src/components/editor/media-panel/views/sounds.tsx
apps/web/src/app/projects/page.tsx
apps/web/src/components/ui/input.tsx
apps/web/src/components/editor/media-panel/views/stickers.tsx
apps/web/src/components/editor/media-panel/views/base-view.tsx
apps/web/src/components/editor/layout-guide-overlay.tsx
apps/web/src/app/editor/[project_id]/layout.tsx
apps/web/src/components/editor/preview-panel.tsx
apps/web/src/components/ui/input-with-back.tsx
apps/web/src/components/panel-preset-selector.tsx
apps/web/src/components/language-select.tsx
apps/web/src/data/colors/syntax-ui.tsx
apps/web/src/components/ui/editable-timecode.tsx
apps/web/src/components/ui/tooltip.tsx
apps/web/src/components/icons.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx,js,jsx}
: Don't use the comma operator
Use for...of statements instead of Array.forEach
Don't initialize variables to undefined
Use .flatMap() instead of map().flat() when possible
Don't assign a value to itself
Avoid unused imports and variables
Don't use await inside loops
Don't hardcode sensitive data like API keys and tokens
Don't use the delete operator
Don't use global eval()
Use String.slice() instead of String.substr() and String.substring()
Don't use else blocks when the if block breaks early
Put default function parameters and optional function parameters last
Use new when throwing an error
Use String.trimStart() and String.trimEnd() over String.trimLeft() and String.trimRight()
Files:
apps/web/src/components/keyboard-shortcuts-help.tsx
apps/web/src/hooks/use-sound-search.ts
apps/web/src/components/footer.tsx
apps/web/src/components/export-button.tsx
apps/web/src/components/editor/properties-panel/text-properties.tsx
apps/web/src/app/api/transcribe/route.ts
apps/web/src/types/sounds.ts
apps/web/src/components/editor/media-panel/views/sounds.tsx
apps/web/src/app/projects/page.tsx
apps/web/src/hooks/use-highlight-scroll.ts
apps/web/src/lib/zk-encryption.ts
apps/web/src/lib/schemas/waitlist.ts
apps/web/src/stores/sounds-store.ts
apps/web/src/stores/playback-store.ts
apps/web/src/components/ui/input.tsx
apps/web/src/components/editor/media-panel/views/stickers.tsx
apps/web/src/app/api/get-upload-url/route.ts
apps/web/src/components/editor/media-panel/views/base-view.tsx
apps/web/src/hooks/use-infinite-scroll.ts
apps/web/src/app/api/waitlist/export/route.ts
apps/web/src/components/editor/layout-guide-overlay.tsx
apps/web/src/app/editor/[project_id]/layout.tsx
apps/web/src/lib/transcription-utils.ts
apps/web/src/components/providers/global-prefetcher.ts
apps/web/src/app/api/sounds/search/route.ts
apps/web/src/components/editor/preview-panel.tsx
apps/web/src/components/ui/input-with-back.tsx
apps/web/src/components/panel-preset-selector.tsx
apps/web/src/components/language-select.tsx
apps/web/src/data/colors/syntax-ui.tsx
apps/web/src/components/ui/editable-timecode.tsx
apps/web/src/components/ui/tooltip.tsx
apps/web/src/lib/editor-utils.ts
apps/web/src/components/icons.tsx
🧠 Learnings (2)
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
PR: OpenCut-app/OpenCut#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Make sure label elements have text content and are associated with an input.
Applied to files:
apps/web/src/components/ui/input.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
PR: OpenCut-app/OpenCut#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Use correct ISO language/country codes for the `lang` attribute.
Applied to files:
apps/web/src/components/language-select.tsx
🧬 Code graph analysis (21)
apps/web/src/app/api/transcribe/route.ts (3)
apps/web/src/lib/rate-limit.ts (1)
baseRateLimit
(11-16)apps/web/src/lib/transcription-utils.ts (1)
isTranscriptionConfigured
(3-13)apps/web/src/env.ts (1)
env
(7-45)
apps/web/src/components/editor/media-panel/views/sounds.tsx (1)
apps/web/src/components/ui/scroll-area.tsx (1)
props
(7-15)
apps/web/src/app/projects/page.tsx (2)
apps/web/src/components/ui/command.tsx (1)
props
(41-53)apps/web/src/components/ui/sidebar.tsx (1)
props
(338-350)
apps/web/src/hooks/use-highlight-scroll.ts (1)
apps/web/src/components/ui/scroll-area.tsx (1)
props
(7-15)
apps/web/src/stores/sounds-store.ts (6)
apps/web/src/types/sounds.ts (2)
SoundEffect
(1-22)SavedSound
(24-34)apps/web/src/lib/storage/storage-service.ts (4)
storageService
(393-393)isSoundSaved
(359-367)removeSavedSound
(343-357)saveSoundEffect
(310-341)apps/web/src/stores/project-store.ts (1)
useProjectStore
(68-527)apps/web/src/stores/media-store.ts (1)
useMediaStore
(161-329)apps/web/src/stores/timeline-store.ts (1)
useTimelineStore
(216-1516)apps/web/src/stores/playback-store.ts (1)
usePlaybackStore
(77-176)
apps/web/src/stores/playback-store.ts (1)
apps/web/src/stores/project-store.ts (1)
DEFAULT_FPS
(11-11)
apps/web/src/components/ui/input.tsx (1)
apps/web/src/lib/utils.ts (1)
cn
(6-8)
apps/web/src/app/api/get-upload-url/route.ts (4)
apps/web/src/app/api/transcribe/route.ts (1)
POST
(52-189)apps/web/src/lib/rate-limit.ts (1)
baseRateLimit
(11-16)apps/web/src/lib/transcription-utils.ts (1)
isTranscriptionConfigured
(3-13)apps/web/src/env.ts (1)
env
(7-45)
apps/web/src/components/editor/media-panel/views/base-view.tsx (3)
apps/web/src/components/ui/scroll-area.tsx (1)
ScrollArea
(18-18)apps/web/src/components/ui/tabs.tsx (4)
Tabs
(55-55)TabsList
(55-55)TabsTrigger
(55-55)TabsContent
(55-55)apps/web/src/components/ui/separator.tsx (2)
Separator
(31-31)props
(12-27)
apps/web/src/hooks/use-infinite-scroll.ts (1)
apps/web/src/components/ui/scroll-area.tsx (1)
props
(7-15)
apps/web/src/app/api/waitlist/export/route.ts (4)
apps/web/src/lib/schemas/waitlist.ts (2)
exportWaitlistSchema
(3-5)exportWaitlistResponseSchema
(7-10)apps/web/src/lib/rate-limit.ts (1)
baseRateLimit
(11-16)packages/db/src/index.ts (2)
db
(20-20)eq
(27-27)packages/db/src/schema.ts (1)
exportWaitlist
(61-70)
apps/web/src/components/editor/layout-guide-overlay.tsx (1)
apps/web/src/stores/editor-store.ts (1)
useEditorStore
(39-91)
apps/web/src/app/editor/[project_id]/layout.tsx (1)
apps/web/src/components/providers/global-prefetcher.ts (1)
useGlobalPrefetcher
(6-78)
apps/web/src/lib/transcription-utils.ts (1)
apps/web/src/env.ts (1)
env
(7-45)
apps/web/src/components/providers/global-prefetcher.ts (1)
apps/web/src/stores/sounds-store.ts (1)
useSoundsStore
(74-282)
apps/web/src/app/api/sounds/search/route.ts (2)
apps/web/src/lib/rate-limit.ts (1)
baseRateLimit
(11-16)apps/web/src/env.ts (1)
env
(7-45)
apps/web/src/components/ui/input-with-back.tsx (3)
apps/web/src/components/ui/phone-input.tsx (2)
props
(62-68)props
(30-55)apps/web/src/components/ui/sidebar.tsx (1)
props
(338-350)apps/web/src/components/ui/command.tsx (1)
props
(41-53)
apps/web/src/components/panel-preset-selector.tsx (1)
apps/web/src/stores/panel-store.ts (2)
PanelPreset
(4-8)usePanelStore
(68-225)
apps/web/src/components/language-select.tsx (2)
apps/web/src/components/editor/media-panel/views/captions.tsx (1)
languages
(20-30)apps/web/src/lib/utils.ts (1)
cn
(6-8)
apps/web/src/components/ui/editable-timecode.tsx (3)
apps/web/src/lib/time.ts (3)
TimeCode
(5-5)formatTimeCode
(8-29)parseTimeCode
(31-122)apps/web/src/stores/project-store.ts (1)
DEFAULT_FPS
(11-11)apps/web/src/lib/utils.ts (1)
cn
(6-8)
apps/web/src/lib/editor-utils.ts (1)
apps/web/src/types/editor.ts (1)
CanvasSize
(5-8)
⏰ 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: Vade Review
const validationResult = searchParamsSchema.safeParse({ | ||
q: searchParams.get("q") || undefined, | ||
type: searchParams.get("type") || undefined, | ||
page: searchParams.get("page") || undefined, | ||
page_size: searchParams.get("page_size") || undefined, | ||
sort: searchParams.get("sort") || undefined, | ||
min_rating: searchParams.get("min_rating") || undefined, | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: commercial_only
query param is ignored.
You define commercial_only
in searchParamsSchema
but never feed it from the URL, so it always defaults to true
and client input is discarded.
Apply this diff to wire it up:
const validationResult = searchParamsSchema.safeParse({
q: searchParams.get("q") || undefined,
type: searchParams.get("type") || undefined,
page: searchParams.get("page") || undefined,
page_size: searchParams.get("page_size") || undefined,
sort: searchParams.get("sort") || undefined,
min_rating: searchParams.get("min_rating") || undefined,
+ commercial_only: searchParams.get("commercial_only") || undefined,
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const validationResult = searchParamsSchema.safeParse({ | |
q: searchParams.get("q") || undefined, | |
type: searchParams.get("type") || undefined, | |
page: searchParams.get("page") || undefined, | |
page_size: searchParams.get("page_size") || undefined, | |
sort: searchParams.get("sort") || undefined, | |
min_rating: searchParams.get("min_rating") || undefined, | |
}); | |
const validationResult = searchParamsSchema.safeParse({ | |
q: searchParams.get("q") || undefined, | |
type: searchParams.get("type") || undefined, | |
page: searchParams.get("page") || undefined, | |
page_size: searchParams.get("page_size") || undefined, | |
sort: searchParams.get("sort") || undefined, | |
min_rating: searchParams.get("min_rating") || undefined, | |
commercial_only: searchParams.get("commercial_only") || undefined, | |
}); |
🤖 Prompt for AI Agents
In apps/web/src/app/api/sounds/search/route.ts around lines 102 to 110, the
searchParamsSchema's commercial_only field is never populated from the URL so
client input is ignored; update the object passed to safeParse to include
commercial_only by reading it from searchParams (e.g., get the raw string with
searchParams.get("commercial_only") and convert "true"/"false" to a boolean or
undefined) so the parsed value reflects the query param instead of always
defaulting to true.
console.error("Freesound API error:", response.status, errorText); | ||
return NextResponse.json( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Replace console.error
with a project logger or telemetry.
Guidelines say “Don’t use console.” Prefer a shared logger, error reporter, or structured logs behind an abstraction.
If a logger exists in the repo, this script will help find it:
Also applies to: 195-198, 247-250, 259-259
🏁 Script executed:
#!/bin/bash
# Search for an existing logger or error reporter utility
rg -n -C2 -P --type=ts --type=tsx '\blog(ger)?\b|report(Error|Exception)|Sentry|LogTail|Pino|Winston|otel|OpenTelemetry|Datadog|track\('
Length of output: 166
🏁 Script executed:
#!/bin/bash
# Search for an existing logger or error reporter utility across TS, TSX, JS, JSX files
rg -n -C2 -P '\blog(ger)?\b|report(Error|Exception)|Sentry|LogTail|Pino|Winston|otel|OpenTelemetry|Datadog|track\(' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx'
Length of output: 124850
Implement a shared logger and remove all bare console.*
calls
We didn’t find any existing logging abstraction (Pino, Winston, Sentry, OpenTelemetry, etc.) in the repo. Per the project’s .github/copilot-instructions.md
(“Don’t use console.”), we should centralize logging behind a small wrapper (e.g. src/lib/logger.ts
exporting info
, warn
, error
, debug
) and replace all native console
calls accordingly.
Key refactors required:
- Create a logger module (for example
src/lib/logger.ts
) with methods:export const logger = { info: (msg: string, meta?: any) => { /* send to stdout or telemetry */ }, warn: (msg: string, meta?: any) => { /* ... */ }, error: (msg: string, meta?: any) => { /* ... */ }, debug: (msg: string, meta?: any) => { /* ... */ }, };
- In
apps/web/src/app/api/sounds/search/route.ts
, replace:(And similarly for the other- console.error("Freesound API error:", response.status, errorText); + logger.error("Freesound API error", { status: response.status, errorText });
console.error
calls at lines 195–198, 247–250, and 259.) - Migrate all
console.log
and otherconsole.*
usages across the codebase (e.g. inapps/web/src/stores/editor-store.ts
,editor-header.tsx
, timeline components, hooks likeuse-timeline-playhead
anduse-selection-box
, captions view, etc.) tologger.debug
orlogger.info
as appropriate. - Remove any remaining bare
console.*
calls once they’ve been migrated.
This refactor is mandatory to ensure consistent, structured logs and to enable future telemetry integrations.
const modalRequestBody: any = { | ||
filename, | ||
language, | ||
}; | ||
|
||
// Add encryption parameters if provided (zero-knowledge) | ||
if (decryptionKey && iv) { | ||
modalRequestBody.decryptionKey = decryptionKey; | ||
modalRequestBody.iv = iv; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove any
from modalRequestBody
; give it a concrete type.
Using any
breaks type safety and violates the TS guidelines. The shape here is simple—type it inline.
- const modalRequestBody: any = {
+ const modalRequestBody: {
+ filename: string;
+ language: string;
+ decryptionKey?: string;
+ iv?: string;
+ } = {
filename,
language,
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const modalRequestBody: any = { | |
filename, | |
language, | |
}; | |
// Add encryption parameters if provided (zero-knowledge) | |
if (decryptionKey && iv) { | |
modalRequestBody.decryptionKey = decryptionKey; | |
modalRequestBody.iv = iv; | |
} | |
const modalRequestBody: { | |
filename: string; | |
language: string; | |
decryptionKey?: string; | |
iv?: string; | |
} = { | |
filename, | |
language, | |
}; | |
// Add encryption parameters if provided (zero-knowledge) | |
if (decryptionKey && iv) { | |
modalRequestBody.decryptionKey = decryptionKey; | |
modalRequestBody.iv = iv; | |
} |
🤖 Prompt for AI Agents
In apps/web/src/app/api/transcribe/route.ts around lines 103 to 112, remove the
use of `any` for `modalRequestBody` and replace it with a concrete inline type:
declare `filename` and `language` as required strings and `decryptionKey` and
`iv` as optional strings; then initialize the object with those properties and
keep the existing conditional assignment for the optional fields.
} | ||
|
||
const rawResult = await response.json(); | ||
console.log("Raw Modal response:", JSON.stringify(rawResult, null, 2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Don’t log full transcription payloads; potential PII and large logs.
Full JSON of transcriptions can include sensitive content and bloat logs.
- console.log("Raw Modal response:", JSON.stringify(rawResult, null, 2));
+ // logger.debug("Modal response received"); // Avoid logging full payloads
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
console.log("Raw Modal response:", JSON.stringify(rawResult, null, 2)); | |
// logger.debug("Modal response received"); // Avoid logging full payloads |
🤖 Prompt for AI Agents
In apps/web/src/app/api/transcribe/route.ts around line 145, the code currently
console.logs the entire transcription JSON (console.log("Raw Modal response:",
JSON.stringify(rawResult, null, 2))); which can expose PII and generate huge
logs; remove that full payload log and instead log a safe, minimal summary
(e.g., transcription ID, length, status) or a truncated/redacted snippet (first
N characters) or a hash of the payload, and use the application logger at a
debug level gated by environment/config so full payloads are never printed in
production.
<motion.div | ||
className="absolute left-0 top-1/2 -translate-y-1/2 cursor-pointer hover:opacity-75 transition-opacity z-10" | ||
initial={{ | ||
x: isExpanded ? 0 : buttonOffset, | ||
opacity: isExpanded ? 1 : 0.5, | ||
}} | ||
animate={{ | ||
x: isExpanded ? 0 : buttonOffset, | ||
opacity: isExpanded ? 1 : 0.5, | ||
}} | ||
transition={smoothTransition} | ||
onClick={() => setIsExpanded(!isExpanded)} | ||
> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessibility: avoid click handlers on non-interactive elements.
onClick on a div breaks keyboard accessibility and violates our guideline to use interactive elements for interactions. Move the handler to the Button (which is already inside) and let the wrapper be purely positional.
<motion.div
className="absolute left-0 top-1/2 -translate-y-1/2 cursor-pointer hover:opacity-75 transition-opacity z-10"
@@
- onClick={() => setIsExpanded(!isExpanded)}
>
- <Button
+ <Button
+ type="button"
+ aria-label={isExpanded ? "Collapse search" : "Expand search"}
+ onClick={() => setIsExpanded(!isExpanded)}
variant="outline"
className="!size-9 rounded-full bg-panel-accent"
>
- <ArrowLeft />
+ <ArrowLeft title="Back" aria-hidden="true" focusable="false" />
</Button>
</motion.div>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<motion.div | |
className="absolute left-0 top-1/2 -translate-y-1/2 cursor-pointer hover:opacity-75 transition-opacity z-10" | |
initial={{ | |
x: isExpanded ? 0 : buttonOffset, | |
opacity: isExpanded ? 1 : 0.5, | |
}} | |
animate={{ | |
x: isExpanded ? 0 : buttonOffset, | |
opacity: isExpanded ? 1 : 0.5, | |
}} | |
transition={smoothTransition} | |
onClick={() => setIsExpanded(!isExpanded)} | |
> | |
<motion.div | |
className="absolute left-0 top-1/2 -translate-y-1/2 cursor-pointer hover:opacity-75 transition-opacity z-10" | |
initial={{ | |
x: isExpanded ? 0 : buttonOffset, | |
opacity: isExpanded ? 1 : 0.5, | |
}} | |
animate={{ | |
x: isExpanded ? 0 : buttonOffset, | |
opacity: isExpanded ? 1 : 0.5, | |
}} | |
transition={smoothTransition} | |
> | |
<Button | |
type="button" | |
aria-label={isExpanded ? "Collapse search" : "Expand search"} | |
onClick={() => setIsExpanded(!isExpanded)} | |
variant="outline" | |
className="!size-9 rounded-full bg-panel-accent" | |
> | |
<ArrowLeft title="Back" aria-hidden="true" focusable="false" /> | |
</Button> | |
</motion.div> |
🤖 Prompt for AI Agents
In apps/web/src/components/ui/input-with-back.tsx around lines 41-53, the motion
div currently has an onClick handler which breaks keyboard accessibility; remove
the onClick and also remove the "cursor-pointer" class from this non-interactive
wrapper so it stays purely positional, then move the toggle logic to the
interactive Button inside (add the onClick there, and set
aria-expanded={isExpanded} and an accessible aria-label or visually hidden text
if needed) so the button handles mouse and keyboard activation; keep the motion
animation props on the wrapper unchanged.
<Button | ||
variant="outline" | ||
className="!size-9 rounded-full bg-panel-accent" | ||
> | ||
<ArrowLeft /> | ||
</Button> | ||
</motion.div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Button type and accessible name required.
Explicitly set type="button" (to avoid accidental form submission) and provide an accessible name since the button is icon-only.
The diff in the previous comment adds both type and aria-label.
🤖 Prompt for AI Agents
In apps/web/src/components/ui/input-with-back.tsx around lines 54 to 60, the
icon-only Button is missing an explicit type and accessible name; update the
Button element to include type="button" to prevent accidental form submission
and add an accessible label via aria-label (e.g., aria-label="Go back" or
"Back") so screen readers can announce its purpose.
<Input | ||
placeholder={placeholder} | ||
className="pl-9 bg-panel-accent w-full" | ||
value={value} | ||
onChange={(e) => onChange?.(e.target.value)} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Input lacks a programmatic label.
This composite component doesn’t expose aria-label/aria-labelledby to label the input; placeholder isn’t a label. Add an optional ariaLabel prop and pass it to Input to satisfy labeling requirements when used in icon-only contexts.
interface InputWithBackProps {
isExpanded: boolean;
setIsExpanded: (isExpanded: boolean) => void;
placeholder?: string;
value?: string;
onChange?: (value: string) => void;
+ ariaLabel?: string;
}
@@
export function InputWithBack({
isExpanded,
setIsExpanded,
placeholder = "Search anything",
value,
onChange,
+ ariaLabel,
}: InputWithBackProps) {
@@
<Input
placeholder={placeholder}
className="pl-9 bg-panel-accent w-full"
value={value}
onChange={(e) => onChange?.(e.target.value)}
+ aria-label={ariaLabel}
/>
If you prefer, I can follow up with a small PR to update the call sites to provide ariaLabel.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<Input | |
placeholder={placeholder} | |
className="pl-9 bg-panel-accent w-full" | |
value={value} | |
onChange={(e) => onChange?.(e.target.value)} | |
/> | |
++ b/apps/web/src/components/ui/input-with-back.tsx | |
@@ interface InputWithBackProps { | |
isExpanded: boolean; | |
setIsExpanded: (isExpanded: boolean) => void; | |
placeholder?: string; | |
value?: string; | |
onChange?: (value: string) => void; | |
ariaLabel?: string; | |
} | |
@@ export function InputWithBack({ | |
isExpanded, | |
setIsExpanded, | |
placeholder = "Search anything", | |
value, | |
onChange, | |
ariaLabel, | |
}: InputWithBackProps) { | |
@@ | |
<Input | |
placeholder={placeholder} | |
className="pl-9 bg-panel-accent w-full" | |
value={value} | |
onChange={(e) => onChange?.(e.target.value)} | |
aria-label={ariaLabel} | |
/> |
🤖 Prompt for AI Agents
In apps/web/src/components/ui/input-with-back.tsx around lines 76 to 81, the
Input currently only uses placeholder for identification which is not an
accessible programmatic label; add an optional ariaLabel prop to the component's
props (and export type/interface), accept it in the component, and pass it
through to the underlying <Input> as either aria-label={ariaLabel} (or
aria-labelledby when an id is provided) so callers can supply a label for
icon-only contexts; ensure prop typing and any default/optional handling are
updated and preserved when forwarding other props.
searchParams.set("commercial_only", commercialOnly.toString()); | ||
const response = await fetch( | ||
`/api/sounds/search?${searchParams.toString()}` | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: initial search ignores the commercialOnly filter (inconsistent with loadMore).
You pass commercial_only to the “load more” request but not to the initial page-1 request, which can yield mixed pages when the filter is enabled. Add the query param to the initial fetch and include commercialOnly in the effect deps so a filter change triggers a fresh search.
Apply this diff:
@@
- const response = await fetch(
- `/api/sounds/search?q=${encodeURIComponent(query)}&type=effects&page=1`
- );
+ const searchParams = new URLSearchParams({
+ q: query,
+ type: "effects",
+ page: "1",
+ commercial_only: commercialOnly.toString(),
+ });
+ const response = await fetch(`/api/sounds/search?${searchParams.toString()}`);
@@
- }, [
+ }, [
query,
+ commercialOnly,
lastSearchQuery,
searchResults.length,
setSearchResults,
setSearching,
setSearchError,
setLastSearchQuery,
setCurrentPage,
setHasNextPage,
setTotalCount,
resetPagination,
]);
Also applies to: 80-87, 94-127, 133-145
🤖 Prompt for AI Agents
In apps/web/src/hooks/use-sound-search.ts around lines 52-56 (and similarly
update ranges 80-87, 94-127, 133-145), the initial search request omits the
commercial_only query param and the effect does not include commercialOnly in
its dependency array; add searchParams.set("commercial_only",
commercialOnly.toString()) to the initial fetch URL construction and ensure
every fetch (initial and loadMore) includes that param, and add commercialOnly
to the useEffect dependency list so toggling the filter triggers a fresh search
(apply the same fix to the other indicated line ranges).
import { env } from "@/env"; | ||
|
||
export function isTranscriptionConfigured() { | ||
const missingVars = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Type the empty array to avoid implicit any/never inference.
Without an explicit type, []
can infer to never[]
or any[]
depending on config. This violates the “no implicit any” guideline and can cause push errors in strict projects.
Apply:
- const missingVars = [];
+ const missingVars: string[] = [];
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const missingVars = []; | |
const missingVars: string[] = []; |
🤖 Prompt for AI Agents
In apps/web/src/lib/transcription-utils.ts around line 4, the const missingVars
= []; is untyped and may infer any[]/never[]; explicitly annotate the array type
(for example use string[] if it holds variable names) when declaring missingVars
to satisfy no-implicit-any/strict checks and avoid inference issues.
{ |
Description
Added an icon to search bar in the project page
Fixes # (issue)
Type of change
Please delete options that are not relevant.
How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
Test Configuration:
Screenshots (if applicable)
Add screenshots to help explain your changes.
Checklist:
Additional context
Add any other context about the pull request here.
Summary by CodeRabbit
Style
Chores