feat(examples): namegraph explorer in enskit-react-example#2295
Conversation
Adds a /namegraph/:registryId route that traverses the ENS namegraph forward from a Registry using the trees.software (@pierre/trees) file-tree as the UI. - Each row is a Domain; domains with a subregistry render as expandable directories and lazily load their subregistry's domains on expand (paginated via a 'load more' sentinel). - Per domain: the constructed addressable name vs canonical name (alias highlighting), ENSv1/ENSv2 version, assigned/effective resolver, and whether the subregistry's reverse canonical pointer agrees with its location. - Selecting a domain forward-resolves it (enssdk imperative Omnigraph client), shown as separate Profile and Records panels plus a Resolution summary with acceleration metadata and request timing derived from the protocol trace. Shared the Omnigraph client via a new src/ensnode.ts.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
3 Skipped Deployments
|
|
|
Warning Review limit reached
More reviews will be available in 9 minutes and 41 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds a Namegraph Explorer to the example app: centralized ENSNode client setup, new ChangesNamegraph Explorer Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds a new interactive Namegraph Explorer page to the examples/enskit-react-example app. It uses @pierre/trees to render registries/domains as a lazily-expanded file tree and uses the ENSIndexer/Omnigraph client to traverse registries and forward-resolve selected domains.
Changes:
- Add a new
/namegraph+/namegraph/:registryIdroute that renders a tree-based explorer for forward traversal ofregistry.domains → subregistry → …. - Introduce
src/ensnode.tsto share a single ENSNode + Omnigraph client betweenOmnigraphProviderand imperative query calls. - Add
@pierre/treesdependency (and lockfile updates) to support the file-tree UI.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks @pierre/trees (and transitive deps) for the example app. |
| examples/enskit-react-example/package.json | Adds @pierre/trees dependency. |
| examples/enskit-react-example/src/App.tsx | Wires up /namegraph routes and navigation link; switches to shared client export. |
| examples/enskit-react-example/src/ensnode.ts | New shared ENSNode + Omnigraph client module. |
| examples/enskit-react-example/src/NamegraphView.tsx | New explorer implementation: tree model, lazy loading, pagination sentinel, and resolution panels. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Greptile SummaryAdds an interactive Namegraph Explorer route (
Confidence Score: 5/5Safe to merge — this is a self-contained new feature added only to the example app, with no changes to library or production code paths. The change is entirely additive, isolated to the example app, and the bugs flagged in earlier review rounds (traceDurationMicros zero sentinel, KeyValueGrid duplicate-key warning, loadChildren permanent error state) are addressed in the current diff. No new defects were found during this pass. No files require special attention. NamegraphView.tsx is the densest file but its logic is well-commented and the previously identified issues are resolved. Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User
participant FT as FileTree (pierre/trees)
participant NV as NamegraphView
participant API as ENSNode Omnigraph
U->>NV: Navigate to /namegraph/:registryId
NV->>API: fetchRegistryPage(registryId, null)
API-->>NV: "page { domains[], hasNextPage, endCursor }"
NV->>FT: model.batch([add domain paths])
note over FT: Subregistry domains added as directories with PLACEHOLDER child
U->>FT: Expand a directory domain
FT->>NV: model.subscribe fires
NV->>NV: loadChildren(dirPath)
NV->>API: fetchRegistryPage(subregistryId, null)
API-->>NV: "page { domains[], hasNextPage }"
NV->>FT: model.batch([remove placeholder, add domains])
U->>FT: Scroll LOAD_MORE sentinel into view
FT->>NV: renderRowDecoration(LOAD_MORE row)
NV->>NV: queueMicrotask → triggerLoadMore(meta)
NV->>API: fetchRegistryPage(registryId, cursor)
API-->>NV: next page
NV->>FT: model.batch([remove sentinel, add new domains, add new sentinel])
U->>FT: Select a domain row
FT->>NV: useFileTreeSelection update
NV->>API: "client.omnigraph.query(ResolveQuery, {id})"
API-->>NV: "resolve { profile, records, trace, acceleration }"
NV->>U: Render DetailPanel + ResolutionPanels
Reviews (9): Last reviewed commit: "fix(examples): dedupe load-more microtas..." | Re-trigger Greptile |
Replace click-to-paginate with infinite scroll: the load-more sentinel triggers the next page from renderRowDecoration (trees' per-visible-row callback) when it scrolls into view, guarded against re-entry while a page is in flight. Sort siblings by name only instead of the library's folder-first default so name-paginated pages append below already-rendered rows; folder-first sorted later pages' subregistry-bearing domains above the viewport, making the tree jump on load.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
examples/enskit-react-example/src/NamegraphView.tsx (1)
280-303:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard nullable
resolverbefore accessingassigned/effective.Line 292 and Line 298 dereference
resolverwithout a null guard. Ifresolveris null in any edge, mapping throws and the registry page load fails.Suggested fix
- const resolver = node.resolver; + const resolver = node.resolver; @@ - assignedResolver: resolver.assigned + assignedResolver: resolver?.assigned ? { chainId: resolver.assigned.contract.chainId, address: resolver.assigned.contract.address, } : null, - effectiveResolver: resolver.effective + effectiveResolver: resolver?.effective ? { chainId: resolver.effective.contract.chainId, address: resolver.effective.contract.address, } : null,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/enskit-react-example/src/NamegraphView.tsx` around lines 280 - 303, The mapping accesses resolver.assigned and resolver.effective without null checks which can throw when resolver is null; update the mapping that produces assignedResolver and effectiveResolver to guard resolver (e.g., use resolver == null or optional-chaining) before accessing assigned/effective and their contract fields so assignedResolver and effectiveResolver become null when resolver is null; specifically modify the block building assignedResolver and effectiveResolver to check resolver (and resolver.assigned/effective) first and only read contract.chainId/contract.address when present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/enskit-react-example/src/ensnode.ts`:
- Line 6: The module sets ENSNODE_URL directly from import.meta.env which delays
config validation; import Zod (e.g., z from "zod") and perform an eager parse at
module load: define a zod schema like z.string().url() (or
z.string().url().default("https://api.v2-sepolia.ensnode.io") if you want the
fallback validated) and call schema.parse(import.meta.env.VITE_ENSNODE_URL ??
undefined) to produce ENSNODE_URL, allowing the ZodError to surface immediately;
update the ENSNODE_URL export to use the validated value and let ZodError
propagate (or catch and rethrow with context) so malformed env values fail fast.
---
Duplicate comments:
In `@examples/enskit-react-example/src/NamegraphView.tsx`:
- Around line 280-303: The mapping accesses resolver.assigned and
resolver.effective without null checks which can throw when resolver is null;
update the mapping that produces assignedResolver and effectiveResolver to guard
resolver (e.g., use resolver == null or optional-chaining) before accessing
assigned/effective and their contract fields so assignedResolver and
effectiveResolver become null when resolver is null; specifically modify the
block building assignedResolver and effectiveResolver to check resolver (and
resolver.assigned/effective) first and only read
contract.chainId/contract.address when present.
🪄 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: 4f1e6492-2464-4e2d-b91d-4ee63b43376f
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
examples/enskit-react-example/package.jsonexamples/enskit-react-example/src/App.tsxexamples/enskit-react-example/src/NamegraphView.tsxexamples/enskit-react-example/src/ensnode.ts
…ollisions - loadMore: show error sentinel on pagination failure (matches loadChildren) - traceDurationMicros: return null instead of 0 when no span timing exists - KeyValueGrid: use index keys to avoid duplicate-label collisions - ExternalLink: restrict hrefs to http(s), add rel=noopener noreferrer
|
@greptile review |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
examples/enskit-react-example/src/NamegraphView.tsx (1)
1092-1092: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider handling missing
root.idas an error.If the GraphQL response succeeds but
root.idis missing (e.g., API/schema change),rootIdstaysnulland the UI shows "Loading…" indefinitely rather than an error.🛡️ Suggested defensive check
- setRootId(result.data?.root.id ?? null); + const id = result.data?.root?.id; + if (!id) throw new Error("Root registry id not found"); + setRootId(id);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/enskit-react-example/src/NamegraphView.tsx` at line 1092, The current assignment setRootId(result.data?.root.id ?? null) lets a successful GraphQL response with a missing root.id leave rootId null and the UI stuck on "Loading…"; update the response handling in NamegraphView to explicitly detect missing result.data?.root?.id after the query and treat it as an error: call the component's error handler / set an error state (e.g., setError or setIsLoading(false) + setError("missing root.id")) instead of silently setting null, and only call setRootId(...) when result.data.root.id is present. Use the existing symbols setRootId, result.data?.root.id and rootId to locate and change the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@examples/enskit-react-example/src/NamegraphView.tsx`:
- Line 1092: The current assignment setRootId(result.data?.root.id ?? null) lets
a successful GraphQL response with a missing root.id leave rootId null and the
UI stuck on "Loading…"; update the response handling in NamegraphView to
explicitly detect missing result.data?.root?.id after the query and treat it as
an error: call the component's error handler / set an error state (e.g.,
setError or setIsLoading(false) + setError("missing root.id")) instead of
silently setting null, and only call setRootId(...) when result.data.root.id is
present. Use the existing symbols setRootId, result.data?.root.id and rootId to
locate and change the logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 45c90fa0-928a-433a-8cda-0a93ca201f4e
📒 Files selected for processing (1)
examples/enskit-react-example/src/NamegraphView.tsx
…prettyUrl - fetchRegistryPage: throw on null registry instead of rendering an empty tree - loadChildren: only mark a dir loaded after the domain/subregistry guard - prettyUrl: case-insensitive scheme strip, matching the ExternalLink guard
|
@greptile review |
- loadChildren: mark a dir loaded only on success, drop a stale error sentinel on re-entry, and on failure collapse + re-queue the dir so a manual re-expand retries (collapsing first avoids a retry storm from the model subscription) - add a banner explaining the page + ensnode.ts are a vibe-coded demo of enskit + the Omnigraph + ensskills
|
@greptile review |
- renderRowDecoration: don't schedule a load-more microtask while that page is already in flight - initial registry load: show an empty sentinel for a zero-domain root, matching the subregistry loader
|
@greptile review |
What
Adds an interactive Namegraph Explorer to
examples/enskit-react-example, using the trees.software (@pierre/trees) file-tree component as the UI.Route:
/namegraph/:registryId(and/namegraph→ redirects to the namespace Root Registry). It traverses the ENS namegraph forward from a Registry viaregistry(by:{id}).domains → subregistry → domains ….How it maps onto a file tree
⤓ load more…sentinel row.⚠).↩✓ / ↩✗ / ↩?), derived by peeking a child'sparent(a Registry has no direct canonical-domain field).Detail + resolution panels
Selecting a domain shows stacked panels (the right column scrolls independently; the page does not):
__typename), assigned/effective resolver, subregistry id + agreement, with an "open subregistry as root" link.acceleration { requested attempted }and request timing derived from the protocoltrace(longest root span's duration), plus separate Profile and Records panels.Notes
src/ensnode.tsshares the Omnigraph client between the ReactOmnigraphProviderand the imperative lazy/paginated fetches.api.v2-sepolia.ensnode.io);VITE_ENSNODE_URLoverridable. Verified end-to-end against a localens-test-envdevnet stack.Test
pnpm -F @ensnode/enskit-react-example dev→ http://localhost:5173/#/namegraph. Typecheck, lint, and build all pass.