refactor: crisp ui [skip-line-limit]#1551
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis 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. ChangesEditorial Design System & UI Refactor
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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.
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
📒 Files selected for processing (19)
examples/CRISP/client/index.htmlexamples/CRISP/client/src/components/Cards/Card.tsxexamples/CRISP/client/src/components/Cards/CardContent.tsxexamples/CRISP/client/src/components/Cards/PollCard.tsxexamples/CRISP/client/src/components/Cards/PollCardResult.tsxexamples/CRISP/client/src/components/Footer.tsxexamples/CRISP/client/src/components/Navbar.tsxexamples/CRISP/client/src/components/VotesBadge.tsxexamples/CRISP/client/src/design/Editorial.tsxexamples/CRISP/client/src/design/editorial.cssexamples/CRISP/client/src/globals.cssexamples/CRISP/client/src/main.tsxexamples/CRISP/client/src/pages/About/About.tsxexamples/CRISP/client/src/pages/DailyPoll/components/ConfirmVote.tsxexamples/CRISP/client/src/pages/HistoricPoll/HistoricPoll.tsxexamples/CRISP/client/src/pages/Landing/components/DailyPoll.tsxexamples/CRISP/client/src/pages/Landing/components/Hero.tsxexamples/CRISP/client/src/pages/Landing/components/PastPoll.tsxexamples/CRISP/client/src/pages/PollResult/PollResult.tsx
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
examples/CRISP/client/src/components/Cards/PollCard.tsxexamples/CRISP/client/src/components/Footer.tsxexamples/CRISP/client/src/design/Editorial.tsxexamples/CRISP/client/src/pages/Landing/components/DailyPoll.tsxexamples/CRISP/client/src/pages/Landing/components/Hero.tsxexamples/CRISP/client/src/pages/Landing/components/PastPoll.tsx
There was a problem hiding this comment.
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 winInteractive div needs keyboard accessibility support.
When
isDetailsis 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:
- Use a semantic button when interactive (preferred):
Wrap content in<button>when!isDetails, or- Add ARIA and keyboard handlers to the div:
role="button"tabIndex={0}onKeyDownhandler 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 winConsider 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 orgetByRole('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
📒 Files selected for processing (20)
examples/CRISP/client/index.htmlexamples/CRISP/client/src/components/Cards/Card.tsxexamples/CRISP/client/src/components/Cards/CardContent.tsxexamples/CRISP/client/src/components/Cards/PollCard.tsxexamples/CRISP/client/src/components/Cards/PollCardResult.tsxexamples/CRISP/client/src/components/Footer.tsxexamples/CRISP/client/src/components/Navbar.tsxexamples/CRISP/client/src/components/VotesBadge.tsxexamples/CRISP/client/src/design/Editorial.tsxexamples/CRISP/client/src/design/editorial.cssexamples/CRISP/client/src/globals.cssexamples/CRISP/client/src/main.tsxexamples/CRISP/client/src/pages/About/About.tsxexamples/CRISP/client/src/pages/DailyPoll/components/ConfirmVote.tsxexamples/CRISP/client/src/pages/HistoricPoll/HistoricPoll.tsxexamples/CRISP/client/src/pages/Landing/components/DailyPoll.tsxexamples/CRISP/client/src/pages/Landing/components/Hero.tsxexamples/CRISP/client/src/pages/Landing/components/PastPoll.tsxexamples/CRISP/client/src/pages/PollResult/PollResult.tsxexamples/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
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 (2)
examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx (2)
70-75:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winClear
pollOptions[].checkedon round change too.Lines 74-75 reset the local mirrors, but
FaceoffSlotrenders selection frompoll.checked. If the next round reuses the currentpollOptionsarray 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 winRe-check round end after the initial block lookup.
This effect only runs when
roundStateorclientchanges. If the page stays open through the deadline,isEndednever flips totrue, and a singlegetBlock()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 winRespect
prefers-reduced-motionfor 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-motionto 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 winUse CSS variable for seal border color.
The hard-coded
#ffffff44breaks 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
📒 Files selected for processing (2)
examples/CRISP/client/src/design/editorial.cssexamples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
Summary by CodeRabbit
New Features
Style
Refactor
Tests