Skip to content

feat(apps/ensadmin): add interactive REST API playground to ENSAdmin#1951

Open
notrab wants to merge 8 commits intomainfrom
ensadmin-rest-playground
Open

feat(apps/ensadmin): add interactive REST API playground to ENSAdmin#1951
notrab wants to merge 8 commits intomainfrom
ensadmin-rest-playground

Conversation

@notrab
Copy link
Copy Markdown
Member

@notrab notrab commented Apr 19, 2026

Lite PR

CleanShot 2026-04-19 at 14 56 48@2x CleanShot 2026-04-19 at 14 57 10@2x

Summary

  • Added a new @ensnode/scalar-react wrapper package around @scalar/api-reference-react with ENSNode-themed styling defaults
  • Added a /api/rest page in ENSAdmin that renders an interactvie REST API playground powered by the connected ENSApi instance's /openapi.json
  • Added "REST API Reference" to the sidebar nav under APIs, with breadcrumb and actions (copy OpenAPI URL) parallel route slots
  • Closes Add interactive REST API playground to ENSAdmin #1875

Why

  • ENSAdmin currently only surfaces GraphQL APIs (Subgraph and Omnigraph). ENSApi also publishes REST APIs (Resolution, Meta, Explore, ENSAwards) via OpenAPI, but there's no way to discover or interact with them from ENSAdmin.
  • This enables interactive REST API exploration against any connected ENSNode instance (not just the production instance), which is useful for testing against Sepolia or local instances

Testing

  • Verified pnpm build succeeds with /api/rest in the route table
  • Verified typecheck passes (only pre-existing api/graphql errors remain)
  • Verified dev server serves the page at /api/rest?connection=https://api.alpha.ensnode.io with HTTP 200
  • Manual browser testing of the Scalar playground loading the OpenAPI spec from the connected instance

Notes for Reviewer (Optional)

The restApi feature is gated simply on config loading successfully (no plugin check), since /openapi.json is always served by any ENSApi instance (same as other pages)

@scalar/api-reference-react is a Vue-under-the-hood wrapper (it's client-side only, which is fine since the page requires an active connection anyway)

The links in the nav are still static, and not dynamic.


Pre-Review Checklist (Blocking)

  • This PR does not introduce significant changes and is low-risk to review quickly.
  • Relevant changesets are included (or are not required)

notrab added 2 commits April 19, 2026 14:58
…t`) powered by Scalar, loading the OpenAPI spec from the connected ENSApi instance. Introduced `@ensnode/scalar-react` wrapper package.
Copilot AI review requested due to automatic review settings April 19, 2026 14:06
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Apr 22, 2026 2:23pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
ensnode.io Skipped Skipped Apr 22, 2026 2:23pm
ensrainbow.io Skipped Skipped Apr 22, 2026 2:23pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 19, 2026

🦋 Changeset detected

Latest commit: ba62521

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 24 packages
Name Type
ensadmin Patch
ensindexer Patch
ensrainbow Patch
ensapi Patch
fallback-ensapi Patch
enssdk Patch
enscli Patch
enskit Patch
ensskills Patch
@ensnode/datasources Patch
@ensnode/ensrainbow-sdk Patch
@ensnode/ensdb-sdk Patch
@ensnode/ensnode-react Patch
@ensnode/ensnode-sdk Patch
@ensnode/ponder-sdk Patch
@ensnode/ponder-subgraph Patch
@ensnode/shared-configs Patch
@docs/ensnode Patch
@docs/ensrainbow Patch
@docs/mintlify Patch
@namehash/ens-referrals Patch
@namehash/namehash-ui Patch
@ensnode/enskit-react-example Patch
@ensnode/integration-test-env Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

Warning

Rate limit exceeded

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

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 45 minutes and 57 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 66d65df9-5e12-414e-9cb8-99269ae7f167

📥 Commits

Reviewing files that changed from the base of the PR and between 955db50 and ba62521.

📒 Files selected for processing (2)
  • apps/ensadmin/src/app/globals.css
  • packages/scalar-react/src/api-reference.tsx
📝 Walkthrough

Walkthrough

Adds an interactive REST API Reference at /api/rest in ENSAdmin using a new @ensnode/scalar-react wrapper; includes pages, a loading spinner, breadcrumbs, a copyable OpenAPI URL action, a hook to compute /openapi.json, a restApi feature flag, sidebar nav entry, and header styling.

Changes

Cohort / File(s) Summary
Scalar React Wrapper Package
packages/scalar-react/package.json, packages/scalar-react/src/api-reference.tsx, packages/scalar-react/src/index.ts, packages/scalar-react/tsconfig.json
New private package @ensnode/scalar-react exporting ScalarApiReference, which configures @scalar/api-reference-react with controlled UI options and injected light-mode CSS.
ENSAdmin REST API Pages & UI
apps/ensadmin/src/app/api/rest/page.tsx, apps/ensadmin/src/app/api/rest/loading.tsx, apps/ensadmin/src/app/@breadcrumbs/(apis)/api/rest/page.tsx, apps/ensadmin/src/app/@actions/api/rest/page.tsx
New client-rendered REST API Reference page using ScalarApiReference, loading spinner, breadcrumb page, and an actions component that displays and copies the computed OpenAPI URL.
Feature Flags & Hooks
apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx, apps/ensadmin/src/hooks/active/use-openapi-url.ts
Added restApi to ENSAdminFeatures and implemented useOpenApiUrl() which memoizes the /openapi.json URL from the validated ENSApi connection.
Navigation, Dependencies & Styling
apps/ensadmin/package.json, apps/ensadmin/src/components/app-sidebar.tsx, apps/ensadmin/src/components/header.tsx
Added @ensnode/scalar-react workspace dependency, added "REST API Reference" nav item under APIs, and made header sticky with sticky top-0 z-10 bg-background.
Release Notes
.changeset/tangy-pants-camp.md
Changeset declaring an ensadmin patch release announcing the REST API Reference and the new wrapper package.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ENSAdmin as ENSAdmin (client page)
  participant ENSApi as ENSApi (server /openapi.json)
  participant Scalar as Scalar UI (ApiReferenceReact)

  Browser->>ENSAdmin: navigate to /api/rest
  ENSAdmin->>ENSAdmin: useValidatedSelectedConnection(), useOpenApiUrl()
  ENSAdmin->>ENSApi: GET /openapi.json (computed URL)
  ENSApi-->>ENSAdmin: OpenAPI JSON URL / spec
  ENSAdmin->>Scalar: render ScalarApiReference with url + serverUrl
  Scalar-->>Browser: interactive API playground UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped to the docs with a curious nose,
Found openapi trails where the dev-light glows.
Scalar laid maps of each RESTful land,
I copied the URL with a clever hand.
Hop, play, explore — the APIs now grand!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR partially addresses issue #1875 goals [1875] by adding a static REST API playground and creating API visibility, but does not implement dynamic navigation generation based on connection state as preferred in the issue. Clarify in comments whether dynamic navigation generation will be addressed in a follow-up PR, as the issue expresses a preference for this approach.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding an interactive REST API playground to ENSAdmin, which matches the primary objective of the PR.
Description check ✅ Passed The PR description follows the template with complete sections: Summary (3 bullets), Why (explains problem and solution), Testing (4 test cases), Notes for Reviewer, and Pre-Review Checklist marked complete.
Out of Scope Changes check ✅ Passed All changes are directly related to the core objective of adding an interactive REST API playground: new packages, routes, hooks, and UI components are all focused on this feature.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ensadmin-rest-playground

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.

@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 19, 2026 14:08 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 19, 2026 14:08 Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an interactive REST API playground to ENSAdmin by introducing an ENSNode-themed wrapper around Scalar’s React API Reference component and wiring it into the ENSAdmin navigation and route structure.

Changes:

  • Introduces new workspace package @ensnode/scalar-react wrapping @scalar/api-reference-react with ENSNode styling defaults.
  • Adds /api/rest page (plus breadcrumb/actions parallel routes + loading state) to render Scalar against the connected ENSApi’s /openapi.json.
  • Exposes the new page via the ENSAdmin sidebar and gates it behind a new restApi feature flag.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds Scalar + transitive dependencies required for the REST API reference UI.
packages/scalar-react/package.json Defines new @ensnode/scalar-react package metadata and dependencies.
packages/scalar-react/tsup.config.ts Adds build config for the new wrapper package.
packages/scalar-react/tsconfig.json Adds TypeScript config for the new wrapper package.
packages/scalar-react/src/index.ts Exposes the wrapper component entrypoint.
packages/scalar-react/src/api-reference.tsx Implements the Scalar wrapper + ENSNode-themed defaults.
apps/ensadmin/package.json Adds dependency on @ensnode/scalar-react.
apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx Adds restApi feature status used for gating the new page.
apps/ensadmin/src/components/app-sidebar.tsx Adds “REST API Reference” nav item under APIs.
apps/ensadmin/src/app/api/rest/page.tsx New REST API reference page loading /openapi.json from the selected connection.
apps/ensadmin/src/app/api/rest/loading.tsx Loading state for the new route.
apps/ensadmin/src/app/@breadcrumbs/(apis)/api/rest/page.tsx Breadcrumb slot content for the new page.
apps/ensadmin/src/app/@actions/api/rest/page.tsx Actions slot (copy OpenAPI URL) for the new page.
.changeset/tangy-pants-camp.md Changeset entry describing the feature addition.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pnpm-lock.yaml
Comment on lines +4324 to +4328
'@scalar/use-tooltip@1.0.6':
resolution: {integrity: sha512-f0gadIaUnILfi9qYAk7g+fNTsvLGXnam8oOUTxovavC1ocYuGTEykdz3g2MTqnAqRS8OkAB64h9mHf0FBfg6mg==}
engines: {node: '>=18'}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

The lockfile adds @scalar/use-tooltip@1.0.6 which is marked as deprecated. Since this is now part of the dependency graph pulled in by @scalar/api-reference-react, it would be good to either (1) upgrade Scalar to a version that no longer depends on the deprecated package, or (2) document/track this deprecation so it doesn’t get missed during dependency health/security reviews.

Copilot uses AI. Check for mistakes.
Comment thread .changeset/tangy-pants-camp.md
Comment thread packages/scalar-react/package.json Outdated
Comment on lines +1 to +29
{
"name": "@ensnode/scalar-react",
"version": "0.0.0",
"type": "module",
"description": "Scalar API Reference React component for ENSNode",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/namehash/ensnode.git",
"directory": "packages/scalar-react"
},
"homepage": "https://github.com/namehash/ensnode/tree/main/packages/scalar-react",
"files": [
"dist"
],
"exports": {
".": "./src/index.ts"
},
"publishConfig": {
"exports": {
".": {
"default": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts"
},
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

@ensnode/scalar-react is introduced with version 0.0.0 and no publishConfig.access. If this package is intended to be published like the other @ensnode/* packages, it should be versioned via changesets (typically aligning with the repo’s fixed-version set) and include publishConfig.access: "public" to match existing packages’ publishing metadata.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@vercel vercel Bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

RequireENSAdminFeature component uses React hooks but is missing the "use client" directive required by Next.js App Router

Fix on Vercel

Comment thread .changeset/tangy-pants-camp.md
Comment thread packages/scalar-react/package.json Outdated
@notrab
Copy link
Copy Markdown
Member Author

notrab commented Apr 20, 2026

One remaining UX issue I need to resolve with this is when you scroll it has a sticky container so the header moves. I'll try modify the class for the docs/playground area so it sits within the normal scrollable main body of the page, keeping the header.

Copy link
Copy Markdown
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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensadmin/src/app/`@actions/api/rest/page.tsx:
- Around line 10-14: Extract the duplicated URL derivation into a shared hook
named useOpenApiUrl that calls useValidatedSelectedConnection and returns
useMemo(() => new URL("/openapi.json", selectedConnection).toString(),
[selectedConnection]); colocate the new hook with the REST feature (e.g.,
hooks/active) and then replace the inline useMemo in both
useValidatedSelectedConnection call sites (the components referencing the exact
expression in `@actions/api/rest/page.tsx` and api/rest/page.tsx) to instead call
useOpenApiUrl().

In `@packages/scalar-react/package.json`:
- Line 31: Replace the deprecated "prepublish" script in package.json with an
explicit "build" script and add a "prepack" script that invokes it: remove the
"prepublish": "tsup" entry, add "build": "tsup" and add "prepack": "npm run
build" so packaging/publishing reliably runs the build step; ensure you update
any CI/publish docs that referenced "prepublish".
- Around line 20-25: The exports object in package.json currently lists the
"default" condition before "types", which causes Node's condition-resolution to
pick "default" and skip the "types" entry; update the exports "." entry so the
"types" key appears before "default" (i.e., reorder the keys under "exports" ->
"." to place "types" first) to ensure TypeScript moduleResolution
(bundler/node16/nodenext) can find the declared types.

In `@packages/scalar-react/src/api-reference.tsx`:
- Around line 27-47: Replace the typeof window check in ScalarApiReference with
the project's useHydrated hook: import useHydrated from "@/hooks/use-hydrated",
call const hydrated = useHydrated() inside the ScalarApiReference function and
return null when !hydrated; keep the configuration object and the
ApiReferenceReact render (configuration and ApiReferenceReact identifiers remain
unchanged) so the component renders consistently between server and client and
avoids hydration mismatch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5957238b-9a9d-4b2a-bc0e-e4025d0a63ef

📥 Commits

Reviewing files that changed from the base of the PR and between fc88ee5 and 3192877.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • .changeset/tangy-pants-camp.md
  • apps/ensadmin/package.json
  • apps/ensadmin/src/app/@actions/api/rest/page.tsx
  • apps/ensadmin/src/app/@breadcrumbs/(apis)/api/rest/page.tsx
  • apps/ensadmin/src/app/api/rest/loading.tsx
  • apps/ensadmin/src/app/api/rest/page.tsx
  • apps/ensadmin/src/components/app-sidebar.tsx
  • apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx
  • packages/scalar-react/package.json
  • packages/scalar-react/src/api-reference.tsx
  • packages/scalar-react/src/index.ts
  • packages/scalar-react/tsconfig.json
  • packages/scalar-react/tsup.config.ts

Comment thread apps/ensadmin/src/app/@actions/api/rest/page.tsx Outdated
Comment thread packages/scalar-react/package.json Outdated
Comment on lines +20 to +25
"exports": {
".": {
"default": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Put "types" before "default" in the exports conditions.

Node resolves export conditions in object-key order, and once a matching condition is hit, later ones are ignored. With "default" listed before "types", TypeScript's moduleResolution: "bundler"/"node16"/"nodenext" will match default first and never use the declared types entry, degrading type resolution for consumers. Conventionally types must always come first.

🔧 Proposed fix
   "publishConfig": {
     "exports": {
       ".": {
-        "default": "./dist/index.js",
-        "types": "./dist/index.d.ts"
+        "types": "./dist/index.d.ts",
+        "default": "./dist/index.js"
       }
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/scalar-react/package.json` around lines 20 - 25, The exports object
in package.json currently lists the "default" condition before "types", which
causes Node's condition-resolution to pick "default" and skip the "types" entry;
update the exports "." entry so the "types" key appears before "default" (i.e.,
reorder the keys under "exports" -> "." to place "types" first) to ensure
TypeScript moduleResolution (bundler/node16/nodenext) can find the declared
types.

Comment thread packages/scalar-react/package.json Outdated
"types": "./dist/index.d.ts"
},
"scripts": {
"prepublish": "tsup",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

prepublish is deprecated by npm; use build + prepack.

npm has deprecated the prepublish lifecycle script (it runs inconsistently across npm install/npm publish and is a known footgun). Replace with an explicit build script, and wire it via prepack (which reliably runs before packing/publishing and doesn't run on npm install).

🔧 Proposed fix
   "scripts": {
-    "prepublish": "tsup",
+    "build": "tsup",
+    "prepack": "tsup",
     "dev": "tsup --watch",
📝 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
"prepublish": "tsup",
"build": "tsup",
"prepack": "tsup",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/scalar-react/package.json` at line 31, Replace the deprecated
"prepublish" script in package.json with an explicit "build" script and add a
"prepack" script that invokes it: remove the "prepublish": "tsup" entry, add
"build": "tsup" and add "prepack": "npm run build" so packaging/publishing
reliably runs the build step; ensure you update any CI/publish docs that
referenced "prepublish".

Comment on lines +27 to +47
export function ScalarApiReference({ url }: ScalarApiReferenceProps) {
if (typeof window === "undefined") return null;

const configuration: NonNullable<ReferenceProps["configuration"]> = {
url,
theme: "none",
hideDownloadButton: true,
hiddenClients: true,
defaultOpenAllTags: true,
forceDarkModeState: "light",
hideDarkModeToggle: true,
withDefaultFonts: false,
hideClientButton: true,
customCss: CUSTOM_CSS,
};

return (
<div className="flex-1">
<ApiReferenceReact configuration={configuration} />
</div>
);
Copy link
Copy Markdown
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:

# Find the scalar-react API reference file and check its content
fd -t f "api-reference.tsx" packages/scalar-react/src/

# Check if the file exists and contains the use client directive
find . -path "*/packages/scalar-react/src/api-reference.tsx" -exec cat -n {} \;

Repository: namehash/ensnode

Length of output: 1866


🏁 Script executed:

# Search for where ScalarApiReference is used
rg "ScalarApiReference" --type tsx --type ts -B 2 -A 2

Repository: namehash/ensnode

Length of output: 87


🏁 Script executed:

# Check the calling site at apps/ensadmin
fd -t f "page.tsx" apps/ensadmin/src/app/api/rest/

Repository: namehash/ensnode

Length of output: 99


🏁 Script executed:

# Look for use client directives in the scalar-react package
rg "use client" packages/scalar-react/

Repository: namehash/ensnode

Length of output: 117


🏁 Script executed:

# Check the actual usage at the calling page
cat -n apps/ensadmin/src/app/api/rest/page.tsx

Repository: namehash/ensnode

Length of output: 954


🏁 Script executed:

# Check if there are any other components in the scalar-react package
ls -la packages/scalar-react/src/

Repository: namehash/ensnode

Length of output: 295


🏁 Script executed:

# Search for other typeof window checks in the codebase for comparison
rg "typeof window" --type ts --type tsx -B 2 -A 2

Repository: namehash/ensnode

Length of output: 87


🏁 Script executed:

# Search for typeof window patterns without specifying type
rg "typeof window" -B 2 -A 2

Repository: namehash/ensnode

Length of output: 1697


🏁 Script executed:

# Check if there are any dynamic imports with ssr: false in the codebase
rg "ssr.*false" -B 2 -A 2

Repository: namehash/ensnode

Length of output: 42


🏁 Script executed:

# Check the structure of `@scalar/api-reference-react` to understand if it requires a mount guard
rg "@scalar/api-reference-react" -B 1 -A 1

Repository: namehash/ensnode

Length of output: 1303


🏁 Script executed:

# Check the GraphiQLEditor component to see how it handles the same pattern
cat -n apps/ensadmin/src/components/graphiql-editor/components.tsx | head -30

Repository: namehash/ensnode

Length of output: 1247


🏁 Script executed:

# Check if there are any hydration-related errors or issues reported in the repo
rg "hydration" -i

Repository: namehash/ensnode

Length of output: 550


🏁 Script executed:

# Look for React version and Next.js configuration
cat apps/ensadmin/package.json | grep -A 5 '"react"' && cat apps/ensadmin/package.json | grep -A 5 '"next"'

Repository: namehash/ensnode

Length of output: 371


🏁 Script executed:

# Check if there's a use-hydrated hook being used anywhere
cat -n apps/ensadmin/src/hooks/use-hydrated.ts

Repository: namehash/ensnode

Length of output: 554


🏁 Script executed:

# Search for usage of the use-hydrated hook to understand the hydration pattern
rg "use-hydrated\|useHydrated" -B 2 -A 2

Repository: namehash/ensnode

Length of output: 42


🏁 Script executed:

# Check Next.js configuration for SSR settings
cat apps/ensadmin/next.config.ts

Repository: namehash/ensnode

Length of output: 307


🏁 Script executed:

# Verify SPA configuration - check if there are any dynamic routes that might trigger SSR
rg "\[.*\]" apps/ensadmin/src/app/ --type ts --type tsx | head -20

Repository: namehash/ensnode

Length of output: 87


🏁 Script executed:

# Check if there are any API routes or server components that might indicate SSR
fd "route.ts" apps/ensadmin/src/app/

Repository: namehash/ensnode

Length of output: 98


🏁 Script executed:

# Confirm the build output by checking if there are any other hydration-related patterns
rg "suppressHydrationWarning" apps/ensadmin/

Repository: namehash/ensnode

Length of output: 42


Use the project's useHydrated() hook to avoid hydration mismatch, not typeof window === "undefined".

The typeof window === "undefined" check causes a hydration mismatch: the component renders null during the static build phase but renders the full tree on the client. Import useHydrated from @/hooks/use-hydrated and wrap the component rendering:

import { useHydrated } from "@ensnode/scalar-react";
// or locally if not exported: import { useHydrated } from "@/hooks/use-hydrated";

export function ScalarApiReference({ url }: ScalarApiReferenceProps) {
  const hydrated = useHydrated();
  if (!hydrated) return null;

  const configuration: NonNullable<ReferenceProps["configuration"]> = {

This aligns with the project's existing hydration pattern (e.g., useIsomorphicEffect from rooks) and avoids the build-time/client-time render mismatch.

📝 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
export function ScalarApiReference({ url }: ScalarApiReferenceProps) {
if (typeof window === "undefined") return null;
const configuration: NonNullable<ReferenceProps["configuration"]> = {
url,
theme: "none",
hideDownloadButton: true,
hiddenClients: true,
defaultOpenAllTags: true,
forceDarkModeState: "light",
hideDarkModeToggle: true,
withDefaultFonts: false,
hideClientButton: true,
customCss: CUSTOM_CSS,
};
return (
<div className="flex-1">
<ApiReferenceReact configuration={configuration} />
</div>
);
import { useHydrated } from "@/hooks/use-hydrated";
export function ScalarApiReference({ url }: ScalarApiReferenceProps) {
const hydrated = useHydrated();
if (!hydrated) return null;
const configuration: NonNullable<ReferenceProps["configuration"]> = {
url,
theme: "none",
hideDownloadButton: true,
hiddenClients: true,
defaultOpenAllTags: true,
forceDarkModeState: "light",
hideDarkModeToggle: true,
withDefaultFonts: false,
hideClientButton: true,
customCss: CUSTOM_CSS,
};
return (
<div className="flex-1">
<ApiReferenceReact configuration={configuration} />
</div>
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/scalar-react/src/api-reference.tsx` around lines 27 - 47, Replace
the typeof window check in ScalarApiReference with the project's useHydrated
hook: import useHydrated from "@/hooks/use-hydrated", call const hydrated =
useHydrated() inside the ScalarApiReference function and return null when
!hydrated; keep the configuration object and the ApiReferenceReact render
(configuration and ApiReferenceReact identifiers remain unchanged) so the
component renders consistently between server and client and avoids hydration
mismatch.

Copilot AI review requested due to automatic review settings April 22, 2026 08:48
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 22, 2026 08:48 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 22, 2026 08:48 Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +53 to +55
export function ScalarApiReference({ url, serverUrl }: ScalarApiReferenceProps) {
if (typeof window === "undefined") return null;

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

ScalarApiReference returns null during the server render (typeof window === "undefined") but renders the full Scalar UI on the first client render. In Next.js client components this can cause a hydration mismatch (server markup is empty, client markup is not). Consider gating on a mounted state set in useEffect (or equivalent hydration guard) so the first client render matches the server output, then render Scalar after mount.

Copilot uses AI. Check for mistakes.
Comment thread apps/ensadmin/src/app/api/rest/page.tsx
Copy link
Copy Markdown
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: 2

♻️ Duplicate comments (1)
packages/scalar-react/src/api-reference.tsx (1)

53-70: ⚠️ Potential issue | 🟠 Major

Use a mount guard instead of typeof window for Scalar.

Line 54 can hydrate as null on the server but as the full Scalar tree on the first browser render. Keep the first client render identical, then mount Scalar after hydration.

🐛 Proposed fix
+import { useEffect, useState } from "react";
 import { ApiReferenceReact, type ReferenceProps } from "@scalar/api-reference-react";
 import "@scalar/api-reference-react/style.css";
@@
 export function ScalarApiReference({ url, serverUrl }: ScalarApiReferenceProps) {
-  if (typeof window === "undefined") return null;
+  const [isMounted, setIsMounted] = useState(false);
+
+  useEffect(() => {
+    setIsMounted(true);
+  }, []);
+
+  if (!isMounted) return null;
 
   const configuration: NonNullable<ReferenceProps["configuration"]> = {

Scalar’s React integration docs describe the wrapper as client-side and call out SSR/SSG caveats: https://scalar.com/products/api-references/integrations/react

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/scalar-react/src/api-reference.tsx` around lines 53 - 70, The
current server/client guard in ScalarApiReference uses typeof window and can
cause a hydration mismatch; replace it with a mount guard inside the component:
add a mounted state (e.g., useState(false)) and set it to true in useEffect(()
=> setMounted(true), []), return null while !mounted, and only render
ApiReferenceReact after mounted; keep the existing configuration object
(configuration) and all props (url, serverUrl) unchanged and ensure React plus
useState/useEffect are imported so the first client render matches the server
render.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensadmin/src/app/`@actions/api/rest/page.tsx:
- Around line 12-20: The URL span can grow indefinitely in the flex row; update
the container div (the element rendering the URL and CopyButton) to include
"min-w-0" and make the URL span (the element rendering {url}) use truncation
classes (e.g., add "truncate" and keep "overflow-hidden" if needed) so long
OpenAPI/local URLs are ellipsized instead of pushing the CopyButton; optionally
add a title attribute to the span to surface the full URL on hover.

In `@packages/scalar-react/src/api-reference.tsx`:
- Around line 16-31: The CUSTOM_CSS currently hardcodes a 4rem header and forces
an internal scroll (see .references-layout, .references-rendered,
.references-navigation-list) which makes Scalar own page layout; remove the
calc(100svh - 4rem) usage and the forced overflow-y:auto and instead let the
component inherit height from its parent (use height: 100% / max-height: 100% or
unset those rules) so the ENSAdmin route container can control header height and
global scrolling; update the CUSTOM_CSS constant to drop those hard-coded
size/scroll rules and rely on parent-provided sizing, and document in the
component README that the parent must provide the available height or scrolling
container.

---

Duplicate comments:
In `@packages/scalar-react/src/api-reference.tsx`:
- Around line 53-70: The current server/client guard in ScalarApiReference uses
typeof window and can cause a hydration mismatch; replace it with a mount guard
inside the component: add a mounted state (e.g., useState(false)) and set it to
true in useEffect(() => setMounted(true), []), return null while !mounted, and
only render ApiReferenceReact after mounted; keep the existing configuration
object (configuration) and all props (url, serverUrl) unchanged and ensure React
plus useState/useEffect are imported so the first client render matches the
server render.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d1b33048-79c9-4daa-9278-28b31235351c

📥 Commits

Reviewing files that changed from the base of the PR and between 3192877 and da1a457.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/ensadmin/src/app/@actions/api/rest/page.tsx
  • apps/ensadmin/src/app/api/rest/page.tsx
  • apps/ensadmin/src/components/header.tsx
  • apps/ensadmin/src/hooks/active/use-openapi-url.ts
  • packages/scalar-react/package.json
  • packages/scalar-react/src/api-reference.tsx

Comment thread apps/ensadmin/src/app/@actions/api/rest/page.tsx Outdated
Comment thread packages/scalar-react/src/api-reference.tsx
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 22, 2026 14:07 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 22, 2026 14:07 Inactive
@notrab notrab marked this pull request as ready for review April 22, 2026 14:08
Copilot AI review requested due to automatic review settings April 22, 2026 14:08
@notrab notrab requested a review from a team as a code owner April 22, 2026 14:08
@notrab
Copy link
Copy Markdown
Member Author

notrab commented Apr 22, 2026

@copilot resolve the merge conflicts in this pull request

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR adds an interactive REST API playground at /api/rest in ENSAdmin powered by Scalar, backed by a new @ensnode/scalar-react wrapper package. It integrates cleanly with the existing connection/feature gating patterns and includes matching breadcrumb and actions (copy OpenAPI URL) parallel route slots. All remaining findings are P2 style suggestions.

Confidence Score: 5/5

Safe to merge — no logic or data-integrity issues found.

All findings are P2 (style/best-practice) suggestions. The feature gating, URL construction, routing slots, and global header change are all correctly implemented and follow existing patterns.

packages/scalar-react/src/api-reference.tsx — minor: configuration not memoized, raw typeof window SSR guard.

Important Files Changed

Filename Overview
packages/scalar-react/src/api-reference.tsx New wrapper around @scalar/api-reference-react with ENSNode theme overrides; configuration is not memoized and the SSR guard pattern is fragile, but both are P2 concerns only.
apps/ensadmin/src/app/api/rest/page.tsx New /api/rest page correctly gates rendering via RequireENSAdminFeature and passes the active connection URL as the Scalar server override.
apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx Added restApi feature status that becomes "supported" whenever config loads successfully, consistent with the always-available nature of /openapi.json.
apps/ensadmin/src/hooks/active/use-openapi-url.ts Correctly constructs the absolute /openapi.json URL from the active connection using new URL(), memoized properly.
apps/ensadmin/src/components/header.tsx Global header made sticky (sticky top-0 z-10 bg-background) — a necessary visual fix so the Scalar playground's calc(100svh - 4rem) height accounts for the header correctly across all pages.
apps/ensadmin/src/app/@actions/api/rest/page.tsx Actions slot renders the OpenAPI URL with a copy-to-clipboard button; clean implementation.
packages/scalar-react/package.json New private workspace package; exports correctly point to source, peer deps are properly declared.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User visits /api/rest] --> B[RequireENSAdminFeature\nfeature=restApi]
    B --> C{configQuery status}
    C -- pending --> D[Show loading spinner]
    C -- error --> E[Show error card]
    C -- success --> F[restApi = supported]
    F --> G[RestApiPage]
    G --> H[useOpenApiUrl\nnew URL /openapi.json, connection]
    G --> I[useValidatedSelectedConnection]
    H --> J[ScalarApiReference\nurl, serverUrl]
    I --> J
    J --> K[ApiReferenceReact\nVue-under-the-hood\nfetches OpenAPI spec]
    K --> L[Interactive REST playground]
Loading

Reviews (1): Last reviewed commit: "apply code suggestions" | Re-trigger Greptile

Comment thread packages/scalar-react/src/api-reference.tsx Outdated
Comment thread packages/scalar-react/src/api-reference.tsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/scalar-react/src/api-reference.tsx Outdated
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 22, 2026 14:22 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 22, 2026 14:22 Inactive
@notrab
Copy link
Copy Markdown
Member Author

notrab commented Apr 22, 2026

@copilot resolve the merge conflicts in this pull request

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.

Add interactive REST API playground to ENSAdmin

2 participants