Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added frontend/src/assets/claude_desgin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions frontend/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useState, useEffect, useCallback } from 'react'
import { Outlet } from 'react-router-dom'
import { listJobs, type Job } from '../services/api'
import Sidebar from './Sidebar'

type LayoutContext = {
refreshJobs: () => void
}

export default function Layout() {
const [sidebarOpen, setSidebarOpen] = useState(true)
const [jobs, setJobs] = useState<Job[]>([])

const refreshJobs = useCallback(() => {
listJobs()
.then(setJobs)
.catch(() => {})

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

Error is silently caught and ignored without any logging or user feedback. Consider logging the error to the console for debugging purposes, as this could mask issues with the jobs API.

Suggested change
.catch(() => {})
.catch((error) => {
console.error('Failed to refresh jobs:', error)
})

Copilot uses AI. Check for mistakes.
}, [])

useEffect(() => {
refreshJobs()
}, [refreshJobs])

return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
<Sidebar jobs={jobs} isOpen={sidebarOpen} onToggle={() => setSidebarOpen(false)} />

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

When the sidebar is toggled, focus management should be considered for accessibility. When closing the sidebar, focus should move to the hamburger menu button. When opening, consider moving focus to a meaningful element within the sidebar or keeping it on the toggle button. This improves keyboard navigation experience.

Copilot uses AI. Check for mistakes.

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The onToggle callback always closes the sidebar (setSidebarOpen(false)) rather than toggling its state. While this may be intentional for this specific button, the naming 'onToggle' is misleading. Consider renaming to 'onClose' to better reflect the actual behavior.

Suggested change
<Sidebar jobs={jobs} isOpen={sidebarOpen} onToggle={() => setSidebarOpen(false)} />
<Sidebar jobs={jobs} isOpen={sidebarOpen} onToggle={() => setSidebarOpen((prev) => !prev)} />

Copilot uses AI. Check for mistakes.
<div
style={{
flex: 1,
marginLeft: sidebarOpen ? '350px' : '0px',
transition: 'margin-left 0.2s ease',
minHeight: '100vh',
}}
>
{!sidebarOpen && (
<button
onClick={() => setSidebarOpen(true)}
style={{
position: 'fixed',
top: '12px',
left: '12px',
zIndex: 101,
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '6px',
display: 'flex',
alignItems: 'center',
}}
>
Comment on lines +36 to +50

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The toggle button should have an aria-label to describe its purpose for screen reader users. Add aria-label="Open sidebar" or similar to improve accessibility.

Copilot uses AI. Check for mistakes.
<span style={{ fontSize: '30px', color: '#9ca3af' }}>☰</span>

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

Using emoji characters (☰) for interactive UI elements can cause accessibility issues. Screen readers may not announce them meaningfully. Consider using an SVG icon with proper aria-label or replace with actual text for better accessibility.

Copilot uses AI. Check for mistakes.
</button>
)}
<Outlet context={{ refreshJobs } satisfies LayoutContext} />
</div>
</div>
)
}
182 changes: 182 additions & 0 deletions frontend/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useNavigate, useLocation } from 'react-router-dom'
import { type Job } from '../services/api'
import StatusBadge from './StatusBadge'
import logoIcon from '../assets/claude_desgin.png'

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The filename 'claude_desgin.png' appears to have a typo. It should likely be 'claude_design.png'. Note that if you rename the file, you'll also need to update the import statement accordingly.

Suggested change
import logoIcon from '../assets/claude_desgin.png'
import logoIcon from '../assets/claude_design.png'

Copilot uses AI. Check for mistakes.

type SidebarProps = {
jobs: Job[]
isOpen: boolean
onToggle: () => void
}

export default function Sidebar({ jobs, isOpen, onToggle }: SidebarProps) {
const navigate = useNavigate()
const location = useLocation()

return (
<div
style={{
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
width: isOpen ? '350px' : '0px',
backgroundColor: '#1a1a1a',
borderRight: '1px solid #374151',
display: 'flex',
flexDirection: 'column',
transition: 'width 0.2s ease',
overflow: 'hidden',
zIndex: 100,
}}
>
<div
style={{
width: '350px',
minWidth: '350px',
display: 'flex',
flexDirection: 'column',
height: '100%',
}}
>
<div
style={{
padding: '12px 16px 10px 16px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
borderBottom: '1px solid #374151',
}}
>
<img
src={logoIcon}
alt="UI Recommender"
style={{ height: '40px', width: '40px', mixBlendMode: 'screen' }}
/>
<button
onClick={onToggle}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '4px',
display: 'flex',
alignItems: 'center',
}}
>
Comment on lines +56 to +66

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The toggle button should have an aria-label to describe its purpose for screen reader users. Add aria-label="Close sidebar" or similar to improve accessibility.

Copilot uses AI. Check for mistakes.
<svg
width="30"
height="30"
viewBox="0 0 24 24"
fill="none"
stroke="#9ca3af"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="3" y="3" width="18" height="18" rx="3" />
<line x1="9" y1="3" x2="9" y2="21" />
</svg>
</button>
</div>

<div
style={{
padding: '0 16px',
height: '63px',
display: 'flex',
alignItems: 'center',
}}
>
<button
onClick={() => navigate('/')}
style={{
width: '100%',
padding: '10px 16px',
backgroundColor: '#2563eb',
color: '#fff',
border: 'none',
borderRadius: '6px',
fontSize: '14px',
fontWeight: 600,
cursor: 'pointer',
}}
>
+ New Job
</button>
</div>

<div style={{ flex: 1, overflowY: 'auto', padding: '0 8px' }}>
{jobs.map((job) => {
const isActive = location.pathname === `/jobs/${job.id}`
return (
<div
key={job.id}
onClick={() => navigate(`/jobs/${job.id}`)}
style={{
padding: '0 12px',
height: '63px',
borderRadius: '6px',
marginBottom: '4px',
cursor: 'pointer',
backgroundColor: isActive ? '#374151' : 'transparent',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
Comment on lines +113 to +127

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The job list items are clickable divs without proper semantic HTML or keyboard navigation. Consider using button elements or adding tabIndex and onKeyDown handlers to support keyboard navigation (Enter/Space keys) for better accessibility.

Copilot uses AI. Check for mistakes.
<div style={{ overflow: 'hidden', flex: 1, marginRight: '8px' }}>
<div
style={{
fontSize: '14px',
fontWeight: 500,
color: 'rgba(255,255,255,0.87)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{job.repo_url.replace('https://github.com/', '')}

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The string replace operation assumes the URL starts with 'https://github.com/'. If the URL doesn't match this pattern (e.g., 'http://github.com/', 'https://gitlab.com/'), it won't be replaced and will display the full URL. Consider using a more robust URL parsing approach or handle non-GitHub URLs more gracefully.

Copilot uses AI. Check for mistakes.
</div>
<div
style={{
fontSize: '13px',
color: '#9ca3af',
marginTop: '2px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{job.instruction.substring(0, 80)}
{job.instruction.length > 80 ? '...' : ''}
</div>
</div>
<StatusBadge status={job.status} />
</div>
)
})}
</div>

<div
onClick={() => navigate('/settings')}
style={{
padding: '0 16px',
height: '63px',
borderTop: '1px solid #374151',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '8px',
fontSize: '16px',
color: '#9ca3af',
backgroundColor: location.pathname === '/settings' ? '#374151' : 'transparent',
}}
>
Comment on lines +161 to +175

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The settings item is a clickable div without proper semantic HTML or keyboard navigation. Consider using a button element or adding tabIndex and onKeyDown handlers to support keyboard navigation (Enter/Space keys) for better accessibility.

Copilot uses AI. Check for mistakes.
<span style={{ fontSize: '30px' }}>⚙</span>

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

Using emoji characters (⚙) for interactive UI elements can cause accessibility issues. Screen readers may not announce them meaningfully. Consider using an SVG icon with proper aria-label or replace with actual text for better accessibility.

Suggested change
<span style={{ fontSize: '30px' }}>⚙</span>

Copilot uses AI. Check for mistakes.
Settings
</div>
</div>
</div>
)
}
9 changes: 9 additions & 0 deletions frontend/src/hooks/useLayoutContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useOutletContext } from 'react-router-dom'

type LayoutContext = {
refreshJobs: () => void
}

export function useLayoutContext() {
return useOutletContext<LayoutContext>()
}
7 changes: 5 additions & 2 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ a:hover {

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

#root {
width: 100%;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import './index.css'
import Layout from './components/Layout'
import Dashboard from './pages/Dashboard'
import JobDetail from './pages/JobDetail'
import Settings from './pages/Settings'
import ErrorPage from './pages/ErrorPage'

const router = createBrowserRouter([
{
path: '/',
element: <Dashboard />,
errorElement: <ErrorPage />,
},
{
path: '/jobs/:jobId',
element: <JobDetail />,
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <Dashboard /> },
{ path: 'jobs/:jobId', element: <JobDetail /> },
{ path: 'settings', element: <Settings /> },
],
},
])

Expand Down
Loading