Skip to content

Conversation

@danieljrc888
Copy link
Contributor

@danieljrc888 danieljrc888 commented Dec 5, 2025

Fixes #issue-number-here

What

  • changed thing a for b
  • also did this other unrelated thing in my path

Why

  • to fix a bug
  • to add more value to the user

Testing done

  • tested the new feature
  • tested the bug fix

Decisions made

Checks

  • I have tested this code
  • I have reviewed my own PR
  • I have created an issue for this PR
  • I have set a descriptive PR title compliant with conventional commits

Reviewing tips

User facing release notes

Summary by CodeRabbit

  • New Features

    • Added GenLayer Studio Explorer service and full web UI: dashboard, transactions browser (search, filters, pagination), detailed transaction view with tabs (overview, monitoring, consensus, data, related), state explorer, validators and providers pages, and sidebar navigation.
    • New UI components: copy-to-clipboard, JSON viewer, stat cards, transaction table, status/type badges, consensus/timeline viewers.
  • Documentation

    • Added Explorer README and sample environment file.
  • Chores

    • Project scaffolding, lint/postcss/tsconfig, gitignore and Docker compose service added.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a new Next.js "explorer" app and Docker service: serverless API routes backed by a Postgres pool, client pages/components (dashboard, transactions, state, validators), TypeScript types/utilities, build tooling, and a multi-stage Dockerfile/service entry for explorer.

Changes

Cohort / File(s) Summary
Docker & Orchestration
docker-compose.yml, docker/Dockerfile.explorer
New explorer service and multi-stage Dockerfile; exposes port, mounts source, sets DB env vars, depends on database-migration, and configures restart/security/logging.
Project Config & Tooling
explorer/package.json, explorer/tsconfig.json, explorer/next.config.ts, explorer/eslint.config.mjs, explorer/postcss.config.mjs
New Next.js project manifest, TypeScript config, Next config stub, ESLint composition, and PostCSS/Tailwind setup.
Env / Docs / Ignore
explorer/.env.sample, explorer/.gitignore, explorer/README.md
Sample environment file, README, and .gitignore for the explorer project.
DB Pool & Lib Barrel
explorer/src/lib/db.ts, explorer/src/lib/index.ts, explorer/src/lib/*.ts
Adds lazy Postgres Pool proxy and library barrel; new utility modules (types, formatters, transactionUtils, consensusUtils, constants). Review DB connection defaults and lazy proxy behavior.
API Routes (DB-backed)
explorer/src/app/api/providers/route.ts, .../state/route.ts, .../state/[id]/route.ts, .../stats/route.ts, .../transactions/route.ts, .../transactions/[hash]/route.ts, .../validators/route.ts
New GET endpoints implementing parameterized queries, pagination/filters, aggregate stats, related-entity retrieval; ensure correct client connect/release, SQL parameterization, and 404/500 responses.
Frontend Pages
explorer/src/app/page.tsx, .../providers/page.tsx, .../state/page.tsx, .../state/[id]/page.tsx, .../transactions/page.tsx, .../transactions/[hash]/page.tsx, .../validators/page.tsx
Client pages for dashboard, providers, states, transactions, detail tabs, and validators; fetch APIs, loading/error handling, and integration with new UI components.
Transaction Detail Tabs
explorer/src/app/transactions/[hash]/components/*, explorer/src/app/transactions/[hash]/components/index.ts
Adds Overview, Monitoring (complex consensus/timeline logic), Consensus, Data, and Related tab components and barrel export. Monitoring/consensus logic warrants careful review.
Core UI Components
explorer/src/components/*, explorer/src/components/index.ts
New reusable components: Navigation, StatCard, StatusBadge, CopyButton, InfoRow, JsonViewer, TransactionTable, TransactionTypeLabel, ConsensusViewer, ConsensusRound, VoteIcon, MonitoringTimeline; centralized re-exports. JsonViewer and ConsensusViewer are high-logic areas.
Styling & Layout
explorer/src/app/layout.tsx, explorer/src/app/globals.css
RootLayout with fonts/metadata and global CSS (tokens, components, scrollbar, animations).

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Client (Browser)
  participant FE as Next.js Frontend
  participant API as Next.js API Route
  participant DB as Postgres

  Note over Browser,FE: User opens transaction detail page
  Browser->>FE: GET /transactions/[hash] (page render)
  FE->>API: fetch("/api/transactions/{hash}")
  activate API
  API->>DB: parameterized SELECT tx WHERE hash = $1
  DB-->>API: transaction row (or null)
  alt transaction found
    API->>DB: SELECT triggered transactions WHERE triggered_by = $1
    DB-->>API: triggered rows
    API->>DB: (optional) SELECT parent transaction if triggered_by present
    DB-->>API: parent row or null
    API-->>FE: 200 JSON { transaction, triggeredTransactions, parentTransaction }
  else not found
    API-->>FE: 404 Not Found
  end
  deactivate API
  FE->>Browser: render tabs (Overview, Monitoring, Consensus, Data, Related)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐇 I nibble SQL and stitch the view,
Tabs and timelines sprout anew,
I copy hashes, chart the vote,
From DB burrows to UI mote—
Explorer hops, the data grew.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning Description is almost entirely a blank template with only placeholder text; critical sections like What, Why, Testing done, and Decisions are completely unfilled with actual content. Replace template placeholders with specific details about changes made, business rationale, testing approach, architectural decisions, and user-facing value. Complete all mandatory sections with concrete information.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.41% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed Title 'feat: studio explorer' is concise and accurately summarizes the main change—adding a new blockchain explorer feature to the Studio application.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c069e03 and 5145526.

📒 Files selected for processing (3)
  • explorer/src/app/globals.css
  • explorer/src/lib/formatters.ts
  • explorer/src/lib/transactionUtils.ts

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.

@danieljrc888 danieljrc888 self-assigned this Dec 5, 2025
@cristiam86 cristiam86 marked this pull request as ready for review January 5, 2026 09:36
Copy link
Contributor

@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: 20

Fix all issues with AI Agents 🤖
In @docker/Dockerfile.explorer:
- Around line 14-15: Replace the floating base and packages with pinned
versions: change "FROM alpine:latest" to a specific Alpine tag (e.g.,
alpine:3.20) and pin the node packages in the "RUN apk add --no-cache nodejs
npm" invocation by specifying explicit package versions (nodejs=<version>
npm=<version>) or switch to an official node:<version>-alpine base image; verify
the chosen Alpine release and package versions exist in that release's
repositories before committing.

In @explorer/src/app/api/stats/route.ts:
- Around line 42-52: The deploy vs call breakdown is missing tx types 2 and 3;
update the SQL used in client.query (the deployResult calculation) to include
all supported types (0,1,2,3) when determining deploys—i.e., count a transaction
as a deploy if its type is one of 0–3 and either it's type 0 or its
contract_snapshot contains a contract_code (check the same three
contract_snapshot paths used now); leave deployCount and callCount variables
(deployCount, callCount, totalTransactions) as-is so callCount =
totalTransactions - deployCount continues to work.

In @explorer/src/app/api/transactions/route.ts:
- Around line 7-11: The pagination parsing uses parseInt on searchParams for
page and limit which can produce NaN and then propagate into offset/LIMIT;
update the logic around page, limit, and offset (the parseInt calls and the
variables page, limit, offset) to validate and sanitize inputs: coerce NaN to
defaults (page = 1, limit = 20), ensure page >= 1, clamp limit to a safe range
(e.g., 1..100), then recompute offset = (page - 1) * limit before using them in
the query so LIMIT/OFFSET never receive NaN or out-of-range values.

In @explorer/src/app/globals.css:
- Around line 81-85: The focus rule for input:focus, select:focus, button:focus
uses invalid properties `ring` and `ring-color`; replace them with valid CSS
focus styles (e.g., set a visible outline or a box-shadow using #3b82f6 and a
2px thickness) so the selectors input:focus, select:focus, button:focus produce
an accessible focus indicator, or remove this rule entirely and apply Tailwind
focus utilities (e.g., focus:ring-2 focus:ring-blue-500) on the HTML components
instead.

In @explorer/src/app/providers/page.tsx:
- Line 179: The conditional for rendering provider.config is overly complex and
mishandles the string case; update the check to first ensure provider.config is
truthy and then render if it's a non-empty string or an object with keys (e.g.
check provider.config && (typeof provider.config === 'string' ||
Object.keys(provider.config).length > 0)), referencing the LLMProvider type and
the provider.config usage in the render to ensure both string and Record<string,
unknown> cases are handled.

In @explorer/src/app/state/page.tsx:
- Around line 16-34: The fetch in useEffect (inside fetchStates) lacks
cancellation; create an AbortController at the start of the effect, pass
controller.signal to fetch(`/api/state?...`), and ensure you call
controller.abort() in the effect cleanup to cancel in-flight requests when
searchQuery changes or the component unmounts; update fetchStates to catch
AbortError (skip calling setLoading/setStates/setError when aborted) so
setLoading(false)/setStates(...) and setError(...) are not invoked after
unmount—adjust references to fetchStates, setLoading, setStates, and setError
accordingly.
- Around line 100-104: The date parsing for state.updated_at before calling
formatDistanceToNow can throw for invalid date strings; update the rendering
logic around formatDistanceToNow(new Date(state.updated_at), ...) in page.tsx to
validate the date first (e.g., use date-fns parseISO/isValid or try creating the
Date and checking isNaN(date.getTime())) and fall back to 'Unknown' when
invalid, or wrap the call in a try/catch and return 'Unknown' on error so the
Clock + formatted string never throws.

In @explorer/src/app/transactions/[hash]/components/RelatedTab.tsx:
- Around line 56-58: The code in RelatedTab uses format(new
Date(ttx.created_at), 'PPpp') without validating ttx.created_at; validate that
ttx.created_at is a parseable date (e.g., check it's a non-empty string and that
new Date(ttx.created_at) is a valid Date via isNaN(date.getTime())) before
calling format, and if invalid render a safe fallback (empty string, "Invalid
date", or omit the element) to avoid RangeError from format; update the JSX
around ttx.created_at accordingly to perform this check before rendering.

In @explorer/src/app/transactions/[hash]/page.tsx:
- Around line 51-68: The fetchTransaction effect needs an AbortController to
cancel in-flight requests: create a controller inside useEffect, pass
controller.signal to fetch(`/api/transactions/${hash}`, { signal }), and return
a cleanup that calls controller.abort(); inside the try/catch, detect an abort
(err.name === 'AbortError' or controller.signal.aborted) and skip calling
setData/setError/setLoading when aborted so you don't update state after unmount
or when hash changes; keep using setLoading(false) only when not aborted. Ensure
you reference fetchTransaction, setData, setError, setLoading and the dependency
[hash].

In @explorer/src/app/validators/page.tsx:
- Around line 164-169: The current render uses format(new
Date(validator.created_at), 'PPpp') without validating the parsed date, which
can throw RangeError for malformed strings; update the JSX around
validator.created_at to first parse into a Date (e.g., const d = new
Date(validator.created_at) or Date.parse) and check validity via
!isNaN(d.getTime()) (or Date.parse !== NaN), then only call format(d, 'PPpp')
when valid; if invalid, render a safe fallback (omit the row or display "Invalid
date" / "--") so the component (page.tsx) no longer throws at runtime when
validator.created_at is malformed.

In @explorer/src/components/StatCard.tsx:
- Around line 27-28: The dynamic class composition in StatCard (props iconBg and
color used in the div and Icon className) can be missed by Tailwind JIT; replace
dynamic class interpolation with a safe approach: either map incoming variant
keys to a predefined lookup of Tailwind classes (create an iconBgClasses and
iconColorClasses map and use those keys instead of raw iconBg/color), or switch
to inline style values for fully dynamic colors (apply backgroundColor on the
wrapper and color on the Icon), or ensure the specific class names are
safelisted in the Tailwind config; update the StatCard usage to reference the
chosen mapping keys or inline style props so Tailwind can detect and include the
styles.

In @explorer/src/components/TransactionTable.tsx:
- Around line 181-184: The code calls formatDistanceToNow(new
Date(tx.created_at)) without validating tx.created_at; to fix, check that
tx.created_at is a parsable/valid date (e.g., Date.parse or new
Date(...).getTime() is finite) before constructing the Date and calling
formatDistanceToNow in TransactionTable (the tx.created_at usage); if the value
is missing or invalid, render the existing placeholder (<span
className="text-slate-400">-</span>) instead of calling formatDistanceToNow to
avoid RangeError.

In @explorer/src/lib/constants.ts:
- Around line 50-60: TRANSACTION_TYPES defines Tailwind utility classes (e.g.,
bg-blue-50, text-blue-700) in explorer/src/lib/constants.ts but
frontend/tailwind.config.js does not scan explorer/src, so JIT will purge these
classes; fix by either adding the specific classes used in TRANSACTION_TYPES to
the safelist in tailwind.config.js or extend the content globs to include the
explorer folder (e.g., add './explorer/src/**/*.{ts,tsx}') so the classes
referenced by TRANSACTION_TYPES are discovered.

In @explorer/src/lib/db.ts:
- Around line 4-8: Remove hardcoded fallback DB_USER/DB_PASSWORD and add startup
validation that required env vars (DB_HOST, DB_NAME, DB_USER, DB_PASSWORD,
DB_PORT) are present before creating the pool; if missing, throw/exit with a
clear error. When constructing the Pool (new Pool or createPool in this module),
set connection limits and timeouts: max (e.g., from DB_MAX or default 10),
idleTimeoutMillis, and connectionTimeoutMillis. Add conditional SSL config (use
DB_SSL=true or NODE_ENV==='production' to enable ssl: { rejectUnauthorized: true
}) and ensure the DB_PORT parsing uses radix 10 (parseInt(process.env.DB_PORT,
10)). Finally, update .env.example to list required DB variables and their
expected formats and add the validation logic near the Pool initialization (the
symbol names to locate: Pool/new Pool/createPool and the DB_PORT parseInt call).

In @explorer/src/lib/formatters.ts:
- Around line 25-30: getDuration currently multiplies (end - start) by 1000
assuming inputs are seconds, which breaks when callers pass millisecond
timestamps; update getDuration to mirror formatTimestamp's unit detection:
normalize start and end to milliseconds by checking magnitude (treat large
values as already milliseconds and small values as seconds) before computing
durationMs, then format as ms/s/min as before; reference the getDuration
function and formatTimestamp for the detection logic and ensure all internal
math uses milliseconds.

In @explorer/src/lib/transactionUtils.ts:
- Around line 28-61: getTimeToAccepted and getTimeToFinalized are passing
millisecond timestamp differences to formatDuration (which expects seconds),
producing durations 1000× too large; update both functions (getTimeToAccepted
and getTimeToFinalized) to convert the delta to seconds before calling
formatDuration by dividing (acceptedTime - pendingTime) and (finalizedTime -
pendingTime) by 1000 respectively, leaving all other checks and variable names
(tx.consensus_history, isNewConsensusFormat, firstRound.monitoring) unchanged.

In @explorer/tsconfig.json:
- Line 14: The tsconfig.json currently sets the "jsx" compiler option to
"react-jsx", which causes TypeScript to emit transformed JSX and can bypass
Next.js's SWC pipeline; change the "jsx" option value to "preserve" so
TypeScript leaves JSX intact for Next.js to compile (update the "jsx" property
in tsconfig.json from "react-jsx" to "preserve").
🧹 Nitpick comments (29)
explorer/package.json (1)

12-12: Consider moving @types/pg to devDependencies.

TypeScript type definitions are typically not needed at runtime and should be in devDependencies. While this won't cause any functional issues, it follows the standard convention and reduces the production dependency footprint.

🔎 Proposed fix
   "dependencies": {
-    "@types/pg": "^8.15.6",
     "date-fns": "^4.1.0",
     "lucide-react": "^0.555.0",
     "next": "16.0.6",
     "pg": "^8.16.3",
     "react": "19.2.0",
     "react-dom": "19.2.0"
   },
   "devDependencies": {
     "@tailwindcss/postcss": "^4",
     "@types/node": "^20",
+    "@types/pg": "^8.15.6",
     "@types/react": "^19",
     "@types/react-dom": "^19",
     "eslint": "^9",
     "eslint-config-next": "16.0.6",
     "tailwindcss": "^4",
     "typescript": "^5"
   }
explorer/src/app/api/state/[id]/route.ts (1)

24-30: Add pagination support for related transactions.

The hardcoded LIMIT 50 (line 28) could be problematic for addresses with many transactions. Consider:

  1. Making the limit configurable via query parameters
  2. Adding pagination (offset/cursor-based)
  3. Including total count in the response
  4. Adding a timestamp filter for better query performance
🔎 Suggested enhancement with pagination
 export async function GET(
   request: NextRequest,
   { params }: { params: Promise<{ id: string }> }
 ) {
   try {
     const { id } = await params;
+    const searchParams = request.nextUrl.searchParams;
+    const limit = parseInt(searchParams.get('limit') || '50', 10);
+    const offset = parseInt(searchParams.get('offset') || '0', 10);
+    
     const client = await pool.connect();

     try {
       // Get the state
       const stateResult = await client.query(
         'SELECT * FROM current_state WHERE id = $1',
         [id]
       );

       if (stateResult.rows.length === 0) {
         return NextResponse.json({ error: 'State not found' }, { status: 404 });
       }

+      // Get total count
+      const countResult = await client.query(
+        'SELECT COUNT(*) as count FROM transactions WHERE to_address = $1 OR from_address = $1',
+        [id]
+      );
+      const total = parseInt(countResult.rows[0].count);
+
       // Get related transactions (to this address)
       const txResult = await client.query(
         `SELECT * FROM transactions
          WHERE to_address = $1 OR from_address = $1
          ORDER BY created_at DESC
-         LIMIT 50`,
-        [id]
+         LIMIT $2 OFFSET $3`,
+        [id, limit, offset]
       );

       return NextResponse.json({
         state: stateResult.rows[0],
         transactions: txResult.rows,
+        pagination: {
+          total,
+          limit,
+          offset,
+        },
       });
     } finally {
       client.release();
     }
   } catch (error) {
     console.error('Database error:', error);
     return NextResponse.json({ error: 'Database connection failed' }, { status: 500 });
   }
 }
explorer/src/app/api/validators/route.ts (1)

9-13: Consider adding pagination to prevent performance issues.

The route returns all validators without pagination. As the validators table grows, this could:

  • Consume excessive memory
  • Slow down response times
  • Transfer unnecessary data to clients

Similar routes in the codebase (e.g., explorer/src/app/api/transactions/route.ts lines 3-70) implement pagination. Consider adopting the same pattern here.

🔎 Suggested pagination pattern
-export async function GET() {
+export async function GET(request: NextRequest) {
   try {
+    const searchParams = request.nextUrl.searchParams;
+    const page = parseInt(searchParams.get('page') || '1', 10);
+    const limit = parseInt(searchParams.get('limit') || '20', 10);
+    const offset = (page - 1) * limit;
+
     const client = await pool.connect();

     try {
+      // Get total count
+      const countResult = await client.query('SELECT COUNT(*) as count FROM validators');
+      const total = parseInt(countResult.rows[0].count);
+
       const result = await client.query(`
         SELECT id, stake, config, address, provider, model, plugin, plugin_config, created_at
         FROM validators
         ORDER BY id
+        LIMIT $1 OFFSET $2
-      `);
+      `, [limit, offset]);

       return NextResponse.json({
         validators: result.rows,
+        pagination: {
+          page,
+          limit,
+          total,
+          totalPages: Math.ceil(total / limit),
+        },
       });
     } finally {
       client.release();
     }
   } catch (error) {
     console.error('Database error:', error);
     return NextResponse.json({ error: 'Database connection failed' }, { status: 500 });
   }
 }
docker/Dockerfile.explorer (2)

19-22: Consider using Next.js standalone output for smaller images.

Next.js can generate a standalone output that includes only necessary files, significantly reducing image size and attack surface. This is especially beneficial for production deployments.

To enable standalone output, add to explorer/next.config.ts:

export default {
  output: 'standalone',
  // ... other config
};

Then update the Dockerfile:

 FROM alpine:3.20 AS final
 RUN apk add --no-cache nodejs=22.14.0-r0 npm=10.9.2-r0 && \
     addgroup --system explorer-user && adduser --system --ingroup explorer-user explorer-user && \
     mkdir /app && chown -R explorer-user:explorer-user /app
 WORKDIR /app
-COPY --from=builder --chown=explorer-user:explorer-user /app/.next /app/.next
-COPY --from=builder --chown=explorer-user:explorer-user /app/node_modules /app/node_modules
+COPY --from=builder --chown=explorer-user:explorer-user /app/.next/standalone ./
+COPY --from=builder --chown=explorer-user:explorer-user /app/.next/static ./.next/static
 COPY --from=builder --chown=explorer-user:explorer-user /app/package.json /app/package.json
 COPY --from=builder --chown=explorer-user:explorer-user /app/public /app/public
 USER explorer-user
 EXPOSE 3000
-CMD ["npm", "run", "start"]
+CMD ["node", "server.js"]

24-25: Add a HEALTHCHECK instruction for better container orchestration.

Docker health checks enable orchestrators (Docker Compose, Kubernetes) to verify the container is functioning and restart it if needed.

🔎 Suggested healthcheck
 USER explorer-user
 EXPOSE 3000
+HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
+  CMD node -e "require('http').get('http://localhost:3000/api/stats', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
 CMD ["npm", "run", "start"]
explorer/src/components/StatusBadge.tsx (2)

5-17: Consider extracting status configuration to a constants file.

The statusConfig mapping (lines 5-17) is a domain-level constant that might be reused elsewhere (e.g., for filtering, legends, or status documentation). Extracting it to explorer/src/lib/constants.ts would:

  • Improve discoverability
  • Enable reuse across components
  • Centralize status-related configuration
🔎 Suggested refactoring

In explorer/src/lib/constants.ts:

import { TransactionStatus } from './types';

export const STATUS_CONFIG: Record<
  TransactionStatus,
  { bg: string; text: string; dot: string }
> = {
  PENDING: { bg: 'bg-amber-50', text: 'text-amber-700', dot: 'bg-amber-500' },
  ACTIVATED: { bg: 'bg-blue-50', text: 'text-blue-700', dot: 'bg-blue-500' },
  // ... rest of config
};

export const DEFAULT_STATUS_CONFIG = {
  bg: 'bg-slate-100',
  text: 'text-slate-600',
  dot: 'bg-slate-400',
};

In StatusBadge.tsx:

 'use client';

 import { TransactionStatus } from '@/lib/types';
+import { STATUS_CONFIG, DEFAULT_STATUS_CONFIG } from '@/lib/constants';

-const statusConfig: Record<TransactionStatus, { bg: string; text: string; dot: string }> = {
-  // ... config
-};
-
-const defaultConfig = { bg: 'bg-slate-100', text: 'text-slate-600', dot: 'bg-slate-400' };

 export function StatusBadge({ status }: { status: TransactionStatus }) {
-  const config = statusConfig[status] || defaultConfig;
+  const config = STATUS_CONFIG[status] || DEFAULT_STATUS_CONFIG;
   // ... rest
 }

21-29: Add return type annotation for better type safety.

Adding an explicit return type (React.ReactElement or JSX.Element) improves type checking and IDE support.

-export function StatusBadge({ status }: { status: TransactionStatus }) {
+export function StatusBadge({ status }: { status: TransactionStatus }): React.ReactElement {
   const config = statusConfig[status] || defaultConfig;

   return (
     <span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-semibold ${config.bg} ${config.text}`}>
       <span className={`w-1.5 h-1.5 rounded-full ${config.dot}`}></span>
       {status.replace(/_/g, ' ')}
     </span>
   );
 }
explorer/src/app/providers/page.tsx (1)

15-29: Consider adding fetch cancellation on unmount.

The fetch call in useEffect doesn't cancel the request if the component unmounts before the request completes. While the error handling prevents state updates via the finally block's setLoading(false), adding an AbortController would be more robust and prevent unnecessary network traffic.

🔎 Proposed refactor with AbortController
 useEffect(() => {
+  const controller = new AbortController();
+
   async function fetchProviders() {
     try {
-      const res = await fetch('/api/providers');
+      const res = await fetch('/api/providers', { signal: controller.signal });
       if (!res.ok) throw new Error('Failed to fetch providers');
       const data = await res.json();
       setProviders(data.providers);
     } catch (err) {
+      if (err instanceof Error && err.name === 'AbortError') return;
       setError(err instanceof Error ? err.message : 'Unknown error');
     } finally {
       setLoading(false);
     }
   }
   fetchProviders();
+
+  return () => controller.abort();
 }, []);
explorer/src/app/api/state/route.ts (1)

4-34: LGTM with optional type refinement suggestion.

The API route correctly implements:

  • SQL injection protection via parameterized queries
  • Proper resource cleanup with client release in the finally block
  • Appropriate error handling
  • Consistent pattern with other routes in the codebase
Optional: Consider more flexible params typing for future extensibility

If you anticipate adding more query parameters in the future (e.g., filtering by other fields), you might want to type params more flexibly:

-const params: string[] = [];
+const params: (string | number)[] = [];

This matches the pattern used in explorer/src/app/api/transactions/route.ts and allows for numeric parameters without type assertion.

explorer/src/components/VoteIcon.tsx (1)

10-21: Consider using a className utility for cleaner concatenation.

The template literal concatenation works correctly, but using a utility like clsx or a cn helper would improve readability and maintainability.

🔎 Example refactor using clsx

Install clsx if not already available:

npm install clsx

Then refactor the component:

+'use client';
+
+import { CheckCircle, XCircle, Clock, AlertCircle } from 'lucide-react';
+import clsx from 'clsx';
+
 interface VoteIconProps {
   vote: string | undefined;
   className?: string;
 }
 
 export function VoteIcon({ vote, className = 'w-4 h-4' }: VoteIconProps) {
   switch (vote) {
     case 'agree':
-      return <CheckCircle className={`${className} text-green-500`} />;
+      return <CheckCircle className={clsx(className, 'text-green-500')} />;
     case 'disagree':
-      return <XCircle className={`${className} text-red-500`} />;
+      return <XCircle className={clsx(className, 'text-red-500')} />;
     case 'timeout':
-      return <Clock className={`${className} text-yellow-500`} />;
+      return <Clock className={clsx(className, 'text-yellow-500')} />;
     default:
-      return <AlertCircle className={`${className} text-gray-400`} />;
+      return <AlertCircle className={clsx(className, 'text-gray-400')} />;
   }
 }
explorer/src/components/Navigation.tsx (2)

59-68: Consider making connection status dynamic.

The database host and schema name are currently hardcoded. If these values vary by environment or if the connection status should reflect actual health, consider fetching this information dynamically.

💡 Example approach

Create an API endpoint that returns connection info:

// explorer/src/app/api/connection-info/route.ts
export async function GET() {
  return NextResponse.json({
    host: process.env.DB_HOST || 'localhost:5432',
    database: process.env.DB_NAME || 'genlayer_state',
    connected: true, // Add actual health check if needed
  });
}

Then fetch and display in the Navigation component:

const [connectionInfo, setConnectionInfo] = useState({ 
  host: 'localhost:5432', 
  database: 'genlayer_state' 
});

useEffect(() => {
  fetch('/api/connection-info')
    .then(res => res.json())
    .then(setConnectionInfo);
}, []);

42-52: Optional: Use className utility for improved readability.

Similar to the VoteIcon component, consider using clsx or a cn utility for cleaner className management.

explorer/src/components/TransactionTypeLabel.tsx (1)

10-47: Clarify badge semantics and type distinctions.

The badge rendering logic has overlapping semantics that may confuse users:

  1. Both type === 0 (blue) and type === 1 with contract code (orange) render "Deploy" badges with different colors, but the distinction isn't clear from the label alone.
  2. Both type === 1 without contract code (emerald) and type === 2 (violet) render "Call" badges with different colors, again with no clear label distinction.

Consider either:

  • Adding descriptive text to differentiate badge types (e.g., "Deploy (Legacy)" vs "Deploy", "Call (Type 1)" vs "Call (Type 2)")
  • Adding a comment block explaining the semantic meaning of each type value
  • Using distinct labels that better convey the actual transaction semantics
docker-compose.yml (2)

78-104: Consider adding a healthcheck for the explorer service.

Other services like jsonrpc (line 146) and consensus-worker (line 307) define healthchecks for monitoring and orchestration. Consider adding a similar healthcheck for the explorer service to improve reliability and enable proper health-based routing.

🔎 Proposed healthcheck addition
   explorer:
     build:
       context: ./
       dockerfile: ./docker/Dockerfile.explorer
       target: ${EXPLORER_BUILD_TARGET:-final}
     ports:
       - "${EXPLORER_PORT:-3001}:3000"
     volumes:
       - ./explorer/src:/app/src
     depends_on:
       database-migration:
         condition: service_completed_successfully
     environment:
       - DB_HOST=${DBHOST}
       - DB_NAME=${DBNAME}
       - DB_USER=${DBUSER}
       - DB_PASSWORD=${DBPASSWORD}
       - DB_PORT=${DBPORT}
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 40s
     restart: always
     security_opt:
       - "no-new-privileges=true"
     logging:
       driver: "json-file"
       options:
         max-size: "10m"
         max-file: "3"

78-104: Consider adding resource limits for the explorer service.

Services like jsonrpc (lines 160-172) and consensus-worker (lines 313-330) define memory and CPU limits to prevent resource exhaustion. Consider adding similar resource constraints for the explorer service, especially if running in production or shared environments.

🔎 Proposed resource limits addition
   explorer:
     build:
       context: ./
       dockerfile: ./docker/Dockerfile.explorer
       target: ${EXPLORER_BUILD_TARGET:-final}
     ports:
       - "${EXPLORER_PORT:-3001}:3000"
     volumes:
       - ./explorer/src:/app/src
     depends_on:
       database-migration:
         condition: service_completed_successfully
     environment:
       - DB_HOST=${DBHOST}
       - DB_NAME=${DBNAME}
       - DB_USER=${DBUSER}
       - DB_PASSWORD=${DBPASSWORD}
       - DB_PORT=${DBPORT}
     restart: always
     security_opt:
       - "no-new-privileges=true"
     logging:
       driver: "json-file"
       options:
         max-size: "10m"
         max-file: "3"
+    mem_limit: ${COMPOSE_EXPLORER_MEM_LIMIT:-2g}
+    mem_reservation: ${COMPOSE_EXPLORER_MEM_RESERVATION:-512m}
+    memswap_limit: ${COMPOSE_EXPLORER_MEM_SWAP_LIMIT:-2g}
+    oom_kill_disable: false
+    deploy:
+      resources:
+        limits:
+          cpus: '${COMPOSE_EXPLORER_CPU_LIMIT:-1}'
+          memory: ${COMPOSE_EXPLORER_MEM_LIMIT:-2g}
+        reservations:
+          cpus: ${COMPOSE_EXPLORER_CPU_RESERVATION:-0.5}
+          memory: ${COMPOSE_EXPLORER_MEM_RESERVATION:-512m}
explorer/src/components/MonitoringTimeline.tsx (1)

14-14: Consider memoizing the sorted entries.

The Object.entries(monitoring).sort() operation runs on every render. For monitoring objects with many entries, memoizing this computation would improve performance.

🔎 Proposed memoization
+'use client';
+
+import { useMemo } from 'react';
 import { ConsensusRoundMonitoring } from '@/lib/types';
 import { formatTimestamp, getDuration } from '@/lib/formatters';
 import { getPhaseColor } from '@/lib/consensusUtils';
 
 interface MonitoringTimelineProps {
   monitoring: ConsensusRoundMonitoring;
   title?: string;
   globalStartTime?: number;
 }
 
 export function MonitoringTimeline({ monitoring, title, globalStartTime }: MonitoringTimelineProps) {
-  const entries = Object.entries(monitoring).sort((a, b) => a[1] - b[1]);
+  const entries = useMemo(
+    () => Object.entries(monitoring).sort((a, b) => a[1] - b[1]),
+    [monitoring]
+  );
 
   if (entries.length === 0) return null;
explorer/src/app/state/page.tsx (1)

9-34: Consider using TanStack Query for data fetching.

The manual useState + useEffect pattern works but lacks built-in caching, request deduplication, and stale-while-revalidate behavior. TanStack Query would provide these benefits automatically and reduce boilerplate.

Based on learnings, TanStack Query is recommended for state synchronization with APIs.

Example with TanStack Query
import { useQuery } from '@tanstack/react-query';

export default function StatePage() {
  const [searchInput, setSearchInput] = useState('');
  const [searchQuery, setSearchQuery] = useState('');
  
  const { data, isLoading, error } = useQuery({
    queryKey: ['states', searchQuery],
    queryFn: async () => {
      const params = new URLSearchParams();
      if (searchQuery) params.set('search', searchQuery);
      const res = await fetch(`/api/state?${params.toString()}`);
      if (!res.ok) throw new Error('Failed to fetch states');
      return res.json();
    },
  });

  const states = data?.states ?? [];
  // ... rest of component
}
explorer/src/app/validators/page.tsx (1)

97-194: Consider adding pagination for large validator lists.

The validator list renders all items without pagination. With many validators, this could impact performance and user experience.

Consider implementing:

  • Client-side pagination (e.g., 20-50 items per page)
  • Virtual scrolling for very large lists
  • Or server-side pagination if the API supports it
explorer/src/components/ConsensusViewer.tsx (1)

25-29: Format detection is correct, but type assertion could be cleaner.

The type assertion on line 28 is safe due to the isLegacyConsensusFormat guard, but TypeScript's type narrowing doesn't automatically infer the narrowed type from the utility function.

Alternative approach using type predicate directly
// In consensusUtils.ts, ensure the type predicate is:
export function isLegacyConsensusFormat(
  data: ConsensusHistoryData | null
): data is LegacyConsensusEntry[] {
  return data !== null && Array.isArray(data) && !isNewConsensusFormat(data);
}

// Then the type should be automatically narrowed:
const legacyEntries = isLegacyConsensusFormat(consensusHistory)
  ? consensusHistory  // TypeScript should infer this as LegacyConsensusEntry[]
  : null;

If TypeScript still doesn't narrow correctly, the current explicit cast is acceptable.

explorer/src/app/state/[id]/page.tsx (2)

186-193: Prefer the truncateHash utility for consistency.

The hash is manually truncated inline with a different pattern than the truncateHash utility that exists in @/lib/formatters. Using the utility would improve consistency across the codebase.

🔎 Proposed refactor
+import { truncateAddress, truncateHash } from '@/lib/formatters';
-import { truncateAddress } from '@/lib/formatters';
                        <Link
                          href={`/transactions/${tx.hash}`}
                          className="text-blue-600 hover:underline font-mono text-sm"
                        >
-                         {tx.hash.slice(0, 10)}...{tx.hash.slice(-8)}
+                         {truncateHash(tx.hash)}
                        </Link>

32-49: Consider adding fetch abort controller for cleanup.

The useEffect hook doesn't clean up the fetch request when the component unmounts or when id changes, which could lead to state updates on unmounted components if the request completes after navigation.

🔎 Proposed improvement
  useEffect(() => {
+   const controller = new AbortController();
    async function fetchState() {
      try {
-       const res = await fetch(`/api/state/${encodeURIComponent(id)}`);
+       const res = await fetch(`/api/state/${encodeURIComponent(id)}`, {
+         signal: controller.signal
+       });
        if (!res.ok) {
          if (res.status === 404) throw new Error('State not found');
          throw new Error('Failed to fetch state');
        }
        const data = await res.json();
        setData(data);
      } catch (err) {
+       if (err instanceof Error && err.name === 'AbortError') return;
        setError(err instanceof Error ? err.message : 'Unknown error');
      } finally {
        setLoading(false);
      }
    }
    fetchState();
+   return () => controller.abort();
  }, [id]);
explorer/src/lib/formatters.ts (1)

6-11: Consider extracting duration formatting logic.

The formatting logic for converting milliseconds to human-readable strings is duplicated between formatDuration (lines 8-10) and getDuration (lines 27-29). Extracting this into a helper would reduce duplication and ensure consistency.

🔎 Proposed refactor
+/**
+ * Format milliseconds to human-readable string
+ */
+function formatMs(durationMs: number): string {
+  if (durationMs < 1000) return `${durationMs.toFixed(0)}ms`;
+  if (durationMs < 60000) return `${(durationMs / 1000).toFixed(2)}s`;
+  return `${(durationMs / 60000).toFixed(2)}m`;
+}
+
 /**
  * Format a duration in seconds to a human-readable string
  */
 export function formatDuration(durationSeconds: number): string {
   const durationMs = durationSeconds * 1000;
-  if (durationMs < 1000) return `${durationMs.toFixed(0)}ms`;
-  if (durationMs < 60000) return `${(durationMs / 1000).toFixed(2)}s`;
-  return `${(durationMs / 60000).toFixed(2)}m`;
+  return formatMs(durationMs);
 }

 /**
  * Get the duration between two timestamps (in seconds)
  */
 export function getDuration(start: number, end: number): string {
   const durationMs = Math.abs(end - start) * 1000;
-  if (durationMs < 1000) return `${durationMs.toFixed(0)}ms`;
-  if (durationMs < 60000) return `${(durationMs / 1000).toFixed(2)}s`;
-  return `${(durationMs / 60000).toFixed(2)}min`;
+  return formatMs(durationMs);
 }

Also applies to: 25-30

explorer/src/components/ConsensusRound.tsx (2)

173-176: Truncate validator addresses in votes section for consistency.

The validator address is displayed in full (line 175), but addresses are truncated in the Leader (line 90) and Validators (line 134) sections using truncateAddress. This inconsistency could cause UI overflow with long addresses.

🔎 Proposed fix
                    <div className="flex-1 min-w-0">
                      {vote.validator_address && (
                        <code className="text-xs font-mono block truncate">
-                         {vote.validator_address}
+                         {truncateAddress(vote.validator_address, 10, 8)}
                        </code>
                      )}
                    </div>

44-52: Vote stats calculation silently ignores unexpected vote types.

The reduce function only counts 'agree', 'disagree', and 'timeout' votes. Any other vote type is silently ignored, which could mask data quality issues or future vote type additions.

Consider logging a warning or counting unknown votes separately for debugging:

  const voteStats = entry.votes?.reduce(
    (acc, v) => {
      if (v.vote === 'agree') acc.agree++;
      else if (v.vote === 'disagree') acc.disagree++;
      else if (v.vote === 'timeout') acc.timeout++;
+     else if (v.vote && v.vote !== '') {
+       console.warn('Unknown vote type:', v.vote);
+     }
      return acc;
    },
    { agree: 0, disagree: 0, timeout: 0 }
  ) || { agree: 0, disagree: 0, timeout: 0 };
explorer/src/app/transactions/page.tsx (2)

29-30: Add radix parameter to parseInt calls.

For clarity and to avoid potential issues with leading zeros, explicitly specify radix 10.

🔎 Proposed fix
-  const page = parseInt(searchParams.get('page') || '1');
-  const limit = parseInt(searchParams.get('limit') || '20');
+  const page = parseInt(searchParams.get('page') || '1', 10);
+  const limit = parseInt(searchParams.get('limit') || '20', 10);

176-180: Edge case: "Showing 1 - 0 of 0" when no results.

When data.pagination.total is 0, the display shows "Showing 1 - 0 of 0 transactions", which is awkward. Consider handling the empty state explicitly.

🔎 Proposed fix
              <div className="text-sm text-slate-600">
-                Showing <span className="font-medium text-slate-800">{((page - 1) * limit) + 1}</span> - <span className="font-medium text-slate-800">{Math.min(page * limit, data.pagination.total)}</span> of <span className="font-medium text-slate-800">{data.pagination.total}</span> transactions
+                {data.pagination.total === 0 ? (
+                  <span>No transactions found</span>
+                ) : (
+                  <>Showing <span className="font-medium text-slate-800">{((page - 1) * limit) + 1}</span> - <span className="font-medium text-slate-800">{Math.min(page * limit, data.pagination.total)}</span> of <span className="font-medium text-slate-800">{data.pagination.total}</span> transactions</>
+                )}
              </div>
explorer/src/components/JsonViewer.tsx (2)

17-21: Add error handling for clipboard API.

The copyToClipboard function doesn't handle potential failures (e.g., clipboard permissions denied, non-secure context). Consider adding a try-catch.

🔎 Proposed fix
  const copyToClipboard = () => {
-   navigator.clipboard.writeText(JSON.stringify(data, null, 2));
-   setCopied(true);
-   setTimeout(() => setCopied(false), 2000);
+   navigator.clipboard.writeText(JSON.stringify(data, null, 2))
+     .then(() => {
+       setCopied(true);
+       setTimeout(() => setCopied(false), 2000);
+     })
+     .catch(() => {
+       // Silently fail or optionally show error state
+     });
  };

70-70: Inconsistent default expansion depth between arrays and objects.

Arrays use level < 2 (expand 2 levels deep), while objects use level < 1 (expand 1 level deep) for initialExpanded. If this is intentional UX, consider adding a comment; otherwise, align them for consistency.

Also applies to: 111-111

explorer/src/app/transactions/[hash]/components/MonitoringTab.tsx (1)

288-288: Unnecessary type assertion on round.round.

The as number cast is redundant since round.round is already typed as number | undefined in ConsensusHistoryEntry, and the nullish coalescing handles the undefined case.

🔎 Proposed fix
-                        Round {(round.round as number) ?? idx + 1}
+                        Round {round.round ?? idx + 1}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a58427c and ea576e3.

⛔ Files ignored due to path filters (6)
  • explorer/public/file.svg is excluded by !**/*.svg
  • explorer/public/globe.svg is excluded by !**/*.svg
  • explorer/public/next.svg is excluded by !**/*.svg
  • explorer/public/vercel.svg is excluded by !**/*.svg
  • explorer/public/window.svg is excluded by !**/*.svg
  • explorer/src/app/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (52)
  • docker-compose.yml
  • docker/Dockerfile.explorer
  • explorer/.env.sample
  • explorer/.gitignore
  • explorer/README.md
  • explorer/eslint.config.mjs
  • explorer/next.config.ts
  • explorer/package.json
  • explorer/postcss.config.mjs
  • explorer/src/app/api/providers/route.ts
  • explorer/src/app/api/state/[id]/route.ts
  • explorer/src/app/api/state/route.ts
  • explorer/src/app/api/stats/route.ts
  • explorer/src/app/api/transactions/[hash]/route.ts
  • explorer/src/app/api/transactions/route.ts
  • explorer/src/app/api/validators/route.ts
  • explorer/src/app/globals.css
  • explorer/src/app/layout.tsx
  • explorer/src/app/page.tsx
  • explorer/src/app/providers/page.tsx
  • explorer/src/app/state/[id]/page.tsx
  • explorer/src/app/state/page.tsx
  • explorer/src/app/transactions/[hash]/components/ConsensusTab.tsx
  • explorer/src/app/transactions/[hash]/components/DataTab.tsx
  • explorer/src/app/transactions/[hash]/components/MonitoringTab.tsx
  • explorer/src/app/transactions/[hash]/components/OverviewTab.tsx
  • explorer/src/app/transactions/[hash]/components/RelatedTab.tsx
  • explorer/src/app/transactions/[hash]/components/index.ts
  • explorer/src/app/transactions/[hash]/page.tsx
  • explorer/src/app/transactions/page.tsx
  • explorer/src/app/validators/page.tsx
  • explorer/src/components/ConsensusRound.tsx
  • explorer/src/components/ConsensusViewer.tsx
  • explorer/src/components/CopyButton.tsx
  • explorer/src/components/InfoRow.tsx
  • explorer/src/components/JsonViewer.tsx
  • explorer/src/components/MonitoringTimeline.tsx
  • explorer/src/components/Navigation.tsx
  • explorer/src/components/StatCard.tsx
  • explorer/src/components/StatusBadge.tsx
  • explorer/src/components/TransactionTable.tsx
  • explorer/src/components/TransactionTypeLabel.tsx
  • explorer/src/components/VoteIcon.tsx
  • explorer/src/components/index.ts
  • explorer/src/lib/consensusUtils.ts
  • explorer/src/lib/constants.ts
  • explorer/src/lib/db.ts
  • explorer/src/lib/formatters.ts
  • explorer/src/lib/index.ts
  • explorer/src/lib/transactionUtils.ts
  • explorer/src/lib/types.ts
  • explorer/tsconfig.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint and Prettier for TypeScript/JavaScript code formatting and linting with strict mode enabled

Files:

  • explorer/src/lib/db.ts
  • explorer/src/app/api/transactions/route.ts
  • explorer/src/components/Navigation.tsx
  • explorer/src/app/api/transactions/[hash]/route.ts
  • explorer/src/app/api/stats/route.ts
  • explorer/src/components/StatusBadge.tsx
  • explorer/src/app/transactions/[hash]/components/RelatedTab.tsx
  • explorer/src/app/api/providers/route.ts
  • explorer/src/app/transactions/[hash]/components/ConsensusTab.tsx
  • explorer/src/components/InfoRow.tsx
  • explorer/src/lib/transactionUtils.ts
  • explorer/src/app/state/page.tsx
  • explorer/src/components/CopyButton.tsx
  • explorer/src/lib/consensusUtils.ts
  • explorer/src/app/transactions/page.tsx
  • explorer/src/app/transactions/[hash]/components/OverviewTab.tsx
  • explorer/src/components/index.ts
  • explorer/src/lib/formatters.ts
  • explorer/src/components/VoteIcon.tsx
  • explorer/src/components/StatCard.tsx
  • explorer/src/lib/constants.ts
  • explorer/src/components/TransactionTypeLabel.tsx
  • explorer/src/lib/types.ts
  • explorer/next.config.ts
  • explorer/src/components/ConsensusViewer.tsx
  • explorer/src/app/validators/page.tsx
  • explorer/src/app/transactions/[hash]/components/DataTab.tsx
  • explorer/src/components/JsonViewer.tsx
  • explorer/src/app/transactions/[hash]/components/index.ts
  • explorer/src/app/state/[id]/page.tsx
  • explorer/src/app/api/state/[id]/route.ts
  • explorer/src/app/transactions/[hash]/components/MonitoringTab.tsx
  • explorer/src/app/page.tsx
  • explorer/src/app/providers/page.tsx
  • explorer/src/components/ConsensusRound.tsx
  • explorer/src/lib/index.ts
  • explorer/src/components/TransactionTable.tsx
  • explorer/src/app/api/state/route.ts
  • explorer/src/app/transactions/[hash]/page.tsx
  • explorer/src/app/api/validators/route.ts
  • explorer/src/components/MonitoringTimeline.tsx
  • explorer/src/app/layout.tsx
🧠 Learnings (4)
📚 Learning: 2025-12-28T17:12:28.860Z
Learnt from: CR
Repo: genlayerlabs/genlayer-studio PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T17:12:28.860Z
Learning: Applies to frontend/src/**/*.{ts,tsx,vue} : Use Pinia stores for state management in the frontend, specifically SimulatorStore for contract execution, ContractsStore for deployed contracts, and AccountsStore for user accounts

Applied to files:

  • explorer/src/app/state/page.tsx
📚 Learning: 2025-12-28T17:12:28.860Z
Learnt from: CR
Repo: genlayerlabs/genlayer-studio PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T17:12:28.860Z
Learning: Applies to frontend/src/**/*.{ts,tsx,vue} : Use TanStack Query for caching and state synchronization with the API in frontend code

Applied to files:

  • explorer/src/app/state/page.tsx
📚 Learning: 2025-12-28T17:12:28.860Z
Learnt from: CR
Repo: genlayerlabs/genlayer-studio PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T17:12:28.860Z
Learning: Applies to **/*.{js,ts,tsx} : Use ESLint and Prettier for TypeScript/JavaScript code formatting and linting with strict mode enabled

Applied to files:

  • explorer/eslint.config.mjs
  • explorer/tsconfig.json
📚 Learning: 2025-12-28T17:12:28.860Z
Learnt from: CR
Repo: genlayerlabs/genlayer-studio PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T17:12:28.860Z
Learning: Applies to frontend/tsconfig.json : Frontend must use strict mode for TypeScript configuration

Applied to files:

  • explorer/tsconfig.json
🧬 Code graph analysis (30)
explorer/src/app/api/transactions/route.ts (3)
explorer/src/app/api/state/route.ts (1)
  • GET (4-34)
explorer/src/app/api/transactions/[hash]/route.ts (1)
  • GET (4-55)
explorer/src/app/api/validators/route.ts (1)
  • GET (4-25)
explorer/src/components/Navigation.tsx (1)
explorer/src/components/index.ts (1)
  • Navigation (5-5)
explorer/src/app/api/transactions/[hash]/route.ts (2)
explorer/src/app/api/state/[id]/route.ts (1)
  • GET (4-43)
explorer/src/app/api/transactions/route.ts (1)
  • GET (4-71)
explorer/src/app/api/stats/route.ts (4)
explorer/src/app/api/providers/route.ts (1)
  • GET (4-25)
explorer/src/app/api/state/route.ts (1)
  • GET (4-34)
explorer/src/app/api/transactions/route.ts (1)
  • GET (4-71)
explorer/src/app/api/validators/route.ts (1)
  • GET (4-25)
explorer/src/components/StatusBadge.tsx (2)
explorer/src/lib/types.ts (1)
  • TransactionStatus (1-12)
explorer/src/components/index.ts (1)
  • StatusBadge (7-7)
explorer/src/app/transactions/[hash]/components/RelatedTab.tsx (3)
explorer/src/lib/types.ts (1)
  • Transaction (14-51)
explorer/src/app/transactions/[hash]/components/index.ts (1)
  • RelatedTab (5-5)
explorer/src/components/StatusBadge.tsx (1)
  • StatusBadge (21-30)
explorer/src/app/api/providers/route.ts (6)
explorer/src/app/api/state/[id]/route.ts (1)
  • GET (4-43)
explorer/src/app/api/state/route.ts (1)
  • GET (4-34)
explorer/src/app/api/stats/route.ts (1)
  • GET (4-81)
explorer/src/app/api/transactions/[hash]/route.ts (1)
  • GET (4-55)
explorer/src/app/api/transactions/route.ts (1)
  • GET (4-71)
explorer/src/app/api/validators/route.ts (1)
  • GET (4-25)
explorer/src/app/transactions/[hash]/components/ConsensusTab.tsx (3)
explorer/src/lib/types.ts (1)
  • Transaction (14-51)
explorer/src/components/ConsensusViewer.tsx (1)
  • ConsensusViewer (14-107)
explorer/src/components/JsonViewer.tsx (1)
  • JsonViewer (13-121)
explorer/src/components/InfoRow.tsx (2)
explorer/src/components/index.ts (2)
  • InfoRow (3-3)
  • CopyButton (2-2)
explorer/src/components/CopyButton.tsx (1)
  • CopyButton (12-36)
explorer/src/lib/transactionUtils.ts (3)
explorer/src/lib/types.ts (1)
  • Transaction (14-51)
explorer/src/lib/consensusUtils.ts (1)
  • isNewConsensusFormat (6-13)
explorer/src/lib/formatters.ts (1)
  • formatDuration (6-11)
explorer/src/app/state/page.tsx (1)
explorer/src/lib/types.ts (1)
  • CurrentState (114-119)
explorer/src/components/CopyButton.tsx (1)
explorer/src/components/index.ts (1)
  • CopyButton (2-2)
explorer/src/lib/consensusUtils.ts (1)
explorer/src/lib/types.ts (3)
  • NewConsensusHistory (74-78)
  • ConsensusHistoryData (81-81)
  • ConsensusHistoryEntry (53-60)
explorer/src/app/transactions/[hash]/components/OverviewTab.tsx (5)
explorer/src/lib/types.ts (1)
  • Transaction (14-51)
explorer/src/lib/transactionUtils.ts (1)
  • getExecutionResult (66-83)
explorer/src/components/InfoRow.tsx (1)
  • InfoRow (12-24)
explorer/src/components/StatusBadge.tsx (1)
  • StatusBadge (21-30)
explorer/src/components/TransactionTypeLabel.tsx (1)
  • TransactionTypeLabel (10-48)
explorer/src/components/VoteIcon.tsx (1)
explorer/src/components/index.ts (1)
  • VoteIcon (16-16)
explorer/src/components/StatCard.tsx (1)
explorer/src/components/index.ts (1)
  • StatCard (6-6)
explorer/src/lib/constants.ts (1)
explorer/src/lib/types.ts (1)
  • TransactionStatus (1-12)
explorer/src/components/TransactionTypeLabel.tsx (1)
explorer/src/lib/transactionUtils.ts (1)
  • isContractDeploy (8-23)
explorer/src/components/ConsensusViewer.tsx (4)
explorer/src/lib/types.ts (1)
  • ConsensusHistoryData (81-81)
explorer/src/lib/consensusUtils.ts (2)
  • isNewConsensusFormat (6-13)
  • isLegacyConsensusFormat (18-20)
explorer/src/components/ConsensusRound.tsx (2)
  • LegacyConsensusEntry (9-34)
  • ConsensusRound (41-189)
explorer/src/components/JsonViewer.tsx (1)
  • JsonViewer (13-121)
explorer/src/app/transactions/[hash]/components/DataTab.tsx (4)
explorer/src/lib/types.ts (1)
  • Transaction (14-51)
explorer/src/app/transactions/[hash]/components/index.ts (1)
  • DataTab (4-4)
explorer/src/components/JsonViewer.tsx (1)
  • JsonViewer (13-121)
explorer/src/components/index.ts (1)
  • JsonViewer (4-4)
explorer/src/components/JsonViewer.tsx (1)
explorer/src/components/index.ts (1)
  • JsonViewer (4-4)
explorer/src/app/state/[id]/page.tsx (5)
explorer/src/lib/types.ts (2)
  • CurrentState (114-119)
  • Transaction (14-51)
explorer/src/components/CopyButton.tsx (1)
  • CopyButton (12-36)
explorer/src/components/JsonViewer.tsx (1)
  • JsonViewer (13-121)
explorer/src/components/StatusBadge.tsx (1)
  • StatusBadge (21-30)
explorer/src/lib/formatters.ts (1)
  • truncateAddress (35-38)
explorer/src/app/api/state/[id]/route.ts (4)
explorer/src/app/api/providers/route.ts (1)
  • GET (4-25)
explorer/src/app/api/state/route.ts (1)
  • GET (4-34)
explorer/src/app/api/transactions/[hash]/route.ts (1)
  • GET (4-55)
explorer/src/app/api/validators/route.ts (1)
  • GET (4-25)
explorer/src/app/transactions/[hash]/components/MonitoringTab.tsx (4)
explorer/src/lib/types.ts (2)
  • Transaction (14-51)
  • ConsensusHistoryEntry (53-60)
explorer/src/lib/consensusUtils.ts (2)
  • getConsensusRoundCount (25-37)
  • isNewConsensusFormat (6-13)
explorer/src/components/MonitoringTimeline.tsx (1)
  • MonitoringTimeline (13-71)
explorer/src/components/JsonViewer.tsx (1)
  • JsonViewer (13-121)
explorer/src/app/providers/page.tsx (3)
explorer/src/lib/types.ts (1)
  • LLMProvider (121-131)
explorer/src/components/JsonViewer.tsx (1)
  • JsonViewer (13-121)
explorer/src/components/index.ts (1)
  • JsonViewer (4-4)
explorer/src/components/ConsensusRound.tsx (3)
explorer/src/lib/formatters.ts (1)
  • truncateAddress (35-38)
explorer/src/components/JsonViewer.tsx (1)
  • JsonViewer (13-121)
explorer/src/components/VoteIcon.tsx (1)
  • VoteIcon (10-21)
explorer/src/app/api/state/route.ts (4)
explorer/src/app/api/providers/route.ts (1)
  • GET (4-25)
explorer/src/app/api/state/[id]/route.ts (1)
  • GET (4-43)
explorer/src/app/api/transactions/route.ts (1)
  • GET (4-71)
explorer/src/app/api/validators/route.ts (1)
  • GET (4-25)
explorer/src/app/transactions/[hash]/page.tsx (7)
explorer/src/components/CopyButton.tsx (1)
  • CopyButton (12-36)
explorer/src/components/StatusBadge.tsx (1)
  • StatusBadge (21-30)
explorer/src/app/transactions/[hash]/components/OverviewTab.tsx (1)
  • OverviewTab (15-109)
explorer/src/app/transactions/[hash]/components/MonitoringTab.tsx (1)
  • MonitoringTab (24-113)
explorer/src/app/transactions/[hash]/components/ConsensusTab.tsx (1)
  • ConsensusTab (11-44)
explorer/src/app/transactions/[hash]/components/DataTab.tsx (1)
  • DataTab (11-60)
explorer/src/app/transactions/[hash]/components/RelatedTab.tsx (1)
  • RelatedTab (14-72)
explorer/src/app/api/validators/route.ts (6)
explorer/src/app/api/providers/route.ts (1)
  • GET (4-25)
explorer/src/app/api/state/[id]/route.ts (1)
  • GET (4-43)
explorer/src/app/api/state/route.ts (1)
  • GET (4-34)
explorer/src/app/api/stats/route.ts (1)
  • GET (4-81)
explorer/src/app/api/transactions/[hash]/route.ts (1)
  • GET (4-55)
explorer/src/app/api/transactions/route.ts (1)
  • GET (4-71)
explorer/src/app/layout.tsx (2)
explorer/src/components/Navigation.tsx (1)
  • Navigation (15-71)
explorer/src/components/index.ts (1)
  • Navigation (5-5)
🪛 Biome (2.1.2)
explorer/src/app/globals.css

[error] 83-83: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 84-84: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

🪛 dotenv-linter (4.0.0)
explorer/.env.sample

[warning] 5-5: [UnorderedKey] The DB_PASSWORD key should go before the DB_USER key

(UnorderedKey)


[warning] 6-6: [UnorderedKey] The DB_PORT key should go before the DB_USER key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: backend-unit-tests

</div>
</div>
<div className="space-y-4">
{provider.config && (typeof provider.config === 'object' ? Object.keys(provider.config).length > 0 : provider.config) && (
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Simplify the conditional check for config rendering.

The conditional at Line 179 is complex and checks both object type and non-empty keys, but according to the type definition in explorer/src/lib/types.ts (lines 120-130), config can be Record<string, unknown> | string. The current logic doesn't handle the string case correctly and could be simplified.

🔎 Proposed simplification
-{provider.config && (typeof provider.config === 'object' ? Object.keys(provider.config).length > 0 : provider.config) && (
+{provider.config && (typeof provider.config === 'string' || Object.keys(provider.config).length > 0) && (
   <div>
     <h4 className="font-medium text-gray-700 mb-2">Config</h4>
     <div className="bg-gray-50 p-3 rounded-lg overflow-auto max-h-48">
       <JsonViewer data={provider.config} initialExpanded={false} />
     </div>
   </div>
 )}

This also properly handles the string case defined in the LLMProvider type.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{provider.config && (typeof provider.config === 'object' ? Object.keys(provider.config).length > 0 : provider.config) && (
{provider.config && (typeof provider.config === 'string' || Object.keys(provider.config).length > 0) && (
🤖 Prompt for AI Agents
In @explorer/src/app/providers/page.tsx around line 179, The conditional for
rendering provider.config is overly complex and mishandles the string case;
update the check to first ensure provider.config is truthy and then render if
it's a non-empty string or an object with keys (e.g. check provider.config &&
(typeof provider.config === 'string' || Object.keys(provider.config).length >
0)), referencing the LLMProvider type and the provider.config usage in the
render to ensure both string and Record<string, unknown> cases are handled.

Comment on lines 4 to 8
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'genlayer_state',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
port: parseInt(process.env.DB_PORT || '5432'),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and read its content
cat -n explorer/src/lib/db.ts | head -30

Repository: genlayerlabs/genlayer-studio

Length of output: 466


🏁 Script executed:

# Search for .env documentation and environment variable setup
fd -type f "\.(env|example)" explorer/ || echo "No .env files found"
fd -type f -e md explorer/ | xargs grep -l "DB_" 2>/dev/null | head -5

Repository: genlayerlabs/genlayer-studio

Length of output: 450


🏁 Script executed:

# Check for DB environment variable validation and documentation
rg -n "DB_USER|DB_PASSWORD|DB_HOST|DB_PORT" explorer/src --type ts -A 2 -B 2 | head -50

Repository: genlayerlabs/genlayer-studio

Length of output: 567


🏁 Script executed:

# Check if there's any pool configuration setup elsewhere
rg -n "Pool\|max\|idleTimeout\|connectionTimeout" explorer/src --type ts | head -20

Repository: genlayerlabs/genlayer-studio

Length of output: 54


Fix database pool configuration: add connection limits, timeouts, SSL, and remove hardcoded fallback credentials.

The pool setup has critical configuration gaps and security concerns:

  1. Lines 6-7: Hardcoded default credentials create a security risk. If DB_USER or DB_PASSWORD are not set, the connection silently falls back to 'postgres'. These should be required and validated at startup.

  2. Missing pool limits and timeouts: No max (max connections), idleTimeoutMillis, or connectionTimeoutMillis configured, risking connection exhaustion or hung connections in production.

  3. No SSL/TLS: Production environments require encrypted database connections. Add conditional SSL configuration.

  4. Line 8: parseInt must specify radix: parseInt(process.env.DB_PORT || '5432', 10).

Additionally, create or update .env.example with required DB variables and add validation logic to ensure all required environment variables are set before the pool is created.

Recommended changes
 const pool = new Pool({
   host: process.env.DB_HOST || 'localhost',
   database: process.env.DB_NAME || 'genlayer_state',
-  user: process.env.DB_USER || 'postgres',
-  password: process.env.DB_PASSWORD || 'postgres',
-  port: parseInt(process.env.DB_PORT || '5432'),
+  user: process.env.DB_USER,
+  password: process.env.DB_PASSWORD,
+  port: parseInt(process.env.DB_PORT || '5432', 10),
+  max: parseInt(process.env.DB_POOL_MAX || '10', 10),
+  idleTimeoutMillis: 30000,
+  connectionTimeoutMillis: 5000,
+  ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: true } : false,
 });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @explorer/src/lib/db.ts around lines 4-8, Remove hardcoded fallback
DB_USER/DB_PASSWORD and add startup validation that required env vars (DB_HOST,
DB_NAME, DB_USER, DB_PASSWORD, DB_PORT) are present before creating the pool; if
missing, throw/exit with a clear error. When constructing the Pool (new Pool or
createPool in this module), set connection limits and timeouts: max (e.g., from
DB_MAX or default 10), idleTimeoutMillis, and connectionTimeoutMillis. Add
conditional SSL config (use DB_SSL=true or NODE_ENV==='production' to enable
ssl: { rejectUnauthorized: true }) and ensure the DB_PORT parsing uses radix 10
(parseInt(process.env.DB_PORT, 10)). Finally, update .env.example to list
required DB variables and their expected formats and add the validation logic
near the Pool initialization (the symbol names to locate: Pool/new
Pool/createPool and the DB_PORT parseInt call).

"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the explorer/tsconfig.json file
find . -name "tsconfig.json" -path "*/explorer/*" | head -5

Repository: genlayerlabs/genlayer-studio

Length of output: 200


🌐 Web query:

Next.js 16 React 19 jsx tsconfig.json preserve vs react-jsx configuration

💡 Result:

Short answer

  • Use "jsx": "preserve" in tsconfig.json for Next.js (Next’s build pipeline/SWC expects to receive JSX to transform). Use "jsx": "react-jsx" only if you want TypeScript itself to emit the new automatic React runtime output (_jsx/_jsxDEV) and you are not relying on Next/SWC to transform raw JSX. [1][2]

Why

  • TypeScript modes:
    • preserve — leaves JSX in the output (for a later transform step such as Babel/SWC). Useful when your bundler/toolchain will perform the JSX transform. [1]
    • react-jsx / react-jsxdev — TypeScript performs the new automatic JSX transform (no need to import React) and emits _jsx/_jsxDEV calls. This was introduced for the React automatic runtime (React 17+) and requires TS support. [1]
  • Next.js builds run their own compiler (SWC) that expects to handle JSX; Next’s recommended flow is to let the framework handle transforms rather than have TypeScript emit runtime code. That makes "preserve" the safer default inside Next projects. [2][3]

When to choose each

  • Keep "preserve" (recommended for Next.js):
    • You want Next/SWC to handle JSX, minification, and optimizations.
    • You rely on Next’s fast refresh, server compilation, and platform-specific transforms.
  • Use "react-jsx" if:
    • You intentionally want TypeScript to emit the automatic JSX runtime (avoids requiring a further JSX transform step).
    • You have a custom build pipeline where having TypeScript emit _jsx calls is desired/compatible.

Practical note

  • If you switch to "react-jsx", ensure your build tooling and Next version support consuming the emitted output; otherwise stick with "preserve". Test a dev build (next dev / next build) after changing this option. [1][2]

Sources

  • TypeScript JSX docs (explains preserve vs react-jsx / react-jsxdev). [1]
  • Next.js TypeScript / configuration docs (Next’s build pipeline and recommended behavior). [2][3]

References
[1] TypeScript — JSX (tsconfig "jsx" modes).
[2] Next.js — TypeScript / configuration docs.
[3] Next.js — Configuring TypeScript (project-generated tsconfig guidance).


Change jsx compiler option to "preserve" for Next.js compatibility.

The jsx setting is configured as "react-jsx", but Next.js projects should use "jsx": "preserve" instead. Next.js runs its own SWC compiler that expects to receive raw JSX for transformation. When TypeScript emits _jsx calls via react-jsx, it bypasses Next's build pipeline and optimizations, potentially causing build or compatibility issues.

🤖 Prompt for AI Agents
In @explorer/tsconfig.json around line 14, The tsconfig.json currently sets the
"jsx" compiler option to "react-jsx", which causes TypeScript to emit
transformed JSX and can bypass Next.js's SWC pipeline; change the "jsx" option
value to "preserve" so TypeScript leaves JSX intact for Next.js to compile
(update the "jsx" property in tsconfig.json from "react-jsx" to "preserve").

Copy link
Contributor

@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: 1

Fix all issues with AI Agents 🤖
In @explorer/src/components/TransactionTypeLabel.tsx:
- Around line 19-60: The component TransactionTypeLabel currently hardcodes
Tailwind classes for each branch; replace these with the values from the
TRANSACTION_TYPES constant (import TRANSACTION_TYPES from
explorer/src/lib/constants.ts), use type and isDeploy to pick the proper entry
(e.g., map type===0 or isDeploy to the "deploy" entry, otherwise map numeric
type to its TRANSACTION_TYPES key) and inject the class names from that object
instead of literal strings for bg/text/etc., preserving the same displayed
labels ("Deploy", "Call", "Upgrade", "Unknown") and using a fallback
TRANSACTION_TYPES.unknown if a key is missing.
♻️ Duplicate comments (1)
explorer/src/lib/constants.ts (1)

50-95: Verify Tailwind v4 content detection for explorer directory.

A previous review flagged that these Tailwind classes may not be detected by JIT compilation. With Tailwind CSS v4.0.0, you should verify that the build configuration scans explorer/src/ for class names. In Tailwind v4:

  1. Check if @source directive in your CSS includes the explorer directory, or
  2. Ensure your build tool's Tailwind integration discovers files in explorer/src/

Additionally, note that TransactionTypeLabel.tsx (reviewed below) hardcodes these same classes instead of importing them from this constant. Consider refactoring the component to use TRANSACTION_TYPES to eliminate duplication.

Verification approach for Tailwind v4

Check your Tailwind configuration to ensure explorer files are included:

#!/bin/bash
# Find and examine Tailwind CSS configuration
echo "=== Checking Tailwind configuration ==="
fd -e js -e ts -e mjs -e cjs 'tailwind.config' --exec cat {}

echo -e "\n=== Checking for @source directives in CSS ==="
fd -e css --exec grep -l "@source" {} --exec cat {}

echo -e "\n=== Verifying TransactionTypeLabel uses constants ==="
rg "TRANSACTION_TYPES" explorer/src/components/TransactionTypeLabel.tsx
🧹 Nitpick comments (1)
explorer/src/lib/constants.ts (1)

6-18: Consider making the array readonly for stronger type safety.

Since TRANSACTION_STATUS_OPTIONS is used as a constant reference for filtering and validation, marking it as readonly would prevent accidental mutations.

🔎 Proposed refactor
-export const TRANSACTION_STATUS_OPTIONS: TransactionStatus[] = [
+export const TRANSACTION_STATUS_OPTIONS: readonly TransactionStatus[] = [
   "PENDING",
   "ACTIVATED",
   "CANCELED",
   "PROPOSING",
   "COMMITTING",
   "REVEALING",
   "ACCEPTED",
   "FINALIZED",
   "UNDETERMINED",
   "LEADER_TIMEOUT",
   "VALIDATORS_TIMEOUT",
-];
+] as const;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea576e3 and b02b6a8.

📒 Files selected for processing (3)
  • explorer/src/app/api/stats/route.ts
  • explorer/src/components/TransactionTypeLabel.tsx
  • explorer/src/lib/constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • explorer/src/app/api/stats/route.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint and Prettier for TypeScript/JavaScript code formatting and linting with strict mode enabled

Files:

  • explorer/src/components/TransactionTypeLabel.tsx
  • explorer/src/lib/constants.ts
🧬 Code graph analysis (2)
explorer/src/components/TransactionTypeLabel.tsx (3)
explorer/src/components/index.ts (1)
  • TransactionTypeLabel (11-11)
explorer/src/components/TransactionTable.tsx (1)
  • TransactionTypeLabel (221-221)
explorer/src/lib/transactionUtils.ts (1)
  • isContractDeploy (8-23)
explorer/src/lib/constants.ts (1)
explorer/src/lib/types.ts (1)
  • TransactionStatus (1-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: backend-unit-tests
🔇 Additional comments (2)
explorer/src/lib/constants.ts (1)

37-45: LGTM!

The pagination constants are well-structured with proper use of as const for type safety.

explorer/src/components/TransactionTypeLabel.tsx (1)

19-60: Note: Dynamic class construction may require safelist in Tailwind v4.

When using template literals to construct class names dynamically (e.g., `${config.bgColor}`), Tailwind's content detection may not recognize them during the build. Since the classes exist as string literals in TRANSACTION_TYPES, they should be detected there. However, if you encounter missing styles after refactoring, you may need to:

  1. Use @source directive to explicitly include explorer/src/lib/constants.ts, or
  2. Add the specific color classes to a safelist in your Tailwind configuration

Based on library documentation (Tailwind v4), dynamic class construction should work if the literal class strings are present elsewhere in scanned content.

Comment on lines +19 to +60
if (type === 0) {
return (
<span className="bg-blue-50 text-blue-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
Deploy
</span>
);
}

if (isDeploy) {
return (
<span className="bg-orange-50 text-orange-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
Deploy
</span>
);
}

switch (type) {
case 1:
return (
<span className="bg-emerald-50 text-emerald-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
Call
</span>
);
case 2:
return (
<span className="bg-violet-50 text-violet-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
Call
</span>
);
case 3:
return (
<span className="bg-slate-100 text-slate-600 px-2.5 py-1 rounded-lg text-xs font-semibold">
Upgrade
</span>
);
default:
return (
<span className="bg-slate-100 text-slate-600 px-2.5 py-1 rounded-lg text-xs font-semibold">
Unknown
</span>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor to use TRANSACTION_TYPES constant and eliminate hardcoded classes.

The component hardcodes all Tailwind color classes (bg-blue-50, text-blue-700, etc.) that are already defined in explorer/src/lib/constants.ts in the TRANSACTION_TYPES object. This creates maintenance burden and duplication.

🔎 Proposed refactor to use the constant
 "use client";
 
 import { isContractDeploy } from "@/lib/transactionUtils";
+import { TRANSACTION_TYPES } from "@/lib/constants";
 
 interface TransactionTypeLabelProps {
   type: number | null;
   contractSnapshot?: Record<string, unknown> | null;
 }
 
 export function TransactionTypeLabel({
   type,
   contractSnapshot,
 }: TransactionTypeLabelProps) {
-  // Check if this is a contract deployment (type 1,2,3 with contract_code in snapshot)
-  const isDeploy =
-    (type === 1 || type === 2 || type === 3) &&
-    isContractDeploy(contractSnapshot ?? null);
-
-  if (type === 0) {
-    return (
-      <span className="bg-blue-50 text-blue-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
-        Deploy
-      </span>
-    );
-  }
-
-  if (isDeploy) {
-    return (
-      <span className="bg-orange-50 text-orange-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
-        Deploy
-      </span>
-    );
-  }
-
-  switch (type) {
-    case 1:
-      return (
-        <span className="bg-emerald-50 text-emerald-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
-          Call
-        </span>
-      );
-    case 2:
-      return (
-        <span className="bg-violet-50 text-violet-700 px-2.5 py-1 rounded-lg text-xs font-semibold">
-          Call
-        </span>
-      );
-    case 3:
-      return (
-        <span className="bg-slate-100 text-slate-600 px-2.5 py-1 rounded-lg text-xs font-semibold">
-          Upgrade
-        </span>
-      );
-    default:
-      return (
-        <span className="bg-slate-100 text-slate-600 px-2.5 py-1 rounded-lg text-xs font-semibold">
-          Unknown
-        </span>
-      );
-  }
+  const isDeploy =
+    (type === 1 || type === 2 || type === 3) &&
+    isContractDeploy(contractSnapshot ?? null);
+
+  let config;
+  if (type === 0) {
+    config = TRANSACTION_TYPES.DEPLOY.type0;
+  } else if (isDeploy) {
+    config = TRANSACTION_TYPES.DEPLOY[`type${type}` as 'type1' | 'type2' | 'type3'];
+  } else if (type === 1 || type === 2) {
+    config = TRANSACTION_TYPES.CALL[`type${type}` as 'type1' | 'type2'];
+  } else if (type === 3) {
+    config = TRANSACTION_TYPES.CALL.type3;
+  } else {
+    config = TRANSACTION_TYPES.UNKNOWN;
+  }
+
+  return (
+    <span className={`${config.bgColor} ${config.textColor} px-2.5 py-1 rounded-lg text-xs font-semibold`}>
+      {config.label}
+    </span>
+  );
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @explorer/src/components/TransactionTypeLabel.tsx around lines 19-60, The
component TransactionTypeLabel currently hardcodes Tailwind classes for each
branch; replace these with the values from the TRANSACTION_TYPES constant
(import TRANSACTION_TYPES from explorer/src/lib/constants.ts), use type and
isDeploy to pick the proper entry (e.g., map type===0 or isDeploy to the
"deploy" entry, otherwise map numeric type to its TRANSACTION_TYPES key) and
inject the class names from that object instead of literal strings for
bg/text/etc., preserving the same displayed labels ("Deploy", "Call", "Upgrade",
"Unknown") and using a fallback TRANSACTION_TYPES.unknown if a key is missing.

Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
explorer/src/lib/db.ts (1)

5-19: Add environment variable validation and SSL configuration.

The pool creation has two important gaps:

  1. Missing validation (lines 8-11): Required environment variables (DB_HOST, DB_NAME, DB_USER, DB_PASSWORD) are not validated before pool creation. If any are undefined, the pool will be created but connection attempts will fail with unclear error messages.

  2. Missing SSL configuration: Production databases typically require encrypted connections. Add conditional SSL support based on NODE_ENV or an explicit DB_SSL flag.

🔎 Proposed enhancements
 function createPool(): Pool {
   if (!pool) {
+    // Validate required environment variables
+    const requiredEnvVars = ['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASSWORD'];
+    const missing = requiredEnvVars.filter(v => !process.env[v]);
+    if (missing.length > 0) {
+      throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
+    }
+
     pool = new Pool({
       host: process.env.DB_HOST,
       database: process.env.DB_NAME,
       user: process.env.DB_USER,
       password: process.env.DB_PASSWORD,
       port: parseInt(process.env.DB_PORT || '5432', 10),
       max: parseInt(process.env.DB_MAX || '10', 10),
       idleTimeoutMillis: 30000,
       connectionTimeoutMillis: 10000,
+      ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: true } : false,
     });
   }
   return pool;
 }
🧹 Nitpick comments (1)
explorer/src/lib/db.ts (1)

21-27: Consider type safety trade-offs in the Proxy pattern.

The lazy initialization via Proxy is clever but has type safety concerns:

  • Line 22: {} as Pool is a type assertion that doesn't match runtime reality
  • Line 25: Complex casting masks potential type issues

While this pattern works functionally, it sacrifices compile-time type checking. Consider these alternatives:

  1. Simpler approach: Export createPool() directly and let callers manage initialization
  2. Explicit getter: Use a function like getPool() that returns Pool after ensuring initialization
  3. Module-load initialization: Remove lazy pattern if startup cost is acceptable
Alternative: explicit getter function
-// Use a proxy to lazily initialize the pool only when accessed
-const poolProxy = new Proxy({} as Pool, {
-  get(_target, prop) {
-    const actualPool = createPool();
-    return (actualPool as unknown as Record<string | symbol, unknown>)[prop];
-  },
-});
-
-export default poolProxy;
+export function getPool(): Pool {
+  return createPool();
+}
+
+export default { getPool };

Callers would then use: import { getPool } from '@/lib/db'; const pool = getPool();

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b02b6a8 and c069e03.

📒 Files selected for processing (3)
  • docker/Dockerfile.explorer
  • explorer/src/app/api/transactions/route.ts
  • explorer/src/lib/db.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • explorer/src/app/api/transactions/route.ts
  • docker/Dockerfile.explorer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint and Prettier for TypeScript/JavaScript code formatting and linting with strict mode enabled

Files:

  • explorer/src/lib/db.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: backend-unit-tests

@cristiam86 cristiam86 merged commit 3fc9e70 into main Jan 5, 2026
9 of 10 checks passed
@github-actions
Copy link
Contributor

github-actions bot commented Jan 5, 2026

🎉 This PR is included in version 0.85.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 5, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
5 Security Hotspots
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants