Conversation
📝 WalkthroughWalkthroughAdds a root-level Next.js Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant TokenList as ProfileTokensListItem
participant Dialog as WalletOperationsDialog
rect rgba(200,220,255,0.5)
User->>TokenList: open actions dropdown & select operation
end
rect rgba(200,255,200,0.5)
TokenList->>TokenList: setActiveOperation(operation)
TokenList->>Dialog: render with controlled.show = true
end
rect rgba(255,220,200,0.5)
Dialog->>User: display modal (initialized/reset)
User->>Dialog: complete or cancel
Dialog->>TokenList: call controlled.onHide()
TokenList->>Dialog: setActiveOperation(null) (dialog hidden)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/src/app/(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts (1)
25-41:⚠️ Potential issue | 🟠 MajorMissing
alternates.canonical— potential SEO regression.
metaUrlis computed and used only foropenGraph.url(a relative path). Thealternates.canonicalfield that signals the authoritative URL to search engines is absent from the return object. If the previous implementation set a canonical URL (the AI summary notes thatmetaCanonicalwas removed), this is a regression that can lead to duplicate-content indexing across/posts,/comments,/replies, etc. sections of the same profile.🔧 Suggested addition
const metaUrl = `/@${username.replace("@", "")}${section ? `/${section}` : ""}`; + const metaCanonical = `${base}${metaUrl}`; const metaImage = `${defaults.imageServer}/u/${username.replace("@", "")}/avatar/medium`; const metaKeywords = ...; return { title: metaTitle, description: metaDescription, + alternates: { + canonical: metaCanonical, + }, openGraph: { ... }, twitter: { ... }, };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts around lines 25 - 41, The returned metadata object from generate-profile-metadata is missing alternates.canonical which causes an SEO regression; update the return value to include an alternates object with canonical set to the authoritative URL (use the existing metaCanonical if present, otherwise build an absolute URL from metaUrl), e.g. add alternates: { canonical: metaCanonical || metaUrlAbsolute } alongside the existing openGraph and twitter fields so search engines get the canonical URL; reference the same symbols used in the diff (metaUrl, metaTitle, metaDescription, metaImage, metaKeywords) when adding alternates.canonical.apps/web/src/app/(dynamicPages)/feed/[...sections]/_helpers/generate-feed-metadata.ts (1)
11-41:⚠️ Potential issue | 🟠 Major
canonicalandrssare dead code; their absence from the return is a potential SEO/discovery regression.
canonicalandrssare computed with conditional logic across two branches (lines 11–25) but are not present in the returned object (lines 28–41). The branch-aware computation strongly suggests these were previously included in analternatesblock. Omitting them means:
- Feed pages have no canonical URL, risking duplicate-content indexing across filter/tag permutations.
- RSS feeds (e.g.,
/trending/bitcoin/rss.xml) are not advertised in<head>, breaking feed-reader auto-discovery.🔧 Suggested fix
return { title, description, + alternates: { + canonical, + ...(rss ? { types: { "application/rss+xml": rss } } : {}), + }, openGraph: { title, description, url, }, twitter: { card: "summary", title, description, }, };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/feed/[...sections]/_helpers/generate-feed-metadata.ts around lines 11 - 41, The computed canonical and rss variables in generate-feed-metadata.ts are not returned and thus dropped; update the returned metadata object to include the branch-aware canonical URL and advertise the RSS feed (use the existing canonical and rss variables) — e.g., add a top-level canonical property set to canonical and include the RSS under alternates (or alternates.types / alternates['application/rss+xml']) so feed readers and search engines receive the correct canonical URL and feed auto-discovery.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/web/src/app/`(dynamicPages)/community/[community]/_helpers/generate-community-metadata.ts:
- Around line 18-36: metaRss and metaCanonical are computed but never used, so
add them back into the metadata return by populating alternates.canonical with
metaCanonical and exposing the RSS link (e.g., under
alternates.types["application/rss+xml"] or a links/rss field) so the generated
object returned from this helper includes the canonical URL and RSS discovery;
update the return in the function that currently builds
title/description/openGraph/twitter (referencing metaRss, metaCanonical,
metaImage, and the metadata return object) to include alternates: { canonical:
metaCanonical, types: { "application/rss+xml": metaRss } } (or the existing app
convention for RSS links) so the values are no longer dead code.
---
Outside diff comments:
In
`@apps/web/src/app/`(dynamicPages)/feed/[...sections]/_helpers/generate-feed-metadata.ts:
- Around line 11-41: The computed canonical and rss variables in
generate-feed-metadata.ts are not returned and thus dropped; update the returned
metadata object to include the branch-aware canonical URL and advertise the RSS
feed (use the existing canonical and rss variables) — e.g., add a top-level
canonical property set to canonical and include the RSS under alternates (or
alternates.types / alternates['application/rss+xml']) so feed readers and search
engines receive the correct canonical URL and feed auto-discovery.
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts:
- Around line 25-41: The returned metadata object from generate-profile-metadata
is missing alternates.canonical which causes an SEO regression; update the
return value to include an alternates object with canonical set to the
authoritative URL (use the existing metaCanonical if present, otherwise build an
absolute URL from metaUrl), e.g. add alternates: { canonical: metaCanonical ||
metaUrlAbsolute } alongside the existing openGraph and twitter fields so search
engines get the canonical URL; reference the same symbols used in the diff
(metaUrl, metaTitle, metaDescription, metaImage, metaKeywords) when adding
alternates.canonical.
There was a problem hiding this comment.
🧹 Nitpick comments (6)
apps/web/src/features/wallet/wallet-operations-dialog.tsx (2)
52-55: ControlledsetShow(true)is silently a no-op.When in controlled mode, calling
setShow(true)does nothing. Currently no code path callssetShow(true)in controlled mode (the trigger wrapper is hidden), so this is safe. But the asymmetry is worth a brief inline comment for future maintainers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/wallet/wallet-operations-dialog.tsx` around lines 52 - 55, The controlled-mode setter currently only invokes controlled.onHide() when given false, so setShow in controlled mode is a no-op for true; add a brief inline comment next to the setShow definition (near symbols show, setShow, controlled, internalShow, setInternalShow, controlled.onHide) stating that setShow(true) is intentionally a no-op in controlled mode (no onShow is called) because the trigger is hidden and visibility is driven externally, to prevent future confusion for maintainers.
64-70: MissinginitialDatain the dependency array of the reset effect.This effect calls
setData(initialData)but omitsinitialDatafrom its dependency list. If a future caller provides bothcontrolledandinitialDataand the latter changes independently, the form could reset with a stale value. Current consumers don't passinitialDatain controlled mode, so the risk is theoretical.Suggested fix
useEffect(() => { if (controlled?.show) { setStep("form"); setData(initialData); setSignError(undefined); } - }, [controlled?.show]); + }, [controlled?.show, initialData]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/wallet/wallet-operations-dialog.tsx` around lines 64 - 70, The effect that resets form state (useEffect that checks controlled?.show and calls setStep("form"), setData(initialData), setSignError(undefined)) is missing initialData from its dependency array; update the dependency array to include initialData (e.g., [controlled?.show, initialData]) so the reset uses the latest initialData when it changes, ensuring setData(initialData) runs with the current value.apps/web/src/app/(dynamicPages)/profile/[username]/wallet/_components/profile-wallet-tokens-list-item.tsx (1)
187-198: Consider adding akeyto force remount on operation change.
WalletOperationsDialogis conditionally rendered only whenactiveOperation !== null, so it mounts fresh each time. However, if a code path ever setsactiveOperationdirectly from one non-null value to another (without passing throughnull), the component would receive newoperation/assetprops without the controlled-show reset effect re-firing (sincecontrolled.showremainstrue). Adding akeytied to the operation prevents stale form state in that edge case.Suggested fix
const activeOperationDialog = activeOperation !== null ? ( <WalletOperationsDialog + key={activeOperation} controlled={{ show: true, onHide: () => setActiveOperation(null) }} asset={assetSymbol} operation={activeOperation}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/profile/[username]/wallet/_components/profile-wallet-tokens-list-item.tsx around lines 187 - 198, The WalletOperationsDialog can stay mounted if activeOperation flips between non-null values, so add a key prop tied to the changing identifiers to force a remount when operation changes; update the JSX that renders WalletOperationsDialog (the expression using activeOperation, controlled={{ show: true, onHide: () => setActiveOperation(null) }}, asset={assetSymbol}, operation={activeOperation}) to include a key derived from activeOperation and assetSymbol (e.g. combine operation id/type and assetSymbol) so the component fully remounts whenever the operation/asset changes.apps/web/src/app/(dynamicPages)/feed/[...sections]/_helpers/generate-feed-metadata.ts (1)
40-44: Consider"summary_large_image"for richer social previews on tag/feed pages.
twitter.card: "summary"renders only a small thumbnail. For tag/topic feed pages that aggregate community content,"summary_large_image"produces a more prominent card — especially relevant if a default OG image is added to these pages in a follow-up.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/feed/[...sections]/_helpers/generate-feed-metadata.ts around lines 40 - 44, Update the Twitter card type in the feed metadata to use a large preview: in the twitter object inside generate-feed-metadata (the twitter property where card is currently set to "summary"), change the card value to "summary_large_image" so tag/topic feed pages render with a bigger image preview (prepare to pair this with any default OG image you may add later).apps/web/src/app/(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts (2)
28-33: RSS alternate is emitted unconditionally for allsectionvalues.The
application/rss+xmllink is always added, even whensectionis"engine"(tokens),"communities","replies", etc., where a posts-RSS feed is semantically irrelevant to the current page. Consider restricting this to sections that actually have a corresponding feed:💡 Suggested fix
alternates: { canonical: `${base}${metaUrl}`, - types: { - "application/rss+xml": `${base}/@${username.replace("@", "")}/rss`, - }, + ...((!section || section === "posts" || section === "blog") && { + types: { + "application/rss+xml": `${base}/@${username.replace("@", "")}/rss`, + }, + }), },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts around lines 28 - 33, The alternates.types entry unconditionally adds an "application/rss+xml" RSS alternate for every profile page; restrict this so RSS is only emitted for sections that have a posts feed (e.g., the default/profile posts view and any other list-of-posts sections) by checking the section variable before adding the RSS type in generate-profile-metadata.ts: when building alternates (the object that includes canonical and types), only set types["application/rss+xml"] = `${base}/@${username.replace("@","")}/rss` if section is one of the allowed sections (e.g., undefined or "posts" or other post-list sections), otherwise omit the RSS key so pages like "engine", "communities", "replies" don't get an RSS alternate.
24-24: Consider usingstring[]foropenGraph.tagsto emit separate keyword tags.Line 24 defines
metaKeywordsas a single comma-separated string. While Next.js acceptsstring | string[] | nullforopenGraph.tags, passing a comma-separated string produces a single<meta property="og:article:tag">element with the entire value, not two distinct tags. To emit separate keyword tags, use an array:- const metaKeywords = `${username.replace("@", "")}, ${username.replace("@", "")}'s blog`; + const metaKeywords = [ + username.replace("@", ""), + `${username.replace("@", "")}'s blog`, + ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts at line 24, metaKeywords is built as a single comma-separated string which causes openGraph.tags to output one combined tag; change metaKeywords to an array of strings and pass that array to openGraph.tags instead. Specifically, replace the single-string construction referenced by metaKeywords with an array like [username.replace("@",""), `${username.replace("@","")}'s blog`] and ensure the openGraph.tags property (where metaKeywords is used) receives this string[] so Next.js emits separate <meta property="og:article:tag"> entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@apps/web/src/app/`(dynamicPages)/community/[community]/_helpers/generate-community-metadata.ts:
- Around line 21-42: No change required to behavior — the metadata now
consistently uses the metaImage constant in openGraph.images and twitter.images
and includes alternates.canonical and alternates.types["application/rss+xml"];
just ensure any remaining dead vars metaRss or metaCanonical are removed if
still declared and keep metaImage, openGraph, twitter, and alternates as shown
(references: metaImage, openGraph.images, twitter.images, alternates.canonical,
alternates.types["application/rss+xml"]).
---
Nitpick comments:
In
`@apps/web/src/app/`(dynamicPages)/feed/[...sections]/_helpers/generate-feed-metadata.ts:
- Around line 40-44: Update the Twitter card type in the feed metadata to use a
large preview: in the twitter object inside generate-feed-metadata (the twitter
property where card is currently set to "summary"), change the card value to
"summary_large_image" so tag/topic feed pages render with a bigger image preview
(prepare to pair this with any default OG image you may add later).
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts:
- Around line 28-33: The alternates.types entry unconditionally adds an
"application/rss+xml" RSS alternate for every profile page; restrict this so RSS
is only emitted for sections that have a posts feed (e.g., the default/profile
posts view and any other list-of-posts sections) by checking the section
variable before adding the RSS type in generate-profile-metadata.ts: when
building alternates (the object that includes canonical and types), only set
types["application/rss+xml"] = `${base}/@${username.replace("@","")}/rss` if
section is one of the allowed sections (e.g., undefined or "posts" or other
post-list sections), otherwise omit the RSS key so pages like "engine",
"communities", "replies" don't get an RSS alternate.
- Line 24: metaKeywords is built as a single comma-separated string which causes
openGraph.tags to output one combined tag; change metaKeywords to an array of
strings and pass that array to openGraph.tags instead. Specifically, replace the
single-string construction referenced by metaKeywords with an array like
[username.replace("@",""), `${username.replace("@","")}'s blog`] and ensure the
openGraph.tags property (where metaKeywords is used) receives this string[] so
Next.js emits separate <meta property="og:article:tag"> entries.
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/wallet/_components/profile-wallet-tokens-list-item.tsx:
- Around line 187-198: The WalletOperationsDialog can stay mounted if
activeOperation flips between non-null values, so add a key prop tied to the
changing identifiers to force a remount when operation changes; update the JSX
that renders WalletOperationsDialog (the expression using activeOperation,
controlled={{ show: true, onHide: () => setActiveOperation(null) }},
asset={assetSymbol}, operation={activeOperation}) to include a key derived from
activeOperation and assetSymbol (e.g. combine operation id/type and assetSymbol)
so the component fully remounts whenever the operation/asset changes.
In `@apps/web/src/features/wallet/wallet-operations-dialog.tsx`:
- Around line 52-55: The controlled-mode setter currently only invokes
controlled.onHide() when given false, so setShow in controlled mode is a no-op
for true; add a brief inline comment next to the setShow definition (near
symbols show, setShow, controlled, internalShow, setInternalShow,
controlled.onHide) stating that setShow(true) is intentionally a no-op in
controlled mode (no onShow is called) because the trigger is hidden and
visibility is driven externally, to prevent future confusion for maintainers.
- Around line 64-70: The effect that resets form state (useEffect that checks
controlled?.show and calls setStep("form"), setData(initialData),
setSignError(undefined)) is missing initialData from its dependency array;
update the dependency array to include initialData (e.g., [controlled?.show,
initialData]) so the reset uses the latest initialData when it changes, ensuring
setData(initialData) runs with the current value.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/web/src/app/(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts (2)
22-24: ExtractcleanUsernameto avoid four repeatedreplace("@", "")calls
username.replace("@", "")appears on lines 22, 23, 24, and 33. A single extracted variable improves readability and keeps the stripping logic in one place.♻️ Proposed refactor
+ const cleanUsername = username.replace("@", ""); - const metaUrl = `/@${username.replace("@", "")}${section ? `/${section}` : ""}`; - const metaImage = `${defaults.imageServer}/u/${username.replace("@", "")}/avatar/medium`; - const metaKeywords = [username.replace("@", ""), `${username.replace("@", "")}'s blog`]; + const metaUrl = `/@${cleanUsername}${section ? `/${section}` : ""}`; + const metaImage = `${defaults.imageServer}/u/${cleanUsername}/avatar/medium`; + const metaKeywords = [cleanUsername, `${cleanUsername}'s blog`]; const rsssections = ["posts", "blog", ""]; return { ... types: { - "application/rss+xml": `${base}/@${username.replace("@", "")}/rss`, + "application/rss+xml": `${base}/@${cleanUsername}/rss`, },Also applies to: 33-33
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts around lines 22 - 24, Extract a single cleaned username variable (e.g., cleanUsername) in generate-profile-metadata so you don't call username.replace("@", "") repeatedly; replace all occurrences used to build metaUrl, metaImage, metaKeywords (and the use on line 33) with that variable to centralize the stripping logic and improve readability.
25-25:undefinedinrsssectionsis unreachable dead code
sectionis inferred asstringfrom its default value"posts", so it can never beundefinedat runtime. Theundefinedentry in the array is never matched and adds confusion.♻️ Proposed fix
- const rsssections = ["posts", "blog", undefined, ""]; + const rsssections = ["posts", "blog", ""];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts at line 25, The array constant rsssections contains an unreachable undefined entry because the parameter/variable section is inferred as string with default "posts"; remove the undefined element from rsssections (leave valid string values like "posts", "blog", ""), and scan any comparisons against undefined in the generate-profile-metadata logic to adjust them to check for empty string or use explicit fallbacks; target the rsssections constant and any code referencing it (e.g., the section variable handling) to ensure no runtime reliance on undefined remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts:
- Around line 29-36: openGraph.url is currently set to the relative metaUrl
while alternates.canonical uses the absolute `${base}${metaUrl}`; update the
openGraph.url assignment (in the generate-profile-metadata function) to use the
same absolute URL by concatenating base and metaUrl (e.g., `${base}${metaUrl}`)
so social crawlers receive a fully-qualified og:url consistent with
alternates.canonical; keep existing username/section/rsssections logic
unchanged.
---
Nitpick comments:
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts:
- Around line 22-24: Extract a single cleaned username variable (e.g.,
cleanUsername) in generate-profile-metadata so you don't call
username.replace("@", "") repeatedly; replace all occurrences used to build
metaUrl, metaImage, metaKeywords (and the use on line 33) with that variable to
centralize the stripping logic and improve readability.
- Line 25: The array constant rsssections contains an unreachable undefined
entry because the parameter/variable section is inferred as string with default
"posts"; remove the undefined element from rsssections (leave valid string
values like "posts", "blog", ""), and scan any comparisons against undefined in
the generate-profile-metadata logic to adjust them to check for empty string or
use explicit fallbacks; target the rsssections constant and any code referencing
it (e.g., the section variable handling) to ensure no runtime reliance on
undefined remains.
| alternates: { | ||
| canonical: `${base}${metaUrl}`, | ||
| ...(rsssections.includes(section) && { | ||
| types: { | ||
| "application/rss+xml": `${base}/@${username.replace("@", "")}/rss`, | ||
| }, | ||
| }), | ||
| }, |
There was a problem hiding this comment.
openGraph.url should be an absolute URL, consistent with alternates.canonical
alternates.canonical is correctly built as ${base}${metaUrl} (absolute), but openGraph.url (line 41) is still the bare relative path (e.g. /@username/posts). Social-media crawlers expect og:url to be a fully-qualified absolute URL; a relative value may be silently dropped or misresolved, degrading link previews.
🐛 Proposed fix
openGraph: {
title: metaTitle,
description: metaDescription,
images: [metaImage],
- url: metaUrl,
+ url: `${base}${metaUrl}`,
tags: metaKeywords,
},Also applies to: 41-41
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts
around lines 29 - 36, openGraph.url is currently set to the relative metaUrl
while alternates.canonical uses the absolute `${base}${metaUrl}`; update the
openGraph.url assignment (in the generate-profile-metadata function) to use the
same absolute URL by concatenating base and metaUrl (e.g., `${base}${metaUrl}`)
so social crawlers receive a fully-qualified og:url consistent with
alternates.canonical; keep existing username/section/rsssections logic
unchanged.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/web/src/app/(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts (1)
14-14: Use an anchored regex to strip the leading@fromusername.
String.prototype.replace("@", string)removes only the first occurrence, anywhere in the string. Ifusernamesomehow contains an embedded@(e.g., from a malformed input), this silently corruptscleanUsername. Using/^@/makes the intent explicit and limits removal to the leading character.♻️ Proposed fix
- const cleanUsername = username.replace("@", ""); + const cleanUsername = username.replace(/^@/, "");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts at line 14, Replace the unanchored replace call that can remove any first "@" with a leading-only removal: update the cleanUsername assignment (the username.replace call used to compute cleanUsername inside generate-profile-metadata) to use an anchored regex that only strips a leading "@" (e.g., use /^@/), so embedded "@" characters in username are preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts:
- Line 42: openGraph.url is being set to the relative metaUrl; update it to use
the absolute URL built from base and metaUrl so og:url is fully qualified (e.g.
use `${base}${metaUrl}`) — modify the openGraph.url assignment in
generate-profile-metadata.ts (the same place alternates.canonical uses
`${base}${metaUrl}`) to concatenate base and metaUrl so social crawlers receive
an absolute URL.
---
Nitpick comments:
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/_helpers/generate-profile-metadata.ts:
- Line 14: Replace the unanchored replace call that can remove any first "@"
with a leading-only removal: update the cleanUsername assignment (the
username.replace call used to compute cleanUsername inside
generate-profile-metadata) to use an anchored regex that only strips a leading
"@" (e.g., use /^@/), so embedded "@" characters in username are preserved.
Summary by CodeRabbit
New Features
Bug Fixes
Style