Skip to content

feat: vintage cream PC demo on the landing page#73

Open
ripgrim wants to merge 11 commits into
mainfrom
retro-homepage
Open

feat: vintage cream PC demo on the landing page#73
ripgrim wants to merge 11 commits into
mainfrom
retro-homepage

Conversation

@ripgrim

@ripgrim ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Adds a CSS-built late-80s cream desktop (Philips P3120 XT vibes) to the landing page, layered over the existing FaultyTerminal dither background:

  • 4:3 CRT monitor running a miniature demo of the new moderation dashboard — dither-kit sparkline stat cards, a flagged-PR queue, and a status line, with plain scanlines (no glow, no blur)
  • System unit with the red accent stripe, vent slats, twin floppy drives, and an orange power switch
  • Attached keyboard whose arrow cluster visually depresses with real arrow-key presses — the same keys that already flip the page into Space Invaders (press an arrow key to play caption included)
  • The terminal dither background persists; the PC just sits over it

Everything is hard-edged plastic bevels — no glows, no blurs, no letter-spacing.

Summary by CodeRabbit

  • New Features
    • Added a powered retro CRT system on the landing page with an animated demo tour (now spanning four scenes) and optional live gameplay inside the monitor.
    • Added a real-time on-screen keyboard that highlights physically pressed keys (including the arrow cluster).
    • Introduced an animated “tripwire eye” component with support for visually hiding it.
    • Added interactive demo views (queue, automod/analytics, integrations) with expandable detail panels.
  • Bug Fixes
    • Simplified activation: pressing an arrow key boots immediately when powered; powering off stops play.
    • Standardized the game display to a fixed 4:3 render surface.
  • Style
    • Refreshed landing header layout and contrast.

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.
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
tripwire Ready Ready Preview, Comment Jul 4, 2026 6:03am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new RetroComputer landing component with a CRT monitor, power control, keyboard, and optional game canvas mounting. It also adds demo scene components, shared demo chrome/theme/data, a side panel, and a rewritten DemoScreen tour flow. The landing route now uses powered state to show or hide the retro shell and starts the game on arrow-key press. The CloudEye component now renders a procedural WebGL cloud eye. The Space Invaders offscreen canvas uses fixed 800x600 dimensions, and the landing header plus Biome overrides were updated.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the landing-page retro PC demo added by this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

React Doctor found 17 issues in 4 files · 7 errors & 10 warnings · score 61 / 100 (Needs work) · vs main

Errors

10 warnings

src/components/layout/landing/demo-screen.tsx

  • ⚠️ L3 Full Framer Motion import use-lazy-motion
  • ⚠️ L68 Multiple setState calls in one effect no-cascading-set-state
  • ⚠️ L189 useEffect setState flashes on mount rendering-hydration-no-flicker
  • ⚠️ L213 Live state pushed to parent via effect no-pass-live-state-to-parent
  • ⚠️ L213 Parent kept in sync with a callback effect no-prop-callback-in-effect

src/components/layout/landing/retro-computer.tsx

  • ⚠️ L3 Full Framer Motion import use-lazy-motion

src/components/layout/landing/space-invaders.tsx

  • ⚠️ L196 Redundant manual memoization react-compiler-no-manual-memoization

src/routes/index.tsx

  • ⚠️ L2 Full Framer Motion import use-lazy-motion
  • ⚠️ L29 Redundant manual memoization react-compiler-no-manual-memoization
  • ⚠️ L30 Redundant manual memoization react-compiler-no-manual-memoization
⚠️ Warning: .github/workflows/react-doctor.yml is configured incorrectly. See below to fix.

React Doctor compares against main to report only the issues this pull request introduces. This run couldn't complete that comparison (usually a shallow CI checkout with no merge base), so it listed every issue in the changed files, including ones that already existed on main.

Add fetch-depth: 0 to the actions/checkout step in .github/workflows/react-doctor.yml so the checkout includes the history React Doctor needs:

 jobs:
   react-doctor:
     steps:
       - uses: actions/checkout@v5
+        with:
+          fetch-depth: 0

       - uses: millionco/react-doctor@v2

To silence this warning, set silence-missing-baseline-warning: true on the React Doctor action.

Reviewed by React Doctor for commit 72835ea. See inline comments for fixes.

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

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Round two, per feedback:

  • The game plays on the CRT now — arrow key boots Space Invaders inside the tube, replacing the dashboard demo; esc hands the machine back. Game canvas is a fixed 800×600 so it maps 1:1 onto the 4:3 glass. The terminal background stays put instead of doing the takeover.
  • Keyboard lies flat — perspective off the case seam with a thick front edge; the system reads as stacked on a desk.
  • Bigger machinemax-w-xlmax-w-3xl, and the idle demo now fills the whole tube (status line pinned to the bottom edge).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
apps/web/src/components/layout/landing/retro-computer.tsx (1)

180-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate scanline overlay markup between DemoScreen and GameScreen.

The identical repeating-linear-gradient scanline overlay is duplicated. Extract a small shared ScanlineOverlay component 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 both DemoScreen and GameScreen.

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 win

Em 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 win

Comments 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 to gameActive/transitioning was 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8d56e91 and 6c22834.

📒 Files selected for processing (3)
  • apps/web/src/components/layout/landing/retro-computer.tsx
  • apps/web/src/components/layout/landing/space-invaders.tsx
  • apps/web/src/routes/index.tsx

Comment on lines +6 to +14
/**
* 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.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Comment thread apps/web/src/components/layout/landing/retro-computer.tsx
Comment thread apps/web/src/components/layout/landing/retro-computer.tsx Outdated
Comment on lines +194 to +221
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>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Comment thread apps/web/src/components/layout/landing/retro-computer.tsx Outdated
…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
@ripgrim

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Round three:

  • Every key is pressable — the drawn board mirrors the physical one (letters, digits, shift/ctrl/alt/space/caps, arrows), with a blur reset so nothing sticks.
  • Win98 teal desktop (#008080) replaces the terminal shader.
  • Hero dead-centre, machine at the fold — only the monitor peeks up under the CTA; arrow key still boots Space Invaders on the visible tube (screenshot-verified).

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

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Round four:

  • Power-button boot — only the system unit waits at the bottom; pressing its plain moulded power button (which latches in, like real hardware — no glyphs) springs the monitor up out of the case, warms the picture in a beat later, and slides the keyboard out underneath. Powering off packs it all away and brings the hero back.
  • Bliss wallpaper vendored to public/wallpapers/win98.jpg, full-bleed with teal underneath while it loads.
  • Hardware realism — brand debossed into the monitor chin plus a metallic cm 1431 model plate and tube power light; drive bays are unlabelled slot + activity light + eject.
  • Contrast pass — dark header/hero text and solid black CTAs for the bright sky; removed the header's CRT-curvature tilt along with the terminal it compensated for.
  • Mobile — machine hidden, plain wallpaper + hero.

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.
@ripgrim

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Round five:

  • The demo is a demo — the CRT now runs a self-playing tour: a little cursor glides to the nav, clicks, and the page changes (home → insights → events), with the real app's chrome and palette, dither-chart entrances replaying on every insights visit, and staggered list loads. Reduced-motion gets plain crossfades.
  • The eye is a cloud ☁️ — the mouse-following eye returns as a WebGL cumulus over Bliss: fbm-domain-warped eye silhouette (billowy lobes, not a logo stamp), scattering-based lighting (white sunlit tops, gray-blue undersides, silver-lined fringe), cottony surface mottle, and a wind uniform from cursor velocity so it drags and stretches while following, then settles. The pupil still looks at your pointer. Hidden on mobile alongside the machine.

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.
@ripgrim

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

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.
@ripgrim

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
apps/web/src/components/layout/landing/cloud-eye.tsx (3)

322-324: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider 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 win

Trim 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

uResolution is 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 the uResolution entry in uniforms (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

📥 Commits

Reviewing files that changed from the base of the PR and between 6c22834 and 05ebbdb.

⛔ Files ignored due to path filters (1)
  • apps/web/public/wallpapers/win98.jpg is excluded by !**/*.jpg
📒 Files selected for processing (6)
  • apps/web/biome.json
  • apps/web/src/components/layout/landing/cloud-eye.tsx
  • apps/web/src/components/layout/landing/demo-screen.tsx
  • apps/web/src/components/layout/landing/header.tsx
  • apps/web/src/components/layout/landing/retro-computer.tsx
  • apps/web/src/routes/index.tsx

Comment on lines +7 to +15
/**
* 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.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment thread apps/web/src/components/layout/landing/demo-screen.tsx Outdated
Comment thread apps/web/src/components/layout/landing/demo-screen.tsx Outdated
Comment thread apps/web/src/components/layout/landing/demo-screen.tsx
Comment thread apps/web/src/routes/index.tsx
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.
@ripgrim

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

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).
@ripgrim

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Round nine: the demo is a working mini-app now. Two routes deeper, all screenshot-verified end-to-end:

  • Queue — Severity/Newest sort, the full reason chips (incl. NSFW), and rows that slide open the 328px detail panel (grid springs over like the real dashboard layout) with the arm-then-confirm Approve/Ban/Remove bar. Confirming resolves the item out of the queue.
  • Charts → Analytics — stat cards navigate with their source+metric; the big chart takes commit clicks (crosshair pins, the big number repoints), the events feed pulls the nearest event to the top, and the Show Metrics sheet lifts the page height exactly like gittoskill — sheet cards switch the focused metric. Moderation and automod metric sets both in.
  • Automod, in full — stat cards, three-way sort, category chips, live rule switches, and the rule-detail panel (pattern block, hit chart, FP-rate warning, recent matches with Confirm/False-positive workflow).
  • Integrations — account cards (real GitHub avatars), repo picker with working pagination and set-active.
  • Theme toggle, scoped to the glass — moon/sun in the demo topbar flips the tube between modkit's dark and light palettes (CSS vars on the demo root); the machine and Bliss outside don't budge.

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

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Round ten: demo wordmark now reads tripwire; the powered-up machine is a size bigger (max-w-4xl) so the tube reads clearly; and while the demo has your cursor the cloud eye sinks out of the sky (eased sink + fade in the shader) so nothing tugs at your attention outside the glass — it drifts back when the demo lets go. All three verified in screenshots.

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.
@ripgrim

ripgrim commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

setPhase("resting")
}

useEffect(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

rest()
}, CLICK_MS)
}
rest()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Docs

onEngagement?: (engaged: boolean) => void
}) {
const [reduceMotion, setReduceMotion] = useState(false)
useEffect(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Docs

}) {
const [reduceMotion, setReduceMotion] = useState(false)
useEffect(() => {
setReduceMotion(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Docs

/** Mounts the live game canvas as the tube's picture. */
function GameScreen({ canvas }: { canvas: HTMLCanvasElement }) {
const host = useRef<HTMLDivElement>(null)
useEffect(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

useEffect(() => {
const el = host.current
if (!el) return
canvas.style.width = "100%"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

@@ -1,50 +1,12 @@
import { createFileRoute, Link } from "@tanstack/react-router"
import { motion } from "motion/react"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

const exitGame = useCallback(() => {
setGameActive(false)
setTransitioning(false)
const exitGame = useCallback(() => setGameActive(false), [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

setGameActive(false)
setTransitioning(false)
const exitGame = useCallback(() => setGameActive(false), [])
const togglePower = useCallback(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Docs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (5)
apps/web/src/components/layout/landing/demo-screen.tsx (5)

19-29: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Decorative 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 of reduceMotion, but TourCursor — 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 win

Inline prop types on TourCursor and DemoScreen.

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 win

ASCII 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

phase param typed as generic string, losing type safety.

useDemoCursor's phase is string even though it only ever receives "resting" | "clicking" from useTour. 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 win

Extract a named CloudEyeProps interface 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 win

Dead no-op ternary.

{i >= 3 ? null : null} always renders nothing regardless of i. 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 win

Inline 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

📥 Commits

Reviewing files that changed from the base of the PR and between 05ebbdb and 72835ea.

📒 Files selected for processing (13)
  • apps/web/src/components/layout/landing/cloud-eye.tsx
  • apps/web/src/components/layout/landing/demo-screen.tsx
  • apps/web/src/components/layout/landing/demo/analytics-scene.tsx
  • apps/web/src/components/layout/landing/demo/automod-scene.tsx
  • apps/web/src/components/layout/landing/demo/chrome.tsx
  • apps/web/src/components/layout/landing/demo/data.ts
  • apps/web/src/components/layout/landing/demo/integrations-scene.tsx
  • apps/web/src/components/layout/landing/demo/motion.ts
  • apps/web/src/components/layout/landing/demo/queue-scene.tsx
  • apps/web/src/components/layout/landing/demo/side-panel.tsx
  • apps/web/src/components/layout/landing/demo/theme.ts
  • apps/web/src/components/layout/landing/retro-computer.tsx
  • apps/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

Comment on lines +247 to +248
cursor.x.set((e.clientX - rect.left) / 0.6)
cursor.y.set((e.clientY - rect.top) / 0.6)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +340 to +358
<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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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


🏁 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.tsx

Repository: 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.tsx

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


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 */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment on lines +43 to +112
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>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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 */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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 */}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
{/* 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

Comment on lines +25 to +33
export function PanelGrid({
open,
main,
panel,
}: {
open: boolean
main: ReactNode
panel: ReactNode
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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 */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment on lines +177 to +179
/** 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 }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
/** 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant