Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 15 minutes and 8 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis PR introduces white-label branding capabilities with theme-aware logos and accent colors, adds reusable UI component primitives, refactors the design system with new CSS tokens and gradients, adds Linux ARM64 support for the Solc binary target, and updates the CI workflow to use explicit Rust toolchain versions. Changes
Sequence DiagramsequenceDiagram
participant User
participant BrandingContext as Branding Context
participant ColorUtils as Color Utils
participant DOM as Document/CSS
participant UIComponent as UI Component
User->>BrandingContext: App starts (config + theme)
BrandingContext->>ColorUtils: deriveBrandTokens(bgHex, accentHex, mode)
ColorUtils->>ColorUtils: Mix accent with surfaces<br/>Generate gradients
ColorUtils-->>BrandingContext: tokens: DerivedBrandTokens
BrandingContext->>DOM: applyBrandTokens(tokens)
DOM->>DOM: Set --color-brand-*<br/>Set --page-gradient vars
BrandingContext->>UIComponent: Render with CSS variables
UIComponent-->>User: Display branded UI
Note over BrandingContext: On accent change:<br/>recalculate + reapply tokens
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
# Conflicts: # frontend/src/components/Layout.tsx # frontend/src/components/SearchBar.tsx # frontend/src/components/index.ts # frontend/src/pages/TransactionDetailPage.tsx
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/index.css (1)
156-165:⚠️ Potential issue | 🟠 MajorOverlapping placeholder rules can darken placeholder text on the default (light) theme.
Line 156–159 correctly targets dark mode only. Line 162–165 targets
:root:not([data-theme='light']), which also matches the default:rootstate when nodata-themeattribute is present yet (e.g., initial HTML before JS sets the attribute). Since:roothere defines the light palette, that selector will render a white placeholder on light inputs during the brief pre-hydration window (and permanently for any deployment that forgets to setdata-theme). Drop the broader:not([data-theme='light'])rule and rely on the explicit[data-theme='dark']variant, or ensure the document always boots withdata-theme="light"or"dark"set before first paint.🛠️ Proposed fix
:root[data-theme='dark'] input::placeholder, :root[data-theme='dark'] textarea::placeholder { color: rgb(255 255 255 / 0.72); } -} - - /* Match form input placeholder brightness to search bar in dark mode. */ - :root:not([data-theme='light']) input::placeholder, - :root:not([data-theme='light']) textarea::placeholder { - color: rgb(255 255 255 / 0.72); - } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/index.css` around lines 156 - 165, The overlapping placeholder rules cause white placeholders on the default/light theme; remove or replace the broader selectors `:root:not([data-theme='light']) input::placeholder` and `:root:not([data-theme='light']) textarea::placeholder` so they only target dark mode (i.e., use the existing `:root[data-theme='dark'] input::placeholder` / `textarea::placeholder` selector), ensuring you either delete the `:not([data-theme='light'])` block or change its selector to `:root[data-theme='dark']` to avoid applying dark placeholder styles when no data-theme is set.
🧹 Nitpick comments (12)
frontend/src/components/Pagination.tsx (1)
58-73: Optional: render the ellipsis as a non-interactive element.The
'...'separator is a disabled<button>, so assistive tech still announces it as a button. Consider rendering a<span>(witharia-hidden="true") for the ellipsis and keeping buttons only for real page targets. Purely a polish item; keyboard focus is already blocked viadisabled.♻️ Sketch
- {getPageNumbers().map((page, index) => ( - <button - key={index} - onClick={() => typeof page === 'number' && onPageChange(page)} - disabled={page === '...'} - className={`btn min-w-[40px] ${ - page === currentPage - ? 'btn-primary' - : page === '...' - ? 'bg-transparent cursor-default text-fg-subtle' - : 'btn-secondary' - }`} - > - {page} - </button> - ))} + {getPageNumbers().map((page, index) => + page === '...' ? ( + <span + key={`gap-${index}`} + aria-hidden="true" + className="min-w-[40px] text-center text-fg-subtle" + > + … + </span> + ) : ( + <button + key={page} + onClick={() => onPageChange(page)} + aria-current={page === currentPage ? 'page' : undefined} + className={`btn min-w-[40px] ${page === currentPage ? 'btn-primary' : 'btn-secondary'}`} + > + {page} + </button> + ), + )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/Pagination.tsx` around lines 58 - 73, The ellipsis items from getPageNumbers() are currently rendered as disabled <button>s which remain exposed to assistive tech; change the rendering so that when page === '...' you render a non-interactive <span> (e.g., <span aria-hidden="true">...</span>) with the same visual classes used for the disabled state, and only render <button>s for numeric pages that call onPageChange(page) and use currentPage to determine the active class; update the rendering logic in the component where getPageNumbers(), onPageChange, and currentPage are used so the ellipsis is not a button and is marked aria-hidden.frontend/src/pages/BlockTransactionsPage.tsx (1)
16-31: Avoid<br/>inside the hero title; use two-line layout via structure.
PageHerorenderstitleinside an<h1 className="page-title">. A<br>inside a heading is valid HTML but is awkward for screen readers and responsive layouts — the line break is forced regardless of viewport. Consider a small span-based structure, or collapse to a single-line title likeBlock #{n} — Transactions.♻️ Suggested tweak
- title={ - <> - Block #{formatNumber(blockNumber || 0)} - <br /> - Transactions - </> - } + title={`Transactions in Block #${formatNumber(blockNumber || 0)}`}Also note: when
blockNumberisundefinedthe title falls back to#0, which would mis-render on an invalid route. Consider guarding against the missing param earlier.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/BlockTransactionsPage.tsx` around lines 16 - 31, The hero title uses a forced <br/> which hurts accessibility and responsiveness; change the PageHero title so it renders semantic, multi-line content via structure or punctuation (e.g., two inline spans or "Block #{...} — Transactions") instead of a literal line break: update the title prop passed to PageHero (and the EntityHeroVisual usage is fine) to use a split/span layout or single-line separator and remove the <br/>; additionally, guard the blockNumber/formatNumber usage by checking blockNumber is defined (e.g., early-return or conditional render) so you don't render "Block `#0`" when the route param is missing, referencing the blockNumber variable and formatNumber(...) call to locate the code to change.frontend/src/pages/AddressesPage.tsx (2)
215-269: Duplicated inline pagination instead of the sharedPaginationcomponent.Other pages in this PR (e.g.,
TokensPage,NFTsPage,BlockTransactionsPage) continue to use the sharedPaginationcomponent, whileAddressesPagenow hand-rolls a first/prev/range/next/last control. This introduces drift: behavior/styling fixes toPaginationwon't propagate here, and the range label logic (Lines 241–243) is easy to get wrong on the last page whenaddresses.length < limit.If the goal was the pill-shaped container styling or the range readout, consider folding that into
Paginationitself so all pages benefit, rather than diverging in this one page.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/AddressesPage.tsx` around lines 215 - 269, AddressesPage contains a duplicated, hand-rolled pagination UI (the button group and range label using addresses, pagination, setPage, formatNumber) instead of using the shared Pagination component; replace the inline control in AddressesPage with the existing Pagination component (or extend Pagination to support the pill-shaped container and range readout) so styling/behavior is centralized, ensure Pagination receives pagination, page, setPage, and addresses.length (or limit) to compute the correct range display (fixing the last-page case where addresses.length < limit), and remove the duplicated JSX/buttons so TokensPage/NFTsPage/BlockTransactionsPage behavior stays consistent.
104-113: Minor a11y/UX: "Live On/Off" label loses iconography.The previous SVG-based toggle conveyed state at a glance. Plain text "Live On" / "Live Off" is functional but lower affordance, and "Live Off" next to a primary/secondary button color swap is the main state signal now. Consider adding a small pulse dot or keeping an icon alongside the label for scanability.
aria-pressedis correctly set, so a11y is fine.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/AddressesPage.tsx` around lines 104 - 113, The Live On/Off button currently uses plain text which reduces visual affordance; update the button rendered in the AddressesPage so it includes a small decorative state indicator (e.g., an inline SVG or a <span> dot) next to the text that reflects autoRefresh (pulse/green when true, grey when false), keep the current text label derived from autoRefresh, ensure the decorative icon has aria-hidden="true" so screen readers still rely on aria-pressed, and use the existing setAutoRefresh and autoRefresh identifiers to implement the conditional classes/styles for the icon.frontend/src/App.tsx (1)
27-29: Consider using the sharedLoadingspinner for route fallbacks.A text-only
kickerplaceholder is a regression in perceived responsiveness compared to the rebrandedLoadingspinner already used elsewhere. Since this fallback is shown on every lazy route transition, a visual spinner is likely more consistent with the rest of the app.♻️ Optional refactor
- <div className="card flex h-64 items-center justify-center"> - <span className="kicker">Loading route</span> - </div> + <div className="card flex h-64 items-center justify-center"> + <Loading size="md" /> + </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.tsx` around lines 27 - 29, Replace the text-only kicker placeholder with the shared Loading spinner component: inside the JSX block that renders the route fallback (the div with className "card flex h-64 items-center justify-center"), remove the <span className="kicker">Loading route</span> and render the Loading component instead (e.g., <Loading />), and add the corresponding import for Loading at the top of the file; keep the existing wrapper div and its classes so the spinner preserves the same sizing/alignment for lazy route transitions.frontend/src/pages/WelcomePage.tsx (1)
20-29: Optional: cancel the chain‑age fetch on unmount.
getBlockByNumber(1)is fired once on mount with no cancellation flag, sosetChainAgecan run after the component unmounts if navigation happens mid‑flight. Acancelledref/flag (matching the pattern used inTransactionDetailPage.tsxlines 30–66) would make this consistent with the rest of the codebase.♻️ Suggested pattern
- useEffect(() => { - getBlockByNumber(1).then((block) => { - if (!block.timestamp) return; - const now = Math.floor(Date.now() / 1000); - const seconds = now - block.timestamp; - const days = Math.floor(seconds / 86400); - setChainAge(`${days} days`); - }).catch(() => {}); - }, []); + useEffect(() => { + let cancelled = false; + getBlockByNumber(1) + .then((block) => { + if (cancelled || !block?.timestamp) return; + const now = Math.floor(Date.now() / 1000); + const days = Math.floor((now - block.timestamp) / 86400); + setChainAge(`${days} days`); + }) + .catch(() => {}); + return () => { cancelled = true; }; + }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/WelcomePage.tsx` around lines 20 - 29, The effect calling getBlockByNumber(1) can set state after unmount; update the useEffect in WelcomePage.tsx to use the same cancellation pattern as TransactionDetailPage (create a ref/flag like cancelledRef), set cancelledRef.current = false on mount and true in the cleanup function, and before calling setChainAge check the flag (and avoid state updates if cancelled); ensure the promise rejection handling still swallows/errors appropriately but does not call setChainAge after unmount.frontend/src/pages/StatusPage.tsx (2)
259-268:StatusStatwrapper is redundant.
StatusStatnow just forwardslabelandvaluestraight toStatCardwith no additional behavior. InlineStatCardat the call sites (lines 103–108) and drop theStatusStathelper + its props interface.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/StatusPage.tsx` around lines 259 - 268, Remove the redundant StatusStat wrapper and its props interface StatusStatProps: replace all uses of <StatusStat label={...} value={...} /> with direct <StatCard label={...} value={...} /> calls (at the existing call sites) and delete the StatusStat function and StatusStatProps interface declaration; ensure imports/exports remain correct and run a quick typecheck to remove any leftover references to StatusStat or StatusStatProps.
341-354: Dead branches informatBucketTick.
WINDOWSonly exposes'1h' | '6h' | '24h' | '7d' | '1m', so the'6m'and'1y'branches (and their entries inBUCKET_MS) are currently unreachable from this page. If these windows are intentionally reserved for a future UI, leave a comment; otherwise drop them to avoid confusing maintainers. Note that this is pre-existing and unchanged by the PR, so feel free to defer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/StatusPage.tsx` around lines 341 - 354, The function formatBucketTick contains unreachable branches for '6m' and '1y' (and BUCKET_MS entries) because ChartWindow only allows '1h' | '6h' | '24h' | '7d' | '1m'; remove the '6m' and '1y' cases (and corresponding BUCKET_MS entries) to eliminate dead code, or if those window values are intentionally reserved, add a clear comment near formatBucketTick (and BUCKET_MS) stating they are kept for future UI support so maintainers know they are intentionally present.frontend/src/pages/BlocksPage.tsx (2)
87-108: Fresh-block state update looks correct; one small tidy-up.The add + auto-expire flow (build
newlyPrepended, seedfreshBlocks, schedule 1400ms timeouts with de-duped clearing of stale timers, and cleanup viafreshBlockTimeoutsRef) is race-safe with the RAF batching above. One tidy-up: the per-block add loop (95–107) already accumulates intofreshBlockTimeoutsRef, so the precedingsetFreshBlocks(88–94) is redundant with the fact that the timeout will also callsetFreshBlocks. It's fine as-is for immediate visual feedback, but worth a one-line comment explaining that 88–94 provides the initial "on" state while 95–107 schedules the "off" transition, to aid future readers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/BlocksPage.tsx` around lines 87 - 108, Add a one-line clarifying comment above the setFreshBlocks call that seeds the immediate "on" state for blocks (when handling newlyPrepended) and note that the subsequent loop using freshBlockTimeoutsRef schedules the "off" transition and also de-dupes/cleans up timers; reference the newlyPrepended loop, setFreshBlocks, and freshBlockTimeoutsRef so future readers understand why both the immediate state update and the timeout-based state removal are present.
113-135: Minor race between fresh-block reset RAF and a concurrent SSE prepend.When
page !== 1 || !autoRefresh, this effect clears pending per-block timeouts and schedulessetFreshBlocks(new Set())on the next frame. IfautoRefreshis toggled off and then on again within the same frame (or a queued SSE event is already flushing), thesetFreshBlocks(new Set())from the reset path can land after new entries are added, briefly wiping freshly-prepended block highlights. Consider clearingfreshBlockssynchronously here (no RAF needed, since it's only reached when fresh highlights should be off) to remove the race.♻️ Suggested simplification
if (page !== 1 || !autoRefresh) { bufferedDaBlocksRef.current = new Set(); for (const [, timeoutId] of freshBlockTimeoutsRef.current) clearTimeout(timeoutId); freshBlockTimeoutsRef.current.clear(); - freshBlocksResetRafRef.current = window.requestAnimationFrame(() => { - setFreshBlocks((prev) => (prev.size === 0 ? prev : new Set())); - freshBlocksResetRafRef.current = null; - }); + setFreshBlocks((prev) => (prev.size === 0 ? prev : new Set())); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/BlocksPage.tsx` around lines 113 - 135, The effect in BlocksPage that runs when page !== 1 || !autoRefresh currently schedules clearing fresh highlights via freshBlocksResetRafRef and requestAnimationFrame, which can race with concurrent SSE prepends; instead cancel any pending RAF, clear freshBlockTimeoutsRef.current, set bufferedDaBlocksRef.current = new Set(), and call setFreshBlocks(() => new Set()) synchronously (and set freshBlocksResetRafRef.current = null) so highlights are cleared immediately; update the logic around freshBlocksResetRafRef, freshBlockTimeoutsRef, bufferedDaBlocksRef, and setFreshBlocks inside the useEffect to remove the RAF-based reset path.frontend/src/index.css (1)
246-250: Ornament/hero overrides split awkwardly between dark and:not(dark)selectors.
:root[data-theme='dark'] .brand-ornament, :root[data-theme='dark'] .entity-hero-visual(246–250) and the light-mode counterpart:root:not([data-theme='dark']) .brand-ornament, :root:not([data-theme='dark']) .entity-hero-visual(407–413) setborder-colorandbox-shadowto nearly identical values, with only thebackgrounddiffering. You can fold the shared declarations into the base.brand-ornament/.entity-hero-visualrules (283–288, 332–337) and only theme-override thebackground. This also avoids the same subtle bug as the placeholder rule::not([data-theme='dark'])matches the attribute-less initial state.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/index.css` around lines 246 - 250, The dark- and light-theme rules for .brand-ornament and .entity-hero-visual duplicate border-color and box-shadow; move the shared declarations (border-color, box-shadow) into the base .brand-ornament and .entity-hero-visual rules and leave only the differing background in the theme-specific blocks (currently :root[data-theme='dark'] .brand-ornament/.entity-hero-visual and :root:not([data-theme='dark']) .brand-ornament/.entity-hero-visual), and replace the :not([data-theme='dark']) selector with an explicit theme selector such as :root[data-theme='light'] or only override background where needed to avoid matching the attribute-less initial state.frontend/src/components/Layout.tsx (1)
68-157: Duplicated nav rendering between desktop and mobile.The
NAV_ITEMS.map(...)block plus the conditional FaucetNavLinkis rendered identically in both the desktop nav (lines 68–79) and the mobile nav (lines 146–157). Consider extracting a small inline component / render helper (or computing a singleitemsarray that optionally includes faucet) so there's one source of truth for link markup.♻️ Proposed refactor
+ const navItems = faucet.enabled + ? [...NAV_ITEMS, { to: '/faucet', label: 'Faucet' }] + : NAV_ITEMS; + + const renderNavLinks = () => + navItems.map((item) => ( + <NavLink key={item.to} to={item.to} className={navLinkClass}> + {item.label} + </NavLink> + ));Then use
{renderNavLinks()}inside both<nav>elements.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/Layout.tsx` around lines 68 - 157, The desktop and mobile nav both duplicate the NAV_ITEMS.map(...) + conditional Faucet NavLink; extract that mapping into a single render helper (e.g., function renderNavLinks() or const navLinks = computeNavLinks()) that uses NAV_ITEMS, faucet.enabled and navLinkClass to produce the same set of <NavLink> elements, then replace both inline NAV_ITEMS.map(...) blocks in Layout (the duplicated blocks around nav elements) with a call to the helper so there is one source of truth for link markup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/crates/atlas-server/src/api/handlers/contracts.rs`:
- Around line 399-412: The solc_binary_target function currently assumes
linux-arm64 binaries exist for all versions; update it to either accept a
compiler version and validate availability or perform a runtime availability
check against Solidity's list.json and return a clearer error: change fn
solc_binary_target(os: &str, arch: &str) -> Result<&'static str, AtlasError> to
include a version parameter (e.g., version: &str) or add a helper that
fetches/parses list.json, then for ("linux","aarch64") only return
Ok("linux-arm64") when the requested version is present (otherwise fall back to
"linux-amd64" or return AtlasError::Verification with a message that the
requested version is not available for ARM64), and update callers of
solc_binary_target accordingly so the Verification error distinguishes
platform-unsupported vs version-unavailable.
In `@backend/crates/atlas-server/src/main.rs`:
- Around line 120-123: Add a unit test in this file's #[cfg(test)] mod tests
that verifies the new empty-"dbname" branch: construct a connection URL like
"postgres://user:pass@host:5432/?dbname=" (and another case with a path DB name
present) then call the same parsing logic that sets database_name (referencing
the database_name variable/logic in main.rs) and assert that an empty dbname
query param is ignored (database_name stays unchanged or uses the path DB name
as appropriate). Make the test call the parsing/path-selection code exercised by
the match arm for "dbname" so it covers the branch and add assertions for both
the empty and non-empty dbname cases.
In `@frontend/src/components/BrandPrimitives.tsx`:
- Around line 129-142: BrandOrnament currently hardcodes the "Atlas" /
"Explorer" labels; update BrandOrnament to read the brand name from the
BrandingContext by importing and calling useBranding() (same pattern as
Layout.tsx) and render the returned chainName instead of the two static
spans—e.g., get const { chainName } = useBranding() inside BrandOrnament and
replace the hardcoded <span>Atlas</span><span>Explorer</span> with the chainName
rendered (if you need two-line styling, split chainName on whitespace or render
it in one element but preserve the existing className "brand-ornament__label").
Ensure the component still accepts the compact prop and keeps the same wrapper
classes.
In `@frontend/src/index.css`:
- Around line 705-731: Stylelint is failing due to camelCase keyframe names and
missing blank lines before declarations; rename the keyframes highlightFade,
livePulse, fadeInUp to kebab-case (highlight-fade, live-pulse, fade-in-up) and
update any references to those animations, and run the repo stylelint autofix
(e.g., bunx stylelint --fix) or manually add the required
declaration-empty-line-before blank lines and fix import/quote notation issues
flagged elsewhere in the file so the CSS conforms to the project's Stylelint
rules.
- Line 1: The CSS currently uses an external `@import` url(...) in
frontend/src/index.css which is render-blocking and leaks requests to Google;
replace this by self-hosting the font files (add woff2/woff assets to the repo)
and declare them with `@font-face` in frontend/src/index.css including
font-display: swap, updating the font-family usages to the same names, or if you
must keep remote fonts move the request into index.html using <link
rel="preconnect"> + <link rel="stylesheet"> instead of `@import`; ensure the new
approach points to local asset paths and that build/static asset pipeline serves
the fonts.
In `@frontend/src/pages/StatusPage.tsx`:
- Around line 284-287: Replace the hardcoded black spinner border so it remains
visible in dark mode: in the StatusPage.tsx loading JSX (the inner div with
className "w-5 h-5 border-2 border-black border-t-transparent rounded-full
animate-spin"), remove "border-black" and use a theme-aware approach such as
"border-current" and add a color token class like "text-fg" (or "text-fg-subtle"
if you prefer a subtler tone) on that spinner element (or its container) so the
spinner uses currentColor and is visible in both light and dark themes.
In `@frontend/src/pages/TransactionDetailPage.tsx`:
- Around line 131-142: The EmptyState is rendered before the PageHero causing
inconsistent layout with other detail pages; in TransactionDetailPage.tsx move
the PageHero (and its visual EntityHeroVisual) so it renders unconditionally
before the conditional EmptyState, then render EmptyState only when !txLoading
&& !transaction (using txError?.error for the description) to mirror
BlockDetailPage's order; ensure you keep the existing props (compact,
title="Transaction") on PageHero and leave the EmptyState conditional unchanged.
---
Outside diff comments:
In `@frontend/src/index.css`:
- Around line 156-165: The overlapping placeholder rules cause white
placeholders on the default/light theme; remove or replace the broader selectors
`:root:not([data-theme='light']) input::placeholder` and
`:root:not([data-theme='light']) textarea::placeholder` so they only target dark
mode (i.e., use the existing `:root[data-theme='dark'] input::placeholder` /
`textarea::placeholder` selector), ensuring you either delete the
`:not([data-theme='light'])` block or change its selector to
`:root[data-theme='dark']` to avoid applying dark placeholder styles when no
data-theme is set.
---
Nitpick comments:
In `@frontend/src/App.tsx`:
- Around line 27-29: Replace the text-only kicker placeholder with the shared
Loading spinner component: inside the JSX block that renders the route fallback
(the div with className "card flex h-64 items-center justify-center"), remove
the <span className="kicker">Loading route</span> and render the Loading
component instead (e.g., <Loading />), and add the corresponding import for
Loading at the top of the file; keep the existing wrapper div and its classes so
the spinner preserves the same sizing/alignment for lazy route transitions.
In `@frontend/src/components/Layout.tsx`:
- Around line 68-157: The desktop and mobile nav both duplicate the
NAV_ITEMS.map(...) + conditional Faucet NavLink; extract that mapping into a
single render helper (e.g., function renderNavLinks() or const navLinks =
computeNavLinks()) that uses NAV_ITEMS, faucet.enabled and navLinkClass to
produce the same set of <NavLink> elements, then replace both inline
NAV_ITEMS.map(...) blocks in Layout (the duplicated blocks around nav elements)
with a call to the helper so there is one source of truth for link markup.
In `@frontend/src/components/Pagination.tsx`:
- Around line 58-73: The ellipsis items from getPageNumbers() are currently
rendered as disabled <button>s which remain exposed to assistive tech; change
the rendering so that when page === '...' you render a non-interactive <span>
(e.g., <span aria-hidden="true">...</span>) with the same visual classes used
for the disabled state, and only render <button>s for numeric pages that call
onPageChange(page) and use currentPage to determine the active class; update the
rendering logic in the component where getPageNumbers(), onPageChange, and
currentPage are used so the ellipsis is not a button and is marked aria-hidden.
In `@frontend/src/index.css`:
- Around line 246-250: The dark- and light-theme rules for .brand-ornament and
.entity-hero-visual duplicate border-color and box-shadow; move the shared
declarations (border-color, box-shadow) into the base .brand-ornament and
.entity-hero-visual rules and leave only the differing background in the
theme-specific blocks (currently :root[data-theme='dark']
.brand-ornament/.entity-hero-visual and :root:not([data-theme='dark'])
.brand-ornament/.entity-hero-visual), and replace the :not([data-theme='dark'])
selector with an explicit theme selector such as :root[data-theme='light'] or
only override background where needed to avoid matching the attribute-less
initial state.
In `@frontend/src/pages/AddressesPage.tsx`:
- Around line 215-269: AddressesPage contains a duplicated, hand-rolled
pagination UI (the button group and range label using addresses, pagination,
setPage, formatNumber) instead of using the shared Pagination component; replace
the inline control in AddressesPage with the existing Pagination component (or
extend Pagination to support the pill-shaped container and range readout) so
styling/behavior is centralized, ensure Pagination receives pagination, page,
setPage, and addresses.length (or limit) to compute the correct range display
(fixing the last-page case where addresses.length < limit), and remove the
duplicated JSX/buttons so TokensPage/NFTsPage/BlockTransactionsPage behavior
stays consistent.
- Around line 104-113: The Live On/Off button currently uses plain text which
reduces visual affordance; update the button rendered in the AddressesPage so it
includes a small decorative state indicator (e.g., an inline SVG or a <span>
dot) next to the text that reflects autoRefresh (pulse/green when true, grey
when false), keep the current text label derived from autoRefresh, ensure the
decorative icon has aria-hidden="true" so screen readers still rely on
aria-pressed, and use the existing setAutoRefresh and autoRefresh identifiers to
implement the conditional classes/styles for the icon.
In `@frontend/src/pages/BlocksPage.tsx`:
- Around line 87-108: Add a one-line clarifying comment above the setFreshBlocks
call that seeds the immediate "on" state for blocks (when handling
newlyPrepended) and note that the subsequent loop using freshBlockTimeoutsRef
schedules the "off" transition and also de-dupes/cleans up timers; reference the
newlyPrepended loop, setFreshBlocks, and freshBlockTimeoutsRef so future readers
understand why both the immediate state update and the timeout-based state
removal are present.
- Around line 113-135: The effect in BlocksPage that runs when page !== 1 ||
!autoRefresh currently schedules clearing fresh highlights via
freshBlocksResetRafRef and requestAnimationFrame, which can race with concurrent
SSE prepends; instead cancel any pending RAF, clear
freshBlockTimeoutsRef.current, set bufferedDaBlocksRef.current = new Set(), and
call setFreshBlocks(() => new Set()) synchronously (and set
freshBlocksResetRafRef.current = null) so highlights are cleared immediately;
update the logic around freshBlocksResetRafRef, freshBlockTimeoutsRef,
bufferedDaBlocksRef, and setFreshBlocks inside the useEffect to remove the
RAF-based reset path.
In `@frontend/src/pages/BlockTransactionsPage.tsx`:
- Around line 16-31: The hero title uses a forced <br/> which hurts
accessibility and responsiveness; change the PageHero title so it renders
semantic, multi-line content via structure or punctuation (e.g., two inline
spans or "Block #{...} — Transactions") instead of a literal line break: update
the title prop passed to PageHero (and the EntityHeroVisual usage is fine) to
use a split/span layout or single-line separator and remove the <br/>;
additionally, guard the blockNumber/formatNumber usage by checking blockNumber
is defined (e.g., early-return or conditional render) so you don't render "Block
`#0`" when the route param is missing, referencing the blockNumber variable and
formatNumber(...) call to locate the code to change.
In `@frontend/src/pages/StatusPage.tsx`:
- Around line 259-268: Remove the redundant StatusStat wrapper and its props
interface StatusStatProps: replace all uses of <StatusStat label={...}
value={...} /> with direct <StatCard label={...} value={...} /> calls (at the
existing call sites) and delete the StatusStat function and StatusStatProps
interface declaration; ensure imports/exports remain correct and run a quick
typecheck to remove any leftover references to StatusStat or StatusStatProps.
- Around line 341-354: The function formatBucketTick contains unreachable
branches for '6m' and '1y' (and BUCKET_MS entries) because ChartWindow only
allows '1h' | '6h' | '24h' | '7d' | '1m'; remove the '6m' and '1y' cases (and
corresponding BUCKET_MS entries) to eliminate dead code, or if those window
values are intentionally reserved, add a clear comment near formatBucketTick
(and BUCKET_MS) stating they are kept for future UI support so maintainers know
they are intentionally present.
In `@frontend/src/pages/WelcomePage.tsx`:
- Around line 20-29: The effect calling getBlockByNumber(1) can set state after
unmount; update the useEffect in WelcomePage.tsx to use the same cancellation
pattern as TransactionDetailPage (create a ref/flag like cancelledRef), set
cancelledRef.current = false on mount and true in the cleanup function, and
before calling setChainAge check the flag (and avoid state updates if
cancelled); ensure the promise rejection handling still swallows/errors
appropriately but does not call setChainAge after unmount.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 98b358ab-d9cd-4080-a376-39d8a898c4ee
⛔ Files ignored due to path filters (2)
frontend/src/assets/evolve-logo-light.svgis excluded by!**/*.svgfrontend/src/assets/evolve-logo.svgis excluded by!**/*.svg
📒 Files selected for processing (34)
.env.example.github/workflows/ci.ymlbackend/crates/atlas-server/src/api/handlers/contracts.rsbackend/crates/atlas-server/src/main.rsdocker-compose.ymlfrontend/src/App.tsxfrontend/src/assets/defaultLogos.tsfrontend/src/components/BrandPrimitives.tsxfrontend/src/components/CopyButton.tsxfrontend/src/components/Error.tsxfrontend/src/components/Layout.tsxfrontend/src/components/Loading.tsxfrontend/src/components/Pagination.tsxfrontend/src/components/SearchBar.tsxfrontend/src/components/index.tsfrontend/src/context/BrandingContext.tsxfrontend/src/hooks/useChartColors.tsfrontend/src/index.cssfrontend/src/pages/AddressesPage.tsxfrontend/src/pages/BlockDetailPage.tsxfrontend/src/pages/BlockTransactionsPage.tsxfrontend/src/pages/BlocksPage.tsxfrontend/src/pages/FaucetPage.tsxfrontend/src/pages/NFTsPage.tsxfrontend/src/pages/NotFoundPage.tsxfrontend/src/pages/SearchResultsPage.tsxfrontend/src/pages/StatusPage.tsxfrontend/src/pages/TokensPage.tsxfrontend/src/pages/TransactionDetailPage.tsxfrontend/src/pages/TransactionsPage.tsxfrontend/src/pages/WelcomePage.tsxfrontend/src/utils/color.test.tsfrontend/src/utils/color.tsfrontend/tailwind.config.js
Summary
Summary by CodeRabbit
Release Notes
New Features
Style
Chores