Skip to content

Migrate to Vite/React 19 and polish protocol table UX#15

Merged
easeev merged 3 commits intomainfrom
codex/vite-react19-ui-polish
Feb 16, 2026
Merged

Migrate to Vite/React 19 and polish protocol table UX#15
easeev merged 3 commits intomainfrom
codex/vite-react19-ui-polish

Conversation

@easeev
Copy link
Contributor

@easeev easeev commented Feb 16, 2026

Summary

This PR modernizes the app stack and applies the requested UI/data polish across protocol naming, icon coverage, sorting behavior, and data filtering.

Changes

  • Migrated build/dev setup from Create React App to Vite.
  • Upgraded core frontend stack to React 19 and Ant Design 6.
  • Added CI workflow (.github/workflows/ci.yml) and protocol icon coverage check script.
  • Added/updated protocol icons, including Unichain and other newly supported networks.
  • Introduced shared protocol/network metadata and formatting helpers.
  • Normalized naming and labels:
    • BNB Smart Chain
    • Polygon PoS
    • Removed redundant testnet token from composed display names where appropriate.
  • Updated data parsing to include only public dedicated nodes with valid numeric storage data.
  • Hid rows with missing storage values to avoid No data rows in the table.
  • Implemented deterministic row ordering by protocol/network family/node type/client.
  • Fixed protocol-column row-span glitches when sorting/filtering by non-protocol columns.
  • Added tests for protocol display formatting, data parsing, and icon mapping.
  • Refreshed README and AGENTS guidance for the current toolchain.

Validation

  • npm run test passed (7 tests).
  • npm run build passed.
  • npm run check:protocol-icons passed (27 visible protocols).

Summary by CodeRabbit

  • New Features

    • Improved search with protocol aliases and better filtering
    • Redesigned protocol list and cards with richer metadata and icons
    • Accessible theme toggle with descriptive ARIA labels
    • User-facing error and loading states for data fetches
  • Bug Fixes / Tests

    • Added icon-coverage check and unit tests for protocol utilities and icons
  • Documentation

    • Restructured README and updated local development URLs and meta info
  • Chores

    • Migrated to Vite for dev/build; added CI workflow and updated ignore rules

- migrate from CRA to Vite and update app entry/build setup
- upgrade React/Ant Design and refresh dependency lockfile
- add CI workflow and protocol icon coverage check script
- centralize protocol/network/client display metadata and formatting helpers
- add tests for data parsing, protocol display formatting, and icon mapping
- add missing protocol icons (including Unichain) and protocol badge fallback
- fix theme toggle button regression and improve table/search UX
- enforce network/node/client ordering and hide rows with missing size data
- normalize protocol/network naming (for example BNB Smart Chain, Polygon PoS)
- refresh README and AGENTS guidance for the new toolchain
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

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

⌛ 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.

📝 Walkthrough

Walkthrough

Migrates the project from Create React App to Vite, converts key components from .js to .jsx, adds protocol metadata/display helpers, introduces an icon-coverage validation script and CI workflow, updates docs and HTML metadata, and adds tests and styling updates.

Changes

Cohort / File(s) Summary
Build & CI
\.github/workflows/ci.yml, vite.config.mjs, package.json, \.gitignore
Adds a GitHub Actions CI workflow, introduces Vite config with React and SVGR plugins, migrates scripts/deps to Vite/Vitest, and ignores /dist.
Documentation & HTML
README.md, AGENTS.md, index.html
Rewrites README for Chainstats, updates local dev URL and .js→.jsx references in docs, updates meta tags/OG images to absolute paths, and adds module script tag for /src/index.js.
App entry & bootstrap
src/index.js, src/App.js, src/App.jsx, src/App.scss
Removes old src/App.js, adds src/App.jsx with async data loading, search/filter UI and error handling, changes root rendering call style, and adds .layoutWrapper_error styling.
Table & Cards UI
src/components/TableWrapper/TableWrapper.js, src/components/TableWrapper/TableWrapper.jsx, src/components/ProtocolCards/ProtocolCards.js, src/components/ProtocolCards/ProtocolCards.jsx
Replaces deleted .js table/cards with new .jsx implementations: complex Ant Design table with row-span logic and protocol-based card rendering.
Protocol Iconing
src/components/ProtocolIcon/ProtocolIcon.js, src/components/ProtocolIcon/ProtocolIcon.jsx, src/components/ProtocolIcon/ProtocolIcon.scss, src/components/ProtocolIcon/ProtocolIcon.test.jsx
Removes old JS icon module, adds new SVG-based ProtocolIcon.jsx with fallback badge and iconTypes mapping, styles, and tests covering SVG and fallback paths.
Header & Styling
src/components/Header/Header.jsx, src/components/Header/Header.scss
Adjusts theme toggle Button to type="text", adds theme_toggle class and ARIA label, and introduces scoped header toggle styles.
Data helpers & metadata
src/helpers/getData.js, src/helpers/getData.test.js, src/helpers/protocolDisplay.js, src/helpers/protocolDisplay.test.js, src/helpers/protocolMetadata.json, src/helpers/clientDisplay.js, src/helpers/utils.js
Adds protocol metadata JSON, new display utilities (protocol/network/client), tests for them, refactors getData to filter public nodes and normalize sizes, removes some legacy utils, and exposes gbToTb.
Validation script & tooling
scripts/check-protocol-icons.js
New Node script that fetches visible protocols from the API, compares against available icon keys (ProtocolIcon.jsx + metadata), and fails CI on missing icons.
Local storage & minor tests
src/helpers/useLocalStorage.js
Updates error logging to use console.error in catch blocks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped from CRA to Vite so spry,
Icons lined up, no names left shy.
JSX carrots and tests to taste,
Metadata stored with no waste,
Chainstats blooms — a happy sigh. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Migrate to Vite/React 19 and polish protocol table UX' clearly and concisely summarizes the main changes: migrating the build toolchain from Create React App to Vite, upgrading React, and improving the protocol table user experience.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/vite-react19-ui-polish

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.

Copy link

@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

🤖 Fix all issues with AI agents
In @.github/workflows/ci.yml:
- Around line 26-30: The workflow is missing a test step so CI doesn't run the
test suite; add a step that runs "npm run test" (or the project's test script)
into the job steps — e.g. insert a step named "Run tests" calling `npm run test`
(or `npm ci && npm test` if installing deps is needed) between the "Check
protocol icon coverage" and "Build app" steps (or before "Build app") so
failures fail the workflow; ensure the step uses the same runner/context as the
existing "Check protocol icon coverage" and "Build app" steps.

In `@index.html`:
- Line 28: The meta tags using relative paths (meta property="og:image" and meta
name="twitter:image") must use fully qualified absolute URLs; update the content
attributes for the elements identified by meta property="og:image" and meta
name="twitter:image" to an absolute URL (e.g.,
https://yourdomain.com/og-image.png), or construct them dynamically from a
canonical site base (env var or config) so social crawlers can resolve the
images correctly.

In `@package.json`:
- Around line 8-10: The three test-only packages "@testing-library/jest-dom",
"@testing-library/react", and "@testing-library/user-event" are currently listed
under dependencies in package.json; remove these three entries from the
"dependencies" section and add them under "devDependencies" with the same
version strings, then update the lockfile by running your package manager
(npm/yarn/pnpm install) so production installs no longer include them. Ensure
you modify package.json (look for the "dependencies" and "devDependencies" keys)
and run the install command to persist changes to package-lock.json/yarn.lock.

In `@scripts/check-protocol-icons.js`:
- Around line 148-151: The log uses error.message in the main().catch handler
which can contain newline characters and allow log injection; change the handler
in scripts/check-protocol-icons.js so you sanitize the error message before
printing (e.g., compute a safeMessage = (error && error.message) ?
String(error.message).replace(/[\r\n]+/g, ' ') : String(error) or
JSON.stringify(error) and then log that safeMessage in the console.error call),
keep process.exitCode = 1 as-is.

In `@src/App.jsx`:
- Around line 42-53: The filter in the useEffect uses
alias.includes(searchValue) which is case-sensitive while searchValue is
lowercased, so update the comparison to be case-insensitive by lowercasing
aliases before comparing (e.g., in the useEffect where setSearchResult is
called, call getProtocolSearchAliases(item.protocol).some(alias =>
alias.toLowerCase().includes(searchValue))), or alternatively ensure
getProtocolSearchAliases/formatProtocolDisplayName returns lowercased aliases;
change either getProtocolSearchAliases or the filter in useEffect to normalize
aliases with .toLowerCase() before .includes().

In `@src/components/ProtocolIcon/ProtocolIcon.jsx`:
- Around line 85-91: Guard against protocolName being null/undefined in
ProtocolIcon before calling split: ensure protocolName has a safe default (e.g.,
empty string) or return early. Update ProtocolIcon (where fallbackLabel is
computed) to coerce protocolName to a string (protocolName || '') or add a
default parameter in the ProtocolIcon signature so fallbackLabel calculation
never calls split on undefined/null. Keep the rest of the fallbackLabel logic
unchanged.

In `@src/components/TableWrapper/TableWrapper.jsx`:
- Around line 95-99: The merged-row lag happens because visibleData is updated
via setVisibleData in an effect/onChange path, so protocolRowSpans is computed
from stale visibleData; instead lift the sort/filter parameters into state
(e.g., sortState/filterState saved in the TableWrapper component when onChange
fires) and remove the effect that syncs visibleData, then derive visibleData
inside a useMemo from tableData + those params and compute protocolRowSpans from
that same memoized visibleData (so TableWrapper, visibleData, protocolRowSpans,
setVisibleData/onChange and useEffect are replaced by memoized derivation using
useMemo to keep both visibleData and protocolRowSpans consistent in the same
render).
🧹 Nitpick comments (14)
src/components/Header/Header.scss (1)

44-48: Consider adding a :focus-visible outline for keyboard accessibility.

Since the button strips all default visual cues (border, shadow, background), keyboard users won't see a focus indicator. A subtle outline on :focus-visible would pair well with the aria-label added in the JSX.

♻️ Suggested tweak
       &:hover,
       &:focus {
         background: transparent;
         color: var(--color-typo-primary);
       }
+
+      &:focus-visible {
+        outline: 2px solid var(--color-typo-brand);
+        outline-offset: 2px;
+      }
src/helpers/utils.js (1)

1-7: Consider rounding the TB value and handling the 1000 GB boundary.

Two minor display nits:

  • Exactly 1000 GB returns "1000 GB" instead of "1 TB" because the check is > rather than >=.
  • Values like 1234 GB produce "1.234 TB" — a toFixed(1) or toFixed(2) would give cleaner output.
Suggested tweak
 const gbToTb = (value) => {
-  if (value > 1000) {
-    return value / 1000 + ' TB';
+  if (value >= 1000) {
+    return (value / 1000).toFixed(2) + ' TB';
   } else {
     return value + ' GB';
   }
 };
src/App.scss (1)

22-26: Hardcoded error color breaks theme consistency.

The rest of the file uses CSS variables (e.g., var(--color-typo-primary)), but &_error hardcodes #c62828. This won't adapt to dark/light theme changes.

Consider introducing a CSS variable like --color-error and referencing it here, consistent with how the sibling &_no-results class uses var(--color-typo-primary).

src/helpers/getData.js (1)

15-42: for…in loops in parseProtocolData lack own-property guards.

parseNodeData (Line 47) correctly uses Object.hasOwn, but the two for…in loops here (Lines 18–19) iterate without an own-property check. This is safe for plain JSON objects from the API but inconsistent with the pattern used elsewhere in the same file.

♻️ Suggested consistency fix
- for (const protocol in protocols) {
-   for (const network in protocols[protocol]) {
+ for (const protocol of Object.keys(protocols)) {
+   for (const network of Object.keys(protocols[protocol])) {
src/helpers/protocolDisplay.js (2)

9-15: Duplicated helpers in scripts/check-protocol-icons.js.

capitalizeFirstLetter and formatProtocolDisplayName are duplicated nearly verbatim in scripts/check-protocol-icons.js (Lines 28–42 of that file). If the script can import or require from this module (or a shared CJS wrapper), consider consolidating. If the environments are incompatible (ESM vs CJS), the duplication is acceptable but worth a // NOTE: comment linking the two copies.

Also applies to: 36-42


58-73: Search aliases may contain duplicates.

For single-word display names (e.g., "Unichain"), the spaceless variant is identical to the display name, producing duplicate entries. This doesn't affect correctness (.some() still works), but you could add a new Set(...) or [...new Set(arr)] to deduplicate if the alias list grows.

src/index.js (1)

7-13: React.createElement workaround for .js extension under Vite.

This works correctly but is an unusual pattern. Consider renaming the file to index.jsx so you can use the more readable JSX syntax, updating the <script> tag in index.html accordingly. This would be consistent with the other .jsx files in the project (e.g., App.jsx, TableWrapper.jsx).

src/components/ProtocolIcon/ProtocolIcon.test.jsx (1)

6-26: Consider splitting into individual test cases per protocol.

Bundling four assertions into one it block means a failure on Monad masks whether Plasma/Tempo/Unichain also pass. Using it.each or separate it blocks would give clearer diagnostics on failure.

♻️ Example with it.each
- it('renders provided SVG icons for newly added protocols', () => {
-   const { container: monadContainer } = render(
-     <ProtocolIcon protocolName="Monad" />
-   );
-   expect(monadContainer.querySelector('svg')).toBeTruthy();
-
-   const { container: plasmaContainer } = render(
-     <ProtocolIcon protocolName="Plasma" />
-   );
-   expect(plasmaContainer.querySelector('svg')).toBeTruthy();
-
-   const { container: tempoContainer } = render(
-     <ProtocolIcon protocolName="Tempo" />
-   );
-   expect(tempoContainer.querySelector('svg')).toBeTruthy();
-
-   const { container: unichainContainer } = render(
-     <ProtocolIcon protocolName="Unichain" />
-   );
-   expect(unichainContainer.querySelector('svg')).toBeTruthy();
- });
+ it.each(['Monad', 'Plasma', 'Tempo', 'Unichain'])(
+   'renders an SVG icon for %s',
+   (protocolName) => {
+     const { container } = render(<ProtocolIcon protocolName={protocolName} />);
+     expect(container.querySelector('svg')).toBeTruthy();
+   }
+ );
.github/workflows/ci.yml (1)

19-21: Consider pinning Node.js to an LTS minor for reproducibility.

node-version: 20 will float to the latest Node 20.x. For a more reproducible CI, consider using a specific LTS version (e.g., 20.x is equivalent here, but lts/* or a pinned version like 20.18 avoids surprises from patch-level changes).

src/App.jsx (1)

4-4: Remove commented-out import.

Dead code: the PlannedUpgrades import is commented out. Consider removing it to keep the file clean.

scripts/check-protocol-icons.js (2)

29-43: Duplicated display-name logic from protocolDisplay.js.

capitalizeFirstLetter and formatProtocolDisplayName are duplicated here. If the canonical versions in protocolDisplay.js diverge, this script will silently produce different display names, causing false positives/negatives in the icon check. Consider extracting shared logic into a CJS-compatible module, or converting this script to ESM (Vite projects typically support it).


45-56: Fragile regex-based icon key extraction.

The regex on Line 48 relies on exact two-space indentation and JSX formatting of ProtocolIcon.jsx to extract icon keys. Any reformatting (e.g., a prettier config change) could silently break the check. Consider exporting iconTypes keys from a shared data module, or parsing the AST with a lightweight tool.

src/components/ProtocolCards/ProtocolCards.jsx (1)

16-48: Move Card component outside of ProtocolCards to avoid reconciliation issues.

Defining Card as a nested function inside the render body creates a new component identity on every render, causing React to unmount/remount rather than update. Extract it to module scope or memoize it.

Proposed fix
+const Card = ({ cardHeader, size, client }) => {
+  return (
+    <div className="card">
+      <div className="card_header">
+        <div>{cardHeader}</div>
+      </div>
+      <div className="card_data__block">
+        <div className="metadata_wrapper">
+          <div className="metadata">
+            <span>Size:</span>
+            <div className="size_value">
+              <Tag
+                icon={<DatabaseOutlined />}
+                bordered={false}
+                className="custom_tag"
+              >
+                {size ? size : 'No Data'}
+              </Tag>
+            </div>
+          </div>
+          <div className="metadata">
+            <span>Client:</span>
+            <div>
+              <Tag color="default" bordered={false}>
+                {client}
+              </Tag>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
 export default function ProtocolCards({ data }) {
   const protocolsArray = Array.from(new Set(data.map((item) => item.protocol)));
-
-  const Card = ({ cardHeader, size, client }) => {
-    ...
-  };
src/components/TableWrapper/TableWrapper.jsx (1)

153-168: Consider extracting inline styles to CSS/SCSS.

The Protocol column render function uses inline style objects (Lines 155, 159–161). These create new object references each render. Since the component already imports SCSS elsewhere, moving these to a stylesheet would be more consistent and avoids unnecessary object allocations.

Copy link

@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.

🧹 Nitpick comments (3)
scripts/check-protocol-icons.js (3)

51-62: Regex-based JSX parsing is fragile; \s in the character class is overly permissive.

Two observations on extractIconKeys:

  1. The regex assumes exactly 2-space indentation and a specific key–value layout. Any auto-formatter change (e.g., switching to tabs, changing indent depth) will silently break the check.
  2. \s inside the character class ['A-Za-z0-9\s.-] also matches \n, \r, and \t, not just spaces. For matching multi-word keys like 'BNB Smart Chain', a literal space would be more precise.

Low risk in practice, but worth tightening:

Suggested regex tweak
-    /^\s{2}(['A-Za-z0-9\s.-]+):\s*</gm
+    /^\s{2}(['A-Za-z0-9 .-]+):\s*</gm

64-94: No request timeout — CI job can hang indefinitely if the API is unresponsive.

If the upstream API stalls without closing the connection, this promise never settles and the CI job hangs until the runner kills it. Adding a timeout is a simple safeguard.

Suggested fix
     client
-      .get(target, (response) => {
+      .get(target, { timeout: 30_000 }, (response) => {
         if (response.statusCode < 200 || response.statusCode >= 300) {
       .on('error', (error) => reject(error));
+      .on('timeout', function () { this.destroy(new Error('Request timed out')); });

Note: the timeout option sets a socket-idle timeout. You may also want setTimeout on the request object for a hard deadline.


35-49: Duplicated helpers from protocolDisplay.js — acceptable given CJS/ESM boundary, but worth a note.

capitalizeFirstLetter and formatProtocolDisplayName are near-copies of their counterparts in src/helpers/protocolDisplay.js. Since this is a CJS Node script and the app uses ESM, direct import isn't straightforward. Consider adding a brief comment noting the duplication so future maintainers keep them in sync.

Also note the subtle difference: protocolDisplay.js returns '' for a falsy protocolSlug, while this version doesn't guard against it. Currently safe because Object.entries always yields string keys, but worth aligning for defensiveness.

- add CI test step and pin Node version in workflow
- switch social meta images to absolute chainstats.org URLs
- move testing-library packages to devDependencies
- harden icon check script logging and reduce format fragility
- remove stale commented import and enforce case-insensitive search matching
- guard ProtocolIcon against null/undefined protocol names
- rework table sort/filter state to avoid row-span lag during reordering
- move protocol cell inline styles into TableWrapper.scss
- add focus-visible outline for theme toggle accessibility
- make size formatter handle 1000 GB boundary and consistent TB precision
- replace hardcoded error color with theme variable
- iterate parseProtocolData with Object.keys for consistency
- deduplicate protocol search aliases
- convert entrypoint to index.jsx and update index.html module path
- split ProtocolIcon SVG checks into parameterized tests
- move ProtocolCards Card component to module scope
@easeev easeev force-pushed the codex/vite-react19-ui-polish branch from d2e7485 to bf215c6 Compare February 16, 2026 06:54
- bump GitHub Actions Node.js to 24.13.1
- update README prerequisite from Node.js 20+ to 24+
@easeev easeev merged commit 6b550ba into main Feb 16, 2026
4 checks passed
@easeev easeev deleted the codex/vite-react19-ui-polish branch February 16, 2026 07:11
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

Comments