feat: vintage cream PC demo on the landing page#73
Conversation
A late-80s beige-box computer (Philips P3120 XT energy) built entirely from CSS sits over the terminal background — 4:3 CRT running a miniature of the new moderation dashboard (dither-kit sparklines and a flagged-PR queue), system unit with red stripe, vents, floppy drives, and an attached keyboard whose arrow cluster mirrors real key presses — the same presses that flip the page into the terminal game. Flat plastic bevels only: no glows, no blurs, no tracking.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR adds a new 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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 |
|
React Doctor found 17 issues in 4 files · 7 errors & 10 warnings · score 61 / 100 (Needs work) · vs Errors
10 warnings
|
- space invaders now boots inside the retro computer's tube, replacing the dashboard demo (esc hands the machine back) — the game canvas is a fixed 800x600 to match the 4:3 glass, and the terminal backdrop no longer does the takeover dance - the keyboard gets real perspective (rotateX off the case seam with a thick front edge) so the system reads as stacked on a desk instead of two front-facing slabs - whole machine scaled up from max-w-xl to max-w-3xl, demo status line pinned to the tube's bottom edge
|
Round two, per feedback:
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
apps/web/src/components/layout/landing/retro-computer.tsx (1)
180-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate scanline overlay markup between
DemoScreenandGameScreen.The identical
repeating-linear-gradientscanline overlay is duplicated. Extract a small sharedScanlineOverlaycomponent to avoid divergence.♻️ Suggested extraction
+function ScanlineOverlay() { + return ( + <div + aria-hidden + className="pointer-events-none absolute inset-0" + style={{ + backgroundImage: + "repeating-linear-gradient(to bottom, rgba(0,0,0,0.22) 0px, rgba(0,0,0,0.22) 1px, transparent 1px, transparent 3px)", + }} + /> + ) +}Then use
<ScanlineOverlay />in bothDemoScreenandGameScreen.Also applies to: 211-218
🤖 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 `@apps/web/src/components/layout/landing/retro-computer.tsx` around lines 180 - 188, The scanline overlay markup is duplicated between DemoScreen and GameScreen, so extract the shared repeating-linear-gradient block into a reusable ScanlineOverlay component in retro-computer.tsx. Replace both inline overlay divs with ScanlineOverlay to keep the styling in one place and prevent drift if the effect changes later.apps/web/src/components/layout/landing/space-invaders.tsx (1)
167-168: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEm dashes in comments violate the style guideline.
Both new comments use em dashes:
"...for the landing page's CRT — the RetroComputer mounts it..."and"Fixed 4:3 — the CRT's aspect ratio, downscaled by CSS into the tube."As per coding guidelines, comments "should be sparse, contain no em dashes or overly verbose language patterns."✏️ Proposed fix
-// The game renders to an offscreen 4:3 canvas sized for the landing page's -// CRT — the RetroComputer mounts it inside the tube as the display. +// The game renders to an offscreen 4:3 canvas sized for the landing page's +// CRT. RetroComputer mounts it inside the tube as the display.- // Fixed 4:3 — the CRT's aspect ratio, downscaled by CSS into the tube. + // Fixed 4:3 to match the CRT's aspect ratio; CSS downscales it into the tube.Also applies to: 214-216
🤖 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 `@apps/web/src/components/layout/landing/space-invaders.tsx` around lines 167 - 168, The comments in space-invaders.tsx use em dashes and should be rewritten to match the comment style guideline. Update the newly added comments near the game/canvas setup in the space-invaders component so they are sparse, concise, and contain no em dashes or overly verbose phrasing; keep the intent clear while removing the dash-separated commentary around the CRT, RetroComputer, and 4:3 aspect ratio references.Source: Coding guidelines
apps/web/src/routes/index.tsx (1)
68-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winComments use em dashes and one is now stale.
- Lines 68-69:
"...the retro computer's CRT, replacing the dashboard demo — the terminal backdrop stays put."uses an em dash and mostly explains what the code does. As per coding guidelines, comments "should be sparse, contain no em dashes... and should ONLY be used to give important context about programmatic decisions or nuance—never to just explain what a line does."- Line 103:
"Landing content — fades out when game activates"— this claim is now inaccurate. Per this diff, the transition-driven opacity/transform/blur wrapper tied togameActive/transitioningwas removed in favor of a simpler wrapper, so the content no longer fades on activation. This stale comment will mislead future readers.✏️ Proposed fix
- // The game boots straight onto the retro computer's CRT, replacing the - // dashboard demo — the terminal backdrop stays put. - const gameCanvas = useSpaceInvaders(gameActive, exitGame) + const gameCanvas = useSpaceInvaders(gameActive, exitGame)- {/* Landing content — fades out when game activates */} + {/* Landing content */}Also applies to: 103-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 `@apps/web/src/routes/index.tsx` around lines 68 - 71, The comments around useSpaceInvaders and the landing content wrapper are now misleading and violate the comment style guideline. Remove the explanatory comment with the em dash above the gameCanvas/useSpaceInvaders setup, and update or delete the “Landing content” comment so it no longer claims a fade-out on activation since the gameActive/transitioning-driven wrapper is gone. Keep only any sparse comment that adds real implementation nuance.Source: Coding guidelines
🤖 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 `@apps/web/src/components/layout/landing/retro-computer.tsx`:
- Around line 16-17: Remove the banned ASCII divider comments in
retro-computer.tsx, including the palette/demo content/keyboard/monitor/system
unit/shell section separators. Update the affected areas in the RetroComputer
component and related JSX by deleting the divider-style comments and relying on
whitespace plus clear nearby naming to indicate sections. Keep the code
structure intact while cleaning up the comment style throughout the file.
- Around line 6-14: Remove the JSDoc-style block at the top of
retro-computer.tsx and convert the non-exported documentation for DemoScreen,
GameScreen, KEY_ROWS, and usePressedArrows into plain sparse comments or delete
it entirely; keep JSDoc only on exported APIs. Also scan the inline comments
around the referenced sections and replace any em dashes with plain hyphens or
reword them to match the comment style guidelines.
- Around line 194-221: Use named props interfaces instead of inline object types
for the component props in RetroComputer-related components. Update GameScreen
and the other affected components (Key, Monitor, RetroComputer) to reference
shared or locally declared interfaces rather than destructuring raw inline types
in the function signature. Keep the prop shape typed through explicit interface
names so the component definitions are easier to reuse and align with the
TypeScript guidelines.
- Around line 271-294: The pressed-arrow tracking in usePressedArrows can remain
stale when the window loses focus before keyup fires. Update the hook to clear
the pressed Set on focus loss by handling blur/visibility changes (for example
in the same useEffect that registers the keyboard listeners), and make sure the
cleanup removes those listeners as well so the highlighted state resets when the
app is alt-tabbed or the window is unfocused.
- Around line 99-102: `DemoScreen`’s root container is missing a positioning
context for the scanline overlay. Update the root wrapper in `DemoScreen` to
include a positioned class such as `relative` (matching the `GameScreen`
pattern) so the `absolute inset-0` overlay anchors to the screen itself instead
of depending on `Monitor`’s tube div. Keep the change localized to the
`DemoScreen` root element and preserve the existing overlay structure.
---
Nitpick comments:
In `@apps/web/src/components/layout/landing/retro-computer.tsx`:
- Around line 180-188: The scanline overlay markup is duplicated between
DemoScreen and GameScreen, so extract the shared repeating-linear-gradient block
into a reusable ScanlineOverlay component in retro-computer.tsx. Replace both
inline overlay divs with ScanlineOverlay to keep the styling in one place and
prevent drift if the effect changes later.
In `@apps/web/src/components/layout/landing/space-invaders.tsx`:
- Around line 167-168: The comments in space-invaders.tsx use em dashes and
should be rewritten to match the comment style guideline. Update the newly added
comments near the game/canvas setup in the space-invaders component so they are
sparse, concise, and contain no em dashes or overly verbose phrasing; keep the
intent clear while removing the dash-separated commentary around the CRT,
RetroComputer, and 4:3 aspect ratio references.
In `@apps/web/src/routes/index.tsx`:
- Around line 68-71: The comments around useSpaceInvaders and the landing
content wrapper are now misleading and violate the comment style guideline.
Remove the explanatory comment with the em dash above the
gameCanvas/useSpaceInvaders setup, and update or delete the “Landing content”
comment so it no longer claims a fade-out on activation since the
gameActive/transitioning-driven wrapper is gone. Keep only any sparse comment
that adds real implementation nuance.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 73b183b0-fdcd-4f70-ae4f-1f3e8f325296
📒 Files selected for processing (3)
apps/web/src/components/layout/landing/retro-computer.tsxapps/web/src/components/layout/landing/space-invaders.tsxapps/web/src/routes/index.tsx
| /** | ||
| * A late-80s cream desktop PC (Philips P3120 XT energy) built entirely from | ||
| * CSS — monitor on the system unit with the keyboard attached at its foot. | ||
| * The 4:3 CRT runs a miniature of the tripwire moderation dashboard, and the | ||
| * keyboard's arrow cluster lights up with real key presses — the same presses | ||
| * that flip the landing page into the terminal game. | ||
| * | ||
| * Deliberately flat: hard bevels and plastic seams only. No glows, no blurs. | ||
| */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
JSDoc on non-exported code, and em dashes in comments.
The top-of-file doc block (lines 6-14) and the JSDoc-style comments on DemoScreen, GameScreen, KEY_ROWS, and usePressedArrows (which are not exported) violate the JSDoc-only-on-exports rule. Several of these comments (and the inline ones at lines 180, 210, 329) also use em dashes.
As per coding guidelines, "Only use JSDoc on exported functions and types, not on decorative multi-line doc comments that describe sections" and "Comments should be sparse, contain no em dashes or overly verbose language patterns."
Also applies to: 95-96, 193-193, 225-226, 270-270, 180-180, 210-210, 329-329
🤖 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 `@apps/web/src/components/layout/landing/retro-computer.tsx` around lines 6 -
14, Remove the JSDoc-style block at the top of retro-computer.tsx and convert
the non-exported documentation for DemoScreen, GameScreen, KEY_ROWS, and
usePressedArrows into plain sparse comments or delete it entirely; keep JSDoc
only on exported APIs. Also scan the inline comments around the referenced
sections and replace any em dashes with plain hyphens or reword them to match
the comment style guidelines.
| function GameScreen({ canvas }: { canvas: HTMLCanvasElement }) { | ||
| const host = useRef<HTMLDivElement>(null) | ||
| useEffect(() => { | ||
| const el = host.current | ||
| if (!el) return | ||
| canvas.style.width = "100%" | ||
| canvas.style.height = "100%" | ||
| canvas.style.display = "block" | ||
| el.appendChild(canvas) | ||
| return () => { | ||
| canvas.remove() | ||
| } | ||
| }, [canvas]) | ||
| return ( | ||
| <div className="absolute inset-0" style={{ background: "#0d0d0f" }}> | ||
| <div ref={host} className="absolute inset-0" /> | ||
| {/* same scanlines as the demo — it's the same tube */} | ||
| <div | ||
| aria-hidden | ||
| className="pointer-events-none absolute inset-0" | ||
| style={{ | ||
| backgroundImage: | ||
| "repeating-linear-gradient(to bottom, rgba(0,0,0,0.22) 0px, rgba(0,0,0,0.22) 1px, transparent 1px, transparent 3px)", | ||
| }} | ||
| /> | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Add named props interfaces instead of inline object types.
GameScreen, Key, Monitor, and RetroComputer all destructure inline object type literals ({ canvas }: { canvas: HTMLCanvasElement }, etc.) rather than named interfaces.
As per coding guidelines, "Type Safety First: TypeScript interfaces for all props, state, return types" and "Never introduce a raw inline type to a component-level or route-level file. Keep it scoped to a generally existing file where similar types live."
✏️ Example fix
+interface GameScreenProps {
+ canvas: HTMLCanvasElement
+}
+
-function GameScreen({ canvas }: { canvas: HTMLCanvasElement }) {
+function GameScreen({ canvas }: GameScreenProps) {Also applies to: 242-250, 356-356, 480-485
🤖 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 `@apps/web/src/components/layout/landing/retro-computer.tsx` around lines 194 -
221, Use named props interfaces instead of inline object types for the component
props in RetroComputer-related components. Update GameScreen and the other
affected components (Key, Monitor, RetroComputer) to reference shared or locally
declared interfaces rather than destructuring raw inline types in the function
signature. Keep the prop shape typed through explicit interface names so the
component definitions are easier to reuse and align with the TypeScript
guidelines.
…fold - the whole drawn keyboard now mirrors the real one (letters, digits, modifiers, space — not just arrows), with a window-blur reset so no key sticks down - landing background swaps the terminal shader for the Windows 98 desktop teal (#008080) — the stage for what's next - hero stays dead centre; the machine tucks under the fold with just the monitor peeking up beneath the CTA
|
Round three:
|
- at rest only the system unit waits at the page bottom; its (plain, moulded, latching) power button boots the machine — monitor springs up out of the case, the picture warms in a beat later, and the keyboard slides out from underneath - stock wallpaper downloaded into public/wallpapers and used full-bleed; the hero fades away while the machine runs and returns on power-off - badges like the real thing: brand debossed into the monitor chin, a metallic model plate, tube power light; drive bays lose their letter labels for slot + activity light + eject - contrast pass for the bright backdrop: dark header + hero text, solid black CTAs, and the old CRT-curvature header tilt removed with the terminal it compensated for - machine hidden on mobile — plain wallpaper + hero there
|
Round four:
|
demo-screen.tsx — the tube now runs a looping tour of the real app: a cursor glides to the nav, clicks, and the page changes (home events, insights with dither-chart entrances, events queue with filter tabs), each scene staggering in with the shipped app's exact palette and chrome. Respects prefers-reduced-motion (crossfades, no cursor). cloud-eye.tsx — the mouse-following eye from the old landing page returns as an OGL cumulus: the eye paths are rasterized into a blurred density field, domain-warped by fbm so the silhouette billows; alpha smoothsteps for soft edges with eroding wisps; lighting compares density toward the sun for self-shadowed undersides, a silver-lined fringe, and cottony mottle; cursor velocity feeds a wind uniform so the cloud drags and stretches in flight and relaxes at rest. The pupil leans toward the live cursor inside the socket.
|
Round five:
|
The tour keeps playing itself until the visitor's mouse enters the tube — then the drawn pointer becomes their cursor (the OS cursor is hidden over the screen), nav clicks actually navigate, and presses dip the pointer like the tour's own clicks. On leave the loop resumes from wherever they left it. One motion-value cursor serves both modes: the tour animates it on spring rails, the mouse writes it directly.
|
Round six: hover takes the wheel — the tour plays itself until your mouse enters the tube, then the demo's drawn pointer becomes your cursor (OS cursor hidden over the glass), the nav items respond to real clicks, and presses dip the pointer just like the tour's choreographed clicks. Mouse out, and the tour picks the loop back up from wherever you left it. Verified: follow, click-to-navigate (events page), and auto-resume. |
Leaving the tube no longer snaps the tour back: the pointer stays parked where the visitor left it, and the tour only picks the loop back up after five seconds without any interaction (same timer covers going idle while still hovering). The shared motion values mean the resumed tour springs off from the parked position.
|
Round seven: control lingers — mouse-out leaves the demo pointer parked right where you dropped it; the tour only resumes after 5 seconds without interaction (moving, clicking, or re-entering resets the window), springing off from the parked spot rather than teleporting. Verified: parked at t+1.5s, gliding again at t+6.5s. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
apps/web/src/components/layout/landing/cloud-eye.tsx (3)
322-324: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider honoring
prefers-reduced-motion. The idle drift keeps the cloud animating continuously even without pointer input. For reduced-motion users, gating the drift (and optionally pausing the churn) would be a nice accessibility touch. Cursor-follow can remain since it's user-driven.🤖 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 `@apps/web/src/components/layout/landing/cloud-eye.tsx` around lines 322 - 324, The idle cloud motion in cloud-eye.tsx should respect reduced-motion preferences instead of always drifting. Update the animation logic around the driftX and driftY calculations so they are gated by a prefers-reduced-motion check, and keep the pointer-driven cursor-follow behavior intact since it is user initiated. If the component has a churn or continuous animation loop, optionally pause that too when reduced motion is enabled, using the existing cloud-eye component’s motion state/animation code paths.
16-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTrim the decorative doc block and drop em dashes from comments.
This top-of-file block is a decorative multi-line doc comment (not attached to an exported function/type) and uses em dashes and verbose prose. The same em-dash pattern appears in the JS comment on Line 208 (
// the shader reads .r — flatten ...). Please condense to sparse context and replace em dashes with hyphens/rephrasing.As per coding guidelines: "Comments should be sparse, contain no em dashes or overly verbose language patterns" and "Only use JSDoc on exported functions and types, not on decorative multi-line doc comments that describe sections".
🤖 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 `@apps/web/src/components/layout/landing/cloud-eye.tsx` around lines 16 - 38, The top-of-file block in cloud-eye.tsx is a decorative doc comment and should be trimmed to a sparse section note instead of verbose JSDoc-style prose; keep only minimal context near the relevant component symbols and remove the long descriptive paragraphs. Also update the inline shader comment near the .r flattening logic to replace any em dashes with simple hyphens or rephrased wording. Follow the comment guidelines by reserving JSDoc for exported functions/types and keeping comments brief and non-decorative.Source: Coding guidelines
293-295: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
uResolutionis dead. It's declared in the fragment shader (Line 59) and written on every resize, but never sampled anywhere in the shader body. Consider removing the uniform and its updates to avoid confusion.♻️ Proposed cleanup
- renderer.setSize(container.offsetWidth, container.offsetHeight) - const res = program.uniforms.uResolution.value as Float32Array - res[0] = gl.canvas.width - res[1] = gl.canvas.height + renderer.setSize(container.offsetWidth, container.offsetHeight)Also drop
uniform vec2 uResolution;(Line 59) and theuResolutionentry inuniforms(Line 275).🤖 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 `@apps/web/src/components/layout/landing/cloud-eye.tsx` around lines 293 - 295, Remove the unused uResolution uniform from CloudEye: it is declared in the shader, included in the uniforms map, and updated during resize in the cloud-eye component, but never read in the shader body. Update the relevant shader setup and resize logic in cloud-eye.tsx by deleting the uResolution uniform declaration, its uniforms entry, and the write to program.uniforms.uResolution.value so the implementation matches actual usage.
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx`:
- Around line 548-560: The root container in demo-screen applies cursor: "none"
unconditionally while TourCursor is only rendered when !reduceMotion, so
reduced-motion users lose any visible cursor. Update the cursor handling in the
main demo-screen render so the hidden cursor style is applied only when
TourCursor is available, using the existing reduceMotion condition and the
rootRef/onPointer* interaction logic to keep hover feedback intact.
- Around line 99-131: Replace the inline prop object literals in Avatar and Chip
with named interfaces, and apply the same pattern to TopNav and TourCursor in
the referenced sections. Also tighten useDemoCursor so its phase parameter uses
the actual "resting" | "clicking" union from useTour instead of a generic
string. Keep the component and hook signatures easy to locate by their names and
move all component-level typing to named interfaces per the TypeScript
guidelines.
- Around line 7-15: The comments in this component use JSDoc on non-exported
items and include em dashes/verbose phrasing, which violates the comment
guidelines. In demo-screen.tsx, replace the header block and the JSDoc above
useTour, useDemoCursor, and the dip prop with short plain comments or remove
them unless they document exported APIs, and rewrite the inline comments near
the marked spots to be sparse and free of em dashes. Keep the existing symbols
(useTour, useDemoCursor, dip) easy to find while trimming comment style to match
the codebase rules.
- Line 17: Remove the banned ASCII divider comments from the landing demo screen
module by deleting the section separator lines around the palette, tour,
fragments, scenes, nav, and screen sections in demo-screen.tsx. Keep the
structure readable with whitespace and existing naming instead of divider
comments, and update any nearby section headers in the DemoScreen-related
constants/components so the file remains organized without ASCII-art separators.
In `@apps/web/src/routes/index.tsx`:
- Around line 65-94: The hero content in the main motion.div is only visually
hidden when powered is true, so the Link remains keyboard-focusable even though
it is not visible. Update the hero container and the conditional Link inside to
be inaccessible while powered is true by adding the appropriate hidden semantics
and removing focusability (for example, aria-hidden on the hidden state and
tabIndex={-1} on the Link), and make sure the behavior is tied to the powered
flag in the landing route component.
---
Nitpick comments:
In `@apps/web/src/components/layout/landing/cloud-eye.tsx`:
- Around line 322-324: The idle cloud motion in cloud-eye.tsx should respect
reduced-motion preferences instead of always drifting. Update the animation
logic around the driftX and driftY calculations so they are gated by a
prefers-reduced-motion check, and keep the pointer-driven cursor-follow behavior
intact since it is user initiated. If the component has a churn or continuous
animation loop, optionally pause that too when reduced motion is enabled, using
the existing cloud-eye component’s motion state/animation code paths.
- Around line 16-38: The top-of-file block in cloud-eye.tsx is a decorative doc
comment and should be trimmed to a sparse section note instead of verbose
JSDoc-style prose; keep only minimal context near the relevant component symbols
and remove the long descriptive paragraphs. Also update the inline shader
comment near the .r flattening logic to replace any em dashes with simple
hyphens or rephrased wording. Follow the comment guidelines by reserving JSDoc
for exported functions/types and keeping comments brief and non-decorative.
- Around line 293-295: Remove the unused uResolution uniform from CloudEye: it
is declared in the shader, included in the uniforms map, and updated during
resize in the cloud-eye component, but never read in the shader body. Update the
relevant shader setup and resize logic in cloud-eye.tsx by deleting the
uResolution uniform declaration, its uniforms entry, and the write to
program.uniforms.uResolution.value so the implementation matches actual usage.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 23f40fc1-e757-4f1b-a544-00e5c10a8326
⛔ Files ignored due to path filters (1)
apps/web/public/wallpapers/win98.jpgis excluded by!**/*.jpg
📒 Files selected for processing (6)
apps/web/biome.jsonapps/web/src/components/layout/landing/cloud-eye.tsxapps/web/src/components/layout/landing/demo-screen.tsxapps/web/src/components/layout/landing/header.tsxapps/web/src/components/layout/landing/retro-computer.tsxapps/web/src/routes/index.tsx
| /** | ||
| * The picture on the retro computer's CRT: a self-playing tour of the real | ||
| * product. A little cursor glides to a nav item, clicks it, and the page | ||
| * changes — charts run their dither entrances, lists stagger in — looping | ||
| * home → insights → events, exactly the chrome and palette of the shipped app. | ||
| * | ||
| * Rendered at 2× and scaled to 0.5 so type and hairlines stay crisp on the | ||
| * tube. | ||
| */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
JSDoc on non-exported code, plus em dashes.
The file header (7-15), useTour (51-54), useDemoCursor (443-445) and the dip prop doc (481-483) are JSDoc-style comments on non-exported entities, several containing em dashes (lines 10, 443-444). Inline comments at 587 and 617 also use em dashes.
As per coding guidelines, "Only use JSDoc on exported functions and types" and "Comments should be sparse, contain no em dashes or overly verbose language patterns."
Also applies to: 51-54, 443-445, 481-483, 587-587, 617-617
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` around lines 7 - 15,
The comments in this component use JSDoc on non-exported items and include em
dashes/verbose phrasing, which violates the comment guidelines. In
demo-screen.tsx, replace the header block and the JSDoc above useTour,
useDemoCursor, and the dip prop with short plain comments or remove them unless
they document exported APIs, and rewrite the inline comments near the marked
spots to be sparse and free of em dashes. Keep the existing symbols (useTour,
useDemoCursor, dip) easy to find while trimming comment style to match the
codebase rules.
Source: Coding guidelines
Deep-dived the real frontend and rebuilt every scene to match it: - chrome: the modkit pixel wordmark, Queue/Automod/Analytics/ Integrations nav with tabular counts, the search-reports input, theme toggle and avatar — on the app's zinc palette, resolved from its oklch tokens - Queue: the Moderation header and subtitle, all four DitherStatCards (Pending/Resolved today/Automod 24h/Banned, correct colors, deltas with invert semantics, aura-bloomed dither charts flush at the card foot), the Pending/Log toggle, and real flagged rows from the mock data with reason pills and severity dots - Automod: the rule list with sparklines, 24h hit counts, and miniaturized shadcn switches (disabled rule renders grey) - Analytics: the focused-metric header (big mono value + delta + range) over the full-bleed dither chart, with metric mini-cards Tour, cursor takeover, and idle-resume mechanics unchanged.
|
Round eight: the demo is now a 1:1 clone of modkit (the new frontend). Deep-dived the real app and rebuilt each scene faithfully — the pixel wordmark + counted nav chrome, the Queue page with all four dither stat cards and real flagged-item rows (reason pills, severity dots), Automod's rule list with sparklines/hit counts/switches, and Analytics' focused-metric chart with aura bloom. Palette is the app's actual zinc tokens. Tour + mouse-takeover behavior unchanged. All three scenes screenshot-verified. |
Two routes deeper — the demo is now a mini-app with the real product's interactions, split into landing/demo/*: - queue: Severity/Newest sort, the full reason chip row (through NSFW), and rows that open the sliding detail panel — the page grid springs to make room for a 328px panel exactly like the real dashboard layout. The panel carries badges, the problematic-content preview, author/reporter metadata, and the arm-then-confirm Approve/Ban/Remove bar (confirming resolves the item out of the queue). - stat cards navigate into analytics with their source and metric; analytics has the committed-index chart (click to pin the crosshair and reprice the big number), the activity feed that pulls the nearest event to the top, and the bottom Show Metrics sheet that lifts the page height with a spring — clicking a sheet card refocuses the chart. Both metric sets (moderation + automod) included. - automod in full: stat cards, three-way sort, category chips, rule rows with working switches, and the rule detail panel with pattern block, hit chart, FP-rate amber warning, and recent matches you can Confirm / mark False positive. - integrations: account cards, the active-repository picker with working pagination and set-active. - a moon/sun toggle scoped to the demo — both modkit palettes resolved from its oklch tokens, exposed as CSS variables on the tube root. Tour cycles all four pages; the visitor's mouse still takes over on hover with the 5s linger. All interactions verified end-to-end with a scripted playwright run (10 stages, screenshots).
|
Round nine: the demo is a working mini-app now. Two routes deeper, all screenshot-verified end-to-end:
Tour still walks all four pages hands-off; mouse takeover + 5s linger unchanged. |
- the demo topbar reads tripwire instead of modkit - the powered machine grows from max-w-3xl to max-w-4xl — the tube was a touch hard to read - while the demo owns the visitor's cursor, the cloud eye sinks down and fades out of the sky (eased in the shader loop via a uHide uniform, anchor drifting downward as it goes) so it never distracts from the screen; it drifts back once the demo releases control
|
Round ten: demo wordmark now reads tripwire; the powered-up machine is a size bigger ( |
The tube was still squinty: the monitor takes more of the machine and the demo now renders at a 0.6 downscale instead of 0.5, so type on the glass is a fifth larger. Pointer mapping updated to match.
|
Round eleven: bigger picture — monitor widened to 80% of the machine and the demo content renders at 0.6 scale instead of 0.5 (~20% larger type on the glass). The queue page is properly readable on the tube now; screenshot-verified, still fits a 900px viewport. |
| @@ -0,0 +1,408 @@ | |||
| "use client" | |||
|
|
|||
| import { AnimatePresence, animate, motion, useMotionValue } from "motion/react" | |||
There was a problem hiding this comment.
React Doctor · react-doctor/use-lazy-motion (warning)
Importing "motion" ships about 30 kb of extra code and slows page load. Use "m" with LazyMotion instead.
Fix → Use import { LazyMotion, m } from "framer-motion" with domAnimation features. Saves about 30kb.
| setPhase("resting") | ||
| } | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
React Doctor · react-doctor/no-cascading-set-state (warning)
3 setState calls in one useEffect redraw your screen each time they run together.
Fix → Combine related updates in useReducer so one effect does not redraw the screen once per setState call.
| rest() | ||
| }, CLICK_MS) | ||
| } | ||
| rest() |
There was a problem hiding this comment.
React Doctor · react-doctor/no-adjust-state-on-prop-change (error)
This effect adjusts state after a prop changes, so users briefly see the stale value.
Fix → Adjust the state inline during render with a prev-prop comparison (if (prop !== prevProp) { setPrevProp(prop); setX(...); }), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
| onEngagement?: (engaged: boolean) => void | ||
| }) { | ||
| const [reduceMotion, setReduceMotion] = useState(false) | ||
| useEffect(() => { |
There was a problem hiding this comment.
React Doctor · react-doctor/rendering-hydration-no-flicker (warning)
This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning
Fix → Use useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) or add suppressHydrationWarning to the element
| }) { | ||
| const [reduceMotion, setReduceMotion] = useState(false) | ||
| useEffect(() => { | ||
| setReduceMotion( |
There was a problem hiding this comment.
React Doctor · react-hooks-js/set-state-in-effect (error)
This component misses React Compiler's automatic memoization & re-renders more than it should: Calling setState synchronously within an effect can trigger cascading renders. Rewrite the flagged code so the compiler can optimize it.
Fix → Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
- Update external systems with the latest state from React.
- Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
| /** Mounts the live game canvas as the tube's picture. */ | ||
| function GameScreen({ canvas }: { canvas: HTMLCanvasElement }) { | ||
| const host = useRef<HTMLDivElement>(null) | ||
| useEffect(() => { |
There was a problem hiding this comment.
React Doctor · react-hooks-js/immutability (error)
This component misses React Compiler's automatic memoization & re-renders more than it should: Cannot modify local variables after render completes. Rewrite the flagged code so the compiler can optimize it.
Fix → This argument is a function which may reassign or mutate canvas after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
| useEffect(() => { | ||
| const el = host.current | ||
| if (!el) return | ||
| canvas.style.width = "100%" |
There was a problem hiding this comment.
React Doctor · react-hooks-js/immutability (error)
This component misses React Compiler's automatic memoization & re-renders more than it should: This value cannot be modified. Rewrite the flagged code so the compiler can optimize it.
Fix → Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
| @@ -1,50 +1,12 @@ | |||
| import { createFileRoute, Link } from "@tanstack/react-router" | |||
| import { motion } from "motion/react" | |||
There was a problem hiding this comment.
React Doctor · react-doctor/use-lazy-motion (warning)
Importing "motion" ships about 30 kb of extra code and slows page load. Use "m" with LazyMotion instead.
Fix → Use import { LazyMotion, m } from "framer-motion" with domAnimation features. Saves about 30kb.
| const exitGame = useCallback(() => { | ||
| setGameActive(false) | ||
| setTransitioning(false) | ||
| const exitGame = useCallback(() => setGameActive(false), []) |
There was a problem hiding this comment.
React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)
This useCallback is dead weight, since React Compiler already caches every function here. Delete it.
Fix → Delete the useMemo / useCallback / memo call and use the plain value or component. React Compiler caches it for you.
| setGameActive(false) | ||
| setTransitioning(false) | ||
| const exitGame = useCallback(() => setGameActive(false), []) | ||
| const togglePower = useCallback(() => { |
There was a problem hiding this comment.
React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)
This useCallback is dead weight, since React Compiler already caches every function here. Delete it.
Fix → Delete the useMemo / useCallback / memo call and use the plain value or component. React Compiler caches it for you.
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (5)
apps/web/src/components/layout/landing/demo-screen.tsx (5)
19-29: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winDecorative JSDoc-style comment on a non-exported section.
This multi-line
/** ... */block sits above the tour constants section rather than an exported symbol — same category of issue flagged previously for the file header.As per coding guidelines, "Only use JSDoc on exported functions and types."
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` around lines 19 - 29, Remove the JSDoc-style block above the tour constants in demo-screen.tsx and replace it with a regular non-Javadoc comment if the note still needed. The comment is attached to a non-exported section rather than an exported symbol, so it should not use JSDoc syntax; check the nearby tour constants/landing demo setup and keep only plain comments there.Source: Coding guidelines
305-310: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
cursor: "none"still unconditional; reduced-motion users get no visible cursor.
cursor: "none"(line 309) is applied regardless ofreduceMotion, butTourCursor— the only rendered cursor — is gated by!reduceMotion(line 380). This exact issue was raised previously and marked addressed, but the code still shows the unconditional value.🩹 Suggested fix
style={{ ...themeVars(theme), background: "var(--d-bg)", - cursor: "none", + cursor: reduceMotion ? "default" : "none", }}Also applies to: 380-394
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` around lines 305 - 310, The demo screen still hides the system cursor unconditionally while TourCursor is only rendered when reduceMotion is false, so reduced-motion users end up with no visible cursor. Update the styling in demo-screen’s main container to apply cursor: "none" only when the same reduceMotion condition allows TourCursor to render, and keep the cursor visible otherwise; use the existing reduceMotion gate around TourCursor as the source of truth.
137-145: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winInline prop types on
TourCursorandDemoScreen.Both destructure inline object type literals for props rather than named interfaces — the same pattern previously flagged (and marked addressed) still appears here.
As per coding guidelines, "Never introduce a raw inline type to a component-level or route-level file."
Also applies to: 182-187
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` around lines 137 - 145, The component props for TourCursor and DemoScreen are still using raw inline object type literals, which violates the coding guideline against inline component-level types. Replace the inline prop annotations on these components with named interface or type aliases, and use those shared prop types in the TourCursor and DemoScreen declarations so the component signatures stay consistent with the rest of the file.Source: Coding guidelines
31-31: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winASCII divider comments reappear.
/* --- tour --- */and/* --- cursor --- */style dividers persist despite this being previously flagged and marked addressed.As per coding guidelines, "Do not use ASCII divider comments... Use whitespace and clear naming instead."
Also applies to: 106-106
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` at line 31, Remove the remaining ASCII divider comments in the landing demo-screen component and replace them with whitespace plus clear code structure. Update the affected spots in demo-screen.tsx around the tour and cursor sections, using the relevant symbols in that file (such as the component block containing those comments) to locate and delete the divider-style comments without changing behavior.Source: Coding guidelines
108-113: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
phaseparam typed as genericstring, losing type safety.
useDemoCursor'sphaseisstringeven though it only ever receives"resting" | "clicking"fromuseTour. This is the same type-safety gap flagged previously.✏️ Suggested fix
function useDemoCursor( - phase: string, + phase: "resting" | "clicking", next: DemoPage, page: DemoPage, interactive: boolean )🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` around lines 108 - 113, The useDemoCursor helper currently weakens type safety by typing phase as a generic string even though it only accepts the tour phases used by useTour. Update the phase parameter in useDemoCursor to use the same narrow phase union type coming from useTour (or a shared type alias) and make sure all call sites in demo-screen.tsx continue to pass the typed phase value without widening it to string.Source: Coding guidelines
🧹 Nitpick comments (3)
apps/web/src/components/layout/landing/cloud-eye.tsx (1)
221-234: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a named
CloudEyePropsinterface and drop the em dash from the prop comment.The props are declared as a raw inline type on the component, and the prop doc comment on Line 227 uses an em dash.
♻️ Suggested change
+interface CloudEyeProps { + size?: number + /** + * Sinks the cloud down and fades it out (e.g. while the demo owns the + * visitor's cursor); eased in the render loop so it drifts, not pops. + */ + hidden?: boolean +} + -export function CloudEye({ - size = 300, - hidden = false, -}: { - size?: number - /** Sinks the cloud down and fades it out (e.g. while the demo owns the - * visitor's cursor) — eased in the render loop so it drifts, not pops. */ - hidden?: boolean -}) { +export function CloudEye({ size = 300, hidden = false }: CloudEyeProps) {As per path instructions, "Never introduce a raw inline type to a component-level or route-level file" and "Always define props interface for components"; and per coding guidelines, comments should "contain no em dashes."
🤖 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 `@apps/web/src/components/layout/landing/cloud-eye.tsx` around lines 221 - 234, Extract the component props into a named CloudEyeProps interface instead of keeping the raw inline type on CloudEye, and update the CloudEye function signature to use that interface. Also revise the hidden prop JSDoc to remove the em dash from the comment while preserving the same meaning.Sources: Coding guidelines, Path instructions
apps/web/src/components/layout/landing/demo/analytics-scene.tsx (1)
143-182: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDead no-op ternary.
{i >= 3 ? null : null}always renders nothing regardless ofi. Looks like debug/leftover code from an unfinished idea (e.g. capping visible extra content to the first 3 events). Please remove it, or implement the intended behavior if there was one.🧹 Proposed fix
- {e.impact && ( - <span - className="shrink-0 font-mono text-[10px] tabular-nums" - style={{ - color: e.impact.good ? ACCENT.good : ACCENT.amber, - }} - > - {e.impact.label} - </span> - )} - {i >= 3 ? null : null} + {e.impact && ( + <span + className="shrink-0 font-mono text-[10px] tabular-nums" + style={{ + color: e.impact.good ? ACCENT.good : ACCENT.amber, + }} + > + {e.impact.label} + </span> + )}🤖 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 `@apps/web/src/components/layout/landing/demo/analytics-scene.tsx` around lines 143 - 182, The events renderer in analyticsScene has a dead no-op ternary inside the events.map loop that always returns null and does nothing. Remove the leftover conditional entirely, or if the intent was to limit extra items, implement that behavior explicitly in the analytics-scene.tsx event list rendering around the motion.div block.apps/web/src/components/layout/landing/demo/queue-scene.tsx (1)
30-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInline prop type on
QueueScene.Props are typed with an inline object literal rather than a named interface.
As per coding guidelines, "Never introduce a raw inline type to a component-level or route-level file."
🤖 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 `@apps/web/src/components/layout/landing/demo/queue-scene.tsx` around lines 30 - 40, The QueueScene component currently declares its props as an inline object type, which violates the guideline against raw inline types in component-level files. Extract the props shape into a named interface or type near QueueScene, then use that named symbol in the component signature while keeping the existing prop fields items, activeItemId, onOpenItem, and onOpenMetric unchanged.Source: Coding guidelines
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx`:
- Around line 247-248: The pointer scaling value is duplicated as a magic number
in the cursor math and the overlay transform, so the two can drift apart.
Introduce a single shared constant in demo-screen.tsx and use it in both the
cursor position calculations and the transform styling within the relevant
cursor/overlay logic (around the handlers that update cursor.x and cursor.y), so
any future scale change stays in sync.
- Around line 340-358: The scene wrapper in DemoScreen is using direct
initial/animate/exit target objects, which prevents the list-level stagger
variants in QueueScene, AutomodScene, AnalyticsScene, and IntegrationsScene from
coordinating their children. Update the motion.div wrapper to use a named
variant label that matches the scene variants, or move the stagger orchestration
onto the direct children so the existing listStagger behavior is actually
triggered. Keep the wrapper’s reduceMotion handling and route.page key intact
while wiring the variant connection.
In `@apps/web/src/components/layout/landing/demo/chrome.tsx`:
- Line 29: Remove the ASCII divider comments in the chrome component and replace
them with plain whitespace or clearer semantic naming as needed. Update the
section markers in the relevant spots within chrome.tsx, keeping the surrounding
structure readable without using divider-style comments like the current nav
separators.
- Around line 43-112: The interactive controls in TopNav are using plain span
elements with onClick, which makes them non-focusable and inaccessible by
keyboard. Replace these spans with the appropriate shared UI primitives from
apps/web/src/components/ui (or equivalent button/toggle components) in TopNav,
ensuring the nav items, theme toggle, and similar controls expose proper button
semantics, focus handling, and keyboard interaction through the existing
component APIs.
In `@apps/web/src/components/layout/landing/demo/data.ts`:
- Line 302: Remove the banned ASCII divider comments from the data file,
including the section markers near the analytics and integrations blocks.
Replace those divider-style comments with whitespace or rely on the existing
clear naming in the surrounding constants/functions, keeping the same code
structure while deleting the decorative separators.
In `@apps/web/src/components/layout/landing/demo/queue-scene.tsx`:
- Line 64: The comment in queue-scene.tsx uses an em dash and should be
simplified to match the comment style guidelines. Update the inline JSX comment
near the stat cards to remove the em dash and keep it short and sparse,
preserving the intent in the QueueScene component.
In `@apps/web/src/components/layout/landing/demo/side-panel.tsx`:
- Line 140: Remove the banned ASCII divider comments from the component,
specifically the divider-style markers around the moderation/rule detail
sections in side-panel.tsx. Keep the section separation using whitespace and
clear naming only, and check the related rule detail marker as well so both
occurrences are deleted from the same component.
- Around line 25-33: Replace the inline object prop types in the component
definitions with named TypeScript interfaces or type aliases scoped
appropriately for this file. Update PanelGrid, PanelShell, Field, AvatarDot,
ModerationDetail, and RuleDetail so their props are declared through reusable
named types instead of destructuring raw inline literals, and keep the
definitions grouped near the relevant component symbols to match the coding
guidelines.
- Around line 177-179: Remove the JSDoc-style block above ConfirmActions and
replace it with a plain non-JSDoc comment or inline section note if the
description should be kept. The issue is that ConfirmActions is an internal,
non-exported function, so it should not use JSDoc; update the comment in
side-panel.tsx near ConfirmActions to follow the doc-comment guideline while
leaving the function behavior unchanged.
---
Duplicate comments:
In `@apps/web/src/components/layout/landing/demo-screen.tsx`:
- Around line 19-29: Remove the JSDoc-style block above the tour constants in
demo-screen.tsx and replace it with a regular non-Javadoc comment if the note
still needed. The comment is attached to a non-exported section rather than an
exported symbol, so it should not use JSDoc syntax; check the nearby tour
constants/landing demo setup and keep only plain comments there.
- Around line 305-310: The demo screen still hides the system cursor
unconditionally while TourCursor is only rendered when reduceMotion is false, so
reduced-motion users end up with no visible cursor. Update the styling in
demo-screen’s main container to apply cursor: "none" only when the same
reduceMotion condition allows TourCursor to render, and keep the cursor visible
otherwise; use the existing reduceMotion gate around TourCursor as the source of
truth.
- Around line 137-145: The component props for TourCursor and DemoScreen are
still using raw inline object type literals, which violates the coding guideline
against inline component-level types. Replace the inline prop annotations on
these components with named interface or type aliases, and use those shared prop
types in the TourCursor and DemoScreen declarations so the component signatures
stay consistent with the rest of the file.
- Line 31: Remove the remaining ASCII divider comments in the landing
demo-screen component and replace them with whitespace plus clear code
structure. Update the affected spots in demo-screen.tsx around the tour and
cursor sections, using the relevant symbols in that file (such as the component
block containing those comments) to locate and delete the divider-style comments
without changing behavior.
- Around line 108-113: The useDemoCursor helper currently weakens type safety by
typing phase as a generic string even though it only accepts the tour phases
used by useTour. Update the phase parameter in useDemoCursor to use the same
narrow phase union type coming from useTour (or a shared type alias) and make
sure all call sites in demo-screen.tsx continue to pass the typed phase value
without widening it to string.
---
Nitpick comments:
In `@apps/web/src/components/layout/landing/cloud-eye.tsx`:
- Around line 221-234: Extract the component props into a named CloudEyeProps
interface instead of keeping the raw inline type on CloudEye, and update the
CloudEye function signature to use that interface. Also revise the hidden prop
JSDoc to remove the em dash from the comment while preserving the same meaning.
In `@apps/web/src/components/layout/landing/demo/analytics-scene.tsx`:
- Around line 143-182: The events renderer in analyticsScene has a dead no-op
ternary inside the events.map loop that always returns null and does nothing.
Remove the leftover conditional entirely, or if the intent was to limit extra
items, implement that behavior explicitly in the analytics-scene.tsx event list
rendering around the motion.div block.
In `@apps/web/src/components/layout/landing/demo/queue-scene.tsx`:
- Around line 30-40: The QueueScene component currently declares its props as an
inline object type, which violates the guideline against raw inline types in
component-level files. Extract the props shape into a named interface or type
near QueueScene, then use that named symbol in the component signature while
keeping the existing prop fields items, activeItemId, onOpenItem, and
onOpenMetric unchanged.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d3ba2a8d-34f5-46b4-978b-d0d07e5fa65b
📒 Files selected for processing (13)
apps/web/src/components/layout/landing/cloud-eye.tsxapps/web/src/components/layout/landing/demo-screen.tsxapps/web/src/components/layout/landing/demo/analytics-scene.tsxapps/web/src/components/layout/landing/demo/automod-scene.tsxapps/web/src/components/layout/landing/demo/chrome.tsxapps/web/src/components/layout/landing/demo/data.tsapps/web/src/components/layout/landing/demo/integrations-scene.tsxapps/web/src/components/layout/landing/demo/motion.tsapps/web/src/components/layout/landing/demo/queue-scene.tsxapps/web/src/components/layout/landing/demo/side-panel.tsxapps/web/src/components/layout/landing/demo/theme.tsapps/web/src/components/layout/landing/retro-computer.tsxapps/web/src/routes/index.tsx
✅ Files skipped from review due to trivial changes (1)
- apps/web/src/components/layout/landing/demo/motion.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/src/routes/index.tsx
- apps/web/src/components/layout/landing/retro-computer.tsx
| cursor.x.set((e.clientX - rect.left) / 0.6) | ||
| cursor.y.set((e.clientY - rect.top) / 0.6) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Scale factor 0.6 duplicated as a magic number.
The pointer-math divisor (/ 0.6) and the transform: scale(0.6) must always match, but they're hardcoded independently in two places. A future change to one without the other silently desyncs the cursor overlay from the real pointer.
♻️ Suggested fix
+const RENDER_SCALE = 0.6
+
...
- cursor.x.set((e.clientX - rect.left) / 0.6)
- cursor.y.set((e.clientY - rect.top) / 0.6)
+ cursor.x.set((e.clientX - rect.left) / RENDER_SCALE)
+ cursor.y.set((e.clientY - rect.top) / RENDER_SCALE)
...
- transform: "scale(0.6)",
+ transform: `scale(${RENDER_SCALE})`,Also applies to: 325-325
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` around lines 247 -
248, The pointer scaling value is duplicated as a magic number in the cursor
math and the overlay transform, so the two can drift apart. Introduce a single
shared constant in demo-screen.tsx and use it in both the cursor position
calculations and the transform styling within the relevant cursor/overlay logic
(around the handlers that update cursor.x and cursor.y), so any future scale
change stays in sync.
| <AnimatePresence mode="wait"> | ||
| <motion.div | ||
| key={route.page} | ||
| className="h-full min-h-0 overflow-hidden" | ||
| initial={reduceMotion ? { opacity: 0 } : { opacity: 0, y: 14 }} | ||
| animate={{ | ||
| opacity: 1, | ||
| y: 0, | ||
| transition: { staggerChildren: 0.07 }, | ||
| }} | ||
| exit={{ | ||
| opacity: 0, | ||
| y: reduceMotion ? 0 : -8, | ||
| transition: { duration: 0.15 }, | ||
| }} | ||
| > | ||
| {scene} | ||
| </motion.div> | ||
| </AnimatePresence> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does Framer Motion propagate variants to children when the parent's animate prop is a target object instead of a variant label string?
💡 Result:
In Framer Motion, variant propagation is fundamentally tied to the use of variant labels (strings) rather than direct target objects [1][2]. When a parent component's animate prop is set to a target object (e.g., animate={{ opacity: 1 }}), it does not propagate that state to its children as a variant label would [3][2]. Variant propagation relies on the parent and children sharing common variant label names defined in their respective variants props [3][4][2]. Because target objects do not represent a "named state" within a variants object, the propagation mechanism—which coordinates children by looking for matching keys—is effectively bypassed [3][2]. Key points regarding variant propagation: - Mechanism: Variants propagate down the tree when a parent uses a string label (e.g., animate="visible") [3][2]. Children with matching keys in their own variants objects will automatically transition to that state [3][4][2]. - Blocking Propagation: Propagation automatically stops at any child component that has its own animate prop defined, as this signifies that the child is now controlling its own animation state [5][6][7]. - Target Objects: When you provide a target object directly to the animate prop, you are defining a specific animation for that component only. It does not trigger the variant orchestration (such as staggerChildren or when transitions) that requires the parent and children to be linked via shared variant labels [6][8][2].
Citations:
- 1: https://github.com/motiondivision/motion/blob/cd531784/packages/motion-dom/src/node/types.ts
- 2: https://www.mintlify.com/motiondivision/motion/api/types/variants
- 3: https://www.mintlify.com/motiondivision/motion/concepts/variants
- 4: https://motion.mighty.guide/some-examples/24-variants-animation-propagation/
- 5: https://stackoverflow.com/questions/58980261/transition-when-doesnt-work-in-framer-motion
- 6: [BUG] staggerChildren doesn't work if you try to add 'animate' or 'initial' attr on children. motiondivision/motion#536
- 7: https://blog.maximeheckel.com/posts/advanced-animation-patterns-with-framer-motion/
- 8: https://motion.dev/tutorials/react-variants
🏁 Script executed:
sed -n '1,260p' apps/web/src/components/layout/landing/demo-screen.tsx && printf '\n---CUT---\n' && sed -n '260,420p' apps/web/src/components/layout/landing/demo-screen.tsx && printf '\n---SEARCH---\n' && rg -n "listStagger|variants=|AnimatePresence|motion\.div" apps/web/src/components/layout/landing/demo-screen.tsxRepository: bountydotnew/tripwire
Length of output: 12273
🏁 Script executed:
rg -n "listStagger|variants=|initial=|animate=|staggerChildren" apps/web/src/components/layout/landing/demo -g '*.tsx' && printf '\n---QUEUE---\n' && sed -n '1,260p' apps/web/src/components/layout/landing/demo/queue-scene.tsx && printf '\n---AUTOMOD---\n' && sed -n '1,260p' apps/web/src/components/layout/landing/demo/automod-scene.tsx && printf '\n---ANALYTICS---\n' && sed -n '1,260p' apps/web/src/components/layout/landing/demo/analytics-scene.tsx && printf '\n---INTEGRATIONS---\n' && sed -n '1,260p' apps/web/src/components/layout/landing/demo/integrations-scene.tsxRepository: bountydotnew/tripwire
Length of output: 28214
🌐 Web query:
Framer Motion animate target object staggerChildren variants root child propagation docs
💡 Result:
In Framer Motion, staggerChildren is an orchestration property used within variants to sequence the animations of child motion components [1]. For it to function correctly, both parent and child components must utilize the variants system, and propagation must remain uninterrupted [1][2]. Key Requirements for staggerChildren: 1. Use Variants: The parent motion component must define a variant object containing a transition property with staggerChildren [3][1]. 2. Propagation: Variants automatically propagate down the component tree [1]. However, this propagation stops at any child component that defines its own animate or initial props [2]. To ensure staggering works, remove animate, initial, and exit props from the child motion components and rely instead on the parent to pass the state through the variants [2][4]. 3. Matching Variant Names: Children must have a variants prop that includes keys matching the states defined in the parent's variant object (e.g., if the parent animates to "visible", the children should have a "visible" variant defined) [1]. Orchestration Properties: - staggerChildren: The duration (in seconds) to stagger the start of each child's animation [1]. - delayChildren: The time (in seconds) to wait before starting the animation of children [1]. - staggerDirection: Set to 1 (default) to stagger from the first child, or -1 to stagger from the last [1]. - when: Controls when children animate relative to the parent (e.g., "beforeChildren" or "afterChildren") [1]. Advanced Staggering: You can also use the stagger function directly within delayChildren for more granular control [3][5]. For example, transition: { delayChildren: stagger(0.1, { from: "last" }) } allows you to customize the stagger timing and direction dynamically [3][5]. If staggering is not working, the most common cause is the child components overriding the parent's animation state by defining their own animate or initial props, which effectively breaks the propagation chain [2][6].
Citations:
- 1: https://www.mintlify.com/motiondivision/motion/concepts/variants
- 2: https://stackoverflow.com/questions/78877044/stagger-children-with-framer-motion-where-the-child-is-a-custom-component
- 3: https://motion.dev/docs/stagger
- 4: https://stackoverflow.com/questions/77932057/stagger-children-framer-motion
- 5: https://motion.dev/tutorials/react-variants
- 6: https://stackoverflow.com/questions/74654059/react-framer-motion-why-are-my-child-elements-not-staggered
Use a variant label on the scene wrapper
QueueScene/AutomodScene/AnalyticsScene/IntegrationsScene rely on variants={listStagger} at their roots, but DemoScreen only passes target objects in animate/initial/exit. That leaves the scene-level stagger disconnected, so the child orchestration won’t run. Use a named variant on the wrapper or move the stagger onto the direct children.
🤖 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 `@apps/web/src/components/layout/landing/demo-screen.tsx` around lines 340 -
358, The scene wrapper in DemoScreen is using direct initial/animate/exit target
objects, which prevents the list-level stagger variants in QueueScene,
AutomodScene, AnalyticsScene, and IntegrationsScene from coordinating their
children. Update the motion.div wrapper to use a named variant label that
matches the scene variants, or move the stagger orchestration onto the direct
children so the existing listStagger behavior is actually triggered. Keep the
wrapper’s reduceMotion handling and route.page key intact while wiring the
variant connection.
|
|
||
| export type DemoPage = DemoRoute["page"] | ||
|
|
||
| /* ------------------------------------------------------------------ nav */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Remove ASCII divider comments.
Per coding guidelines, section dividers like /* ---- nav ---- */ should be replaced with whitespace/clear naming.
♻️ Proposed fix
-/* ------------------------------------------------------------------ nav */
-
export const NAV_ITEMS: {-/* ---------------------------------------------------------------- atoms */
-
export function SortToggle<T extends string>({-/* ------------------------------------------------------------ stat card */
-
export type DitherColor =Also applies to: 114-114, 286-286
🤖 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 `@apps/web/src/components/layout/landing/demo/chrome.tsx` at line 29, Remove
the ASCII divider comments in the chrome component and replace them with plain
whitespace or clearer semantic naming as needed. Update the section markers in
the relevant spots within chrome.tsx, keeping the surrounding structure readable
without using divider-style comments like the current nav separators.
Source: Coding guidelines
| export function TopNav({ | ||
| active, | ||
| theme, | ||
| onNavigate, | ||
| onToggleTheme, | ||
| }: { | ||
| active: DemoPage | ||
| theme: DemoTheme | ||
| onNavigate: (page: DemoPage) => void | ||
| onToggleTheme: () => void | ||
| }) { | ||
| return ( | ||
| <div className="flex items-center gap-3 px-3 py-2"> | ||
| <span | ||
| className="px-1 text-[13px] font-medium" | ||
| style={{ color: V.fg, fontFamily: '"Geist Pixel", monospace' }} | ||
| > | ||
| tripwire | ||
| </span> | ||
| <div className="flex items-center gap-0.5"> | ||
| {NAV_ITEMS.map((item) => ( | ||
| <span | ||
| key={item.page} | ||
| data-demo-nav={item.page} | ||
| onClick={() => onNavigate(item.page)} | ||
| className="flex h-7 items-center gap-1.5 rounded-md px-2.5 text-[12px] font-medium" | ||
| style={ | ||
| item.page === active | ||
| ? { background: V.surface0, color: V.fg } | ||
| : { color: V.fg2 } | ||
| } | ||
| > | ||
| <item.icon size={12} /> | ||
| {item.label} | ||
| {typeof item.count === "number" && ( | ||
| <span className="tabular-nums" style={{ color: V.fg2 }}> | ||
| {item.count} | ||
| </span> | ||
| )} | ||
| </span> | ||
| ))} | ||
| </div> | ||
| <div | ||
| className="ml-auto flex h-7 w-40 items-center gap-2 rounded-md px-2.5" | ||
| style={{ background: V.surface1 }} | ||
| > | ||
| <SearchIcon size={11} style={{ color: V.fg2 }} /> | ||
| <span className="text-[11px]" style={{ color: V.fg2 }}> | ||
| Search reports… | ||
| </span> | ||
| </div> | ||
| {/* theme toggle — scoped to the demo */} | ||
| <span | ||
| onClick={onToggleTheme} | ||
| className="flex size-7 items-center justify-center rounded-md" | ||
| style={{ color: V.fg2 }} | ||
| aria-label="Toggle demo theme" | ||
| > | ||
| {theme === "dark" ? <MoonIcon size={13} /> : <SunIcon size={13} />} | ||
| </span> | ||
| <span | ||
| className="size-6 rounded-full" | ||
| style={{ | ||
| border: `1px solid ${V.border}`, | ||
| background: "linear-gradient(135deg, #6d28d9, #1e293b)", | ||
| }} | ||
| /> | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Interactive controls use non-focusable <span> instead of a button primitive.
TopNav nav items, the theme toggle, SortToggle, FilterChip, and MiniSwitch all attach onClick to plain <span> elements with no tabIndex, role, or onKeyDown, so none of them are reachable or operable via keyboard. As per path instructions, "Never use raw html components... Always use their react counterparts defined in apps/web/src/components/ui" — these spans are standing in for buttons/toggles without any of the accessibility semantics that a shared button/toggle primitive would provide.
Also applies to: 117-147, 149-175, 213-235
🤖 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 `@apps/web/src/components/layout/landing/demo/chrome.tsx` around lines 43 -
112, The interactive controls in TopNav are using plain span elements with
onClick, which makes them non-focusable and inaccessible by keyboard. Replace
these spans with the appropriate shared UI primitives from
apps/web/src/components/ui (or equivalent button/toggle components) in TopNav,
ensuring the nav items, theme toggle, and similar controls expose proper button
semantics, focus handling, and keyboard interaction through the existing
component APIs.
Source: Coding guidelines
| }, | ||
| ] | ||
|
|
||
| /* ------------------------------------------------------------ analytics */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Remove ASCII divider comments.
/* ------------------------------------------------------------ analytics */ and /* ---------------------------------------------------------- integrations */ use the banned divider style.
As per coding guidelines, "Do not use ASCII divider comments (// ─── Section Name ───────). Use whitespace and clear naming instead."
✏️ Suggested cleanup
-/* ------------------------------------------------------------ analytics */
export const METRICS: Record<"moderation" | "automod", Metric[]> = {-/* ---------------------------------------------------------- integrations */
export type Repo = { name: string; owner: string; isPrivate: boolean }Also applies to: 430-430
🤖 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 `@apps/web/src/components/layout/landing/demo/data.ts` at line 302, Remove the
banned ASCII divider comments from the data file, including the section markers
near the analytics and integrations blocks. Replace those divider-style comments
with whitespace or rely on the existing clear naming in the surrounding
constants/functions, keeping the same code structure while deleting the
decorative separators.
Source: Coding guidelines
| /> | ||
| </motion.div> | ||
|
|
||
| {/* the four dither stat cards — each opens its analytics view */} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Em dash in comment.
{/* the four dither stat cards — each opens its analytics view */} uses an em dash.
As per coding guidelines, "Comments should be sparse, contain no em dashes or overly verbose language patterns."
✏️ Suggested fix
- {/* the four dither stat cards — each opens its analytics view */}
+ {/* stat cards open their analytics view */}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {/* the four dither stat cards — each opens its analytics view */} | |
| {/* stat cards open their analytics view */} |
🤖 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 `@apps/web/src/components/layout/landing/demo/queue-scene.tsx` at line 64, The
comment in queue-scene.tsx uses an em dash and should be simplified to match the
comment style guidelines. Update the inline JSX comment near the stat cards to
remove the em dash and keep it short and sparse, preserving the intent in the
QueueScene component.
Source: Coding guidelines
| export function PanelGrid({ | ||
| open, | ||
| main, | ||
| panel, | ||
| }: { | ||
| open: boolean | ||
| main: ReactNode | ||
| panel: ReactNode | ||
| }) { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Inline prop types throughout the file instead of named interfaces.
PanelGrid, PanelShell, Field, AvatarDot, ModerationDetail, and RuleDetail all destructure inline object type literals for props.
As per coding guidelines, "Never introduce a raw inline type to a component-level or route-level file. Keep it scoped to a generally existing file where similar types live" and "Type Safety First: TypeScript interfaces for all props, state, return types."
Also applies to: 66-78, 117-117, 128-128, 227-235, 310-318
🤖 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 `@apps/web/src/components/layout/landing/demo/side-panel.tsx` around lines 25 -
33, Replace the inline object prop types in the component definitions with named
TypeScript interfaces or type aliases scoped appropriately for this file. Update
PanelGrid, PanelShell, Field, AvatarDot, ModerationDetail, and RuleDetail so
their props are declared through reusable named types instead of destructuring
raw inline literals, and keep the definitions grouped near the relevant
component symbols to match the coding guidelines.
Source: Coding guidelines
| ) | ||
| } | ||
|
|
||
| /* --------------------------------------------------- moderation detail */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Remove ASCII divider comments.
/* --------------------------------------------------- moderation detail */ and /* --------------------------------------------------------- rule detail */ reintroduce the banned divider style.
As per coding guidelines, "Do not use ASCII divider comments (// ─── Section Name ───────). Use whitespace and clear naming instead."
Also applies to: 308-308
🤖 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 `@apps/web/src/components/layout/landing/demo/side-panel.tsx` at line 140,
Remove the banned ASCII divider comments from the component, specifically the
divider-style markers around the moderation/rule detail sections in
side-panel.tsx. Keep the section separation using whitespace and clear naming
only, and check the related rule detail marker as well so both occurrences are
deleted from the same component.
Source: Coding guidelines
| /** modkit's arm-then-confirm action bar: the armed button swells to fill the | ||
| * row while its siblings collapse away. Confirming resolves the item. */ | ||
| function ConfirmActions({ onResolve }: { onResolve: (a: Action) => void }) { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
JSDoc on a non-exported function.
ConfirmActions is not exported, but carries a JSDoc-style /** ... */ block.
As per coding guidelines, "Only use JSDoc on exported functions and types, not on decorative multi-line doc comments that describe sections."
✏️ Suggested cleanup
-/** modkit's arm-then-confirm action bar: the armed button swells to fill the
- * row while its siblings collapse away. Confirming resolves the item. */
+// arm-then-confirm action bar: armed button swells, siblings collapse
function ConfirmActions({ onResolve }: { onResolve: (a: Action) => void }) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** modkit's arm-then-confirm action bar: the armed button swells to fill the | |
| * row while its siblings collapse away. Confirming resolves the item. */ | |
| function ConfirmActions({ onResolve }: { onResolve: (a: Action) => void }) { | |
| // arm-then-confirm action bar: armed button swells, siblings collapse | |
| function ConfirmActions({ onResolve }: { onResolve: (a: Action) => void }) { |
🤖 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 `@apps/web/src/components/layout/landing/demo/side-panel.tsx` around lines 177
- 179, Remove the JSDoc-style block above ConfirmActions and replace it with a
plain non-JSDoc comment or inline section note if the description should be
kept. The issue is that ConfirmActions is an internal, non-exported function, so
it should not use JSDoc; update the comment in side-panel.tsx near
ConfirmActions to follow the doc-comment guideline while leaving the function
behavior unchanged.
Source: Coding guidelines
Adds a CSS-built late-80s cream desktop (Philips P3120 XT vibes) to the landing page, layered over the existing FaultyTerminal dither background:
press an arrow key to playcaption included)Everything is hard-edged plastic bevels — no glows, no blurs, no letter-spacing.
Summary by CodeRabbit