Skip to content

[in-progress] new home page design#2343

Open
stopachka wants to merge 29 commits intomainfrom
new-homepage-stopa-ideas
Open

[in-progress] new home page design#2343
stopachka wants to merge 29 commits intomainfrom
new-homepage-stopa-ideas

Conversation

@stopachka
Copy link
Contributor

@stopachka stopachka commented Mar 5, 2026

Geetting to a home page

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Switzer and Berk Mono fonts and theme tokens, updates Tailwind font/colors, introduces many new landing components/pages (hero, demos, canvas backgrounds, enterprise), replaces LandingFooter with a new Footer across pages, and adds interactive demo suites plus small utility hooks.

Changes

Cohort / File(s) Summary
Global styles & Tailwind
client/packages/components/src/style.css, client/www/styles/globals.css, client/packages/components/tailwind.config.cjs, client/www/tailwind.config.js
Adds Switzer and Berk Mono @font-face, CSS theme variables and landing-width utility; prepends Switzer to sans stack; introduces new color tokens.
Font registration
client/www/font/font.ts
Registers local Switzer font via Next.js localFont and exports switzer (CSS variable).
Canvas backgrounds & preview
client/www/components/home/AgentPathsBg.tsx, client/www/components/home/AgentPathsBgSoftCenter.tsx, client/www/components/home/ConstellationBg.tsx, client/www/components/home/RipplesBg.tsx, client/www/components/home/DotGridBg.tsx, client/www/components/home/HeroBackgroundPreviews.tsx
Adds multiple high‑DPI canvas backgrounds and a preview page with per‑variant rendering and pointer interactions.
Homepage layout & pages
client/www/components/home/HomePageLayout.tsx, client/www/pages/home/v2-*, client/www/pages/home/*
Adds HomePageLayout accepting a Background prop and multiple new pages wiring the new backgrounds and landing sections.
New landing primitives & navigation
client/www/components/new-landing/Section.tsx, client/www/components/new-landing/Button.tsx, client/www/components/new-landing/AnimateIn.tsx, client/www/components/new-landing/Header.tsx, client/www/components/marketingUi.tsx
Introduces Section/SectionHeader, Button, AnimateIn, responsive Header with mobile menu, LogoType and active-link logic; marketingUi refactor (logo behavior, SectionWide removal).
Footer & footer migration
client/www/components/new-landing/Footer.tsx, various client/www/pages/*
Adds new Footer component and replaces prior LandingFooter usages across many pages (essays, examples, hiring, pricing, privacy, recipes, status, tutorial, intern, etc.).
Core landing sections & hero
client/www/components/new-landing/Hero.tsx, client/www/components/new-landing/BuiltForAI.tsx, client/www/components/new-landing/BatteriesForAI.tsx, client/www/components/new-landing/SocialProof.tsx, client/www/components/new-landing/WallOfLove.tsx, client/www/components/new-landing/FinalCTA.tsx, client/www/components/new-landing/FirebaseTestimonial.tsx, client/www/components/new-landing/StartupShowcase.tsx
Adds Hero (video/demo), BuiltForAI, BatteriesForAI, SocialProof, WallOfLove, FinalCTA, FirebaseTestimonial, and StartupShowcase components.
Large interactive demo suites
client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx, client/www/components/new-landing/OfflineDemoIdeas.tsx, client/www/components/new-landing/SyncDemoIdeas.tsx, client/www/components/new-landing/PaymentsDemoIdeas.tsx, client/www/components/new-landing/PaymentsIdea_*.tsx
Adds extensive interactive demo modules for instant updates, offline-first flows, sync/collaboration patterns, and multiple payments/revenue demo variants (checkout, dashboard, pricing builder, journey).
Sync relations demos & engine
client/www/components/new-landing/SyncEngine.tsx, client/www/components/new-landing/SyncRelations*.tsx, client/www/components/new-landing/SyncRelationsAppDemo.tsx
Adds SyncEngine and multiple SyncRelations variants (V1–V5) plus AppDemo wiring demonstrating relation/query UIs and animated code previews.
Utilities & small components
client/www/lib/hooks/useInView.tsx, client/www/components/new-landing/icons.tsx, client/www/components/CoolBackground.tsx
Adds useInView hook (IntersectionObserver), GitHub/X SVG icons, and a simple CoolBackground wrapper.
Pages: new & replaced landing
client/www/pages/index.tsx, client/www/pages/about.tsx, client/www/pages/enterprise.tsx, many new demo/marketing pages
Replaces old landing with Landing2026, adds many demo and marketing pages (about, enterprise, startup-showcase, instant/sync/offline/payments demos, hero-background previews), and wires pages to new components.
Minor presentational tweaks
client/www/components/dash/Auth.tsx, client/www/components/dash/Billing.tsx, client/www/pages/dash/index.tsx, client/www/pages/document.tsx
Small class changes for badges/titles, remove gray utility class, apply switzer.className to Document body, and tighten getInitialProps typing.

Sequence Diagram(s)

(omitted — changes are primarily UI/component additions and pages; no multi-component runtime control flow that requires a sequence diagram)

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • [WWW] Add product pages #2339: Overlaps edits to client/www/components/marketingUi.tsx (navigation/logo behavior) and may conflict with this PR's navigation refactor.

Suggested reviewers

  • nezaj
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch new-homepage-stopa-ideas

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
client/www/pages/index.tsx (1)

129-132: ⚠️ Potential issue | 🟠 Major

Add a title to the preview iframe.

The iframe needs a descriptive title for accessibility.

Proposed fix
               <iframe
                 className="flex-1"
+                title={`Recipe preview: ${pathName}`}
+                loading="lazy"
                 src={'/recipes/' + pathName + '?__appId=' + appId}
               />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/index.tsx` around lines 129 - 132, The preview iframe in
index.tsx is missing an accessible title; update the <iframe> element (the one
with className="flex-1" and src={'/recipes/' + pathName + '?__appId=' + appId})
to include a descriptive title attribute (for example "Recipe preview" or one
constructed from pathName/appId) so screen readers can identify the frame;
ensure the title is concise and meaningful.
🟠 Major comments (19)
client/www/components/new-landing/PaymentsIdea_PricingBuilder.tsx-70-81 (1)

70-81: ⚠️ Potential issue | 🟠 Major

Add proper switch semantics for the toggle control.

On Line 70, this control is visually a switch but has no switch semantics/state for assistive tech. Add role="switch" + aria-checked (and an accessible label).

Suggested fix
 function Toggle({
   checked,
   onChange,
   color,
+  label,
 }: {
   checked: boolean;
   onChange: () => void;
   color: string;
+  label: string;
 }) {
   return (
     <button
+      type="button"
       onClick={onChange}
+      role="switch"
+      aria-checked={checked}
+      aria-label={label}
       className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors ${
         checked ? color : 'bg-gray-200'
       }`}
     >
               <Toggle
                 checked={enabled[m.key]}
                 onChange={() => toggleModel(m.key)}
+                label={m.label}
                 color={
                   m.key === 'one-time'
                     ? 'bg-blue-500'
                     : m.key === 'subscription'
                       ? 'bg-purple-500'
                       : 'bg-green-500'
                 }
               />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_PricingBuilder.tsx` around
lines 70 - 81, The toggle button lacks switch semantics for assistive tech;
update the button element in PaymentsIdea_PricingBuilder (the element using
onClick={onChange} and reading checked/color) to include role="switch",
aria-checked={checked}, and an accessible label via aria-label or
aria-labelledby (e.g., aria-label describing the toggle purpose or point it to a
visible label). Keep the existing onClick/onChange behavior and visual classes
intact so keyboard and mouse interactions continue to work.
client/www/components/new-landing/PaymentsIdea_Journey.tsx-232-233 (1)

232-233: ⚠️ Potential issue | 🟠 Major

Track and clear all per-step timers, not just one.

Line 258-Line 265 schedules multiple line-reveal timers, but only the completion timer is tracked/cleared (Line 269, Line 279, Line 287). Reset/unmount/interrupted playback can leave stale callbacks that mutate playingLineIndex out of sequence.

Proposed fix
-  const timerRef = useRef<ReturnType<typeof setTimeout>>();
+  const timerRefs = useRef<ReturnType<typeof setTimeout>[]>([]);
+
+  const clearTimers = useCallback(() => {
+    timerRefs.current.forEach(clearTimeout);
+    timerRefs.current = [];
+  }, []);

   const playStep = useCallback(
     (stepId: number) => {
       const status = getStepStatus(stepId);
       if (status === 'locked' || status === 'playing') return;

-      // Clear any existing timers
-      if (timerRef.current) clearTimeout(timerRef.current);
+      clearTimers();

       setActiveStep(stepId);
       setPlayingLineIndex(-1);

       // Reveal lines one by one
       const step = steps[stepId];
       step.resultLines.forEach((_, i) => {
-        setTimeout(
+        const lineTimer = setTimeout(
           () => {
             setPlayingLineIndex(i);
           },
           400 + i * 500,
         );
+        timerRefs.current.push(lineTimer);
       });

       // Mark complete after all lines shown
       const totalTime = 400 + step.resultLines.length * 500 + 300;
-      timerRef.current = setTimeout(() => {
+      const doneTimer = setTimeout(() => {
         setCompletedSteps((prev) => new Set([...prev, stepId]));
         setActiveStep(null);
         setPlayingLineIndex(-1);
       }, totalTime);
+      timerRefs.current.push(doneTimer);
     },
-    [getStepStatus],
+    [clearTimers, getStepStatus],
   );

   const handleReset = useCallback(() => {
-    if (timerRef.current) clearTimeout(timerRef.current);
+    clearTimers();
     setCompletedSteps(new Set());
     setActiveStep(null);
     setPlayingLineIndex(-1);
-  }, []);
+  }, [clearTimers]);

   useEffect(() => {
     return () => {
-      if (timerRef.current) clearTimeout(timerRef.current);
+      clearTimers();
     };
-  }, []);
+  }, [clearTimers]);

Also applies to: 250-273, 278-289

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_Journey.tsx` around lines 232
- 233, The code currently stores only one timer in timerRef (const timerRef =
useRef<ReturnType<typeof setTimeout>>()) while multiple per-line reveal timers
are scheduled and only the final timer is cleared, which can leave stale
callbacks mutating playingLineIndex out of order; change timerRef to hold an
array (e.g., timerRefs: useRef<number[]>) and push each setTimeout id when you
schedule per-step timers (the places that set timers around
playingLineIndex/line-reveal logic), then on reset/unmount/interruption iterate
over timerRefs.current and clearTimeout each id and reset the array; ensure
every place that previously overwrote timerRef now appends to the array and that
you clear all timers before starting a new playback and in the component
cleanup.
client/www/components/new-landing/PaymentsIdea_Journey.tsx-343-354 (1)

343-354: ⚠️ Potential issue | 🟠 Major

Add an accessible name for each icon-only step button.

Line 343 renders icon-only controls; the text labels are outside the button, so assistive tech may announce unnamed buttons.

Proposed fix
                 <motion.button
                   onClick={() => playStep(step.id)}
                   disabled={!isClickable}
+                  aria-label={`${step.label} ${step.sublabel} (${step.price})`}
                   className={`relative flex h-10 w-10 items-center justify-center rounded-full border-2 transition-colors ${
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_Journey.tsx` around lines 343
- 354, The icon-only step button rendered by motion.button (onClick={() =>
playStep(step.id)}, disabled={!isClickable}) lacks an accessible name; add an
accessible label by including either an aria-label (e.g., aria-label={`Step
${step.id}: ${step.title || step.label}`}) or aria-labelledby that points to the
visible text label element so screen readers announce the button; ensure the
label uses step.title/step.label or a localized string and keep the existing
props (status, step.textColor, step.bgDone) intact.
client/www/components/new-landing/SyncDemoIdeas.tsx-444-448 (1)

444-448: ⚠️ Potential issue | 🟠 Major

Move nondeterministic state initialization to useEffect to prevent hydration mismatch.

Lines 444–448 initialize state with Math.random() inside the useState callback. Since this page is server-rendered and the component uses 'use client', the server and client compute different random values, causing a hydration mismatch.

Proposed fix
-  const [counts, setCounts] = useState<Record<string, number>>(() =>
-    Object.fromEntries(
-      REACTIONS.map((r) => [r, Math.floor(Math.random() * 5) + 1]),
-    ),
-  );
+  const [counts, setCounts] = useState<Record<string, number>>(
+    Object.fromEntries(REACTIONS.map((r) => [r, 1])),
+  );
+
+  useEffect(() => {
+    setCounts(
+      Object.fromEntries(
+        REACTIONS.map((r) => [r, Math.floor(Math.random() * 5) + 1]),
+      ),
+    );
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/SyncDemoIdeas.tsx` around lines 444 - 448,
State is being initialized nondeterministically via Math.random inside the
useState initializer (counts, setCounts with REACTIONS) which causes SSR/client
hydration mismatches; change the initial state to a deterministic default (e.g.,
zeros or a fixed map) and move the random population into a client-only
useEffect that runs once and calls setCounts to generate the randomized counts
from REACTIONS, ensuring the component first renders the same on server and
client then updates on mount.
client/www/pages/payments-demo-ideas.tsx-33-45 (1)

33-45: ⚠️ Potential issue | 🟠 Major

Make the demo grid responsive; current 3-column layout is mobile-hostile.

This forces desktop layout on all breakpoints and can break readability/overflow on phones.

Proposed fix
-              <div className="grid grid-cols-3 items-center gap-7">
-                <div className="col-span-1">
+              <div className="grid grid-cols-1 items-center gap-7 lg:grid-cols-3">
+                <div className="lg:col-span-1">
                   <h3 className="text-2xl font-semibold sm:text-3xl">
                     Payments
                   </h3>
@@
-                <div className="col-span-2">
+                <div className="lg:col-span-2">
                   <div className="bg-radial from-white to-[`#FFF9F4`] px-6 py-6">
                     <Demo />
                   </div>
                 </div>
               </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/payments-demo-ideas.tsx` around lines 33 - 45, The 3-column
grid "grid grid-cols-3 items-center gap-7" is forcing a desktop layout on all
breakpoints; change the grid and column span classes to be responsive (e.g., use
grid-cols-1 at base, then sm:grid-cols-2, lg:grid-cols-3) and update the child
spans (replace static "col-span-1" and "col-span-2" with responsive variants
like "sm:col-span-1 lg:col-span-1" and "sm:col-span-2 lg:col-span-2" or
"lg:col-span-2" as needed) so the Payments block stacks on mobile and expands on
larger screens (adjust padding/margins on the inner "bg-radial ..." container if
needed for small screens).
client/www/components/new-landing/PaymentsDemoIdeas.tsx-102-134 (1)

102-134: ⚠️ Potential issue | 🟠 Major

Clear pending action timeout on reset/unmount to prevent stale state updates.

A pending timer from handleAction can still fire after Reset, restoring stale result/state unexpectedly.

Proposed fix
 export function CreditSystemDemo() {
   const [credits, setCredits] = useState(50);
   const [loading, setLoading] = useState(false);
   const [activeAction, setActiveAction] = useState<string | null>(null);
+  const actionTimeoutRef = useRef<number | null>(null);
   const [result, setResult] = useState<{
     label: string;
     text: string;
   } | null>(null);
   const animatedCredits = useAnimatedNumber(credits);

   const handleAction = useCallback(
     (action: (typeof creditActions)[number]) => {
       if (loading || credits < action.cost) return;

       setLoading(true);
       setActiveAction(action.label);
       setResult(null);
       setCredits((c) => c - action.cost);

-      setTimeout(() => {
+      actionTimeoutRef.current = window.setTimeout(() => {
         setLoading(false);
         setActiveAction(null);
         setResult({ label: action.label, text: action.result });
       }, 500);
     },
     [loading, credits],
   );

   const handleReset = useCallback(() => {
+    if (actionTimeoutRef.current !== null) {
+      clearTimeout(actionTimeoutRef.current);
+      actionTimeoutRef.current = null;
+    }
     setCredits(50);
     setResult(null);
     setLoading(false);
     setActiveAction(null);
   }, []);
+
+  useEffect(() => {
+    return () => {
+      if (actionTimeoutRef.current !== null) {
+        clearTimeout(actionTimeoutRef.current);
+      }
+    };
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsDemoIdeas.tsx` around lines 102 -
134, handleAction currently starts a setTimeout that updates state later but its
timer ID isn't tracked or cleared, causing stale updates after handleReset or
component unmount; change handleAction to store the timer ID (e.g., in a ref
like actionTimerRef) when calling setTimeout, clear that timer in handleReset
before resetting state, and add a useEffect cleanup that clears the timer on
unmount; update references to the timer ref in handleAction, handleReset, and
the cleanup effect so no stale setResult/setLoading/setActiveAction runs after
reset/unmount.
client/www/components/new-landing/Footer.tsx-18-18 (1)

18-18: ⚠️ Potential issue | 🟠 Major

Fix the contact URL scheme.

founders:hello@instantdb.com won’t open a mail client. Use mailto:hello@instantdb.com.

Proposed fix
-    { href: 'founders:hello@instantdb.com', label: 'Contact' },
+    { href: 'mailto:hello@instantdb.com', label: 'Contact' },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/Footer.tsx` at line 18, Update the contact
link in Footer.tsx so the href uses a mailto scheme instead of the incorrect
custom scheme; locate the link object where href is
'founders:hello@instantdb.com' (in the array entry with label 'Contact') and
change it to 'mailto:hello@instantdb.com' so clicking opens the user's mail
client.
client/www/components/new-landing/Header.tsx-64-68 (1)

64-68: ⚠️ Potential issue | 🟠 Major

Avoid nesting <button> inside <a> for the CTA.

The Button component renders a <button> element, and Next.js Link renders an <a> element. Nesting them creates invalid HTML (<a><button>...</button></a>) and causes keyboard/screen-reader interaction issues. Replace with either a styled Link without the Button wrapper or use Button with onClick navigation.

This pattern appears at lines 64-68 and 131-135.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/Header.tsx` around lines 64 - 68, The CTA
currently nests the Button component (which renders a <button>) inside Next.js
Link (which renders an <a>), producing invalid HTML; update the two occurrences
(around the Link href="/dash" wrapping Button at lines ~64-68 and ~131-135) by
choosing one fix: either replace the Link+Button wrapper with a styled Link that
has the Button's classes (remove Button and apply its className to Link so the
anchor looks like a button), or keep Button and remove Link—make Button perform
navigation via router.push('/dash') (use Next's useRouter and an onClick
handler). Ensure you only use one element (anchor or button) for each CTA and
preserve the "Get a DB" label and "hidden sm:inline-flex" styling.
client/www/pages/about.tsx-66-70 (1)

66-70: ⚠️ Potential issue | 🟠 Major

Missing <Head> component for SEO metadata.

Unlike other pages in this PR (hiring, status, examples), this page doesn't include a <Head> component with title, description, and og:image metadata. This will impact SEO and social sharing.

Proposed fix
+import Head from 'next/head';
 import { MainNav } from '@/components/marketingUi';
 import { Footer } from '@/components/new-landing/Footer';
+import * as og from '@/lib/og';
 
 // ... existing code ...

 export default function AboutPage() {
   return (
     <div className="flex min-h-screen flex-col">
+      <Head>
+        <title>About | InstantDB</title>
+        <meta name="description" content="Building the database for the AI era" />
+        <meta
+          key="og:image"
+          property="og:image"
+          content={og.url({ title: 'About InstantDB', section: 'about' })}
+        />
+      </Head>
       <MainNav />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/about.tsx` around lines 66 - 70, AboutPage is missing the
Head metadata component; add a <Head> element at the top of the AboutPage render
(inside the AboutPage function before the root div) that sets a descriptive
<title>, a meta name="description" with the page summary, and Open Graph tags
(og:title, og:description, og:image) matching other pages (e.g.,
hiring/status/examples) so SEO and social sharing are consistent; update
AboutPage to import Head from "next/head" if not already present and reuse the
same og:image URL and wording style used by the other pages for consistency.
client/www/components/marketingUi.tsx-263-266 (1)

263-266: ⚠️ Potential issue | 🟠 Major

Add accessible labels to icon-only menu buttons.

The open/close controls are icon-only and currently expose ambiguous “button” announcements to assistive tech.

Proposed fix
       <button
+        type="button"
+        aria-label="Open navigation menu"
         className="ml-auto min-[60rem]:hidden"
         onClick={() => setIsOpen(true)}
       >
         <Bars3Icon height={'1em'} />
       </button>
@@
-          <button className="z-50 mt-0.5" onClick={() => setIsOpen(false)}>
+          <button
+            type="button"
+            aria-label="Close navigation menu"
+            className="z-50 mt-0.5"
+            onClick={() => setIsOpen(false)}
+          >
             <XMarkIcon height="1em" />
           </button>

Also applies to: 291-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/marketingUi.tsx` around lines 263 - 266, The icon-only
menu buttons (the button using setIsOpen(true) and the corresponding close
button) lack accessible labels; update these buttons to include clear aria-label
attributes (e.g., aria-label="Open menu" for the open button and
aria-label="Close menu" for the close button) and where state is relevant add
aria-expanded or toggle the label based on the isOpen state; locate the buttons
referenced by setIsOpen and the close handler in marketingUi.tsx (also the other
instance around the lines noted) and add the appropriate aria-labels or visually
hidden text to make them screen-reader friendly.
client/www/components/marketingUi.tsx-253-255 (1)

253-255: ⚠️ Potential issue | 🟠 Major

Restore body scroll state on cleanup for the mobile menu.

The current effect can leave document.body.style.overflow in a bad state after unmount/navigation while the menu is open.

Proposed fix
   useEffect(() => {
-    document.body.style.overflow = isOpen ? 'hidden' : 'auto';
+    if (!isOpen) return;
+    const previousOverflow = document.body.style.overflow;
+    document.body.style.overflow = 'hidden';
+    return () => {
+      document.body.style.overflow = previousOverflow;
+    };
   }, [isOpen]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/marketingUi.tsx` around lines 253 - 255, The useEffect
that mutates document.body.style.overflow for the mobile menu (useEffect with
dependency isOpen) should save the previous overflow value, set overflow to
'hidden' when isOpen and 'auto' otherwise, and restore the saved previous value
in the effect's cleanup to avoid leaving a bad state after unmount/navigation;
update the effect surrounding document.body.style.overflow in the component
(useEffect that references isOpen) to capture prevOverflow at start, apply the
new value, and return a cleanup that restores prevOverflow (also guard with
typeof document !== "undefined" if necessary).
client/www/components/new-landing/SyncRelationsAppDemo.tsx-179-187 (1)

179-187: ⚠️ Potential issue | 🟠 Major

Make item rows keyboard-accessible.

The row on Line 179 is mouse-clickable only; keyboard users can’t select an item. Add keyboard semantics (role, tabIndex, onKeyDown) to preserve accessibility.

Proposed fix
                 <div
                   key={item.id}
                   className={`flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-2 transition-colors ${
                     activeItemId === item.id
                       ? 'border border-orange-200 bg-orange-50'
                       : 'hover:bg-gray-50'
                   }`}
+                  role="button"
+                  tabIndex={0}
                   onClick={() => onSelectItem(item.id)}
+                  onKeyDown={(e) => {
+                    if (e.key === 'Enter' || e.key === ' ') {
+                      e.preventDefault();
+                      onSelectItem(item.id);
+                    }
+                  }}
                 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/SyncRelationsAppDemo.tsx` around lines 179
- 187, The item row is clickable but not keyboard-accessible; update the element
in SyncRelationsAppDemo that renders each row (the div using key={item.id} and
onClick={() => onSelectItem(item.id)}) to include accessibility attributes: add
role="button", tabIndex={0}, and an onKeyDown handler that listens for Enter and
Space and calls onSelectItem(item.id) (preventDefault for Space). Keep existing
activeItemId class logic and ensure the handler references the same onSelectItem
and item.id symbols.
client/www/components/new-landing/SyncRelationsAppDemo.tsx-188-198 (1)

188-198: ⚠️ Potential issue | 🟠 Major

Add an accessible label to the item toggle button.

The icon-only button on Line 188 has no accessible name, so screen readers announce an ambiguous control.

Proposed fix
                   <button
+                    type="button"
+                    aria-label={
+                      item.done
+                        ? `Mark "${item.text}" as not done`
+                        : `Mark "${item.text}" as done`
+                    }
                     onClick={(e) => {
                       e.stopPropagation();
                       onToggleItem(item.id);
                     }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/SyncRelationsAppDemo.tsx` around lines 188
- 198, The toggle button inside SyncRelationsAppDemo (the <button> with onClick
calling onToggleItem(item.id)) lacks an accessible name; update that button to
include an appropriate accessible label (e.g., add aria-label or aria-labelledby
that uses item.title or a localized string like "Toggle item {item.id}" and
consider aria-pressed to reflect item.done) so screen readers can identify the
control; ensure the label conveys the action and state (checked/pressed) and
keep the existing onClick and stopPropagation behavior unchanged.
client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx-445-457 (1)

445-457: ⚠️ Potential issue | 🟠 Major

Prevent drag-over reorder thrashing and write-count inflation.

Line 452 and Line 453 reorder on every dragover. Hovering the same target repeatedly can flip positions back and forth and spike writeCount, causing jitter and unnecessary rerenders.

💡 Suggested fix
 const [dragging, setDragging] = useState<number | null>(null);
+const lastOverTargetRef = useRef<number | null>(null);
 const [writeCount, setWriteCount] = useState(0);

-const dragStart = (id: number) => setDragging(id);
+const dragStart = (id: number) => {
+  setDragging(id);
+  lastOverTargetRef.current = null;
+};

 const dragOver = (e: React.DragEvent, targetId: number) => {
   e.preventDefault();
-  if (dragging === null || dragging === targetId) return;
+  if (
+    dragging === null ||
+    dragging === targetId ||
+    lastOverTargetRef.current === targetId
+  ) {
+    return;
+  }
   setItems((prev) => {
     const arr = [...prev];
     const fromIdx = arr.findIndex((i) => i.id === dragging);
     const toIdx = arr.findIndex((i) => i.id === targetId);
+    if (fromIdx < 0 || toIdx < 0 || fromIdx === toIdx) return prev;
     const [item] = arr.splice(fromIdx, 1);
     arr.splice(toIdx, 0, item);
+    lastOverTargetRef.current = targetId;
     return arr;
   });
   setWriteCount((c) => c + 1);
 };

-const dragEnd = () => setDragging(null);
+const dragEnd = () => {
+  setDragging(null);
+  lastOverTargetRef.current = null;
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx` around lines
445 - 457, The dragOver handler currently reorders on every dragover causing
thrash and inflating writeCount; modify dragOver (and its use of dragging,
targetId, setItems, setWriteCount) to first compute fromIdx and toIdx, validate
they are not -1 and return early if fromIdx === toIdx (or dragging ===
targetId), and only call setItems and setWriteCount when the indices differ so
you avoid unnecessary reorders and writeCount increments.
client/www/components/new-landing/FinalCTA.tsx-11-15 (1)

11-15: ⚠️ Potential issue | 🟠 Major

Handle clipboard failures before showing copied state.

Line 12 is async, but Line 13 sets success immediately. If clipboard access is denied/unavailable, UI still shows success and can produce an unhandled rejection.

🛠️ Proposed fix
-  const copy = () => {
-    navigator.clipboard.writeText(text);
-    setCopied(true);
-    setTimeout(() => setCopied(false), 2000);
-  };
+  const copy = async () => {
+    try {
+      await navigator.clipboard.writeText(text);
+      setCopied(true);
+      window.setTimeout(() => setCopied(false), 2000);
+    } catch {
+      setCopied(false);
+    }
+  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/FinalCTA.tsx` around lines 11 - 15, The
copy function currently calls navigator.clipboard.writeText(text) without
awaiting or handling failures and immediately sets setCopied(true); update the
copy handler (function copy) to await navigator.clipboard.writeText(text) inside
a try/catch, only call setCopied(true) on successful await, handle errors in the
catch (e.g., log or show an error state) and ensure the timeout to reset
setCopied(false) still runs only on success; keep references to
navigator.clipboard.writeText, copy, setCopied and the existing timeout logic so
the UI only shows the "copied" state when the clipboard operation succeeds and
avoids unhandled rejections.
client/www/components/new-landing/PaymentsIdea_Checkout.tsx-190-198 (1)

190-198: ⚠️ Potential issue | 🟠 Major

Add an accessible name to the requests slider.

The range input needs a label (label/aria-label) so assistive tech can identify its purpose.

Proposed fix
         <input
           type="range"
+          aria-label="Estimated requests per month"
           min={1000}
           max={50000}
           step={1000}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_Checkout.tsx` around lines 190
- 198, The range input for selecting requests in PaymentsIdea_Checkout.tsx lacks
an accessible name; update the <input> (the slider that uses value={requests}
and onChange={(e) => setRequests(Number(e.target.value))}) to include an
accessible label — either add a visible <label
htmlFor="requests-slider">Requests per month</label> and give the input
id="requests-slider", or add an aria-label (e.g., aria-label="Requests per
month") on the input so screen readers can identify the control; ensure the
label text clearly describes the control and keep the existing props and
handlers unchanged.
client/www/components/new-landing/enterprise/EnterpriseHero.tsx-35-35 (1)

35-35: ⚠️ Potential issue | 🟠 Major

Use max-w instead of min-w for the hero paragraph.

min-w-[930px] can force mobile overflow. This should be a max width constraint.

Proposed fix
-          <p className="mx-auto mt-6 min-w-[930px] text-lg text-balance sm:text-[24px]">
+          <p className="mx-auto mt-6 max-w-[930px] text-lg text-balance sm:text-[24px]">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/enterprise/EnterpriseHero.tsx` at line 35,
The hero paragraph in EnterpriseHero (the <p> with className containing
"min-w-[930px]") uses min-w which forces overflow on small screens; change that
class to use a max width constraint (replace "min-w-[930px]" with
"max-w-[930px]") and ensure it still centers (keep "mx-auto") so the paragraph
will cap at 930px instead of forcing a minimum width on mobile.
client/www/components/new-landing/BatteriesForAI.tsx-735-735 (1)

735-735: ⚠️ Potential issue | 🟠 Major

Missing responsive breakpoints on grid layout.

grid-cols-3 will force 3 columns on all screen sizes, which will likely break on mobile devices. Consider adding responsive breakpoints:

Proposed fix
-      <div className="grid auto-rows-fr grid-cols-3 gap-6">
+      <div className="grid auto-rows-fr grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/BatteriesForAI.tsx` at line 735, The grid
in the BatteriesForAI component currently uses a fixed "grid-cols-3" which
forces three columns on all screen sizes; update the className on the <div> that
contains "grid auto-rows-fr grid-cols-3 gap-6" to use responsive Tailwind
breakpoints (for example: single column on small screens, two columns on medium,
three on large) such as replacing "grid-cols-3" with "grid-cols-1 sm:grid-cols-2
lg:grid-cols-3" (keeping the other classes like auto-rows-fr and gap-6) so the
layout adapts correctly on mobile, tablet, and desktop.
client/www/components/new-landing/BatteriesForAI.tsx-791-791 (1)

791-791: ⚠️ Potential issue | 🟠 Major

Payments section grid also lacks responsive breakpoints.

Similar to the features grid, grid-cols-3 here will cause layout issues on smaller screens.

Proposed fix
-        <div className="grid grid-cols-3 items-center gap-7">
+        <div className="grid grid-cols-1 items-center gap-7 lg:grid-cols-3">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/BatteriesForAI.tsx` at line 791, The
payments section div using the CSS class string "grid grid-cols-3 items-center
gap-7" is not responsive; update the className on that div to use responsive
grid breakpoints (for example grid-cols-1 on mobile, sm:grid-cols-2 on small
screens, and lg:grid-cols-3 for large screens) so the layout collapses on narrow
viewports while preserving gap and alignment (keep items-center and gap-7);
locate the div containing the exact className "grid grid-cols-3 items-center
gap-7" in BatteriesForAI.tsx and replace it with the responsive variant.
🟡 Minor comments (18)
client/www/components/new-landing/PaymentsIdea_PricingBuilder.tsx-183-188 (1)

183-188: ⚠️ Potential issue | 🟡 Minor

Set explicit button type for CTA actions.

On Line 183, this <button> should set type="button" to avoid accidental form submission if this component is ever rendered inside a <form>.

Suggested fix
                 <button
+                  type="button"
                   onClick={() => handleSelect(m.label)}
                   className={`w-full rounded-md ${m.btnColor} px-3 py-1.5 text-xs font-medium text-white transition-colors active:scale-[0.98]`}
                 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_PricingBuilder.tsx` around
lines 183 - 188, The CTA button in PaymentsIdea_PricingBuilder (the <button>
that calls handleSelect(m.label) and renders {m.key === 'one-time' ? 'Buy' :
'Select'}) needs an explicit type to prevent accidental form submission; update
that button element to include type="button" so it won't submit a surrounding
<form> when clicked.
client/www/components/new-landing/PaymentsIdea_PricingBuilder.tsx-93-103 (1)

93-103: ⚠️ Potential issue | 🟡 Minor

Clear pending toast timers on unmount.

On Line 102, a pending timeout can still fire after unmount. Add cleanup to clear toastTimeout.current.

Suggested fix
-import React, { useState, useCallback } from 'react';
+import React, { useState, useCallback, useEffect } from 'react';
@@
   const [toast, setToast] = useState<string | null>(null);
   const toastTimeout = React.useRef<NodeJS.Timeout | null>(null);
+
+  useEffect(() => {
+    return () => {
+      if (toastTimeout.current) clearTimeout(toastTimeout.current);
+    };
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_PricingBuilder.tsx` around
lines 93 - 103, The component currently stores toastTimeout in
toastTimeout.current and sets timeouts inside handleSelect, but doesn't clear
them on unmount; add a useEffect in the PaymentsIdea_PricingBuilder component
that returns a cleanup function which checks toastTimeout.current and calls
clearTimeout on it (and sets it to null) to prevent the timeout from firing
after unmount. Ensure you reference the existing toastTimeout ref when
implementing the cleanup.
client/www/components/new-landing/PaymentsIdea_Journey.tsx-305-307 (1)

305-307: ⚠️ Potential issue | 🟡 Minor

Set explicit button types.

Line 305 and Line 343 rely on default button behavior (submit). If this component is rendered inside a <form>, clicks can trigger unintended submissions.

Proposed fix
-        <button
+        <button
+          type="button"
           onClick={handleReset}
           className="rounded-lg border border-gray-200 px-3 py-1.5 text-xs font-medium text-gray-500 transition-colors hover:bg-gray-50 hover:text-gray-700 active:scale-[0.98]"
         >
@@
-                <motion.button
+                <motion.button
+                  type="button"
                   onClick={() => playStep(step.id)}
                   disabled={!isClickable}

Also applies to: 343-346

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_Journey.tsx` around lines 305
- 307, The buttons in the PaymentsIdea_Journey component rely on default button
behavior and can unintentionally submit a parent form; update the two affected
button elements (the one with onClick={handleReset} and the other button in the
same component around lines 343-346) to explicitly set type="button" so clicks
won't trigger form submissions; locate these buttons within the
PaymentsIdea_Journey component and add the type attribute to each.
client/www/components/new-landing/SyncDemoIdeas.tsx-200-209 (1)

200-209: ⚠️ Potential issue | 🟡 Minor

Read-only panes currently expose dead buttons to keyboard users.

Line 200 and Line 506 render focusable <button> controls even when they do nothing (onToggle absent / interactive={false}). This creates non-functional tab stops.

♿ Proposed fix
-            <button
+            <button
+              type="button"
               key={item.id}
-              onClick={() => onToggle?.(item.id)}
+              onClick={onToggle ? () => onToggle(item.id) : undefined}
+              disabled={!onToggle}
+              aria-disabled={!onToggle}
               className={`flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-all duration-300 ${
                 highlights.has(item.id)
                   ? 'bg-orange-50 ring-1 ring-orange-200'
@@
-              <button
+              <button
+                type="button"
                 key={emoji}
                 onClick={interactive ? () => react(emoji) : undefined}
+                disabled={!interactive}
+                aria-disabled={!interactive}
                 className={`flex items-center justify-center gap-1.5 rounded-lg border py-1.5 text-xs transition-all duration-200 ${
                   bumped === 'local'

Also applies to: 506-517

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/SyncDemoIdeas.tsx` around lines 200 - 209,
The buttons rendered in SyncDemoIdeas are focusable even when non-interactive,
causing dead tab stops; update the rendering logic around the element that
currently uses onClick={() => onToggle?.(item.id)} (and the interactive prop
variant) so that when onToggle is undefined or interactive===false you render a
non-focusable element (e.g., a <div> or <span>) or make the element inert
(remove onClick, set tabIndex={-1} and aria-disabled="true"), keep the same
className/highlight styling keyed by item.id and highlights.has(item.id), and
only render a real <button> with onClick when onToggle is present and
interactive is true to restore keyboard focusability for actionable items.
client/www/components/new-landing/PaymentsDemoIdeas.tsx-525-533 (1)

525-533: ⚠️ Potential issue | 🟡 Minor

Expose toggle state to assistive tech with aria-pressed.

These buttons behave like a segmented toggle; add pressed semantics so screen readers can identify the active plan.

Proposed fix
             <button
               key={option}
               onClick={() => setPlan(option)}
+              aria-pressed={plan === option}
               className="relative z-10 px-5 py-1.5 text-sm font-medium capitalize transition-colors"
               style={{
                 color: plan === option ? '#fff' : '#6b7280',
               }}
             >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsDemoIdeas.tsx` around lines 525 -
533, The segmented-toggle buttons lack accessible pressed state; update the
button in the PaymentsDemoIdeas component to expose its state to assistive tech
by adding aria-pressed={plan === option} (and ensure the element is a real
button with type="button" if not already) so screen readers can identify the
active plan; locate the button using the setPlan onClick handler and the
plan/option variables and set aria-pressed to true when plan === option and
false otherwise.
client/www/components/new-landing/PaymentsDemoIdeas.tsx-560-560 (1)

560-560: ⚠️ Potential issue | 🟡 Minor

Tailwind v4 compatibility: replace flex-shrink-0 with shrink-0.

The flex-shrink-* utilities were removed in Tailwind v4, so this class will not apply and will cause layout drift in icon rows.

Proposed fix
-              <div className="flex h-6 w-6 flex-shrink-0 items-center justify-center">
+              <div className="flex h-6 w-6 shrink-0 items-center justify-center">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsDemoIdeas.tsx` at line 560, Replace
the removed Tailwind v3 utility by updating the div that currently uses
"flex-shrink-0" in the PaymentsDemoIdeas component to use "shrink-0" instead;
locate the element with className containing "flex h-6 w-6 flex-shrink-0
items-center justify-center" (in the PaymentsDemoIdeas.tsx JSX) and change the
utility so layout remains consistent under Tailwind v4.
client/packages/components/src/style.css-36-44 (1)

36-44: ⚠️ Potential issue | 🟡 Minor

Incorrect font-weight for BoldItalic variant.

The font file is BerkeleyMono-BoldItalic.woff2 but font-weight is set to normal. This should be bold to match the font file.

🐛 Proposed fix
   `@font-face` {
     font-family: 'Berk Mono';
-    font-weight: normal;
+    font-weight: bold;
     font-style: italic;
     font-stretch: normal;
     src: url('https://stopaio.s3.amazonaws.com/public/BerkeleyMono-BoldItalic.woff2')
       format('woff2');
     font-display: swap;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/packages/components/src/style.css` around lines 36 - 44, The
`@font-face` block defining font-family 'Berk Mono' uses the
BerkeleyMono-BoldItalic.woff2 file but sets font-weight: normal; update the
font-weight to bold (and keep font-style: italic) so the declared weight matches
the BoldItalic font file; locate the `@font-face` that references
'https://stopaio.s3.amazonaws.com/public/BerkeleyMono-BoldItalic.woff2' and
change its font-weight from normal to bold.
client/www/components/new-landing/Header.tsx-69-74 (1)

69-74: ⚠️ Potential issue | 🟡 Minor

Expose mobile menu state with ARIA attributes.

Add aria-expanded and aria-controls on the toggle button, and an id on the mobile menu container.

Proposed fix
             <button
               type="button"
               className="p-2 text-gray-500 transition-colors md:hidden"
+              aria-expanded={mobileMenuOpen}
+              aria-controls="mobile-nav"
               onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
             >
               <span className="sr-only">Toggle menu</span>
@@
       {mobileMenuOpen && (
-        <div className="bg-white md:hidden">
+        <div id="mobile-nav" className="bg-white md:hidden">

Also applies to: 101-103

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/Header.tsx` around lines 69 - 74, The
mobile menu toggle button currently toggles state via
setMobileMenuOpen(!mobileMenuOpen) but lacks ARIA attributes; update the button
(where onClick uses setMobileMenuOpen and reads mobileMenuOpen) to include
aria-expanded={mobileMenuOpen} and aria-controls="mobile-menu" and add
id="mobile-menu" to the mobile menu container element (the div that is
shown/hidden based on mobileMenuOpen). Repeat the same change for the other
toggle instance that also uses setMobileMenuOpen/mobileMenuOpen so both toggles
reference the same controlled menu id.
client/www/components/new-landing/Footer.tsx-34-49 (1)

34-49: ⚠️ Potential issue | 🟡 Minor

Add accessible names for icon-only social links.

The icon links should expose an accessible label for screen readers.

Proposed fix
               <a
                 href="https://twitter.com/instant_db"
                 target="_blank"
                 rel="noopener noreferrer"
+                aria-label="Instant on X"
                 className="text-gray-500 transition-colors"
               >
@@
               <a
                 href="https://github.com/instantdb/instant"
                 target="_blank"
                 rel="noopener noreferrer"
+                aria-label="Instant on GitHub"
                 className="text-gray-500 transition-colors"
               >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/Footer.tsx` around lines 34 - 49, The
social anchor links containing only icons (the <a> elements wrapping XIcon and
GitHubIcon) lack accessible names; add descriptive accessible labels by
including an aria-label attribute on each anchor (e.g., aria-label="Twitter" for
the XIcon link and aria-label="GitHub" for the GitHubIcon link) or add a
visually hidden span inside each anchor with the service name to ensure screen
readers announce the link targets; update the anchors that wrap XIcon and
GitHubIcon accordingly.
client/www/styles/globals.css-10-13 (1)

10-13: ⚠️ Potential issue | 🟡 Minor

Remove duplicated Tailwind plugin registration.

tailwindcss-animate is registered at both line 10 and line 13. Remove the duplicate on line 13.

Proposed fix
 `@plugin` 'tailwindcss-animate';
 `@plugin` '@tailwindcss/typography';
 `@plugin` '@tailwindcss/forms';
-@plugin 'tailwindcss-animate';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/styles/globals.css` around lines 10 - 13, The CSS file registers
Tailwind plugins twice; remove the duplicate `@plugin` 'tailwindcss-animate' entry
(the second occurrence) so each plugin—@plugin 'tailwindcss-animate', `@plugin`
'@tailwindcss/typography', and `@plugin` '@tailwindcss/forms'—is only declared
once; edit the globals.css plugin block to delete the redundant `@plugin`
'tailwindcss-animate' line.
client/www/components/new-landing/Button.tsx-27-37 (1)

27-37: ⚠️ Potential issue | 🟡 Minor

Set a safe default type="button" for this reusable button component.

Without an explicit type, the button defaults to type="submit" inside forms, causing unintended form submissions when clicked. Since ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, the type attribute is available but not currently defaulted.

Proposed fix
 export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
   (
-    { variant = 'primary', size = 'md', className = '', children, ...props },
+    {
+      variant = 'primary',
+      size = 'md',
+      type = 'button',
+      className = '',
+      children,
+      ...props
+    },
     ref,
   ) => {
     return (
       <button
         ref={ref}
+        type={type}
         className={`${baseStyles} ${variantStyles[variant]} ${sizeStyles[size]} ${className}`}
         {...props}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/Button.tsx` around lines 27 - 37, The
Button component rendered by forwardRef (the function receiving { variant =
'primary', size = 'md', className = '', children, ...props }) should default its
HTML type to "button" to avoid implicit form submit; update the render to ensure
the rendered <button> uses props.type if provided otherwise "button" (e.g., set
the element's type to props.type ?? 'button' or pull type = 'button' in the
destructured props) so ButtonProps still allows explicit overrides while
preventing accidental submits.
client/www/pages/about.tsx-72-81 (1)

72-81: ⚠️ Potential issue | 🟡 Minor

Hero section flex layout may break on smaller screens.

The gap-[148px] creates a fixed 148px gap between the h1 and p elements in a flex row with items-end. On narrow viewports, this will likely cause horizontal overflow since the container doesn't wrap.

Proposed fix - make the layout responsive
-        <section className="mx-auto flex items-end gap-[148px] pt-20 pb-16 sm:pt-32 sm:pb-24">
-          <h1 className="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">
+        <section className="mx-auto flex flex-col gap-6 pt-20 pb-16 sm:pt-32 sm:pb-24 lg:flex-row lg:items-end lg:gap-16">
+          <h1 className="text-4xl font-bold tracking-tight sm:text-5xl lg:max-w-[60%] lg:text-6xl">
             Building the database for the AI era
           </h1>
-          <p className="mt-6 text-lg text-balance sm:text-xl">
+          <p className="text-lg text-balance sm:text-xl">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/about.tsx` around lines 72 - 81, The hero section currently
uses a fixed horizontal gap ("gap-[148px]") on a single-row flex layout which
can cause overflow on small screens; update the section's className to a
responsive layout by switching to a column on small viewports and row on larger
ones (e.g., replace "flex items-end gap-[148px]" with "flex flex-col sm:flex-row
sm:items-end gap-y-6 sm:gap-[148px]") so the h1 and p stack vertically on narrow
screens and keep the large gap on wider screens; ensure the change is applied to
the section containing the h1 and p in about.tsx.
client/www/components/marketingUi.tsx-216-216 (1)

216-216: ⚠️ Potential issue | 🟡 Minor

Use a root-relative path for the GitHub icon.

Line 216 uses a relative URL, which will resolve incorrectly on nested routes (e.g. /docs/img/...).

Proposed fix
-            src={'img/github-icon.svg'}
+            src="/img/github-icon.svg"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/marketingUi.tsx` at line 216, The GitHub icon uses a
relative path 'img/github-icon.svg' which breaks on nested routes; update the
img src to a root-relative path so it always resolves (e.g. change the src value
referencing 'github-icon.svg' to start with a leading slash). Locate the img
element in the marketingUi.tsx component (the JSX with
src={'img/github-icon.svg'}) and replace the relative path with a root-relative
path for the icon.
client/www/components/new-landing/SyncRelationsAppDemo.tsx-125-130 (1)

125-130: ⚠️ Potential issue | 🟡 Minor

Avoid non-null assertion for activeList lookup.

Line 125 can throw if lists is empty or activeListId is invalid. Guard this path to avoid hard crashes.

Proposed fix
-  const activeList = lists.find((l) => l.id === activeListId)!;
+  const activeList = lists.find((l) => l.id === activeListId);
+  if (!activeList) {
+    return <div className="p-3 text-xs text-gray-500">No lists available</div>;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/SyncRelationsAppDemo.tsx` around lines 125
- 130, The code currently uses a non-null assertion on activeList (const
activeList = lists.find((l) => l.id === activeListId)!) which can throw if lists
is empty or activeListId is invalid; change this to safely handle a missing list
by assigning the find result to a possibly undefined variable (e.g., activeList
= lists.find(...)) and then guard subsequent uses (activeItem lookup, doneCount,
totalCount) — either early-return/render a fallback when activeList is undefined
or compute doneCount/totalCount using optional chaining and defaults (e.g.,
activeList?.items ?? []), and update any usages of activeItemId/activeItem
accordingly to avoid crashes.
client/www/components/new-landing/FinalCTA.tsx-18-22 (1)

18-22: ⚠️ Potential issue | 🟡 Minor

Add explicit button semantics for accessibility/safety.

At Line 18, this icon-only control should include an accessible name and type="button" to avoid accidental form submission if reused inside a form context.

♿ Proposed fix
     <button
+      type="button"
+      aria-label="Copy command to clipboard"
       onClick={copy}
       className="text-gray-400 transition-colors hover:text-gray-600"
       title="Copy to clipboard"
     >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/FinalCTA.tsx` around lines 18 - 22, The
icon-only button in FinalCTA.tsx (the <button> that calls the copy function)
lacks explicit semantics; update that element to include type="button" to
prevent accidental form submission and provide an accessible name (e.g.,
aria-label="Copy link" or add a visually-hidden span inside the button) so
screen readers can announce its purpose; locate the button using the
onClick={copy} handler and className="text-gray-400 transition-colors
hover:text-gray-600" and add these attributes accordingly.
client/www/components/new-landing/PaymentsIdea_Checkout.tsx-287-292 (1)

287-292: ⚠️ Potential issue | 🟡 Minor

Clean up the success timeout to avoid stale updates.

The timer should be cleared on unmount and before scheduling a new one.

Proposed fix
-import { useState, useCallback } from 'react';
+import { useState, useCallback, useEffect, useRef } from 'react';

 export function CheckoutFlowDemo() {
   const [selected, setSelected] = useState<PlanId>('one-time');
   const [showSuccess, setShowSuccess] = useState(false);
+  const successTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

   const handlePay = useCallback(() => {
+    if (successTimerRef.current) clearTimeout(successTimerRef.current);
     setShowSuccess(true);
-    setTimeout(() => {
+    successTimerRef.current = setTimeout(() => {
       setShowSuccess(false);
+      successTimerRef.current = null;
     }, 2000);
   }, []);
+
+  useEffect(() => {
+    return () => {
+      if (successTimerRef.current) clearTimeout(successTimerRef.current);
+    };
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/PaymentsIdea_Checkout.tsx` around lines 287
- 292, The handlePay callback schedules a setTimeout to clear showSuccess but
doesn't clear previous timers or on unmount, causing stale state updates; modify
the component to store the timeout id (e.g., in a ref like successTimeoutRef)
and in handlePay clearTimeout(successTimeoutRef.current) before creating a new
setTimeout that assigns its id to successTimeoutRef.current and calls
setShowSuccess(false), and add a useEffect cleanup that clears
successTimeoutRef.current on unmount to prevent stale updates.
client/www/components/new-landing/BatteriesForAI.tsx-742-742 (1)

742-742: ⚠️ Potential issue | 🟡 Minor

Typo: "WIth" should be "With"

-                Use auth to enable your users to sign up for your app. WIth
+                Use auth to enable your users to sign up for your app. With
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/BatteriesForAI.tsx` at line 742, Fix the
typo in the UI string inside the BatteriesForAI component: change "WIth" to
"With" in the text node where the sentence starts "Use auth to enable your users
to sign up for your app. WIth" so the rendered copy reads "With"; locate this
string within the BatteriesForAI.tsx component and correct the capitalization.
client/www/components/new-landing/Hero.tsx-152-152 (1)

152-152: ⚠️ Potential issue | 🟡 Minor

Typo: "Insant" should be "Instant"

-            Insant gives you everything you need to vibe code delightful apps.
+            Instant gives you everything you need to vibe code delightful apps.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/Hero.tsx` at line 152, Fix the typo in the
Hero component's JSX string: change "Insant gives you everything you need to
vibe code delightful apps." to use "Instant" instead of "Insant" inside the Hero
function/JSX (look for the text node in the Hero component). Ensure the
corrected sentence preserves the rest of the copy and punctuation exactly as
intended.

<div className="grid grid-cols-2 divide-x divide-gray-100">
{/* Code editor pane */}
<div className="bg-gray-950 p-4 font-mono text-xs">
<div className="">// Generated by AI</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix Biome lint error on comment-like JSX text.

Line 152 currently triggers lint/suspicious/noCommentText; this can block CI.

✅ Proposed lint-safe fix
-                  <div className="">// Generated by AI</div>
+                  <div>{'// Generated by AI'}</div>
📝 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
<div className="">// Generated by AI</div>
<div>{'// Generated by AI'}</div>
🧰 Tools
🪛 Biome (2.4.4)

[error] 152-152: Wrap comments inside children within braces.

(lint/suspicious/noCommentText)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/enterprise/AppBuilders.tsx` at line 152,
Replace the comment-like JSX text inside the <div className=""> element (the "//
Generated by AI" node) with a proper JSX comment or plain text to satisfy Biome
lint; e.g., remove the leading slashes and render "Generated by AI" or convert
it to a JSX comment {/* Generated by AI */} within the same <div> in
AppBuilders.tsx so lint/suspicious/noCommentText no longer triggers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
client/www/components/new-landing/FinalCTA.tsx (2)

4-4: Unused import: Button is imported but not used.

The Button component is imported but the CTA button on line 80-82 uses a custom <button> element with inline styles instead. Consider either using the imported Button component for consistency, or removing the unused import.

Option 1: Use the Button component
-          <Link href="/dash">
-            <button className="inline-flex items-center justify-center rounded-lg bg-orange-600 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-orange-700">
-              Get a DB
-            </button>
-          </Link>
+          <Link href="/dash">
+            <Button size="lg">Get a DB</Button>
+          </Link>
Option 2: Remove unused import
 import { useState } from 'react';
-import { Button } from './Button';
 import { AnimateIn } from './AnimateIn';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/FinalCTA.tsx` at line 4, The import of
Button is unused in FinalCTA: either remove the unused import "Button" from the
top of FinalCTA.tsx or replace the native <button> on the CTA (currently styled
inline) with the imported Button component; if you use Button, pass the same
props/children and transfer the inline styles into Button props or className to
preserve appearance, otherwise simply delete the "import { Button } from
'./Button';" line to avoid the unused import.

11-15: Consider adding error handling for clipboard API.

navigator.clipboard.writeText can fail if clipboard permissions are denied or in insecure contexts. Wrapping in try-catch prevents unhandled promise rejections.

Proposed fix
   const copy = () => {
-    navigator.clipboard.writeText(text);
-    setCopied(true);
-    setTimeout(() => setCopied(false), 2000);
+    navigator.clipboard.writeText(text).then(() => {
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
+    }).catch(() => {
+      // Silently fail or show error feedback
+    });
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/FinalCTA.tsx` around lines 11 - 15, The
copy function should handle possible clipboard failures: make copy an async
function, await navigator.clipboard.writeText(text) inside a try-catch,
setCopied(true) only on success (and schedule setCopied(false) as now), and in
the catch block handle the error (e.g., setCopied(false), optionally show/log an
error or fallback behavior). Update the copy function, referencing
navigator.clipboard.writeText, setCopied, and text to ensure promise rejections
are caught and the UI state remains consistent.
client/www/pages/startup-showcase.tsx (1)

8-91: Consider extracting shared startup data to reduce duplication.

The startups array and AppDemo component are duplicated between this page and client/www/components/new-landing/StartupShowcase.tsx. If this page is intended for production (not just internal comparison), consider extracting the shared data and components.

If this is a temporary comparison page for design review, this duplication is acceptable and can be cleaned up later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/startup-showcase.tsx` around lines 8 - 91, The startups
array and AppDemo component are duplicated here and in StartupShowcase; extract
the shared data and UI into a single reusable module and import it from both
places: move the startups array to a shared export (e.g., startupsData) and move
the AppDemo implementation to a single exported component (e.g., AppDemo or
StartupShowcase) so both this page and the existing StartupShowcase component
import the data/component instead of redefining them; ensure any types used by
startups (props or demo shape) are exported and update imports where usages
referenced (startups, AppDemo) to the new shared exports.
client/www/pages/tamplin-quote-options.tsx (2)

173-175: If this page is for internal comparison only, add noindex.

Given this is an options/testing page, consider preventing accidental indexing in production.

Optional SEO guard
       <Head>
         <title>Tamplin Quote Options | Instant</title>
+        <meta name="robots" content="noindex,nofollow" />
       </Head>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/tamplin-quote-options.tsx` around lines 173 - 175, This page
currently sets only a <Head> title in tamplin-quote-options.tsx; add a robots
noindex meta tag to prevent accidental indexing in production when this is an
internal/testing page by updating the <Head> block (the Head component rendering
in tamplin-quote-options.tsx) to include <meta name="robots"
content="noindex,nofollow" /> or conditionally render that meta based on an
environment flag (e.g., process.env.NEXT_PUBLIC_INTERNAL_PAGE or
process.env.NODE_ENV !== 'production') so the page is excluded from search
engines as appropriate.

150-155: Avoid duplicating the quote literal in Option B.

This quote is already defined at Line 6-7 and reused in Option A. Keeping a second inline copy in Line 150-155 invites drift.

Suggested DRY refactor
-const quote =
-  '“Instant nails the quad-fecta: offline, real-time, relational queries, and open source. At Firebase, the demand for relational queries was off the charts — it’s a genuinely hard engineering problem.”';
+const quoteBody =
+  'Instant nails the quad-fecta: offline, real-time, relational queries, and open source. At Firebase, the demand for relational queries was off the charts — it’s a genuinely hard engineering problem.';
-          <blockquote className="text-base leading-relaxed text-gray-500 max-sm:text-center sm:text-lg">
-            {quote}
-          </blockquote>
+          <blockquote className="text-base leading-relaxed text-gray-500 max-sm:text-center sm:text-lg">
+            &ldquo;{quoteBody}&rdquo;
+          </blockquote>
-          <blockquote className="relative text-lg leading-relaxed font-medium text-gray-700 sm:text-xl">
-            Instant nails the quad-fecta: offline, real-time, relational
-            queries, and open source. At Firebase, the demand for relational
-            queries was off the charts &mdash; it&rsquo;s a genuinely hard
-            engineering problem.
-          </blockquote>
+          <blockquote className="relative text-lg leading-relaxed font-medium text-gray-700 sm:text-xl">
+            {quoteBody}
+          </blockquote>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/tamplin-quote-options.tsx` around lines 150 - 155, The
blockquote literal in the Option B JSX duplicates the same quote used earlier;
remove the inline string and reference the existing shared quote constant used
by Option A (the same variable that holds the quote text near the top of the
component) instead of hardcoding it inside the <blockquote> element; ensure the
shared constant (e.g., the quoteText/QUOTE constant already defined in this
component) is in scope for Option B and replace the literal with that identifier
so the quote is maintained in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/www/pages/tamplin-quote-options.tsx`:
- Around line 9-14: Unify James Tamplin’s title by choosing one canonical string
(either "CEO of Firebase" or "Founder of Firebase") and update all occurrences
in this file to match; specifically change the title in the allBackers array
entry (const allBackers) and any other places in tamplin-quote-options.tsx that
render or define James’s role so they use the exact same string.
- Line 27: Update the role string "Co-Founder of YCombinator" to "Co-Founder of
Y Combinator" in the TamplinQuoteOptions component (look for the object/property
role: 'Co-Founder of YCombinator') so the brand is spelled with a space ("Y
Combinator").

---

Nitpick comments:
In `@client/www/components/new-landing/FinalCTA.tsx`:
- Line 4: The import of Button is unused in FinalCTA: either remove the unused
import "Button" from the top of FinalCTA.tsx or replace the native <button> on
the CTA (currently styled inline) with the imported Button component; if you use
Button, pass the same props/children and transfer the inline styles into Button
props or className to preserve appearance, otherwise simply delete the "import {
Button } from './Button';" line to avoid the unused import.
- Around line 11-15: The copy function should handle possible clipboard
failures: make copy an async function, await navigator.clipboard.writeText(text)
inside a try-catch, setCopied(true) only on success (and schedule
setCopied(false) as now), and in the catch block handle the error (e.g.,
setCopied(false), optionally show/log an error or fallback behavior). Update the
copy function, referencing navigator.clipboard.writeText, setCopied, and text to
ensure promise rejections are caught and the UI state remains consistent.

In `@client/www/pages/startup-showcase.tsx`:
- Around line 8-91: The startups array and AppDemo component are duplicated here
and in StartupShowcase; extract the shared data and UI into a single reusable
module and import it from both places: move the startups array to a shared
export (e.g., startupsData) and move the AppDemo implementation to a single
exported component (e.g., AppDemo or StartupShowcase) so both this page and the
existing StartupShowcase component import the data/component instead of
redefining them; ensure any types used by startups (props or demo shape) are
exported and update imports where usages referenced (startups, AppDemo) to the
new shared exports.

In `@client/www/pages/tamplin-quote-options.tsx`:
- Around line 173-175: This page currently sets only a <Head> title in
tamplin-quote-options.tsx; add a robots noindex meta tag to prevent accidental
indexing in production when this is an internal/testing page by updating the
<Head> block (the Head component rendering in tamplin-quote-options.tsx) to
include <meta name="robots" content="noindex,nofollow" /> or conditionally
render that meta based on an environment flag (e.g.,
process.env.NEXT_PUBLIC_INTERNAL_PAGE or process.env.NODE_ENV !== 'production')
so the page is excluded from search engines as appropriate.
- Around line 150-155: The blockquote literal in the Option B JSX duplicates the
same quote used earlier; remove the inline string and reference the existing
shared quote constant used by Option A (the same variable that holds the quote
text near the top of the component) instead of hardcoding it inside the
<blockquote> element; ensure the shared constant (e.g., the quoteText/QUOTE
constant already defined in this component) is in scope for Option B and replace
the literal with that identifier so the quote is maintained in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 96101ee5-130f-427d-8c75-8d5ff308a53a

📥 Commits

Reviewing files that changed from the base of the PR and between 8742dc4 and ba0600f.

⛔ Files ignored due to path filters (5)
  • client/www/public/img/startups/alex-liu.jpeg is excluded by !**/*.jpeg
  • client/www/public/img/startups/ari-bapna-large.jpeg is excluded by !**/*.jpeg
  • client/www/public/img/startups/junior-garcia-large.jpg is excluded by !**/*.jpg
  • client/www/public/img/startups/mirando-ex.png is excluded by !**/*.png
  • client/www/public/img/startups/tiny-harvest-ex.png is excluded by !**/*.png
📒 Files selected for processing (8)
  • client/www/components/new-landing/FinalCTA.tsx
  • client/www/components/new-landing/FirebaseTestimonial.tsx
  • client/www/components/new-landing/Hero.tsx
  • client/www/components/new-landing/SocialProof.tsx
  • client/www/components/new-landing/StartupShowcase.tsx
  • client/www/pages/index.tsx
  • client/www/pages/startup-showcase.tsx
  • client/www/pages/tamplin-quote-options.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/www/components/new-landing/SocialProof.tsx
  • client/www/components/new-landing/Hero.tsx

Comment on lines +9 to +14
const allBackers = [
{
name: 'James Tamplin',
role: 'CEO of Firebase',
imageSrc: '/img/investors/james-tamplin.jpg',
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unify James’s title across the page.

Line 12 says “CEO of Firebase”, while Line 113 and Line 159 say “Founder of Firebase.” This will read as inconsistent to users. Pick one and use it everywhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/tamplin-quote-options.tsx` around lines 9 - 14, Unify James
Tamplin’s title by choosing one canonical string (either "CEO of Firebase" or
"Founder of Firebase") and update all occurrences in this file to match;
specifically change the title in the allBackers array entry (const allBackers)
and any other places in tamplin-quote-options.tsx that render or define James’s
role so they use the exact same string.

},
{
name: 'Paul Graham',
role: 'Co-Founder of YCombinator',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix brand spelling in role text.

Line 27 should use “Y Combinator” (with a space), not “YCombinator”.

Suggested copy fix
-    role: 'Co-Founder of YCombinator',
+    role: 'Co-Founder of Y Combinator',
📝 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
role: 'Co-Founder of YCombinator',
role: 'Co-Founder of Y Combinator',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/tamplin-quote-options.tsx` at line 27, Update the role
string "Co-Founder of YCombinator" to "Co-Founder of Y Combinator" in the
TamplinQuoteOptions component (look for the object/property role: 'Co-Founder of
YCombinator') so the brand is spelled with a space ("Y Combinator").

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

🧹 Nitpick comments (2)
client/www/components/new-landing/StartupShowcase.tsx (1)

86-121: Define explicit Demo union type to keep this component extensible.

Currently, all startups entries use type: 'video', making the image fallback path dead code. The inferred type from (typeof startups)[0]['demo'] only represents video, which masks the component's ability to handle images. Extract an explicit Demo union type and type startups against it to clarify intent and ensure type safety if image entries are added in the future.

Refactor sketch
+type Demo =
+  | { type: 'video'; src: string; portrait?: boolean }
+  | { type: 'image'; src: string; alt: string; portrait?: boolean };
+
+type Startup = {
+  name: string;
+  url: string;
+  person: string;
+  role: string;
+  avatar: string;
+  quote: string;
+  demo: Demo;
+};
+
-const startups = [
+const startups: Startup[] = [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/StartupShowcase.tsx` around lines 86 - 121,
Define an explicit Demo union type (e.g., type Demo = { type: 'video'; src:
string; portrait?: boolean } | { type: 'image'; src: string; alt?: string;
width?: number; height?: number }) and use it to type the startups entries and
the AppDemo prop instead of inferring from (typeof startups)[0]['demo']; update
the startups declaration to be typed as Demo[] (or a tuple of objects whose demo
fields are Demo) and change AppDemo signature to function AppDemo({ demo }: {
demo: Demo }) so the image branch becomes reachable and the component remains
extensible and type-safe.
client/www/pages/startup-showcase.tsx (1)

166-194: Extract the repeated quote/person block into a shared component.

The founder identity/quote UI is duplicated in both card variants, which increases drift risk during future edits.

Also applies to: 252-280

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/startup-showcase.tsx` around lines 166 - 194, The
quote/person UI block is duplicated; extract it into a new reusable React
component (e.g., FounderQuote or StartupQuote) that accepts props: quote,
avatar, person, role, url, and name, and renders the block currently inside
startup-showcase.tsx (the blockquote, Image, and person/role/link layout).
Replace the duplicated JSX in startup-showcase.tsx (the block at lines ~166-194
and the similar block at ~252-280) with this new component, passing the
corresponding startup.* fields; ensure to export/import the new component and
preserve the existing className, Image props (src, alt, width, height), and link
attributes (target, rel, className).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/www/components/marketingUi.tsx`:
- Around line 263-266: The icon-only menu buttons in marketingUi.tsx (the button
that calls setIsOpen(true) and its counterpart) lack accessible names and state
attributes; update each icon-only <button> to include type="button", a
descriptive aria-label (e.g., "Open menu"/"Close menu"), aria-expanded tied to
the menu state (e.g., aria-expanded={isOpen}), and aria-controls referencing the
id of the controlled menu element (create or reuse an id like menuId and set
id={menuId} on the menu container). Ensure the open/close handlers still call
setIsOpen and that aria-expanded reflects that same isOpen state.
- Line 216: The src attribute for the GitHub icon uses a relative path
("img/github-icon.svg") which can break on nested routes; update the JSX in the
marketingUi component to use an absolute public asset path
("/img/github-icon.svg") wherever the GitHub icon is rendered (look for the
src={'img/github-icon.svg'} occurrence) so the asset loads correctly from the
site root.
- Around line 65-73: Active-nav highlighting currently checks exact equality
using router.pathname === href in the Marketing UI link component
(marketingUi.tsx -> NextLink className), which misses nested routes; change the
condition to treat a link as active when pathname === href OR pathname starts
with href + '/' (e.g., pathname.startsWith(href + '/')) while preserving the
existing exact-match behavior for root and exact routes, and update the
className logic in the NextLink/cn call accordingly.
- Around line 253-255: The effect that sets document.body.style.overflow based
on isOpen must save and restore the previous overflow value and guard against
SSR; inside the useEffect in marketingUi.tsx capture the current
document.body.style.overflow to a local variable, set overflow to 'hidden' when
isOpen is true and 'auto' otherwise, and return a cleanup that restores the
saved value (so unmounting while open won't leave scrolling locked); also ensure
the effect only runs when document is defined to avoid server-side errors.
- Around line 112-123: The Tailwind utility classes duration-320, duration-350,
and duration-420 used in marketingUi.tsx are not in default Tailwind; fix by
either adding those keys under theme.extend.transitionDuration in
client/www/tailwind.config.js (e.g., '320': '320ms', '350': '350ms', '420':
'420ms') or replace occurrences in marketingUi.tsx (the className strings
containing "duration-320", "duration-350", "duration-420") with arbitrary-value
utilities like duration-[320ms], duration-[350ms], duration-[420ms]; update
whichever approach you choose consistently across the span and element
classNames to ensure the CSS is generated.

In `@client/www/components/new-landing/FinalCTA.tsx`:
- Around line 18-22: The copy button in FinalCTA.tsx is icon-only and currently
only uses title, so add an explicit aria-label on the button (e.g.,
aria-label={copied ? 'Copied to clipboard' : 'Copy to clipboard'}) to reflect
its dynamic state; locate the button element with onClick={copy} and update it
to include the aria-label using the existing copied state (or derive state from
the copy handler) so screen readers get a reliable accessible name that updates
when the state changes.
- Around line 11-15: The copy function sets the success state immediately
without awaiting navigator.clipboard.writeText, which can produce a false
"copied" state and unhandled promise rejections; update the copy function to
await navigator.clipboard.writeText(text) (or use .then/.catch) and only call
setCopied(true) after the promise resolves, handle errors by catching the
rejection and optionally logging or showing an error state, and ensure the
existing setTimeout(() => setCopied(false), 2000) remains triggered only on
success; references: copy, navigator.clipboard.writeText, setCopied.
- Around line 79-83: The Link currently wraps a <button> in FinalCTA.tsx (the
Link JSX around the element with className "inline-flex items-center..."), which
yields invalid HTML; replace the nested <button> by either (a) applying the
button styling and text directly to the Link element (use Link href="/dash" with
the same className and inner text "Get a DB"), or (b) render a semantic <a>
inside Link with the className (or use router.push from an onClick handler on a
real <button> if you need button semantics). Update the Link usage accordingly
so no <button> is a descendant of the anchor.

In `@client/www/components/new-landing/Hero.tsx`:
- Line 202: The hero copy in the Hero component contains a typo: change the
string "Insant gives you everything you need to vibe code delightful apps." to
use "Instant" instead of "Insant" within the Hero.tsx JSX (look for the JSX text
inside the Hero component render/return where that sentence appears).
- Around line 235-239: The code reads localStorage.getItem(STORAGE_KEY), parses
JSON and calls setTasks directly, which can crash tasks.map if the payload is
corrupted or shape-mismatched; update this block to (1) catch JSON.parse errors
(already present) then validate the parsed value before calling setTasks by
ensuring Array.isArray(parsed) and that each element is an object with the
expected task properties (or at minimum typeof item === 'object' && item !==
null), and only then call setTasks(parsed); otherwise fall back to a safe
default (e.g. setTasks([])) and consider removing the invalid entry from
localStorage; reference STORAGE_KEY, setTasks, tasks.map and
localStorage.getItem when making the change.
- Line 198: The heading's responsive classes are inverted on the <h1> in the
Hero component: className="hero-stagger-1 text-9xl sm:text-5xl" makes text
larger on small screens and smaller on larger screens; swap the sizes so the
base is the smallest and larger breakpoints scale up (e.g., use text-5xl as the
base and text-9xl for the sm or md breakpoint) by updating the <h1> className in
the Hero.tsx component accordingly.
- Around line 214-218: The Link in Hero.tsx currently wraps a <button>, which
creates nested interactive elements; remove the <button> and move its className
and text directly onto the Link returned by the Link component (the one
rendering the "Get a DB" action). Update the JSX so Link receives the existing
classes (inline-flex items-center justify-center rounded-lg bg-orange-600 px-6
py-3 text-base font-medium text-white transition-colors hover:bg-orange-700
sm:text-lg) and inner text "Get a DB", and ensure any accessibility attributes
(aria-label) that belonged to the button are preserved on Link.
- Around line 11-15: The copy function currently calls
navigator.clipboard.writeText(text) without awaiting or handling rejection and
sets setCopied(true) unconditionally; change the implementation of copy to await
navigator.clipboard.writeText(text) inside a try/catch (or use .then/.catch),
only call setCopied(true) and start the timeout when the write succeeds, and
handle failures in the catch by logging the error or showing a user-visible
fallback (e.g., a toast or console.warn) so there are no unhandled promise
rejections; refer to the copy function, navigator.clipboard.writeText, and
setCopied when locating the code to update.

In `@client/www/components/new-landing/SocialProof.tsx`:
- Line 23: Update the user-facing copy string that currently reads "Co-Founder
of YCombinator" to "Co-Founder of Y Combinator" inside the SocialProof
component; locate the testimonial/entry where role: 'Co-Founder of YCombinator'
(in the SocialProof.tsx component or its testimonials array) and change the role
string to include the space ("Y Combinator").
- Around line 65-79: The badge row currently uses a single-line flex which can
overflow on narrow screens; update the parent container in SocialProof (the div
wrapping YCIcon, SVAngelIcon, TechCrunchIcon) to allow wrapping (e.g., add
flex-wrap and appropriate gap/padding) and adjust the separators and badge
blocks so they reflow cleanly on small viewports (for example hide or reduce
separator visibility on xs and ensure each badge block has consistent
spacing/whitespace handling). Target the outer container div and the individual
badge blocks containing YCIcon, SVAngelIcon, and TechCrunchIcon when making
these responsive class changes.

In `@client/www/components/new-landing/StartupShowcase.tsx`:
- Around line 95-112: The two <video> elements in StartupShowcase.tsx render
autoplaying, looping media for every card (see the video JSX using
demo.src/demo.type and className), which harms performance and accessibility;
change behavior so videos only autoplay when their card is in-view (use an
IntersectionObserver or existing in-view hook to set an "isInView" flag) and
otherwise load with reduced preload (preload="none") and muted but not autoplay,
and add a visible toggle/pause control or an accessible play/pause button (with
appropriate aria-label) so users can stop motion; apply these changes to both
video render branches (the object-contain and object-cover variants).

In `@client/www/pages/startup-showcase.tsx`:
- Around line 107-124: Multiple testimonial <video> elements
(demo.type/demo.src) are all rendered and autoplaying in the StartupShowcase
component, causing heavy concurrent media downloads and CPU; change rendering to
lazy-load and only start the video when the card is visible or active by
deferring src/autoplay: replace immediate <video src={demo.src} autoPlay
loop.../> with a placeholder/poster and use an IntersectionObserver (or track an
"isVisible" flag per card) to set the video src and enable autoplay when the
card enters the viewport (or only render the <video> for the active card), and
set preload="metadata" and muted/playsInline to minimize initial cost.
- Around line 127-133: The iframe for the Vimeo demo is missing an accessible
title; update the iframe element that uses src={demo.src} (in the component
rendering the Vimeo demo) to include a descriptive title attribute (e.g., "Vimeo
demo video" or derive from demo.title if available) so screen readers can
identify the embedded content; modify the JSX for the iframe (the element with
src={demo.src}, className="absolute inset-0 h-full w-full") to add the title
prop with a clear, human-readable string.
- Around line 98-136: The AppDemo component's demo prop is inferred from the
startups array which currently narrows demo to only the 'video' variant, making
the 'vimeo' and image branches unreachable; define an explicit discriminated
union type (e.g., type Demo = { type: 'video'; src: string; portrait?: boolean }
| { type: 'vimeo'; src: string } | { type: 'image'; src: string; alt?: string })
and use it as the prop type in function AppDemo({ demo }: { demo: Demo }), then
update the startups constant to be typed as Demo[] (or cast/annotate entries) so
the compiler recognizes all branches in AppDemo (referencing the AppDemo
function, demo prop, startups array, and the 'video'/'vimeo'/'image'
discriminants).

---

Nitpick comments:
In `@client/www/components/new-landing/StartupShowcase.tsx`:
- Around line 86-121: Define an explicit Demo union type (e.g., type Demo = {
type: 'video'; src: string; portrait?: boolean } | { type: 'image'; src: string;
alt?: string; width?: number; height?: number }) and use it to type the startups
entries and the AppDemo prop instead of inferring from (typeof
startups)[0]['demo']; update the startups declaration to be typed as Demo[] (or
a tuple of objects whose demo fields are Demo) and change AppDemo signature to
function AppDemo({ demo }: { demo: Demo }) so the image branch becomes reachable
and the component remains extensible and type-safe.

In `@client/www/pages/startup-showcase.tsx`:
- Around line 166-194: The quote/person UI block is duplicated; extract it into
a new reusable React component (e.g., FounderQuote or StartupQuote) that accepts
props: quote, avatar, person, role, url, and name, and renders the block
currently inside startup-showcase.tsx (the blockquote, Image, and
person/role/link layout). Replace the duplicated JSX in startup-showcase.tsx
(the block at lines ~166-194 and the similar block at ~252-280) with this new
component, passing the corresponding startup.* fields; ensure to export/import
the new component and preserve the existing className, Image props (src, alt,
width, height), and link attributes (target, rel, className).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5fabfbca-8124-4f6e-a384-7a9c7f881c94

📥 Commits

Reviewing files that changed from the base of the PR and between ba0600f and 2b6a965.

📒 Files selected for processing (7)
  • client/www/components/marketingUi.tsx
  • client/www/components/new-landing/FinalCTA.tsx
  • client/www/components/new-landing/Hero.tsx
  • client/www/components/new-landing/SocialProof.tsx
  • client/www/components/new-landing/StartupShowcase.tsx
  • client/www/pages/startup-showcase.tsx
  • client/www/pages/tamplin-quote-options.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/www/pages/tamplin-quote-options.tsx

Comment on lines +65 to +73
const router = useRouter();
const pathname = router.pathname;
return (
<NextLink
href={href}
className={cn(
'relative z-20 whitespace-nowrap text-gray-700 transition-colors hover:text-gray-950',
pathname === href && 'text-gray-950',
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Active-nav highlighting misses nested pages.

Line 72 uses exact equality (pathname === href), so routes like /docs/... won’t mark /docs active.

Suggested fix
 const NavLink: React.FC<PropsWithChildren<{ href: string }>> = ({
   href,
   children,
 }) => {
   const router = useRouter();
-  const pathname = router.pathname;
+  const pathname = router.asPath.split('?')[0];
+  const isActive =
+    pathname === href || (href !== '/' && pathname.startsWith(`${href}/`));
   return (
     <NextLink
       href={href}
       className={cn(
         'relative z-20 whitespace-nowrap text-gray-700 transition-colors hover:text-gray-950',
-        pathname === href && 'text-gray-950',
+        isActive && 'text-gray-950',
       )}
     >
       {children}
     </NextLink>
   );
 };
📝 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
const router = useRouter();
const pathname = router.pathname;
return (
<NextLink
href={href}
className={cn(
'relative z-20 whitespace-nowrap text-gray-700 transition-colors hover:text-gray-950',
pathname === href && 'text-gray-950',
)}
const router = useRouter();
const pathname = router.asPath.split('?')[0];
const isActive =
pathname === href || (href !== '/' && pathname.startsWith(`${href}/`));
return (
<NextLink
href={href}
className={cn(
'relative z-20 whitespace-nowrap text-gray-700 transition-colors hover:text-gray-950',
isActive && 'text-gray-950',
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/marketingUi.tsx` around lines 65 - 73, Active-nav
highlighting currently checks exact equality using router.pathname === href in
the Marketing UI link component (marketingUi.tsx -> NextLink className), which
misses nested routes; change the condition to treat a link as active when
pathname === href OR pathname starts with href + '/' (e.g.,
pathname.startsWith(href + '/')) while preserving the existing exact-match
behavior for root and exact routes, and update the className logic in the
NextLink/cn call accordingly.

Comment on lines 253 to 255
useEffect(() => {
document.body.style.overflow = isOpen ? 'hidden' : 'auto';
}, [isOpen]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Body scroll-lock is not safely restored.

The effect mutates document.body.style.overflow but does not restore previous state on unmount. If unmounted while open, scrolling can stay locked.

Suggested fix
 useEffect(() => {
-  document.body.style.overflow = isOpen ? 'hidden' : 'auto';
+  if (!isOpen) return;
+  const previousOverflow = document.body.style.overflow;
+  document.body.style.overflow = 'hidden';
+  return () => {
+    document.body.style.overflow = previousOverflow;
+  };
 }, [isOpen]);
📝 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
useEffect(() => {
document.body.style.overflow = isOpen ? 'hidden' : 'auto';
}, [isOpen]);
useEffect(() => {
if (!isOpen) return;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = previousOverflow;
};
}, [isOpen]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/marketingUi.tsx` around lines 253 - 255, The effect
that sets document.body.style.overflow based on isOpen must save and restore the
previous overflow value and guard against SSR; inside the useEffect in
marketingUi.tsx capture the current document.body.style.overflow to a local
variable, set overflow to 'hidden' when isOpen is true and 'auto' otherwise, and
return a cleanup that restores the saved value (so unmounting while open won't
leave scrolling locked); also ensure the effect only runs when document is
defined to avoid server-side errors.

Comment on lines +263 to +266
<button
className="ml-auto min-[60rem]:hidden"
onClick={() => setIsOpen(true)}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Icon-only menu buttons need accessible names and state attributes.

Lines 263-266 and 291-292 render icon-only buttons without labels; add aria-label and menu state wiring (aria-expanded/aria-controls). Also set type="button" explicitly.

Suggested fix
       <button
+        type="button"
+        aria-label="Open navigation menu"
+        aria-expanded={isOpen}
+        aria-controls="marketing-nav-panel"
         className="ml-auto min-[60rem]:hidden"
         onClick={() => setIsOpen(true)}
       >
         <Bars3Icon height={'1em'} />
       </button>
       <div
+        id="marketing-nav-panel"
         onClick={() => setIsOpen(false)}
         className={cn(
@@
         <div className="flex justify-between self-stretch min-[60rem]:hidden">
           <LogoType />
-          <button className="z-50 mt-0.5" onClick={() => setIsOpen(false)}>
+          <button
+            type="button"
+            aria-label="Close navigation menu"
+            className="z-50 mt-0.5"
+            onClick={() => setIsOpen(false)}
+          >
             <XMarkIcon height="1em" />
           </button>
         </div>

Also applies to: 269-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/marketingUi.tsx` around lines 263 - 266, The icon-only
menu buttons in marketingUi.tsx (the button that calls setIsOpen(true) and its
counterpart) lack accessible names and state attributes; update each icon-only
<button> to include type="button", a descriptive aria-label (e.g., "Open
menu"/"Close menu"), aria-expanded tied to the menu state (e.g.,
aria-expanded={isOpen}), and aria-controls referencing the id of the controlled
menu element (create or reuse an id like menuId and set id={menuId} on the menu
container). Ensure the open/close handlers still call setIsOpen and that
aria-expanded reflects that same isOpen state.

Comment on lines +95 to +112
<video
src={demo.src}
autoPlay
loop
muted
playsInline
className="h-full max-h-[460px] rounded-lg object-contain"
/>
</div>
) : demo.type === 'video' ? (
<video
src={demo.src}
autoPlay
loop
muted
playsInline
className="h-full w-full object-cover"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Autoplaying every looping video will hurt landing-page performance and accessibility.

All cards render motion media that starts immediately, with no pause control. That can overload slower devices and creates an accessibility issue for continuous motion content. Consider limiting autoplay (e.g., first/in-view card only), reducing preload, and exposing controls or a pause mechanism.

Suggested direction
-function AppDemo({ demo }: { demo: (typeof startups)[0]['demo'] }) {
+function AppDemo({
+  demo,
+  autoPlay = false,
+}: {
+  demo: (typeof startups)[0]['demo'];
+  autoPlay?: boolean;
+}) {
@@
-          <video
+          <video
             src={demo.src}
-            autoPlay
+            autoPlay={autoPlay}
             loop
             muted
             playsInline
+            controls
+            preload={autoPlay ? 'auto' : 'metadata'}
             className="h-full max-h-[460px] rounded-lg object-contain"
           />
@@
-        <video
+        <video
           src={demo.src}
-          autoPlay
+          autoPlay={autoPlay}
           loop
           muted
           playsInline
+          controls
+          preload={autoPlay ? 'auto' : 'metadata'}
           className="h-full w-full object-cover"
         />
-<AppDemo demo={startup.demo} />
+<AppDemo demo={startup.demo} autoPlay={index === 0} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/StartupShowcase.tsx` around lines 95 - 112,
The two <video> elements in StartupShowcase.tsx render autoplaying, looping
media for every card (see the video JSX using demo.src/demo.type and className),
which harms performance and accessibility; change behavior so videos only
autoplay when their card is in-view (use an IntersectionObserver or existing
in-view hook to set an "isInView" flag) and otherwise load with reduced preload
(preload="none") and muted but not autoplay, and add a visible toggle/pause
control or an accessible play/pause button (with appropriate aria-label) so
users can stop motion; apply these changes to both video render branches (the
object-contain and object-cover variants).

Comment on lines +98 to +136
function AppDemo({ demo }: { demo: (typeof startups)[0]['demo'] }) {
const isPortrait = 'portrait' in demo && demo.portrait;
return (
<div className="overflow-hidden rounded-lg border border-gray-200 bg-gray-100 shadow-sm">
{demo.type === 'video' && isPortrait ? (
<div
className="flex items-center justify-center bg-gray-900 py-6"
style={{ maxHeight: '500px' }}
>
<video
src={demo.src}
autoPlay
loop
muted
playsInline
className="h-full max-h-[460px] rounded-lg object-contain"
/>
</div>
) : demo.type === 'video' ? (
<video
src={demo.src}
autoPlay
loop
muted
playsInline
className="h-full w-full object-cover"
/>
) : demo.type === 'vimeo' ? (
<div className="relative w-full" style={{ paddingTop: '56.25%' }}>
<iframe
src={demo.src}
className="absolute inset-0 h-full w-full"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture"
allowFullScreen
/>
</div>
) : (
<Image
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the startup-showcase.tsx file
find . -name "startup-showcase.tsx" -type f

Repository: instantdb/instant

Length of output: 100


🏁 Script executed:

# Run the verification commands from the review comment
rg -n "type:\s*'video'\s+as\s+const" client/www/pages/startup-showcase.tsx
rg -n "demo\.type === 'video'|demo\.type === 'vimeo'|demo\.type === 'image'" client/www/pages/startup-showcase.tsx

Repository: instantdb/instant

Length of output: 389


🏁 Script executed:

# Let's also check the file size and structure
wc -l client/www/pages/startup-showcase.tsx
head -150 client/www/pages/startup-showcase.tsx | cat -n

Repository: instantdb/instant

Length of output: 6349


Define explicit demo union type to enable all reachable code branches and improve maintainability.

The current typing derives demo from the startups array, where all 6 entries use type: 'video' as const. This causes TypeScript to narrow the inferred type to { type: 'video'; src: string; portrait?: boolean } only, making the branches for 'vimeo' (line 125) and the image fallback (line 135) unreachable. This couples the component's type safety to the current data and prevents adding new demo types without refactoring multiple locations.

🛠️ Proposed fix
+type StartupDemo =
+  | { type: 'video'; src: string; portrait?: boolean }
+  | { type: 'vimeo'; src: string }
+  | { type: 'image'; src: string };
+
+type Startup = {
+  name: string;
+  url: string;
+  person: string;
+  role: string;
+  avatar: string;
+  avatarLarge: string;
+  quote: string;
+  demo: StartupDemo;
+};
+
-const startups = [
+const startups: Startup[] = [
   {
@@
-function AppDemo({ demo }: { demo: (typeof startups)[0]['demo'] }) {
+function AppDemo({ demo }: { demo: StartupDemo }) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/startup-showcase.tsx` around lines 98 - 136, The AppDemo
component's demo prop is inferred from the startups array which currently
narrows demo to only the 'video' variant, making the 'vimeo' and image branches
unreachable; define an explicit discriminated union type (e.g., type Demo = {
type: 'video'; src: string; portrait?: boolean } | { type: 'vimeo'; src: string
} | { type: 'image'; src: string; alt?: string }) and use it as the prop type in
function AppDemo({ demo }: { demo: Demo }), then update the startups constant to
be typed as Demo[] (or cast/annotate entries) so the compiler recognizes all
branches in AppDemo (referencing the AppDemo function, demo prop, startups
array, and the 'video'/'vimeo'/'image' discriminants).

Comment on lines +107 to +124
<video
src={demo.src}
autoPlay
loop
muted
playsInline
className="h-full max-h-[460px] rounded-lg object-contain"
/>
</div>
) : demo.type === 'video' ? (
<video
src={demo.src}
autoPlay
loop
muted
playsInline
className="h-full w-full object-cover"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

All testimonial videos are rendered/autoplayed together, which is expensive on initial load.

Line 211 renders every card, and Lines 109/119 autoplay looping MP4s. This can cause large concurrent media fetches and high CPU on first paint.

⚡ Quick mitigation
           <video
             src={demo.src}
-            autoPlay
+            preload="none"
             loop
             muted
             playsInline
             className="h-full w-full object-cover"
           />
           <video
             src={demo.src}
-            autoPlay
+            preload="none"
             loop
             muted
             playsInline
             className="h-full max-h-[460px] rounded-lg object-contain"
           />

Also applies to: 210-213

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/startup-showcase.tsx` around lines 107 - 124, Multiple
testimonial <video> elements (demo.type/demo.src) are all rendered and
autoplaying in the StartupShowcase component, causing heavy concurrent media
downloads and CPU; change rendering to lazy-load and only start the video when
the card is visible or active by deferring src/autoplay: replace immediate
<video src={demo.src} autoPlay loop.../> with a placeholder/poster and use an
IntersectionObserver (or track an "isVisible" flag per card) to set the video
src and enable autoplay when the card enters the viewport (or only render the
<video> for the active card), and set preload="metadata" and muted/playsInline
to minimize initial cost.

Comment on lines +127 to +133
<iframe
src={demo.src}
className="absolute inset-0 h-full w-full"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture"
allowFullScreen
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an iframe title for accessibility.

The Vimeo iframe (Line 127) is missing title, which harms screen-reader navigation.

♿ Proposed fix
           <iframe
             src={demo.src}
+            title="App demo video"
             className="absolute inset-0 h-full w-full"
             frameBorder="0"
             allow="autoplay; fullscreen; picture-in-picture"
             allowFullScreen
           />
📝 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
<iframe
src={demo.src}
className="absolute inset-0 h-full w-full"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture"
allowFullScreen
/>
<iframe
src={demo.src}
title="App demo video"
className="absolute inset-0 h-full w-full"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture"
allowFullScreen
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/startup-showcase.tsx` around lines 127 - 133, The iframe for
the Vimeo demo is missing an accessible title; update the iframe element that
uses src={demo.src} (in the component rendering the Vimeo demo) to include a
descriptive title attribute (e.g., "Vimeo demo video" or derive from demo.title
if available) so screen readers can identify the embedded content; modify the
JSX for the iframe (the element with src={demo.src}, className="absolute inset-0
h-full w-full") to add the title prop with a clear, human-readable string.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (4)
client/www/components/new-landing/Hero.tsx (1)

127-139: MuxPlayer ref is typed as any.

Consider using a more specific type or the ref type from @mux/mux-player-react for better type safety.

Example
-  const playerRef = useRef<any>(null);
+  const playerRef = useRef<HTMLMuxPlayerElement | null>(null);

Note: The exact type depends on what @mux/mux-player-react exports. If MuxPlayerElement or similar isn't available, HTMLElement is a reasonable fallback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/Hero.tsx` around lines 127 - 139, The ref
playerRef in VideoPlayer is typed as any; replace it with a specific ref type
from `@mux/mux-player-react` (or a safe fallback like HTMLElement) to improve type
safety: import the exported element type (e.g., MuxPlayerElement) and change
const playerRef = useRef<any>(null) to useRef<MuxPlayerElement | null>(null),
update occurrences (e.g., in handlePlay set const el = playerRef.current) to
reflect the narrower type, and ensure any DOM method calls (currentTime, play)
are type-checked or guarded accordingly.
client/www/components/new-landing/BatteriesForAI.tsx (1)

197-291: Consider adding click-outside handler for dropdowns.

The dropdowns don't close when clicking outside. This is minor for a demo component but could confuse users who expect standard dropdown behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/BatteriesForAI.tsx` around lines 197 - 291,
PermissionsDemo's dropdowns (userOpen/opOpen) never close on outside clicks; add
an outside-click handler using refs and effect cleanup: attach a ref to the
dropdown container(s) (or a shared wrapper) and in useEffect listen for document
mousedown/click (and optionally keydown for Escape), check if the event target
is outside the ref, and if so call setUserOpen(false) and setOpOpen(false); be
sure to remove the listener in the cleanup; update component to use the ref and
the effect so both dropdowns close when clicking outside.
client/www/components/new-landing/BuiltForAI.tsx (1)

747-750: Consider responsive padding for terminal demo container.

The fixed pixel padding (px-[129px] py-[96px]) may cause horizontal overflow on smaller viewports. Consider using responsive values or max-width constraints.

Example responsive padding
-          <div className="order-2 grow bg-[`#CC4E05`]/20 px-[129px] py-[96px] lg:order-1">
+          <div className="order-2 grow bg-[`#CC4E05`]/20 px-4 py-12 sm:px-16 sm:py-16 lg:order-1 lg:px-[129px] lg:py-[96px]">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/BuiltForAI.tsx` around lines 747 - 750, The
fixed pixel padding on the div wrapping AnimatedTerminal causes overflow on
small viewports; update the container (the div with className "order-2 grow
bg-[`#CC4E05`]/20 px-[129px] py-[96px] lg:order-1") to use responsive padding
and/or a max-width + centered layout—e.g., replace px-[129px] py-[96px] with
responsive Tailwind classes like px-4 md:px-8 lg:px-[129px] and py-6 md:py-8
lg:py-[96px], and add a max-w constraint (max-w-screen-lg and mx-auto) and/or
overflow-hidden so AnimatedTerminal stays contained on small screens. Ensure
changes are applied where the AnimatedTerminal wrapper is defined.
client/www/pages/home/cta-options.tsx (1)

152-154: Use a lower heading level inside HeroShell to preserve page hierarchy.

HeroShell is rendered multiple times on a page that already has a top-level heading. Prefer h2 here.

Proposed fix
-            <h1 className="mx-auto max-w-4xl text-4xl font-semibold text-balance sm:text-6xl">
+            <h2 className="mx-auto max-w-4xl text-4xl font-semibold text-balance sm:text-6xl">
               {headline}
-            </h1>
+            </h2>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/home/cta-options.tsx` around lines 152 - 154, The HeroShell
currently renders the main headline using an <h1> which breaks page heading
hierarchy; change the element that renders the headline (where {headline} is
used inside the HeroShell instance in cta-options.tsx) from an h1 to an h2 to
preserve semantics when HeroShell is reused on pages that already have a
top-level heading; update any tests or snapshots referencing that element if
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/www/components/new-landing/BatteriesForAI.tsx`:
- Around line 742-744: In the BatteriesForAI component replace the typo "WIth"
with "With" in the Auth section description string so the sentence reads "With
Instant you can easily enable sign up via email, Google, Apple, GitHub, Clerk,
and more."; locate the string in the JSX/return where the Auth copy is rendered
and correct the capitalization.
- Around line 15-22: The name derivation in handleSendCode can produce an empty
string for inputs like "@domain.com"; update the logic in handleSendCode to trim
the email, extract the local part (email.split('@')[0]), check that the local
part has length > 0 after trimming, and only then compute the capitalized
derived name; otherwise fallback to 'Friend' before calling setName and setView
so setName never receives an empty string.

In `@client/www/components/new-landing/Hero.tsx`:
- Line 174: The heading's responsive classes in the Hero component are inverted
(text-9xl sm:text-5xl) so it renders larger on mobile and smaller on larger
screens; update the className on the <p> in Hero.tsx to use a mobile-first scale
(e.g., text-5xl with larger breakpoints like sm:text-7xl or sm:text-9xl /
md:text-9xl) so the font-size increases on larger viewports, preserving other
utilities (leading, font-semibold, tracking, text-white).

In `@client/www/pages/home/cta-options.tsx`:
- Around line 18-23: The icon-only copy button (the <button ... onClick={copy}>
in cta-options.tsx) relies on title but needs an explicit accessible name; add
an aria-label (e.g., aria-label="Copy to clipboard") to the same button element
so assistive tech receives a reliable name, and ensure the icon inside is marked
aria-hidden if present.
- Around line 11-15: The copy handler in the CopyButton component should await
navigator.clipboard.writeText and handle failures: make the copy function async,
wrap writeText(text) in try/catch, only call setCopied(true) on success and
optionally log or show an error on failure. Persist the timeout id (e.g., via a
ref) and clearTimeout in a useEffect cleanup to avoid timers outliving the
component (cancel previous timers before starting a new one). Also add an
explicit aria-label prop to the button (in addition to title) to improve screen
reader accessibility.

---

Nitpick comments:
In `@client/www/components/new-landing/BatteriesForAI.tsx`:
- Around line 197-291: PermissionsDemo's dropdowns (userOpen/opOpen) never close
on outside clicks; add an outside-click handler using refs and effect cleanup:
attach a ref to the dropdown container(s) (or a shared wrapper) and in useEffect
listen for document mousedown/click (and optionally keydown for Escape), check
if the event target is outside the ref, and if so call setUserOpen(false) and
setOpOpen(false); be sure to remove the listener in the cleanup; update
component to use the ref and the effect so both dropdowns close when clicking
outside.

In `@client/www/components/new-landing/BuiltForAI.tsx`:
- Around line 747-750: The fixed pixel padding on the div wrapping
AnimatedTerminal causes overflow on small viewports; update the container (the
div with className "order-2 grow bg-[`#CC4E05`]/20 px-[129px] py-[96px]
lg:order-1") to use responsive padding and/or a max-width + centered
layout—e.g., replace px-[129px] py-[96px] with responsive Tailwind classes like
px-4 md:px-8 lg:px-[129px] and py-6 md:py-8 lg:py-[96px], and add a max-w
constraint (max-w-screen-lg and mx-auto) and/or overflow-hidden so
AnimatedTerminal stays contained on small screens. Ensure changes are applied
where the AnimatedTerminal wrapper is defined.

In `@client/www/components/new-landing/Hero.tsx`:
- Around line 127-139: The ref playerRef in VideoPlayer is typed as any; replace
it with a specific ref type from `@mux/mux-player-react` (or a safe fallback like
HTMLElement) to improve type safety: import the exported element type (e.g.,
MuxPlayerElement) and change const playerRef = useRef<any>(null) to
useRef<MuxPlayerElement | null>(null), update occurrences (e.g., in handlePlay
set const el = playerRef.current) to reflect the narrower type, and ensure any
DOM method calls (currentTime, play) are type-checked or guarded accordingly.

In `@client/www/pages/home/cta-options.tsx`:
- Around line 152-154: The HeroShell currently renders the main headline using
an <h1> which breaks page heading hierarchy; change the element that renders the
headline (where {headline} is used inside the HeroShell instance in
cta-options.tsx) from an h1 to an h2 to preserve semantics when HeroShell is
reused on pages that already have a top-level heading; update any tests or
snapshots referencing that element if present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fc60eeaa-1a4f-40d9-8289-493ed0e2094b

📥 Commits

Reviewing files that changed from the base of the PR and between 2b6a965 and 16d209a.

📒 Files selected for processing (9)
  • client/www/components/home/AgentPathsBgSoftCenter.tsx
  • client/www/components/home/DotGridBg.tsx
  • client/www/components/new-landing/BatteriesForAI.tsx
  • client/www/components/new-landing/BuiltForAI.tsx
  • client/www/components/new-landing/Hero.tsx
  • client/www/pages/home/cta-options.tsx
  • client/www/pages/home/v2-agent-paths-soft-center.tsx
  • client/www/pages/home/v2-dot-grid.tsx
  • client/www/pages/index.tsx

Comment on lines +15 to +22
const handleSendCode = () => {
const derived = email.includes('@')
? email.split('@')[0].charAt(0).toUpperCase() +
email.split('@')[0].slice(1)
: 'Friend';
setName(derived);
setView('success');
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Edge case in email name derivation.

If the user enters just @domain.com, email.split('@')[0] returns an empty string, and charAt(0).toUpperCase() returns an empty string, resulting in an empty name being set. This is a minor UI glitch.

Suggested fix
   const handleSendCode = () => {
-    const derived = email.includes('@')
-      ? email.split('@')[0].charAt(0).toUpperCase() +
-        email.split('@')[0].slice(1)
-      : 'Friend';
+    const localPart = email.includes('@') ? email.split('@')[0] : '';
+    const derived = localPart.length > 0
+      ? localPart.charAt(0).toUpperCase() + localPart.slice(1)
+      : 'Friend';
     setName(derived);
     setView('success');
   };
📝 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
const handleSendCode = () => {
const derived = email.includes('@')
? email.split('@')[0].charAt(0).toUpperCase() +
email.split('@')[0].slice(1)
: 'Friend';
setName(derived);
setView('success');
};
const handleSendCode = () => {
const localPart = email.includes('@') ? email.split('@')[0] : '';
const derived = localPart.length > 0
? localPart.charAt(0).toUpperCase() + localPart.slice(1)
: 'Friend';
setName(derived);
setView('success');
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/BatteriesForAI.tsx` around lines 15 - 22,
The name derivation in handleSendCode can produce an empty string for inputs
like "@domain.com"; update the logic in handleSendCode to trim the email,
extract the local part (email.split('@')[0]), check that the local part has
length > 0 after trimming, and only then compute the capitalized derived name;
otherwise fallback to 'Friend' before calling setName and setView so setName
never receives an empty string.

Comment on lines +742 to +744
Use auth to enable your users to sign up for your app. WIth
Instant you can easily enable sign up via email, Google, Apple,
GitHub, Clerk, and more.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in Auth section description.

Line 742 has "WIth" instead of "With".

Fix
-              Use auth to enable your users to sign up for your app. WIth
+              Use auth to enable your users to sign up for your app. With
📝 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
Use auth to enable your users to sign up for your app. WIth
Instant you can easily enable sign up via email, Google, Apple,
GitHub, Clerk, and more.
Use auth to enable your users to sign up for your app. With
Instant you can easily enable sign up via email, Google, Apple,
GitHub, Clerk, and more.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/BatteriesForAI.tsx` around lines 742 - 744,
In the BatteriesForAI component replace the typo "WIth" with "With" in the Auth
section description string so the sentence reads "With Instant you can easily
enable sign up via email, Google, Apple, GitHub, Clerk, and more."; locate the
string in the JSX/return where the Auth copy is rendered and correct the
capitalization.

Comment on lines +11 to +15
const copy = () => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n client/www/pages/home/cta-options.tsx | head -100

Repository: instantdb/instant

Length of output: 3470


🏁 Script executed:

rg -nP --type=ts --type=tsx 'clipboard\.writeText\s*\(' -C3

Repository: instantdb/instant

Length of output: 88


🏁 Script executed:

rg 'clipboard\.writeText' -C3

Repository: instantdb/instant

Length of output: 7573


🏁 Script executed:

cat -n client/www/pages/home/cta-options.tsx | tail -150

Repository: instantdb/instant

Length of output: 6006


Handle clipboard failures and timer lifecycle in CopyButton.

Line 12 calls navigator.clipboard.writeText(text) without awaiting or catching errors, and Line 13 sets the copied state regardless of whether the write succeeds. Additionally, the timeout created on Line 14 is never cleaned up and can outlive the component lifecycle. The button on Line 18 should also have an explicit aria-label instead of relying solely on the title attribute for screen reader accessibility.

Proposed fix
-import { useState, type ReactNode } from 'react';
+import { useEffect, useRef, useState, type ReactNode } from 'react';

 function CopyButton({ text }: { text: string }) {
   const [copied, setCopied] = useState(false);
+  const resetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  useEffect(() => {
+    return () => {
+      if (resetTimerRef.current) clearTimeout(resetTimerRef.current);
+    };
+  }, []);

-  const copy = () => {
-    navigator.clipboard.writeText(text);
-    setCopied(true);
-    setTimeout(() => setCopied(false), 2000);
+  const copy = async () => {
+    try {
+      await navigator.clipboard.writeText(text);
+      setCopied(true);
+      if (resetTimerRef.current) clearTimeout(resetTimerRef.current);
+      resetTimerRef.current = setTimeout(() => setCopied(false), 2000);
+    } catch {
+      setCopied(false);
+    }
   };
 
   return (
     <button
       type="button"
       onClick={copy}
       className="cursor-pointer rounded-md p-1 text-gray-400 transition-colors hover:text-gray-600"
       title="Copy to clipboard"
+      aria-label="Copy to clipboard"
     >
       {copied ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/home/cta-options.tsx` around lines 11 - 15, The copy handler
in the CopyButton component should await navigator.clipboard.writeText and
handle failures: make the copy function async, wrap writeText(text) in
try/catch, only call setCopied(true) on success and optionally log or show an
error on failure. Persist the timeout id (e.g., via a ref) and clearTimeout in a
useEffect cleanup to avoid timers outliving the component (cancel previous
timers before starting a new one). Also add an explicit aria-label prop to the
button (in addition to title) to improve screen reader accessibility.

Comment on lines +18 to +23
<button
type="button"
onClick={copy}
className="cursor-pointer rounded-md p-1 text-gray-400 transition-colors hover:text-gray-600"
title="Copy to clipboard"
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an explicit accessible name to the icon-only copy button.

title alone is not a reliable accessible name across assistive tech; add aria-label on the button.

Proposed fix
     <button
       type="button"
       onClick={copy}
+      aria-label={copied ? 'Copied to clipboard' : 'Copy command to clipboard'}
       className="cursor-pointer rounded-md p-1 text-gray-400 transition-colors hover:text-gray-600"
       title="Copy to clipboard"
     >
📝 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
<button
type="button"
onClick={copy}
className="cursor-pointer rounded-md p-1 text-gray-400 transition-colors hover:text-gray-600"
title="Copy to clipboard"
>
<button
type="button"
onClick={copy}
aria-label={copied ? 'Copied to clipboard' : 'Copy command to clipboard'}
className="cursor-pointer rounded-md p-1 text-gray-400 transition-colors hover:text-gray-600"
title="Copy to clipboard"
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/home/cta-options.tsx` around lines 18 - 23, The icon-only
copy button (the <button ... onClick={copy}> in cta-options.tsx) relies on title
but needs an explicit accessible name; add an aria-label (e.g., aria-label="Copy
to clipboard") to the same button element so assistive tech receives a reliable
name, and ensure the icon inside is marked aria-hidden if present.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
client/www/components/new-landing/SyncDemoIdeas.tsx (2)

1084-1095: Consider extracting shared keyframes.

The syncDotLR/syncDotRL keyframes here are nearly identical to chatSyncDotLR/chatSyncDotRL in RealtimeChatDemo (lines 943-954). Consider extracting these to a shared style component or CSS file to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/SyncDemoIdeas.tsx` around lines 1084 -
1095, The keyframe animations syncDotLR/syncDotRL in the SyncDemoIdeas component
are duplicated from chatSyncDotLR/chatSyncDotRL in RealtimeChatDemo; refactor by
extracting these shared keyframes into a single shared style (e.g., a CSS
module, shared styled-component, or global stylesheet) and update SyncDemoIdeas
and RealtimeChatDemo to import/use the common keyframe names (or keep one
canonical name and replace the other), ensuring you remove the duplicated
definitions from both SyncDemoIdeas and RealtimeChatDemo and reference the
shared keyframes consistently.

131-146: Minor: Nested timers lack cleanup refs.

The nested setTimeout calls in toggleFromLaptop don't store references for cleanup on unmount. While these are short-lived visual effects (720ms total) with minimal risk, storing refs and clearing them in an effect cleanup would be more robust.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/SyncDemoIdeas.tsx` around lines 131 - 146,
The nested setTimeouts in toggleFromLaptop (which updates state via setItems and
setPhoneHighlights) create timers that aren't cleared on unmount; fix by
creating refs (e.g., phoneHighlightTimeoutRef and phoneHighlightClearRef) to
store the timeout IDs, assign the IDs when calling setTimeout inside
toggleFromLaptop, clear any existing timeouts before setting new ones, and add a
useEffect cleanup that clears those refs' timers on unmount to prevent leaks or
stray callbacks.
client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx (2)

530-534: Unused type definition.

The FooterState type is defined but never used. The fastFooter and slowFooter states use simpler string literal types instead.

🧹 Remove unused type
-type FooterState =
-  | { kind: 'idle' }
-  | { kind: 'instant'; visible: boolean }
-  | { kind: 'ticking' }
-  | { kind: 'done'; ms: number };
-
 export function SideBySideDemo() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx` around lines
530 - 534, The FooterState type is declared but never used; remove the unused
type definition `FooterState` from InstantUpdatesDemoIdeas.tsx, and ensure the
existing state variables `fastFooter` and `slowFooter` continue using their
current string-literal union types (no further changes needed); alternatively if
you intended to use `FooterState`, replace the `fastFooter`/`slowFooter` state
typings/usages with `FooterState` instead—pick one approach and delete the
unused `FooterState` declaration or update the state variables to reference it
so the definition is no longer orphaned.

1033-1034: Unused state variables.

fastDone and slowDone are set throughout the component but never read in the render or any logic. These appear to be unused.

🧹 Remove unused state if not needed
-  const [fastDone, setFastDone] = useState(false);
-  const [slowDone, setSlowDone] = useState(false);

And remove all setFastDone(...) and setSlowDone(...) calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx` around lines
1033 - 1034, Remove the unused React state variables `fastDone` and `slowDone`
from the InstantUpdatesDemoIdeas component and delete all related setters
`setFastDone(...)` and `setSlowDone(...)` invocations; specifically remove the
useState declarations for `fastDone`/`slowDone` and then search for and delete
every call to `setFastDone` and `setSlowDone` to avoid dead state and unused
updates in functions like the demo update handlers.
client/www/pages/sync-chat-demo.tsx (1)

19-19: Consider responsive breakpoints for the grid layout.

The grid-cols-3 class creates a fixed 3-column layout that may not work well on smaller screens. Consider using responsive variants like grid-cols-1 md:grid-cols-3 to stack content vertically on mobile.

📱 Suggested responsive grid
-          <div className="grid grid-cols-3 items-center gap-6">
+          <div className="grid grid-cols-1 items-center gap-6 md:grid-cols-3">

Apply this pattern to all three grid sections (lines 19, 36, 53).

Also applies to: 36-36, 53-53

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/pages/sync-chat-demo.tsx` at line 19, Replace the fixed 3-column
Tailwind class on the grid containers so they become responsive: find the divs
whose className is "grid grid-cols-3 items-center gap-6" (the three grid
sections currently at the three occurrences) and change the grid columns to a
responsive variant by substituting "grid-cols-3" with "grid-cols-1
md:grid-cols-3"; apply the same change to the other two grid sections mentioned
in the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/www/components/new-landing/BuiltForAI.tsx`:
- Line 756: The div in BuiltForAI.tsx using className "flex items-center gap-8
lg:grid-cols-2 lg:gap-12" mixes flex with a grid-only utility; fix by either (A)
making the layout switch to grid at the lg breakpoint—add the breakpoint grid
utility so the class becomes "flex items-center gap-8 lg:grid lg:grid-cols-2
lg:gap-12" (keeps flex on small screens, grid on large), or (B) if you want to
always use flex, remove the ineffective "lg:grid-cols-2" token—update the
className on that div accordingly.

---

Nitpick comments:
In `@client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx`:
- Around line 530-534: The FooterState type is declared but never used; remove
the unused type definition `FooterState` from InstantUpdatesDemoIdeas.tsx, and
ensure the existing state variables `fastFooter` and `slowFooter` continue using
their current string-literal union types (no further changes needed);
alternatively if you intended to use `FooterState`, replace the
`fastFooter`/`slowFooter` state typings/usages with `FooterState` instead—pick
one approach and delete the unused `FooterState` declaration or update the state
variables to reference it so the definition is no longer orphaned.
- Around line 1033-1034: Remove the unused React state variables `fastDone` and
`slowDone` from the InstantUpdatesDemoIdeas component and delete all related
setters `setFastDone(...)` and `setSlowDone(...)` invocations; specifically
remove the useState declarations for `fastDone`/`slowDone` and then search for
and delete every call to `setFastDone` and `setSlowDone` to avoid dead state and
unused updates in functions like the demo update handlers.

In `@client/www/components/new-landing/SyncDemoIdeas.tsx`:
- Around line 1084-1095: The keyframe animations syncDotLR/syncDotRL in the
SyncDemoIdeas component are duplicated from chatSyncDotLR/chatSyncDotRL in
RealtimeChatDemo; refactor by extracting these shared keyframes into a single
shared style (e.g., a CSS module, shared styled-component, or global stylesheet)
and update SyncDemoIdeas and RealtimeChatDemo to import/use the common keyframe
names (or keep one canonical name and replace the other), ensuring you remove
the duplicated definitions from both SyncDemoIdeas and RealtimeChatDemo and
reference the shared keyframes consistently.
- Around line 131-146: The nested setTimeouts in toggleFromLaptop (which updates
state via setItems and setPhoneHighlights) create timers that aren't cleared on
unmount; fix by creating refs (e.g., phoneHighlightTimeoutRef and
phoneHighlightClearRef) to store the timeout IDs, assign the IDs when calling
setTimeout inside toggleFromLaptop, clear any existing timeouts before setting
new ones, and add a useEffect cleanup that clears those refs' timers on unmount
to prevent leaks or stray callbacks.

In `@client/www/pages/sync-chat-demo.tsx`:
- Line 19: Replace the fixed 3-column Tailwind class on the grid containers so
they become responsive: find the divs whose className is "grid grid-cols-3
items-center gap-6" (the three grid sections currently at the three occurrences)
and change the grid columns to a responsive variant by substituting
"grid-cols-3" with "grid-cols-1 md:grid-cols-3"; apply the same change to the
other two grid sections mentioned in the comment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 36cbf193-05a4-4eb3-929c-55d1016e8ecd

📥 Commits

Reviewing files that changed from the base of the PR and between 16d209a and a6c1a1c.

⛔ Files ignored due to path filters (1)
  • client/www/public/img/landing/dog-post.jpg is excluded by !**/*.jpg
📒 Files selected for processing (5)
  • client/www/components/new-landing/BatteriesForAI.tsx
  • client/www/components/new-landing/BuiltForAI.tsx
  • client/www/components/new-landing/InstantUpdatesDemoIdeas.tsx
  • client/www/components/new-landing/SyncDemoIdeas.tsx
  • client/www/pages/sync-chat-demo.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/www/components/new-landing/BatteriesForAI.tsx

@stopachka stopachka changed the base branch from main to new-homepage March 7, 2026 02:30
@stopachka stopachka force-pushed the new-homepage-stopa-ideas branch from 3bb1699 to cb4ea36 Compare March 9, 2026 17:17
@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

View Vercel preview at instant-www-js-new-homepage-stopa-ideas-jsv.vercel.app.

@stopachka stopachka force-pushed the new-homepage-stopa-ideas branch 2 times, most recently from a6275b1 to 1be1390 Compare March 9, 2026 23:31
drew-harris and others added 3 commits March 9, 2026 16:54
sm

roar

roar

roar

roar

roar

roar

roar

roar

ROAR

roar

roar

roar

ROAR

roar

roar

roar

roar

roar

roar

roar

roar

ROAR

roar

roar

sm

roar

roar

roar

sm

roar

roar

roar

ROAR

roar

roar

ROAR

roar

roar

roar

ROAR

Clean up all the extra explorations

current

content update 1

ROAR

Standardize new landing typography components

roar

roar

roar
@drew-harris drew-harris force-pushed the new-homepage-stopa-ideas branch from 8fe774d to 442db97 Compare March 9, 2026 23:58
@drew-harris drew-harris changed the base branch from new-homepage to main March 10, 2026 17:55
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.

3 participants