Skip to content

feat: rebrand atlas frontend#63

Open
pthmas wants to merge 6 commits intomainfrom
pthmas/evolve-rebrand
Open

feat: rebrand atlas frontend#63
pthmas wants to merge 6 commits intomainfrom
pthmas/evolve-rebrand

Conversation

@pthmas
Copy link
Copy Markdown
Collaborator

@pthmas pthmas commented Apr 20, 2026

Summary

  • rework the frontend shell and shared visual primitives around the new Evolve-inspired Atlas design
  • refresh the homepage, explorer page heroes, search, tables, and icons to match the new brand direction
  • restore white-label compatibility for logos and theme-derived colors so custom deployments still drive the UI

Summary by CodeRabbit

Release Notes

  • New Features

    • Added reusable design components for consistent UI patterns across the app
    • Implemented theme-aware default logo system for light/dark modes
    • Added support for Linux ARM64 architecture
    • Introduced configurable search bar with multiple layout variants
    • Enhanced brand token customization system for accent colors
  • Style

    • Updated loading spinner and error card styling
    • Redesigned pagination and button components
    • Improved layout spacing and visual hierarchy across all pages
    • Enhanced copy button styling with modern design
  • Chores

    • Updated CI toolchain configuration
    • Added Docker platform configuration documentation

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@pthmas has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 8 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d12a219-8024-4265-a9a7-81a8172eab28

📥 Commits

Reviewing files that changed from the base of the PR and between 31f1636 and 56e07a8.

📒 Files selected for processing (7)
  • backend/crates/atlas-server/src/api/handlers/contracts.rs
  • backend/crates/atlas-server/src/main.rs
  • frontend/src/components/BrandPrimitives.tsx
  • frontend/src/index.css
  • frontend/src/pages/StatusPage.tsx
  • frontend/src/pages/TransactionDetailPage.tsx
  • frontend/tailwind.config.js
📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Configuration & Environment
.env.example, docker-compose.yml
Added DOCKER_DEFAULT_PLATFORM documentation with architecture examples (amd64, arm64). Removed hardcoded amd64 platform constraint from atlas-server service and added light/dark chain logo environment variables with empty string defaults.
CI/CD Workflow
.github/workflows/ci.yml
Updated all "Setup Rust" steps from dtolnay/rust-toolchain@stable to dtolnay/rust-toolchain@master with explicit with: toolchain: stable configuration, maintaining existing component specifications across all jobs.
Backend Platform Support
backend/crates/atlas-server/src/api/handlers/contracts.rs, backend/crates/atlas-server/src/main.rs
Added Linux ARM64 (aarch64) support in Solc binary target mapping to linux-arm64. Refactored dbname query parameter parsing in postgres connection config to use guard-based match arm with explicit empty-value handling.
Frontend Theme & Branding
frontend/src/index.css, frontend/src/context/BrandingContext.tsx, frontend/src/utils/color.ts, frontend/src/utils/color.test.ts, frontend/src/hooks/useChartColors.ts
Comprehensive redesign of theme tokens with new brand colors, gradients, and layout primitives. Added brand token derivation/application pipeline supporting accent-color-driven surface tinting. Updated chart color resolution to use CSS variables. Added test coverage for color utility functions.
Frontend UI Primitives
frontend/src/components/BrandPrimitives.tsx, frontend/src/components/Layout.tsx, frontend/src/components/Loading.tsx, frontend/src/components/CopyButton.tsx, frontend/src/components/Error.tsx, frontend/src/components/Pagination.tsx, frontend/src/components/SearchBar.tsx, frontend/src/components/index.ts
Introduced six reusable brand components (PageHero, StatCard, SectionPanel, EmptyState, BrandOrnament, EntityHeroVisual). Refactored Layout with shared navigation items, updated header/footer styling, and integrated default logo resolution. Updated SearchBar to support variant prop. Refined button, error display, and pagination styling.
Frontend Pages
frontend/src/pages/AddressesPage.tsx, frontend/src/pages/BlockDetailPage.tsx, frontend/src/pages/BlockTransactionsPage.tsx, frontend/src/pages/BlocksPage.tsx, frontend/src/pages/FaucetPage.tsx, frontend/src/pages/NFTsPage.tsx, frontend/src/pages/NotFoundPage.tsx, frontend/src/pages/SearchResultsPage.tsx, frontend/src/pages/StatusPage.tsx, frontend/src/pages/TokensPage.tsx, frontend/src/pages/TransactionDetailPage.tsx, frontend/src/pages/TransactionsPage.tsx, frontend/src/pages/WelcomePage.tsx
Systematically replaced inline header blocks, card markup, and stat displays with new reusable components (PageHero, StatCard, EmptyState, SectionPanel, EntityHeroVisual). Refactored BlocksPage fresh-block highlighting mechanism. Updated WelcomePage to fetch chain metadata and use theme-aware logo resolution.
Frontend Configuration
frontend/tailwind.config.js, frontend/src/assets/defaultLogos.ts
Added Geist Mono and new font display aliases. Introduced brand color palette extending Tailwind theme. Created logo module with theme-aware logo selection function.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested reviewers

  • tac0turtle

Poem

🐰 A brand new palette blooms so bright,
With tokens dancing dark and light,
From ARM64 to pristine cards—
The Explorer's art transcends the bards!
Each UI block now sings as one,
A white-label symphony, at last begun. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: rebrand atlas frontend' clearly and concisely summarizes the main change: a rebranding effort for the Atlas frontend. It is specific, directly related to the substantial frontend updates, and aligns with the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pthmas/evolve-rebrand

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

pthmas added 3 commits April 20, 2026 17:17
# Conflicts:
#	frontend/src/components/Layout.tsx
#	frontend/src/components/SearchBar.tsx
#	frontend/src/components/index.ts
#	frontend/src/pages/TransactionDetailPage.tsx
@pthmas pthmas marked this pull request as ready for review April 21, 2026 11:45
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Overlapping 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 :root state when no data-theme attribute is present yet (e.g., initial HTML before JS sets the attribute). Since :root here 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 set data-theme). Drop the broader :not([data-theme='light']) rule and rely on the explicit [data-theme='dark'] variant, or ensure the document always boots with data-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> (with aria-hidden="true") for the ellipsis and keeping buttons only for real page targets. Purely a polish item; keyboard focus is already blocked via disabled.

♻️ 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.

PageHero renders title inside 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 like Block #{n} — Transactions.

♻️ Suggested tweak
-        title={
-          <>
-            Block #{formatNumber(blockNumber || 0)}
-            <br />
-            Transactions
-          </>
-        }
+        title={`Transactions in Block #${formatNumber(blockNumber || 0)}`}

Also note: when blockNumber is undefined the 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 shared Pagination component.

Other pages in this PR (e.g., TokensPage, NFTsPage, BlockTransactionsPage) continue to use the shared Pagination component, while AddressesPage now hand-rolls a first/prev/range/next/last control. This introduces drift: behavior/styling fixes to Pagination won't propagate here, and the range label logic (Lines 241–243) is easy to get wrong on the last page when addresses.length < limit.

If the goal was the pill-shaped container styling or the range readout, consider folding that into Pagination itself 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-pressed is 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 shared Loading spinner for route fallbacks.

A text-only kicker placeholder is a regression in perceived responsiveness compared to the rebranded Loading spinner 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, so setChainAge can run after the component unmounts if navigation happens mid‑flight. A cancelled ref/flag (matching the pattern used in TransactionDetailPage.tsx lines 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: StatusStat wrapper is redundant.

StatusStat now just forwards label and value straight to StatCard with no additional behavior. Inline StatCard at the call sites (lines 103–108) and drop the StatusStat helper + 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 in formatBucketTick.

WINDOWS only exposes '1h' | '6h' | '24h' | '7d' | '1m', so the '6m' and '1y' branches (and their entries in BUCKET_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, seed freshBlocks, schedule 1400ms timeouts with de-duped clearing of stale timers, and cleanup via freshBlockTimeoutsRef) is race-safe with the RAF batching above. One tidy-up: the per-block add loop (95–107) already accumulates into freshBlockTimeoutsRef, so the preceding setFreshBlocks (88–94) is redundant with the fact that the timeout will also call setFreshBlocks. 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 schedules setFreshBlocks(new Set()) on the next frame. If autoRefresh is toggled off and then on again within the same frame (or a queued SSE event is already flushing), the setFreshBlocks(new Set()) from the reset path can land after new entries are added, briefly wiping freshly-prepended block highlights. Consider clearing freshBlocks synchronously 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) set border-color and box-shadow to nearly identical values, with only the background differing. You can fold the shared declarations into the base .brand-ornament / .entity-hero-visual rules (283–288, 332–337) and only theme-override the background. 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 Faucet NavLink is 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 single items array 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

📥 Commits

Reviewing files that changed from the base of the PR and between cf9cabb and 31f1636.

⛔ Files ignored due to path filters (2)
  • frontend/src/assets/evolve-logo-light.svg is excluded by !**/*.svg
  • frontend/src/assets/evolve-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (34)
  • .env.example
  • .github/workflows/ci.yml
  • backend/crates/atlas-server/src/api/handlers/contracts.rs
  • backend/crates/atlas-server/src/main.rs
  • docker-compose.yml
  • frontend/src/App.tsx
  • frontend/src/assets/defaultLogos.ts
  • frontend/src/components/BrandPrimitives.tsx
  • frontend/src/components/CopyButton.tsx
  • frontend/src/components/Error.tsx
  • frontend/src/components/Layout.tsx
  • frontend/src/components/Loading.tsx
  • frontend/src/components/Pagination.tsx
  • frontend/src/components/SearchBar.tsx
  • frontend/src/components/index.ts
  • frontend/src/context/BrandingContext.tsx
  • frontend/src/hooks/useChartColors.ts
  • frontend/src/index.css
  • frontend/src/pages/AddressesPage.tsx
  • frontend/src/pages/BlockDetailPage.tsx
  • frontend/src/pages/BlockTransactionsPage.tsx
  • frontend/src/pages/BlocksPage.tsx
  • frontend/src/pages/FaucetPage.tsx
  • frontend/src/pages/NFTsPage.tsx
  • frontend/src/pages/NotFoundPage.tsx
  • frontend/src/pages/SearchResultsPage.tsx
  • frontend/src/pages/StatusPage.tsx
  • frontend/src/pages/TokensPage.tsx
  • frontend/src/pages/TransactionDetailPage.tsx
  • frontend/src/pages/TransactionsPage.tsx
  • frontend/src/pages/WelcomePage.tsx
  • frontend/src/utils/color.test.ts
  • frontend/src/utils/color.ts
  • frontend/tailwind.config.js

Comment thread backend/crates/atlas-server/src/api/handlers/contracts.rs Outdated
Comment thread backend/crates/atlas-server/src/main.rs
Comment thread frontend/src/components/BrandPrimitives.tsx Outdated
Comment thread frontend/src/index.css Outdated
Comment thread frontend/src/index.css Outdated
Comment thread frontend/src/pages/StatusPage.tsx
Comment thread frontend/src/pages/TransactionDetailPage.tsx Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant