Skip to content

Text copy#1580

Merged
simo6529 merged 24 commits intomainfrom
text-copy
Oct 29, 2025
Merged

Text copy#1580
simo6529 merged 24 commits intomainfrom
text-copy

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Oct 29, 2025

Summary by CodeRabbit

  • New Features

    • Clipboard support for WaveDrops notes: plain and Markdown export, range/selection copy, modifier-based Markdown toggle, and toast confirmations.
  • Bug Fixes / Improvements

    • Drop elements now include stable identifiers in the DOM to improve copy/selection accuracy.
    • Highlighting wrapper receives stable IDs to aid accurate interactions.
  • Documentation

    • Updated internal release metadata date for TKT-0010.
  • Tests

    • Adjusted a timezone helper in tests to preserve existing behavior.

Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 29, 2025

Walkthrough

Adds data-wave-drop-id and data-serial-no DOM attributes, implements a new client hook useWaveDropsClipboard to build plain/Markdown clipboard payloads (embeds, attachments, quotes, selection ranges), wires the hook into the WaveDrops renderer, updates DropsList/HighlightDropWrapper to pass IDs, and adjusts a timezone test helper.

Changes

Cohort / File(s) Summary
Ticket / Docs
codex/STATE.md, codex/tickets/TKT-0010.md
Updated ticket metadata last-updated date and documented the new WaveDrops clipboard feature.
Wave drop DOM attrs
components/waves/drops/WaveDrop.tsx, components/drops/view/HighlightDropWrapper.tsx
Added data-wave-drop-id to drop container elements; WaveDrop also adds data-serial-no. HighlightDropWrapper gains optional waveDropId prop and maps it to data-wave-drop-id.
Clipboard hook (new)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
New client-side hook that inspects selection/range, builds per-drop ClipboardMessage objects (plain + markdown), extracts embeds/attachments/quotes/timestamps, supports range-based selection, listens for copy events and modifier keys (Shift → markdown), and writes to the Clipboard API.
Hook integration
components/waves/drops/wave-drops-all/index.tsx
Introduced containerRef, computed dropsForClipboard with useMemo, wired useWaveDropsClipboard({ containerRef, drops }), and attached ref to wrapper div.
Drops rendering
components/drops/view/DropsList.tsx
Computes and passes waveDropId (stableHash
Tests
__tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
Replaced imported helper with a local mintEndInstantUtcForMintDay using wallTimeToUtcInstantInZone to compute end-of-mint instant in tests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as WaveDrops UI
    participant Hook as useWaveDropsClipboard
    participant DOM as Document/Elements
    participant Clipboard as Clipboard API

    User->>UI: Select messages (click/drag or keyboard)
    UI->>Hook: containerRef active & drops supplied
    Hook->>DOM: attach copy listener / inspect selection on copy
    DOM-->>Hook: copy event (Ctrl/Cmd+C) with modifiers

    Note over Hook,DOM: Map DOM nodes -> data-wave-drop-id -> drop objects
    Hook->>Hook: Build per-drop payloads (plain + markdown), resolve embeds/quotes, format timestamps
    Hook->>Clipboard: write text/plain and optionally text/markdown
    Clipboard->>User: clipboard populated

    alt Fallback path
        Hook->>Clipboard: navigator.clipboard.writeText() fallback
        Clipboard->>User: fallback success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Attention points:
    • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts: selection-to-drop mapping, embed/attachment parsing, modifier-key logic, async Clipboard API usage, event listener lifecycle.
    • Integration in components/waves/drops/wave-drops-all/index.tsx: ref lifecycle and dropsForClipboard memoization correctness.
    • HighlightDropWrapper prop addition: ensure consumers (styles, analytics, tests) handle new data attribute.

Possibly related PRs

Suggested reviewers

  • ragnep

Poem

🐰
I hopped through drops both near and wide,
Collected quotes and embeds with pride.
Shift for Markdown, Ctrl for a paste,
Clipboard snug — no bytes to waste!
— ~CodeRabbit

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
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.
Title Check ❓ Inconclusive The pull request title is "Text copy," which is extremely generic and vague. While the main changeset implements a comprehensive clipboard handling feature for WaveDrops notes—including a new useWaveDropsClipboard hook, custom copy formatting, Markdown export with modifiers, and supporting data attributes—the title provides almost no meaningful information about what the changeset actually accomplishes. The phrase "Text copy" uses non-descriptive terms similar to "misc updates" that fail to convey the primary purpose of the changes, making it difficult for teammates scanning the history to understand the real scope and intent of this pull request. Consider revising the title to be more descriptive and specific about the main change. A clearer title might be "Add clipboard handling with custom copy formatting for WaveDrops" or "Implement WaveDrops clipboard feature with Markdown export" to help developers quickly understand the pull request's purpose and impact when reviewing history.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 text-copy

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c29a90e and 44ac648.

📒 Files selected for processing (1)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
🧬 Code graph analysis (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)
generated/models/ApiDropMetadata.ts (1)
  • ApiDropMetadata (15-41)
helpers/waves/drop.helpers.ts (1)
  • ExtendedDrop (16-20)
⏰ 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: Analyze (javascript-typescript)
🔇 Additional comments (9)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (9)

1-30: LGTM! Clean type definitions and proper use of readonly modifiers.

The imports, type definitions, and interfaces are well-structured. All interface properties correctly use the readonly modifier as per coding guidelines, and TypeScript is used appropriately throughout.


31-141: LGTM! Solid DOM utility functions with proper type guards and safe character handling.

The helper functions demonstrate good defensive programming:

  • Type guards are correctly implemented
  • The character-iteration approach in escapeAttributeValue (lines 74-89) properly addresses the control character issue from past reviews
  • Range API usage is correct for selection handling
  • The nodeIsEditable function comprehensively checks for editable contexts including the custom data-wave-clipboard-allow-default attribute

143-227: LGTM! Well-implemented text transformation with proper handling of edge cases.

The markdown link parser correctly handles nested parentheses and image syntax, and the toPlainText function uses targeted regex patterns (addressing past review feedback) that safely strip markdown formatting without over-removing legitimate characters.


229-518: LGTM! Clean embed extraction and formatting logic with immutable patterns.

The embed extraction logic properly groups metadata entries and builds formatted output. The use of readonly types and immutable updates (spread operators) demonstrates good TypeScript practices.


332-473: LGTM! Comprehensive reference handling with proper fallbacks.

The quote/reference system correctly handles multiple scenarios (deleted drops, missing data, normal references) and properly formats blockquote-style output with appropriate metadata (author, wave name, content).


520-753: LGTM! Robust message building with proper formatting for both plain and markdown outputs.

The message construction logic comprehensively handles all drop components (replies, quotes, content, embeds, attachments) and correctly formats timestamps with locale awareness. The differentiation between single and multiple message formatting improves readability.


755-877: LGTM! Careful selection handling with proper boundary checks and defensive programming.

The selection context resolution properly validates that the selection is within the container, excludes editable areas, and uses defensive error handling around intersectsNode. The message sorting ensures consistent ordering.


879-1052: LGTM! Sophisticated range handling correctly manages both full and partial selections.

The range payload building logic properly handles complex scenarios including partial selections at boundaries, same-drop selections, and mixed full/partial selections. The use of loose equality (== null) at lines 940-943 is intentional for checking both null and undefined.


1054-1207: LGTM! Well-architected hook with proper memoization and event handling.

The main hook demonstrates excellent React practices:

  • Strengthened type guard (lines 1062-1066) ensures runtime safety by validating non-empty stableKey and stableHash (addressing past feedback)
  • Appropriate use of useMemo for expensive computations reduces unnecessary recalculation
  • useRef for format tracking avoids re-renders
  • Event listeners are properly scoped (keydown on global, copy on container) and cleaned up
  • The empty catch at line 1193 is acceptable since the event clipboard fallback has already executed

The hook correctly handles both plain and markdown formats based on the Shift key modifier.


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.

Copy link
Copy Markdown

@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 (6)
components/waves/drops/wave-drops-all/index.tsx (1)

3-3: Clipboard wiring looks solid; minor perf/type nits.

  • Consider avoiding a freshly allocated [] in the memo to reduce needless re-runs:
    const dropsForClipboard = waveMessages?.drops ?? EMPTY_DROPS; with EMPTY_DROPS as a module constant.
  • Narrow the ref type in options to HTMLDivElement to match usage and catch mismatches earlier.
-const containerRef = useRef<HTMLDivElement | null>(null);
+const containerRef = useRef<HTMLDivElement | null>(null);

-const dropsForClipboard = useMemo(
-  () => waveMessages?.drops ?? [],
-  [waveMessages?.drops]
-);
+const EMPTY_DROPS: readonly ExtendedDrop[] = [];
+const dropsForClipboard = useMemo(
+  () => waveMessages?.drops ?? EMPTY_DROPS,
+  [waveMessages?.drops]
+);

Also applies to: 18-18, 55-55, 72-76, 77-81, 148-151

components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (5)

590-596: Also write text/markdown when exporting Markdown.

In Markdown mode, set both text/plain and text/markdown to maximize compatibility.

-      if (event.clipboardData) {
-        event.clipboardData.setData("text/plain", payload);
-      }
+      if (event.clipboardData) {
+        event.clipboardData.setData("text/plain", payload);
+        if (formatRef.current === "markdown") {
+          event.clipboardData.setData("text/markdown", payload);
+        }
+      }

72-79: Harden CSS escaping fallback.

The fallback only escapes quotes/backslashes; IDs with ] or control chars could still break attribute selectors. Prefer a small local escape that covers CSS specials or ship a tiny CSS.escape polyfill.

-const escapeAttributeValue = (value: string): string => {
+const escapeAttributeValue = (value: string): string => {
   if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
     return CSS.escape(value);
   }
-  return value.replace(/["\\]/g, "\\$&");
+  return value.replace(/[\0-\x1F\x7F"\\\[\]]/g, "\\$&");
 };

34-70: Simplify editable-node detection.

Minor redundancy and an unnecessary ternary; tighten without behavior change.

-const nodeIsEditable = (node: Node | null): boolean => {
+const nodeIsEditable = (node: Node | null): boolean => {
   if (!node) {
     return false;
   }
   if (isHTMLElement(node)) {
     if (node.isContentEditable) {
       return true;
     }
     if (
       node instanceof HTMLInputElement ||
       node instanceof HTMLTextAreaElement
     ) {
       return true;
     }
     if (node.dataset?.waveClipboardAllowDefault === "true") {
       return true;
     }
   }
-  const parent = isHTMLElement(node) ? node.parentElement : node.parentElement;
+  const parent = (node as Node).parentElement;
   if (!parent) {
     return false;
   }
   if (parent.closest('[contenteditable="true"]')) {
     return true;
   }
   if (parent instanceof HTMLInputElement || parent instanceof HTMLTextAreaElement) {
     return true;
   }
   return (
     parent.closest('[data-wave-clipboard-allow-default="true"]') !== null
   );
 };

264-274: Improve Markdown for attachments (optional).

In Markdown mode, consider formatting attachments as links to distinguish them from plain text.

-    attachmentMarkdownLines: uniqueAttachments,
+    attachmentMarkdownLines: uniqueAttachments.map((u) => `[attachment](${u})`),

598-599: Remove inline comment to match “no comments in TS/TSX” guideline.

Delete the in-catch comment.

-          .catch(() => {
-            // Silently ignore clipboard promise failures – the event clipboard fallback already ran.
-          });
+          .catch(() => {});

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22b8e2a and 8cb9170.

📒 Files selected for processing (5)
  • codex/STATE.md (1 hunks)
  • codex/tickets/TKT-0010.md (1 hunks)
  • components/waves/drops/WaveDrop.tsx (1 hunks)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
  • components/waves/drops/wave-drops-all/index.tsx (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/drops/WaveDrop.tsx
  • components/waves/drops/wave-drops-all/index.tsx
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/waves/drops/WaveDrop.tsx
  • components/waves/drops/wave-drops-all/index.tsx
🧬 Code graph analysis (2)
components/waves/drops/wave-drops-all/index.tsx (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1)
  • useWaveDropsClipboard (398-613)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)
generated/models/ApiDropMetadata.ts (1)
  • ApiDropMetadata (15-41)
helpers/waves/drop.helpers.ts (1)
  • ExtendedDrop (16-20)
⏰ 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: Analyze (javascript-typescript)
🔇 Additional comments (3)
codex/STATE.md (1)

16-16: LGTM — metadata date updated correctly.

components/waves/drops/WaveDrop.tsx (1)

325-327: Attributes align with clipboard selector usage.

The data attributes match the hook’s query logic (dataset.waveDropId). No issues.

codex/tickets/TKT-0010.md (1)

45-45: Log entry reads clear and auditable.

Entry captures scope and verification status. No changes needed.

Comment thread components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts Outdated
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1)

132-142: Fix Markdown-to-plain conversion; current bold regex is wrong.

The pattern uses the captured text as the closing delimiter, so bold markers aren't stripped reliably; the generic character cull then over-removes legitimate characters like hyphens and underscores.

🧹 Nitpick comments (2)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)

54-54: Simplify redundant ternary.

Both branches of the ternary return node.parentElement, making the conditional check unnecessary.

Apply this diff:

-  const parent = isHTMLElement(node) ? node.parentElement : node.parentElement;
+  const parent = node.parentElement;

177-177: Remove unnecessary type assertion.

TypeScript can infer the type from the EmbedInfo interface definition.

Apply this diff:

-    const group = groups.get(groupKey) ?? { extras: [] as string[] };
+    const group = groups.get(groupKey) ?? { extras: [] };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cb9170 and 980bd70.

📒 Files selected for processing (2)
  • codex/STATE.md (1 hunks)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • codex/STATE.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
🧬 Code graph analysis (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)
generated/models/ApiDropMetadata.ts (1)
  • ApiDropMetadata (15-41)
helpers/waves/drop.helpers.ts (1)
  • ExtendedDrop (16-20)
⏰ 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: Analyze (javascript-typescript)

Comment thread components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts Outdated
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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

♻️ Duplicate comments (2)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)

607-609: Remove comment to comply with coding guidelines.

The inline comment violates the coding guideline that specifies no comments in the code.

As per coding guidelines

Apply this diff:

       if (navigator?.clipboard?.writeText) {
         void navigator.clipboard
           .writeText(payload)
-          .catch(() => {
-            // Silently ignore clipboard promise failures – the event clipboard fallback already ran.
-          });
+          .catch(() => {});
       }

132-144: Fix the broken bold/strong regex on line 138.

The regex /(?:\*\*|__)(.*?)\1/g is incorrect. The \1 backreferences the first capture group (.*?) (the content), not the delimiter. This means it tries to match **content followed by the same content repeated (e.g., **hello**hello**), which will never match standard markdown bold syntax.

Apply this diff to fix the bold pattern:

 const toPlainText = (markdown: string): string =>
   markdown
     .replace(/```([\s\S]*?)```/g, (_, code) => code.trim())
     .replace(/`([^`]+)`/g, "$1")
     .replace(/!\[.*?]\((.*?)\)/g, (_, url: string) => url)
     .replace(/\[(.*?)]\((.*?)\)/g, "$1 ($2)")
-    .replace(/(?:\*\*|__)(.*?)\1/g, "$1")
+    .replace(/(\*\*|__)(.*?)\1/g, "$2")
     .replace(/(^|[^\w])\*([^*\s][^*]*?)\*(?=$|[^\w])/g, (_, prefix: string, content: string) => `${prefix}${content}`)
     .replace(/(^|[^\w])_([^_\s][^_]*?)_(?=$|[^\w])/g, (_, prefix: string, content: string) => `${prefix}${content}`)
     .replace(/~~([^~]+)~~/g, "$1")
     .replace(/^\s{0,3}>\s?/gm, "")
     .replace(/\n{3,}/g, "\n\n")
     .trim();

Note: A past review comment marked this as addressed, but the fix is not present in the current code.

🧹 Nitpick comments (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1)

105-105: Remove deprecated detach() calls.

Range.detach() has been deprecated and is now a no-op in modern browsers. The optional chaining indicates awareness it might not exist, but these calls are unnecessary and can be removed.

Apply this diff:

   const coversStart = range.compareBoundaryPoints(Range.START_TO_START, elementRange) <= 0;
   const coversEnd = range.compareBoundaryPoints(Range.END_TO_END, elementRange) >= 0;
 
-  elementRange.detach?.();
-
   return coversStart && coversEnd;
   const text = clipped.toString();
 
-  clipped.detach?.();
-  elementRange.detach?.();
-
   return text;

Also applies to: 127-128

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 980bd70 and 2b92e59.

📒 Files selected for processing (2)
  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts (1 hunks)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
**/__tests__/**

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in __tests__ directories when organizing test suites

Files:

  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
🧬 Code graph analysis (2)
__tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts (1)
components/meme-calendar/meme-calendar.helpers.tsx (1)
  • wallTimeToUtcInstantInZone (103-135)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)
generated/models/ApiDropMetadata.ts (1)
  • ApiDropMetadata (15-41)
helpers/waves/drop.helpers.ts (1)
  • ExtendedDrop (16-20)
⏰ 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: Analyze (javascript-typescript)
🔇 Additional comments (2)
__tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts (1)

18-27: The original review comment is incorrect. The function is internal to the module and cannot be imported.

Verification shows that mintEndInstantUtcForMintDay exists in components/meme-calendar/meme-calendar.helpers.tsx at line 176, but it is not exported—it's an internal helper function. The test file correctly defines a local copy because the actual implementation is not part of the module's public API and therefore cannot be imported.

This is not code duplication or a test isolation issue—it's the expected pattern when working with internal module helpers in tests. The local implementation in the test allows the test suite to function independently without exposing internal details.

No changes are needed.

Likely an incorrect or invalid review comment.

components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1)

427-444: Verify the safety of the formatRef pattern for keyboard-to-clipboard coordination.

Using useRef to coordinate state between keydown and copy events works, but creates a potential race condition if:

  • Multiple key events occur before copy
  • Copy event is delayed or queued
  • User presses Cmd+Shift+C then quickly presses Cmd+C

The last keydown always wins, which may not reflect the actual key combo that triggered the copy.

Consider whether this edge case matters for your use case. If the timing window is acceptable, the current approach is fine. Otherwise, you might need to:

  1. Store the format in the event itself (if possible)
  2. Read modifier keys directly from the copy event
  3. Use a timestamp-based approach to match keydown to copy events

Comment thread components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts Outdated
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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 (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1)

445-457: Fix inconsistent indentation.

Lines 448-457 have inconsistent indentation compared to lines 446-447. All properties in the return object should align.

Apply this diff:

   return {
     id: drop.stableHash ?? drop.id,
     author: drop.author?.handle ?? "Unknown",
-   timestamp: drop.created_at,
-   markdownContent,
-   plainContent,
+    timestamp: drop.created_at,
+    markdownContent,
+    plainContent,
     embedPlainLines,
     embedMarkdownLines,
     attachmentPlainLines: uniqueAttachments,
     attachmentMarkdownLines: uniqueAttachments.map(
       (url) => `[attachment](${url})`
     ),
   };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b92e59 and 393c63b.

📒 Files selected for processing (5)
  • components/drops/view/DropsList.tsx (1 hunks)
  • components/drops/view/HighlightDropWrapper.tsx (3 hunks)
  • components/waves/drops/WaveDrop.tsx (1 hunks)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
  • components/waves/drops/wave-drops-all/index.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/waves/drops/WaveDrop.tsx
  • components/waves/drops/wave-drops-all/index.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
  • components/drops/view/DropsList.tsx
  • components/drops/view/HighlightDropWrapper.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/drops/view/DropsList.tsx
  • components/drops/view/HighlightDropWrapper.tsx
🧬 Code graph analysis (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)
generated/models/ApiDropMetadata.ts (1)
  • ApiDropMetadata (15-41)
helpers/waves/drop.helpers.ts (1)
  • ExtendedDrop (16-20)
🪛 Biome (2.1.2)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts

[error] 79-79: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

⏰ 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: Analyze (javascript-typescript)
🔇 Additional comments (7)
components/drops/view/HighlightDropWrapper.tsx (1)

24-24: LGTM! Clean integration of waveDropId prop.

The optional waveDropId prop is properly typed with readonly, correctly destructured, and applied as a data attribute. The implementation follows React conventions where undefined values don't render attributes.

Also applies to: 43-43, 247-253

components/drops/view/DropsList.tsx (1)

127-131: LGTM! Correct fallback chain for wave drop IDs.

The logic correctly computes waveDropId only for FULL drops using a sensible fallback chain (stableHash ?? drop.id ?? drop.stableKey), matching the pattern used in buildClipboardMessage (line 446 of useWaveDropsClipboard.ts).

components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (5)

74-80: Static analysis warning is a false positive.

The control character range \0-\x1F in the regex is intentional for escaping CSS selector special characters when CSS.escape is unavailable. The implementation correctly falls back to manual escaping for older environments.

However, given modern browser support, consider simplifying to use CSS.escape exclusively if the target environment guarantees support, or document the fallback necessity.


158-224: LGTM! Robust metadata parsing with proper fallbacks.

The extractEmbeds function handles hierarchical metadata keys correctly using separators, and the quote source registry pattern ensures referenced drops are tracked and merged properly.


460-577: LGTM! Clean formatting with proper locale support.

The formatting functions correctly handle both plain and markdown formats, use Intl.DateTimeFormat for locale-aware timestamps, and adjust heading style based on message count for better readability.


579-868: LGTM! Comprehensive selection handling with partial-range support.

The selection logic correctly handles complex scenarios: detects editable contexts to avoid interference, safely escapes selectors, uses Range API for boundary detection, and extracts partial text when drops are only partially selected.


870-1019: LGTM! Well-structured hook with proper React patterns.

The hook correctly:

  • Memoizes expensive computations (drop filtering, quote lookup, message building)
  • Tracks mutable format state with useRef
  • Handles keyboard (Shift for markdown) and copy events
  • Uses both event.clipboardData and navigator.clipboard for broad compatibility
  • Properly cleans up event listeners on unmount

Copy link
Copy Markdown

@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 (3)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (3)

984-1073: Re-run effect when the container node changes; avoid stale listeners.

The effect depends on containerRef (stable object), so if containerRef.current swaps (remount), listeners won’t re-bind. Depend on the actual node.

-  }, [clipboardMessages, containerRef]);
+  }, [clipboardMessages, containerRef.current]);

To verify potential leaks across remounts, search for places where the container is unmounted/remounted and confirm listeners are reattached.


728-736: Don’t bail on unknown ids; skip them to make selection handling resilient.

Returning null if any selected id isn’t in clipboardMessages disables copy needlessly (e.g., mixed content). Skip missing ids and proceed if at least one remains.

   const messages: ClipboardMessage[] = [];
   for (const id of selectedIds) {
     const message = clipboardMessages.get(id);
-    if (!message) {
-      return null;
-    }
-    messages.push(message);
+    if (message) {
+      messages.push(message);
+    }
   }
 
+  if (messages.length === 0) {
+    return null;
+  }

134-147: toPlainText improvements are solid; consider ordered lists too.

Current replacements look good. Optionally also strip ordered list prefixes like “1. ” or “1) ”.

   markdown
     .replaceAll(/```([\s\S]*?)```/g, (_, code) => code.trim())
     .replaceAll(/`([^`]+)`/g, "$1")
     .replaceAll(/!\[[^\]]*]\(([^)]+)\)/g, "$1")
     .replaceAll(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)")
     .replaceAll(/(\*\*|__)(.*?)\1/g, "$2")
     .replaceAll(/([*_])(.*?)\1/g, "$2")
     .replaceAll(/~~(.*?)~~/g, "$1")
     .replaceAll(/(^|\n)#{1,6}\s+/g, "$1")
     .replaceAll(/(^|\n)\s*[-*+]\s+/g, "$1")
+    .replaceAll(/(^|\n)\s*\d+[.)]\s+/g, "$1")
     .replaceAll(/(^|\n)>\s?/g, "$1")
     .replaceAll(/\n{3,}/g, "\n\n")
     .trim();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 393c63b and 3a96a2a.

📒 Files selected for processing (2)
  • codex/STATE.md (1 hunks)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • codex/STATE.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
🪛 Biome (2.1.2)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts

[error] 79-79: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 79-79: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

Copy link
Copy Markdown

@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 (2)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)

81-82: Consider uppercase hex literals for consistency.

While the lowercase hex notation (0x1f, 0x7f) works correctly, uppercase (0x1F, 0x7F) is more conventional in JavaScript/TypeScript codebases and improves readability.

Apply this diff:

     const code = ch.codePointAt(0)!;
-    if (code <= 0x1f || code === 0x7f || ch === "\"" || ch === "\\" || ch === "[" || ch === "]") {
+    if (code <= 0x1F || code === 0x7F || ch === "\"" || ch === "\\" || ch === "[" || ch === "]") {
       out += "\\" + ch;

943-948: Consider adding readonly to properties for consistency.

While not strictly required since this is an internal type, adding readonly to the properties of BuildSegmentsOptions would improve immutability guarantees and align with the pattern used in other types like UseWaveDropsClipboardOptions.

Apply this diff:

 type BuildSegmentsOptions = {
-  selectedIds: string[];
-  partialSegments: Map<string, string>;
-  messagesById: Map<string, ClipboardMessage>;
-  format: ClipboardFormat;
+  readonly selectedIds: string[];
+  readonly partialSegments: Map<string, string>;
+  readonly messagesById: Map<string, ClipboardMessage>;
+  readonly format: ClipboardFormat;
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b080aa and b61642b.

📒 Files selected for processing (1)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
🧬 Code graph analysis (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)
generated/models/ApiDropMetadata.ts (1)
  • ApiDropMetadata (15-41)
helpers/waves/drop.helpers.ts (1)
  • ExtendedDrop (16-20)
⏰ 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: Analyze (javascript-typescript)
🔇 Additional comments (10)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (10)

1-29: LGTM: Type definitions follow best practices.

The type definitions are well-structured with appropriate readonly modifiers on interface properties, and the imports are organized correctly.


31-72: LGTM: Node checking logic is thorough.

The utility functions correctly identify editable nodes and handle various edge cases including contentEditable elements, form inputs, and custom data attributes.


91-141: LGTM: Element finding and range utilities are well-implemented.

The DOM traversal and range manipulation logic correctly handles element lookups, range coverage checks, and text extraction from selections.


143-157: LGTM: Markdown-to-plain-text conversion is well-structured.

The function correctly strips various Markdown constructs in the proper order, with appropriate use of backreferences to match paired delimiters. The fix from previous reviews addressed the earlier regex issues.


159-260: LGTM: Embed extraction logic is well-designed.

The metadata parsing and grouping logic correctly handles various embed patterns using immutable data structures. The fallback mechanisms for title, URL, and description fields are appropriate.


262-403: LGTM: Quote and reference handling is comprehensive.

The code correctly merges quote drop sources, maintains a registry, and formats references with appropriate fallbacks for deleted or incomplete data. The blockquote formatting properly handles multi-line content.


405-563: LGTM: Message building logic is robust.

The clipboard message construction correctly assembles content from multiple sources (replies, quotes, parts, metadata, media) with appropriate handling of different drop types and graceful fallbacks for missing data.


566-683: LGTM: Formatting utilities handle edge cases well.

The timestamp formatting includes proper error handling, and the message formatting logic correctly differentiates between single and multiple message contexts, with appropriate format-specific styling.


685-942: LGTM: Selection and range handling is sophisticated and correct.

The code properly handles complex selection scenarios including:

  • Filtering editable elements
  • Detecting range intersections with error handling
  • Sorting messages chronologically
  • Managing partial selections at boundaries
  • Clipping selection ranges to element bounds

The logic correctly distinguishes between fully-selected and partially-selected drops.


984-1134: LGTM: Hook implementation is well-architected.

The main hook correctly:

  • Uses useMemo to optimize expensive computations (drop filtering, lookup maps, message building)
  • Manages format state with a ref to avoid re-renders
  • Properly coordinates keydown and copy events for format selection
  • Handles partial and full selections
  • Supports both plain and markdown output
  • Includes proper event listener cleanup

The clipboard API usage with fallback handling is appropriate, and the dependencies array is correct.

Copy link
Copy Markdown

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b61642b and c29a90e.

📒 Files selected for processing (1)
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts
🧬 Code graph analysis (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (2)
generated/models/ApiDropMetadata.ts (1)
  • ApiDropMetadata (15-41)
helpers/waves/drop.helpers.ts (1)
  • ExtendedDrop (16-20)
⏰ 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: Analyze (javascript-typescript)
🔇 Additional comments (8)
components/waves/drops/wave-drops-all/hooks/useWaveDropsClipboard.ts (8)

1-29: LGTM!

The imports and type definitions are well-structured. All properties correctly use readonly as per coding guidelines.


31-141: LGTM!

The utility functions are well-implemented with appropriate defensive checks. Past review issues (redundant ternary, control character regex) have been properly addressed.


143-157: LGTM!

The markdown-to-plain text conversion has been properly fixed with correct regex patterns for bold, italic, and other markdown elements. The past review issue has been addressed.


159-260: LGTM!

The embed extraction logic is well-structured with appropriate null handling and immutable updates.


262-403: LGTM!

The quote and reference handling logic is well-organized with clear separation of concerns and proper null handling.


405-667: LGTM!

The message building logic is comprehensive and handles edge cases well with appropriate fallbacks. The timestamp formatting includes proper error handling.


669-982: LGTM!

The selection handling logic is robust with proper error handling and well-organized helper functions for complex range operations.


1045-1134: LGTM!

The effect correctly sets up and cleans up event listeners. The global keydown handler is acceptable since it verifies the event is relevant to this container before acting. Dependency array is correct and cleanup is properly implemented.

Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

@simo6529 simo6529 merged commit be05319 into main Oct 29, 2025
8 checks passed
@simo6529 simo6529 deleted the text-copy branch October 29, 2025 16:45
@coderabbitai coderabbitai Bot mentioned this pull request Dec 3, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 17, 2025
This was referenced Jan 7, 2026
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