Skip to content

Conversation

@oscartbeaumont
Copy link
Contributor

@oscartbeaumont oscartbeaumont commented Oct 23, 2025

Summary by CodeRabbit

  • New Features
    • Recording controls now support multiple placements (above, below, right, left, inside-bottom) and choose the best position based on selection size and screen space.
    • Controls adapt width per placement and refresh positioning on mount and window resize.
    • Optional control background/frame with adjusted button styling and dynamic classes.
    • Helper hints are hidden when controls are inside-bottom or placement is inappropriate.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 23, 2025

Walkthrough

Replaces top/bottom toggle with a memoized controlsPlacement, adds control width/height measurement on mount/resize, implements full-height (≥90%) placement flow (try rightleftinside-bottom) with placement-specific classes/width rules, and adds/propagates showBackground?: boolean to RecordingControls.

Changes

Cohort / File(s) Change Summary
Target select overlay / Recording controls
apps/desktop/src/routes/target-select-overlay.tsx
Added control-dimension tracking (controlsWidth, controlsHeight) with mount/resize measurement; introduced memoized controlsPlacement that prefers right, then left, then inside-bottom when selection height ≥90% of window, otherwise falls back to above/below logic; mapped placements to CSS classes and width rules (auto for right/left/inside-bottom, fixed otherwise); hid helper hints for inside-bottom/inapplicable placements; replaced wrapper div with a button and applied classList-driven styling; added optional showBackground?: boolean prop to RecordingControls and passed it through to control rendering to toggle background/frame and button styles.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant O as TargetSelectOverlay
    participant B as Browser / DOM
    participant P as controlsPlacement (memo)

    U->>O: open selection / resize
    O->>B: measure controls on mount/resize
    B-->>O: controlsWidth, controlsHeight
    O->>P: compute placement (selection height, window space, control dims)
    alt selection height >= 90% (full-height)
        P-->>O: try -> right
        alt right not feasible
            P-->>O: try -> left
            alt left not feasible
                P-->>O: choose -> inside-bottom
            end
        end
    else normal height
        P-->>O: choose -> above or below based on overflow
    end
    O->>B: apply placement classes, width rules, and showBackground flag
    B-->>U: controls rendered with chosen placement
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I measured widths with nimble paws,

I checked the window, weighed every clause.
Right then left, or tucked inside-bottom,
A cozy frame where buttons blossom.
Hop—controls land, all tidy and calm.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Area select fix button positions" directly and clearly relates to the main change in the changeset. The summary shows the PR primarily focuses on improving the placement logic of recording control buttons in the target-select-overlay component, introducing multiple placement options (above, below, right, left, inside-bottom), responsive positioning logic, and related UI refinements. The title is concise, specific, and avoids vague or generic terms, making it easy for a teammate scanning the history to understand that this PR addresses button positioning in the area select feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch area-select-fix-buttons-pos

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 October 23, 2025 13:58
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 (4)
apps/desktop/src/routes/target-select-overlay.tsx (4)

277-308: Document magic numbers and verify fallback dimensions.

The placement logic uses undocumented magic numbers:

  • 300 (line 285): Fallback width for controls
  • 0.9 (line 289): Full-height threshold
  • 64 (line 284): Fallback height for controls

These fallback dimensions may cause incorrect initial placement before onMount sets actual dimensions, potentially causing a visual flash.

Consider:

  1. Extract these as named constants with explanatory comments
  2. Verify the 300px fallback matches typical control width
  3. Add JSDoc comments explaining the placement algorithm

Apply this diff:

+// Fallback dimensions for controls before they mount
+const CONTROLS_FALLBACK_HEIGHT = 64;
+const CONTROLS_FALLBACK_WIDTH = 300;
+// Consider selection "full-height" if it spans 90% or more of window height
+const FULL_HEIGHT_THRESHOLD = 0.9;
+
 // Determine the best placement for controls
 const controlsPlacement = createMemo(() => {
   const top = bounds.position.y;
   const left = bounds.position.x;
   const width = bounds.size.width;
   const height = bounds.size.height;
-  // Measure controls dimensions (fallback if not yet mounted)
-  const ctrlH = controlsHeight() ?? 64;
-  const ctrlW = controlsWidth() ?? 300;
+  const ctrlH = controlsHeight() ?? CONTROLS_FALLBACK_HEIGHT;
+  const ctrlW = controlsWidth() ?? CONTROLS_FALLBACK_WIDTH;
   const margin = 16;

-  // Check if selection spans full height (or nearly full height)
-  const isFullHeight = height >= window.innerHeight * 0.9;
+  const isFullHeight = height >= window.innerHeight * FULL_HEIGHT_THRESHOLD;

314-317: Consider debouncing the resize handler.

The resize event can fire very frequently during window resizing. Reading offsetHeight and offsetWidth synchronously on every resize event may cause performance issues due to layout thrashing.

Consider wrapping the dimension updates in requestAnimationFrame:

 createEventListener(window, "resize", () => {
-  setControlsHeight(controlsEl?.offsetHeight);
-  setControlsWidth(controlsEl?.offsetWidth);
+  requestAnimationFrame(() => {
+    setControlsHeight(controlsEl?.offsetHeight);
+    setControlsWidth(controlsEl?.offsetWidth);
+  });
 });

754-774: Extract controlsPlacement() to avoid repeated calls.

controlsPlacement() is called multiple times in the JSX (lines 757-764 for classes, 768-773 for width). While memoized, extracting it once improves readability and reduces function call overhead.

Apply this diff:

 <div
   ref={controlsEl}
+  use:((el) => {
+    const placement = controlsPlacement();
+    return (
       <div
-        ref={controlsEl}
         class={cx(
           "flex absolute flex-col items-center m-2",
-          controlsPlacement().position === "above" && "bottom-full",
-          controlsPlacement().position === "below" && "top-full",
-          controlsPlacement().position === "right" &&
+          placement.position === "above" && "bottom-full",
+          placement.position === "below" && "top-full",
+          placement.position === "right" &&
             "left-full top-1/2 -translate-y-1/2",
-          controlsPlacement().position === "left" &&
+          placement.position === "left" &&
             "right-full top-1/2 -translate-y-1/2",
-          controlsPlacement().position === "inside-bottom" &&
+          placement.position === "inside-bottom" &&
             "bottom-2 left-1/2 -translate-x-1/2",
         )}
         style={{
           width:
-            controlsPlacement().position === "right" ||
-            controlsPlacement().position === "left"
+            placement.position === "right" ||
+            placement.position === "left"
               ? "auto"
-              : controlsPlacement().position === "inside-bottom"
+              : placement.position === "inside-bottom"
                 ? "auto"
                 : `${bounds.size.width}px`,
         }}
       >
+    );
+  })()}

Alternative simpler approach: Destructure at the start of the JSX block:

+{(() => {
+  const placement = controlsPlacement();
+  return (
     <div
       ref={controlsEl}
       class={cx(
         "flex absolute flex-col items-center m-2",
-        controlsPlacement().position === "above" && "bottom-full",
+        placement.position === "above" && "bottom-full",
         // ... rest of the conditions using placement.position
       )}
+  );
+})()}

766-774: Simplify the width calculation logic.

The nested ternary for width can be simplified. Currently, it checks right || leftauto, then inside-bottomauto, else → width. Since all three positions use "auto", this can be consolidated.

 style={{
   width:
-    controlsPlacement().position === "right" ||
-    controlsPlacement().position === "left"
-      ? "auto"
-      : controlsPlacement().position === "inside-bottom"
-        ? "auto"
-        : `${bounds.size.width}px`,
+    ["right", "left", "inside-bottom"].includes(controlsPlacement().position)
+      ? "auto"
+      : `${bounds.size.width}px`,
 }}

Or if you extract placement as suggested in the previous comment:

 style={{
   width:
-    placement.position === "right" ||
-    placement.position === "left"
-      ? "auto"
-      : placement.position === "inside-bottom"
-        ? "auto"
-        : `${bounds.size.width}px`,
+    ["right", "left", "inside-bottom"].includes(placement.position)
+      ? "auto"
+      : `${bounds.size.width}px`,
 }}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 750b7e0 and 1cfc514.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/target-select-overlay.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/desktop/src/routes/target-select-overlay.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/routes/target-select-overlay.tsx
apps/desktop/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/src/**/*.{ts,tsx}: Desktop icons are auto-imported (unplugin-icons); do not import icons manually
Desktop IPC: Call generated tauri_specta commands/events; listen to generated events and use typed interfaces
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/target-select-overlay.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 (1)
apps/desktop/src/routes/target-select-overlay.tsx (1)

273-275: LGTM!

The controlsWidth signal follows the same pattern as controlsHeight and is correctly initialized.

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

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

310-317: Consider debouncing the resize handler.

The resize event can fire very frequently during window resizing, causing excessive recalculations of control dimensions and placement. This could impact performance during resize operations.

Consider using a debounced resize handler:

+import { debounce } from "@solid-primitives/scheduled";
+
 onMount(() => {
   setControlsHeight(controlsEl?.offsetHeight);
   setControlsWidth(controlsEl?.offsetWidth);
 });
-createEventListener(window, "resize", () => {
+const updateDimensions = debounce(() => {
   setControlsHeight(controlsEl?.offsetHeight);
   setControlsWidth(controlsEl?.offsetWidth);
-});
+}, 100);
+createEventListener(window, "resize", updateDimensions);

766-774: Consider extracting width calculation for readability.

The nested ternary for width calculation is correct but difficult to parse at a glance.

Consider extracting the width logic:

+const controlsWidth = createMemo(() => {
+  const pos = controlsPlacement().position;
+  if (pos === "right" || pos === "left" || pos === "inside-bottom") {
+    return "auto";
+  }
+  return `${bounds.size.width}px`;
+});
+
 <div
   ref={controlsEl}
   class={cx(
     "flex absolute flex-col items-center m-2",
     controlsPlacement().position === "above" && "bottom-full",
     controlsPlacement().position === "below" && "top-full",
     controlsPlacement().position === "right" &&
       "left-full top-1/2 -translate-y-1/2",
     controlsPlacement().position === "left" &&
       "right-full top-1/2 -translate-y-1/2",
     controlsPlacement().position === "inside-bottom" &&
       "bottom-2 left-1/2 -translate-x-1/2",
   )}
-  style={{
-    width:
-      controlsPlacement().position === "right" ||
-      controlsPlacement().position === "left"
-        ? "auto"
-        : controlsPlacement().position === "inside-bottom"
-          ? "auto"
-          : `${bounds.size.width}px`,
-  }}
+  style={{ width: controlsWidth() }}
 >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cfc514 and 1684481.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/target-select-overlay.tsx (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/desktop/src/routes/target-select-overlay.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/routes/target-select-overlay.tsx
apps/desktop/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/src/**/*.{ts,tsx}: Desktop icons are auto-imported (unplugin-icons); do not import icons manually
Desktop IPC: Call generated tauri_specta commands/events; listen to generated events and use typed interfaces
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/target-select-overlay.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). (4)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
apps/desktop/src/routes/target-select-overlay.tsx (4)

273-275: LGTM!

The controlsWidth state follows the same pattern as controlsHeight and is properly initialized.


782-784: LGTM!

Correctly passes showBackground when controls are positioned inside-bottom, ensuring the controls remain visible against the selection area.


814-818: LGTM!

Good UX improvement—hiding the helper text when controls are positioned inside-bottom prevents visual clutter and overlap.


278-308: Verify fallback dimensions during initial render phase.

The width handling reveals that the horizontal overflow concern is not applicable: for "above" and "below" positions, the controls container width is explicitly set to match the selection width (line 774), so no overflow occurs.

However, the fallback dimensions concern remains valid:

Fallback dimensions (line 285): The ctrlW = 300px fallback is used to determine placement before controls are measured. If the actual RecordingControls component has a different width (e.g., 250px or 400px), the initial placement decision could be suboptimal. The placement recalculates after onMount with the correct measured dimensions, but the initial render may show incorrect positioning.

For right/left positions (where width is "auto"), the concern is less critical since width adapts to content. For above/below positions, the width matches the selection anyway, so fallback accuracy matters less there. The main risk is if the controls are significantly wider than 300px and need right/left placement—the initial decision could be wrong.

@oscartbeaumont oscartbeaumont marked this pull request as draft October 23, 2025 14:28
@oscartbeaumont oscartbeaumont marked this pull request as ready for review October 23, 2025 14:28
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

♻️ Duplicate comments (1)
apps/desktop/src/routes/target-select-overlay.tsx (1)

999-1013: Fix conflicting hover opacity styles.

Lines 1003 and 1005 both apply hover opacity utilities (hover:opacity-80 and hover:opacity-60) when showBackground is true, creating conflicting styles.

Apply this diff to use a single hover opacity:

 <button
   onClick={() => props.setToggleModeSelect?.(true)}
   class="cursor-pointer flex gap-1 items-center mb-5 transition-all duration-200"
   classList={{
-    "bg-black/40 p-2 rounded-lg backdrop-blur-sm border border-white/10 hover:bg-black/50 hover:opacity-80":
+    "bg-black/40 p-2 rounded-lg backdrop-blur-sm border border-white/10 hover:bg-black/50":
       props.showBackground,
-    "hover:opacity-60": props.showBackground,
   }}
 >
🧹 Nitpick comments (1)
apps/desktop/src/routes/target-select-overlay.tsx (1)

767-774: Simplify nested ternary for better readability.

The width calculation uses nested ternaries that can be flattened since both conditions return "auto".

Apply this diff:

 style={{
   width:
     controlsPlacement().position === "right" ||
-    controlsPlacement().position === "left"
+    controlsPlacement().position === "left" ||
+    controlsPlacement().position === "inside-bottom"
       ? "auto"
-      : controlsPlacement().position === "inside-bottom"
-        ? "auto"
-        : `${bounds.size.width}px`,
+      : `${bounds.size.width}px`,
 }}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1684481 and f979f2d.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/target-select-overlay.tsx (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/desktop/src/routes/target-select-overlay.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/routes/target-select-overlay.tsx
apps/desktop/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/src/**/*.{ts,tsx}: Desktop icons are auto-imported (unplugin-icons); do not import icons manually
Desktop IPC: Call generated tauri_specta commands/events; listen to generated events and use typed interfaces
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/target-select-overlay.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). (4)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Typecheck
  • GitHub Check: Analyze (rust)
🔇 Additional comments (5)
apps/desktop/src/routes/target-select-overlay.tsx (5)

278-308: LGTM! Well-structured placement logic.

The placement computation correctly handles both full-height and standard cases, with appropriate fallbacks and margin calculations. The 90% threshold for full-height detection is reasonable.


310-317: LGTM! Dimension tracking is correctly implemented.

The onMount and resize handlers properly measure control dimensions, with fallback values in the memo handling cases where controls aren't yet rendered.


782-786: LGTM! Appropriate use of background styling.

The showBackground prop is correctly set to true only when controls are placed inside the selection, ensuring proper contrast.


815-820: LGTM! Hint text visibility aligns with placement.

Hiding the hint when controls are inside the selection avoids visual clutter and improves UX.


833-833: LGTM! Prop addition is well-typed.

The optional showBackground prop is correctly typed and appropriately optional for flexible component usage.

@richiemcilroy richiemcilroy merged commit 16ff461 into main Oct 28, 2025
15 checks passed
@oscartbeaumont oscartbeaumont deleted the area-select-fix-buttons-pos branch October 28, 2025 04:43
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.

3 participants