Skip to content

refactor: crisp ui [skip-line-limit]#1551

Merged
hmzakhalid merged 5 commits into
mainfrom
refactor/crisp
May 22, 2026
Merged

refactor: crisp ui [skip-line-limit]#1551
hmzakhalid merged 5 commits into
mainfrom
refactor/crisp

Conversation

@ctrlc03

@ctrlc03 ctrlc03 commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • The Faceoff polling UI: slot-based voting, live/active tags, post-vote result flow and “View results” CTA; default title updated to “The Faceoff”
    • Countdown timers, cipher/tally visuals and updated vote/status badges
  • Style

    • New editorial design system, theme, typography and background color
    • Added web font loading and updated footer/external links
  • Refactor

    • Simplified page, card and navbar layouts for consistent presentation
  • Tests

    • Updated UI smoke test selectors and expectations

Review Change Stack

@ctrlc03 ctrlc03 requested a review from 0xjei May 21, 2026 18:31
@vercel

vercel Bot commented May 21, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
crisp Ready Ready Preview, Comment May 22, 2026 5:15am
enclave-docs Ready Ready Preview, Comment May 22, 2026 5:15am
enclave-enclave-dashboard Ready Ready Preview, Comment May 22, 2026 5:15am

Request Review

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds a scoped editorial design system (components + CSS), imports it and new fonts at app startup, refactors multiple pages and components to use the editorial primitives, and adds post-round tally polling to the Daily Poll (Faceoff) feature.

Changes

Editorial Design System & UI Refactor

Layer / File(s) Summary
Editorial Design System component primitives
examples/CRISP/client/src/design/Editorial.tsx
New TSX library (Cipher, Gutter, ThresholdSeal/Row, TallyBar, SectionHeader, MarkerStrike/Underline, Countdown, EditorialShell) and deterministic hex utilities.
Editorial Design System stylesheet
examples/CRISP/client/src/design/editorial.css
Scoped .crisp-editorial stylesheet (tokens, palettes, typography, layout scaffolding, buttons, cards, tags, faceoff, cipher/tally visuals, footer, and animations).
App-level styling and entrypoint
examples/CRISP/client/src/globals.css, examples/CRISP/client/src/main.tsx, examples/CRISP/client/index.html
Change body background color, import ./design/editorial.css in main.tsx, and add Google Fonts preconnect/stylesheet links for Source Serif 4 and JetBrains Mono.
Navbar and Footer refactor
examples/CRISP/client/src/components/Navbar.tsx, examples/CRISP/client/src/components/Footer.tsx
Navbar adds "Live Poll" and switches to editorial topbar; Footer rewritten to crisp-editorial wrapper with external links, accessible icons, and updated attribution/logo.
Card and Poll UI updates
examples/CRISP/client/src/components/Cards/Card.tsx, CardContent.tsx, PollCard.tsx, PollCardResult.tsx, examples/CRISP/client/src/components/VotesBadge.tsx
Simplify Card/CardContent to editorial classes and inline styles; PollCard restructured as clickable button with status tag; PollCardResult reworked grid, encrypted/result displays and pluralization; VotesBadge uses tag span with pluralization.
Daily Poll feature: Faceoff voting and tally polling
examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
Refactors into EditorialShell, adds FaceoffSlot and Cipher, introduces tallyReady state and an effect polling getWebResultByRound every 8s after round end until decrypted tally is available, then enables "View results" navigation.
Landing page components: Hero and PastPoll
examples/CRISP/client/src/pages/Landing/components/Hero.tsx, PastPoll.tsx
Hero rebuilt with PRINCIPLES array and Cipher visual inside EditorialShell; PastPoll wrapped in EditorialShell and CTA restyled.
Content and result pages
examples/CRISP/client/src/pages/About/About.tsx, HistoricPoll/HistoricPoll.tsx, PollResult/PollResult.tsx, DailyPoll/components/ConfirmVote.tsx
About refactored to use SECTIONS array inside EditorialShell; HistoricPoll and PollResult now use EditorialShell wrappers and updated typography/layout; ConfirmVote text layout updated to editorial classes.

Sequence Diagram(s)

sequenceDiagram
  participant UI as DailyPoll UI
  participant Store as Vote Context
  participant API as getWebResultByRound
  participant Tally as Result State

  UI->>Store: Check if poll ended
  alt Poll Ended & Tally Not Ready
    activate Store
    loop Every 8 seconds
      Store->>API: getWebResultByRound(roundId)
      API-->>Store: { tally: [] or [...] }
      Store->>Tally: Set tallyReady if tally non-empty
    end
    deactivate Store
  else Tally Ready
    UI->>UI: Show "View results" button
  else Still Tallying
    UI->>UI: Show "tallying under encryption" msg
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • 0xjei
  • cedoor
  • hmzakhalid

Poem

🐰 I fetched some fonts and drew the frames,

hex blocks hum with seeded names,
Faceoff slots stand ready, bright,
tally polls will chase the night,
a rabbit hops — the UI's alight!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'refactor: crisp ui' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes made. Consider a more specific title that highlights the key changes, such as 'refactor: migrate CRISP UI from Tailwind to editorial design system' or 'refactor: redesign CRISP components with new editorial styling'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 refactor/crisp

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.

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

🤖 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/CRISP/client/src/components/Cards/PollCard.tsx`:
- Around line 62-70: The card currently uses a clickable div in PollCard.tsx
(the wrapper with className 'card col' and onClick={handleNavigation}), which
breaks keyboard accessibility; replace that outer div with a semantic
interactive element (preferably a <button> or your routing <Link> if navigating)
so the element receives focus and keyboard events, move the
onClick={handleNavigation} to that button/Link, preserve all inline styles and
className, and ensure an accessible label/aria-label is present (or include the
inner title text) so the PollCard component supports keyboard activation and
screen readers.

In `@examples/CRISP/client/src/components/Footer.tsx`:
- Around line 19-35: In the Footer component replace react-router-dom Link
usages that point to absolute external URLs (the elements wrapping GithubLogo,
TwitterLogo, TelegramLogo, CastleTurret and the text links "Secured with The
Interfold" and "Gnosis Guild") with plain anchor tags using href, keep
target="_blank", and add rel="noopener noreferrer"; preserve existing props like
aria-label, size, and inline style/spacing when converting so visual/accessible
behavior remains identical.

In `@examples/CRISP/client/src/design/editorial.css`:
- Around line 35-37: Normalize CSS keyword casing for the font-related custom
properties: ensure generic family names and any CSS keywords in --f-serif,
--f-italic, --f-mono (and the similar variable at line 55) use lowercase (e.g.,
serif, monospace, ui-monospace, etc.) rather than capitalized forms so
stylelint's keyword-case rule is satisfied; locate the variables by name in the
CSS and update only the keyword tokens to lowercase while leaving quoted font
family names and order unchanged.

In `@examples/CRISP/client/src/design/Editorial.tsx`:
- Around line 56-60: The memoized blocks creation in the useMemo callback (const
blocks = useMemo(...)) can infinite-loop because the for loop increments by
blockSize (i += blockSize) without validating blockSize; ensure blockSize is
clamped/validated before the loop (e.g., compute const step = Math.max(1,
Math.floor(blockSize || 0)) or return an empty array if blockSize <= 0) and then
iterate with i += step; reference the makeHex(seed + length, length), seed,
length, and blockSize symbols when updating the logic.

In `@examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx`:
- Around line 73-91: The polling check function in the DailyPoll component calls
getWebResultByRound without error handling and can produce unhandled promise
rejections; wrap the async check() body in a try/catch, log or surface errors
(e.g., console.error or existing logger), and ensure you still respect the
cancelled flag before calling setTallyReady; keep the interval behavior but
avoid throwing from the interval handler so transient failures don't crash the
loop.
- Around line 73-91: When the active round changes the per-round derived flags
are left stale; add a short useEffect that watches the round identifier (e.g.
roundState?.id) and resets per-round state like tallyReady via
setTallyReady(false) and any local isEnded state (or cause its recomputation) so
the UI doesn't stay in a previous round's closed/results view; ensure you
reference the same symbols used in the polling effect (roundState, tallyReady,
setTallyReady, getWebResultByRound) and update dependency arrays to use
roundState.id (not the whole object) so the reset runs when the round actually
changes.

In `@examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx`:
- Around line 33-35: The Link currently wraps a button in PastPoll (and the same
pattern in Hero) creating nested interactive elements; change each instance so
the Link is the interactive element by moving the button styling onto the Link
(e.g. replace the Link>button pattern with a Link element that has
className='btn ghost' and the to="/historic" and contains the text "View all
polls →"), remove the inner <button>, and ensure any accessibility attributes
(aria-label, role) previously on the button are applied to the Link as needed.
🪄 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: 22b873fe-4000-48ca-a049-7b820f97dcfd

📥 Commits

Reviewing files that changed from the base of the PR and between e82d4bd and 203b66b.

📒 Files selected for processing (19)
  • examples/CRISP/client/index.html
  • examples/CRISP/client/src/components/Cards/Card.tsx
  • examples/CRISP/client/src/components/Cards/CardContent.tsx
  • examples/CRISP/client/src/components/Cards/PollCard.tsx
  • examples/CRISP/client/src/components/Cards/PollCardResult.tsx
  • examples/CRISP/client/src/components/Footer.tsx
  • examples/CRISP/client/src/components/Navbar.tsx
  • examples/CRISP/client/src/components/VotesBadge.tsx
  • examples/CRISP/client/src/design/Editorial.tsx
  • examples/CRISP/client/src/design/editorial.css
  • examples/CRISP/client/src/globals.css
  • examples/CRISP/client/src/main.tsx
  • examples/CRISP/client/src/pages/About/About.tsx
  • examples/CRISP/client/src/pages/DailyPoll/components/ConfirmVote.tsx
  • examples/CRISP/client/src/pages/HistoricPoll/HistoricPoll.tsx
  • examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
  • examples/CRISP/client/src/pages/Landing/components/Hero.tsx
  • examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx
  • examples/CRISP/client/src/pages/PollResult/PollResult.tsx

Comment thread examples/CRISP/client/src/components/Cards/PollCard.tsx Outdated
Comment thread examples/CRISP/client/src/components/Footer.tsx Outdated
Comment thread examples/CRISP/client/src/design/Editorial.tsx
Comment thread examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
Comment thread examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx Outdated
@ctrlc03 ctrlc03 changed the title refactor: CRISP UI refactor: CRISP UI [skip-line-limit] May 21, 2026
@vercel vercel Bot temporarily deployed to Preview – enclave-docs May 21, 2026 19:30 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.

Actionable comments posted: 1

🤖 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/CRISP/client/src/pages/Landing/components/DailyPoll.tsx`:
- Around line 65-84: When roundState.id changes we clear trackedRoundId and
tallyReady synchronously but leave isEnded to be updated only after awaiting
client.getBlock(), causing a flicker; update the effect to synchronously
setIsEnded(false) (or compute from roundState if you can tell it's active)
immediately when trackedRoundId.current !== roundState.id so the UI doesn't show
the previous round's closed state, then proceed with the async client.getBlock()
and setIsEnded based on its result; change occurs inside the same useEffect that
references trackedRoundId, setTallyReady, setIsEnded, client.getBlock, and
roundState.id.
🪄 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: d6e13701-1418-4fc8-9bd4-87ec01390d48

📥 Commits

Reviewing files that changed from the base of the PR and between 203b66b and c39fab0.

📒 Files selected for processing (6)
  • examples/CRISP/client/src/components/Cards/PollCard.tsx
  • examples/CRISP/client/src/components/Footer.tsx
  • examples/CRISP/client/src/design/Editorial.tsx
  • examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
  • examples/CRISP/client/src/pages/Landing/components/Hero.tsx
  • examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx

Comment thread examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
@ctrlc03 ctrlc03 changed the title refactor: CRISP UI [skip-line-limit] refactor: crisp ui [skip-line-limit] May 21, 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: 3

Caution

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

⚠️ Outside diff range comments (1)
examples/CRISP/client/src/components/Cards/Card.tsx (1)

32-39: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Interactive div needs keyboard accessibility support.

When isDetails is false (or undefined), this card becomes clickable but lacks keyboard interaction. Users navigating with keyboard cannot activate these cards, violating WCAG 2.1.1 Level A.

Choose one solution:

  1. Use a semantic button when interactive (preferred):
    Wrap content in <button> when !isDetails, or
  2. Add ARIA and keyboard handlers to the div:
    • role="button"
    • tabIndex={0}
    • onKeyDown handler for Enter/Space keys
    • Consider aria-pressed={derivedIsClicked} for toggle state
♿ Proposed fix using conditional element type
+  const ElementType = isDetails ? 'div' : 'button'
+
   return (
-    <div
+    <ElementType
+      type={isDetails ? undefined : 'button'}
       data-test-id='card'
       className={`faceoff-slot ${derivedIsClicked ? 'selected' : ''}`}
-      style={{ aspectRatio: '1 / 1', cursor: isDetails ? 'default' : 'pointer', minHeight: isDetails ? 96 : 144 }}
+      style={{ 
+        aspectRatio: '1 / 1', 
+        cursor: isDetails ? 'default' : 'pointer', 
+        minHeight: isDetails ? 96 : 144,
+        ...(!isDetails && { border: 'none', background: 'transparent', padding: 0, font: 'inherit', textAlign: 'inherit' })
+      }}
       onClick={handleClick}
     >
       {children}
-    </div>
+    </ElementType>
   )
🤖 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/CRISP/client/src/components/Cards/Card.tsx` around lines 32 - 39,
The clickable card div lacks keyboard accessibility when isDetails is false:
update the component to render a semantic interactive element when not in
details mode (preferred) or add ARIA/keyboard handlers. Specifically, when
!isDetails replace/wrap the <div data-test-id='card' ...> with a <button>
(preserving className, style, onClick={handleClick}, children and using
aria-pressed={derivedIsClicked}) so keyboard/enter/space activation and
semantics are native; if you must keep a div, add role="button", tabIndex={0},
onKeyDown that triggers handleClick for Enter/Space and include
aria-pressed={derivedIsClicked}. Ensure derivedIsClicked and handleClick are
used unchanged.
🧹 Nitpick comments (1)
examples/CRISP/test/crisp.spec.ts (1)

148-148: ⚡ Quick win

Consider using a more specific selector for the Cast button.

The partial text match "Cast" could match unintended buttons if the UI evolves (e.g., "Broadcast", "Recast"). Using a data-test-id attribute or getByRole('button', { name: /^Cast$/ }) would be more robust.

🎯 Proposed alternative selectors

Option 1: Add a data-test-id to the button:

-await page.locator('button:has-text("Cast")').click()
+await page.locator('button[data-test-id="cast-vote-button"]').click()

Option 2: Use getByRole with exact name match:

-await page.locator('button:has-text("Cast")').click()
+await page.getByRole('button', { name: /^Cast$/ }).click()
🤖 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/CRISP/test/crisp.spec.ts` at line 148, The selector
page.locator('button:has-text("Cast")').click() is fragile because partial text
can match unintended buttons; update the test to use a more specific selector
such as a data-test-id on the Cast button (e.g., query by data-test-id) or
Playwright's role-based exact match (e.g., getByRole('button', { name: /^Cast$/
})) to ensure only the intended button is clicked; locate the call to
page.locator('button:has-text("Cast")').click() in crisp.spec.ts and replace it
with one of these more robust selectors.
🤖 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/CRISP/client/src/design/editorial.css`:
- Around line 35-37: CSS keyword-case violations are caused by capitalized
generic font-family and CSS keywords in the custom properties; update the values
for --f-serif, --f-italic, and --f-mono (and the other occurrence flagged on the
file) so that generic family names and CSS keywords are lowercase (e.g., serif,
monospace, ui-monospace) while preserving quoted font names as-is; locate and
modify the declarations for --f-serif, --f-italic, and --f-mono to use lowercase
keywords to satisfy stylelint.

In `@examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx`:
- Around line 76-79: The async IIFE calling client.getBlock() can throw and
cause unhandled rejections; wrap the await in a try/catch inside the effect (or
the IIFE) around the client.getBlock() call, only call
setIsEnded(block.timestamp > roundState.end_time) when not cancelled and block
is defined, and handle/log the error (or silently ignore transient RPC failures)
so failures don't bubble as unhandled rejections; reference client.getBlock(),
setIsEnded, roundState.end_time and cancelled when implementing the try/catch
guard.
- Around line 70-74: When trackedRoundId.current !== roundState.id, also reset
local selection state so the UI can't act on a previous round: inside that
branch (alongside trackedRoundId.current = roundState.id, setTallyReady(false),
setIsEnded(false)) call the setter(s) that clear vote selection state (for
example setNoPollSelected(true) and reset any selected option state such as
setSelectedOption(null) or setSelectedPoll(null)) so noPollSelected cannot
remain false from the prior round.

---

Outside diff comments:
In `@examples/CRISP/client/src/components/Cards/Card.tsx`:
- Around line 32-39: The clickable card div lacks keyboard accessibility when
isDetails is false: update the component to render a semantic interactive
element when not in details mode (preferred) or add ARIA/keyboard handlers.
Specifically, when !isDetails replace/wrap the <div data-test-id='card' ...>
with a <button> (preserving className, style, onClick={handleClick}, children
and using aria-pressed={derivedIsClicked}) so keyboard/enter/space activation
and semantics are native; if you must keep a div, add role="button",
tabIndex={0}, onKeyDown that triggers handleClick for Enter/Space and include
aria-pressed={derivedIsClicked}. Ensure derivedIsClicked and handleClick are
used unchanged.

---

Nitpick comments:
In `@examples/CRISP/test/crisp.spec.ts`:
- Line 148: The selector page.locator('button:has-text("Cast")').click() is
fragile because partial text can match unintended buttons; update the test to
use a more specific selector such as a data-test-id on the Cast button (e.g.,
query by data-test-id) or Playwright's role-based exact match (e.g.,
getByRole('button', { name: /^Cast$/ })) to ensure only the intended button is
clicked; locate the call to page.locator('button:has-text("Cast")').click() in
crisp.spec.ts and replace it with one of these more robust selectors.
🪄 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: ab47566c-7d78-4de5-b508-e57f89fb9ac7

📥 Commits

Reviewing files that changed from the base of the PR and between c39fab0 and fe7de43.

📒 Files selected for processing (20)
  • examples/CRISP/client/index.html
  • examples/CRISP/client/src/components/Cards/Card.tsx
  • examples/CRISP/client/src/components/Cards/CardContent.tsx
  • examples/CRISP/client/src/components/Cards/PollCard.tsx
  • examples/CRISP/client/src/components/Cards/PollCardResult.tsx
  • examples/CRISP/client/src/components/Footer.tsx
  • examples/CRISP/client/src/components/Navbar.tsx
  • examples/CRISP/client/src/components/VotesBadge.tsx
  • examples/CRISP/client/src/design/Editorial.tsx
  • examples/CRISP/client/src/design/editorial.css
  • examples/CRISP/client/src/globals.css
  • examples/CRISP/client/src/main.tsx
  • examples/CRISP/client/src/pages/About/About.tsx
  • examples/CRISP/client/src/pages/DailyPoll/components/ConfirmVote.tsx
  • examples/CRISP/client/src/pages/HistoricPoll/HistoricPoll.tsx
  • examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
  • examples/CRISP/client/src/pages/Landing/components/Hero.tsx
  • examples/CRISP/client/src/pages/Landing/components/PastPoll.tsx
  • examples/CRISP/client/src/pages/PollResult/PollResult.tsx
  • examples/CRISP/test/crisp.spec.ts
✅ Files skipped from review due to trivial changes (2)
  • examples/CRISP/client/src/globals.css
  • examples/CRISP/client/src/main.tsx

Comment thread examples/CRISP/client/src/design/editorial.css Outdated
Comment thread examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
Comment thread examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx Outdated

@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 (2)
examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx (2)

70-75: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear pollOptions[].checked on round change too.

Lines 74-75 reset the local mirrors, but FaceoffSlot renders selection from poll.checked. If the next round reuses the current pollOptions array until fresh data arrives, the old option will still show as selected.

Suggested fix
       if (trackedRoundId.current !== roundState.id) {
         trackedRoundId.current = roundState.id
         setTallyReady(false)
         setIsEnded(false)
         setPollSelected(null)
         setNoPollSelected(true)
+        setPollOptions((prevOptions) =>
+          prevOptions.map((option) => ({
+            ...option,
+            checked: 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 `@examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx` around
lines 70 - 75, When detecting a round change (the block where
trackedRoundId.current is compared to roundState.id and you call setTallyReady,
setIsEnded, setPollSelected, setNoPollSelected), also clear any lingering
selection flags by resetting pollOptions[].checked to false in the pollOptions
state so FaceoffSlot doesn't render the old checked option; implement this by
mapping the current pollOptions state and producing a new array with every
option.checked = false, then call the poll options setter (e.g., setPollOptions)
alongside the other resets.

65-91: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-check round end after the initial block lookup.

This effect only runs when roundState or client changes. If the page stays open through the deadline, isEnded never flips to true, and a single getBlock() failure also leaves the poll interactive indefinitely. Keep retrying until the round is closed instead of doing a one-shot lookup.

Suggested fix
   useEffect(() => {
-    let cancelled = false
-    ;(async () => {
-      if (!client || !roundState) return
+    if (!client || !roundState) return
+
+    let cancelled = false
+    let timer: ReturnType<typeof window.setTimeout> | undefined
+
+    const syncEndedState = async () => {
+      if (!client || !roundState) return
 
       if (trackedRoundId.current !== roundState.id) {
         trackedRoundId.current = roundState.id
         setTallyReady(false)
         setIsEnded(false)
         setPollSelected(null)
         setNoPollSelected(true)
       }
 
       try {
         const block = await client.getBlock()
-        if (!cancelled) {
-          setIsEnded(block.timestamp > roundState.end_time)
-        }
+        if (cancelled) return
+
+        const ended = block.timestamp > roundState.end_time
+        setIsEnded(ended)
+
+        if (!ended) {
+          timer = window.setTimeout(syncEndedState, 8000)
+        }
       } catch {
-        // Transient RPC failure — leave isEnded untouched and retry on the next run.
+        if (!cancelled) {
+          timer = window.setTimeout(syncEndedState, 8000)
+        }
       }
-    })()
+    }
+
+    void syncEndedState()
 
     return () => {
       cancelled = true
+      if (timer) window.clearTimeout(timer)
     }
-  }, [roundState, client])
+  }, [roundState?.id, roundState?.end_time, client])
🤖 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/CRISP/client/src/pages/Landing/components/DailyPoll.tsx` around
lines 65 - 91, The current useEffect does a one-shot getBlock() and can leave
isEnded false if the page remains open through the deadline or on a transient
RPC failure; change the logic in the useEffect (the async block using
client.getBlock, trackedRoundId.current, setIsEnded, setTallyReady,
setPollSelected, setNoPollSelected) to poll/retry periodically (e.g., via
setInterval or recursive setTimeout) until either the round changes
(trackedRoundId.current differs) or the block timestamp exceeds
roundState.end_time, updating setIsEnded(true) when that happens; ensure
transient errors just trigger the next retry instead of swallowing state
changes, and clear the interval/timeout on cleanup (respecting the cancelled
flag) so polling stops when the component unmounts or roundState/client changes.
🧹 Nitpick comments (2)
examples/CRISP/client/src/design/editorial.css (2)

557-557: ⚡ Quick win

Respect prefers-reduced-motion for animations.

The animations (pulse, encrypt-pop, scan) do not check the user's motion preference. Users with vestibular disorders or motion sensitivity rely on prefers-reduced-motion to disable animations.

♿ Proposed fix to add motion preference support

Wrap animation declarations in a media query:

+@media (prefers-reduced-motion: no-preference) {
 .crisp-editorial .tag.live.dot::before {
-  background: var(--paper);
   animation: crisp-pulse 1.4s ease-in-out infinite;
 }
+}
+@media (prefers-reduced-motion: reduce) {
+  .crisp-editorial .tag.live.dot::before {
+    animation: none;
+  }
+}

Apply the same pattern to .cipher .blk (line 602) and .scan (line 843).

Also applies to: 602-602, 843-843

🤖 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/CRISP/client/src/design/editorial.css` at line 557, The animations
(crisp-pulse, encrypt-pop, scan) should respect the user's motion preference:
wrap the existing animation declarations (e.g., the rule with "animation:
crisp-pulse 1.4s ease-in-out infinite", the ".cipher .blk" rule that uses
encrypt-pop, and the ".scan" rule) inside a media query for
prefers-reduced-motion: no-preference so they only run for users who allow
motion, and add a matching prefers-reduced-motion: reduce override that sets
animation: none (or animation: 0s/none with !important) for those who prefer
reduced motion.

630-630: ⚡ Quick win

Use CSS variable for seal border color.

The hard-coded #ffffff44 breaks consistency with the theming system. When palettes or modes change, this semi-transparent white overlay won't adapt.

♻️ Proposed fix for theme consistency

Consider defining a CSS variable for this overlay border color:

+  --seal-active-border: `#ffffff44`;

Then reference it in the seal active state:

 .crisp-editorial .seal.active::before {
-  border-color: `#ffffff44`;
+  border-color: var(--seal-active-border);
 }

Or use an existing variable with opacity, e.g., color: color-mix(in srgb, var(--paper) 27%, transparent); if your browser support allows.

🤖 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/CRISP/client/src/design/editorial.css` at line 630, Replace the
hard-coded semi-transparent color with a theme-aware CSS variable: add a new
variable (e.g., --seal-border) to your theme root (or reuse an existing variable
and apply opacity via color-mix/alpha) and then change the border-color
declaration that currently uses "`#ffffff44`" to reference var(--seal-border,
`#ffffff44`); locate the border-color in the seal active selector (the rule
containing "border-color: `#ffffff44`") and update it to use the variable so the
seal adapts when palettes/modes change.
🤖 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/CRISP/client/src/pages/Landing/components/DailyPoll.tsx`:
- Around line 70-75: When detecting a round change (the block where
trackedRoundId.current is compared to roundState.id and you call setTallyReady,
setIsEnded, setPollSelected, setNoPollSelected), also clear any lingering
selection flags by resetting pollOptions[].checked to false in the pollOptions
state so FaceoffSlot doesn't render the old checked option; implement this by
mapping the current pollOptions state and producing a new array with every
option.checked = false, then call the poll options setter (e.g., setPollOptions)
alongside the other resets.
- Around line 65-91: The current useEffect does a one-shot getBlock() and can
leave isEnded false if the page remains open through the deadline or on a
transient RPC failure; change the logic in the useEffect (the async block using
client.getBlock, trackedRoundId.current, setIsEnded, setTallyReady,
setPollSelected, setNoPollSelected) to poll/retry periodically (e.g., via
setInterval or recursive setTimeout) until either the round changes
(trackedRoundId.current differs) or the block timestamp exceeds
roundState.end_time, updating setIsEnded(true) when that happens; ensure
transient errors just trigger the next retry instead of swallowing state
changes, and clear the interval/timeout on cleanup (respecting the cancelled
flag) so polling stops when the component unmounts or roundState/client changes.

---

Nitpick comments:
In `@examples/CRISP/client/src/design/editorial.css`:
- Line 557: The animations (crisp-pulse, encrypt-pop, scan) should respect the
user's motion preference: wrap the existing animation declarations (e.g., the
rule with "animation: crisp-pulse 1.4s ease-in-out infinite", the ".cipher .blk"
rule that uses encrypt-pop, and the ".scan" rule) inside a media query for
prefers-reduced-motion: no-preference so they only run for users who allow
motion, and add a matching prefers-reduced-motion: reduce override that sets
animation: none (or animation: 0s/none with !important) for those who prefer
reduced motion.
- Line 630: Replace the hard-coded semi-transparent color with a theme-aware CSS
variable: add a new variable (e.g., --seal-border) to your theme root (or reuse
an existing variable and apply opacity via color-mix/alpha) and then change the
border-color declaration that currently uses "`#ffffff44`" to reference
var(--seal-border, `#ffffff44`); locate the border-color in the seal active
selector (the rule containing "border-color: `#ffffff44`") and update it to use
the variable so the seal adapts when palettes/modes change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a2e81038-5103-402b-b6ab-30d6beca76ab

📥 Commits

Reviewing files that changed from the base of the PR and between fe7de43 and a2edcb0.

📒 Files selected for processing (2)
  • examples/CRISP/client/src/design/editorial.css
  • examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx

@hmzakhalid hmzakhalid merged commit 5c76293 into main May 22, 2026
32 checks passed
@github-actions github-actions Bot deleted the refactor/crisp branch May 30, 2026 03:23
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