Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions __tests__/components/waves/list/WaveItemDropped.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => <a href={href}>{children}</a> }));
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' } }));
Expand Down
1 change: 1 addition & 0 deletions codex/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
38 changes: 38 additions & 0 deletions codex/tickets/TKT-0021.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
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.
- 2025-10-29T07:48:13Z – Restored outer `<Link>` semantics with internal button replacements, updated contributor avatars to use buttons, adjusted tests, and reran lint/type-check/tests.
24 changes: 15 additions & 9 deletions components/waves/Waves.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -45,6 +46,7 @@ export default function Waves({
openDirectMessage,
close,
isApp,
isWaveModalOpen,
} = useCreateModalState();

useSetTitle(documentTitle);
Expand Down Expand Up @@ -173,21 +175,25 @@ export default function Waves({
),
};

const activeView =
isApp || viewMode === WavesViewMode.CREATE
? viewMode
: WavesViewMode.VIEW;
const activeView = isApp ? viewMode : WavesViewMode.VIEW;

return (
<div className="tailwind-scope">
{components[activeView]}

{!isApp && connectedProfile && (
<CreateDirectMessageModal
isOpen={isDirectMessageModalOpen}
onClose={handleViewReset}
profile={connectedProfile}
/>
<>
<CreateWaveModal
isOpen={isWaveModalOpen}
onClose={handleViewReset}
profile={connectedProfile}
/>
<CreateDirectMessageModal
isOpen={isDirectMessageModalOpen}
onClose={handleViewReset}
profile={connectedProfile}
/>
</>
)}
</div>
);
Expand Down
71 changes: 51 additions & 20 deletions components/waves/list/WaveItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function CardContainer({
}: CardContainerProps) {
const className = `${CARD_BASE_CLASSES} ${
isInteractive ? CARD_INTERACTIVE_CLASSES : ""
}`;
} tw-no-underline`;

if (isInteractive && href) {
return (
Expand All @@ -69,7 +69,6 @@ function CardContainer({
prefetch={false}
className={className}
aria-label={ariaLabel}
style={{ textDecoration: "none" }}
onClick={onClick}
onKeyDown={onKeyDown}
>
Expand Down Expand Up @@ -176,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<HTMLButtonElement>) => {
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<HTMLButtonElement>) => {
if (!authorHref || event.button !== 1) {
return;
}
event.preventDefault();
event.stopPropagation();
window.open(authorHref, "_blank", "noopener,noreferrer");
},
[authorHref]
);

let authorSection: ReactNode;
if (!wave) {
authorSection = (
Expand All @@ -188,17 +217,24 @@ export default function WaveItem({
);
} else if (authorHref) {
authorSection = (
<Link
href={authorHref}
prefetch={false}
className={`${authorWrapperClass} tw-no-underline`}
<button
type="button"
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`}
aria-label={
author?.handle
? `View @${author.handle}`
: "View author profile"
}
>
<div className="tw-h-6 tw-w-6 tw-flex-shrink-0">{authorAvatar}</div>
<span className={linkedAuthorNameClass}>
{author?.handle ?? userPlaceholder}
</span>
{authorLevelBadge}
</Link>
</button>
);
} else {
authorSection = (
Expand All @@ -219,6 +255,11 @@ export default function WaveItem({
}
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) {
return;
}
if (!event.defaultPrevented) {
Expand Down Expand Up @@ -277,19 +318,9 @@ export default function WaveItem({
<div className="tw-absolute tw-inset-x-0 tw-bottom-0 tw-flex tw-items-end tw-justify-between tw-gap-3">
<div className="tw-flex tw-min-w-0 tw-items-end tw-px-3 tw-pb-3">
<div className="tw-min-w-0">
{waveHref ? (
<Link
href={waveHref}
prefetch={false}
className="tw-no-underline tw-text-lg tracking-tight tw-font-semibold tw-text-white desktop-hover:hover:tw-text-iron-400 tw-transition tw-duration-300 tw-ease-out tw-line-clamp-1"
>
{wave?.name ?? titlePlaceholder}
</Link>
) : (
<span className="tw-text-lg tw-font-semibold tw-text-white">
{wave?.name ?? titlePlaceholder}
</span>
)}
<span className="tw-text-lg tw-font-semibold tw-text-white desktop-hover:group-hover:tw-text-iron-400 tw-transition tw-duration-300 tw-ease-out tw-line-clamp-1">
{wave?.name ?? titlePlaceholder}
</span>
</div>
</div>
<div className="tw-hidden sm:tw-block" />
Expand Down
48 changes: 45 additions & 3 deletions components/waves/list/WaveItemDropped.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import Link from "next/link";
import { MouseEvent, useCallback } from "react";
import { useRouter } from "next/navigation";
Comment thread
ragnep marked this conversation as resolved.
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<HTMLButtonElement>) => {
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<HTMLButtonElement>) => {
if (event.button !== 1) {
return;
}
event.preventDefault();
event.stopPropagation();
window.open(href, "_blank", "noopener,noreferrer");
},
[]
);

return (
<div className="tw-flex tw-items-center tw-gap-x-2 tw-min-w-0">
Expand Down Expand Up @@ -45,9 +76,20 @@ export default function WaveItemDropped({ wave }: { readonly wave: ApiWave }) {
return (
<div key={baseKey} className="tw-block tw-group/item">
{contributorHref ? (
<Link href={contributorHref} prefetch={false}>
<button
type="button"
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"
aria-label={
c.contributor_identity
? `View @${c.contributor_identity}`
: "View contributor profile"
}
>
{avatar}
</Link>
</button>
) : (
avatar
)}
Expand Down
6 changes: 5 additions & 1 deletion scripts/dev-with-fallback.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ async function findAvailablePort() {
async function run() {
try {
const port = await findAvailablePort();
const env = { ...process.env, PORT: String(port) };
const env = {
...process.env,
__NEXT_EXPERIMENTAL_MCP_SERVER: "true",
PORT: String(port),
};
console.log(`Starting Next.js dev server on port ${port}...`);

const child = spawn(
Expand Down