Conversation
- Tailwind: purple/pink color palette, cream colors, font-primary/secondary, gradients, shadows - Fonts: PP Fraktion Mono + PP Valve via @font-face, S3 fetch script - Logos: new purple chevron, gradient wordmark/title - Icons: updated colors (collapse→gray, confirmed/delivered→green, info→black) - Header: centered logo, mobile hamburger menu with DropdownMenu - Footer: gradient logo, shared nav links - Nav: shared nav system with Stake, X, Hyperlane, Discord, Docs, Github - Buttons: accent/red use gradient+glow, rounded instead of rounded-lg - TipCard: gradient bg, watermark logo, responsive sizing - ConnectWallet: accent gradient with white text - SideBar: accent gradient headers, blur collapse button - Loading: app-gradient background with purple spinner - Background: grid pattern overlay SVG Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n mark icon - FeeSectionButton: show dash when no fees, animated "Loading..." dots - TransferFeeModal: accent gradient header matching v2 style - Nav: replace Discord with Support (help.hyperlane.xyz) - Add QuestionMarkIcon component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t for nav - Add font-secondary to form labels, buttons, token select, transfer modal - Soften top-right background gradient with lavender mid-stop - Bump header/footer nav breakpoint from sm to lg to avoid Intercom overlap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
5 Skipped Deployments
|
📝 WalkthroughWalkthroughReintroduces an RPC newline and adds S3 font support (env vars, fetch script, gitignore), adds eight SVG icon components, reorganizes navigation (NavItem + navLinks) with responsive Header/Footer, updates theme/fonts/styles, and applies multiple UI visual tweaks across components. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/features/transfer/TransferFeeModal.tsx (1)
23-41:⚠️ Potential issue | 🟡 MinorSkeletons are unreachable when
feesisnullduring initial load.The fee rows are gated behind
fees?.localQuote && fees.localQuote.amount > 0n, so whenfeesisnull(first load, before any data arrives), all three rows — including their skeletons — get hidden entirely. The modal would just show the header and the "Read more" link with nothing in between. Not exactly a warm welcome.This is pre-existing behavior, not introduced by this PR, but since the rebrand is coordinating loading states (loading dots in the FeeSection, etc.), it might be worth addressing here or in a follow-up so the modal isn't a barren swamp on first open.
🤖 Fix all issues with AI agents
In `@src/components/nav/Header.tsx`:
- Around line 34-40: The Link element in Header.tsx (inside the Header
component) wraps three decorative images with empty alt text, so add an
accessible name to the link by giving the Link component an aria-label (e.g.,
aria-label="Homepage" or aria-label="Go to [SiteName] home") or include
visually-hidden text inside the Link; update the Link JSX (the <Link href="/">
element) to include the aria-label or hidden text so screen readers announce the
destination.
- Around line 42-44: The header element lacks positioning so the absolutely
positioned wrapper div around ConnectWalletButton (the div with classes
"lg:absolute lg:right-12") is positioning against an ancestor (AppLayout)
instead of the header; update the Header component to add a relative positioning
class to the header element (e.g., add "relative" or "lg:relative" to the
<header> element in the Header component) so the ConnectWalletButton's absolute
positioning is anchored to the header itself.
In `@src/consts/links.ts`:
- Line 18: The stake URL value for the stake constant contains a checksum-cased
address (0xE1F23869776c82f691d9Cb34597Ab1830Fb0De58) which should be converted
to lowercase to match the project’s EVM address style; update the stake entry in
src/consts/links.ts to use the lowercase address
(0xe1f23869776c82f691d9cb34597ab1830fb0de58) and verify the link still works in
a browser (or revert to the original mixed-case if the external site is
case-sensitive) so you don’t break routing on app.symbiotic.fi.
In `@src/pages/_app.tsx`:
- Around line 36-39: Remove or update the stale comment about requiring the font
definition in _document.tsx: the project now loads fonts via `@font-face` in
globals.css and applies them with the Tailwind class used in the JSX (the div
with className="font-primary text-black"), so delete or replace the two lines
noting the old next/font requirement to avoid confusion.
In `@tailwind.config.js`:
- Around line 124-130: The boxShadow config defines both 'accent-glow' and
'error-glow' identically; update the 'error-glow' entry in the boxShadow object
so it uses an error-specific color token instead of theme('colors.accent.100')
(e.g., theme('colors.error.*') or a red color from your palette) so error states
(shadow-error-glow) visually differ from accent states; modify the boxShadow
object where 'accent-glow' and 'error-glow' are defined to reference the correct
error color token.
🧹 Nitpick comments (16)
src/features/tokens/TokenSelectField.tsx (1)
112-114: Looks good — but while ye're in this swamp, consider tidying the class string.The
font-secondaryaddition fits right in with the rebrand, no issues there. One wee thing though — since you're already touching this line: the expression`${!token?.symbol && 'text-slate-400'}`stringifies to"false"whentoken?.symbolis truthy, which means you'll getclass="ml-2 font-secondary false"in the DOM. It's harmless but a bit muddy. A ternary keeps it clean:✨ Optional tidy-up
- <span className={`ml-2 font-secondary ${!token?.symbol && 'text-slate-400'}`}> + <span className={`ml-2 font-secondary ${!token?.symbol ? 'text-slate-400' : ''}`}>src/features/wallet/SideBarMenu.tsx (1)
78-79: Gradient headers look proper — just a wee accessibility thought.Both section headers now use
bg-accent-gradientwithtext-white. The purple/pink palette generally plays well with white text, but it'd be worth making sure the contrast ratio meets WCAG AA (4.5:1 for normal text). If the gradient has lighter stops, the white text might get a bit lost in the middle of the swamp... er, the midtones.Otherwise, the rename to "My Wallets" and "Transaction History" reads cleaner. No functional concerns.
Also applies to: 88-89
src/features/transfer/FeeSectionButton.tsx (2)
6-20:dotCountdoesn't reset when loading restarts — might look a wee bit offWhen
isLoadingflips fromfalseback totrue,dotCountpicks up from its last value instead of resetting to1. So the animation might start mid-cycle — like walking into a swamp halfway through, y'know?Also, since hooks can't be called conditionally, this interval keeps tickin' even when
visibleisfalseand the component renders nothing. Not the end of the world, but no reason to keep the donkey runnin' when nobody's watching.🔧 Proposed fix: reset dotCount and respect visibility
-function useLoadingDots(isLoading: boolean, intervalMs = 1000) { +function useLoadingDots(isActive: boolean, intervalMs = 1000) { const [dotCount, setDotCount] = useState(1); useEffect(() => { - if (!isLoading) return; + if (!isActive) { + setDotCount(1); + return; + } const interval = setInterval(() => { setDotCount((prev) => (prev % 3) + 1); }, intervalMs); return () => clearInterval(interval); - }, [isLoading, intervalMs]); + }, [isActive, intervalMs]); return 'Loading' + '.'.repeat(dotCount); }Then at the call site, pass the combined condition:
- const loadingText = useLoadingDots(isLoading); + const loadingText = useLoadingDots(isLoading && visible);
44-53: Hover styles still fire on the disabled button — minor visual thingWhen
!isClickable, the button getsdisabledandcursor-default, which is grand. But the hover pseudo-classes (hover:text-gray-900,[&_path]:hover:fill-gray-900) still apply visually on disabled buttons in most browsers, making it look like somethin' is gonna happen when nothin' will. A little misleading for the folks in the swamp.You could conditionally apply the hover classes, or just slap
pointer-events-noneon there when not clickable. Not urgent — just a bit of polish.✨ Proposed tweak
- className={`flex w-fit items-center font-secondary text-xxs text-gray-700 hover:text-gray-900 [&_path]:fill-gray-700 [&_path]:hover:fill-gray-900 ${!isClickable ? 'cursor-default' : ''}`} + className={`flex w-fit items-center font-secondary text-xxs text-gray-700 [&_path]:fill-gray-700 ${isClickable ? 'hover:text-gray-900 [&_path]:hover:fill-gray-900' : 'cursor-default pointer-events-none'}`}src/features/transfer/TransfersDetailsModal.tsx (1)
143-143: Strayitemsclass on this line.The
font-secondaryaddition is spot on for the rebrand. But while we're in this neck of the woods — there's a loneitemsclass that doesn't do anything useful in Tailwind. Looks like it wandered in from somewhere and never left. Might want to shoo it out.🧹 Suggested cleanup
- <div className="items ml-2 flex items-baseline font-secondary"> + <div className="ml-2 flex items-baseline font-secondary">src/features/wallet/ConnectWalletButton.tsx (1)
21-22: Broad descendant override — just somethin' to keep an eye on.The
[&_*]:text-whiteselector applies white text to every descendant inside the inner widget. Right now that's fine for a gradient button, but ifConnectWalletButtonInnerever renders status badges or colored indicators, this'll steamroll their styles. The[&_path]:fill-whitehas the same reach for SVG fills.Not a blocker at all — the visual result is correct for the rebrand. Just worth knowin' it's there if things ever look a bit... onion-layered later.
src/components/icons/QuestionMarkIcon.tsx (1)
5-21: Default fill differs from sibling icons.This icon defaults to
Color.primary[500]while others (likeHamburgerIcon) default to'currentColor'. That means this one won't inherit the parent's text color out of the box, which could be a wee surprise if someone tries to theme it via a parenttext-*class.If the primary fill is intentional for this specific icon's usage, no worries — just somethin' to be aware of for consistency across the icon family.
src/components/icons/StakeIcon.tsx (1)
4-14: Hardcoded colors diverge from the pattern used by sibling icons.The other icon components in this PR (XIcon, BookIcon, WebSimpleIcon) destructure and apply the
colorprop with a fallback. This one spreads all props but never usescolor, so all fills/strokes are baked in. That's fair enough for a multi-color icon — but it means if somebody passescolorexpecting it to do something, it'll just vanish into the swamp.Worth at least destructuring and discarding
colorexplicitly, or applying it to the primary fill so the intent is clear.src/components/icons/HyperlaneTransparentLogo.tsx (1)
3-18: This component doesn't accept any props, unlike its siblings.
_HyperlaneGradientLogoinHyperlaneGradientLogo.tsxacceptsDefaultIconPropsand spreads them onto the<svg>, letting consumers control className, dimensions, and accessibility attributes. This one is sealed shut — hardcodedwidth,height, andstroke="white"with no way to override anything from the outside.If this logo needs to be used at different sizes or needs an
aria-label, nobody can do that without editing this file.Proposed fix
-function _HyperlaneTransparentLogo() { +import { DefaultIconProps } from '@hyperlane-xyz/widgets'; + +function _HyperlaneTransparentLogo({ ...props }: DefaultIconProps) { return ( <svg - width="146" - height="127" viewBox="0 0 146 127" fill="none" xmlns="http://www.w3.org/2000/svg" + {...props} >scripts/fetch-fonts.mjs (2)
73-76: Top-level catch swallows unexpected errors with only awarn.This catch-all handles any error — including programming bugs or invariant violations — with
console.warnand a zero exit code. That's fine for the "missing fonts shouldn't block the build" case, but per the project guidelines, unexpected issues should fail loudly. If the script has a genuine bug (not an S3/network failure), you'd want to know about it.Consider using
console.errorhere, or at least distinguishing between expected S3/network errors (already handled per-font in the loop) and unexpected ones at this level.Proposed tweak
fetchFonts().catch((error) => { - console.warn('Font fetch script encountered an error:', error.message); - // Exit gracefully - don't fail the build + console.error('Font fetch script encountered an unexpected error:', error.message); + // Don't fail the build, but log as error for visibility });
52-55: Pipe the stream straight through without the conversion dance.
response.Bodyfrom AWS SDK v3's GetObjectCommand is already a NodeReadablestream in Node.js. Converting it to a web stream and back again is just taking the long way around the barn.Simpler pipeline
- await pipeline(Readable.fromWeb(response.Body.transformToWebStream()), writeStream); + await pipeline(response.Body, writeStream);src/components/icons/HyperlaneGradientLogo.tsx (1)
4-30: Exported name doesn't match the file name — could confuse someone lookin' for it.The file is
HyperlaneGradientLogo.tsx, the internal function is_HyperlaneGradientLogo, but the export isHyperlaneFooterLogo. When someone goes searchin' for where this logo lives, the name mismatch could send them on a bit of a wild goose chase through the swamp.Either rename the file to
HyperlaneFooterLogo.tsxor the export toHyperlaneGradientLogo— whichever reflects the intended usage better.tailwind.config.js (1)
28-38: Heads up: overriding default Tailwind gray keys.Spreadin'
defaultColors.grayand then overridin' keys like300,400,900,950means anyone reachin' for the standard Tailwind gray at those stops will get your custom values instead. That's perfectly fine if it's intentional — just make sure the rest of the team knows the swamp rules have changed.src/styles/globals.css (1)
78-96: Hardcoded hex values duplicate theme tokens.
#e8caffmatchesprimary.50and#d4a3ffis close toprimary.100from your Tailwind config. If those tokens ever change, these scrollbar colors will be left behind in the swamp. Consider using CSS custom properties set from your theme, or at minimum drop a comment noting which token each hex corresponds to.src/components/nav/Nav.tsx (1)
45-63:forwardRefusage andLinkfor external URLs — works, with a note.The
forwardRefpattern is appropriate if a parent needs to grab the anchor ref (e.g., for tooltips or focus management). The use of Next.jsLinkfor external URLs is supported in Next.js 13+ — it essentially renders a standard<a>tag for absolute URLs, so no concern there.One thing to be mindful of: Next.js
Linkaddsrel="noopener"automatically whentarget="_blank"is set, but it does not addnoreferrer. If you want to prevent leaking the referrer header to third-party sites (X.com, GitHub, etc.), you'd want to add that explicitly. It's not a security vulnerability per se — just a privacy consideration.Optional: add noreferrer
<Link ref={ref} className={clsx( 'flex items-center gap-2 text-primary-500 decoration-primary-500 underline-offset-2 hover:underline', className, )} target="_blank" + rel="noopener noreferrer" href={item.url} >src/consts/app.ts (1)
6-8: Some hardcoded colors exist as theme tokens and should be refactored.The hex values in this file are a bit of a mixed bunch, really.
#F8F8FFmaps tocream.300and#E8CAFFmaps toprimary.50in the Tailwind theme, so those could pull fromColor.cream['300']andColor.primary['50']respectively to stay consistent with howBRAND_COLORworks on line 5.However,
#F2E4FFand#F8F2FFaren't defined in the theme, so they'll need to stay as hardcoded values unless those colors are added to the Tailwind config first.
- Header: add `relative` positioning, `aria-label` on logo link - Nav: add `rel="noopener noreferrer"` to external links - FeeSectionButton: reset dot count on load end, fix hover on disabled state - TokenSelectField: fix false class string in DOM (use ternary) - TransfersDetailsModal: remove stray `items` class - _app.tsx: remove stale comment about old next/font approach - fetch-fonts.mjs: use console.error for unexpected errors, simplify stream pipeline Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/nav/Header.tsx`:
- Around line 16-18: The mobile logo Link in Header.tsx (the Link wrapping
Image/Logo) is missing an accessible name; update that Link component to include
an accessible label (e.g., aria-label="Homepage") so screen readers announce its
purpose (the same pattern used for the desktop logo Link) — locate the Link
around the Image (Logo) and add the aria-label attribute to it.
🧹 Nitpick comments (2)
src/features/transfer/FeeSectionButton.tsx (2)
6-23: Consider refactoring to avoid synchronoussetStateinside the effectLook, I get it — this hook works just fine in me swamp and yours. But the ESLint
react-hooks/set-state-in-effectrule is grumbling aboutsetDotCount(1)being called synchronously in the effect body (line 11), which can cause cascading renders. Since the caller only readsloadingTextwhenisLoadingis true anyway, you could sidestep the whole thing with auseReffor the counter and a tick-driven state bump:♻️ Suggested refactor using useRef
-function useLoadingDots(isLoading: boolean, intervalMs = 1000) { - const [dotCount, setDotCount] = useState(1); - - useEffect(() => { - if (!isLoading) { - setDotCount(1); - return; - } - - const interval = setInterval(() => { - setDotCount((prev) => (prev % 3) + 1); - }, intervalMs); - - return () => clearInterval(interval); - }, [isLoading, intervalMs]); - - return 'Loading' + '.'.repeat(dotCount); -} +function useLoadingDots(isLoading: boolean, intervalMs = 1000) { + const dotCountRef = useRef(1); + const [, tick] = useState(0); + + useEffect(() => { + if (!isLoading) { + dotCountRef.current = 1; + return; + } + + const interval = setInterval(() => { + dotCountRef.current = (dotCountRef.current % 3) + 1; + tick((t) => t + 1); + }, intervalMs); + + return () => clearInterval(interval); + }, [isLoading, intervalMs]); + + return 'Loading' + '.'.repeat(dotCountRef.current); +}(You'd need to add
useRefto the React import on line 3.)Alternatively, if you'd rather keep things simple and the team is fine suppressing the rule for this isolated case, an inline
// eslint-disable-next-linewould do — just make sure that's a conscious choice, not something lurking in the swamp unnoticed.
47-56:pointer-events-nonemakescursor-defaulta no-op and is redundant withdisabledNow, I'm not one to tell folks how to decorate their swamp, but there are a few layers here doing the same job. When
!isClickable:
disabledalready prevents click events and signals non-interactivity to assistive tech.pointer-events-nonekills all pointer events — meaningcursor-defaulton the same branch never actually takes effect (no pointer → no cursor style).- The hover styles (
hover:text-gray-900, etc.) are already absent from the non-clickable branch, sopointer-events-noneisn't needed to suppress them.
pointer-events-nonecan also bite you later if someone adds a tooltip to explain why fees aren't shown — the tooltip won't fire. Consider droppingpointer-events-noneandcursor-defaultin favor of just relying ondisabled:♻️ Simplified class string
- className={`flex w-fit items-center font-secondary text-xxs text-gray-700 [&_path]:fill-gray-700 ${isClickable ? 'hover:text-gray-900 [&_path]:hover:fill-gray-900' : 'cursor-default pointer-events-none'}`} + className={`flex w-fit items-center font-secondary text-xxs text-gray-700 [&_path]:fill-gray-700 ${isClickable ? 'hover:text-gray-900 [&_path]:hover:fill-gray-900 cursor-pointer' : ''}`}
| <Link href="/"> | ||
| <Image src={Logo} width={36} alt="" className="h-auto" /> | ||
| </Link> |
There was a problem hiding this comment.
Mobile logo link is still wandering around without a name tag.
The desktop logo link got its aria-label="Homepage" (line 34), but this mobile counterpart was left out of that party. Screen readers will just announce "link" with nothing useful. Same swamp, different day.
🛡️ Proposed fix
- <Link href="/">
+ <Link href="/" aria-label="Homepage">
<Image src={Logo} width={36} alt="" className="h-auto" />
</Link>🤖 Prompt for AI Agents
In `@src/components/nav/Header.tsx` around lines 16 - 18, The mobile logo Link in
Header.tsx (the Link wrapping Image/Logo) is missing an accessible name; update
that Link component to include an accessible label (e.g., aria-label="Homepage")
so screen readers announce its purpose (the same pattern used for the desktop
logo Link) — locate the Link around the Image (Logo) and add the aria-label
attribute to it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Cherry-picks the visual/branding changes from the
xaroz/warp-ui-v2branch — only styling, no behavioral changes.font-primary/font-secondarylgto avoid Intercom overlap)font-secondaryapplied to form labels, buttons, token select, and transfer detail modalscripts/fetch-fonts.mjs) for S3-hosted fonts🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Style