Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions __tests__/components/waves/OpenGraphPreview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,23 @@ describe('OpenGraphPreview', () => {
);
});

it('wraps long unbroken segments to keep layout consistent', () => {
(removeBaseEndpoint as jest.Mock).mockReturnValue('/article');

const longUrl = `https://example.com/${'a'.repeat(48)}`;

render(
<OpenGraphPreview
href="https://example.com/article"
preview={{
description: `Visit ${longUrl} for additional context`,
}}
/>
);

const wrappedSegment = screen.getByText(longUrl);
expect(wrappedSegment.tagName).toBe('SPAN');
expect(wrappedSegment).toHaveClass('tw-break-all');
});

});
1 change: 1 addition & 0 deletions codex/tickets/TKT-0009.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ title: Refactor Brain notifications shell for modular clarity
- 2025-10-15T08:35:49Z – Outlined modular breakdown: main shell in `index.tsx`, hook layer (`useNotificationsController`, `useNotificationsScroll`), presentational states (`NotificationsContent`, `NotificationsStateMessage`), and error utilities.
- 2025-10-15T09:44:55Z – Refactored Notifications into modular files, attempted type-check/lint (failing due to pre-existing repository issues).
- 2025-10-15T10:56:58Z – Hardened mark-all-as-read success handler against context cleanup failures.
- 2025-10-16T07:48:38Z – Removed unicode regex flag from `OpenGraphPreview` to restore compatibility with current TS target; type-check attempt still blocked by existing test typing failures.
17 changes: 14 additions & 3 deletions components/waves/LinkHandlerFrame.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ReactNode } from "react";

import { removeBaseEndpoint } from "@/helpers/Helpers";

import ChatItemHrefButtons from "./ChatItemHrefButtons";

interface LinkHandlerFrameProps {
Expand All @@ -15,13 +17,22 @@ export default function LinkHandlerFrame({
hideLink = false,
relativeHref,
}: LinkHandlerFrameProps) {
const effectiveRelativeHref =
relativeHref ??
(() => {
const relative = removeBaseEndpoint(href);
return relative?.startsWith("/") ? relative : undefined;
})();

return (
<div className="tw-flex tw-items-stretch tw-w-full tw-gap-x-1">
<div className="tw-flex-1 tw-min-w-0">{children}</div>
<div className="tw-flex tw-items-stretch tw-w-full tw-min-w-0 tw-max-w-full tw-gap-x-1">
<div className="tw-flex-1 tw-min-w-0 tw-max-w-full tw-overflow-hidden focus-within:tw-overflow-visible">
{children}
</div>
<ChatItemHrefButtons
href={href}
hideLink={hideLink}
relativeHref={relativeHref}
relativeHref={effectiveRelativeHref}
/>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions components/waves/LinkPreviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ export default function LinkPreviewCard({

return (
<LinkPreviewCardLayout href={href}>
<div className="tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4">
<div className="tw-flex tw-h-full tw-w-full tw-items-center tw-justify-start">
<div className="tw-w-full tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4">
<div className="tw-flex tw-h-full tw-w-full tw-max-w-full tw-items-center tw-justify-start tw-overflow-hidden tw-break-words tw-[overflow-wrap:anywhere]">
{fallbackContent}
</div>
</div>
Expand All @@ -102,7 +102,7 @@ export default function LinkPreviewCard({
if (state.type === "ens") {
return (
<LinkPreviewCardLayout href={href}>
<div className="tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4">
<div className="tw-w-full tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4">
<EnsPreviewCard preview={state.data} />
</div>
</LinkPreviewCardLayout>
Expand Down
68 changes: 55 additions & 13 deletions components/waves/OpenGraphPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ReactNode } from "react";
import { Fragment, type ReactElement, type ReactNode } from "react";

import Image from "next/image";
import Link from "next/link";
Expand Down Expand Up @@ -51,6 +51,7 @@ const IMAGE_KEYS = [
"secureUrl",
];
const IMAGE_COLLECTION_KEYS = ["images", "ogImages", "og_images", "thumbnails"];
const LONG_UNBROKEN_SEGMENT_THRESHOLD = 32;

function readFirstString(
data: OpenGraphPreviewData | null | undefined,
Expand Down Expand Up @@ -135,6 +136,45 @@ function extractImageUrl(
return undefined;
}

function wrapLongUnbrokenSegments(value: string | undefined): ReactNode {
if (!value) {
return value ?? "";
}

const tokens = value.split(/(\s+)/);
let mutated = false;
let offset = 0;

const nodes = tokens
.map((token) => {
if (!token) {
return null;
}

const key = `og-wrap-${offset}`;
offset += token.length;

const trimmed = token.trim();
if (trimmed.length >= LONG_UNBROKEN_SEGMENT_THRESHOLD) {
mutated = true;
return (
<span key={key} className="tw-break-all">
{token}
</span>
);
}

return <Fragment key={key}>{token}</Fragment>;
})
.filter((token): token is ReactElement => token !== null);

if (!mutated) {
return value;
}

return nodes;
}

function extractDomainFromUrl(url: string | undefined): string | undefined {
if (!url) {
return undefined;
Expand Down Expand Up @@ -197,8 +237,10 @@ export function LinkPreviewCardLayout({
const relativeHref = getRelativeHref(href);

return (
<div className="tw-flex tw-w-full tw-items-stretch tw-gap-x-1">
<div className="tw-flex-1 tw-min-w-0">{children}</div>
<div className="tw-flex tw-w-full tw-min-w-0 tw-max-w-full tw-items-stretch tw-gap-x-1">
<div className="tw-flex-1 tw-min-w-0 tw-max-w-full tw-overflow-hidden focus-within:tw-overflow-visible">
{children}
</div>
<ChatItemHrefButtons href={href} relativeHref={relativeHref} />
</div>
);
Expand Down Expand Up @@ -232,7 +274,7 @@ export default function OpenGraphPreview({
return (
<LinkPreviewCardLayout href={href}>
<div
className="tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4">
className="tw-w-full tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4">
<div
className="tw-animate-pulse tw-flex tw-flex-col tw-gap-y-3"
data-testid="og-preview-skeleton">
Expand All @@ -256,7 +298,7 @@ export default function OpenGraphPreview({
return (
<LinkPreviewCardLayout href={href}>
<div
className="tw-flex tw-h-full tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-6"
className="tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-6"
data-testid="og-preview-unavailable">
<div className="tw-text-center tw-space-y-2">
<p className="tw-m-0 tw-text-sm tw-font-medium tw-text-iron-400">
Expand All @@ -266,8 +308,8 @@ export default function OpenGraphPreview({
href={effectiveHref}
target={linkTarget}
rel={linkRel}
className="tw-text-sm tw-font-semibold tw-text-iron-100 tw-no-underline tw-transition tw-duration-200 hover:tw-text-white">
{domain ?? href}
className="tw-break-words tw-[overflow-wrap:anywhere] tw-text-sm tw-font-semibold tw-text-iron-100 tw-no-underline tw-transition tw-duration-200 hover:tw-text-white">
{wrapLongUnbrokenSegments(domain ?? href)}
</Link>
</div>
</div>
Expand All @@ -278,7 +320,7 @@ export default function OpenGraphPreview({
return (
<LinkPreviewCardLayout href={href}>
<div
className="tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4"
className="tw-w-full tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900/40 tw-p-4"
data-testid="og-preview-card">
<div className="tw-flex tw-flex-col tw-gap-4 md:tw-flex-row">
{imageUrl && (
Expand All @@ -304,19 +346,19 @@ export default function OpenGraphPreview({
<div className="tw-flex tw-min-w-0 tw-flex-1 tw-flex-col tw-gap-y-2">
{domain && (
<span className="tw-text-xs tw-font-medium tw-uppercase tw-tracking-wide tw-text-iron-400">
{domain}
{wrapLongUnbrokenSegments(domain)}
</span>
)}
<Link
href={effectiveHref}
target={linkTarget}
rel={linkRel}
className="tw-text-lg tw-font-semibold tw-leading-snug tw-text-iron-100 tw-no-underline tw-transition tw-duration-200 hover:tw-text-white">
{title ?? domain ?? href}
className="tw-break-words tw-[overflow-wrap:anywhere] tw-text-lg tw-font-semibold tw-leading-snug tw-text-iron-100 tw-no-underline tw-transition tw-duration-200 hover:tw-text-white">
{wrapLongUnbrokenSegments(title ?? domain ?? href)}
</Link>
{description && (
<p className="tw-m-0 tw-text-sm tw-text-iron-300 tw-line-clamp-3 tw-break-words tw-whitespace-pre-line">
{description}
<p className="tw-m-0 tw-text-sm tw-text-iron-300 tw-line-clamp-3 tw-break-words tw-[overflow-wrap:anywhere] tw-whitespace-pre-line">
{wrapLongUnbrokenSegments(description)}
</p>
)}
</div>
Expand Down