Skip to content

Better boosted drop#2216

Merged
simo6529 merged 4 commits intomainfrom
better-boosted-drop
Apr 8, 2026
Merged

Better boosted drop#2216
simo6529 merged 4 commits intomainfrom
better-boosted-drop

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Apr 6, 2026

Summary by CodeRabbit

  • New Features

    • Added wave selections to organize and manage drops within waves
    • Introduced chat variant for boosted cards featuring inline boost button and ranking display
    • Added filtering capability by selection across drop endpoints
  • Tests

    • Expanded test coverage for boosted card variants and wave selection functionality

simo6529 added 2 commits April 6, 2026 09:55
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

The PR refactors BoostedDropCardHome with variant and rank props, introduces wave selections support across API endpoints and utilities, and updates DropsList to render the new chat-variant boosted cards with boost interaction buttons. Multiple utility functions now include selection data structures.

Changes

Cohort / File(s) Summary
Boosted Card Component Refactoring
components/home/boosted/BoostedDropCardHome.tsx, components/home/boosted/BoostedDropCardHomeContent.tsx, components/home/boosted/BoostedDropLinkPreview.tsx
Refactored BoostedDropCardHome into memoized subcomponents (Header, Content, Footer); added variant?: "home" | "chat" and rank?: number props; introduced BoostedDropCardChatBoostButton for chat variant with boost/unboost logic; split content rendering into separate home/chat layouts with overflow measurement and responsive media aspect-ratio calculation; added variant prop to BoostedDropLinkPreview to thread variant through to SmartLinkPreview.
DropsList Integration
components/drops/view/DropsList.tsx
Updated boosted card rendering to use BoostedDropCardHome instead of BoostedDropCard; wrapped component in <div className="tw-p-3">; added variant="chat" prop to boosted cards alongside existing drop, rank, and onClick props.
Wave/Drop Utilities
components/waves/memes/submission/utils/buildPreviewDrop.ts, components/waves/utils/getOptimisticDrop.ts, hooks/useWaveDropsSearch.ts
Extended buildPreviewDrop and getOptimisticDrop to include wave.selections and top-level selections: [] fields in returned drop objects; updated useWaveDropsSearch toWaveMin mapping to include selections from ApiWave.
Visibility & Exports
components/user/layout/userPageVisibility.ts
Changed normalizeCountry from exported to file-local constant; internal usage by shouldHideSubscriptions unchanged.
API Schema & Endpoints
openapi.yaml
Added optional selection_id query parameter to /drops, /light-drops, and /waves/{id}/drops; introduced new wave selection management endpoints (GET/POST /waves/{id}/selections, DELETE /waves/{id}/selections/{selectionId}, and drop-management sub-routes); extended ApiDrop, ApiWave, and related schemas with selections arrays; added new component schemas (ApiWaveSelection, ApiWaveSelectionRequest, ApiWaveSelectionDropRequest).
Test Coverage
__tests__/components/home/boosted/BoostedDropCardHome.test.tsx, __tests__/components/drops/view/DropsList.test.tsx
Added comprehensive test suite for BoostedDropCardHome covering variant/rank rendering, link preview deduplication, overflow measurement, chat media aspect-ratio calculation, and boost button event propagation; updated DropsList tests with boosted card mock, props verification, and click handler assertions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ragnep
  • prxt6529

🐰✨ The boosted card now wears two faces,
Home and chat in their own places,
Wave selections bloom like clover springs,
While overflow metrics measure all things,
A refactored dream, hopping with grace! 🌿

🚥 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 'Better boosted drop' is vague and generic, using non-specific language that doesn't clearly convey the actual changes made. Consider using a more specific title that describes the actual work, such as 'Extract BoostedDropCardHome into reusable chat-variant component' or 'Add chat variant to boosted drop card with inline boost button'.
✅ 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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch better-boosted-drop

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.

Caution

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

⚠️ Outside diff range comments (1)
openapi.yaml (1)

8380-8475: ⚠️ Potential issue | 🟠 Major

Don't mark selections as required without backend confirmation that all endpoints always serialize it.

Making selections required in ApiDrop, ApiDropWithoutWave, ApiWave, and ApiWaveMin is a breaking schema change. Local builders in components/waves/memes/submission/utils/buildPreviewDrop.ts, components/waves/utils/getOptimisticDrop.ts, and hooks/useWaveDropsSearch.ts already had to initialize this field explicitly—indicating it wasn't previously guaranteed to exist. Unless every backend endpoint and code path that returns these schemas now serializes selections (even as empty arrays), clients will encounter validation failures or undefined errors at runtime.

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

In `@openapi.yaml` around lines 8380 - 8475, The OpenAPI change made the
selections property required on ApiDrop, ApiDropWithoutWave, ApiWave, and
ApiWaveMin causing a breaking change; revert that by removing selections from
the required arrays (or make it nullable/optional) so clients that don't receive
selections (or that initialize it locally in buildPreviewDrop.ts,
getOptimisticDrop, and useWaveDropsSearch.ts) won't fail validation—update the
schemas for ApiDrop, ApiDropWithoutWave, ApiWave, and ApiWaveMin to treat
selections as optional (or allow an empty/nullable array) instead of required.
🧹 Nitpick comments (4)
components/home/boosted/BoostedDropCardHome.tsx (1)

192-205: Consider improving the author link fallback when handle is missing.

When author.handle is falsy, the link navigates to "#" which isn't ideal UX. Consider either not rendering the link as clickable or using the primary address as a fallback route.

💡 Optional: Render as non-clickable span when handle is missing
-      <Link
-        href={author.handle ? `/${author.handle}` : "#"}
-        onClick={(event) => event.stopPropagation()}
-        className={AUTHOR_LINK_CLASSES}
-      >
+      {author.handle ? (
+        <Link
+          href={`/${author.handle}`}
+          onClick={(event) => event.stopPropagation()}
+          className={AUTHOR_LINK_CLASSES}
+        >
+          <ProfileAvatar
+            pfpUrl={author.pfp}
+            alt={author.handle}
+            size={ProfileBadgeSize.SMALL}
+          />
+          <span className="tw-break-words tw-text-sm tw-font-medium tw-text-iron-50">
+            {author.handle}
+          </span>
+        </Link>
+      ) : (
+        <div className={AUTHOR_LINK_CLASSES}>
+          <ProfileAvatar
+            pfpUrl={author.pfp}
+            alt="Anonymous"
+            size={ProfileBadgeSize.SMALL}
+          />
+          <span className="tw-break-words tw-text-sm tw-font-medium tw-text-iron-50">
+            Anonymous
+          </span>
+        </div>
+      )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/home/boosted/BoostedDropCardHome.tsx` around lines 192 - 205, The
author link currently renders an anchor to "#" when author.handle is falsy;
update the JSX in BoostedDropCardHome so that if author.handle exists you render
the Link component with href={`/${author.handle}`} (keeping onClick={event =>
event.stopPropagation()} and AUTHOR_LINK_CLASSES), but if author.handle is
missing render a non-clickable span (same classes/contents: ProfileAvatar and
handle display) or alternatively use author.address as the fallback route
(href={`/${author.address}`} when present); ensure only the Link variant has the
onClick stopPropagation handler and the non-clickable span is used for better UX
when there's no handle.
components/home/boosted/BoostedDropCardHomeContent.tsx (1)

318-345: Retry interval continues after successful measurement.

The useOverflowMeasurementRetry hook continues running the interval even after hasMeasuredOverflow becomes true. While it stops after 12 attempts, stopping early when measurement succeeds would be more efficient.

💡 Optional: Stop retry interval once measurement succeeds
 const useOverflowMeasurementRetry = ({
   enabled,
   measureOverflow,
+  hasMeasuredOverflow,
 }: {
   readonly enabled: boolean;
   readonly measureOverflow: () => void;
+  readonly hasMeasuredOverflow: boolean;
 }) => {
   useEffect(() => {
-    if (!enabled) {
+    if (!enabled || hasMeasuredOverflow) {
       return;
     }

     let attempts = 0;
     const intervalId = globalThis.setInterval(() => {
       measureOverflow();
       attempts += 1;
       if (attempts >= MAX_OVERFLOW_MEASUREMENT_ATTEMPTS) {
         globalThis.clearInterval(intervalId);
       }
     }, OVERFLOW_MEASUREMENT_INTERVAL_MS);

     return () => {
       if (typeof intervalId === "number") {
         globalThis.clearInterval(intervalId);
       }
     };
-  }, [enabled, measureOverflow]);
+  }, [enabled, measureOverflow, hasMeasuredOverflow]);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/home/boosted/BoostedDropCardHomeContent.tsx` around lines 318 -
345, The retry interval in useOverflowMeasurementRetry should stop as soon as
overflow measurement succeeds; modify the hook so measureOverflow indicates
success (e.g., change its contract to return a boolean or have an accessible
hasMeasuredOverflow getter) and after calling measureOverflow() inside the
interval, check the result and call globalThis.clearInterval(intervalId)
immediately when it returns true (in addition to the existing attempts cap),
ensuring the cleanup return still clears the interval; keep the effect
dependencies ([enabled, measureOverflow]) unchanged.
openapi.yaml (2)

1012-1017: Clarify whether selection_id is globally unique.

GET /drops can now filter by selection_id alone, but selections are managed under /waves/{id}/selections/{selectionId}. If those IDs are only wave-scoped, this filter is ambiguous; if they are global, say that in the description. Otherwise require wave_id together with selection_id.

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

In `@openapi.yaml` around lines 1012 - 1017, The OpenAPI param `selection_id` used
by GET /drops is ambiguous about scope; clarify whether `selection_id` values
are globally unique or require a `wave_id`. Update the `selection_id` parameter
description (the param named selection_id in the GET /drops operation) to state
explicitly that selection IDs are global if they are unique across waves, or
change the schema/requirements so that `wave_id` must be provided alongside
`selection_id` (or vice versa) and document that requirement; reference the
existing selection resource path `/waves/{id}/selections/{selectionId}` in the
description to explain scoping.

5905-6020: Document the common 4xxs on selection mutations.

These endpoints only advertise success responses. Please add the expected failure cases too—at least forbidden and not-found—so generated clients can distinguish permission errors from missing wave, selection, or drop IDs.

📄 Example contract tweak
   /waves/{id}/selections/{selectionId}:
     delete:
       tags:
         - Waves
       summary: Delete selection from wave
       operationId: deleteWaveSelection
       parameters:
         - name: id
           in: path
           required: true
           schema:
             type: string
         - name: selectionId
           in: path
           required: true
           schema:
             type: string
       responses:
         "200":
           description: successful operation
+        "403":
+          description: Only wave creators or admins can manage selections
+        "404":
+          description: Wave or selection not found
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openapi.yaml` around lines 5905 - 6020, For each selection mutation operation
(operationId createWaveSelection, deleteWaveSelection, addDropToWaveSelection,
removeDropFromWaveSelection) add explicit 4xx responses: at minimum 403
(forbidden) and 404 (not found) alongside the existing success codes; include a
descriptive "description" and reference the project's standard error schema
(e.g., ApiError or the common error response component) so generated clients can
distinguish permission errors from missing wave/selection/drop IDs—apply this
change to the POST /waves/{id}/selections, DELETE
/waves/{id}/selections/{selectionId}, POST
/waves/{id}/selections/{selectionId}/drops, and DELETE
/waves/{id}/selections/{selectionId}/drops/{dropId} entries (keep existing
success schemas like ApiWaveSelection, ApiWaveSelectionRequest,
ApiWaveSelectionDropRequest as-is).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@openapi.yaml`:
- Around line 8380-8475: The OpenAPI change made the selections property
required on ApiDrop, ApiDropWithoutWave, ApiWave, and ApiWaveMin causing a
breaking change; revert that by removing selections from the required arrays (or
make it nullable/optional) so clients that don't receive selections (or that
initialize it locally in buildPreviewDrop.ts, getOptimisticDrop, and
useWaveDropsSearch.ts) won't fail validation—update the schemas for ApiDrop,
ApiDropWithoutWave, ApiWave, and ApiWaveMin to treat selections as optional (or
allow an empty/nullable array) instead of required.

---

Nitpick comments:
In `@components/home/boosted/BoostedDropCardHome.tsx`:
- Around line 192-205: The author link currently renders an anchor to "#" when
author.handle is falsy; update the JSX in BoostedDropCardHome so that if
author.handle exists you render the Link component with
href={`/${author.handle}`} (keeping onClick={event => event.stopPropagation()}
and AUTHOR_LINK_CLASSES), but if author.handle is missing render a non-clickable
span (same classes/contents: ProfileAvatar and handle display) or alternatively
use author.address as the fallback route (href={`/${author.address}`} when
present); ensure only the Link variant has the onClick stopPropagation handler
and the non-clickable span is used for better UX when there's no handle.

In `@components/home/boosted/BoostedDropCardHomeContent.tsx`:
- Around line 318-345: The retry interval in useOverflowMeasurementRetry should
stop as soon as overflow measurement succeeds; modify the hook so
measureOverflow indicates success (e.g., change its contract to return a boolean
or have an accessible hasMeasuredOverflow getter) and after calling
measureOverflow() inside the interval, check the result and call
globalThis.clearInterval(intervalId) immediately when it returns true (in
addition to the existing attempts cap), ensuring the cleanup return still clears
the interval; keep the effect dependencies ([enabled, measureOverflow])
unchanged.

In `@openapi.yaml`:
- Around line 1012-1017: The OpenAPI param `selection_id` used by GET /drops is
ambiguous about scope; clarify whether `selection_id` values are globally unique
or require a `wave_id`. Update the `selection_id` parameter description (the
param named selection_id in the GET /drops operation) to state explicitly that
selection IDs are global if they are unique across waves, or change the
schema/requirements so that `wave_id` must be provided alongside `selection_id`
(or vice versa) and document that requirement; reference the existing selection
resource path `/waves/{id}/selections/{selectionId}` in the description to
explain scoping.
- Around line 5905-6020: For each selection mutation operation (operationId
createWaveSelection, deleteWaveSelection, addDropToWaveSelection,
removeDropFromWaveSelection) add explicit 4xx responses: at minimum 403
(forbidden) and 404 (not found) alongside the existing success codes; include a
descriptive "description" and reference the project's standard error schema
(e.g., ApiError or the common error response component) so generated clients can
distinguish permission errors from missing wave/selection/drop IDs—apply this
change to the POST /waves/{id}/selections, DELETE
/waves/{id}/selections/{selectionId}, POST
/waves/{id}/selections/{selectionId}/drops, and DELETE
/waves/{id}/selections/{selectionId}/drops/{dropId} entries (keep existing
success schemas like ApiWaveSelection, ApiWaveSelectionRequest,
ApiWaveSelectionDropRequest as-is).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab952f77-6813-430e-848a-dc099e919a1f

📥 Commits

Reviewing files that changed from the base of the PR and between 97d225e and fea362d.

⛔ Files ignored due to path filters (9)
  • generated/models/ApiDrop.ts is excluded by !**/generated/**
  • generated/models/ApiDropWithoutWave.ts is excluded by !**/generated/**
  • generated/models/ApiWave.ts is excluded by !**/generated/**
  • generated/models/ApiWaveMin.ts is excluded by !**/generated/**
  • generated/models/ApiWaveSelection.ts is excluded by !**/generated/**
  • generated/models/ApiWaveSelectionDropRequest.ts is excluded by !**/generated/**
  • generated/models/ApiWaveSelectionRequest.ts is excluded by !**/generated/**
  • generated/models/ObjectSerializer.ts is excluded by !**/generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • __tests__/components/drops/view/DropsList.test.tsx
  • __tests__/components/home/boosted/BoostedDropCardHome.test.tsx
  • components/drops/view/DropsList.tsx
  • components/home/boosted/BoostedDropCardHome.tsx
  • components/home/boosted/BoostedDropCardHomeContent.tsx
  • components/home/boosted/BoostedDropLinkPreview.tsx
  • components/user/layout/userPageVisibility.ts
  • components/waves/memes/submission/utils/buildPreviewDrop.ts
  • components/waves/utils/getOptimisticDrop.ts
  • hooks/useWaveDropsSearch.ts
  • openapi.yaml

simo6529 added 2 commits April 6, 2026 15:17
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 8, 2026

@simo6529 simo6529 merged commit c1b97d8 into main Apr 8, 2026
8 checks passed
@simo6529 simo6529 deleted the better-boosted-drop branch April 8, 2026 10:49
@coderabbitai coderabbitai Bot mentioned this pull request Apr 22, 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