Skip to content

feat: general dashboard [skip-line-limit]#1546

Merged
ctrlc03 merged 5 commits into
mainfrom
feat/general-dashboard
May 20, 2026
Merged

feat: general dashboard [skip-line-limit]#1546
ctrlc03 merged 5 commits into
mainfrom
feat/general-dashboard

Conversation

@ctrlc03

@ctrlc03 ctrlc03 commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features
    • Enclave-dashboard: a public observation interface for CRISP polls with real-time Sepolia blockchain data
    • Live poll viewing with countdown timers, stage progression tracking, and ballot metrics
    • E3 Inspector component for detailed transaction and event inspection
    • Poll history archive with filtering and result summaries
    • Live mode with staged timeline progression and manual stage navigation
    • Network activity metrics display

Review Change Stack

@ctrlc03 ctrlc03 requested a review from 0xjei May 20, 2026 14:41
@vercel

vercel Bot commented May 20, 2026

Copy link
Copy Markdown

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

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
crisp Skipped Skipped May 20, 2026 4:13pm
enclave-docs Skipped Skipped May 20, 2026 4:13pm

Request Review

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d0c1d6f8-fc82-4d7a-b6fc-5740a6603df1

📥 Commits

Reviewing files that changed from the base of the PR and between f4e73cf and 1e2b6d0.

⛔ Files ignored due to path filters (1)
  • packages/enclave-dashboard/src/assets/interfold.svg is excluded by !**/*.svg
📒 Files selected for processing (14)
  • packages/enclave-dashboard/.env.example
  • packages/enclave-dashboard/src/App.tsx
  • packages/enclave-dashboard/src/History.tsx
  • packages/enclave-dashboard/src/Inspector.tsx
  • packages/enclave-dashboard/src/PollCard.tsx
  • packages/enclave-dashboard/src/Pulse.tsx
  • packages/enclave-dashboard/src/Timeline.tsx
  • packages/enclave-dashboard/src/data.ts
  • packages/enclave-dashboard/src/lib/adapt.ts
  • packages/enclave-dashboard/src/lib/chain.ts
  • packages/enclave-dashboard/src/lib/e3.ts
  • packages/enclave-dashboard/src/lib/links.ts
  • packages/enclave-dashboard/src/lib/useE3s.ts
  • packages/enclave-dashboard/src/styles.css

📝 Walkthrough

Walkthrough

This PR introduces the enclave-dashboard, a complete new Vite/React package for observing CRISP polls and inspecting E3 compute requests on Sepolia. The dashboard fetches on-chain data, adapts it for UI consumption, and renders two main views: a CRISP poll observer with countdown and timeline, and a technical E3 inspector showing detailed request/keygen/compute/decryption lifecycle with event logs.

Changes

Enclave Dashboard Implementation

Layer / File(s) Summary
Project configuration and build setup
package.json, tsconfig.json, vite.config.ts, vercel.json, .gitignore, .env.example, pnpm-workspace.yaml, index.html
Adds workspace package with ES module React/Vite setup, TypeScript config, Vercel deployment settings with monorepo awareness, environment defaults for Sepolia contracts/RPC, and standard HTML/browser entry point.
Data types and stage constants
src/data.ts
Exports Stage interface, STAGES array (6 E3 lifecycle stages), STAGE_STATUS UI labels, and shared UI types (Poll, HistoryEntry, PulseData) used across components.
Sepolia chain configuration and ABI setup
src/lib/chain.ts
Centralizes Sepolia RPC client creation, contract address mappings (Enclave/CiphernodeRegistry/CRISPProgram), env-overridable deployment block and timeout windows, and exports ABIs sourced from @enclave-e3/contracts typechain.
On-chain E3 list and detail fetching
src/lib/e3.ts
Core on-chain data layer: fetchE3List queries E3Requested logs with CRISP filtering and stage lookup; fetchE3Details assembles full E3 lifecycle (committee/keygen/compute/decryption/publication) by reading events, view functions, and ballot logs; includes tally decoding and stage→UI index mapping.
Data adaptation and UI formatting
src/lib/adapt.ts, src/lib/links.ts, src/lib/pollMeta.ts
Transforms on-chain E3 data into UI-ready shapes: adaptTodaysPoll, adaptHistoryEntries, adaptInspectorDetail; includes off-chain metadata lookup, address/hash shortening, program identification, event timeline building, and explorer link generation.
React polling and state management hooks
src/lib/useE3s.ts
Custom hooks polling on-chain data: useCrispPolls and useAllE3s wrap list fetching with configurable intervals; useRecentBallots tracks recent ballot activity; useE3Details polls a selected E3; all prevent overlapping requests and manage mount/unmount cleanup.
App component and main page structure
src/main.tsx, src/App.tsx
React entrypoint and app shell orchestrating on-chain hooks, managing view switching (CRISP vs inspector), poll state/stage/liveMode progression, derived stage reconciliation, and main render logic with loading/error/empty branches; renders Pulse and SiteFooter always.
Poll card and timeline components
src/PollCard.tsx, src/Timeline.tsx
CRISP poll visualization: PollCard renders stage badge, countdown timer, encrypted ballot grid, collapsible privacy explainer, and pre/post-publication messaging; Timeline shows interactive stage progression with hover-driven explainer and optional stage-jump callbacks.
History, Inspector, Loader, and Pulse components
src/Loader.tsx, src/Pulse.tsx, src/History.tsx, src/Inspector.tsx
Supporting views: Loader displays on-chain loading UI with spinner and skeleton bars; Pulse renders network activity metrics; History shows filtered/expandable poll archive; Inspector displays detailed E3 state with stage strip, section cards, and event log table.
Complete styling system and documentation
src/styles.css, README.md
Comprehensive CSS with design tokens, component styling (poll/timeline/history/inspector), animations/transitions, responsive breakpoints (max-width: 880px), reduced-motion support, and README documenting setup, on-chain data sources, environment config, and Vercel deployment.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • gnosisguild/enclave#1274: Both PRs involve CRISP tally/ballot decoding—this PR adds a JavaScript decodeCrispTally decoder in the dashboard for PlaintextOutputPublished data, while the related PR updates server-side Rust tally decoding and the indexer to dynamically decode tallies.

Suggested reviewers

  • 0xjei
  • cedoor

🐰 In Sepolia's light, a dashboard takes flight,
CRISP polls counted, E3s in sight,
Stage by stage, the journey unfolds,
On-chain secrets that the inspector holds.
Timeline dancing, votes encrypted tight,
A rabbit-built view of compute's light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: general dashboard' accurately describes the main change: introducing a comprehensive new dashboard package for the Interfold/CRISP public observation system, which is a significant feature addition.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/general-dashboard

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.

@ctrlc03 ctrlc03 changed the title feat: general dashboard feat: general dashboard [skip-line-limit] May 20, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 14

🧹 Nitpick comments (2)
packages/enclave-dashboard/.gitignore (1)

1-1: 💤 Low value

Consider adding common build artifacts and local env files.

The .gitignore only covers .env, which is good for preventing secret leaks. However, consider also ignoring common build artifacts and local environment variations:

  • dist/ (Vite build output)
  • .env.local (local environment overrides)
  • .vercel/ (Vercel CLI artifacts)

These might already be covered by a root-level .gitignore, in which case this is fine as-is.

📝 Suggested additions
-.env 
+.env
+.env.local
+dist/
+.vercel/
🤖 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 `@packages/enclave-dashboard/.gitignore` at line 1, Add common local and build
artifacts to the package's .gitignore: include entries for dist/ (Vite build
output), .env.local (local overrides), and .vercel/ (Vercel CLI artifacts)
alongside the existing .env entry so local builds and platform artifacts aren’t
accidentally committed; if these are already covered by a repository-level
.gitignore, verify consistency and add any missing entries to
packages/enclave-dashboard/.gitignore.
packages/enclave-dashboard/index.html (1)

8-13: ⚡ Quick win

Consider self-hosting fonts for privacy and reliability.

Loading fonts from Google Fonts CDN has potential privacy implications (Google can track users) and reliability concerns (external dependency). For a production dashboard, consider self-hosting the Geist font files to improve privacy, ensure availability, and reduce external dependencies.

Self-hosting fonts also provides:

  • Better privacy compliance (GDPR/CCPA)
  • Guaranteed availability (no external dependency)
  • Potential performance improvements (same-origin, better caching control)
🤖 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 `@packages/enclave-dashboard/index.html` around lines 8 - 13, Replace the
external Google Fonts links that load "Geist" and "Geist Mono" with locally
hosted font files: download the required font weights/variants for Geist and
Geist Mono, add them to your static assets, create a local CSS with `@font-face`
declarations for each family/weight, and update the HTML to reference that local
stylesheet instead of the Google CDN hrefs; ensure the new CSS paths match the
served static directory and remove crossorigin attributes for the external
links.
🤖 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 `@packages/enclave-dashboard/src/App.tsx`:
- Around line 293-324: The UI currently treats any non-ready state as a loader
so fetch errors (status === 'error') spin forever; update the rendering logic to
check each data fetch status explicitly and show an error UI instead of the
Loader. For the inspector branch, check the allE3s (or whatever hook/result
holds E3 list) status and if status === 'error' render a StatusNote (or similar)
with the error message before falling back to Loader or the Inspector; keep
inspectorDetail.status === 'loading' for Inspector.loading and
inspectorDetail.error for Inspector.error. Do the same for crispPolls and
todaysDetail (used with crispReady/livePoll): if crispPolls.status === 'error'
or todaysDetail.status === 'error' render a StatusNote with the error text
rather than the Loader. Ensure you reference and use the existing symbols
inspectorReady, inspectorList/allE3s, inspectorDetail, crispReady, crispPolls,
todaysDetail/livePoll and set loading props only when status === 'loading'.

In `@packages/enclave-dashboard/src/History.tsx`:
- Around line 137-140: The "Load earlier polls" button in History.tsx currently
has no behavior and gives a false affordance; either wire it to an actual
handler (e.g., add an onClick that calls a loadEarlierPolls or onLoadEarlier
prop/function to fetch/append earlier items and update state) or mark it
non-interactive (add disabled, aria-disabled="true", and update
styling/className to a disabled variant like 'link-btn--disabled' and ensure
pointer-events are none). Update the button element accordingly and ensure any
new handler is defined/exported (e.g., loadEarlierPolls) and connected to the
component props/state so the control is functional or clearly disabled.

In `@packages/enclave-dashboard/src/Inspector.tsx`:
- Around line 397-417: The SectionCard status props are hardcoded to 'pending'
causing incorrect UI; derive each card's status from e3.currentStage instead.
Update the three SectionCard instances (the ones titled 'FHE computation',
'Threshold decryption', and 'Result on-chain') to compute status based on
e3.currentStage and the corresponding stage identifiers (e.g., compare
e3.currentStage to 'compute', 'decryption', 'publication' or an ordered stage
index) and pass the correct { kind, label } object to the status prop so the
card shows 'pending', 'active', or 'complete' as appropriate.

In `@packages/enclave-dashboard/src/lib/e3.ts`:
- Around line 226-233: The code in fetchE3Details uses getLogsChunked with
DEPLOY_BLOCK as the lower bound causing full-history scans on each refresh;
change the lower bound to use the specific event's request block
(e3.requestBlock) or another tight per-E3 lower bound when calling
getLogsChunked for requestLogs (and the other similar calls between lines
~239-308) so each query starts at the E3's requestBlock instead of DEPLOY_BLOCK;
update the getLogsChunked call sites (referenced by requestLogs and other
log-fetching variables in fetchE3Details) to pass e3.requestBlock (or
Math.max(e3.requestBlock, DEPLOY_BLOCK) if you need a safe floor) as the start
block.
- Around line 385-392: The decoder currently converts each uint64 word to a JS
Number via Number(BigInt('0x'+word)) which can silently lose precision; in the
loop that reads words (using variables lengthWord, len, hex and pushing into
out), add a check after parsing BigInt('0x'+word) to ensure the bigint value <=
BigInt(Number.MAX_SAFE_INTEGER) (or alternatively assert it fits within the
contract's MAX_VOTE_BITS), and if it exceeds the safe threshold return null (or
throw) instead of converting, so you avoid silent precision loss before pushing
into out and before any Math.max/percentage calculations.

In `@packages/enclave-dashboard/src/lib/useE3s.ts`:
- Around line 30-44: The polling `tick` can overlap when a fetch takes longer
than POLL_MS; modify the `tick` in useE3s (and mirror the same change in
useE3Details) to prevent concurrent runs by adding an `inFlight` flag (e.g., let
inFlight = false outside tick) or replace setInterval with an async loop using
awaited setTimeout: at start of `tick` return immediately if `inFlight` is true,
set `inFlight = true` before calling `fetchE3List`, and in a finally block set
`inFlight = false`; ensure cleanup clears interval or signals the loop to stop
on unmount/cancelled so no overlapping fetches occur.

In `@packages/enclave-dashboard/src/PollCard.tsx`:
- Around line 146-147: Replace the non-null assertion on the winner lookup
(const winner = items.find((i) => i.id === poll.result.winner)!) with a safe
lookup that can be undefined (e.g., const winner = items.find((i) => i.id ===
poll.result.winner) || null) and then guard all places that access winner
properties (render, counts, labels) to handle null/undefined (show a
placeholder, skip rendering, or use a fallback label). Do the same for any other
similar lookups around PollCard that use `!` (the other find usages referenced
in the comment) so the component never dereferences an undefined value during
render.
- Around line 207-208: The code dereferences STAGES[currentStageIdx] without
guarding against undefined (used to compute stageId and later passed into
StageBadge), which can crash rendering; update the logic around STAGES and
currentStageIdx to clamp currentStageIdx into the valid range or provide a
fallback stage object, e.g. derive a safeStage = STAGES[currentStageIdx] ?? {
id: 'unknown', label: 'Unknown' } and then use safeStage.id and safeStage.label
(and compute status from a default key when needed) so StageBadge and the status
lookup never receive undefined.
- Around line 281-284: The click handler on the PollCard currently always calls
e.preventDefault() so the link is inert when onNavigate is undefined; update the
onClick in the PollCard component to only call e.preventDefault() and call
onNavigate('inspector') when onNavigate is provided (i.e., if (onNavigate) {
e.preventDefault(); onNavigate('inspector') }) so native navigation proceeds
when onNavigate is not passed; locate the onClick handler referencing onNavigate
in PollCard.tsx and change its behavior accordingly.

In `@packages/enclave-dashboard/src/styles.css`:
- Line 1918: Replace the deprecated CSS rule "word-break: break-word" with a
modern equivalent: remove "word-break: break-word" and add "overflow-wrap:
anywhere;" (or "overflow-wrap: break-word;" if preferred) in the same CSS rule
where "word-break: break-word" appears so the selector's behavior is preserved
and stylelint no longer fails.
- Around line 1050-1052: The new global rule ".poll-card__head .stage-badge {
margin-left: 0; }" is undoing the intended desktop alignment set earlier
(".poll-card__head .stage-badge { margin-left: auto; }"); to fix, remove or
scope this override so it only applies where needed (e.g., wrap the
margin-left:0 inside a mobile media query like `@media` (max-width: ...){
.poll-card__head .stage-badge { margin-left: 0; } } or apply a more specific
selector for the mobile/state variant), ensuring the original .poll-card__head
.stage-badge margin-left: auto remains effective on desktop.

In `@packages/enclave-dashboard/src/Timeline.tsx`:
- Around line 52-55: The code assumes stages[explainerIdx] always exists and
will throw if stages is empty or explainerIdx is out of range; update the logic
around hoverIdx, explainerIdx and explainer to guard against empty or OOB
indexes (e.g., compute explainerIdx = clamp(hoverIdx ?? currentStageIdx, 0,
stages.length - 1) or detect stages.length === 0 and provide a safe fallback
object), and replace direct accesses to explainer.label/explainer.blurb (and the
same occurrences around the block referenced at lines ~97-103) with
checks/optional chaining or the fallback so the UI renders an empty state
instead of crashing. Ensure to keep references to hoverIdx, setHoverIdx,
currentStageIdx and explainer when locating and changing the code.

In `@packages/enclave-dashboard/src/tweaks-panel.tsx`:
- Around line 322-334: The radio group is currently pointer-only; add keyboard
operability by handling key events and proper focus management: attach an
onKeyDown to the container (where trackRef and onPointerDown are used) to listen
for ArrowLeft/ArrowRight (prev/next), Home (first) and End (last) and update the
selected value using the same state setter you use for pointer selection (the
handler that updates value), then move focus to the newly selected button; also
set tabIndex on each rendered radio button (tabIndex=0 for the selected option,
-1 for others) and ensure aria-checked is updated based on value so screen
readers and keyboard users can navigate (use opts, value, idx, n to compute
next/prev indices).

In `@packages/enclave-dashboard/tsconfig.json`:
- Around line 8-9: Update the TypeScript configuration to enable strict mode by
setting the "strict" and "noImplicitAny" compiler options to true in
tsconfig.json; then run the type checker (tsc) and fix resulting type errors
across the codebase (e.g., add explicit types, handle null/undefined, and refine
interfaces) so the project compiles cleanly under strict mode. Ensure you modify
the "strict" and "noImplicitAny" options in tsconfig.json and address failures
reported by tsc by updating functions, components, and types where implicit any
or unsafe null handling occurs.

---

Nitpick comments:
In `@packages/enclave-dashboard/.gitignore`:
- Line 1: Add common local and build artifacts to the package's .gitignore:
include entries for dist/ (Vite build output), .env.local (local overrides), and
.vercel/ (Vercel CLI artifacts) alongside the existing .env entry so local
builds and platform artifacts aren’t accidentally committed; if these are
already covered by a repository-level .gitignore, verify consistency and add any
missing entries to packages/enclave-dashboard/.gitignore.

In `@packages/enclave-dashboard/index.html`:
- Around line 8-13: Replace the external Google Fonts links that load "Geist"
and "Geist Mono" with locally hosted font files: download the required font
weights/variants for Geist and Geist Mono, add them to your static assets,
create a local CSS with `@font-face` declarations for each family/weight, and
update the HTML to reference that local stylesheet instead of the Google CDN
hrefs; ensure the new CSS paths match the served static directory and remove
crossorigin attributes for the external links.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9f108579-afd4-4709-b8b6-f8dc0ecadb69

📥 Commits

Reviewing files that changed from the base of the PR and between f766572 and 4f6ba23.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • packages/enclave-dashboard/.env.example
  • packages/enclave-dashboard/.gitignore
  • packages/enclave-dashboard/README.md
  • packages/enclave-dashboard/index.html
  • packages/enclave-dashboard/package.json
  • packages/enclave-dashboard/src/App.tsx
  • packages/enclave-dashboard/src/History.tsx
  • packages/enclave-dashboard/src/Inspector.tsx
  • packages/enclave-dashboard/src/Loader.tsx
  • packages/enclave-dashboard/src/PollCard.tsx
  • packages/enclave-dashboard/src/Pulse.tsx
  • packages/enclave-dashboard/src/Timeline.tsx
  • packages/enclave-dashboard/src/data.ts
  • packages/enclave-dashboard/src/lib/adapt.ts
  • packages/enclave-dashboard/src/lib/chain.ts
  • packages/enclave-dashboard/src/lib/e3.ts
  • packages/enclave-dashboard/src/lib/links.ts
  • packages/enclave-dashboard/src/lib/pollMeta.ts
  • packages/enclave-dashboard/src/lib/useE3s.ts
  • packages/enclave-dashboard/src/main.tsx
  • packages/enclave-dashboard/src/styles.css
  • packages/enclave-dashboard/src/tweaks-panel.tsx
  • packages/enclave-dashboard/src/useTweaks.ts
  • packages/enclave-dashboard/tsconfig.json
  • packages/enclave-dashboard/vercel.json
  • packages/enclave-dashboard/vite.config.ts
  • pnpm-workspace.yaml

Comment thread packages/enclave-dashboard/src/App.tsx Outdated
Comment thread packages/enclave-dashboard/src/History.tsx Outdated
Comment thread packages/enclave-dashboard/src/Inspector.tsx Outdated
Comment thread packages/enclave-dashboard/src/lib/e3.ts
Comment thread packages/enclave-dashboard/src/lib/e3.ts
Comment thread packages/enclave-dashboard/src/styles.css
Comment thread packages/enclave-dashboard/src/styles.css Outdated
Comment thread packages/enclave-dashboard/src/Timeline.tsx
Comment thread packages/enclave-dashboard/src/tweaks-panel.tsx Outdated
Comment thread packages/enclave-dashboard/tsconfig.json
@vercel vercel Bot temporarily deployed to Preview – crisp May 20, 2026 14:50 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs May 20, 2026 14:50 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp May 20, 2026 15:02 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs May 20, 2026 15:02 Inactive

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/enclave-dashboard/src/lib/useE3s.ts (1)

78-104: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset detail state when switching between E3 selections.

This effect only resets to loading when e3Id === null. When the user changes from one non-null E3 to another, the hook retains the previous { status: 'ready', data } until the new fetch completes, causing the UI to briefly display the newly selected E3's ID with the old selection's stale data and loading={false}. Add an unconditional state reset before the polling setup for each new non-null e3Id.

🔄 Minimal fix
   if (e3Id === null) {
     setState({ status: 'loading', data: null, error: null })
     return
   }
+  setState({ status: 'loading', data: null, error: null })
   let cancelled = false
   let inFlight = false
🤖 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 `@packages/enclave-dashboard/src/lib/useE3s.ts` around lines 78 - 104, When
e3Id changes to a new non-null value the hook should immediately reset the
detail state to loading so the UI doesn't show stale ready data; in useE3s
(inside the effect that references e3Id, mounted, tick, and setInterval) add an
unconditional setState({ status: 'loading', data: null, error: null }) before
creating the tick/polling (i.e., after the early-return for e3Id === null) so
every new non-null e3Id starts from loading while fetchE3Details runs; keep the
existing inFlight/cancelled/tick logic unchanged.
♻️ Duplicate comments (2)
packages/enclave-dashboard/src/tweaks-panel.tsx (1)

320-329: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep focus aligned with the checked radio.

Arrow keys update value, but focus stays on the previously focused button. After rerender, the active element can be an unchecked radio, which makes keyboard and screen-reader interaction inconsistent. Move focus to the newly selected button when the selection changes; Home/End can reuse the same helper.

♿ Minimal fix
+  const focusOption = (nextIdx: number) => {
+    onChange(opts[nextIdx].value)
+    requestAnimationFrame(() => {
+      trackRef.current?.querySelectorAll<HTMLButtonElement>('button[role="radio"]')[nextIdx]?.focus()
+    })
+  }
+
   const onKeyDown = (e: React.KeyboardEvent) => {
     const cur = opts.findIndex((o: any) => o.value === value)
     if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
       e.preventDefault()
-      onChange(opts[Math.min(opts.length - 1, cur + 1)].value)
+      focusOption(Math.min(opts.length - 1, cur + 1))
     } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
       e.preventDefault()
-      onChange(opts[Math.max(0, cur - 1)].value)
+      focusOption(Math.max(0, cur - 1))
+    } else if (e.key === 'Home') {
+      e.preventDefault()
+      focusOption(0)
+    } else if (e.key === 'End') {
+      e.preventDefault()
+      focusOption(opts.length - 1)
     }
   }

Also applies to: 333-355

🤖 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 `@packages/enclave-dashboard/src/tweaks-panel.tsx` around lines 320 - 329, The
arrow-key handler (onKeyDown) updates the selected value but doesn't move DOM
focus to the newly checked radio, causing mismatch for keyboard/screen-reader
users; after calling onChange with the new value (computed via opts and cur),
programmatically focus the corresponding radio element (e.g., query/select the
radio by value or use a ref array) so the focused element matches the checked
one, and extract this focus logic into a small helper that is also reused by the
Home/End handlers and the similar keyboard handler later in the file to keep
behavior consistent across the component.
packages/enclave-dashboard/src/Timeline.tsx (1)

52-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle the empty stages case before clamping.

This still crashes when stages.length === 0: clamp() returns -1, explainer becomes undefined, and the explainer render dereferences it below. Add an explicit empty state before computing explainerIdx.

🛡️ Minimal fix
+  if (stages.length === 0) {
+    return (
+      <section className={`timeline timeline--${density}`}>
+        <div className='timeline__explainer'>No lifecycle stages available.</div>
+      </section>
+    )
+  }
+
   const [hoverIdx, setHoverIdx] = useState<number | null>(null)
   const clamp = (i: number) => Math.min(Math.max(i, 0), stages.length - 1)
   const explainerIdx = clamp(hoverIdx ?? currentStageIdx)

Also applies to: 98-103

🤖 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 `@packages/enclave-dashboard/src/Timeline.tsx` around lines 52 - 55, The code
crashes when stages is empty because clamp computes -1 and explainer becomes
undefined; before calling clamp and indexing stages, add an explicit empty-state
guard (e.g., if stages.length === 0) and return or render a safe fallback, or
set explainerIdx only when stages.length > 0; update the logic around hoverIdx,
clamp, explainerIdx and explainer so they are only computed/used when
stages.length > 0 (also apply same guard to the similar block around lines
98-103).
🤖 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 `@packages/enclave-dashboard/src/lib/useE3s.ts`:
- Around line 78-104: When e3Id changes to a new non-null value the hook should
immediately reset the detail state to loading so the UI doesn't show stale ready
data; in useE3s (inside the effect that references e3Id, mounted, tick, and
setInterval) add an unconditional setState({ status: 'loading', data: null,
error: null }) before creating the tick/polling (i.e., after the early-return
for e3Id === null) so every new non-null e3Id starts from loading while
fetchE3Details runs; keep the existing inFlight/cancelled/tick logic unchanged.

---

Duplicate comments:
In `@packages/enclave-dashboard/src/Timeline.tsx`:
- Around line 52-55: The code crashes when stages is empty because clamp
computes -1 and explainer becomes undefined; before calling clamp and indexing
stages, add an explicit empty-state guard (e.g., if stages.length === 0) and
return or render a safe fallback, or set explainerIdx only when stages.length >
0; update the logic around hoverIdx, clamp, explainerIdx and explainer so they
are only computed/used when stages.length > 0 (also apply same guard to the
similar block around lines 98-103).

In `@packages/enclave-dashboard/src/tweaks-panel.tsx`:
- Around line 320-329: The arrow-key handler (onKeyDown) updates the selected
value but doesn't move DOM focus to the newly checked radio, causing mismatch
for keyboard/screen-reader users; after calling onChange with the new value
(computed via opts and cur), programmatically focus the corresponding radio
element (e.g., query/select the radio by value or use a ref array) so the
focused element matches the checked one, and extract this focus logic into a
small helper that is also reused by the Home/End handlers and the similar
keyboard handler later in the file to keep behavior consistent across the
component.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 06f0d2c2-e4c0-456e-add6-92f86169b6be

📥 Commits

Reviewing files that changed from the base of the PR and between 4f6ba23 and f4e73cf.

📒 Files selected for processing (9)
  • packages/enclave-dashboard/src/App.tsx
  • packages/enclave-dashboard/src/History.tsx
  • packages/enclave-dashboard/src/Inspector.tsx
  • packages/enclave-dashboard/src/PollCard.tsx
  • packages/enclave-dashboard/src/Timeline.tsx
  • packages/enclave-dashboard/src/lib/e3.ts
  • packages/enclave-dashboard/src/lib/useE3s.ts
  • packages/enclave-dashboard/src/styles.css
  • packages/enclave-dashboard/src/tweaks-panel.tsx
💤 Files with no reviewable changes (1)
  • packages/enclave-dashboard/src/History.tsx

@vercel vercel Bot temporarily deployed to Preview – enclave-docs May 20, 2026 16:13 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp May 20, 2026 16:13 Inactive

@0xjei 0xjei left a comment

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.

utACK - reviewed in pair-programming

@ctrlc03 ctrlc03 merged commit f822158 into main May 20, 2026
28 checks passed
@ctrlc03 ctrlc03 deleted the feat/general-dashboard branch May 20, 2026 16:14
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.

2 participants