Skip to content

Conversation

@oscartbeaumont
Copy link
Member

@oscartbeaumont oscartbeaumont commented Aug 28, 2025

  • Stop instant recording after 5 minutes on free
  • 5 minute countdown
  • Upgrade banner on the share page
    • Check with the DB if the author of the share link is on free and if so present the banner
  • Document limit on recording flow w/ upgrade to Cap button
  • Prevent studio upload that's more than 5 minutes Already works

Summary by CodeRabbit

  • New Features

    • Enforce 5-minute Instant Mode on free plans: auto-stop, remaining-time UI, and Start label shows “Start 5 min recording”.
    • Added upgrade prompts: tooltips, overlay warnings, Upgrade-to-Pro actions (open Upgrade window).
    • Disable target-mode switching while recording; target controls visually dim when disabled.
    • Desktop: focusing Upgrade window hides target overlay and activates app in Dock when appropriate.
  • Style

    • Share and sidebar/toolbars/cards refreshed with white backgrounds, rounded corners, and updated gray borders; copy link shows inline checkmark feedback.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 28, 2025

Walkthrough

Adds Instant Mode free-limit enforcement (5 minutes) with UI warnings and upgraded plan prompts across desktop UI and overlay. Introduces macOS WindowEvent::Focused handling in Tauri to hide TargetSelectOverlay when Upgrade gains focus and adjust activation policy. Web share pages receive styling updates and an owner-only upgrade banner with inline copy feedback.

Changes

Cohort / File(s) Summary of changes
Desktop: Instant-mode cap & overlay wiring
apps/desktop/src/routes/(window-chrome)/(main).tsx, apps/desktop/src/routes/in-progress-recording.tsx, apps/desktop/src/routes/target-select-overlay.tsx
Enforce 5‑minute Instant‑mode limit for non‑upgraded users (auto‑stop, remaining time display); add contextual Upgrade warnings and Upgrade window triggers; wrap overlay with RecordingOptionsProvider and switch options access to context; add tooltip+messaging around Start recording control.
Desktop: macOS focus handling (Tauri)
apps/desktop/src-tauri/src/lib.rs
Rewrite WindowEvent::Focused handling to parse window label, hide TargetSelectOverlay windows when Upgrade gains focus, and set activation policy to Regular when appropriate.
Desktop: target-mode guarding
apps/desktop/src/routes/(window-chrome)/new-main/index.tsx, apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx
Add createCurrentRecordingQuery() usage; disable target-mode switching while recording by passing disabled prop to TargetTypeButton; TargetTypeButton now accepts optional disabled?: boolean.
Desktop: minor UI disabled-state tweaks
apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx, .../MicrophoneSelect.tsx, .../SystemAudio.tsx
Add disabled:opacity-70 and reorder classNames for consistent disabled appearance; no behavioral changes.
Web: Share page and components styling
apps/web/app/s/[videoId]/Share.tsx, apps/web/app/s/[videoId]/page.tsx, apps/web/app/s/[videoId]/_components/Sidebar.tsx, .../Toolbar.tsx, .../tabs/Activity/CommentInput.tsx, .../tabs/Activity/Comments.tsx
Replace new-card-style with explicit Tailwind classes; adjust backgrounds, borders, radii, spacing, placeholder text/size/color; purely presentational changes.
Web: Share header UX changes
apps/web/app/s/[videoId]/_components/ShareHeader.tsx
Add conditional upgrade banner for non‑Pro owners; dynamic layout spacing; replace toast on copy with ephemeral in-place icon feedback; semantic button tweak.
UI Kit: Button style tweak
packages/ui/src/components/Button.tsx
Change white variant border from border-gray-6 to border-gray-5; no API changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as Recording UI (Instant Mode)
  participant Auth as authStore
  participant Timer as Elapsed Timer
  participant Mut as toggleRecording
  participant Win as Upgrade Window

  User->>UI: Click Start
  UI->>Auth: Check plan (upgraded?)
  alt Not upgraded & Instant
    UI->>Timer: Start tracking (adjustedTime)
    UI-->>User: Show 5‑min tooltip/warnings
    loop Each tick
      Timer->>UI: elapsed time
      UI->>UI: remainingRecordingTime()
      alt Remaining <= 0 and not aborted
        UI->>Mut: Stop recording
        UI->>UI: Set aborted flag
      else Continue
        UI-->>User: Show remaining time
      end
    end
    User-->>Win: Click Upgrade
    Win->>Win: commands.showWindow("Upgrade")
  else Upgraded or non‑Instant
    UI->>Timer: Start/Stop normal
    UI-->>User: Show elapsed time
  end
Loading
sequenceDiagram
  autonumber
  participant OS as macOS
  participant App as Tauri App
  participant W as WebviewWindow(s)
  participant Policy as Activation Policy

  OS-->>App: WindowEvent::Focused(window, focused)
  App->>App: Parse window label -> window_id
  alt focused == true
    alt window_id == Upgrade
      App->>W: Iterate webview windows
      W-->>App: Identify TargetSelectOverlay labeled windows
      App->>W: Hide those overlays
    end
    alt activates_dock(window_id)
      App->>Policy: Set to Regular
    end
  else focused == false
    Note over App: No new behavior
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title ‘Enforce recording limit’ succinctly and clearly highlights the primary change in this pull request, which is implementing and enforcing a recording limit for free users. It avoids unnecessary details or noise and directly relates to the main feature.
Description Check ✅ Passed The pull request description consists of a concise checklist of implemented objectives that directly correspond to the changes in the code, clearly reflecting the enforcement of the free recording limit and related UI updates.

Poem

I tapped my paw—five minutes fly,
A carrot clock beneath the sky.
“Upgrade!” I thump, a gentle nudge,
While windows hide at Focus’ judge.
New borders crisp, the share page gleams—
I hop through flows and time-bound dreams. 🥕✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enforce-recording-limit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@oscartbeaumont oscartbeaumont marked this pull request as ready for review September 8, 2025 15:03
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (17)
apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (1)

216-219: Typo in Omit key: placholder → placeholder

This typo defeats the intent to block overriding CommentInput’s placeholder via commentInputProps.

Apply:

-					"user" | "placholder" | "buttonLabel"
+					"user" | "placeholder" | "buttonLabel"
apps/desktop/src-tauri/src/lib.rs (1)

2220-2239: Gate overlay-hiding to focus gained to avoid unnecessary hides

Currently runs on both focus and blur; gate under focused==true.

Apply:

-                WindowEvent::Focused(focused) => {
-                    let window_id = CapWindowId::from_str(label);
-
-                    if matches!(window_id, Ok(CapWindowId::Upgrade)) {
-                        for (label, window) in app.webview_windows() {
-                            if let Ok(id) = CapWindowId::from_str(&label)
-                                && matches!(id, CapWindowId::TargetSelectOverlay { .. })
-                            {
-                                let _ = window.hide();
-                            }
-                        }
-                    }
-
-                    if *focused {
-                        if let Ok(window_id) = window_id
-                            && window_id.activates_dock()
-                        {
-                            app.set_activation_policy(tauri::ActivationPolicy::Regular)
-                                .ok();
-                        }
-                    }
-                }
+                WindowEvent::Focused(focused) => {
+                    let window_id = CapWindowId::from_str(label);
+                    if *focused {
+                        if matches!(window_id, Ok(CapWindowId::Upgrade)) {
+                            for (label, window) in app.webview_windows() {
+                                if let Ok(id) = CapWindowId::from_str(&label)
+                                    && matches!(id, CapWindowId::TargetSelectOverlay { .. })
+                                {
+                                    let _ = window.hide();
+                                }
+                            }
+                        }
+                        if let Ok(window_id) = window_id
+                            && window_id.activates_dock()
+                        {
+                            app.set_activation_policy(tauri::ActivationPolicy::Regular).ok();
+                        }
+                    }
+                }
apps/web/app/s/[videoId]/Share.tsx (2)

248-259: Simplify absolute child sizing; inset already constrains width/height

inset-3 makes the extra w-[calc(100%-1.5rem)] h-[calc(100%-1.5rem)] redundant. This trims CSS and avoids double-calculation.

-<div className="absolute inset-3 w-[calc(100%-1.5rem)] h-[calc(100%-1.5rem)] overflow-hidden rounded-xl">
+<div className="absolute inset-3 overflow-hidden rounded-xl">

340-375: AI card styling LGTM; also replace the remaining new-card-style in the skeleton for consistency

Nice move to explicit Tailwind classes here. For consistency (and per “Tailwind only” guideline), also update the skeleton wrapper to drop new-card-style.

Apply at Line 308:

-<div className="p-4 animate-pulse new-card-style">
+<div className="p-4 animate-pulse bg-white rounded-2xl border border-gray-3">
apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx (2)

54-63: UI polish looks good; add an aria-label to the textarea

Small a11y boost: add an explicit label for screen readers instead of relying on placeholder.

<textarea
   ref={inputRef}
   value={content}
   disabled={disabled}
   onChange={(e) => setContent(e.target.value)}
   onKeyDown={handleKeyDown}
   placeholder={placeholder || "Leave a comment..."}
+  aria-label={placeholder || "Leave a comment"}
   className="w-full placeholder:text-gray-8 text-sm leading-[22px] text-gray-12 bg-transparent focus:outline-none"
/>

65-71: Disable the submit button when the component itself is disabled

Prevents clickable “Reply” when disabled is true (textarea is disabled, but button is not).

-  disabled={!content}
+  disabled={!content || disabled}
apps/web/app/s/[videoId]/_components/ShareHeader.tsx (2)

172-188: Add ARIA semantics to the sticky banner

Helps announce the upgrade notice to assistive tech.

-<div className="flex sticky flex-col sm:flex-row inset-x-0 top-0 z-10 gap-4 justify-center items-center px-3 py-2 mx-auto w-[calc(100%-20px)] max-w-fit rounded-b-xl border bg-gray-4 border-gray-6">
+<div role="region" aria-label="Upgrade banner" className="flex sticky flex-col sm:flex-row inset-x-0 top-0 z-10 gap-4 justify-center items-center px-3 py-2 mx-auto w-[calc(100%-20px)] max-w-fit rounded-b-xl border bg-gray-4 border-gray-6">

57-58: Clean up copy timeout and add error handling

  • Add useRef to the React import.
  • Store the timeout ID in copyTimeoutRef and clear it on unmount.
  • Refactor the onClick handler to async/await the clipboard API, wrap in try/catch, and clear any existing timeout before setting a new one.
--- apps/web/app/s/[videoId]/_components/ShareHeader.tsx
@@
-import { useEffect, useState } from "react";
+import { useEffect, useState, useRef } from "react";
@@
-const [linkCopied, setLinkCopied] = useState(false);
+const [linkCopied, setLinkCopied] = useState(false);
+const copyTimeoutRef = useRef<number | null>(null);
+
+useEffect(() => {
+  return () => {
+    if (copyTimeoutRef.current) {
+      clearTimeout(copyTimeoutRef.current);
+    }
+  };
+}, []);
@@
-<Button
-  variant="white"
-  onClick={() => {
-    navigator.clipboard.writeText(getVideoLink());
-    setLinkCopied(true);
-    setTimeout(() => {
-      setLinkCopied(false);
-    }, 2000);
-  }}
->
+<Button
+  variant="white"
+  onClick={async () => {
+    try {
+      await navigator.clipboard.writeText(getVideoLink());
+      setLinkCopied(true);
+      if (copyTimeoutRef.current) {
+        clearTimeout(copyTimeoutRef.current);
+      }
+      copyTimeoutRef.current = window.setTimeout(() => {
+        setLinkCopied(false);
+      }, 2000);
+    } catch {
+      toast.error("Failed to copy link. Please try again.");
+    }
+  }}
+>

Applies to lines 14 (import), 57–58 (state), and 243–258 (copy handler).

apps/web/app/s/[videoId]/page.tsx (2)

314-316: Remove unused idx; Effect.map provides a single argument

Effect.map only passes the mapped value; the second param will always be undefined and may trigger noUnusedParameters. Drop idx.

-        Effect.map((data, idx) => (
+        Effect.map((data) => (

710-717: Add rel to external target=_blank link

Prevent reverse tabnabbing by adding rel="noopener noreferrer".

-               <a
+               <a
                  target="_blank"
                  href={`/?ref=video_${video.id}`}
+                 rel="noopener noreferrer"
                  className="flex justify-center items-center px-4 py-2 mx-auto mb-2 space-x-2 bg-white rounded-full border border-gray-5 w-fit"
                >
apps/desktop/src/routes/in-progress-recording.tsx (2)

36-37: Extract limit to a shared constant

Consider moving MAX_RECORDING_FOR_FREE to a shared constants module so the cap stays consistent across routes/components.


162-171: Tighten cutoff and simplify remaining-time clamp

Current logic can overshoot by one tick. Use >= for the cutoff and clamp remaining time with Math.max.

-    if (
-      isMaxRecordingLimitEnabled() &&
-      adjustedTime() > MAX_RECORDING_FOR_FREE &&
-      !aborted
-    ) {
+    if (
+      isMaxRecordingLimitEnabled() &&
+      adjustedTime() >= MAX_RECORDING_FOR_FREE &&
+      !aborted
+    ) {
       aborted = true;
       stopRecording.mutate();
     }
-  const remainingRecordingTime = () => {
-    if (MAX_RECORDING_FOR_FREE < adjustedTime()) return 0;
-    return MAX_RECORDING_FOR_FREE - adjustedTime();
-  };
+  const remainingRecordingTime = () =>
+    Math.max(0, MAX_RECORDING_FOR_FREE - adjustedTime());

Also applies to: 172-183, 184-188, 229-234

apps/desktop/src/routes/target-select-overlay.tsx (2)

44-49: Unify options source in Inner

Inner still uses createOptionsQuery while children use useRecordingOptions. Prefer the context hook in Inner too to avoid duplicate state sources.

-function Inner() {
+function Inner() {
   const [params] = useSearchParams<{ displayId: DisplayId }>();
-  const { rawOptions, setOptions } = createOptionsQuery();
+  const { rawOptions, setOptions } = useRecordingOptions();

838-856: Avoid duplicate auth queries in warning

ShowCapFreeWarning calls authStore.createQuery again. Optionally pass upgraded as a prop from parent (or read from a shared context) to cut redundant queries.

apps/desktop/src/routes/(window-chrome)/(main).tsx (3)

496-501: Add button type and track the Upgrade CTA click

Prevents default submit behavior and gives you a funnel metric for this entry point.

-                <button
-                  class="underline"
-                  onClick={() => commands.showWindow("Upgrade")}
-                >
+                <button
+                  type="button"
+                  class="underline"
+                  onClick={() => {
+                    trackEvent("upgrade_cta_clicked", { source: "instant_limit_tooltip" });
+                    commands.showWindow("Upgrade");
+                  }}
+                >

506-511: Disable tooltip while recording

Avoids showing an upgrade promo when the user is already recording.

-            disabled={
-              !(
-                rawOptions.mode === "instant" &&
-                auth.data?.plan?.upgraded === false
-              )
-            }
+            disabled={
+              !(
+                rawOptions.mode === "instant" &&
+                auth.data?.plan?.upgraded === false
+              ) || isRecording()
+            }

493-504: De-hardcode the 5-min limit and standardize copy

Use a shared constant to keep UI text in sync with enforcement logic and avoid “min/mins” drift.

-            content={
-              <>
-                Instant Mode recordings are limited
-                <br /> to 5 mins,{" "}
+            content={
+              <>
+                Instant Mode recordings are limited
+                <br /> to {MAX_RECORDING_FOR_FREE_MINUTES} min,{" "}
                 <button
                   class="underline"
                   onClick={() => commands.showWindow("Upgrade")}
                 >
                   Upgrade to Pro
                 </button>
               </>
             }
-              {rawOptions.mode === "instant" &&
-              auth.data?.plan?.upgraded === false
-                ? "Start 5 min recording"
-                : "Start recording"}
+              {rawOptions.mode === "instant" &&
+              auth.data?.plan?.upgraded === false
+                ? `Start ${MAX_RECORDING_FOR_FREE_MINUTES} min recording`
+                : "Start recording"}

Add a shared constant (example):

// apps/desktop/src/constants.ts
export const MAX_RECORDING_FOR_FREE_MINUTES = 5 as const;

And import it here:

import { MAX_RECORDING_FOR_FREE_MINUTES } from "~/constants";

If you'd like, I can hoist the existing MAX_RECORDING_FOR_FREE from in-progress-recording.tsx into this constants module and wire both call sites to it.

Also applies to: 543-546

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 196aad9 and 5d98419.

📒 Files selected for processing (12)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/(main).tsx (1 hunks)
  • apps/desktop/src/routes/in-progress-recording.tsx (5 hunks)
  • apps/desktop/src/routes/target-select-overlay.tsx (6 hunks)
  • apps/web/app/s/[videoId]/Share.tsx (2 hunks)
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx (6 hunks)
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx (2 hunks)
  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (1 hunks)
  • apps/web/app/s/[videoId]/page.tsx (3 hunks)
  • packages/ui/src/components/Button.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • packages/ui/src/components/Button.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/desktop/src/routes/in-progress-recording.tsx
  • packages/ui/src/components/Button.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
  • apps/desktop/src/routes/(window-chrome)/(main).tsx
apps/desktop/src-tauri/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Use tauri_specta on the Rust side for IPC (typed commands/events) and emit events via generated types

Files:

  • apps/desktop/src-tauri/src/lib.rs
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/**/*.{ts,tsx}: In the desktop app, rely on unplugin-icons auto-imports; do not manually import icon modules
Use generated tauri_specta commands/events (commands, events) in the desktop frontend; listen to generated events directly
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/desktop/src/routes/(window-chrome)/(main).tsx
{apps/desktop,packages/ui-solid}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Component naming (Solid): components in PascalCase; hooks/utilities in camelCase starting with 'use' where applicable

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/desktop/src/routes/(window-chrome)/(main).tsx
⏰ 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). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (14)
apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (1)

231-231: Styling palette alignment — LGTM

Border/background updates match the new gray scale across the share view.

packages/ui/src/components/Button.tsx (1)

24-24: White variant border color tweak — LGTM

Switch to border-gray-5 is consistent with the broader card/border refresh.

apps/web/app/s/[videoId]/_components/Toolbar.tsx (1)

192-192: Toolbar border and separator color refresh — LGTM

Matches the new card + divider palette; no behavior changes.

Also applies to: 275-275

apps/web/app/s/[videoId]/_components/Sidebar.tsx (2)

158-158: Card styling update — LGTM

bg-white + rounded-2xl + border-gray-5 aligns with the new container style.

Also applies to: 160-160


161-170: Ignore corrupted/garbled characters comment – Sidebar.tsx contains no non-ASCII or garbled content.

Likely an incorrect or invalid review comment.

apps/web/app/s/[videoId]/_components/ShareHeader.tsx (2)

11-11: Imports LGTM

Icon additions are correct and used below.


262-262: Explicit button type is good

Prevents accidental form submissions when placed inside forms.

apps/web/app/s/[videoId]/page.tsx (1)

678-709: Layout/padding tweak looks good

Container padding change aligns with the new share-page styling.

apps/desktop/src/routes/in-progress-recording.tsx (1)

162-171: Confirm intended behavior while auth is loading

Until auth.data resolves, the limit isn’t enforced. That’s okay if intentional; otherwise treat “unknown” as not-upgraded.

apps/desktop/src/routes/target-select-overlay.tsx (5)

34-37: Good: centralizing options via context

RecordingOptionsProvider/useRecordingOptions introduction is sound.


176-181: Upgrade nudge placement looks right

Cap-free warning is injected in all relevant flows (display, window, area).

Also applies to: 246-249, 681-684


327-329: Delta change is fine; clamping covers negatives

Dropping Math.max on drag deltas is safe given downstream clamping in setBounds.


705-706: Correct: useRecordingOptions in RecordingControls

Switching RecordingControls to the options context keeps state consistent.


846-851: Verify ‘Upgrade’ window registration
Ensure the literal “Upgrade” passed to commands.showWindow("Upgrade") in apps/desktop/src/routes/target-select-overlay.tsx (lines 846–851) exactly matches the id used when registering or creating that window in the main process.

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

Caution

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

⚠️ Outside diff range comments (5)
apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (1)

216-219: Fix typo in Omit key: 'placholder' → 'placeholder'.

Type-level bug currently doesn’t omit the prop.

- componentProps?: Omit<
-   ComponentProps<typeof CommentInput>,
-   "user" | "placholder" | "buttonLabel"
- >;
+ componentProps?: Omit<
+   ComponentProps<typeof CommentInput>,
+   "user" | "placeholder" | "buttonLabel"
+ >;
apps/web/app/s/[videoId]/_components/Sidebar.tsx (1)

157-170: Corrupted/gibberish content in render; this will break build.

There’s binary garbage after the button className block; likely a paste/encoding issue. This must be removed.

Proposed restoration for the mapped tab button:

-               className={classNames(
-                 "flex-1 px-5 py-3 text-sm font-medium relative transition-colors duration-200",
-                 "hover:bg-gray-1",
-                 activeTab === tab.id ? "bg-gray-3" : "",
-               )}
-             >	8#s�...
+               className={classNames(
+                 "flex-1 px-5 py-3 text-sm font-medium relative transition-colors duration-200",
+                 "hover:bg-gray-1",
+                 activeTab === tab.id ? "bg-gray-3" : "",
+               )}
+             >
+               {tab.label}
+             </button>

Please ensure the closing for the tabs container remains balanced after this fix.

apps/desktop/src/routes/in-progress-recording.tsx (1)

64-75: Bug: aborted never resets after restart; subsequent recordings won’t re-stop at 5 min

After hitting the limit once, aborted stays true across restarts within the same view; the effect will no-op and not enforce the limit again.

Apply:

   } else if (payload.variant === "Started") {
+    aborted = false;
     setState({ variant: "recording" });
     setStart(Date.now());
   }
   await commands.restartRecording();
 
+  aborted = false;
   setState({ variant: "recording" });
   setTime(Date.now());

Also applies to: 122-136, 172-183

apps/web/app/s/[videoId]/page.tsx (1)

698-705: Add rel="noopener noreferrer" for target="_blank" link

Prevents reverse tabnabbing and isolates the opener context.

-        <a
-          target="_blank"
-          href={`/?ref=video_${video.id}`}
+        <a
+          target="_blank"
+          rel="noopener noreferrer"
+          href={`/?ref=video_${video.id}`}
apps/desktop/src/routes/target-select-overlay.tsx (1)

26-26: Use useRecordingOptions instead of direct createOptionsQuery
In apps/desktop/src/routes/target-select-overlay.tsx (around line 53), remove the stale import and replace the hook call:

- import { createOptionsQuery } from "~/utils/queries";
+ // remove this import

- const { rawOptions, setOptions } = createOptionsQuery();
+ const { rawOptions, setOptions } = useRecordingOptions();

This ensures the component shares the context-backed state and avoids desync.

♻️ Duplicate comments (3)
apps/desktop/src/routes/in-progress-recording.tsx (1)

16-16: Unify entitlement gating to licenseQuery and default to conservative while loading

Elsewhere (main.tsx) you already have createLicenseQuery; here you’re mixing auth.plan.upgraded. This risks inconsistent UX (e.g., commercial users limited) and bypass while auth loads.

Apply:

-import { authStore } from "~/store";
+// no auth gating here; use licenseQuery for a single source of truth
-import {
-  createCurrentRecordingQuery,
-  createOptionsQuery,
-} from "~/utils/queries";
+import {
+  createCurrentRecordingQuery,
+  createOptionsQuery,
+  createLicenseQuery,
+} from "~/utils/queries";
-const auth = authStore.createQuery();
+const license = createLicenseQuery();
-const isMaxRecordingLimitEnabled = () => {
-  // Only enforce the limit on instant mode.
-  // We enforce it on studio mode when exporting.
-  return (
-    optionsQuery.rawOptions.mode === "instant" &&
-    // If the data is loaded and the user is not upgraded
-    auth.data?.plan?.upgraded === false
-  );
-};
+const isMaxRecordingLimitEnabled = () => {
+  // Only enforce the limit on instant mode.
+  // Studio mode enforcement happens on export.
+  if (optionsQuery.rawOptions.mode !== "instant") return false;
+  // Be conservative while loading to avoid bypass.
+  if (license.isLoading) return true;
+  return !["pro", "commercial"].includes(license.data?.type ?? "personal");
+};

Also applies to: 52-53, 162-171, 229-234

apps/desktop/src/routes/(window-chrome)/(main).tsx (1)

490-512: Use licenseQuery for Instant-mode 5‑min gating (avoid auth.plan.upgraded)

Keep entitlement checks consistent and include a conservative loading default so the tooltip/label behavior matches enforcement.

-            disabled={
-              !(
-                rawOptions.mode === "instant" &&
-                auth.data?.plan?.upgraded === false
-              )
-            }
+            disabled={
+              !(
+                rawOptions.mode === "instant" &&
+                (license.isLoading ? true : license.data?.type === "personal")
+              )
+            }
-              {rawOptions.mode === "instant" &&
-              auth.data?.plan?.upgraded === false
+              {rawOptions.mode === "instant" &&
+              (license.isLoading ? true : license.data?.type === "personal")
                 ? "Start 5 min recording"
                 : "Start recording"}

Also applies to: 543-546

apps/web/app/s/[videoId]/_components/ShareHeader.tsx (1)

63-64: Normalize owner check and reuse computed flags (prevents banner not rendering).

Unify on the already-normalized owner check and the computed isUserPro. Also coerce isOwner to a real boolean. This addresses the prior review about ID type mismatch and avoids a second userIsPro() call.

-const isOwner = user && user.id.toString() === data.ownerId;
+const isOwner = Boolean(user && user.id.toString() === data.ownerId);

 const isUserPro = userIsPro(user);
-const showUpgradeBanner =
-  user && data.ownerId === user.id && !userIsPro(user);
+const showUpgradeBanner = Boolean(isOwner && !isUserPro);

Also applies to: 129-131

🧹 Nitpick comments (11)
packages/ui/src/components/Button.tsx (1)

69-74: Remove any casts in polymorphic Button.

Current casts hide type safety when href/asChild are used.

Apply a minimal typing improvement:

- const Comp = href ? "a" : asChild ? Slot : ("button" as any);
+ const Comp: React.ElementType = href ? "a" : asChild ? Slot : "button";
- ref={ref as any}
+ ref={ref}

If stricter polymorphic typing is desired, I can follow up with a typed Polymorphic version.

apps/web/app/s/[videoId]/_components/Toolbar.tsx (2)

236-239: Avoid double getTimestamp() computation.

Minor nit: compute once to prevent duplicate reads.

- {videoElement && getTimestamp() > 0
-   ? `Comment at ${getTimestamp().toFixed(2)}`
+ {videoElement && (() => { const ts = getTimestamp(); return ts > 0
+   ? `Comment at ${ts.toFixed(2)}`
    : "Comment"}

33-46: Prefer MutationObserver over polling for element availability.

Polling every 100ms is okay, but an observer is cleaner and avoids timers.

I can provide a small MutationObserver snippet if you want to switch.

apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx (1)

65-71: Disable submit based on trimmed content.

Currently whitespace-only content enables the button but is rejected on submit.

- disabled={!content}
+ disabled={!content.trim()}

If desired, mirror this check in handleSubmit to keep logic aligned.

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

2220-2240: Upgrade focus behavior: good; consider restoring overlays on blur.

Hiding TargetSelectOverlay when Upgrade gains focus is correct. If the Upgrade window stays open and loses focus (without being destroyed), overlays remain hidden; consider showing them back on blur.

 #[cfg(target_os = "macos")]
 WindowEvent::Focused(focused) => {
   let window_id = CapWindowId::from_str(label);

   if matches!(window_id, Ok(CapWindowId::Upgrade)) {
     for (label, window) in app.webview_windows() {
       if let Ok(id) = CapWindowId::from_str(&label)
         && matches!(id, CapWindowId::TargetSelectOverlay { .. })
       {
-        let _ = window.hide();
+        if *focused {
+          let _ = window.hide();
+        } else {
+          let _ = window.show();
+        }
       }
     }
   }

   if *focused {
     if let Ok(window_id) = window_id
       && window_id.activates_dock()
     {
       app.set_activation_policy(tauri::ActivationPolicy::Regular).ok();
     }
   }
 }

Alternatively, rely solely on Destroyed to reshow if that’s the intended UX; in that case, ignore this.

apps/desktop/src/routes/in-progress-recording.tsx (1)

174-181: Tighten boundary checks to stop exactly at 5:00 and keep UI consistent

Use >= for the cutoff and <= for the remaining-time zeroing.

-    adjustedTime() > MAX_RECORDING_FOR_FREE &&
+    adjustedTime() >= MAX_RECORDING_FOR_FREE &&
-  if (MAX_RECORDING_FOR_FREE < adjustedTime()) return 0;
+  if (adjustedTime() >= MAX_RECORDING_FOR_FREE) return 0;

Also applies to: 185-187

apps/web/app/s/[videoId]/_components/ShareHeader.tsx (2)

245-257: Avoid dangling timers when unmounting (minor leak) and handle copy failure.

Store/clear the timeout on unmount and surface clipboard write errors.

-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
-const [linkCopied, setLinkCopied] = useState(false);
+const [linkCopied, setLinkCopied] = useState(false);
+const copyTimerRef = useRef<number | null>(null);
+
+useEffect(() => {
+  return () => {
+    if (copyTimerRef.current) window.clearTimeout(copyTimerRef.current);
+  };
+}, []);
-  navigator.clipboard.writeText(getVideoLink());
-  setLinkCopied(true);
-  setTimeout(() => {
-    setLinkCopied(false);
-  }, 2000);
+  navigator.clipboard.writeText(getVideoLink())
+    .then(() => {
+      setLinkCopied(true);
+      if (copyTimerRef.current) window.clearTimeout(copyTimerRef.current);
+      copyTimerRef.current = window.setTimeout(() => {
+        setLinkCopied(false);
+      }, 2000);
+    })
+    .catch(() => toast.error("Failed to copy link"));

Also applies to: 14-14, 57-58


254-257: Prefer Tailwind for animations; drop custom css class.

Replace the custom svgpathanimation with a Tailwind animation to align with styling guidelines.

-  <Check className="ml-2 w-4 h-4 svgpathanimation" />
+  <Check className="ml-2 h-4 w-4 motion-safe:animate-pulse" />
apps/desktop/src/routes/target-select-overlay.tsx (3)

43-49: Name the default-exported component (PascalCase) per guidelines.

Improves devtools traces and follows component naming rules.

-export default function () {
+export default function TargetSelectOverlay() {
   return (
     <RecordingOptionsProvider>
       <Inner />
     </RecordingOptionsProvider>
   );
 }

180-181: Let the warning read from context; remove prop drilling.

Have ShowCapFreeWarning derive isInstantMode from useRecordingOptions(), and simplify callsites.

-<ShowCapFreeWarning isInstantMode={rawOptions.mode === "instant"} />
+<ShowCapFreeWarning />
-<ShowCapFreeWarning
-  isInstantMode={rawOptions.mode === "instant"}
-/>

+<ShowCapFreeWarning />
-<ShowCapFreeWarning
-  isInstantMode={rawOptions.mode === "instant"}
-/>

+<ShowCapFreeWarning />
-function ShowCapFreeWarning(props: { isInstantMode: boolean }) {
-  const auth = authStore.createQuery();
-
-  return (
-    <Suspense>
-      <Show when={props.isInstantMode && auth.data?.plan?.upgraded === false}>
-        <p class="text-sm text-center max-w-64">
-          Instant Mode recordings are limited to 5 mins,{" "}
-          <button
-            class="underline"
-            onClick={() => commands.showWindow("Upgrade")}
-          >
-            Upgrade to Pro
-          </button>
-        </p>
-      </Show>
-    </Suspense>
-  );
-}
+function ShowCapFreeWarning() {
+  const auth = authStore.createQuery();
+  const { rawOptions } = useRecordingOptions();
+  return (
+    <Suspense>
+      <Show when={rawOptions.mode === "instant" && auth.data?.plan?.upgraded === false}>
+        <p class="max-w-64 text-center text-sm">
+          Instant Mode recordings are limited to 5 minutes,{" "}
+          <button class="underline" onClick={() => commands.showWindow("Upgrade")}>
+            Upgrade to Pro
+          </button>
+        </p>
+      </Show>
+    </Suspense>
+  );
+}

Also applies to: 246-248, 681-683, 838-856


845-852: Copy nit: “5 minutes” reads better than “5 mins”.

-Instant Mode recordings are limited to 5 mins,
+Instant Mode recordings are limited to 5 minutes,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 737c7ee and ceec472.

📒 Files selected for processing (12)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/(main).tsx (1 hunks)
  • apps/desktop/src/routes/in-progress-recording.tsx (5 hunks)
  • apps/desktop/src/routes/target-select-overlay.tsx (6 hunks)
  • apps/web/app/s/[videoId]/Share.tsx (2 hunks)
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx (6 hunks)
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx (2 hunks)
  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx (1 hunks)
  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (1 hunks)
  • apps/web/app/s/[videoId]/page.tsx (3 hunks)
  • packages/ui/src/components/Button.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • packages/ui/src/components/Button.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx
  • apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx
  • apps/web/app/s/[videoId]/_components/Toolbar.tsx
  • apps/web/app/s/[videoId]/page.tsx
  • apps/desktop/src/routes/in-progress-recording.tsx
  • packages/ui/src/components/Button.tsx
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/desktop/src/routes/(window-chrome)/(main).tsx
  • apps/web/app/s/[videoId]/_components/Sidebar.tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
  • apps/web/app/s/[videoId]/_components/ShareHeader.tsx
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/**/*.{ts,tsx}: In the desktop app, rely on unplugin-icons auto-imports; do not manually import icon modules
Use generated tauri_specta commands/events (commands, events) in the desktop frontend; listen to generated events directly
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
  • apps/desktop/src/routes/(window-chrome)/(main).tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
{apps/desktop,packages/ui-solid}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Component naming (Solid): components in PascalCase; hooks/utilities in camelCase starting with 'use' where applicable

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
  • apps/desktop/src/routes/(window-chrome)/(main).tsx
  • apps/desktop/src/routes/target-select-overlay.tsx
apps/desktop/src-tauri/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Use tauri_specta on the Rust side for IPC (typed commands/events) and emit events via generated types

Files:

  • apps/desktop/src-tauri/src/lib.rs
⏰ 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). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (12)
packages/ui/src/components/Button.tsx (1)

24-24: Border color tweak looks good.

Aligns with the new gray scale; no API changes.

apps/web/app/s/[videoId]/_components/Toolbar.tsx (2)

192-193: Toolbar container border update is consistent.

The added border/border-gray-5 matches the new card pattern.


275-275: Separator color update is consistent.

bg-gray-5 aligns with the updated palette.

apps/web/app/s/[videoId]/_components/tabs/Activity/CommentInput.tsx (2)

54-55: Border color change LGTM.

Matches the rest of the PR’s border-gray-5 updates.


61-63: Keep placeholder copy consistent; styling looks good.

The “...” and placeholder color update are fine.

apps/web/app/s/[videoId]/_components/tabs/Activity/Comments.tsx (1)

231-248: Bottom bar palette tweak looks good.

border-gray-5 + bg-gray-2 is consistent with the card updates.

apps/web/app/s/[videoId]/_components/Sidebar.tsx (1)

157-161: Card container/border updates are consistent.

bg-white + rounded-2xl + border-gray-5 aligns with the new design language.

apps/web/app/s/[videoId]/Share.tsx (3)

245-256: Video container card styling LGTM.

The move to a bordered white card with internal inset preserves layout and simplifies spacing.


337-371: AI summary/chapters card styling LGTM.

White card + border-gray-3 reads well against bg-gray-2 pages.


6-15: Versions align across monorepo
All React-using packages (apps/web, packages/database, ui, utils) pin React ^19.1.0, and apps/web is on @tanstack/react-query ^5.75.4—no mismatches detected.

apps/desktop/src/routes/in-progress-recording.tsx (1)

36-37: Good: single constant for free limit

Centralizing the 5-minute threshold is clean and makes future changes easy.

apps/web/app/s/[videoId]/page.tsx (1)

308-308: Style updates look consistent with the new token palette

Container/bg/border changes align with the broader Share page refresh.

Also applies to: 666-666, 701-701

@ameer2468 ameer2468 merged commit 3b0eb5b into main Sep 9, 2025
14 of 15 checks passed
@ameer2468 ameer2468 deleted the enforce-recording-limit branch September 9, 2025 11:06
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: 0

🧹 Nitpick comments (3)
apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx (1)

16-16: Fix typo in className: curosr-default -> cursor-default

The misspelling prevents the intended cursor style.

Apply this diff:

-      class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors curosr-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
+      class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx (1)

9-10: Don’t forward non-DOM props; add aria-disabled

selected, Component, name, and disabled are being spread onto a div. Destructure and spread the rest; also expose aria-disabled for accessibility.

Apply this diff:

-function TargetTypeButton(
-  props: {
-    selected: boolean;
-    Component: Component<ComponentProps<"svg">>;
-    name: string;
-    disabled?: boolean;
-  } & ComponentProps<"div">,
-) {
+function TargetTypeButton({
+  selected,
+  Component,
+  name,
+  disabled,
+  ...rest
+}: {
+  selected: boolean;
+  Component: Component<ComponentProps<"svg">>;
+  name: string;
+  disabled?: boolean;
+} & ComponentProps<"div">) {
   return (
     <div
-      {...props}
+      {...rest}
+      aria-disabled={disabled}
       class={cx(
         "flex-1 text-center hover:bg-gray-4 bg-gray-3 flex flex-col ring-offset-gray-1 ring-offset-2 items-center justify-end gap-2 py-1.5 rounded-lg transition-all",
-        props.selected
+        selected
           ? "bg-gray-3 text-white ring-blue-9 ring-1"
           : "ring-transparent ring-0",
-        props.disabled ? "opacity-70 pointer-events-none" : "",
+        disabled ? "opacity-70 pointer-events-none" : "",
       )}
     >
-      <props.Component
+      <Component
         class={cx(
           "size-6 transition-colors",
-          props.selected ? "text-gray-12" : "text-gray-9",
+          selected ? "text-gray-12" : "text-gray-9",
         )}
       />
-      <p class="text-xs text-gray-12">{props.name}</p>
+      <p class="text-xs text-gray-12">{name}</p>
     </div>
   );
 }

Also applies to: 14-21, 23-30

apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (1)

395-403: DRY the “recording guard” for onClick

You repeat the early-return check in three handlers. Wrap once and reuse to reduce duplication.

Apply this diff to each onClick:

- onClick={() => {
-   //if recording early return
-   if (isRecording()) return;
-   setOptions("targetMode", (v) =>
-     v === "display" ? null : "display",
-   );
- }}
+ onClick={withRecordingGuard(() => {
+   setOptions("targetMode", (v) => (v === "display" ? null : "display"));
+ })}
- onClick={() => {
-   if (isRecording()) return;
-   setOptions("targetMode", (v) => (v === "window" ? null : "window"));
- }}
+ onClick={withRecordingGuard(() => {
+   setOptions("targetMode", (v) => (v === "window" ? null : "window"));
+ })}
- onClick={() => {
-   if (isRecording()) return;
-   setOptions("targetMode", (v) => (v === "area" ? null : "area"));
- }}
+ onClick={withRecordingGuard(() => {
+   setOptions("targetMode", (v) => (v === "area" ? null : "area"));
+ })}

Add this helper near isRecording():

const withRecordingGuard = (fn: () => void) => () => {
  if (isRecording()) return;
  fn();
};

Also applies to: 408-413, 418-423

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ceec472 and 5c2c966.

📒 Files selected for processing (5)
  • apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx (2 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/**/*.{ts,tsx}: In the desktop app, rely on unplugin-icons auto-imports; do not manually import icon modules
Use generated tauri_specta commands/events (commands, events) in the desktop frontend; listen to generated events directly
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx
{apps/desktop,packages/ui-solid}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Component naming (Solid): components in PascalCase; hooks/utilities in camelCase starting with 'use' where applicable

Files:

  • apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

**/*.{ts,tsx}: Use Biome to format/lint TypeScript with a 2-space indent
TypeScript file names should be kebab-case (e.g., user-menu.tsx)

Files:

  • apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

React/Solid components should be named using PascalCase

Files:

  • apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
  • apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx
🧬 Code graph analysis (1)
apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (1)
apps/desktop/src/utils/queries.ts (1)
  • createCurrentRecordingQuery (120-126)
⏰ 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). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (6)
apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx (1)

16-16: Consistent disabled styling added

Adding disabled:opacity-70 aligns this control with Camera/Microphone selectors. Looks good.

apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx (1)

69-69: Consistent disabled visual state

disabled:opacity-70 matches the rest of the UI and clearly communicates non-interactivity.

apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx (1)

41-41: Consistent disabled visual state

The added disabled:opacity-70 keeps styling uniform across selectors. Good change.

apps/desktop/src/routes/(window-chrome)/new-main/TargetTypeButton.tsx (1)

20-21: Good disabled UX guard

Opacity + pointer-events-none prevents accidental interactions while recording.

apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (2)

33-34: Good: use shared createCurrentRecordingQuery()

Centralizing recording state via the shared query keeps concerns consistent across components.


116-118: Simple, reactive isRecording() helper

Clear and idiomatic for Solid Query state access.

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.

Implement usage limits in desktop app (+ web app)

3 participants