Skip to content

Extract reusable clipboard hook and standardize media queries#1006

Merged
juliusmarminge merged 6 commits intomainfrom
feature/web/clipboard-hook-media-query
Mar 13, 2026
Merged

Extract reusable clipboard hook and standardize media queries#1006
juliusmarminge merged 6 commits intomainfrom
feature/web/clipboard-hook-media-query

Conversation

@juliusmarminge
Copy link
Member

@juliusmarminge juliusmarminge commented Mar 13, 2026

Note

Medium Risk
Replaces clipboard and media-query logic used across UI, so regressions could affect copy interactions and responsive layout behavior despite limited surface-area changes.

Overview
Centralizes clipboard behavior by adding useCopyToClipboard (with timeout-driven isCopied state plus onCopy/onError callbacks) and migrating plan/message copy actions and the thread context-menu “Copy Thread ID” flow to use it.

Standardizes responsive queries by rewriting useMediaQuery to use useSyncExternalStore and support breakpoint-based query strings/objects, then updating the sidebar to use a new useIsMobile() helper. Also includes a tiny className whitespace tweak in _chat.$threadId.tsx and lockfile metadata adjustments.

Written by Cursor Bugbot for commit b28cdbf. This will update automatically on new commits. Configure here.

Note

Extract reusable useCopyToClipboard hook and rewrite useMediaQuery with SSR safety

  • Adds useCopyToClipboard, a shared hook that writes to the clipboard, tracks transient isCopied state, and accepts optional onCopy/onError callbacks with configurable timeout.
  • Replaces ad-hoc clipboard logic in PlanSidebar, Sidebar, and MessageCopyButton with the new hook.
  • Rewrites useMediaQuery using useSyncExternalStore for SSR safety and adds support for semantic breakpoint strings (e.g. 'max-md'); adds a useIsMobile() convenience hook.
  • Behavioral Change: copy actions now only set the success state after a confirmed clipboard write — previously the state toggled even when the Clipboard API was unavailable or the write failed.

Macroscope summarized b28cdbf.

- add `useCopyToClipboard` and adopt it in plan, sidebar, and message copy UI
- extend `useMediaQuery` with breakpoint-aware queries and add `useIsMobile`
- switch chat diff layout and sidebar mobile checks to semantic breakpoint queries
@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7569fb66-5c4f-4586-9b34-5924503c092c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/web/clipboard-hook-media-query
📝 Coding Plan
  • Generate coding plan for human review comments

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

@github-actions github-actions bot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 13, 2026
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Breakpoint mismatch between JS hook and Tailwind CSS
    • Changed BREAKPOINTS.md from 800 to 768 to match Tailwind CSS v4's default md breakpoint (48rem = 768px), eliminating the 768-799px gap where the sidebar trigger was hidden but the sidebar was in mobile sheet mode.
  • ✅ Fixed: Unstable copyToClipboard reference defeats downstream memoization
    • Wrapped copyToClipboard in useCallback with stable refs for onCopy, onError, and timeout, ensuring the returned function identity is stable across renders and downstream useCallback hooks are not needlessly re-created.

Create PR

Or push these changes by commenting:

@cursor push 152076a0b5
Preview (152076a0b5)
diff --git a/apps/web/src/hooks/useCopyToClipboard.ts b/apps/web/src/hooks/useCopyToClipboard.ts
--- a/apps/web/src/hooks/useCopyToClipboard.ts
+++ b/apps/web/src/hooks/useCopyToClipboard.ts
@@ -11,8 +11,15 @@
 } = {}): { copyToClipboard: (value: string, ctx: TContext) => void; isCopied: boolean } {
   const [isCopied, setIsCopied] = React.useState(false);
   const timeoutIdRef = React.useRef<NodeJS.Timeout | null>(null);
+  const onCopyRef = React.useRef(onCopy);
+  const onErrorRef = React.useRef(onError);
+  const timeoutRef = React.useRef(timeout);
 
-  const copyToClipboard = (value: string, ctx: TContext): void => {
+  onCopyRef.current = onCopy;
+  onErrorRef.current = onError;
+  timeoutRef.current = timeout;
+
+  const copyToClipboard = React.useCallback((value: string, ctx: TContext): void => {
     if (typeof window === "undefined" || !navigator.clipboard?.writeText) {
       return;
     }
@@ -26,26 +33,24 @@
         }
         setIsCopied(true);
 
-        if (onCopy) {
-          onCopy(ctx);
-        }
+        onCopyRef.current?.(ctx);
 
-        if (timeout !== 0) {
+        if (timeoutRef.current !== 0) {
           timeoutIdRef.current = setTimeout(() => {
             setIsCopied(false);
             timeoutIdRef.current = null;
-          }, timeout);
+          }, timeoutRef.current);
         }
       },
       (error) => {
-        if (onError) {
-          onError(error, ctx);
+        if (onErrorRef.current) {
+          onErrorRef.current(error, ctx);
         } else {
           console.error(error);
         }
       },
     );
-  };
+  }, []);
 
   // Cleanup timeout on unmount
   React.useEffect(() => {

diff --git a/apps/web/src/hooks/useMediaQuery.ts b/apps/web/src/hooks/useMediaQuery.ts
--- a/apps/web/src/hooks/useMediaQuery.ts
+++ b/apps/web/src/hooks/useMediaQuery.ts
@@ -5,7 +5,7 @@
   "3xl": 1600,
   "4xl": 2000,
   lg: 1024,
-  md: 800,
+  md: 768,
   sm: 640,
   xl: 1280,
 } as const;

@juliusmarminge
Copy link
Member Author

@cursor push 152076a

- Change BREAKPOINTS.md from 800 to 768 to match Tailwind CSS v4's default
  md breakpoint, fixing the 768-799px gap where the sidebar trigger was
  hidden but the sidebar was in mobile sheet mode.

- Wrap copyToClipboard in useCallback with refs for onCopy/onError/timeout
  so the returned function identity is stable across renders, preventing
  unnecessary re-creation of downstream useCallback hooks.

Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>

Applied via @cursor push command
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Clipboard unavailability silently swallowed, onError callback bypassed
    • Added onErrorRef.current?.(new Error("Clipboard API unavailable."), ctx) before the early return so the onError callback is invoked when the Clipboard API is unavailable.

Create PR

Or push these changes by commenting:

@cursor push 6b03ce9bea
Preview (6b03ce9bea)
diff --git a/apps/web/src/hooks/useCopyToClipboard.ts b/apps/web/src/hooks/useCopyToClipboard.ts
--- a/apps/web/src/hooks/useCopyToClipboard.ts
+++ b/apps/web/src/hooks/useCopyToClipboard.ts
@@ -21,6 +21,7 @@
 
   const copyToClipboard = React.useCallback((value: string, ctx: TContext): void => {
     if (typeof window === "undefined" || !navigator.clipboard?.writeText) {
+      onErrorRef.current?.(new Error("Clipboard API unavailable."), ctx);
       return;
     }

cursoragent and others added 2 commits March 13, 2026 03:51
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
@juliusmarminge
Copy link
Member Author

@cursor push 6b03ce9

Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>

Applied via @cursor push command
@juliusmarminge juliusmarminge merged commit 876bbd7 into main Mar 13, 2026
11 checks passed
@juliusmarminge juliusmarminge deleted the feature/web/clipboard-hook-media-query branch March 13, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants