Skip to content

Conversation

DeveloperAromal
Copy link

@DeveloperAromal DeveloperAromal commented Aug 23, 2025

Description

Added an icon to search bar in the project page

Fixes # (issue)

  • Added lucide react search icon to input

Type of change

  • Updated UI design

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • Localhost test

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

  • Tested in localhost
  • Tested in browser inspect tab for every screen size

Test Configuration:

  • Node version: 19
  • Browser (if applicable): Brave
  • Operating System: Windows

Screenshots (if applicable)

Screenshot 2025-08-23 171924

Add screenshots to help explain your changes.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have added screenshots if ui has been changed
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • [ ] I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Additional context

Add any other context about the pull request here.

Summary by CodeRabbit

  • Style

    • Added a search icon to the Projects page search bar with improved spacing for clearer input.
    • Applied broad formatting and whitespace cleanups across UI components, API routes, hooks, stores, and icons with no behavioral changes.
  • Chores

    • Removed the environment-variable example template.
    • Normalized migration metadata formatting and added trailing newlines where missing.

Copy link

vercel bot commented Aug 23, 2025

@DeveloperAromal is attempting to deploy a commit to the OpenCut OSS Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

netlify bot commented Aug 23, 2025

👷 Deploy request for appcut pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit e4536a2

Copy link
Contributor

coderabbitai bot commented Aug 23, 2025

Walkthrough

Primarily 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

Cohort / File(s) Summary
Environment template removal
apps/web/.env.example
Deleted environment-variable template file.
Migrations formatting
apps/web/migrations/meta/0003_snapshot.json, apps/web/migrations/meta/_journal.json
Normalized JSON array formatting and EOF newline adjustments; no schema changes.
API routes (formatting only)
apps/web/src/app/api/get-upload-url/route.ts, apps/web/src/app/api/sounds/search/route.ts, apps/web/src/app/api/transcribe/route.ts, apps/web/src/app/api/waitlist/export/route.ts
Whitespace/formatting changes; identical logic, control flow, and exports.
Projects page UI tweak
apps/web/src/app/projects/page.tsx
Added inline Search icon inside search field; container made relative; input left padding increased to accommodate icon.
Editor pages/layout
apps/web/src/app/editor/[project_id]/layout.tsx
Whitespace-only adjustments.
Editor components (formatting only)
apps/web/src/components/editor/layout-guide-overlay.tsx, .../media-panel/views/base-view.tsx, .../media-panel/views/sounds.tsx, .../preview-panel.tsx, .../properties-panel/text-properties.tsx, .../media-panel/views/stickers.tsx
Reformatting/spacing; no logic changes. Stickers: multiline prop/style formatting without behavior change.
UI components (formatting only)
apps/web/src/components/export-button.tsx, apps/web/src/components/footer.tsx, apps/web/src/components/icons.tsx, apps/web/src/components/keyboard-shortcuts-help.tsx, apps/web/src/components/language-select.tsx, apps/web/src/components/panel-preset-selector.tsx, apps/web/src/components/ui/editable-timecode.tsx, apps/web/src/components/ui/input-with-back.tsx, apps/web/src/components/ui/input.tsx, apps/web/src/components/ui/tooltip.tsx
Rewrapped JSX/strings, minor class reflows, added EOF newlines; no behavioral changes.
Hooks (formatting only)
apps/web/src/hooks/use-highlight-scroll.ts, apps/web/src/hooks/use-infinite-scroll.ts, apps/web/src/hooks/use-sound-search.ts
Whitespace/formatting edits; logic unchanged.
Providers
apps/web/src/components/providers/global-prefetcher.ts
Formatting only; prefetch logic unchanged.
Libs (formatting only)
apps/web/src/lib/editor-utils.ts, apps/web/src/lib/iconify-api.ts, apps/web/src/lib/schemas/waitlist.ts, apps/web/src/lib/transcription-utils.ts, apps/web/src/lib/zk-encryption.ts, apps/web/src/data/colors/syntax-ui.tsx
Formatting adjustments; APIs and behavior unchanged.
Stores and types (formatting only)
apps/web/src/stores/playback-store.ts, apps/web/src/stores/sounds-store.ts, apps/web/src/types/sounds.ts
Reformatting; same logic and exported types.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • fix: footer icon #552 — Also modifies apps/web/src/components/footer.tsx around the Brand logo Image props, overlapping the same component.
  • feat: stickers panel #539 — Touches apps/web/src/lib/iconify-api.ts (ICONIFY_HOSTS), same file adjusted here for formatting.

Poem

A nibble of code, a nibble of style,
I hop through lines and rest a while.
An icon peeks where searches start,
While commas dance and braces part.
Env file gone—carrot light—
Ship it swift, ears up, code tight! 🥕🐇

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 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 keyboard

The clear action is wired to onMouseDown only. Because there’s no onClick, pressing Enter/Space on the button won’t clear the input, which breaks keyboard and assistive tech activation. Keep onMouseDown to preserve input focus, but add onClick 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: Avoid any for element 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: Replace any for activeProject in FullscreenPreview 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 explicit type. 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", and aria-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 threshold

If isLoading flips to true slightly after onLoadMore() 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 values

If 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 SR

Button 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 minimal onKeyDown. Also, mark icons aria-hidden to avoid double announcement since the button has a proper aria-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 readers

If 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 misuse

Currently 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 logging

Guidelines 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 spoofing

I 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 using export type aliases instead of interface to match repo guidelines.

The coding guidelines encourage “Use export type for types” and “Use import 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, then x-real-ip, then request.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 when hasError 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 variable origin.

-    const origin = request.headers.get("origin");

66-69: Avoid console 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 under process.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 uses console.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: Replace console.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 to next/image with fill and appropriate sizes. 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 with subtle.generateKey instead of manual bytes

Using 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 encode

Building 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 optional

I 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) importing encryptWithRandomKey

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: Remove as any on CSS custom properties

The 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: Prefer for...of over Array.forEach

Guideline asks to favor for...of. This also plays nicer with await 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: Avoid console usage in stores

Our 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 presentational

This inline SVG is purely decorative. Add aria-hidden, focusable="false", and role="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 and audio/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.

📥 Commits

Reviewing files that changed from the base of the PR and between 792fb6c and e4536a2.

⛔ 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 use accessKey attribute on any HTML element.
Don't set aria-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 the scope 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 assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex 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 a title 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.
Assign tabIndex to non-interactive HTML elements with aria-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 a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/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.
Use as const instead of literal types and type annotations.
Use either T[] or Array<T> consistently.
Initialize each enum member value explicitly.
Use export type for types.
Use import 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 the arguments 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 useless this 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

Comment on lines +102 to +110
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,
});

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

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.

Suggested change
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.

Comment on lines +184 to +185
console.error("Freesound API error:", response.status, errorText);
return NextResponse.json(
Copy link
Contributor

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:
    - console.error("Freesound API error:", response.status, errorText);
    + logger.error("Freesound API error", { status: response.status, errorText });
    (And similarly for the other console.error calls at lines 195–198, 247–250, and 259.)
  • Migrate all console.log and other console.* usages across the codebase (e.g. in apps/web/src/stores/editor-store.ts, editor-header.tsx, timeline components, hooks like use-timeline-playhead and use-selection-box, captions view, etc.) to logger.debug or logger.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.

Comment on lines +103 to +112
const modalRequestBody: any = {
filename,
language,
};

// Add encryption parameters if provided (zero-knowledge)
if (decryptionKey && iv) {
modalRequestBody.decryptionKey = decryptionKey;
modalRequestBody.iv = iv;
}
Copy link
Contributor

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.

Suggested change
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));
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +41 to +53
<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)}
>
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

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.

Suggested change
<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.

Comment on lines +54 to +60
<Button
variant="outline"
className="!size-9 rounded-full bg-panel-accent"
>
<ArrowLeft />
</Button>
</motion.div>
Copy link
Contributor

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.

Comment on lines +76 to +81
<Input
placeholder={placeholder}
className="pl-9 bg-panel-accent w-full"
value={value}
onChange={(e) => onChange?.(e.target.value)}
/>
Copy link
Contributor

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.

Suggested change
<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.

Comment on lines +52 to +56
searchParams.set("commercial_only", commercialOnly.toString());
const response = await fetch(
`/api/sounds/search?${searchParams.toString()}`
);

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

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 = [];
Copy link
Contributor

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.

Suggested change
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.

@porterlock112
Copy link

{
"name": "@opencut/auth",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"scripts": {
"build": "tsup src/index.ts --dts --format esm,cjs --out-dir dist --clean",
"dev": "tsup src/index.ts --dts --format esm,cjs --out-dir dist --watch",
"lint": "eslint ."
},
"devDependencies": {
"tsup": "^8.0.2",
"typescript": "^5.5.4",
"eslint": "^9.8.0"
},
"peerDependencies": {
"next": ">=14",
"react": ">=18",
"react-dom": ">=18"
}
}

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.

2 participants