Skip to content

hmtl preview on touch screens#2176

Merged
ragnep merged 3 commits intomainfrom
quick-vote-ux-fixes-2
Mar 27, 2026
Merged

hmtl preview on touch screens#2176
ragnep merged 3 commits intomainfrom
quick-vote-ux-fixes-2

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Mar 27, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved mobile swipe gesture detection with axis-locking for more accurate interactions
  • Improvements

    • Enhanced mobile layout positioning and structure for better usability
    • Optimized touch surface handling and event processing
    • Refined media preview loading with device-aware capabilities
    • Streamlined memes quick vote component architecture

Signed-off-by: ragnep <ragneinfo@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 27, 2026

Warning

Rate limit exceeded

@ragnep has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 43 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 19 minutes and 43 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecacfcd8-a86f-42ad-8373-04d65b679cda

📥 Commits

Reviewing files that changed from the base of the PR and between fd2d61d and fe78510.

📒 Files selected for processing (1)
  • components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.ts
📝 Walkthrough

Walkthrough

This PR extracts description overflow detection logic into a dedicated component, refactors mobile layout from absolute to flex positioning, and significantly enhances touch handling with Y-axis awareness and device-aware HTML preview support for touch screens.

Changes

Cohort / File(s) Summary
Description Component Extraction
components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteControls.tsx, components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDescription.tsx
Moved inline MemesQuickVoteDescription implementation from MemesQuickVoteControls to new dedicated component file; new component handles line-clamping, overflow detection via ResizeObserver, and "See more/See less" toggle with aria-expanded accessibility.
Mobile Layout Refinement
components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialog.tsx, components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialogSkeleton.tsx
Changed preview container sizing from tw-h-full to tw-min-h-0 tw-flex-1 (flex-grow); converted mobile controls from absolutely positioned overlay to flex-based flow item (tw-shrink-0); adjusted skeleton description lines to render dynamically from descriptionLineClassNames prop.
Preview Media & Touch Handling
components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVotePreview.tsx, components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.ts, components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.helpers.ts
Added device-aware HTML preview image URL computation conditional on hasTouchScreen; refactored artwork rendering into helper; introduced touch surface components with touchAction: "pan-y"; expanded touch event model from X-only to X/Y-aware with axis-lock logic; split touch fallback handling into dedicated hooks (usePatchComputedStyleForFallbackSwipe, useQuickVotePreviewCardTouchFallback); changed hook return shape to expose touch surface handlers instead of internal fallback refs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • see more btn and media loading #2173: Both PRs refactor the description "See more/See less" overflow behavior; this PR extracts it into a dedicated component while the prior PR initially added the inline implementation.
  • Html mobile preview 2503 #2167: Both PRs add device-aware htmlPreviewImageUrl support and wire it through media rendering paths based on device capabilities (touch screen detection).
  • Tap to load gating for html media #1759: Both PRs modify interactive HTML media rendering behavior via conditional disableAutoPlay and preview image gating, complementing tap-to-load interaction patterns.

Suggested reviewers

  • simo6529
  • prxt6529

Poem

🐰 A rabbit hops through swipe and touch,
With Y-axis dance that means so much,
From overflow clamps to flex-y flows,
Device-aware paths—now everyone knows!
Preview images hint at tales untold, ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'hmtl preview on touch screens' contains a typo ('hmtl' instead of 'html') and is only partially related to the changeset. While the PR does add HTML preview handling for touch screens, it encompasses much broader refactoring including description component extraction, mobile layout adjustments, touch gesture handling, and swipe interaction improvements. Correct the typo to 'html' and consider a more comprehensive title that reflects the primary refactoring effort, such as 'Refactor memes quick vote touch interactions and layout' or 'Extract description component and improve touch handling'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch quick-vote-ux-fixes-2

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.

Signed-off-by: ragnep <ragneinfo@gmail.com>
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 (4)
components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDescription.tsx (1)

61-90: ResizeObserver may miss initial observation if ref isn't set on first render.

The effect captures visibleDescriptionRef.current at effect time. If the ref is null when the effect first runs (which can happen with conditional rendering), the observer won't observe the element. Since measureOverflow is stable (empty deps), the effect won't re-run to pick up the ref later.

However, in this component the <p> element is always rendered, so the ref should be available. The requestAnimationFrame also provides an initial measurement. This is likely fine in practice.

💡 Alternative: observe inside a ref callback

If you encounter issues with the observer not picking up the element, consider using a ref callback pattern or adding visibleDescriptionRef.current to the dependency array (though that won't work directly with refs).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDescription.tsx`
around lines 61 - 90, The effect may miss observing the element if
visibleDescriptionRef.current is null when the effect first runs; update the
hook so the ResizeObserver is attached as soon as the element becomes available
by using a ref callback or by observing inside a callback ref assignment for the
<p> element (instead of relying only on the captured
visibleDescriptionRef.current in the useEffect). Specifically, change the ref
usage around visibleDescriptionRef and the observer setup so that when the ref
callback runs you create/observe with the ResizeObserver and call
measureOverflow, and ensure you still disconnect the observer and cancel frameId
in the cleanup of that setup (referencing measureOverflow,
visibleDescriptionRef, ResizeObserver, and the requestAnimationFrame
cancellation).
components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.helpers.ts (2)

10-61: Global getComputedStyle patch is a risky workaround.

This permanently patches window.getComputedStyle for the entire session to handle swipe animation fallback on browsers without native Touch support. While scoped to elements with data-quick-vote-transform, this approach:

  1. Can interfere with third-party libraries or browser devtools that rely on getComputedStyle
  2. Has no cleanup mechanism - once patched, stays patched
  3. Makes debugging transform-related issues more difficult

The implementation is technically correct (checking the flag, using prototype chain), but consider documenting this patch prominently or exploring alternatives like reading the data attribute directly in the swipe logic rather than patching a browser API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.helpers.ts`
around lines 10 - 61, The global patch in patchComputedStyleForFallbackSwipe
replaces window.getComputedStyle (flag QUICK_VOTE_COMPUTED_STYLE_PATCH_FLAG)
which is risky; instead, stop monkey-patching getComputedStyle and update the
swipe rendering logic to read the fallback transform directly from the element's
data attribute (QUICK_VOTE_TRANSFORM_DATA_ATTRIBUTE) where needed, or if a patch
is absolutely necessary, limit its scope by applying a local wrapper only when
computing styles for the quick-vote elements and provide a teardown/cleanup that
restores original getComputedStyle and documents the behavior; locate
patchComputedStyleForFallbackSwipe and replace the global patch approach with
direct data-attribute reads in the swipe code path or implement scoped patching
with restore and clear documentation.

108-119: Handler wrapper functions are redundant.

These wrappers just forward to the passed handlers without any transformation:

const handleTouchStart = (event: TouchEvent) => {
  handleTouchSurfaceStart(event);
};

You can pass the handlers directly to addEventListener:

♻️ Suggested simplification
-    const handleTouchStart = (event: TouchEvent) => {
-      handleTouchSurfaceStart(event);
-    };
-    const handleTouchMove = (event: TouchEvent) => {
-      handleTouchSurfaceMove(event);
-    };
-    const handleTouchEnd = (event: TouchEvent) => {
-      handleTouchSurfaceEnd(event);
-    };
-    const handleTouchCancel = (event: TouchEvent) => {
-      handleTouchSurfaceCancel(event);
-    };
-
-    previewCardNode.addEventListener("touchstart", handleTouchStart);
-    previewCardNode.addEventListener("touchmove", handleTouchMove);
-    previewCardNode.addEventListener("touchend", handleTouchEnd);
-    previewCardNode.addEventListener("touchcancel", handleTouchCancel);
+    previewCardNode.addEventListener("touchstart", handleTouchSurfaceStart);
+    previewCardNode.addEventListener("touchmove", handleTouchSurfaceMove);
+    previewCardNode.addEventListener("touchend", handleTouchSurfaceEnd);
+    previewCardNode.addEventListener("touchcancel", handleTouchSurfaceCancel);

     return () => {
-      previewCardNode.removeEventListener("touchstart", handleTouchStart);
-      previewCardNode.removeEventListener("touchmove", handleTouchMove);
-      previewCardNode.removeEventListener("touchend", handleTouchEnd);
-      previewCardNode.removeEventListener("touchcancel", handleTouchCancel);
+      previewCardNode.removeEventListener("touchstart", handleTouchSurfaceStart);
+      previewCardNode.removeEventListener("touchmove", handleTouchSurfaceMove);
+      previewCardNode.removeEventListener("touchend", handleTouchSurfaceEnd);
+      previewCardNode.removeEventListener("touchcancel", handleTouchSurfaceCancel);
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.helpers.ts`
around lines 108 - 119, The four redundant wrapper functions (handleTouchStart,
handleTouchMove, handleTouchEnd, handleTouchCancel) merely forward the event to
the provided handlers (handleTouchSurfaceStart, handleTouchSurfaceMove,
handleTouchSurfaceEnd, handleTouchSurfaceCancel); remove those wrappers and pass
the original handlers directly into addEventListener calls (replace references
to handleTouchStart/Move/End/Cancel with
handleTouchSurfaceStart/Move/End/Cancel) so the code is simpler and avoids an
extra layer of indirection.
components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVotePreview.tsx (1)

302-307: Consider conditional rendering instead of CSS hiding for mobile details.

MemesQuickVoteMobileDetails is always rendered but hidden on desktop via md:tw-hidden. Since it includes touch handlers and is only relevant on mobile, conditionally rendering based on isMobile would avoid unnecessary DOM nodes and event handler attachment on desktop.

💡 Optional optimization
-        <MemesQuickVoteMobileDetails
-          description={description}
-          drop={drop}
-          title={title}
-          touchSurfaceProps={mobileTouchSurfaceProps}
-        />
+        {isMobile && (
+          <MemesQuickVoteMobileDetails
+            description={description}
+            drop={drop}
+            title={title}
+            touchSurfaceProps={mobileTouchSurfaceProps}
+          />
+        )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVotePreview.tsx`
around lines 302 - 307, Currently MemesQuickVoteMobileDetails is always mounted
and only hidden via CSS; instead wrap its JSX in a conditional so it only
renders when the mobile flag is true (e.g. if (isMobile) return
<MemesQuickVoteMobileDetails ... />). Modify the render that uses
MemesQuickVoteMobileDetails to check the existing isMobile state/hook and only
include the component when isMobile is true, passing mobileTouchSurfaceProps,
description, drop and title as before; remove the md:tw-hidden class (or keep if
needed for safety) so desktop does not mount the component or attach its touch
handlers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialogSkeleton.tsx`:
- Around line 34-36: The map over descriptionLineClassNames in the
MemesQuickVoteDialogSkeleton component uses className as the React key which can
collide if duplicate class names exist; change the key to a stable per-item
unique value (e.g., include the index or combine className+index) when rendering
<SkeletonBlock> so each element has a unique key during reconciliation (use
descriptionLineClassNames.map((className, idx) => ...) and set key to idx or
`${className}-${idx}`).

---

Nitpick comments:
In
`@components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDescription.tsx`:
- Around line 61-90: The effect may miss observing the element if
visibleDescriptionRef.current is null when the effect first runs; update the
hook so the ResizeObserver is attached as soon as the element becomes available
by using a ref callback or by observing inside a callback ref assignment for the
<p> element (instead of relying only on the captured
visibleDescriptionRef.current in the useEffect). Specifically, change the ref
usage around visibleDescriptionRef and the observer setup so that when the ref
callback runs you create/observe with the ResizeObserver and call
measureOverflow, and ensure you still disconnect the observer and cancel frameId
in the cleanup of that setup (referencing measureOverflow,
visibleDescriptionRef, ResizeObserver, and the requestAnimationFrame
cancellation).

In
`@components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVotePreview.tsx`:
- Around line 302-307: Currently MemesQuickVoteMobileDetails is always mounted
and only hidden via CSS; instead wrap its JSX in a conditional so it only
renders when the mobile flag is true (e.g. if (isMobile) return
<MemesQuickVoteMobileDetails ... />). Modify the render that uses
MemesQuickVoteMobileDetails to check the existing isMobile state/hook and only
include the component when isMobile is true, passing mobileTouchSurfaceProps,
description, drop and title as before; remove the md:tw-hidden class (or keep if
needed for safety) so desktop does not mount the component or attach its touch
handlers.

In
`@components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.helpers.ts`:
- Around line 10-61: The global patch in patchComputedStyleForFallbackSwipe
replaces window.getComputedStyle (flag QUICK_VOTE_COMPUTED_STYLE_PATCH_FLAG)
which is risky; instead, stop monkey-patching getComputedStyle and update the
swipe rendering logic to read the fallback transform directly from the element's
data attribute (QUICK_VOTE_TRANSFORM_DATA_ATTRIBUTE) where needed, or if a patch
is absolutely necessary, limit its scope by applying a local wrapper only when
computing styles for the quick-vote elements and provide a teardown/cleanup that
restores original getComputedStyle and documents the behavior; locate
patchComputedStyleForFallbackSwipe and replace the global patch approach with
direct data-attribute reads in the swipe code path or implement scoped patching
with restore and clear documentation.
- Around line 108-119: The four redundant wrapper functions (handleTouchStart,
handleTouchMove, handleTouchEnd, handleTouchCancel) merely forward the event to
the provided handlers (handleTouchSurfaceStart, handleTouchSurfaceMove,
handleTouchSurfaceEnd, handleTouchSurfaceCancel); remove those wrappers and pass
the original handlers directly into addEventListener calls (replace references
to handleTouchStart/Move/End/Cancel with
handleTouchSurfaceStart/Move/End/Cancel) so the code is simpler and avoids an
extra layer of indirection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e3f4dbd-260d-486e-9612-9b2d20403f64

📥 Commits

Reviewing files that changed from the base of the PR and between 4c2de45 and fd2d61d.

📒 Files selected for processing (7)
  • components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteControls.tsx
  • components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDescription.tsx
  • components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialog.tsx
  • components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVoteDialogSkeleton.tsx
  • components/brain/left-sidebar/waves/memes-quick-vote/MemesQuickVotePreview.tsx
  • components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.helpers.ts
  • components/brain/left-sidebar/waves/memes-quick-vote/useMemesQuickVotePreviewSwipe.ts

Signed-off-by: ragnep <ragneinfo@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

@ragnep ragnep merged commit 1d9a249 into main Mar 27, 2026
8 checks passed
@ragnep ragnep deleted the quick-vote-ux-fixes-2 branch March 27, 2026 13:32
@coderabbitai coderabbitai Bot mentioned this pull request Apr 2, 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