Skip to content

Edit memes submission#2288

Merged
simo6529 merged 19 commits intomainfrom
edit-memes-submission
Apr 20, 2026
Merged

Edit memes submission#2288
simo6529 merged 19 commits intomainfrom
edit-memes-submission

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Apr 17, 2026

Summary by CodeRabbit

  • New Features

    • Complete resubmission workflow: acknowledgement, resubmit UI, delete-original confirmation, and modal-driven resubmission shell/steps.
    • Fullscreen artwork overlay with voting, position, focus/backdrop handling.
    • Download section and richer previews: video, HTML, GLTF/GLB support, existing-media resubmission, customizable submit labels.
  • Bug Fixes / UX

    • Mobile menu/resubmit sequencing now opens modals after menu transitions.
    • Leaderboard refreshes when a source submission is deleted.
  • Tests

    • Expanded coverage for resubmission, mobile touch flows, media validation, deletion, and preview behaviors.

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 Apr 17, 2026

📝 Walkthrough

Walkthrough

Adds a resubmission flow, modal wiring for desktop and touch (deferred open after mobile transition), extensive submission form refactor (reducer, actions, media controls, validation), new drop/media preview components and types, deletion flow/hook, and broad test coverage updates for resubmit and preview behaviors.

Changes

Cohort / File(s) Summary
Leaderboard & Drop Integration
components/memes/drops/MemesLeaderboardDrop.tsx, components/waves/leaderboard/drops/WaveLeaderboardDrop.tsx, components/waves/leaderboard/drops/WaveLeaderboardDrops.tsx, __tests__/components/waves/leaderboard/drops/WaveLeaderboardDrop.test.tsx, __tests__/components/waves/leaderboard/drops/WaveLeaderboardDrops.test.tsx
Thread wave and onSourceDropDeleted through leaderboard components; leaderboard list refetches when a source drop is deleted; tests updated to assert callback propagation and refetch.
Resubmit Action & Modal
components/waves/memes/submission/MemesArtResubmitAction.tsx, components/waves/memes/MemesArtSubmissionModal.tsx, components/waves/memes/submission/MemesArtSubmissionShell.tsx, __tests__/components/waves/memes/submission/MemesArtResubmitAction.test.tsx
New Resubmit action component (icon/menu/button variants) with optional external opener and internal modal path; modal accepts sourceDrop/onSourceDropDeleted; tests cover opener/modal/delete flows.
Submission Container & Steps
components/waves/memes/submission/MemesArtSubmissionContainer.tsx, components/waves/memes/submission/MemesArtSubmissionStepContent.tsx, components/waves/memes/submission/ResubmitAcknowledgement.tsx, components/waves/memes/submission/ResubmitDeleteConfirmation.tsx, components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.tsx, components/waves/memes/submission/steps/AdditionalInfoStep.tsx, components/waves/memes/submission/steps/ArtworkStep.tsx, __tests__/components/waves/memes/submission/MemesArtSubmissionContainer.test.tsx
Container now accepts sourceDrop/onSourceDropDeleted, presents resubmit acknowledgement/delete flows, parameterizes submit label, and adapts step rendering; tests added/expanded for resubmission, deletion, GLTF, and existing-media flows.
Form State, Actions & Media Controls
components/waves/memes/submission/hooks/artworkSubmissionFormState.ts, components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts, components/waves/memes/submission/hooks/useArtworkSubmissionFormActions.ts, components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts, components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts, components/waves/memes/submission/hooks/useResubmissionDelete.ts, __tests__/components/waves/memes/submission/hooks/useArtworkSubmissionForm.test.ts
Large extraction of form reducer/initialization, actions, media controls, mutation and resubmission-delete hook; main hook now composes these and accepts initialDraft; media validation and existingMedia flow integrated; tests updated for React Query and FileReader error paths.
Preview / Media Utilities & Drafts
components/waves/memes/submission/utils/resubmissionMediaType.ts, components/waves/memes/submission/utils/submissionDraft.ts, components/waves/memes/submission/utils/buildPreviewDrop.ts, components/waves/memes/submission/utils/resubmissionMediaType.test.ts, components/waves/memes/submission/utils/submissionDraft.test.ts
New utilities for classifying media (GLTF/GLB/HTML/video), building submission drafts from drops (traits, operationalData, existingMedia), and preview builder changed to prefer existingMedia; unit tests added.
Drop Info Subcomponents & Types
components/waves/drop/MemesDropArtworkHero.tsx, components/waves/drop/MemesDropSummarySection.tsx, components/waves/drop/MemesDropDetailsSection.tsx, components/waves/drop/MemesDropFullscreenOverlay.tsx, components/waves/drop/memesDropPanelTypes.ts, components/waves/drop/MemesSingleWaveDropInfoPanel.tsx, components/waves/drop/MemesSingleWaveDrop.tsx, __tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx
Extracted drop panel into focused components (artwork hero, summary, details, fullscreen overlay) with new types and isMemesDropImageMedia guard; forwarded onClose and added overlay keyboard/backdrop handling; tests expanded.
File Upload & Preview Enhancements
components/waves/memes/file-upload/components/FilePreview.tsx, components/waves/memes/MemesArtSubmissionFile.tsx, components/waves/memes/file-upload/reducers/types.ts, __tests__/components/waves/memes/MemesArtSubmissionFile.test.tsx, __tests__/components/waves/memes/submission/steps/ArtworkStep.test.tsx
FilePreview accepts mimeType and adds HTML preview; submission file consumes uploadError/artworkMimeType, resets uploader state on error; types adjusted and tests verify error+reset behavior.
Mobile Dropdown / Transition Behavior
components/utils/select/dropdown/CommonDropdownItemsMobileWrapper.tsx, __tests__/components/utils/select/dropdown/CommonDropdownItemsMobileWrapper.test.tsx, __tests__/components/memes/drops/MemesLeaderboardDrop.test.tsx
Added optional onAfterLeave to mobile dropdown transition to defer actions until after close; tests ensure afterLeave is forwarded and used to open modal after transition.
Tests & Normalizations
multiple __tests__/* files
Widespread test updates: standardized quoting, expanded mocks for resubmission subcomponents, added fixture fields (e.g., wave.id), and new coverage for long-press/mobile resubmit, deletion, FileReader error paths, and interactive-media validation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Leaderboard as Leaderboard UI
    participant Resubmit as MemesArtResubmitAction
    participant MobileMenu as Mobile Dropdown
    participant Modal as MemesArtSubmissionModal/Shell
    participant Form as Submission Form (hooks)
    participant API as Backend/API

    User->>Leaderboard: open drop actions
    Leaderboard->>Resubmit: render with wave & onSourceDropDeleted
    User->>Resubmit: tap/click "Resubmit"
    alt mobile menu present
        Resubmit->>MobileMenu: setIsActive(false) (close menu)
        MobileMenu-->>Resubmit: onAfterLeave (deferred)
    end
    Resubmit->>Modal: open submission modal (internal or external opener)
    User->>Modal: acknowledge / fill form / submit
    Modal->>Form: submit resubmission
    Form->>API: POST resubmitted artwork
    API-->>Form: returns new drop
    Form->>Modal: surface delete-original option (if applicable)
    User->>Modal: confirm delete original
    Modal->>API: DELETE original drop
    API-->>Modal: success
    Modal->>Leaderboard: call onSourceDropDeleted()
    Leaderboard->>Leaderboard: refetch leaderboard
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ragnep

Poem

🐰✨ I tap the menu, close, then wait,
A modal pops up — resubmit fate.
Hooks hop apart, drafts neat and new,
Old drops bow out as fresh pixels brew.
Carrot cheers for code that bounces through!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.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 "Edit memes submission" is partially related to the changeset. While resubmission/editing of artwork is a major feature in this PR, the title is vague and does not clearly specify that this is about enabling artwork resubmission functionality with deletion, modal workflows, and media type handling. Consider a more specific title such as "Add artwork resubmission flow with deletion and media handling" or "Enable memes artwork resubmission with original drop deletion" to better convey the primary features being introduced.
✅ 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 edit-memes-submission

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

🧹 Nitpick comments (19)
components/waves/drop/MemesDropFullscreenOverlay.tsx (1)

18-18: onClose typed as MouseEventHandler limits reuse.

Typing onClose as MouseEventHandler<HTMLButtonElement> couples the API to a click origin. If you later trigger it from a keyboard handler (ESC) or backdrop click, the signature becomes awkward. Prefer a plain () => void.

Proposed change
-  readonly onClose: MouseEventHandler<HTMLButtonElement>;
+  readonly onClose: () => void;

And drop the MouseEventHandler import.

Also applies to: 59-59

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

In `@components/waves/drop/MemesDropFullscreenOverlay.tsx` at line 18, The prop
type for onClose in the MemesDropFullscreenOverlay component is too specific;
change the onClose signature from MouseEventHandler<HTMLButtonElement> to a
simple () => void in the props/interface (reference: onClose and
MemesDropFullscreenOverlay) so it can be invoked from clicks, keyboard (ESC) or
backdrop events, and remove the now-unused MouseEventHandler import; update any
callers to pass a zero-arg function if needed.
components/waves/memes/submission/hooks/useResubmissionDelete.ts (1)

59-63: Stale auth check if requestAuth runs before setting loading state? Verify ordering.

setIsDeletingOriginal(true) fires before requestAuth(), so if auth is cancelled the loading state briefly flashes true then false. Minor UX nit — consider performing requestAuth() first, then flipping the loading flag.

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

In `@components/waves/memes/submission/hooks/useResubmissionDelete.ts` around
lines 59 - 63, In useResubmissionDelete change the order around the auth call
and loading flag: call requestAuth() and await its result before calling
setIsDeletingOriginal(true) so the UI loading state is only set if auth
succeeds; specifically, move the requestAuth() invocation earlier (or
conditionalize setIsDeletingOriginal) around the existing requestAuth /
setIsDeletingOriginal lines to avoid the brief flash when requestAuth is
cancelled.
components/waves/drop/MemesDropSummarySection.tsx (1)

10-10: Consider making description optional.

It is rendered conditionally ({description && ...}) on line 51, so the string can effectively be empty. Typing it as readonly description?: string | undefined matches usage and removes a requirement for callers to pass an empty string explicitly.

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

In `@components/waves/drop/MemesDropSummarySection.tsx` at line 10, The prop type
for the drop summary currently forces callers to provide description via
"readonly description: string;" even though the component checks "{description
&& ...}" and treats it as optional; change the property on the relevant
type/interface to be optional (e.g. "readonly description?: string" or "readonly
description?: string | undefined") so callers can omit it and the typing matches
runtime usage; update the type declaration where "readonly description" is
defined (the interface/type used by MemesDropSummarySection) accordingly.
components/waves/drop/memesDropPanelTypes.ts (1)

1-14: Consider naming consistency between mime_type and fileInfo.

MemesDropMedia uses snake_case (mime_type) while MemesDropDownloadData uses camelCase (fileInfo). Within a file introducing related types, pick one convention (snake_case likely tracks API payloads, camelCase tracks UI-shaped data — splitting them by origin is fine, but worth a quick look to confirm the intent here).

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

In `@components/waves/drop/memesDropPanelTypes.ts` around lines 1 - 14,
MemesDropMedia currently defines mime_type (snake_case) while
MemesDropDownloadData and MemesDropFileInfo use camelCase (fileInfo, name,
extension); make the naming consistent by choosing one convention (e.g., convert
mime_type to mimeType or convert fileInfo to file_info), update the interfaces
(MemesDropMedia, MemesDropFileInfo, MemesDropDownloadData) accordingly, and then
update all usages of those symbols (references to mime_type/mimeType and
fileInfo/file_info) throughout the codebase to match the chosen convention so
types align with their origin (API payload vs UI model).
__tests__/components/waves/memes/submission/utils/resubmissionMediaType.test.ts (1)

3-31: Consider broadening coverage to other branches of getResubmissionMediaTypeInfo.

The suite only covers GLTF/GLB paths. The implementation also handles video/*, text/html, unknown types ({ label: null, isInteractive: false }), extension-with-query-string stripping, and case-insensitive normalization — all untested here.

Suggested additional cases
it("classifies video mime types", () => {
  expect(getResubmissionMediaTypeInfo({ mimeType: "video/mp4" })).toEqual({
    label: "Video",
    isInteractive: false,
  });
});

it("classifies text/html as interactive HTML", () => {
  expect(getResubmissionMediaTypeInfo({ mimeType: "text/html" })).toEqual({
    label: "HTML",
    isInteractive: true,
  });
});

it("returns null label for unknown types", () => {
  expect(getResubmissionMediaTypeInfo({ mimeType: "image/png" })).toEqual({
    label: null,
    isInteractive: false,
  });
});

it("ignores query strings and fragments when matching extensions", () => {
  expect(
    getResubmissionMediaTypeInfo({ url: "https://x/y.GLB?v=1#frag" })
  ).toEqual({ label: "GLB", isInteractive: true });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@__tests__/components/waves/memes/submission/utils/resubmissionMediaType.test.ts`
around lines 3 - 31, Add unit tests covering the other branches of
getResubmissionMediaTypeInfo: add cases that pass mimeType "video/mp4" expecting
{ label: "Video", isInteractive: false }, mimeType "text/html" expecting {
label: "HTML", isInteractive: true }, an unknown mimeType like "image/png"
expecting { label: null, isInteractive: false }, and a URL with mixed case and
query/fragment like "https://x/y.GLB?v=1#frag" expecting { label: "GLB",
isInteractive: true }; place these new it(...) blocks alongside the existing
GLTF/GLB tests in the same test file to exercise extension stripping and
case-insensitive normalization in getResubmissionMediaTypeInfo.
components/waves/memes/submission/MemesArtSubmissionShell.tsx (1)

27-39: Optional: drop motion.* wrappers if no animation is applied.

motion.h3 and motion.button are used without any initial/animate/exit/transition props, so they add framer-motion overhead with no behavioral benefit. Plain h3/button would suffice.

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

In `@components/waves/memes/submission/MemesArtSubmissionShell.tsx` around lines
27 - 39, The motion wrappers around the header and close button in
MemesArtSubmissionShell (motion.h3 and motion.button) provide no animation props
and should be replaced with plain h3 and button elements (preserve title,
className, aria-label, onClose handler and inner FontAwesomeIcon) to avoid
framer-motion overhead; after replacing, remove any unused framer-motion imports
(e.g., motion) from the file to clean up unused dependencies.
__tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx (1)

101-112: Brittle useState spy.

jest.spyOn(React, "useState").mockImplementationOnce(...) depends on useState being the very first hook invoked on render. Any reordering or added hook upstream in MemesSingleWaveDropInfoPanel will silently break this test (it will target the wrong state slot). Consider testing fullscreen behavior through user interaction (clicking an "Enter fullscreen" affordance) instead of mocking React internals. Noting as pre-existing pattern.

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

In `@__tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx` around
lines 101 - 112, The test is brittle because it spies on React.useState; remove
the useState spy in the "closes fullscreen when button clicked" test and instead
exercise the component through user interactions: render
MemesSingleWaveDropInfoPanel, simulate the user clicking the "Enter fullscreen
view" control (or whatever affordance opens fullscreen), then click the "Exit
fullscreen view" button via screen.getByRole and assert the visible outcome
(e.g., fullscreen element is removed, a particular aria-hidden/aria-expanded
state changes, or the Exit button is no longer in the document) rather than
asserting a mocked setState was called.
components/waves/memes/file-upload/components/FilePreview.tsx (1)

45-56: Consider URL-extension fallback for GLB detection on resubmission.

isGLB still relies on file?.name for extension-based detection. When resubmitting existing GLB media (no file, only url + mimeType), detection works only if mimeType is correctly model/gltf-binary/model/gltf+json. If the existing media URL ends in .glb/.gltf but has a generic/incorrect mimeType, the preview will miss the GLB branch. Consider falling back to extracting the extension from url similar to the helper in resubmissionMediaType.ts.

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

In `@components/waves/memes/file-upload/components/FilePreview.tsx` around lines
45 - 56, The isGLB detection currently checks effectiveMimeType and file?.name
extensions but misses the case where there's no file and the resubmitted media
URL ends with .glb/.gltf; update the isGLB logic in FilePreview.tsx (symbols:
effectiveMimeType, isGLB, url, file) to also inspect the url's
pathname/extension when file is undefined or file?.name is absent—e.g., extract
the extension from url (similar to the helper in resubmissionMediaType.ts) and
treat .glb/.gltf as GLB so resubmitted GLB URLs with incorrect/blank mimeType
still enter the GLB branch.
components/waves/drop/MemesDropDetailsSection.tsx (1)

39-41: Nit: artworkMedia !== null allows undefined through.

artworkMedia is typed as MemesDropMedia | null | undefined, so !== null is true when artworkMedia is undefined. It's safe today because fileInfo will be null whenever artworkMedia is falsy (per the parent MemesSingleWaveDropInfoPanel), but a truthy check is more defensive:

🛡️ Proposed tweak
-  const hasArtworkDownload = artworkMedia !== null && fileInfo !== null;
+  const hasArtworkDownload = Boolean(artworkMedia) && fileInfo !== null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/drop/MemesDropDetailsSection.tsx` around lines 39 - 41,
Change the null-only check for artworkMedia to a defensive truthy/nonnull check
so undefined doesn’t slip through: update the hasArtworkDownload expression
(which currently uses artworkMedia !== null && fileInfo !== null) to ensure
artworkMedia is present (e.g., artworkMedia != null or a truthy check like
Boolean(artworkMedia)) before checking fileInfo; keep hasDownloads logic using
previewImageData and promoVideoData unchanged besides using consistent
null/undefined-safe checks if desired.
__tests__/components/memes/drops/MemesLeaderboardDrop.test.tsx (1)

175-201: LGTM — deferred-modal behavior is well captured.

The test correctly asserts that clicking the resubmit action calls setIsActive(false) without opening the modal, and that the modal only appears after the mocked after-leave trigger — matching the production afterLeave wiring on TransitionChild.

Minor observation: the CommonDropdownItemsMobileWrapper mock renders its children unconditionally, which means this test doesn't enforce that the resubmit action lives behind the transition. If you want to harden the invariant, guard the children with {p.isOpen ? p.children : null} in the mock.

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

In `@__tests__/components/memes/drops/MemesLeaderboardDrop.test.tsx` around lines
175 - 201, The CommonDropdownItemsMobileWrapper test mock currently renders
children unconditionally which bypasses the Transition gating; update the mock
used in this test to only render children when the wrapper's isOpen prop is true
(i.e., change the mock component CommonDropdownItemsMobileWrapper to return
p.isOpen ? p.children : null) so the resubmit action is actually behind the
open/close transition and the test enforces the production behavior in
MemesLeaderboardDrop.
components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts (1)

25-43: LGTM on existingMedia extension.

The validation (hasFile || hasExisting || hasExternal), the explicit mimeType check for existingMedia, and the payload construction branch (url/mime_type) are consistent with ApiDropMedia. The normalized existingUrl = data.existingMedia?.url.trim() ?? "" keeps the control flow safe when existingMedia is absent.

One small nit: externalMedia.mimeType was narrowed to InteractiveMediaMimeType; widening it to string here removes a type-level guard at the call sites. If the only caller that passes a non-interactive value is the existingMedia/resubmission path, consider keeping externalMedia.mimeType typed as InteractiveMediaMimeType and using existingMedia.mimeType: string for the resubmission mime-type passthrough (which is what you already do).

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

In `@components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts`
around lines 25 - 43, Update the ArtworkSubmissionData type so
externalMedia.mimeType stays narrowed to InteractiveMediaMimeType while
existingMedia.mimeType is a plain string: change externalMedia?: { url: string;
mimeType: InteractiveMediaMimeType } | undefined and existingMedia?: { url:
string; mimeType: string } | undefined; leave all other fields and the
normalized existingUrl logic untouched so callers that require interactive-only
mime types still get type-level guarantees while resubmission passthroughs
accept arbitrary existingMedia mime strings.
components/waves/memes/submission/hooks/useArtworkSubmissionFormActions.ts (2)

48-51: Nit: double-applying profile defaults on handleContinueFromArtwork.

applyProfileDefaults runs again here in addition to handleContinueFromTerms. reduceProfileDefaults is idempotent (only fills empty fields), so this is safe, but if it isn't intentional (e.g., to cover cases where user skipped terms or re-entered the flow), the second call could be removed.

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

In `@components/waves/memes/submission/hooks/useArtworkSubmissionFormActions.ts`
around lines 48 - 51, The handler handleContinueFromArtwork currently calls
applyProfileDefaults again even though applyProfileDefaults is already invoked
in handleContinueFromTerms and reduceProfileDefaults is idempotent; remove the
duplicate applyProfileDefaults call from handleContinueFromArtwork so it only
dispatches SET_STEP to SubmissionStep.ADDITIONAL_INFO (leave
applyProfileDefaults in handleContinueFromTerms as the single source of default
application), referencing the functions handleContinueFromArtwork,
handleContinueFromTerms, applyProfileDefaults and reduceProfileDefaults when
making the change.

123-134: Redundant re-assignment of title and description.

Both fields are already in traits via the spread; the explicit title: traits.title / description: traits.description lines are no-ops. Safe to drop for clarity.

♻️ Proposed diff
   const getSubmissionData = useCallback(() => {
     const { artworkUrl, operationalData, traits } = state;
     return {
       imageUrl: artworkUrl,
-      traits: {
-        ...traits,
-        title: traits.title,
-        description: traits.description,
-      },
+      traits: { ...traits },
       operationalData,
     };
   }, [state]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/memes/submission/hooks/useArtworkSubmissionFormActions.ts`
around lines 123 - 134, The getSubmissionData function redundantly reassigns
traits.title and traits.description after spreading traits; remove the no-op
explicit entries and return traits directly from state.traits. Locate the
getSubmissionData useCallback (references: getSubmissionData, state, artworkUrl,
traits, operationalData) and update its return to include imageUrl: artworkUrl,
traits: { ...traits }, operationalData (or simply traits), eliminating title:
traits.title and description: traits.description.
components/waves/memes/submission/MemesArtResubmitAction.tsx (1)

115-131: aria-disabled only — verify no interactive side effects for disabled users.

The buttons set aria-disabled but not the native disabled attribute (presumably so the title/tooltip remains accessible). openModal correctly guards with if (disabled) return;, but note:

  • Keyboard users activating via Enter/Space will hit the onClick early-return path silently (no error feedback beyond the static inline reason).
  • The disabledReason paragraphs in menu/button variants aren't programmatically associated with the control (no aria-describedby), so assistive tech won't announce the reason.

Non-blocking, but consider wiring an aria-describedby={tooltipId}/inline-reason id when disabled is true.

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

In `@components/waves/memes/submission/MemesArtResubmitAction.tsx` around lines
115 - 131, The button currently uses aria-disabled but doesn't associate the
visible disabled reason with the control; create a stable id for the disabled
message (e.g., tooltipId) and render that id on the disabledReason paragraph in
the menu/button variants, then set aria-describedby={tooltipId} on the
interactive element when disabled is true; keep the existing openModal guard (if
(disabled) return) and preserve onOpenModal logic so keyboard activation still
short-circuits safely while assistive tech can announce the reason via
aria-describedby.
components/waves/memes/submission/MemesArtSubmissionStepContent.tsx (1)

60-84: Consider memoizing the media-type derivation.

mediaTypeInfo is computed in an IIFE on every render. It's cheap today, but memoizing keeps the contract stable for downstream components that receive previewRequiredMediaType/requiresPreviewImage/requiresPromoVideoOption as props and would otherwise see a fresh object reference on every render.

♻️ Proposed refactor
-  const mediaTypeInfo = (() => {
-    if (form.mediaSource === "upload" && form.selectedFile) {
-      return getResubmissionMediaTypeInfo({
-        mimeType: form.selectedFile.type,
-        fileName: form.selectedFile.name,
-      });
-    }
-    if (form.mediaSource === "upload" && form.existingMedia) {
-      return getResubmissionMediaTypeInfo({
-        mimeType: form.existingMedia.mimeType,
-        url: form.existingMedia.url,
-      });
-    }
-    if (form.mediaSource === "url" && form.isExternalMediaValid) {
-      return getResubmissionMediaTypeInfo({
-        mimeType: form.externalMediaMimeType,
-        url: form.externalMediaUrl,
-      });
-    }
-    return { label: null, isInteractive: false };
-  })();
+  const mediaTypeInfo = useMemo(() => {
+    if (form.mediaSource === "upload" && form.selectedFile) {
+      return getResubmissionMediaTypeInfo({
+        mimeType: form.selectedFile.type,
+        fileName: form.selectedFile.name,
+      });
+    }
+    if (form.mediaSource === "upload" && form.existingMedia) {
+      return getResubmissionMediaTypeInfo({
+        mimeType: form.existingMedia.mimeType,
+        url: form.existingMedia.url,
+      });
+    }
+    if (form.mediaSource === "url" && form.isExternalMediaValid) {
+      return getResubmissionMediaTypeInfo({
+        mimeType: form.externalMediaMimeType,
+        url: form.externalMediaUrl,
+      });
+    }
+    return { label: null, isInteractive: false };
+  }, [
+    form.mediaSource,
+    form.selectedFile,
+    form.existingMedia,
+    form.isExternalMediaValid,
+    form.externalMediaMimeType,
+    form.externalMediaUrl,
+  ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/memes/submission/MemesArtSubmissionStepContent.tsx` around
lines 60 - 84, mediaTypeInfo is recomputed on every render causing a new object
identity each time; wrap its computation in React.useMemo to memoize the
returned object and stabilize downstream props (use the same logic inside the
memo). Use form.mediaSource, form.selectedFile, form.existingMedia,
form.isExternalMediaValid, form.externalMediaMimeType and form.externalMediaUrl
as the memo dependency list so getResubmissionMediaTypeInfo is only recalculated
when inputs change, and leave previewRequiredMediaType, requiresPreviewImage and
requiresPromoVideoOption derived from the memoized mediaTypeInfo as before.
components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts (1)

91-110: Clarify: setArtworkUploaded(true) is a no-op.

The name suggests a setter, but uploaded=true early-returns while false resets upload/external state. Consider renaming (e.g. clearArtwork / resetUploadedArtwork) or at least documenting that true is intentionally a no-op, since SET_UPLOAD_MEDIA is the only path that sets artworkUploaded: true.

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

In `@components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts`
around lines 91 - 110, The function setArtworkUploaded in
useArtworkSubmissionMediaControls is misleading because setArtworkUploaded(true)
is a no-op while false clears state; rename it to resetUploadedArtwork or
clearArtwork (and update all callers) to reflect that it clears upload/external
state, or alternatively add an inline comment above setArtworkUploaded
documenting that true intentionally no-ops and only false triggers reset; when
renaming, update the function reference in the dependency array and replace
usages that call setArtworkUploaded(true)/false, and ensure the implementation
still calls updateExternalMediaState("", state.externalMedia.provider) for
mediaSource === "url" and dispatch({ type: "RESET_UPLOAD_MEDIA" }) otherwise.
components/waves/memes/submission/utils/submissionDraft.ts (2)

88-117: Clarify trait override for title/description.

If title/description are present in traitDefinitions (they are, given they're included in getInitialTraitsValues), the loop at Line 88–108 assigns them from metadata.get("title"|"description") first, then Line 110–114 re‑overrides them. This is not a bug (same metadata source), but the fallback to drop.title / drop.parts.at(0)?.content only fires when metadata lacks these keys. Consider skipping title/description in the loop or adding a brief comment noting the intentional re-assignment for the drop.* fallback path.

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

In `@components/waves/memes/submission/utils/submissionDraft.ts` around lines 88 -
117, The code currently sets title/description inside the
fieldDefinitions.forEach loop from metadata.get(...) and then unconditionally
reassigns traits.title and traits.description from metadata.get(...) ??
drop.title ?? ... which is redundant and obscures the intent; update the
fieldDefinitions processing in submissionDraft.ts to skip the "title" and
"description" fields (check definition.field === "title" || "description") so
they are only resolved by the explicit fallback assignment, or alternatively add
a short inline comment above the final assignments (traits.title /
traits.description) explaining that title/description are intentionally handled
after the loop to allow drop.title and drop.parts fallback when metadata is
absent; reference the loop over fieldDefinitions, metadata.get, and the final
traits.title/traits.description assignments when making the change.

64-72: Nit: booleanValue treats many unrelated strings as false.

Anything not matching true / 1 / yes becomes false, including stored values like on or locale-specific encodings. If the producer only ever writes "true"/"false", this is fine — otherwise consider rejecting ambiguous inputs back to the trait default (mirroring numberValue's fallback pattern). Non-blocking.

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

In `@components/waves/memes/submission/utils/submissionDraft.ts` around lines 64 -
72, The current booleanValue function coerces any non-true-like string to false;
change booleanValue to accept a fallback parameter (like numberValue) and return
that fallback for ambiguous inputs: normalize the input string, explicitly check
for true-like tokens ("true","1","yes","on") to return true, check for
false-like tokens ("false","0","no","off") to return false, and otherwise return
the provided fallback; update all call sites to pass the appropriate trait
default or leave a safe default when calling booleanValue.
components/waves/memes/submission/MemesArtSubmissionContainer.tsx (1)

241-302: Nit: shell description only applies to the non-preview step path.

shellDescription is non-null only when isResubmission && hasAcknowledgedResubmission && !replacementDrop, i.e. while filling the form. During the ResubmitAcknowledgement and ResubmitDeleteConfirmation substeps the shell has no description, which is intentional per the flow — just flagging so it's considered if those screens would benefit from their own header subtext. No change required.

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

In `@components/waves/memes/submission/MemesArtSubmissionContainer.tsx` around
lines 241 - 302, shellDescription is only set for the form path (when
isResubmission && hasAcknowledgedResubmission && !replacementDrop), so
ResubmitAcknowledgement and ResubmitDeleteConfirmation render without shell
description; if you want consistent header subtext either compute
shellDescription to cover those substeps (e.g., branch on isResubmission +
specific substep flags) or have ResubmitAcknowledgement and
ResubmitDeleteConfirmation accept a description prop and pass it through when
rendering inside MemesArtSubmissionShell; update the logic around
shellDescription (and the render of ResubmitAcknowledgement /
ResubmitDeleteConfirmation) in MemesArtSubmissionContainer to supply the desired
text.
🤖 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/memes/drops/MemesLeaderboardDrop.tsx`:
- Around line 272-279: The mobile path is missing a callback so deleting the
original after a resubmit doesn’t notify the leaderboard; add an optional
onSourceDropDeleted prop to the MemesLeaderboardDrop component signature and its
props interface, thread that prop through where the resubmit modal is opened
(use the existing isResubmitModalOpen state and setIsResubmitModalOpen setter),
and pass onSourceDropDeleted into the MemesArtSubmissionModal invocation so the
leaderboard can react (close/remove card or invalidate queries) when the
original drop is deleted.

In `@components/waves/drop/MemesDropFullscreenOverlay.tsx`:
- Around line 29-78: MemesDropFullscreenOverlay currently only closes via the
close button (props/state: isOpen, onClose, artworkMedia) so add an Escape key
handler and backdrop-click close and implement focus trap/restore: attach a
useEffect in MemesDropFullscreenOverlay that adds a keydown listener for
"Escape" to call onClose when isOpen is true, ensure the backdrop (the
motion.div or an outer backdrop element) handles clicks and only closes when
clicking outside the inner content area, and add focus management (trap focus
inside the overlay while open and restore focus to the previously focused
element on close) either by integrating a Dialog primitive (e.g., Headless UI
Dialog) or using a focus-trap utility inside the component. Ensure you clean up
listeners on unmount/close and that onClose is invoked consistently.

In `@components/waves/memes/submission/hooks/artworkSubmissionFormState.ts`:
- Around line 625-637: The reducer reduceResetUploadMedia currently nulls out
existingMedia (used to revert to the drop's original media) on
RESET_UPLOAD_MEDIA; change it to preserve existingMedia unless the user
explicitly opted out, i.e., remove the existingMedia: null assignment and only
clear selectedFile, uploadArtworkUrl (and set artworkUrl/artworkUploaded based
on shouldFallbackToExternal) so resubmission can revert to original media; if
nulling was intentional, add a test/comment in reduceResetUploadMedia explaining
the explicit opt-out behavior.

In
`@components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts`:
- Around line 77-89: The handleFileSelect callback currently ignores FileReader
errors/aborts; add reader.onerror and reader.onabort handlers alongside
reader.onloadend to dispatch an error state (or show a toast) so failures are
surfaced to the user. Specifically, in useArtworkSubmissionMediaControls'
handleFileSelect, keep the existing reader.onloadend dispatch for successful
reads but also dispatch a new action (e.g., type: "SET_UPLOAD_ERROR" or reuse
existing form invalid mechanism) with an error message when reader.onerror or
reader.onabort fires, and ensure any cleanup/reset of upload state (clearing
file/artworkUrl) happens in those handlers so the UI doesn't retain old state.

In `@components/waves/memes/submission/hooks/useResubmissionDelete.ts`:
- Around line 94-99: The finally block is returning which can swallow errors;
remove the return-from-finally pattern in the finally and instead reset
setIsDeletingOriginal(false) in the explicit success and error paths of the
async flow inside useResubmissionDelete; keep the early onClose() behavior (when
didClose is true) and avoid calling setIsDeletingOriginal after onClose (since
onClose may unmount the component), so move the setIsDeletingOriginal(false)
calls to the branches where the operation completes or fails and drop the
finally entirely, referencing the didClose flag, onClose() call, and
setIsDeletingOriginal state updater to locate the code to change.

In `@components/waves/memes/submission/MemesArtSubmissionShell.tsx`:
- Around line 30-39: The close button in the MemesArtSubmissionShell component
(the motion.button with onClick={onClose} and the FontAwesomeIcon) lacks an
explicit type, causing it to default to type="submit" and potentially submit
enclosing forms; update that motion.button element to include type="button" to
prevent accidental form submissions.

---

Nitpick comments:
In `@__tests__/components/memes/drops/MemesLeaderboardDrop.test.tsx`:
- Around line 175-201: The CommonDropdownItemsMobileWrapper test mock currently
renders children unconditionally which bypasses the Transition gating; update
the mock used in this test to only render children when the wrapper's isOpen
prop is true (i.e., change the mock component CommonDropdownItemsMobileWrapper
to return p.isOpen ? p.children : null) so the resubmit action is actually
behind the open/close transition and the test enforces the production behavior
in MemesLeaderboardDrop.

In `@__tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx`:
- Around line 101-112: The test is brittle because it spies on React.useState;
remove the useState spy in the "closes fullscreen when button clicked" test and
instead exercise the component through user interactions: render
MemesSingleWaveDropInfoPanel, simulate the user clicking the "Enter fullscreen
view" control (or whatever affordance opens fullscreen), then click the "Exit
fullscreen view" button via screen.getByRole and assert the visible outcome
(e.g., fullscreen element is removed, a particular aria-hidden/aria-expanded
state changes, or the Exit button is no longer in the document) rather than
asserting a mocked setState was called.

In
`@__tests__/components/waves/memes/submission/utils/resubmissionMediaType.test.ts`:
- Around line 3-31: Add unit tests covering the other branches of
getResubmissionMediaTypeInfo: add cases that pass mimeType "video/mp4" expecting
{ label: "Video", isInteractive: false }, mimeType "text/html" expecting {
label: "HTML", isInteractive: true }, an unknown mimeType like "image/png"
expecting { label: null, isInteractive: false }, and a URL with mixed case and
query/fragment like "https://x/y.GLB?v=1#frag" expecting { label: "GLB",
isInteractive: true }; place these new it(...) blocks alongside the existing
GLTF/GLB tests in the same test file to exercise extension stripping and
case-insensitive normalization in getResubmissionMediaTypeInfo.

In `@components/waves/drop/MemesDropDetailsSection.tsx`:
- Around line 39-41: Change the null-only check for artworkMedia to a defensive
truthy/nonnull check so undefined doesn’t slip through: update the
hasArtworkDownload expression (which currently uses artworkMedia !== null &&
fileInfo !== null) to ensure artworkMedia is present (e.g., artworkMedia != null
or a truthy check like Boolean(artworkMedia)) before checking fileInfo; keep
hasDownloads logic using previewImageData and promoVideoData unchanged besides
using consistent null/undefined-safe checks if desired.

In `@components/waves/drop/MemesDropFullscreenOverlay.tsx`:
- Line 18: The prop type for onClose in the MemesDropFullscreenOverlay component
is too specific; change the onClose signature from
MouseEventHandler<HTMLButtonElement> to a simple () => void in the
props/interface (reference: onClose and MemesDropFullscreenOverlay) so it can be
invoked from clicks, keyboard (ESC) or backdrop events, and remove the
now-unused MouseEventHandler import; update any callers to pass a zero-arg
function if needed.

In `@components/waves/drop/memesDropPanelTypes.ts`:
- Around line 1-14: MemesDropMedia currently defines mime_type (snake_case)
while MemesDropDownloadData and MemesDropFileInfo use camelCase (fileInfo, name,
extension); make the naming consistent by choosing one convention (e.g., convert
mime_type to mimeType or convert fileInfo to file_info), update the interfaces
(MemesDropMedia, MemesDropFileInfo, MemesDropDownloadData) accordingly, and then
update all usages of those symbols (references to mime_type/mimeType and
fileInfo/file_info) throughout the codebase to match the chosen convention so
types align with their origin (API payload vs UI model).

In `@components/waves/drop/MemesDropSummarySection.tsx`:
- Line 10: The prop type for the drop summary currently forces callers to
provide description via "readonly description: string;" even though the
component checks "{description && ...}" and treats it as optional; change the
property on the relevant type/interface to be optional (e.g. "readonly
description?: string" or "readonly description?: string | undefined") so callers
can omit it and the typing matches runtime usage; update the type declaration
where "readonly description" is defined (the interface/type used by
MemesDropSummarySection) accordingly.

In `@components/waves/memes/file-upload/components/FilePreview.tsx`:
- Around line 45-56: The isGLB detection currently checks effectiveMimeType and
file?.name extensions but misses the case where there's no file and the
resubmitted media URL ends with .glb/.gltf; update the isGLB logic in
FilePreview.tsx (symbols: effectiveMimeType, isGLB, url, file) to also inspect
the url's pathname/extension when file is undefined or file?.name is
absent—e.g., extract the extension from url (similar to the helper in
resubmissionMediaType.ts) and treat .glb/.gltf as GLB so resubmitted GLB URLs
with incorrect/blank mimeType still enter the GLB branch.

In `@components/waves/memes/submission/hooks/useArtworkSubmissionFormActions.ts`:
- Around line 48-51: The handler handleContinueFromArtwork currently calls
applyProfileDefaults again even though applyProfileDefaults is already invoked
in handleContinueFromTerms and reduceProfileDefaults is idempotent; remove the
duplicate applyProfileDefaults call from handleContinueFromArtwork so it only
dispatches SET_STEP to SubmissionStep.ADDITIONAL_INFO (leave
applyProfileDefaults in handleContinueFromTerms as the single source of default
application), referencing the functions handleContinueFromArtwork,
handleContinueFromTerms, applyProfileDefaults and reduceProfileDefaults when
making the change.
- Around line 123-134: The getSubmissionData function redundantly reassigns
traits.title and traits.description after spreading traits; remove the no-op
explicit entries and return traits directly from state.traits. Locate the
getSubmissionData useCallback (references: getSubmissionData, state, artworkUrl,
traits, operationalData) and update its return to include imageUrl: artworkUrl,
traits: { ...traits }, operationalData (or simply traits), eliminating title:
traits.title and description: traits.description.

In
`@components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts`:
- Around line 91-110: The function setArtworkUploaded in
useArtworkSubmissionMediaControls is misleading because setArtworkUploaded(true)
is a no-op while false clears state; rename it to resetUploadedArtwork or
clearArtwork (and update all callers) to reflect that it clears upload/external
state, or alternatively add an inline comment above setArtworkUploaded
documenting that true intentionally no-ops and only false triggers reset; when
renaming, update the function reference in the dependency array and replace
usages that call setArtworkUploaded(true)/false, and ensure the implementation
still calls updateExternalMediaState("", state.externalMedia.provider) for
mediaSource === "url" and dispatch({ type: "RESET_UPLOAD_MEDIA" }) otherwise.

In `@components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts`:
- Around line 25-43: Update the ArtworkSubmissionData type so
externalMedia.mimeType stays narrowed to InteractiveMediaMimeType while
existingMedia.mimeType is a plain string: change externalMedia?: { url: string;
mimeType: InteractiveMediaMimeType } | undefined and existingMedia?: { url:
string; mimeType: string } | undefined; leave all other fields and the
normalized existingUrl logic untouched so callers that require interactive-only
mime types still get type-level guarantees while resubmission passthroughs
accept arbitrary existingMedia mime strings.

In `@components/waves/memes/submission/hooks/useResubmissionDelete.ts`:
- Around line 59-63: In useResubmissionDelete change the order around the auth
call and loading flag: call requestAuth() and await its result before calling
setIsDeletingOriginal(true) so the UI loading state is only set if auth
succeeds; specifically, move the requestAuth() invocation earlier (or
conditionalize setIsDeletingOriginal) around the existing requestAuth /
setIsDeletingOriginal lines to avoid the brief flash when requestAuth is
cancelled.

In `@components/waves/memes/submission/MemesArtResubmitAction.tsx`:
- Around line 115-131: The button currently uses aria-disabled but doesn't
associate the visible disabled reason with the control; create a stable id for
the disabled message (e.g., tooltipId) and render that id on the disabledReason
paragraph in the menu/button variants, then set aria-describedby={tooltipId} on
the interactive element when disabled is true; keep the existing openModal guard
(if (disabled) return) and preserve onOpenModal logic so keyboard activation
still short-circuits safely while assistive tech can announce the reason via
aria-describedby.

In `@components/waves/memes/submission/MemesArtSubmissionContainer.tsx`:
- Around line 241-302: shellDescription is only set for the form path (when
isResubmission && hasAcknowledgedResubmission && !replacementDrop), so
ResubmitAcknowledgement and ResubmitDeleteConfirmation render without shell
description; if you want consistent header subtext either compute
shellDescription to cover those substeps (e.g., branch on isResubmission +
specific substep flags) or have ResubmitAcknowledgement and
ResubmitDeleteConfirmation accept a description prop and pass it through when
rendering inside MemesArtSubmissionShell; update the logic around
shellDescription (and the render of ResubmitAcknowledgement /
ResubmitDeleteConfirmation) in MemesArtSubmissionContainer to supply the desired
text.

In `@components/waves/memes/submission/MemesArtSubmissionShell.tsx`:
- Around line 27-39: The motion wrappers around the header and close button in
MemesArtSubmissionShell (motion.h3 and motion.button) provide no animation props
and should be replaced with plain h3 and button elements (preserve title,
className, aria-label, onClose handler and inner FontAwesomeIcon) to avoid
framer-motion overhead; after replacing, remove any unused framer-motion imports
(e.g., motion) from the file to clean up unused dependencies.

In `@components/waves/memes/submission/MemesArtSubmissionStepContent.tsx`:
- Around line 60-84: mediaTypeInfo is recomputed on every render causing a new
object identity each time; wrap its computation in React.useMemo to memoize the
returned object and stabilize downstream props (use the same logic inside the
memo). Use form.mediaSource, form.selectedFile, form.existingMedia,
form.isExternalMediaValid, form.externalMediaMimeType and form.externalMediaUrl
as the memo dependency list so getResubmissionMediaTypeInfo is only recalculated
when inputs change, and leave previewRequiredMediaType, requiresPreviewImage and
requiresPromoVideoOption derived from the memoized mediaTypeInfo as before.

In `@components/waves/memes/submission/utils/submissionDraft.ts`:
- Around line 88-117: The code currently sets title/description inside the
fieldDefinitions.forEach loop from metadata.get(...) and then unconditionally
reassigns traits.title and traits.description from metadata.get(...) ??
drop.title ?? ... which is redundant and obscures the intent; update the
fieldDefinitions processing in submissionDraft.ts to skip the "title" and
"description" fields (check definition.field === "title" || "description") so
they are only resolved by the explicit fallback assignment, or alternatively add
a short inline comment above the final assignments (traits.title /
traits.description) explaining that title/description are intentionally handled
after the loop to allow drop.title and drop.parts fallback when metadata is
absent; reference the loop over fieldDefinitions, metadata.get, and the final
traits.title/traits.description assignments when making the change.
- Around line 64-72: The current booleanValue function coerces any non-true-like
string to false; change booleanValue to accept a fallback parameter (like
numberValue) and return that fallback for ambiguous inputs: normalize the input
string, explicitly check for true-like tokens ("true","1","yes","on") to return
true, check for false-like tokens ("false","0","no","off") to return false, and
otherwise return the provided fallback; update all call sites to pass the
appropriate trait default or leave a safe default when calling booleanValue.
🪄 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: fae2a9c7-b6d7-4ecc-b771-efa4a57ad377

📥 Commits

Reviewing files that changed from the base of the PR and between e1bb24f and b3ae346.

📒 Files selected for processing (40)
  • __tests__/components/memes/drops/MemesLeaderboardDrop.test.tsx
  • __tests__/components/utils/select/dropdown/CommonDropdownItemsMobileWrapper.test.tsx
  • __tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx
  • __tests__/components/waves/memes/submission/MemesArtResubmitAction.test.tsx
  • __tests__/components/waves/memes/submission/MemesArtSubmissionContainer.test.tsx
  • __tests__/components/waves/memes/submission/hooks/useArtworkSubmissionForm.test.ts
  • __tests__/components/waves/memes/submission/utils/resubmissionMediaType.test.ts
  • __tests__/components/waves/memes/submission/utils/submissionDraft.test.ts
  • components/memes/drops/MemesLeaderboardDrop.tsx
  • components/utils/select/dropdown/CommonDropdownItemsMobileWrapper.tsx
  • components/waves/drop/MemesDropArtworkHero.tsx
  • components/waves/drop/MemesDropDetailsSection.tsx
  • components/waves/drop/MemesDropFullscreenOverlay.tsx
  • components/waves/drop/MemesDropSummarySection.tsx
  • components/waves/drop/MemesSingleWaveDrop.tsx
  • components/waves/drop/MemesSingleWaveDropInfoPanel.tsx
  • components/waves/drop/memesDropPanelTypes.ts
  • components/waves/leaderboard/drops/WaveLeaderboardDrop.tsx
  • components/waves/memes/MemesArtSubmissionFile.tsx
  • components/waves/memes/MemesArtSubmissionModal.tsx
  • components/waves/memes/file-upload/components/FilePreview.tsx
  • components/waves/memes/file-upload/reducers/types.ts
  • components/waves/memes/submission/MemesArtResubmitAction.tsx
  • components/waves/memes/submission/MemesArtSubmissionContainer.tsx
  • components/waves/memes/submission/MemesArtSubmissionShell.tsx
  • components/waves/memes/submission/MemesArtSubmissionStepContent.tsx
  • components/waves/memes/submission/ResubmitAcknowledgement.tsx
  • components/waves/memes/submission/ResubmitDeleteConfirmation.tsx
  • components/waves/memes/submission/hooks/artworkSubmissionFormState.ts
  • components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts
  • components/waves/memes/submission/hooks/useArtworkSubmissionFormActions.ts
  • components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts
  • components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts
  • components/waves/memes/submission/hooks/useResubmissionDelete.ts
  • components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.tsx
  • components/waves/memes/submission/steps/AdditionalInfoStep.tsx
  • components/waves/memes/submission/steps/ArtworkStep.tsx
  • components/waves/memes/submission/utils/buildPreviewDrop.ts
  • components/waves/memes/submission/utils/resubmissionMediaType.ts
  • components/waves/memes/submission/utils/submissionDraft.ts

Comment thread components/memes/drops/MemesLeaderboardDrop.tsx
Comment thread components/waves/drop/MemesDropFullscreenOverlay.tsx
Comment thread components/waves/memes/submission/hooks/useResubmissionDelete.ts Outdated
Comment thread components/waves/memes/submission/MemesArtSubmissionShell.tsx
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>
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: 2

🧹 Nitpick comments (3)
__tests__/components/waves/memes/submission/hooks/useArtworkSubmissionForm.test.ts (1)

223-223: Nit: restore global.FileReader across tests.

Several tests (lines 223, 264, 327, 355, 396, 531) assign global.FileReader = MockFileReader as any but never restore it. Because each consumer re-assigns it, this works today, but any future test that relies on the real jsdom FileReader (or runs after these without re-assigning) would inherit the last mock. Consider capturing and restoring in an afterEach to keep tests hermetic.

♻️ Suggested harness tweak
+  const OriginalFileReader = global.FileReader;
   afterEach(() => {
     jest.clearAllMocks();
+    global.FileReader = OriginalFileReader;
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@__tests__/components/waves/memes/submission/hooks/useArtworkSubmissionForm.test.ts`
at line 223, Tests assign global.FileReader = MockFileReader but never restore
it; capture the original global.FileReader before overriding (e.g., const
originalFileReader = global.FileReader) and restore it in an afterEach hook so
each test resets the environment. Update the file to save originalFileReader,
assign global.FileReader = MockFileReader in the individual tests or a
beforeEach, and ensure afterEach sets global.FileReader = originalFileReader
(and deletes it if it was undefined) to keep tests hermetic; reference
MockFileReader, global.FileReader, and add an afterEach to perform the restore.
components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts (1)

27-82: LGTM — clean decomposition and react-query wiring.

The split between profileIdentityDefaults (used for lazy reducer init) and profileDefaults (passed to form actions for deferred bio hydration) is consistent with the test that manual about_artist is not overwritten after the CIC fetch resolves. Query is correctly gated by !isDraftInitialized && normalizedProfileHandle.length > 0 so a draft-initialized flow makes no network request.

Minor nit: the initialStateInput memo on lines 62–68 is used only as the third argument to useReducer, whose lazy initializer runs exactly once at mount, so the memoization provides no benefit — a plain object literal would be equivalent. Feel free to leave as-is for consistency with the other memos.

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

In `@components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts` around
lines 27 - 82, The useMemo for initialStateInput in useArtworkSubmissionForm is
unnecessary because it's only used as the lazy initializer argument to
useReducer (createInitialState runs once at mount); replace the useMemo-created
value with a plain object literal passed directly as the third argument to
useReducer (i.e., eliminate the initialStateInput constant and pass {
initialDraft, profileDefaults: profileIdentityDefaults } into
useReducer(createInitialState)), keeping references to formReducer and
createInitialState intact.
components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts (1)

128-147: Rename setArtworkUploaded to clarify its asymmetric behavior.

The function only performs work when called with false and silently returns when called with true. This asymmetry contradicts the naming convention of a boolean setter, which typically implies symmetric behavior. Callers expecting setArtworkUploaded(true) to mark an upload as complete will be silently ignored.

Consider renaming to clearArtworkUpload() (parameterless) or resetArtworkUpload() to accurately convey that this function only resets state. Update the call site at useArtworkSubmissionForm.ts line 106 accordingly.

Suggested refactor
-  const setArtworkUploaded = useCallback(
-    (uploaded: boolean) => {
-      if (uploaded) {
-        return;
-      }
-
-      if (state.mediaSource === "url") {
-        updateExternalMediaState("", state.externalMedia.provider);
-        return;
-      }
-
-      dispatch({ type: "RESET_UPLOAD_MEDIA" });
-    },
+  const clearArtworkUpload = useCallback(() => {
+    if (state.mediaSource === "url") {
+      updateExternalMediaState("", state.externalMedia.provider);
+      return;
+    }
+    dispatch({ type: "RESET_UPLOAD_MEDIA" });
+  },
     [
       dispatch,
       state.externalMedia.provider,
       state.mediaSource,
       updateExternalMediaState,
     ]
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts`
around lines 128 - 147, The helper setArtworkUploaded has asymmetric behavior
(it only acts when called with false) and should be renamed to convey that it
resets/clears upload state; rename setArtworkUploaded to a parameterless name
like clearArtworkUpload or resetArtworkUpload, remove the boolean parameter, and
update its implementation to always perform the reset logic (handle
state.mediaSource === "url" by calling updateExternalMediaState("",
state.externalMedia.provider) and otherwise dispatch({ type:
"RESET_UPLOAD_MEDIA" })); then update all call sites (notably the caller in
useArtworkSubmissionForm where setArtworkUploaded(...) is invoked) to call the
new clear/reset function with no arguments.
🤖 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/memes/drops/MemesLeaderboardDrop.tsx`:
- Around line 73-85: The openResubmitAfterMenuClose flag can persist and trigger
the resubmit modal unexpectedly; update the component to defensively clear that
flag when the menu becomes active again or on unmount: add a useEffect that
watches isActive and if isActive === true calls
setOpenResubmitAfterMenuClose(false), and ensure the effect returns a cleanup
that also clears setOpenResubmitAfterMenuClose(false) to cover unmounts; this
complements the existing openResubmitAfterMobileMenuCloses and
handleMobileMenuAfterLeave logic (also referencing setIsActive and
CommonDropdownItemsMobileWrapper) so the deferred-open flag never survives a
new/opened menu or component teardown.

In `@components/waves/drop/MemesSingleWaveDropInfoPanel.tsx`:
- Around line 99-149: The component defines isFullscreen and only a
handleCloseFullscreen (which sets isFullscreen false) while passing isFullscreen
to MemesDropFullscreenOverlay, so the overlay can never open; either remove the
unused fullscreen state/overlay wiring (delete isFullscreen,
handleCloseFullscreen, and MemesDropFullscreenOverlay) or implement and wire an
open handler: add handleOpenFullscreen that calls setIsFullscreen(true) and pass
it as a prop (e.g., onOpenFullscreen) into MemesDropArtworkHero so that the hero
can trigger fullscreen; update any prop names consistently between
MemesSingleWaveDropInfoPanel (isFullscreen, handleOpenFullscreen,
handleCloseFullscreen) and the child component to ensure the overlay can
actually be opened.

---

Nitpick comments:
In
`@__tests__/components/waves/memes/submission/hooks/useArtworkSubmissionForm.test.ts`:
- Line 223: Tests assign global.FileReader = MockFileReader but never restore
it; capture the original global.FileReader before overriding (e.g., const
originalFileReader = global.FileReader) and restore it in an afterEach hook so
each test resets the environment. Update the file to save originalFileReader,
assign global.FileReader = MockFileReader in the individual tests or a
beforeEach, and ensure afterEach sets global.FileReader = originalFileReader
(and deletes it if it was undefined) to keep tests hermetic; reference
MockFileReader, global.FileReader, and add an afterEach to perform the restore.

In `@components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts`:
- Around line 27-82: The useMemo for initialStateInput in
useArtworkSubmissionForm is unnecessary because it's only used as the lazy
initializer argument to useReducer (createInitialState runs once at mount);
replace the useMemo-created value with a plain object literal passed directly as
the third argument to useReducer (i.e., eliminate the initialStateInput constant
and pass { initialDraft, profileDefaults: profileIdentityDefaults } into
useReducer(createInitialState)), keeping references to formReducer and
createInitialState intact.

In
`@components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts`:
- Around line 128-147: The helper setArtworkUploaded has asymmetric behavior (it
only acts when called with false) and should be renamed to convey that it
resets/clears upload state; rename setArtworkUploaded to a parameterless name
like clearArtworkUpload or resetArtworkUpload, remove the boolean parameter, and
update its implementation to always perform the reset logic (handle
state.mediaSource === "url" by calling updateExternalMediaState("",
state.externalMedia.provider) and otherwise dispatch({ type:
"RESET_UPLOAD_MEDIA" })); then update all call sites (notably the caller in
useArtworkSubmissionForm where setArtworkUploaded(...) is invoked) to call the
new clear/reset function with no arguments.
🪄 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: fff94ced-8b10-41e0-bcbc-3cfe50b80c9a

📥 Commits

Reviewing files that changed from the base of the PR and between b3ae346 and 3ed458d.

📒 Files selected for processing (22)
  • __tests__/components/memes/drops/MemesLeaderboardDrop.test.tsx
  • __tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx
  • __tests__/components/waves/leaderboard/drops/WaveLeaderboardDrop.test.tsx
  • __tests__/components/waves/leaderboard/drops/WaveLeaderboardDrops.test.tsx
  • __tests__/components/waves/memes/MemesArtSubmissionFile.test.tsx
  • __tests__/components/waves/memes/submission/hooks/useArtworkSubmissionForm.test.ts
  • __tests__/components/waves/memes/submission/steps/ArtworkStep.test.tsx
  • components/memes/drops/MemesLeaderboardDrop.tsx
  • components/waves/drop/MemesDropFullscreenOverlay.tsx
  • components/waves/drop/MemesSingleWaveDropInfoPanel.tsx
  • components/waves/leaderboard/drops/WaveLeaderboardDrop.tsx
  • components/waves/leaderboard/drops/WaveLeaderboardDrops.tsx
  • components/waves/memes/MemesArtSubmissionFile.tsx
  • components/waves/memes/file-upload/reducers/types.ts
  • components/waves/memes/submission/MemesArtSubmissionShell.tsx
  • components/waves/memes/submission/MemesArtSubmissionStepContent.tsx
  • components/waves/memes/submission/hooks/artworkSubmissionFormState.ts
  • components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts
  • components/waves/memes/submission/hooks/useArtworkSubmissionMediaControls.ts
  • components/waves/memes/submission/hooks/useResubmissionDelete.ts
  • components/waves/memes/submission/steps/ArtworkStep.tsx
  • components/waves/memes/submission/utils/buildPreviewDrop.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/components/waves/memes/submission/steps/ArtworkStep.test.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
  • components/waves/leaderboard/drops/WaveLeaderboardDrop.tsx
  • components/waves/memes/submission/utils/buildPreviewDrop.ts
  • components/waves/memes/MemesArtSubmissionFile.tsx
  • components/waves/memes/submission/MemesArtSubmissionShell.tsx
  • tests/components/memes/drops/MemesLeaderboardDrop.test.tsx
  • tests/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx
  • components/waves/memes/file-upload/reducers/types.ts
  • components/waves/drop/MemesDropFullscreenOverlay.tsx
  • components/waves/memes/submission/hooks/useResubmissionDelete.ts
  • components/waves/memes/submission/MemesArtSubmissionStepContent.tsx
  • components/waves/memes/submission/hooks/artworkSubmissionFormState.ts

Comment thread components/memes/drops/MemesLeaderboardDrop.tsx Outdated
Comment thread components/waves/drop/MemesSingleWaveDropInfoPanel.tsx
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

🧹 Nitpick comments (1)
components/waves/drop/MemesDropFullscreenOverlay.tsx (1)

143-150: Use tw-flex-1 on the image container instead of tw-h-full.

The <dialog> is a flex-col with tw-h-full tw-max-h-full, and this image wrapper also claims tw-h-full alongside the (auto-sized) header row. In a column flex container, h-full on a child resolves to 100% of the parent height, so the two children's intrinsic heights can sum to more than the dialog height, which may push the image out of the viewport or rely on flex shrinking to hide the overflow. Using tw-flex-1 tw-min-h-0 is the idiomatic pattern that reserves exactly the remaining vertical space for the image while keeping tw-object-contain working correctly.

♻️ Proposed fix
-                  <div className="tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center">
+                  <div className="tw-flex tw-min-h-0 tw-w-full tw-flex-1 tw-items-center tw-justify-center">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/drop/MemesDropFullscreenOverlay.tsx` around lines 143 - 150,
The image container div currently uses "tw-h-full" which in a column flex parent
can exceed available space; update the wrapper (the div that wraps the <img> in
MemesDropFullscreenOverlay) to use "tw-flex-1 tw-min-h-0" instead of "tw-h-full"
so the container takes only the remaining flex space and allows the <img> (with
tw-object-contain) to size correctly without overflowing the dialog.
🤖 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/waves/drop/MemesDropFullscreenOverlay.tsx`:
- Around line 104-107: The code currently coerces a missing drop.rank into 1
when rendering SingleWaveDropPosition, which misrepresents unranked drops;
update the prop passed to SingleWaveDropPosition to forward the actual nullable
value (e.g., pass drop.rank as-is or drop.rank ?? null) so the component
receives number | null and can render the neutral/unranked state; ensure no
fallback to 1 remains and adjust any surrounding logic that assumed a non-null
rank on SingleWaveDropPosition.

---

Nitpick comments:
In `@components/waves/drop/MemesDropFullscreenOverlay.tsx`:
- Around line 143-150: The image container div currently uses "tw-h-full" which
in a column flex parent can exceed available space; update the wrapper (the div
that wraps the <img> in MemesDropFullscreenOverlay) to use "tw-flex-1
tw-min-h-0" instead of "tw-h-full" so the container takes only the remaining
flex space and allows the <img> (with tw-object-contain) to size correctly
without overflowing the dialog.
🪄 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: 782ca3fe-dc25-49f9-9981-e67d0975b9fd

📥 Commits

Reviewing files that changed from the base of the PR and between 3ed458d and da21058.

📒 Files selected for processing (6)
  • __tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx
  • components/memes/drops/MemesLeaderboardDrop.tsx
  • components/waves/drop/MemesDropArtworkHero.tsx
  • components/waves/drop/MemesDropFullscreenOverlay.tsx
  • components/waves/drop/MemesSingleWaveDropInfoPanel.tsx
  • components/waves/drop/memesDropPanelTypes.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • components/waves/drop/MemesDropArtworkHero.tsx
  • components/memes/drops/MemesLeaderboardDrop.tsx
  • components/waves/drop/memesDropPanelTypes.ts
  • tests/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx

Comment thread components/waves/drop/MemesDropFullscreenOverlay.tsx Outdated
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

@simo6529
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

🧹 Nitpick comments (1)
__tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx (1)

167-178: Avoid coupling this test to useState hook order.

This test relies on the first React.useState call belonging to fullscreen state. Adding another state hook before it would break the test for the wrong reason. The component already has two state hooks (line 31-32), making this fragile. Use the existing "Open fullscreen view" button instead to test the actual UI flow.

🧪 Proposed test rewrite
   it("closes fullscreen when button clicked", async () => {
-    const setState = jest.fn();
-    const spy = jest
-      .spyOn(React, "useState")
-      .mockImplementationOnce(() => [true, setState]);
     render(<MemesSingleWaveDropInfoPanel drop={baseDrop} wave={null} />);
+
+    await userEvent.click(
+      screen.getByRole("button", { name: "Open fullscreen view" })
+    );
+
+    expect(
+      screen.getByRole("button", { name: "Exit fullscreen view" })
+    ).toBeInTheDocument();
+
     await userEvent.click(
       screen.getByRole("button", { name: "Exit fullscreen view" })
     );
-    expect(setState).toHaveBeenCalledWith(false);
-    spy.mockRestore();
+
+    expect(
+      screen.queryByRole("button", { name: "Exit fullscreen view" })
+    ).not.toBeInTheDocument();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx` around
lines 167 - 178, The test "closes fullscreen when button clicked" is fragile
because it spies on React.useState and assumes the first hook is the fullscreen
state; remove the useState spy and instead drive the UI: click the existing
"Open fullscreen view" button to open fullscreen in the rendered
MemesSingleWaveDropInfoPanel, then click the "Exit fullscreen view" button and
assert the fullscreen UI is dismissed (e.g., the fullscreen element or exit
button is no longer in the document). Stop using the setState mock and
React.useState spy; rely on userEvent interactions with the "Open fullscreen
view" and "Exit fullscreen view" buttons to verify behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@__tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx`:
- Around line 167-178: The test "closes fullscreen when button clicked" is
fragile because it spies on React.useState and assumes the first hook is the
fullscreen state; remove the useState spy and instead drive the UI: click the
existing "Open fullscreen view" button to open fullscreen in the rendered
MemesSingleWaveDropInfoPanel, then click the "Exit fullscreen view" button and
assert the fullscreen UI is dismissed (e.g., the fullscreen element or exit
button is no longer in the document). Stop using the setState mock and
React.useState spy; rely on userEvent interactions with the "Open fullscreen
view" and "Exit fullscreen view" buttons to verify behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9275fa07-9ca2-4619-a7fc-4ccf8885ff8b

📥 Commits

Reviewing files that changed from the base of the PR and between da21058 and 8d8291b.

📒 Files selected for processing (2)
  • __tests__/components/waves/drop/MemesSingleWaveDropInfoPanel.test.tsx
  • components/waves/drop/MemesDropFullscreenOverlay.tsx

@simo6529 simo6529 merged commit 1f33fa2 into main Apr 20, 2026
8 checks passed
@simo6529 simo6529 deleted the edit-memes-submission branch April 20, 2026 11:06
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