From 03bff76af661423a0e82efd3717a82296ebfa193 Mon Sep 17 00:00:00 2001 From: ragnep Date: Wed, 29 Oct 2025 09:19:49 +0200 Subject: [PATCH 1/4] wip Signed-off-by: ragnep --- codex/STATE.md | 1 + codex/tickets/TKT-0021.md | 37 ++++++++++++++++++++++++++++++ components/waves/Waves.tsx | 24 +++++++++++-------- components/waves/list/WaveItem.tsx | 26 ++++++++++----------- scripts/dev-with-fallback.cjs | 6 ++++- 5 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 codex/tickets/TKT-0021.md diff --git a/codex/STATE.md b/codex/STATE.md index 87fcc94abf..92b36d0425 100644 --- a/codex/STATE.md +++ b/codex/STATE.md @@ -24,6 +24,7 @@ This table is the single source of truth for active and historical tickets. Keep | TKT-0018 | Clean up UrlGuardHooks export | In-Progress | P1 | openai-assistant | — | 2025-10-28 | | TKT-0019 | Make Wave card fully clickable | In-Progress | P1 | openai-assistant | — | 2025-10-27 | | TKT-0020 | Harden CustomTooltip positioning robustness | In-Progress | P1 | openai-assistant | — | 2025-10-28 | +| TKT-0021 | Restore Discover create wave modal | In-Progress | P1 | openai-assistant | — | 2025-10-29 | ## Usage Guidelines diff --git a/codex/tickets/TKT-0021.md b/codex/tickets/TKT-0021.md new file mode 100644 index 0000000000..3b3491a0e8 --- /dev/null +++ b/codex/tickets/TKT-0021.md @@ -0,0 +1,37 @@ +--- +created: 2025-10-29 +id: TKT-0021 +owner: openai-assistant +priority: P1 +status: In-Progress +title: Restore Discover create wave modal +--- + +## Context + +> The Discover page's `Create Wave` CTA should open the modal overlay, but a recent redesign causes the main content to swap to the inline creation form instead. Need to restore the modal behavior without regressing the existing direct message modal or in-app handling. + +## Plan + +- [ ] Reintroduce the wave modal in `Waves` when running on web while keeping app behavior intact. +- [ ] Ensure the inline view is only used where appropriate and URL query strings still toggle modal visibility. +- [ ] Verify both `Create Wave` and `Create DM` buttons behave correctly on Discover. + +## Acceptance + +- [ ] Clicking `Create Wave` on Discover opens the modal overlay and leaves the list view visible. +- [ ] Closing/success from the modal clears the `create` query parameter and returns to the list. +- [ ] `Create DM` button continues to open its modal. +- [ ] `npm run lint`, `npm run type-check`, and targeted tests succeed when run. + +## Links + +- Primary PR: _(add when available)_ +- Follow-ups: _(reference additional tickets or TODO items)_ + +## Log + +- 2025-10-29T07:06:06Z – Created ticket and triaged regression scope. +- 2025-10-29T07:08:01Z – Wired `CreateWaveModal` back into `Waves` for web, keeping inline create for app, and ran `npm run lint`. +- 2025-10-29T07:10:05Z – Confirmed `npm run type-check` and `npm run test:cov:changed` pass (pre-existing console warnings only). +- 2025-10-29T07:15:11Z – Resolved nested link warning by making the card container a focusable div with keyboard/middle-click routing and re-ran checks. diff --git a/components/waves/Waves.tsx b/components/waves/Waves.tsx index 908a385d6d..b231c368f8 100644 --- a/components/waves/Waves.tsx +++ b/components/waves/Waves.tsx @@ -8,6 +8,7 @@ import { useRouter } from "next/navigation"; import { useCallback, useMemo, type JSX } from "react"; import CreateDirectMessage from "./create-dm/CreateDirectMessage"; import CreateWave from "./create-wave/CreateWave"; +import CreateWaveModal from "./create-wave/CreateWaveModal"; import WavesList from "./list/WavesList"; import ConnectWallet from "@/components/common/ConnectWallet"; import { getWavesBaseRoute } from "@/helpers/navigation.helpers"; @@ -45,6 +46,7 @@ export default function Waves({ openDirectMessage, close, isApp, + isWaveModalOpen, } = useCreateModalState(); useSetTitle(documentTitle); @@ -173,21 +175,25 @@ export default function Waves({ ), }; - const activeView = - isApp || viewMode === WavesViewMode.CREATE - ? viewMode - : WavesViewMode.VIEW; + const activeView = isApp ? viewMode : WavesViewMode.VIEW; return (
{components[activeView]} {!isApp && connectedProfile && ( - + <> + + + )}
); diff --git a/components/waves/list/WaveItem.tsx b/components/waves/list/WaveItem.tsx index 9cac87debd..1c5d55efa0 100644 --- a/components/waves/list/WaveItem.tsx +++ b/components/waves/list/WaveItem.tsx @@ -43,16 +43,14 @@ const CARD_INTERACTIVE_CLASSES = type CardContainerProps = { readonly isInteractive: boolean; - readonly href?: string; readonly ariaLabel?: string; - readonly onClick?: (event: MouseEvent) => void; - readonly onKeyDown?: (event: KeyboardEvent) => void; + readonly onClick?: (event: MouseEvent) => void; + readonly onKeyDown?: (event: KeyboardEvent) => void; readonly children: ReactNode; }; function CardContainer({ isInteractive, - href, ariaLabel, onClick, onKeyDown, @@ -62,19 +60,18 @@ function CardContainer({ isInteractive ? CARD_INTERACTIVE_CLASSES : "" }`; - if (isInteractive && href) { + if (isInteractive) { return ( - {children} - + ); } @@ -213,7 +210,7 @@ export default function WaveItem({ } const handleCardClick = useCallback( - (event: MouseEvent) => { + (event: MouseEvent) => { if (!waveHref) { return; } @@ -221,6 +218,10 @@ export default function WaveItem({ if (shouldSkipNavigation(target, event.currentTarget)) { return; } + if (event.button === 1 || event.metaKey || event.ctrlKey) { + window.open(waveHref, "_blank", "noopener,noreferrer"); + return; + } if (!event.defaultPrevented) { event.preventDefault(); router.push(waveHref); @@ -230,7 +231,7 @@ export default function WaveItem({ ); const handleCardKeyDown = useCallback( - (event: KeyboardEvent) => { + (event: KeyboardEvent) => { if (!waveHref || event.target !== event.currentTarget) { return; } @@ -245,7 +246,6 @@ export default function WaveItem({ return ( Date: Wed, 29 Oct 2025 10:06:48 +0200 Subject: [PATCH 2/4] wip Signed-off-by: ragnep --- .../waves/list/WaveItemDropped.test.tsx | 1 + codex/tickets/TKT-0021.md | 1 + components/waves/list/WaveItem.tsx | 89 +++++++++++++------ components/waves/list/WaveItemDropped.tsx | 48 +++++++++- 4 files changed, 107 insertions(+), 32 deletions(-) diff --git a/__tests__/components/waves/list/WaveItemDropped.test.tsx b/__tests__/components/waves/list/WaveItemDropped.test.tsx index 85ef298203..4c1dc8f244 100644 --- a/__tests__/components/waves/list/WaveItemDropped.test.tsx +++ b/__tests__/components/waves/list/WaveItemDropped.test.tsx @@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'; import WaveItemDropped from '@/components/waves/list/WaveItemDropped'; jest.mock('next/link', () => ({ __esModule: true, default: ({ href, children }: any) => {children} })); +jest.mock('next/navigation', () => ({ useRouter: () => ({ push: jest.fn() }) })); jest.mock('@/helpers/Helpers', () => ({ numberWithCommas: (n: number) => n.toString() })); jest.mock('@/helpers/image.helpers', () => ({ getScaledImageUri: (u: string) => `scaled-${u}`, ImageScale: { W_AUTO_H_50: 'scale' } })); diff --git a/codex/tickets/TKT-0021.md b/codex/tickets/TKT-0021.md index 3b3491a0e8..a684922ab0 100644 --- a/codex/tickets/TKT-0021.md +++ b/codex/tickets/TKT-0021.md @@ -35,3 +35,4 @@ title: Restore Discover create wave modal - 2025-10-29T07:08:01Z – Wired `CreateWaveModal` back into `Waves` for web, keeping inline create for app, and ran `npm run lint`. - 2025-10-29T07:10:05Z – Confirmed `npm run type-check` and `npm run test:cov:changed` pass (pre-existing console warnings only). - 2025-10-29T07:15:11Z – Resolved nested link warning by making the card container a focusable div with keyboard/middle-click routing and re-ran checks. +- 2025-10-29T07:48:13Z – Restored outer `` semantics with internal button replacements, updated contributor avatars to use buttons, adjusted tests, and reran lint/type-check/tests. diff --git a/components/waves/list/WaveItem.tsx b/components/waves/list/WaveItem.tsx index 1c5d55efa0..a79efbccbb 100644 --- a/components/waves/list/WaveItem.tsx +++ b/components/waves/list/WaveItem.tsx @@ -43,14 +43,16 @@ const CARD_INTERACTIVE_CLASSES = type CardContainerProps = { readonly isInteractive: boolean; + readonly href?: string; readonly ariaLabel?: string; - readonly onClick?: (event: MouseEvent) => void; - readonly onKeyDown?: (event: KeyboardEvent) => void; + readonly onClick?: (event: MouseEvent) => void; + readonly onKeyDown?: (event: KeyboardEvent) => void; readonly children: ReactNode; }; function CardContainer({ isInteractive, + href, ariaLabel, onClick, onKeyDown, @@ -58,20 +60,20 @@ function CardContainer({ }: CardContainerProps) { const className = `${CARD_BASE_CLASSES} ${ isInteractive ? CARD_INTERACTIVE_CLASSES : "" - }`; + } tw-no-underline`; - if (isInteractive) { + if (isInteractive && href) { return ( -
{children} -
+ ); } @@ -173,6 +175,36 @@ export default function WaveItem({ "tw-text-sm tw-font-semibold tw-text-white desktop-hover:group-hover/author:tw-text-iron-400 tw-transition tw-duration-300 tw-ease-out"; const staticAuthorNameClass = "tw-text-sm tw-font-semibold tw-text-white"; + const handleAuthorClick = useCallback( + (event: MouseEvent) => { + if (!authorHref) { + return; + } + if (event.metaKey || event.ctrlKey) { + event.preventDefault(); + event.stopPropagation(); + window.open(authorHref, "_blank", "noopener,noreferrer"); + return; + } + event.preventDefault(); + event.stopPropagation(); + router.push(authorHref); + }, + [authorHref, router] + ); + + const handleAuthorAuxClick = useCallback( + (event: MouseEvent) => { + if (!authorHref || event.button !== 1) { + return; + } + event.preventDefault(); + event.stopPropagation(); + window.open(authorHref, "_blank", "noopener,noreferrer"); + }, + [authorHref] + ); + let authorSection: ReactNode; if (!wave) { authorSection = ( @@ -185,17 +217,24 @@ export default function WaveItem({ ); } else if (authorHref) { authorSection = ( -
{authorAvatar}
{author?.handle ?? userPlaceholder} {authorLevelBadge} - + ); } else { authorSection = ( @@ -210,16 +249,17 @@ export default function WaveItem({ } const handleCardClick = useCallback( - (event: MouseEvent) => { + (event: MouseEvent) => { if (!waveHref) { return; } const target = event.target as HTMLElement | null; if (shouldSkipNavigation(target, event.currentTarget)) { + event.preventDefault(); + event.stopPropagation(); return; } if (event.button === 1 || event.metaKey || event.ctrlKey) { - window.open(waveHref, "_blank", "noopener,noreferrer"); return; } if (!event.defaultPrevented) { @@ -231,7 +271,7 @@ export default function WaveItem({ ); const handleCardKeyDown = useCallback( - (event: KeyboardEvent) => { + (event: KeyboardEvent) => { if (!waveHref || event.target !== event.currentTarget) { return; } @@ -246,6 +286,7 @@ export default function WaveItem({ return (
- {waveHref ? ( - - {wave?.name ?? titlePlaceholder} - - ) : ( - - {wave?.name ?? titlePlaceholder} - - )} + + {wave?.name ?? titlePlaceholder} +
diff --git a/components/waves/list/WaveItemDropped.tsx b/components/waves/list/WaveItemDropped.tsx index 1df93a2082..e7661bd1ff 100644 --- a/components/waves/list/WaveItemDropped.tsx +++ b/components/waves/list/WaveItemDropped.tsx @@ -1,10 +1,41 @@ -import Link from "next/link"; +import { MouseEvent, useCallback } from "react"; +import { useRouter } from "next/navigation"; import { ApiWave } from "@/generated/models/ApiWave"; import { numberWithCommas } from "@/helpers/Helpers"; import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; export default function WaveItemDropped({ wave }: { readonly wave: ApiWave }) { const contributors = wave.contributors_overview ?? []; + const router = useRouter(); + + const handleContributorClick = useCallback( + (href: string) => + (event: MouseEvent) => { + if (event.metaKey || event.ctrlKey) { + event.preventDefault(); + event.stopPropagation(); + window.open(href, "_blank", "noopener,noreferrer"); + return; + } + event.preventDefault(); + event.stopPropagation(); + router.push(href); + }, + [router] + ); + + const handleContributorAuxClick = useCallback( + (href: string) => + (event: MouseEvent) => { + if (event.button !== 1) { + return; + } + event.preventDefault(); + event.stopPropagation(); + window.open(href, "_blank", "noopener,noreferrer"); + }, + [] + ); return (
@@ -45,9 +76,20 @@ export default function WaveItemDropped({ wave }: { readonly wave: ApiWave }) { return (
{contributorHref ? ( - + ) : ( avatar )} From a803a5d33c70bf9b4be1a9f56fc553b2b0e08a67 Mon Sep 17 00:00:00 2001 From: ragnep Date: Wed, 29 Oct 2025 10:10:33 +0200 Subject: [PATCH 3/4] wip Signed-off-by: ragnep --- components/waves/list/WaveItem.tsx | 2 +- components/waves/list/WaveItemDropped.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/waves/list/WaveItem.tsx b/components/waves/list/WaveItem.tsx index a79efbccbb..7674554c17 100644 --- a/components/waves/list/WaveItem.tsx +++ b/components/waves/list/WaveItem.tsx @@ -222,7 +222,7 @@ export default function WaveItem({ data-wave-item-interactive="true" onClick={handleAuthorClick} onAuxClick={handleAuthorAuxClick} - className={`${authorWrapperClass} tw-no-underline tw-bg-transparent tw-border-none tw-p-0 tw-text-left`} + className={`${authorWrapperClass} tw-cursor-pointer tw-no-underline tw-bg-transparent tw-border-none tw-p-0 tw-text-left`} aria-label={ author?.handle ? `View @${author.handle}` diff --git a/components/waves/list/WaveItemDropped.tsx b/components/waves/list/WaveItemDropped.tsx index e7661bd1ff..312834ec63 100644 --- a/components/waves/list/WaveItemDropped.tsx +++ b/components/waves/list/WaveItemDropped.tsx @@ -81,7 +81,7 @@ export default function WaveItemDropped({ wave }: { readonly wave: ApiWave }) { data-wave-item-interactive="true" onClick={handleContributorClick(contributorHref)} onAuxClick={handleContributorAuxClick(contributorHref)} - className="tw-bg-transparent tw-border-none tw-p-0 tw-m-0 tw-inline-flex" + className="tw-cursor-pointer tw-bg-transparent tw-border-none tw-p-0 tw-m-0 tw-inline-flex" aria-label={ c.contributor_identity ? `View @${c.contributor_identity}` From cc617b3f6d1a3be02984490a4823b14de5e32127 Mon Sep 17 00:00:00 2001 From: ragnep Date: Wed, 29 Oct 2025 10:26:09 +0200 Subject: [PATCH 4/4] wip Signed-off-by: ragnep --- codex/tickets/TKT-0021.md | 1 + components/waves/list/WaveItemDropped.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/codex/tickets/TKT-0021.md b/codex/tickets/TKT-0021.md index a684922ab0..305a578aa8 100644 --- a/codex/tickets/TKT-0021.md +++ b/codex/tickets/TKT-0021.md @@ -36,3 +36,4 @@ title: Restore Discover create wave modal - 2025-10-29T07:10:05Z – Confirmed `npm run type-check` and `npm run test:cov:changed` pass (pre-existing console warnings only). - 2025-10-29T07:15:11Z – Resolved nested link warning by making the card container a focusable div with keyboard/middle-click routing and re-ran checks. - 2025-10-29T07:48:13Z – Restored outer `` semantics with internal button replacements, updated contributor avatars to use buttons, adjusted tests, and reran lint/type-check/tests. +- 2025-10-29T08:22:29Z – Added missing `"use client"` directive to `WaveItemDropped` per hook usage guidance. diff --git a/components/waves/list/WaveItemDropped.tsx b/components/waves/list/WaveItemDropped.tsx index 312834ec63..6d22d6ad38 100644 --- a/components/waves/list/WaveItemDropped.tsx +++ b/components/waves/list/WaveItemDropped.tsx @@ -1,3 +1,5 @@ +"use client"; + import { MouseEvent, useCallback } from "react"; import { useRouter } from "next/navigation"; import { ApiWave } from "@/generated/models/ApiWave";