-
Notifications
You must be signed in to change notification settings - Fork 489
feat: Introduce new UI layout with floating dock, visual effects, and… #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… expanded theme options.
WalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Suggested labels
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Summary of ChangesHello @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 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 AssistThe 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
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 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
|
There was a problem hiding this 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.
| {/* 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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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';
| mouseX: any; | |
| mouseX: MotionValue<number>; |
| 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); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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]);
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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);
}There was a problem hiding this 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-ris not valid Tailwind CSS syntax. According to Tailwind v4 documentation, gradient backgrounds usebg-gradient-to-rfor directional gradients orbg-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-ris not valid Tailwind CSS syntax. Usebg-gradient-to-rinstead 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 fromAppActionsinterface.The actions
setClaudeRefreshInterval,setClaudeUsageLastUpdated, andsetClaudeUsageare implemented here but are not declared in theAppActionsinterface (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
AppActionsinterface (around line 860, beforereset):// 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):
navSectionsfromuseNavigation– 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 existsRemove 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-scrollbarstyles 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-1creates a lift animation that could cause layout shifts if cards are in a dense grid. Consider usingtransform-gpuand 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-transformhint 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-withindeclarations 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/50on line 48 is overridden byborder-ringon 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 usingfont-display: swapexplicitly.External Google Fonts can impact initial load performance and may raise privacy concerns (GDPR). The
display=swapparameter helps, but self-hosting would eliminate the external dependency entirely.
1024-1035: Duplicate:rootdeclarations may cause confusion and override earlier values.This second
:rootblock redefines--font-sans,--font-mono,--background, and--foreground. While CSS allows multiple:rootblocks (later declarations win for same properties), this creates maintenance confusion. Consider consolidating these values into the primary:rootblock at line 176.🔎 Suggested consolidation
Move the font definitions to the primary
:rootblock 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: Duplicatebodystyling outside@layer base.There's already a
bodyrule inside@layer base(lines 388-391). This additionalbodyblock 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) whileglobal.cssdefines--accent-cyanand 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
mousemoveevent 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: Respectprefers-reduced-motionfor 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
useReducedMotionhook respects theprefers-reduced-motionmedia query.apps/ui/src/components/layout/hud.tsx (1)
49-49: Consider memoizing the click handler.The
onClickhandler creates a new function on each render. For a small number of projects this is fine, but consider usinguseCallbackor checking ifp.id === currentProject.idbefore callingsetCurrentProjectto 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
isActivedetermination on line 52 useslocation.pathname.startsWith(item.path), which could lead to false positives if paths overlap (e.g.,/profilewould 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
HotkeyButtonto 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:
- Restoring the
HotkeyButtoncomponent- Adding a tooltip that displays the shortcut
- 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
''forcolumnClass. 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
Sidebarfunction now renders aHud,FloatingDock, and dialogs—no actual sidebar. Consider renaming toAppShell,LayoutController, or similar to reflect its new role as the top-level layout orchestrator.The hidden
ProjectSelectorWithOptionswithsidebarOpen={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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (30)
apps/ui/package.jsonapps/ui/src/app.tsxapps/ui/src/components/layout/floating-dock.tsxapps/ui/src/components/layout/hud.tsxapps/ui/src/components/layout/noise-overlay.tsxapps/ui/src/components/layout/page-shell.tsxapps/ui/src/components/layout/prism-field.tsxapps/ui/src/components/layout/shell.tsxapps/ui/src/components/layout/sidebar.tsxapps/ui/src/components/ui/badge.tsxapps/ui/src/components/ui/button.tsxapps/ui/src/components/ui/card.tsxapps/ui/src/components/ui/dialog.tsxapps/ui/src/components/ui/dropdown-menu.tsxapps/ui/src/components/ui/input.tsxapps/ui/src/components/ui/slider.tsxapps/ui/src/components/ui/switch.tsxapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/components/views/board-view/components/kanban-column.tsxapps/ui/src/components/views/board-view/constants.tsapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/settings-view.tsxapps/ui/src/components/views/settings-view/components/settings-header.tsxapps/ui/src/components/views/spec-view.tsxapps/ui/src/components/views/spec-view/components/spec-header.tsxapps/ui/src/components/views/terminal-view.tsxapps/ui/src/store/app-store.tsapps/ui/src/styles/global.cssindex (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-bgis 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/10andbg-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
childrenand 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 borderborder-input/50for addon variant containersplaceholder:text-muted-foreground/50for placeholder textVerify 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
Shellwrapper. TheSplashScreencorrectly renders insideShell, ensuring it appears above the background layers while maintaining proper z-index hierarchy. The Shell'sz-10content 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
columnClassprop, enabling per-column Prism styling. The column classes (col-in-progress,col-waiting,col-verified) defined inglobal.cssprovide 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/10pattern 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-nonecorrectly 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
showBackgroundElementsprovides 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 wheremin-h-screenon the outer container ensures minimum viewport coverage andh-screenon 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 theoverflow-hiddencontent 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
columnClassprop 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
fullWidthprop 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
nullwhen 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-fullto 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 atapps/ui/src/styles/global.cssand 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 utilityThe
monoclass is intentionally defined as a custom utility inapps/ui/src/styles/global.cssand appliesfont-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
HudandFloatingDockare properly added to support the new UI layout structure.apps/ui/src/store/app-store.ts (2)
7-7: Import addition looks correct.The
FeatureTextFilePathimport aligns with the newtextFilePathsproperty added to theFeatureinterface.
922-964: Initial state updates look reasonable.The default values for
maxConcurrency,kanbanCardDetailLevel,boardViewMode, terminal settings, and Claude usage tracking are sensible defaults. ThedefaultPlanningMode: 'lite'anddefaultRequirePlanApproval: truechanges 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 fullWidthintegrates well with the new layout system while preserving all drag-and-drop functionality.
1649-1667: Drag overlay implementation is correct.The
DragOverlaywith proper drop animation and dynamic text based ondragOverTabIdprovides good visual feedback during drag operations.
| isActive, | ||
| onClick, | ||
| }: { | ||
| mouseX: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.).
| const widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40]); | ||
| const width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"> | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -100Repository: 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 -150Repository: 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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| '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', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Reduce focus on the dialog content
- Make it harder for users to distinguish modal vs. non-modal states
- 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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
- 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">- Add focus-visible styles to CSS:
a:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 2px solid var(--accent-cyan);
outline-offset: 2px;
}- Add
aria-labelattributes 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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| // 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 } | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Horizontal scroll hijacking may harm usability.
The wheel event listener converts all vertical scroll input to horizontal scrolling with preventDefault(). This creates several issues:
- Unexpected behavior: Users attempting to scroll the page vertically will instead scroll horizontally
- Accessibility concern: Users who rely on vertical scrolling (especially with assistive devices) may struggle to navigate
- 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.
| // 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.
|
keep branch around, closing to clean up pr queue |
… expanded theme options.
Summary by CodeRabbit
Release Notes
New Features
UI/Visual Updates
✏️ Tip: You can customize this high-level summary in your review settings.