Skip to content

Conversation

@SuperComboGamer
Copy link
Collaborator

@SuperComboGamer SuperComboGamer commented Dec 23, 2025

… expanded theme options.

Summary by CodeRabbit

Release Notes

  • New Features

    • Floating dock navigation for quick feature access
    • HUD display for streamlined project switching
    • Terminal settings panel for font customization (font family, size, line height)
    • Extended theme options including light, dark, and specialty variants
    • Claude usage tracking support
  • UI/Visual Updates

    • Redesigned components with translucent, glassy aesthetic
    • Enhanced animations and transitions throughout the application
    • Updated color palette with cyan and emerald accents
    • Improved hover effects and visual feedback on interactive elements

✏️ Tip: You can customize this high-level summary in your review settings.

@SuperComboGamer SuperComboGamer added the Do Not Merge Use this label if something should not be merged. label Dec 23, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Walkthrough

This PR introduces a comprehensive UI redesign across the apps/ui directory, adding new layout components (Shell, PageShell, FloatingDock, Hud, PrismField, NoiseOverlay), replacing the sidebar with a floating dock interface, applying a new Prism theme with glass morphism styling, and updating the global stylesheet with extensive color mappings, animations, typography, and custom utilities.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Added framer-motion and rehype-sanitize dependencies.
App Entry & Layout Wrapper
src/app.tsx, src/components/layout/shell.tsx
Wrapped App with new Shell component; Shell manages background layers (PrismField, NoiseOverlay) with optional toggle via showBackgroundElements prop.
New Layout Components
src/components/layout/floating-dock.tsx, src/components/layout/hud.tsx, src/components/layout/page-shell.tsx, src/components/layout/prism-field.tsx, src/components/layout/noise-overlay.tsx
Added 5 new presentational layout components: FloatingDock (animated bottom dock with project navigation), Hud (project dropdown pill), PageShell (fade/scale entrance animation wrapper), PrismField (animated background orbs and grid), NoiseOverlay (SVG noise filter). All use framer-motion and app store integration.
Sidebar Refactor
src/components/layout/sidebar.tsx
Replaced traditional sidebar layout with fragment-based structure; integrated Hud and FloatingDock; removed SidebarHeader, SidebarNavigation rendering; restructured project selection and dialog workflows; preserved existing dialog modals (DeleteProjectDialog, CreateSpecDialog, etc.).
View Wrappers
src/components/views/board-view.tsx, src/components/views/settings-view.tsx, src/components/views/terminal-view.tsx
Wrapped views with PageShell to provide entrance animations and constrained/full-width layouts. Terminal view refactored substantially: reorganized tab/toolbar structure, added Terminal Settings popover (font size, family, line height controls), introduced NewTabDropZone, updated styling and layout.
View Components & Headers
src/components/views/board-view/board-header.tsx, src/components/views/board-view/components/kanban-column.tsx, src/components/views/board-view/constants.ts, src/components/views/board-view/kanban-board.tsx, src/components/views/settings-view/components/settings-header.tsx, src/components/views/spec-view.tsx, src/components/views/spec-view/components/spec-header.tsx
Updated board header layout (heading structure, control groups, Auto Mode toggle styling); added optional columnClass prop to KanbanColumn for per-column styling; updated COLUMNS constant with new columnClass values; simplified spec-view styling; updated header backgrounds with new glass/translucent styling.
UI Component Styling
src/components/ui/badge.tsx, src/components/ui/button.tsx, src/components/ui/card.tsx, src/components/ui/dialog.tsx, src/components/ui/dropdown-menu.tsx, src/components/ui/input.tsx, src/components/ui/slider.tsx, src/components/ui/switch.tsx
Applied Prism theme styling: added new badge variants (prism, prism-orange, prism-green); expanded button variants (glass, prism-primary, prism-glass); updated card with translucent background and hover effects; refined dialog/dropdown/input styling with glass morphism (backdrop blur, translucent borders/backgrounds); recolored slider/switch with cyan accents and adjusted sizing.
App State
src/store/app-store.ts
Expanded ThemeMode union with 15+ new theme variants (forest, ocean, light, cream, github, etc.); added Claude usage tracking fields to AppState; extended Feature type with optional textFilePaths field; adjusted default state values (planning mode, test defaults, etc.).
Global Styling
src/styles/global.css
Major overhaul: added font imports and CSS variable stacks (--font-sans, --font-mono); introduced extensive Prism theme color tokens and semantic variables (--bg-deep, --glass-bg, --accent-*, --sidebar-*, --brand-*); added animation tokens and keyframes (--animate-in-fade, --animate-in-scale, etc.); updated shadow/transition tokens; reworked dark/light mode handling with system prefers-color-scheme detection; added glass/gradient utilities, scrollbar styling, column/badge/toggle classes; introduced new UI utilities for glassy surfaces and interactive elements.
Static HTML Mockup
index (25).html
Added complete static Kanban board UI page with dark glassy theme, sidebar navigation, kanban columns, floating action panel, lucide icon integration, and minimal inline JavaScript for wheel scrolling and keyboard shortcuts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • terminals-mpve #172: Modifies sidebar.tsx Terminal nav item placement and project-scoped navigation structure, directly overlapping with this PR's sidebar refactor.
  • refactor: sidebar #210: Extensive sidebar component exports and hooks refactoring; directly related to this PR's sidebar structural changes and new Hud/FloatingDock integration.
  • feat: add Claude usage tracking via CLI #203: Adds Claude usage tracking fields to app store and wires Claude usage UI components (e.g., board header); shares code-level changes with this PR's app-store.ts and board-header updates.

Suggested labels

Refactor, Do Not Merge

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: a floating dock component, visual effects (PrismField, NoiseOverlay), and expanded theme options are all present throughout the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch weird-sidebar-ever

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @SuperComboGamer, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a major overhaul to the application's user interface, focusing on a modern and dynamic visual experience. The changes introduce a new navigational paradigm with a floating dock and a top-left heads-up display, alongside captivating background animations and a consistent glassmorphism design language applied throughout the UI components. The goal is to provide users with a more engaging, intuitive, and visually appealing environment.

Highlights

  • New UI Layout: Introduced a completely new user interface layout, replacing the traditional sidebar with a floating navigation dock and a heads-up display (HUD) for project management. This streamlines navigation and provides quick access to key project information.
  • Enhanced Visual Effects: Implemented dynamic visual effects, including an animated 'Prism Field' background with moving orbs, a subtle noise overlay, and refined glassmorphism styles across various UI components like cards, buttons, and dialogs. This creates a more immersive and modern aesthetic.
  • Expanded Theme Options and Component Styling: Significantly expanded the available theme modes and updated the styling of core UI components (buttons, cards, inputs, sliders, switches, dialogs, dropdowns) to align with the new visual language, offering a more cohesive and polished look.
  • Dependency Updates: Added framer-motion for advanced animations and rehype-sanitize for improved content sanitization, enhancing both the visual experience and security of the application.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant UI overhaul, transitioning the application to a 'Prism' design language. Key changes include adding framer-motion for new animations and rehype-sanitize for markdown, and refactoring the main application layout to use new Shell, PageShell, PrismField, and NoiseOverlay components for animated backgrounds and consistent content wrapping. The traditional sidebar is replaced by a FloatingDock for navigation and a Hud for project management, both featuring interactive elements. Numerous UI components (Badge, Button, Card, Dialog, DropdownMenu, Input, Slider, Switch, TerminalTabButton) have been restyled with glassmorphism effects, updated colors (cyan, orange, green accents), and new hover/focus animations. The Kanban board's header and columns also received styling updates, including new column-specific classes. Default settings for terminal font, line height, scrollback, and planning mode were adjusted, and new theme modes were added to the app-store. Review comments highlight a regression in the 'Add Feature' button's hotkey functionality, suggest improving type safety for framer-motion's mouseX prop, and recommend a more performant approach for mouse tracking in PrismField using useMotionValue instead of useState with mousemove events. Additionally, a refactoring of the Sidebar component into a hidden div is noted as potentially confusing, with a suggestion to rename it to better reflect its new role as a global UI controller and to extract ProjectSelectorWithOptions for better maintainability. Finally, a !important declaration in the CSS for the .mono class is flagged as a potential specificity issue.

Comment on lines +92 to -101
{/* Add Feature Button */}
<button
onClick={onAddFeature}
hotkey={addFeatureShortcut}
hotkeyActive={false}
data-testid="add-feature-button"
className="btn-cyan px-6 py-2 rounded-xl text-xs font-black flex items-center gap-2 shadow-lg shadow-cyan-500/20"
>
<Plus className="w-4 h-4 mr-2" />
Add Feature
</HotkeyButton>
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The 'Add Feature' button was previously a HotkeyButton which supported a keyboard shortcut. The new implementation uses a standard <button>, which removes this shortcut functionality. This appears to be a regression. If the shortcut is still desired, you should use the HotkeyButton component or wrap the new button to restore the hotkey.

isActive,
onClick,
}: {
mouseX: any;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better type safety, the mouseX prop is typed as any. You should use the specific type from framer-motion, which is MotionValue<number>. You'll need to import MotionValue from framer-motion at the top of the file: import { ..., type MotionValue } from 'framer-motion';

Suggested change
mouseX: any;
mouseX: MotionValue<number>;

Comment on lines +5 to +17
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePosition({
x: e.clientX,
y: e.clientY,
});
};

window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Updating React state on every mousemove event causes frequent re-renders, which can be a performance bottleneck. It's better to use useMotionValue to track mouse coordinates without triggering re-renders.

You'll need to update your import:
import { motion, useMotionValue, useTransform } from 'framer-motion';

Then, you can use these motion values in useTransform hooks and apply them to the style prop of your motion.div components. This will be much more performant. For example:
const orb1X = useTransform(mouseX, (val) => val * 0.02);
<motion.div style={{ x: orb1X, y: orb1Y }} ... />

  const mouseX = useMotionValue(0);
  const mouseY = useMotionValue(0);

  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      mouseX.set(e.clientX);
      mouseY.set(e.clientY);
    };

    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, [mouseX, mouseY]);

Comment on lines +262 to 269
<div className="hidden">
<ProjectSelectorWithOptions
sidebarOpen={sidebarOpen}
sidebarOpen={true}
isProjectPickerOpen={isProjectPickerOpen}
setIsProjectPickerOpen={setIsProjectPickerOpen}
setShowDeleteProjectDialog={setShowDeleteProjectDialog}
/>

<SidebarNavigation
currentProject={currentProject}
sidebarOpen={sidebarOpen}
navSections={navSections}
isActiveRoute={isActiveRoute}
navigate={navigate}
/>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This refactoring moves the ProjectSelectorWithOptions into a hidden div. While the comment explains the intent is to preserve logic, this approach can be confusing and is not ideal for maintainability. The Sidebar component no longer renders a visual sidebar, but acts as a controller for various dialogs and global UI elements.

Consider renaming this Sidebar component to something more descriptive of its new role, like GlobalUIController or LayoutManager.

Additionally, it would be cleaner to extract the ProjectSelectorWithOptions and its related state into its own context or a dedicated component that is rendered at the root of the application, rather than hiding it here. This would make the component's purpose clearer and decouple it from the old sidebar structure.

}

.mono {
font-family: var(--font-mono) !important;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The use of !important here should be avoided if possible. It can lead to specificity wars and make debugging styles difficult. Consider increasing the specificity of this selector or applying the mono class more directly to ensure it takes precedence without needing !important.

.mono {
  font-family: var(--font-mono);
}

Copy link

@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

Caution

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

⚠️ Outside diff range comments (5)
apps/ui/src/components/views/spec-view/components/spec-header.tsx (2)

44-44: Fix invalid gradient class syntax.

The class bg-linear-to-r is not valid Tailwind CSS syntax. According to Tailwind v4 documentation, gradient backgrounds use bg-gradient-to-r for directional gradients or bg-linear-{angle} for angled gradients (e.g., bg-linear-45).

🔎 Proposed fix
-          <div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-linear-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md">
+          <div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-gradient-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md">

66-66: Fix invalid gradient class syntax.

The class bg-linear-to-r is not valid Tailwind CSS syntax. Use bg-gradient-to-r instead for proper gradient rendering.

🔎 Proposed fix
-          <div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-linear-to-r from-destructive/15 to-destructive/5 border border-destructive/30 shadow-lg backdrop-blur-md">
+          <div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-gradient-to-r from-destructive/15 to-destructive/5 border border-destructive/30 shadow-lg backdrop-blur-md">
apps/ui/src/store/app-store.ts (2)

27-62: Duplicate theme literals in union type.

'light' appears on both line 28 and line 47, and 'cream' appears on both line 42 and line 48. While TypeScript tolerates duplicate literals in union types, this is confusing and suggests a copy-paste oversight.

🔎 Proposed fix to remove duplicates
   | 'sunset'
   | 'gray'
   | 'forest'
   | 'ocean'
-  | 'light'
-  | 'cream'
   | 'solarizedlight'

2566-2572: Claude usage actions missing from AppActions interface.

The actions setClaudeRefreshInterval, setClaudeUsageLastUpdated, and setClaudeUsage are implemented here but are not declared in the AppActions interface (around lines 651-862). This causes TypeScript to miss type-checking on these action signatures and makes the API contract incomplete.

🔎 Add these declarations to the AppActions interface

Add the following to the AppActions interface (around line 860, before reset):

// Claude Usage Tracking actions
setClaudeRefreshInterval: (interval: number) => void;
setClaudeUsageLastUpdated: (timestamp: number) => void;
setClaudeUsage: (usage: ClaudeUsage | null) => void;
apps/ui/src/components/layout/sidebar.tsx (1)

36-248: Remove unused imports and dead code from the Sidebar component.

The component contains multiple unused imports and defined-but-unused code:

  • Unused imports (lines 15-18): CollapseToggleButton, SidebarHeader, ProjectActions, SidebarNavigation – imported but never rendered
  • Unused feature flags (line 59): hideWiki, hideRunningAgents – extracted but never referenced
  • Unused extraction (line 224): navSections from useNavigation – computed but never rendered
  • Unused function (lines 244-248): isActiveRoute – defined but never called
  • Ineffective hook (line 126): useSidebarAutoCollapse – manages sidebar state but no sidebar UI exists

Remove these unused items to reduce bundle size and maintenance burden.

🧹 Nitpick comments (17)
index (25).html (1)

90-103: Custom scrollbar only affects WebKit browsers.

The .custom-scrollbar styles use WebKit-specific pseudo-elements, so Firefox and other non-WebKit browsers will display default scrollbars, creating a visual inconsistency.

Consider adding Firefox support:

.custom-scrollbar {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
}

.custom-scrollbar::-webkit-scrollbar {
  width: 5px;
  height: 5px;
}
/* ... rest of webkit styles */
apps/ui/src/components/ui/card.tsx (1)

14-16: Consider layout stability with hover transform.

The hover effect hover:-translate-y-1 creates a lift animation that could cause layout shifts if cards are in a dense grid. Consider using transform-gpu and ensuring parent containers account for the transform to prevent content reflow.

🔎 Suggested improvement
-        'hover:-translate-y-1 hover:bg-white/[0.06] hover:border-white/15',
+        'hover:-translate-y-1 hover:bg-white/[0.06] hover:border-white/15 will-change-transform',

The will-change-transform hint can help the browser optimize the animation performance.

apps/ui/src/components/ui/input.tsx (1)

44-52: Consider consolidating focus-within styles.

The addon wrapper has multiple focus-within declarations that could be consolidated for better maintainability:

🔎 Suggested consolidation
      className={cn(
        'flex items-center h-9 w-full rounded-lg border border-input/50 bg-input/50 shadow-xs backdrop-blur-sm transition-all duration-300',
        'shadow-[inset_0_1px_2px_rgba(0,0,0,0.05)]',
-       'focus-within:bg-input/80 focus-within:border-ring/50',
-       'focus-within:border-ring focus-within:ring-ring/20 focus-within:ring-[4px]',
+       'focus-within:bg-input/80 focus-within:border-ring focus-within:ring-ring/20 focus-within:ring-[4px]',
        'has-[input:disabled]:opacity-50 has-[input:disabled]:cursor-not-allowed',
        'has-[input[aria-invalid]]:ring-destructive/20 has-[input[aria-invalid]]:border-destructive'
      )}

Note: The border-ring/50 on line 48 is overridden by border-ring on line 49, so the first declaration is redundant.

apps/ui/src/components/ui/button.tsx (1)

14-14: Consider shadow utility consistency.

Line 14 uses multiple shadow utilities (shadow-lg shadow-primary/20). In Tailwind CSS v4, when multiple values of the same utility are applied, later ones typically override earlier ones. This might not transition smoothly on hover.

🔎 Suggested approach

If you want the shadow color to transition smoothly, consider using CSS custom properties or ensure the shadow utility classes are composing correctly. Alternatively, you can use a single shadow declaration:

-        'bg-primary text-primary-foreground shadow-lg shadow-primary/20 hover:bg-primary/90 hover:shadow-primary/40 hover:-translate-y-0.5',
+        'bg-primary text-primary-foreground shadow-[0_10px_15px_-3px_rgba(var(--primary-rgb)/0.2)] hover:bg-primary/90 hover:shadow-[0_10px_15px_-3px_rgba(var(--primary-rgb)/0.4)] hover:-translate-y-0.5',

However, this is a minor optimization and the current approach likely works fine for most use cases.

apps/ui/src/styles/global.css (3)

1-1: Consider self-hosting fonts or using font-display: swap explicitly.

External Google Fonts can impact initial load performance and may raise privacy concerns (GDPR). The display=swap parameter helps, but self-hosting would eliminate the external dependency entirely.


1024-1035: Duplicate :root declarations may cause confusion and override earlier values.

This second :root block redefines --font-sans, --font-mono, --background, and --foreground. While CSS allows multiple :root blocks (later declarations win for same properties), this creates maintenance confusion. Consider consolidating these values into the primary :root block at line 176.

🔎 Suggested consolidation

Move the font definitions to the primary :root block and remove the duplicate:

 :root {
   /* Default to light mode (overridden by Prism values below for now as we pivot) */
   --radius: 0.75rem;
+
+  /* Font stacks */
+  --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
+  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;

   /* PRISM THEME VALUES (Base) */
   ...

Then remove lines 1024-1035.


1037-1040: Duplicate body styling outside @layer base.

There's already a body rule inside @layer base (lines 388-391). This additional body block at file end may override those styles unpredictably depending on CSS cascade order. Consider consolidating body styles in one location.

apps/ui/src/components/ui/switch.tsx (1)

13-23: Consider using CSS variables for theme consistency.

The switch uses hardcoded Tailwind colors (cyan-400, cyan-500, white/10) while global.css defines --accent-cyan and other theme variables. For consistency with the Prism theme system, consider using CSS variables:

🔎 Example using CSS variables
-      'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-cyan-500 data-[state=unchecked]:bg-white/10',
+      'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-secondary',

This would allow the switch to adapt to theme changes automatically.

apps/ui/src/components/layout/noise-overlay.tsx (1)

5-12: Potential SVG filter ID collision if component is rendered multiple times.

The filter uses a hardcoded id="noiseFilter". If this component is ever rendered multiple times (e.g., in different routes or portals), the duplicate IDs could cause unexpected rendering behavior where only one filter definition is used.

🔎 Consider using React.useId for unique filter ID
+import { useId } from 'react';
+
 export function NoiseOverlay() {
+  const filterId = useId();
+
   return (
     <div className="fixed inset-0 z-50 pointer-events-none opacity-[0.015] mix-blend-overlay">
       <svg className="w-full h-full">
-        <filter id="noiseFilter">
+        <filter id={filterId}>
           <feTurbulence
             type="fractalNoise"
             baseFrequency="0.80"
             numOctaves="3"
             stitchTiles="stitch"
           />
         </filter>
-        <rect width="100%" height="100%" filter="url(#noiseFilter)" />
+        <rect width="100%" height="100%" filter={`url(#${filterId})`} />
       </svg>
     </div>
   );
 }
apps/ui/src/components/layout/prism-field.tsx (2)

7-17: Consider throttling mouse position updates for better performance.

The mousemove event fires very frequently (potentially 60+ times per second), and each event triggers a state update. While Framer Motion's spring animation helps smooth this, throttling the updates would reduce unnecessary re-renders.

🔎 Throttled mouse tracking implementation
-import { motion } from 'framer-motion';
-import { useEffect, useState } from 'react';
+import { motion } from 'framer-motion';
+import { useEffect, useState, useCallback, useRef } from 'react';

 export function PrismField() {
   const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
+  const rafRef = useRef<number>();

   useEffect(() => {
     const handleMouseMove = (e: MouseEvent) => {
-      setMousePosition({
-        x: e.clientX,
-        y: e.clientY,
-      });
+      if (rafRef.current) return;
+      rafRef.current = requestAnimationFrame(() => {
+        setMousePosition({ x: e.clientX, y: e.clientY });
+        rafRef.current = undefined;
+      });
     };

     window.addEventListener('mousemove', handleMouseMove);
-    return () => window.removeEventListener('mousemove', handleMouseMove);
+    return () => {
+      window.removeEventListener('mousemove', handleMouseMove);
+      if (rafRef.current) cancelAnimationFrame(rafRef.current);
+    };
   }, []);

43-54: Respect prefers-reduced-motion for infinite animations.

The pulsing orb animation runs indefinitely with repeat: Infinity. Users who prefer reduced motion (for accessibility or battery reasons) should have these animations paused or reduced.

🔎 Add reduced motion support
+import { useReducedMotion } from 'framer-motion';

 export function PrismField() {
+  const shouldReduceMotion = useReducedMotion();
   const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

   // ... existing code ...

   <motion.div
     animate={{
-      scale: [1, 1.1, 1],
-      opacity: [0.3, 0.5, 0.3],
+      scale: shouldReduceMotion ? 1 : [1, 1.1, 1],
+      opacity: shouldReduceMotion ? 0.4 : [0.3, 0.5, 0.3],
     }}
     transition={{
       duration: 8,
-      repeat: Infinity,
+      repeat: shouldReduceMotion ? 0 : Infinity,
       ease: 'easeInOut',
     }}

Framer Motion's useReducedMotion hook respects the prefers-reduced-motion media query.

apps/ui/src/components/layout/hud.tsx (1)

49-49: Consider memoizing the click handler.

The onClick handler creates a new function on each render. For a small number of projects this is fine, but consider using useCallback or checking if p.id === currentProject.id before calling setCurrentProject to avoid unnecessary updates.

Optional optimization
           <DropdownMenuItem
             key={p.id}
-            onClick={() => setCurrentProject(p)}
+            onClick={() => p.id !== currentProject.id && setCurrentProject(p)}
             className="font-mono text-xs"
           >
apps/ui/src/components/layout/floating-dock.tsx (1)

17-59: Consider more robust active route detection.

The isActive determination on line 52 uses location.pathname.startsWith(item.path), which could lead to false positives if paths overlap (e.g., /profile would match /profiles). While current paths don't overlap, this is fragile as the application grows.

Consider using TanStack Router's built-in active matching or exact path comparison:

Recommended approach
+import { useMatchRoute } from '@tanstack/react-router';

 export function FloatingDock() {
   const mouseX = useMotionValue(Infinity);
   const navigate = useNavigate();
   const location = useLocation();
+  const matchRoute = useMatchRoute();
   const { currentProject } = useAppStore();

   // ... navItems ...

   return (
     // ...
         {navItems.map((item) => (
           <DockIcon
             key={item.id}
             mouseX={mouseX}
             icon={item.icon}
             path={item.path}
             label={item.label}
-            isActive={location.pathname.startsWith(item.path)}
+            isActive={!!matchRoute({ to: item.path })}
             onClick={() => navigate({ to: item.path })}
           />
         ))}
apps/ui/src/components/views/board-view/board-header.tsx (1)

93-99: Consider restoring keyboard shortcut visibility.

The Add Feature button was changed from HotkeyButton to a plain button, removing the visible keyboard shortcut hint. This is a UX regression for keyboard users who rely on these visual cues to learn and use shortcuts efficiently.

Consider either:

  1. Restoring the HotkeyButton component
  2. Adding a tooltip that displays the shortcut
  3. Including the shortcut in the button label (e.g., "ADD FEATURE (⌘K)")
apps/ui/src/components/views/board-view/constants.ts (1)

7-7: Consider omitting empty columnClass instead of empty string.

The backlog column uses an empty string '' for columnClass. Since the field is optional, it would be cleaner to omit it entirely when there's no value, rather than explicitly setting it to an empty string.

Minor cleanup
-    { id: 'backlog', title: 'Backlog', colorClass: 'bg-white/20', columnClass: '' },
+    { id: 'backlog', title: 'Backlog', colorClass: 'bg-white/20' },
apps/ui/src/components/layout/sidebar.tsx (1)

250-269: Component name no longer reflects its purpose.

The Sidebar function now renders a Hud, FloatingDock, and dialogs—no actual sidebar. Consider renaming to AppShell, LayoutController, or similar to reflect its new role as the top-level layout orchestrator.

The hidden ProjectSelectorWithOptions with sidebarOpen={true} hardcoded appears intentional to keep the dialog logic functional without visual sidebar elements.

apps/ui/src/components/views/terminal-view.tsx (1)

1490-1584: Terminal Settings popover is well-structured.

The settings UI provides clear controls for font size, font family, line height, and default run script. The toast notifications on value commit provide good feedback. One minor observation: the toast messages say "Restart terminal for changes to take effect" for font family and line height, but font size says "New terminals will use this size" - this inconsistency in messaging could confuse users about when changes apply.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 524a973 and 2eb92a0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (30)
  • apps/ui/package.json
  • apps/ui/src/app.tsx
  • apps/ui/src/components/layout/floating-dock.tsx
  • apps/ui/src/components/layout/hud.tsx
  • apps/ui/src/components/layout/noise-overlay.tsx
  • apps/ui/src/components/layout/page-shell.tsx
  • apps/ui/src/components/layout/prism-field.tsx
  • apps/ui/src/components/layout/shell.tsx
  • apps/ui/src/components/layout/sidebar.tsx
  • apps/ui/src/components/ui/badge.tsx
  • apps/ui/src/components/ui/button.tsx
  • apps/ui/src/components/ui/card.tsx
  • apps/ui/src/components/ui/dialog.tsx
  • apps/ui/src/components/ui/dropdown-menu.tsx
  • apps/ui/src/components/ui/input.tsx
  • apps/ui/src/components/ui/slider.tsx
  • apps/ui/src/components/ui/switch.tsx
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/src/components/views/board-view/components/kanban-column.tsx
  • apps/ui/src/components/views/board-view/constants.ts
  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/components/views/settings-view.tsx
  • apps/ui/src/components/views/settings-view/components/settings-header.tsx
  • apps/ui/src/components/views/spec-view.tsx
  • apps/ui/src/components/views/spec-view/components/spec-header.tsx
  • apps/ui/src/components/views/terminal-view.tsx
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/styles/global.css
  • index (25).html
🧰 Additional context used
🧬 Code graph analysis (8)
apps/ui/src/components/layout/hud.tsx (3)
apps/ui/src/components/ui/dropdown-menu.tsx (6)
  • DropdownMenu (284-284)
  • DropdownMenuTrigger (285-285)
  • DropdownMenuContent (286-286)
  • DropdownMenuLabel (290-290)
  • DropdownMenuSeparator (291-291)
  • DropdownMenuItem (287-287)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/lib/electron.ts (1)
  • setCurrentProject (2664-2670)
apps/ui/src/components/views/settings-view/components/settings-header.tsx (1)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/components/views/settings-view.tsx (4)
apps/ui/src/components/layout/page-shell.tsx (1)
  • PageShell (11-30)
apps/ui/src/components/views/settings-view/components/settings-header.tsx (1)
  • SettingsHeader (9-35)
apps/ui/src/components/views/settings-view/components/index.ts (2)
  • SettingsNavigation (4-4)
  • KeyboardMapDialog (2-2)
apps/ui/src/components/views/settings-view/config/navigation.ts (1)
  • NAV_ITEMS (22-32)
apps/ui/src/components/ui/switch.tsx (1)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/components/views/terminal-view.tsx (4)
apps/ui/src/components/layout/page-shell.tsx (1)
  • PageShell (11-30)
apps/ui/src/config/terminal-themes.ts (1)
  • TERMINAL_FONT_OPTIONS (47-55)
apps/ui/src/components/views/terminal-view/terminal-error-boundary.tsx (1)
  • TerminalErrorBoundary (22-85)
apps/ui/src/components/views/terminal-view/terminal-panel.tsx (1)
  • TerminalPanel (91-2166)
apps/ui/src/app.tsx (2)
apps/ui/src/components/layout/shell.tsx (1)
  • Shell (12-32)
apps/ui/src/components/splash-screen.tsx (1)
  • SplashScreen (38-282)
apps/ui/src/components/layout/page-shell.tsx (1)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/components/views/board-view/board-header.tsx (1)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
🔇 Additional comments (35)
apps/ui/src/components/views/spec-view.tsx (1)

118-118: LGTM! Background styling correctly delegated to Shell.

The removal of content-bg is appropriate as the new Shell/PageShell layout components now handle background rendering. The container retains all necessary layout classes.

apps/ui/src/components/views/spec-view/components/spec-header.tsx (1)

34-34: Verify border and background contrast across themes.

The extremely low opacity values (border-white/10 and bg-white/5) may not provide sufficient visual separation between the header and content, especially in light mode or on certain displays. Please verify that the header remains clearly distinguishable in all supported themes.

index (25).html (3)

14-36: LGTM: Well-structured CSS variables and base styles.

The CSS custom properties provide a clean foundation for the theme system, and the base styles appropriately establish the fixed-viewport layout.


118-136: Toggle switch is purely cosmetic.

The toggle track and thumb are static visual elements with no JavaScript functionality. Users clicking on the toggle at lines 357-359 will see no state change.

If this is intentional for the static demo, consider adding a comment in the HTML. Otherwise, implement toggle functionality:

document.querySelectorAll('.toggle-track').forEach(track => {
  track.addEventListener('click', () => {
    const thumb = track.querySelector('.toggle-thumb');
    const isActive = thumb.style.right === 'auto';
    thumb.style.right = isActive ? '3px' : 'auto';
    thumb.style.left = isActive ? '3px' : 'auto';
  });
});

711-720: LGTM: Well-implemented keyboard shortcut.

The "/" search shortcut is properly implemented with appropriate checks to prevent interference when the input is already focused and to prevent the "/" character from appearing in the field.

apps/ui/src/components/ui/dropdown-menu.tsx (1)

7-76: LGTM! Type-safe wrappers for React 19 compatibility.

The type-safe wrapper approach for Radix UI primitives properly addresses React 19 compatibility by explicitly typing children and other props. This ensures proper type inference and prevents TypeScript errors with the newer React version.

apps/ui/src/components/ui/badge.tsx (1)

27-33: LGTM! Consistent Prism variant additions.

The new Prism badge variants follow a consistent pattern with appropriate opacity levels, hover states, and typography choices. The naming convention (prism, prism-orange, prism-green) is clear and aligns with the broader Prism theme introduced in this PR.

apps/ui/src/components/ui/dialog.tsx (1)

102-110: LGTM! Enhanced dialog surface styling.

The updated dialog styling with bg-card/90, backdrop-blur-xl, and refined shadows creates a modern glass morphism effect while maintaining readability. The smoother animations (300ms with ease-out) provide better visual feedback.

apps/ui/src/components/ui/slider.tsx (1)

53-56: LGTM! Consistent Prism theme styling.

The slider styling updates align well with the broader Prism theme, using cyan accent colors and translucent backgrounds. The removal of the default shadow and addition of specific hover/focus states improves visual clarity.

apps/ui/src/components/ui/button.tsx (1)

8-44: LGTM! Comprehensive button variant expansion.

The expanded button variants successfully integrate the Prism design language with:

  • Consistent glass morphism effects (glass, prism-glass)
  • Bold accent variants (prism-primary)
  • Improved hover states with lift animations
  • Smoother transitions (300ms duration)

The size mappings are well-defined and the new variants provide good flexibility for the UI redesign.

apps/ui/package.json (1)

72-72: Both packages verified as valid with no security vulnerabilities detected.

framer-motion@12.23.26 exists and has no direct vulnerabilities in Snyk's database. ReversingLabs scanning detected no risks. rehype-sanitize@6.0.0 also has no direct vulnerabilities and poses no security risk. Both packages are safe to use in their current versions.

apps/ui/src/components/ui/input.tsx (1)

18-32: Verify input border and placeholder contrast against dark background context.

WCAG 2.1 requires input borders to meet 3:1 contrast ratio against adjacent colors. The implementation uses:

  • border-white/10 (10% opacity) for the default border
  • border-input/50 for addon variant containers
  • placeholder:text-muted-foreground/50 for placeholder text

Verify these color values (defined as CSS custom properties) provide sufficient contrast against the dark background. The focus ring at ring-[4px] meets accessibility requirements and requires no changes.

apps/ui/src/styles/global.css (2)

125-173: LGTM!

The animation tokens and keyframes are well-structured with appropriate cubic-bezier easing for smooth UI transitions. The in/out fade and scale animations provide a cohesive animation system.


468-484: LGTM!

The glass morphism utilities provide a clean abstraction for the Prism theme's translucent aesthetic. The progression from subtle to strong variants is logically structured.

apps/ui/src/app.tsx (1)

31-36: LGTM!

Clean integration of the Shell wrapper. The SplashScreen correctly renders inside Shell, ensuring it appears above the background layers while maintaining proper z-index hierarchy. The Shell's z-10 content wrapper (from the relevant snippet) ensures router content stays above the animated background.

apps/ui/src/components/views/board-view/kanban-board.tsx (1)

105-105: LGTM!

Clean integration of the columnClass prop, enabling per-column Prism styling. The column classes (col-in-progress, col-waiting, col-verified) defined in global.css provide subtle visual differentiation with cyan/orange/green accents.

apps/ui/src/components/views/settings-view/components/settings-header.tsx (1)

14-14: LGTM!

The simplified styling aligns well with the Prism theme's glass morphism approach. The bg-white/5 backdrop-blur-xl border-white/10 pattern is consistent with other components in this PR.

apps/ui/src/components/layout/prism-field.tsx (1)

19-67: LGTM on visual implementation!

The layered approach with deep space base, animated orbs, grid overlay, and vignette creates a polished, modern aesthetic. The use of pointer-events-none correctly ensures the background doesn't interfere with user interactions.

apps/ui/src/components/layout/shell.tsx (2)

1-10: LGTM - Clean imports and interface definition.

The component interface is well-structured with appropriate typing for React nodes and optional props. The default value for showBackgroundElements provides good flexibility for different contexts.


14-29: The height constraints are intentional and don't conflict. The Shell component is designed as a full-screen layout container where min-h-screen on the outer container ensures minimum viewport coverage and h-screen on the inner content wrapper establishes the fixed viewport-height layout. This pattern supports the sidebar-based app layout shown in the root route (__root.tsx), where individual pages handle their own scroll behavior internally through the overflow-hidden content wrapper. No changes needed.

apps/ui/src/components/views/board-view/components/kanban-column.tsx (1)

10-10: LGTM - Clean extension for column styling.

The addition of the optional columnClass prop provides a flexible styling hook without breaking existing usage. The prop is correctly threaded through the component and applied in the appropriate position within the class composition.

Also applies to: 25-25, 48-49

apps/ui/src/components/layout/page-shell.tsx (1)

1-9: LGTM - Well-structured component interface.

The imports and prop interface are clean and appropriately typed. The fullWidth prop provides good layout flexibility.

apps/ui/src/components/layout/hud.tsx (3)

1-17: LGTM - Clean component interface and imports.

The component interface is well-defined with appropriate callback props for external actions.


19-22: LGTM - Appropriate guard clause.

Returning null when there's no current project is the correct behavior for this context-dependent component.


46-54: Verify the 5-project limit is intentional.

The dropdown only displays the first 5 projects (projects.slice(0, 5)). Users with more projects must navigate to "All Projects..." to access the rest. This design choice should be intentional and documented, as power users may find it limiting.

Consider whether this limitation aligns with the expected user workflow, or if a "Show more..." option would improve usability.

apps/ui/src/components/views/settings-view.tsx (1)

3-3: LGTM - Clean PageShell integration.

The SettingsView is now properly wrapped in the PageShell layout component, maintaining all existing functionality while adopting the new layout system. The addition of h-full to the inner container aligns with the PageShell's layout requirements.

Also applies to: 160-192

apps/ui/src/components/views/board-view/constants.ts (1)

12-12: CSS classes are properly defined.

All three custom column classes (col-in-progress, col-waiting, col-verified) are defined in the global stylesheet at apps/ui/src/styles/global.css and will apply their intended styling correctly.

apps/ui/src/components/views/board-view/board-header.tsx (1)

65-65: Remove - 'mono' class is a valid custom utility

The mono class is intentionally defined as a custom utility in apps/ui/src/styles/global.css and applies font-family: var(--font-mono). No change needed.

Likely an incorrect or invalid review comment.

apps/ui/src/components/layout/sidebar.tsx (1)

21-22: New layout component imports look correct.

The imports for Hud and FloatingDock are properly added to support the new UI layout structure.

apps/ui/src/store/app-store.ts (2)

7-7: Import addition looks correct.

The FeatureTextFilePath import aligns with the new textFilePaths property added to the Feature interface.


922-964: Initial state updates look reasonable.

The default values for maxConcurrency, kanbanCardDetailLevel, boardViewMode, terminal settings, and Claude usage tracking are sensible defaults. The defaultPlanningMode: 'lite' and defaultRequirePlanApproval: true changes appear intentional for the new UI flow.

apps/ui/src/components/views/terminal-view.tsx (4)

49-49: PageShell import correctly added.

This import supports wrapping the terminal view in the new layout component.


144-150: Tab styling updated to cyan theme.

The styling changes from the previous color scheme to cyan (border-cyan-500, text-cyan-100, ring-cyan-500/50, etc.) are consistent with the broader Prism theme introduced in this PR.


1418-1427: Good restructuring with PageShell and DndContext.

Wrapping the terminal view in PageShell fullWidth integrates well with the new layout system while preserving all drag-and-drop functionality.


1649-1667: Drag overlay implementation is correct.

The DragOverlay with proper drop animation and dynamic text based on dragOverTabId provides good visual feedback during drag operations.

isActive,
onClick,
}: {
mouseX: any;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the any type for mouseX parameter.

The mouseX parameter is typed as any, which bypasses TypeScript's type checking and could lead to runtime errors. It should be typed as MotionValue<number>.

🔎 Type-safe fix
+import { motion, useMotionValue, useSpring, useTransform, MotionValue } from 'framer-motion';

 function DockIcon({
   mouseX,
   icon: Icon,
   path,
   label,
   isActive,
   onClick,
 }: {
-  mouseX: any;
+  mouseX: MotionValue<number>;
   icon: LucideIcon;
   path: string;
   label: string;
   isActive: boolean;
   onClick: () => void;
 }) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mouseX: any;
import { motion, useMotionValue, useSpring, useTransform, MotionValue } from 'framer-motion';
function DockIcon({
mouseX,
icon: Icon,
path,
label,
isActive,
onClick,
}: {
mouseX: MotionValue<number>;
icon: LucideIcon;
path: string;
label: string;
isActive: boolean;
onClick: () => void;
}) {
🤖 Prompt for AI Agents
In apps/ui/src/components/layout/floating-dock.tsx around line 69, replace the
loose any type for mouseX with a strongly typed MotionValue<number>; update the
declaration from "mouseX: any" to "mouseX: MotionValue<number>" and add/import
the type from 'framer-motion' (e.g., import type { MotionValue } from
'framer-motion') if not already present, then ensure any usages align with
MotionValue methods (.get(), onChange, etc.).

Comment on lines +83 to +84
const widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40]);
const width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add reduced motion support for dock animations.

The spring-based width animation does not respect prefers-reduced-motion, which can cause discomfort for users with motion sensitivity. This affects the interactive scaling behavior when hovering over dock icons.

🔎 Recommended fix
+import { motion, useMotionValue, useSpring, useTransform, useReducedMotion } from 'framer-motion';

 function DockIcon({
   mouseX,
   icon: Icon,
   path,
   label,
   isActive,
   onClick,
 }: {
   mouseX: MotionValue<number>;
   icon: LucideIcon;
   path: string;
   label: string;
   isActive: boolean;
   onClick: () => void;
 }) {
   const ref = useRef<HTMLDivElement>(null);
+  const shouldReduceMotion = useReducedMotion();

   const distance = useTransform(mouseX, (val: number) => {
     const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };
     return val - bounds.x - bounds.width / 2;
   });

-  const widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40]);
-  const width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 });
+  const widthSync = useTransform(
+    distance,
+    [-150, 0, 150],
+    shouldReduceMotion ? [60, 60, 60] : [40, 80, 40]
+  );
+  const width = useSpring(widthSync, {
+    mass: 0.1,
+    stiffness: shouldReduceMotion ? 300 : 150,
+    damping: shouldReduceMotion ? 30 : 12
+  });

Also applies to: 89-89

🤖 Prompt for AI Agents
In apps/ui/src/components/layout/floating-dock.tsx around lines 83-84 (and also
line 89), the spring-based width animation ignores user prefers-reduced-motion;
detect reduced motion (e.g. using framer-motion's useReducedMotion or
window.matchMedia) and, when reduced motion is requested, avoid the spring by
using the raw transformed value (or a static width) instead of useSpring;
otherwise keep the existing useSpring behavior. Ensure you import and call the
reduced-motion check at the top of the component and branch so that width =
useSpring(widthSync, ...) is only used when reducedMotion is false, falling back
to width = widthSync (or a non-animated motion value) when reducedMotion is
true.

@@ -0,0 +1,17 @@
export function NoiseOverlay() {
return (
<div className="fixed inset-0 z-50 pointer-events-none opacity-[0.015] mix-blend-overlay">
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check z-index usage in the codebase, especially for modals, dialogs, dropdowns, toasts
rg -n "z-\[?[0-9]+" apps/ui/src --type=tsx --type=css -A 2 -B 2 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Check z-index usage in the codebase
rg "z-\[?[0-9]+" apps/ui/src -g "*.tsx" -g "*.ts" -g "*.css" -A 1 -B 1 | head -150

Repository: AutoMaker-Org/automaker

Length of output: 13647


Lower z-index recommended for decorative overlay.

The noise overlay uses z-50, which is the same level as modals, dialogs, dropdowns, tooltips, and other interactive components throughout the codebase. While the overlay's opacity-[0.015] and mix-blend-overlay properties mitigate visual conflicts, using z-50 for a purely decorative background element isn't ideal. Consider using z-10 or lower to maintain a clear separation from interactive UI components.

🤖 Prompt for AI Agents
In apps/ui/src/components/layout/noise-overlay.tsx around line 3, the decorative
noise overlay currently uses class "z-50", which places it at the same stacking
level as modals and other interactive components; change the z-index to a much
lower value (for example "z-10" or lower) in the className so the overlay stays
behind interactive UI elements while keeping the existing opacity and blend
settings.

Comment on lines +14 to +27
<motion.div
initial={{ opacity: 0, scale: 0.98, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 0.4, ease: [0.2, 0, 0, 1] }}
className={cn(
'w-full h-full rounded-3xl overflow-hidden',
'bg-black/20 backdrop-blur-2xl border border-white/5 shadow-2xl',
'flex flex-col',
!fullWidth && 'max-w-7xl mx-auto',
className
)}
>
{children}
</motion.div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add reduced motion support for accessibility compliance.

The entrance animation does not respect the prefers-reduced-motion user preference, which can cause discomfort for users with vestibular disorders or motion sensitivity. This is a WCAG 2.1 accessibility concern.

🔎 Recommended fix using Framer Motion's useReducedMotion
 import { ReactNode } from 'react';
 import { cn } from '@/lib/utils';
-import { motion } from 'framer-motion';
+import { motion, useReducedMotion } from 'framer-motion';

 interface PageShellProps {
   children: ReactNode;
   className?: string;
   fullWidth?: boolean;
 }

 export function PageShell({ children, className, fullWidth = false }: PageShellProps) {
+  const shouldReduceMotion = useReducedMotion();
+
   return (
     <div className="relative w-full h-full pt-16 pb-24 px-6 overflow-hidden">
       <motion.div
-        initial={{ opacity: 0, scale: 0.98, y: 10 }}
-        animate={{ opacity: 1, scale: 1, y: 0 }}
-        transition={{ duration: 0.4, ease: [0.2, 0, 0, 1] }}
+        initial={shouldReduceMotion ? false : { opacity: 0, scale: 0.98, y: 10 }}
+        animate={{ opacity: 1, scale: 1, y: 0 }}
+        transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.4, ease: [0.2, 0, 0, 1] }}
         className={cn(
🤖 Prompt for AI Agents
In apps/ui/src/components/layout/page-shell.tsx around lines 14 to 27, the
motion.div animation ignores the user's prefers-reduced-motion setting; import
Framer Motion's useReducedMotion, call it, and if it returns true replace the
entrance animation with a non-animating state (e.g. set initial and animate to
the same final values or skip transform/transition or set transition duration to
0) so the element renders without movement; keep the animated variants for the
normal case and apply them conditionally based on the reduced-motion flag.

Comment on lines +69 to +72
'fixed inset-0 z-50 bg-black/40 backdrop-blur-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'duration-200',
'duration-300',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Review reduced overlay opacity for accessibility.

The overlay backdrop opacity was reduced from bg-black/60 to bg-black/40, which decreases visual separation between the dialog and underlying content. This lighter overlay might:

  1. Reduce focus on the dialog content
  2. Make it harder for users to distinguish modal vs. non-modal states
  3. Potentially impact accessibility for users with visual impairments

Consider user testing this change to ensure the lighter overlay still provides adequate visual hierarchy and doesn't diminish the modal's prominence.

🤖 Prompt for AI Agents
In apps/ui/src/components/ui/dialog.tsx around lines 69 to 72 the overlay CSS
was changed from bg-black/60 to bg-black/40 which reduces visual separation and
may harm accessibility; restore the overlay to a stronger opacity (e.g.,
bg-black/60) or make the opacity configurable via a prop/theme token, then run
quick visual/accessibility checks (contrast and keyboard focus testing) to
confirm the dialog remains clearly prominent.

Comment on lines +246 to +252
<a href="#" class="nav-active flex items-center justify-between px-6 py-3 text-sm">
<div class="flex items-center gap-3">
<i data-lucide="layout-grid" class="w-4 h-4"></i
><span class="font-medium">Kanban Board</span>
</div>
<span class="shortcut-badge">E</span>
</a>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Improve keyboard accessibility and focus management.

The navigation links use href="#" which causes page jumps and provides poor keyboard accessibility. Additionally, no focus styles are visible for keyboard navigation.

🔎 Recommended fixes
  1. Change anchor tags to buttons or use href="javascript:void(0)":
-            <a href="#" class="nav-active flex items-center justify-between px-6 py-3 text-sm">
+            <button type="button" class="nav-active flex items-center justify-between px-6 py-3 text-sm w-full text-left">
  1. Add focus-visible styles to CSS:
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--accent-cyan);
  outline-offset: 2px;
}
  1. Add aria-label attributes to icon-only buttons:
-            <i data-lucide="plus" class="w-3.5 h-3.5"></i> New
+            <i data-lucide="plus" class="w-3.5 h-3.5" aria-hidden="true"></i> New
🤖 Prompt for AI Agents
In index (25).html around lines 246 to 252, the anchor uses href="#" causing
page jumps and lacks visible focus states and accessibility labels; replace the
anchor with a <button> (or use href="javascript:void(0)" if it must remain an
anchor) to prevent page jumps, add an aria-label describing the target (e.g.,
"Open Kanban Board") to the interactive element, and ensure keyboard focus
styles are defined by adding focus-visible CSS rules so the element shows a
clear outline when navigated by keyboard.

Comment on lines +382 to +389
<input
type="text"
placeholder="Search features by keyword..."
class="w-full bg-white/5 border border-white/10 rounded-2xl py-2.5 pl-12 pr-12 text-sm focus:outline-none focus:border-cyan-500/50 transition-all mono"
/>
<div class="absolute right-4 top-1/2 -translate-y-1/2">
<span class="shortcut-badge">/</span>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessible label for search input.

The search input lacks a <label> element or aria-label attribute, making it inaccessible to screen reader users. While there's a placeholder and visual search icon, these don't provide proper semantic labeling.

🔎 Recommended fix

Add an aria-label to the input:

          <input
            type="text"
            placeholder="Search features by keyword..."
+           aria-label="Search features by keyword"
            class="w-full bg-white/5 border border-white/10 rounded-2xl py-2.5 pl-12 pr-12 text-sm focus:outline-none focus:border-cyan-500/50 transition-all mono"
          />

Alternatively, use a visually-hidden label:

<label for="search-input" class="sr-only">Search features</label>
<input id="search-input" type="text" ... />

And add the sr-only utility to CSS:

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
📝 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
<input
type="text"
placeholder="Search features by keyword..."
class="w-full bg-white/5 border border-white/10 rounded-2xl py-2.5 pl-12 pr-12 text-sm focus:outline-none focus:border-cyan-500/50 transition-all mono"
/>
<div class="absolute right-4 top-1/2 -translate-y-1/2">
<span class="shortcut-badge">/</span>
</div>
<input
type="text"
placeholder="Search features by keyword..."
aria-label="Search features by keyword"
class="w-full bg-white/5 border border-white/10 rounded-2xl py-2.5 pl-12 pr-12 text-sm focus:outline-none focus:border-cyan-500/50 transition-all mono"
/>
<div class="absolute right-4 top-1/2 -translate-y-1/2">
<span class="shortcut-badge">/</span>
</div>
🤖 Prompt for AI Agents
index (25).html around lines 382 to 389: the search input lacks an accessible
label for screen readers; fix by adding an aria-label (e.g., aria-label="Search
features") directly on the input or add a hidden label element paired via id
(give input id="search-input" and include a visually-hidden <label
for="search-input">Search features</label>), and if using the hidden label
ensure the .sr-only CSS utility is present in your stylesheet to keep it
visually hidden but accessible.

Comment on lines +432 to +466
<div class="glass kanban-card flex flex-col gap-4 group relative">
<div
class="absolute top-5 right-6 opacity-0 group-hover:opacity-100 transition-opacity"
>
<i
data-lucide="trash-2"
class="w-4 h-4 text-slate-600 hover:text-red-400 cursor-pointer"
></i>
</div>
<p class="text-[13px] text-slate-300 leading-relaxed font-medium line-clamp-3">
Create a bouncing animation using CSS keyframes that simulates elastic motion...
</p>
<div
class="flex items-center gap-1 text-[10px] text-slate-500 -mt-1 cursor-pointer hover:text-slate-300"
>
<i data-lucide="chevron-down" class="w-3 h-3"></i> More
</div>
<div
class="text-[10px] font-bold text-cyan-400/80 mono flex items-center gap-1.5 uppercase tracking-tight"
>
<i data-lucide="layers" class="w-3.5 h-3.5"></i> Opus 4.2
</div>
<div class="flex gap-2">
<button
class="flex-1 glass py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:bg-white/10 transition"
>
<i data-lucide="edit-3" class="w-3.5 h-3.5"></i> Edit
</button>
<button
class="flex-1 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/20 py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 transition"
>
<i data-lucide="target" class="w-3.5 h-3.5"></i> Make
</button>
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add ARIA attributes to interactive elements.

The kanban cards contain multiple interactive elements (trash icon, edit button, make button) that lack accessible names and proper ARIA attributes. Icon-only buttons need aria-label attributes, and decorative icons should have aria-hidden="true".

🔎 Recommended improvements
                <div
                  class="absolute top-5 right-6 opacity-0 group-hover:opacity-100 transition-opacity"
                >
                  <i
                    data-lucide="trash-2"
+                   aria-hidden="true"
                    class="w-4 h-4 text-slate-600 hover:text-red-400 cursor-pointer"
                  ></i>
+                 <button type="button" aria-label="Delete card" class="absolute inset-0 opacity-0"></button>
                </div>

...

                <div class="flex gap-2">
                  <button
+                   type="button"
+                   aria-label="Edit feature"
                    class="flex-1 glass py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:bg-white/10 transition"
                  >
-                   <i data-lucide="edit-3" class="w-3.5 h-3.5"></i> Edit
+                   <i data-lucide="edit-3" class="w-3.5 h-3.5" aria-hidden="true"></i> Edit
                  </button>
                  <button
+                   type="button"
+                   aria-label="Make feature"
                    class="flex-1 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/20 py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 transition"
                  >
-                   <i data-lucide="target" class="w-3.5 h-3.5"></i> Make
+                   <i data-lucide="target" class="w-3.5 h-3.5" aria-hidden="true"></i> Make
                  </button>
                </div>

Apply similar patterns to all interactive elements throughout the Kanban columns.

📝 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 class="glass kanban-card flex flex-col gap-4 group relative">
<div
class="absolute top-5 right-6 opacity-0 group-hover:opacity-100 transition-opacity"
>
<i
data-lucide="trash-2"
class="w-4 h-4 text-slate-600 hover:text-red-400 cursor-pointer"
></i>
</div>
<p class="text-[13px] text-slate-300 leading-relaxed font-medium line-clamp-3">
Create a bouncing animation using CSS keyframes that simulates elastic motion...
</p>
<div
class="flex items-center gap-1 text-[10px] text-slate-500 -mt-1 cursor-pointer hover:text-slate-300"
>
<i data-lucide="chevron-down" class="w-3 h-3"></i> More
</div>
<div
class="text-[10px] font-bold text-cyan-400/80 mono flex items-center gap-1.5 uppercase tracking-tight"
>
<i data-lucide="layers" class="w-3.5 h-3.5"></i> Opus 4.2
</div>
<div class="flex gap-2">
<button
class="flex-1 glass py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:bg-white/10 transition"
>
<i data-lucide="edit-3" class="w-3.5 h-3.5"></i> Edit
</button>
<button
class="flex-1 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/20 py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 transition"
>
<i data-lucide="target" class="w-3.5 h-3.5"></i> Make
</button>
</div>
</div>
<div class="glass kanban-card flex flex-col gap-4 group relative">
<div
class="absolute top-5 right-6 opacity-0 group-hover:opacity-100 transition-opacity"
>
<i
data-lucide="trash-2"
aria-hidden="true"
class="w-4 h-4 text-slate-600 hover:text-red-400 cursor-pointer"
></i>
<button type="button" aria-label="Delete card" class="absolute inset-0 opacity-0"></button>
</div>
<p class="text-[13px] text-slate-300 leading-relaxed font-medium line-clamp-3">
Create a bouncing animation using CSS keyframes that simulates elastic motion...
</p>
<div
class="flex items-center gap-1 text-[10px] text-slate-500 -mt-1 cursor-pointer hover:text-slate-300"
>
<i data-lucide="chevron-down" class="w-3 h-3"></i> More
</div>
<div
class="text-[10px] font-bold text-cyan-400/80 mono flex items-center gap-1.5 uppercase tracking-tight"
>
<i data-lucide="layers" class="w-3.5 h-3.5"></i> Opus 4.2
</div>
<div class="flex gap-2">
<button
type="button"
aria-label="Edit feature"
class="flex-1 glass py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:bg-white/10 transition"
>
<i data-lucide="edit-3" class="w-3.5 h-3.5" aria-hidden="true"></i> Edit
</button>
<button
type="button"
aria-label="Make feature"
class="flex-1 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/20 py-2.5 rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 transition"
>
<i data-lucide="target" class="w-3.5 h-3.5" aria-hidden="true"></i> Make
</button>
</div>
</div>
🤖 Prompt for AI Agents
index (25).html lines 432-466: the kanban card contains icon-only and decorative
icons missing ARIA and keyboard affordances; make the trash icon a proper
interactive control (preferably replace the <i> with a <button> or add
role="button" tabindex="0" and an explicit aria-label like "Delete card") and
ensure it supports keyboard activation, mark decorative icons used alongside
visible text (the chevron-down and layers icons and the <i> inside Edit/Make) as
aria-hidden="true" so screen readers ignore them, and add aria-labels to any
remaining icon-only controls across the kanban cards (use concise labels like
"Delete card", "More actions") while keeping the visible-text buttons as-is but
moving aria-hidden="true" to their inner <i> elements.

Comment on lines +681 to +692
<div class="fixed bottom-8 right-8 flex flex-col gap-4 z-50">
<button
class="w-12 h-12 glass rounded-2xl flex items-center justify-center text-slate-400 hover:text-white transition shadow-2xl"
>
<i data-lucide="history" class="w-5 h-5"></i>
</button>
<button
class="w-14 h-14 bg-cyan-500 rounded-2xl flex items-center justify-center text-slate-950 shadow-2xl shadow-cyan-500/40 hover:scale-110 active:scale-95 transition-all"
>
<i data-lucide="message-square" class="w-7 h-7 stroke-[2.5px]"></i>
</button>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add accessible labels to floating action buttons.

The floating UI buttons are icon-only and lack accessible names. Screen reader users won't know their purpose.

🔎 Recommended fix
    <div class="fixed bottom-8 right-8 flex flex-col gap-4 z-50">
      <button
+       type="button"
+       aria-label="View history"
        class="w-12 h-12 glass rounded-2xl flex items-center justify-center text-slate-400 hover:text-white transition shadow-2xl"
      >
-       <i data-lucide="history" class="w-5 h-5"></i>
+       <i data-lucide="history" class="w-5 h-5" aria-hidden="true"></i>
      </button>
      <button
+       type="button"
+       aria-label="Open messages"
        class="w-14 h-14 bg-cyan-500 rounded-2xl flex items-center justify-center text-slate-950 shadow-2xl shadow-cyan-500/40 hover:scale-110 active:scale-95 transition-all"
      >
-       <i data-lucide="message-square" class="w-7 h-7 stroke-[2.5px]"></i>
+       <i data-lucide="message-square" class="w-7 h-7 stroke-[2.5px]" aria-hidden="true"></i>
      </button>
    </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 class="fixed bottom-8 right-8 flex flex-col gap-4 z-50">
<button
class="w-12 h-12 glass rounded-2xl flex items-center justify-center text-slate-400 hover:text-white transition shadow-2xl"
>
<i data-lucide="history" class="w-5 h-5"></i>
</button>
<button
class="w-14 h-14 bg-cyan-500 rounded-2xl flex items-center justify-center text-slate-950 shadow-2xl shadow-cyan-500/40 hover:scale-110 active:scale-95 transition-all"
>
<i data-lucide="message-square" class="w-7 h-7 stroke-[2.5px]"></i>
</button>
</div>
<div class="fixed bottom-8 right-8 flex flex-col gap-4 z-50">
<button
type="button"
aria-label="View history"
class="w-12 h-12 glass rounded-2xl flex items-center justify-center text-slate-400 hover:text-white transition shadow-2xl"
>
<i data-lucide="history" class="w-5 h-5" aria-hidden="true"></i>
</button>
<button
type="button"
aria-label="Open messages"
class="w-14 h-14 bg-cyan-500 rounded-2xl flex items-center justify-center text-slate-950 shadow-2xl shadow-cyan-500/40 hover:scale-110 active:scale-95 transition-all"
>
<i data-lucide="message-square" class="w-7 h-7 stroke-[2.5px]" aria-hidden="true"></i>
</button>
</div>
🤖 Prompt for AI Agents
In index (25).html around lines 681 to 692 the two floating action buttons are
icon-only and missing accessible names; add an accessible name to each button
(e.g., aria-label="Open history" and aria-label="Open chat" or aria-labelledby
to reference hidden text), mark decorative icons as aria-hidden="true", and
ensure buttons include type="button" (or appropriate type) so screen readers can
announce their purpose; optionally add a visible or visually-hidden span with
descriptive text if you prefer labeling via DOM instead of aria-label.

Comment on lines +698 to +709
// Smooth horizontal scroll for the board
const board = document.querySelector('.overflow-x-auto');
board.addEventListener(
'wheel',
(evt) => {
if (evt.deltaY !== 0) {
evt.preventDefault();
board.scrollLeft += evt.deltaY * 1.5;
}
},
{ passive: false }
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Horizontal scroll hijacking may harm usability.

The wheel event listener converts all vertical scroll input to horizontal scrolling with preventDefault(). This creates several issues:

  1. Unexpected behavior: Users attempting to scroll the page vertically will instead scroll horizontally
  2. Accessibility concern: Users who rely on vertical scrolling (especially with assistive devices) may struggle to navigate
  3. Trackpad users: Natural two-finger swipe gestures are overridden
🔎 Recommended improvements

Consider these alternatives:

Option 1: Only convert vertical scroll when horizontal scroll is possible and intentional:

      const board = document.querySelector('.overflow-x-auto');
      board.addEventListener(
        'wheel',
        (evt) => {
-         if (evt.deltaY !== 0) {
+         if (evt.deltaY !== 0 && evt.shiftKey) {  // Only when Shift is held
            evt.preventDefault();
            board.scrollLeft += evt.deltaY * 1.5;
          }
        },
        { passive: false }
      );

Option 2: Detect trackpad horizontal scroll and enhance it:

      board.addEventListener(
        'wheel',
        (evt) => {
          // If already scrolling horizontally, enhance it
          if (Math.abs(evt.deltaX) > Math.abs(evt.deltaY)) {
            evt.preventDefault();
            board.scrollLeft += evt.deltaX * 1.5;
          }
        },
        { passive: false }
      );

Option 3: Remove the scroll hijacking entirely and let users scroll naturally. Add arrow buttons for explicit horizontal navigation.

📝 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
// Smooth horizontal scroll for the board
const board = document.querySelector('.overflow-x-auto');
board.addEventListener(
'wheel',
(evt) => {
if (evt.deltaY !== 0) {
evt.preventDefault();
board.scrollLeft += evt.deltaY * 1.5;
}
},
{ passive: false }
);
// Smooth horizontal scroll for the board
const board = document.querySelector('.overflow-x-auto');
board.addEventListener(
'wheel',
(evt) => {
if (evt.deltaY !== 0 && evt.shiftKey) { // Only when Shift is held
evt.preventDefault();
board.scrollLeft += evt.deltaY * 1.5;
}
},
{ passive: false }
);
🤖 Prompt for AI Agents
In index (25).html around lines 698 to 709, the wheel handler currently prevents
default for all vertical wheel events and forces horizontal scrolling; change it
to only hijack when horizontal scrolling is actually intended and possible:
first check that the board can scroll horizontally (scrollWidth > clientWidth),
then determine user intent by comparing Math.abs(evt.deltaX) and
Math.abs(evt.deltaY) and only call preventDefault() and adjust scrollLeft when
horizontal movement is dominant or when vertical movement occurs but the page
cannot scroll further vertically; otherwise do nothing so native vertical
scrolling and trackpad gestures remain unaffected.

@webdevcody
Copy link
Collaborator

keep branch around, closing to clean up pr queue

@webdevcody webdevcody closed this Jan 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Use this label if something should not be merged. Experiment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants