Skip to content

Feature/UI revamp shadcn#7

Merged
shubhamxdd merged 15 commits into
mainfrom
feature/ui-revamp-shadcn
May 18, 2026
Merged

Feature/UI revamp shadcn#7
shubhamxdd merged 15 commits into
mainfrom
feature/ui-revamp-shadcn

Conversation

@shubhamxdd
Copy link
Copy Markdown
Owner

@shubhamxdd shubhamxdd commented May 18, 2026

in this pr i have made mostly changes on frontend, using shadcn/ui

Summary by CodeRabbit

Release Notes

  • New Features

    • Complete UI redesign with modern, cohesive component library.
    • Enhanced forms, dialogs, sidebars, and interactive controls throughout the app.
    • Improved light/dark mode theming with better visual consistency.
    • Responsive design enhancements for improved mobile and desktop experiences.
  • Documentation

    • Added comprehensive deployment guide with prerequisites, setup instructions, Docker deployment steps, and production security recommendations.

Review Change Stack

shubhamxdd added 14 commits May 18, 2026 03:27
- setup shadcn/ui
- Introduced a new button component with variants and sizes using class-variance-authority.
- Added a components configuration file for Shadcn UI.
- Updated index.css to include new styles and themes.
- Added utility functions for class name management.
- Updated package.json and package-lock.json to include new dependencies.
- Configured TypeScript paths for easier imports.
- Updated Vite configuration to resolve aliases for cleaner imports.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Warning

Rate limit exceeded

@shubhamxdd has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 35 minutes and 35 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b22c6b43-e054-489b-a4d8-3aa1d69874bc

📥 Commits

Reviewing files that changed from the base of the PR and between 2788222 and ffa41ed.

📒 Files selected for processing (4)
  • frontend/src/App.tsx
  • frontend/src/components/AppSidebar.tsx
  • frontend/src/pages/Resources.tsx
  • frontend/src/pages/Solver.tsx
📝 Walkthrough

Walkthrough

This PR implements a complete frontend UI revamp using shadcn/ui, adding a comprehensive component library and refactoring all pages (Login, Register, Dashboard, Resources, Solver) with new responsive layouts, plus Docker deployment infrastructure.

Changes

Frontend UI Revamp with Shadcn/UI

Layer / File(s) Summary
Project Foundation & Configuration
frontend/package.json, frontend/tsconfig.json, frontend/tsconfig.app.json, frontend/vite.config.ts, frontend/components.json, frontend/src/index.css, frontend/src/lib/utils.ts, frontend/src/hooks/use-mobile.ts
Added shadcn/ui dependencies, configured TypeScript path aliases (@/*), extended Vite with @ alias to src/, created global Tailwind theme CSS with light/dark mode variables, and implemented responsive useIsMobile hook to detect viewport breakpoints.
Core UI Components: Basic Building Blocks
frontend/src/components/ui/button.tsx, frontend/src/components/ui/card.tsx, frontend/src/components/ui/badge.tsx, frontend/src/components/ui/input.tsx, frontend/src/components/ui/label.tsx, frontend/src/components/ui/progress.tsx, frontend/src/components/ui/skeleton.tsx, frontend/src/components/ui/separator.tsx
Created foundational styled primitives (Button/Card/Badge/Input/Label/Progress/Skeleton/Separator) wrapping Radix UI primitives with Tailwind classes, data-slot attributes, and variant support via class-variance-authority.
Complex UI Components: Cards, Tables, Tabs, Tooltips
frontend/src/components/ui/avatar.tsx, frontend/src/components/ui/table.tsx, frontend/src/components/ui/tabs.tsx, frontend/src/components/ui/tooltip.tsx
Added composite components (Avatar with size variants, Table with semantic row/cell structure, Tabs with variant styling, Tooltip with delay/portal support) for building complex layouts.
Dialog & Modal Components
frontend/src/components/ui/alert-dialog.tsx, frontend/src/components/ui/dialog.tsx, frontend/src/components/ui/sheet.tsx
Implemented modal/overlay components (AlertDialog with size variants, Dialog with optional close buttons, Sheet with directional positioning) for confirmations and side-panel UIs.
Menu & Selection Components
frontend/src/components/ui/dropdown-menu.tsx, frontend/src/components/ui/select.tsx
Created menu systems (DropdownMenu with sub-menus and item variants, Select with scroll buttons and item indicators) providing interactive selection patterns.
Layout Components
frontend/src/components/ui/scroll-area.tsx
Added ScrollArea wrapper for custom-styled scrollbars and overflow handling.
Responsive Sidebar System
frontend/src/components/ui/sidebar.tsx
Implemented complete sidebar infrastructure with context-based state management, cookie persistence, keyboard shortcuts (Ctrl+B toggle), responsive mobile Sheet mode, menu variants with tooltips for collapsed state, and nested submenu support.
Layout & Navigation Wrapper
frontend/src/components/Layout.tsx, frontend/src/components/AppSidebar.tsx
Created Layout wrapper using SidebarProvider/SidebarInset for main page structure, and AppSidebar component providing navigation menu, user dropdown (with theme toggle/logout), and route-aware active link styling.
Authentication Pages
frontend/src/pages/Login.tsx, frontend/src/pages/Register.tsx
Redesigned login/register pages using Card/Input/Label/Button components with per-field error display, loading spinners (Loader2 icon), and centered "PYQ Solver" branding layouts.
Dashboard Home Page
frontend/src/App.tsx
Refactored dashboard with loading state ("Syncing profile..."), user email/plan display, usage/quota progress bar, and action tiles ("Manage Resources", "Open AI Solver") using Card/Badge/Separator components; wrapped router in TooltipProvider.
Resources Page
frontend/src/pages/Resources.tsx
Overhauled Resources page with Table components for layout, AlertDialog-driven delete/stop confirmations replacing window.confirm, Progress indicators for processing status, Badge-based status display, and inline rename UI with per-row action buttons.
Solver Page
frontend/src/pages/Solver.tsx
Major refactor restructuring chat interface with collapsible desktop sidebars (history/context), mobile Sheet drawers for same sidebars, redesigned message list, updated input form with consistent loading/disabled states, and new HistoryContent/ContextContent components for session and resource management.
Deployment Configuration
DEPLOY.md, frontend/Dockerfile, frontend/nginx.conf, .geminiignore, frontend/.gitignore, todo.md
Added comprehensive deployment guide covering prerequisites/environment setup/Docker backend/frontend options/security checklist/troubleshooting; created Dockerfile multi-stage build (Node compile + Nginx serve) with VITE_API_URL build arg; configured Nginx for SPA routing with try_files fallback to /index.html; updated ignore files and todo list.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • shubhamxdd/pyq-appl#3: Resources page UI refactor directly builds upon the resource-management API and data structures from this PR.
  • shubhamxdd/pyq-appl#4: Solver page chat UI and streaming response handling integrate with the session/message management introduced by this PR.
  • shubhamxdd/pyq-appl#2: Frontend auth pages and layout refactor depend on the backend JWT endpoints and useAuthStore patterns established by this PR.

Poem

🐰 A rabbit's tale of UI delight,
Shadcn components, clean and bright!
Cards and buttons, dialogs refined,
Responsive sidebars, menus aligned,
From login to solver, a revamped sight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/UI revamp shadcn' is clear and specific, directly summarizing the main change: a UI revamp using shadcn/ui components. The changeset validates this intent with comprehensive shadcn/ui integration, component library additions, styling updates, and layout refactors across the frontend.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ui-revamp-shadcn

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/pages/Resources.tsx (1)

82-85: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset the native file input after a successful upload.

setFile(null) only clears React state. The browser still keeps the previous file input value, so picking the same file again will not fire onChange.

Suggested fix
                   <input
                     id="file"
                     type="file"
                     accept=".pdf,.txt"
+                    onClick={(e) => {
+                      e.currentTarget.value = '';
+                    }}
                     onChange={(e) => setFile(e.target.files?.[0] || null)}
                     className="absolute inset-0 opacity-0 cursor-pointer"
                   />

Also applies to: 222-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Resources.tsx` around lines 82 - 85, The onSuccess handler
currently calls setFile(null) which only clears React state but leaves the
native file input value intact; add a ref to the file input (e.g., fileInputRef)
and in the onSuccess handlers (the one shown and the other at lines 222-227) set
fileInputRef.current.value = '' (or otherwise reset the input element) in
addition to setFile(null), so the browser will allow selecting the same file
again.
♻️ Duplicate comments (2)
frontend/src/components/ui/scroll-area.tsx (1)

4-4: ⚠️ Potential issue | 🔴 Critical

Verify the radix-ui package import path.

Same issue as the other files: non-standard import from "radix-ui" instead of @radix-ui/react-scroll-area.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/scroll-area.tsx` at line 4, The import path for
ScrollArea is incorrect—replace the non-standard import from "radix-ui" with the
official package import so the ScrollArea primitive resolves correctly; update
the import that brings in ScrollArea as ScrollAreaPrimitive (used in this file)
to import from "`@radix-ui/react-scroll-area`" so subsequent usages of
ScrollAreaPrimitive work without module resolution errors.
frontend/src/components/ui/select.tsx (1)

2-2: ⚠️ Potential issue | 🔴 Critical

Verify the radix-ui package import path.

Same issue as dropdown-menu.tsx: the import uses from "radix-ui" instead of the standard scoped package format. This should typically be @radix-ui/react-select based on official Radix UI documentation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/select.tsx` at line 2, The import in select.tsx
uses an incorrect package path "radix-ui"; update the import to the proper Radix
package (e.g., change `import { Select as SelectPrimitive } from "radix-ui"` to
import from `@radix-ui/react-select`) so the `Select` (aliased as
`SelectPrimitive`) resolves correctly; ensure the named import remains `Select
as SelectPrimitive` and adjust any other import variants in this file to the
same scoped package.
🧹 Nitpick comments (7)
frontend/src/App.tsx (2)

16-16: ⚡ Quick win

Remove unused import.

SidebarProvider is imported but not used anywhere in the visible code. Only TooltipProvider is actually used to wrap the application routes.

🧹 Proposed cleanup
-import { SidebarProvider } from "`@/components/ui/sidebar`"
 import { TooltipProvider } from "`@/components/ui/tooltip`"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` at line 16, Remove the unused import SidebarProvider
from the top of the file; keep TooltipProvider which is actually used to wrap
the application routes. Locate the import statement that includes
SidebarProvider (the one that also imports from "`@/components/ui/sidebar`") and
delete SidebarProvider from that import so only TooltipProvider (and any other
used symbols) remain.

109-115: ⚡ Quick win

Consider making the quota value configurable.

The quota value 30 is hardcoded in both the progress calculation (line 112) and the display text (line 115). If different user plans have different quotas, this value should come from the user object or a plan configuration.

💡 Suggested approach

If the backend provides a quota field:

 <CardTitle className="text-3xl font-black text-green-600 dark:text-green-500">
   {displayUser?.questions_used} <span className="text-sm font-medium text-muted-foreground uppercase tracking-normal">Questions Answered</span>
 </CardTitle>
+const quota = displayUser?.quota || 30;
 <div className="flex items-center gap-4">
    <div className="flex-1 h-2 bg-muted rounded-full overflow-hidden">
       <div 
         className="h-full bg-green-500 transition-all duration-1000 ease-out" 
-        style={{ width: `${Math.min((displayUser?.questions_used || 0) / 30 * 100, 100)}%` }}
+        style={{ width: `${Math.min((displayUser?.questions_used || 0) / quota * 100, 100)}%` }}
       />
    </div>
-   <span className="text-[10px] font-bold text-muted-foreground uppercase whitespace-nowrap">Quota: {displayUser?.questions_used} / 30</span>
+   <span className="text-[10px] font-bold text-muted-foreground uppercase whitespace-nowrap">Quota: {displayUser?.questions_used} / {quota}</span>
 </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` around lines 109 - 115, The hardcoded quota value 30 is
used in the progress width and display; update the component to read a quota
property (e.g., displayUser?.quota or displayUser?.questions_quota) and use that
for both the Math.min((displayUser?.questions_used || 0) / quota * 100, 100)
calculation and the displayed string "Quota: X / Y", with a sensible fallback
(e.g., 30) if the quota field is missing; modify references around displayUser
and questions_used in App.tsx so both progress bar style and the span use the
same quota variable.
frontend/src/pages/Register.tsx (1)

85-92: ⚡ Quick win

Consider adding ARIA attributes for better accessibility.

Similar to the Login page, adding aria-invalid and aria-describedby attributes will improve screen reader support when validation errors occur.

♿ Proposed accessibility enhancement
 <Input
   id="email"
   type="email"
   placeholder="name@example.com"
   {...register('email')}
+  aria-invalid={!!errors.email}
+  aria-describedby={errors.email ? "email-error" : undefined}
   className={errors.email ? "border-destructive focus-visible:ring-destructive" : ""}
 />
-{errors.email && <p className="text-destructive text-xs italic">{errors.email.message}</p>}
+{errors.email && <p id="email-error" className="text-destructive text-xs italic" role="alert">{errors.email.message}</p>}

Apply similar changes to password (lines 96-102) and confirmPassword (lines 106-112) fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Register.tsx` around lines 85 - 92, Add ARIA attributes to
form inputs to improve screen-reader validation feedback: for the email Input
(where register('email') is used and errors.email is checked) set
aria-invalid={!!errors.email} and aria-describedby to the id of the error
paragraph (e.g., "email-error") when an error exists, and give the error <p>
that id; repeat the same pattern for the password and confirmPassword Inputs
(where register('password') and register('confirmPassword') are used and
errors.password / errors.confirmPassword are checked) so each input has
aria-invalid and aria-describedby pointing to its corresponding error element.
frontend/src/pages/Login.tsx (1)

87-94: ⚡ Quick win

Consider adding ARIA attributes for better accessibility.

When validation errors occur, screen readers should be notified. Add aria-invalid and aria-describedby to improve the experience for users with assistive technologies.

♿ Proposed accessibility enhancement
 <Input
   id="email"
   type="email"
   placeholder="name@example.com"
   {...register('email')}
+  aria-invalid={!!errors.email}
+  aria-describedby={errors.email ? "email-error" : undefined}
   className={errors.email ? "border-destructive focus-visible:ring-destructive" : ""}
 />
-{errors.email && <p className="text-destructive text-xs italic">{errors.email.message}</p>}
+{errors.email && <p id="email-error" className="text-destructive text-xs italic" role="alert">{errors.email.message}</p>}

Apply similar changes to the password field (lines 98-104).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Login.tsx` around lines 87 - 94, Add ARIA attributes to
the email (and similarly the password) input to expose validation state to
assistive tech: set aria-invalid to Boolean(errors.email) and set
aria-describedby to the id of the error message (e.g., `${id}-error`), and
ensure the error <p> has that same id (e.g., "email-error") so screen readers
announce it; update the JSX for the Input using register('email') and the error
<p> (errors.email) accordingly and mirror the same changes for the password
input and its error paragraph.
frontend/src/components/AppSidebar.tsx (2)

73-92: 💤 Low value

Exact path matching may not highlight active state for nested routes.

Line 77 uses location.pathname === item.path for exact matching. If routes like /resources/:id exist, the "Resources" nav item won't be highlighted when viewing a specific resource.

Consider using startsWith for parent routes:

♻️ Suggested fix for nested route matching
  <SidebarMenuButton
    asChild
-   isActive={location.pathname === item.path}
+   isActive={item.path === "/" 
+     ? location.pathname === "/" 
+     : location.pathname.startsWith(item.path)}
    tooltip={item.label}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/AppSidebar.tsx` around lines 73 - 92, The
active-state check uses exact equality (location.pathname === item.path) so
parent nav items in navItems (e.g., "Resources") won't be highlighted for nested
routes like /resources/123; update the SidebarMenuButton isActive logic to
consider prefix matching (e.g., location.pathname.startsWith(item.path)) or a
more robust helper that treats "/" specially and avoids false positives for
similarly prefixed paths; change the comparison where SidebarMenuButton is
rendered (the location.pathname === item.path expression) to this
startsWith-based check and adjust the className conditional to use the same
helper.

39-49: ⚡ Quick win

Dark mode preference is not persisted.

The current implementation toggles dark mode via document.documentElement.classList but doesn't persist the preference to localStorage. Users will lose their theme selection on page refresh.

Consider persisting to localStorage and initializing from it:

♻️ Suggested persistence approach
  const [isDark, setIsDark] = useState(
-   document.documentElement.classList.contains('dark')
+   () => {
+     const stored = localStorage.getItem('theme');
+     if (stored) return stored === 'dark';
+     return document.documentElement.classList.contains('dark');
+   }
  );

  useEffect(() => {
    if (isDark) {
      document.documentElement.classList.add('dark');
+     localStorage.setItem('theme', 'dark');
    } else {
      document.documentElement.classList.remove('dark');
+     localStorage.setItem('theme', 'light');
    }
  }, [isDark]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/AppSidebar.tsx` around lines 39 - 49, Initialize and
persist the dark-mode preference: change the isDark state initialization to read
from localStorage (fallback to
document.documentElement.classList.contains('dark') or a system preference) and
update localStorage whenever isDark changes; specifically update the useState
initializer for isDark and add/localize the side-effect in the useEffect that
runs on [isDark] to both add/remove the 'dark' class on document.documentElement
and call localStorage.setItem('theme' or similar, isDark ? 'dark' : 'light'),
keeping the existing class-add/remove logic in the effect and keeping setIsDark
usage unchanged.
frontend/src/components/ui/alert-dialog.tsx (1)

58-60: 💤 Low value

Redundant size variants set identical max-width.

Both data-[size=default]:max-w-xs and data-[size=sm]:max-w-xs apply the same constraint. Consider simplifying to max-w-xs unless different sizes are planned.

♻️ Simplification
         className={cn(
-          "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
+          "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
           className
         )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/alert-dialog.tsx` around lines 58 - 60, The class
list in alert-dialog.tsx contains redundant size variants
(data-[size=default]:max-w-xs and data-[size=sm]:max-w-xs) that both set the
same max-width; update the className passed to cn inside the alert-dialog
component to remove the duplicate data-[size=sm]:max-w-xs (or replace both with
a single max-w-xs) so the element uses a single, explicit max-width rule; locate
the string passed to cn in the alert dialog's JSX (the
group/alert-dialog-content class block) and simplify the size-related classes
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/Dockerfile`:
- Around line 12-18: The container runs Nginx as root; modify the Dockerfile to
create or use a non-root user and switch to it (e.g., create a user/group or use
the existing nginx user) after copying assets and set proper ownership of
/usr/share/nginx/html so Nginx can serve files without root; also align the
exposed port with the updated nginx.conf (change EXPOSE from 80 to 8080) and
keep CMD ["nginx", "-g", "daemon off;"] (nginx.conf will bind to 8080), ensuring
USER is set after the files are owned by the non-root user and before the final
CMD.

In `@frontend/package.json`:
- Line 27: The package.json currently lists "shadcn" as a runtime dependency;
move it into devDependencies instead. Edit package.json to remove the "shadcn":
"^4.7.0" entry from the top-level "dependencies" object and add the same
"shadcn": "^4.7.0" entry under "devDependencies" so it remains available for
CLI/component generation but is not installed in production.

In `@frontend/src/components/AppSidebar.tsx`:
- Around line 109-111: In AppSidebar, the line rendering user?.plan can output
"undefined/null Plan"; update the rendering in the AppSidebar component to use a
safe fallback (e.g., a default plan label like "Free" or "Unknown") when
user?.plan is null/undefined and ensure capitalization remains; locate the span
that currently shows {user?.plan} Plan and replace it with a fallback expression
or conditional that supplies the default value so the UI never shows "undefined"
or "null".

In `@frontend/src/components/ui/dialog.tsx`:
- Around line 70-82: The close icon currently uses the default 24×24px size
while the surrounding Button uses size="icon-sm" (16×16px); update the XIcon
inside the DialogPrimitive.Close/Button to explicitly match the button by
passing a size or a className (e.g., size={16} or className="w-4 h-4") so the
icon scales to the icon-sm button—locate the XIcon in the showCloseButton block
within DialogPrimitive.Close and adjust its props accordingly.

In `@frontend/src/components/ui/dropdown-menu.tsx`:
- Line 4: The import for the Radix dropdown is incorrect and will fail at
runtime; update the import that currently brings in DropdownMenuPrimitive from
"radix-ui" to import { DropdownMenu as DropdownMenuPrimitive } from
"`@radix-ui/react-dropdown-menu`" and ensure the package
`@radix-ui/react-dropdown-menu` is added to package.json dependencies so the
DropdownMenuPrimitive symbol resolves at runtime.

In `@frontend/src/components/ui/scroll-area.tsx`:
- Line 21: The Tailwind class in the ScrollArea component's className uses
"outline-none" which in Tailwind v4 only removes the outline style; update the
className in frontend/src/components/ui/scroll-area.tsx (the JSX element
containing className="... rounded-[inherit] transition-[color,box-shadow]
outline-none ...") to use "outline-hidden" instead of "outline-none" so the
outline is fully removed per Tailwind v4 breaking changes.

In `@frontend/src/components/ui/select.tsx`:
- Line 45: The Tailwind v4 breaking changes require updating the class string in
the Select component: replace the `outline-none` token with `outline-hidden` and
change the arbitrary rounded token `rounded-[min(var(--radius-md),10px)]` to the
parentheses form `rounded-(min(var(--radius-md),10px))`; locate the className in
frontend/src/components/ui/select.tsx (the long class string used for the Select
wrapper) and make those two substitutions so the component complies with
Tailwind v4.

In `@frontend/src/components/ui/separator.tsx`:
- Around line 17-19: The separator's className is using non-matching selectors
`data-horizontal`/`data-vertical`, so update the selector strings in the
className for the Separator component (the cn(...) call in
frontend/src/components/ui/separator.tsx) to use Radix's attribute selectors
[data-orientation="horizontal"] and [data-orientation="vertical"] (e.g. replace
`data-horizontal:h-px data-horizontal:w-full data-vertical:w-px
data-vertical:self-stretch` with `[data-orientation="horizontal"]:h-px
[data-orientation="horizontal"]:w-full [data-orientation="vertical"]:w-px
[data-orientation="vertical"]:self-stretch`) so the horizontal/vertical sizing
applies correctly.

In `@frontend/src/components/ui/sheet.tsx`:
- Around line 71-83: The close icon in the sheet uses XIcon without an explicit
size, causing a visual mismatch with the Button sized "icon-sm"; update the JSX
inside the SheetPrimitive.Close (the Button containing XIcon) to pass an
explicit size prop to XIcon (matching dialog.tsx usage) so the icon dimensions
align with the Button; locate the XIcon instance inside the showCloseButton
block and add the appropriate size value (e.g., size={16}) to the XIcon element.

In `@frontend/src/components/ui/sidebar.tsx`:
- Line 3: The import of Slot is wrong — replace any occurrences of `import {
Slot } from "radix-ui"` with `import { Slot } from "`@radix-ui/react-slot`"`
(e.g., in components that reference Slot such as the Sidebar component in
sidebar.tsx and similar imports in button.tsx and badge.tsx), and add
`@radix-ui/react-slot` to package.json dependencies and run your package manager
to install it so the new import resolves.

In `@frontend/src/components/ui/tabs.tsx`:
- Around line 27-40: tabsListVariants is using incorrect data selectors
(group-data-horizontal/tabs and group-data-vertical/tabs) that don't match the
actual data-orientation attribute; update the variant class string inside the
cva call to use selectors that match data-orientation, e.g. replace
group-data-horizontal/tabs with group-[data-orientation=horizontal]/tabs and
group-data-vertical/tabs with group-[data-orientation=vertical]/tabs so the
horizontal/vertical styles apply correctly.
- Around line 15-24: Update all uses of custom data-attribute variants to
Tailwind's bracket notation: anywhere you set data-orientation={orientation}
(e.g., in TabsPrimitive.Root and related components) replace occurrences of the
undefined variant syntax like "data-horizontal:flex-col" with the bracket form
"data-[orientation=horizontal]:flex-col"; do the same pattern-mapping for other
data-attributes found in separator.tsx and scroll-area.tsx so every class
conditional targeting data-attributes uses data-[attr=value]:... instead of
data-<name>:... (search for strings like data-horizontal, data-vertical, etc.,
and change them to data-[orientation=horizontal]:...,
data-[orientation=vertical]:..., or the appropriate attribute/value pair).

In `@frontend/src/pages/Resources.tsx`:
- Around line 332-345: Buttons that are icon-only (e.g., the Button wrapping
<Check />, <Edit2 /> and other icon actions) lack accessible names; update the
icon-only controls used for rename (startRenaming, handleRename), open, retry,
stop, and delete to include an explicit accessible label by adding a descriptive
aria-label (or title) and/or a visually-hidden text node for screen readers,
matching the same pattern used for the rename button; apply this change for all
occurrences including the block around startRenaming/handleRename and the other
actions referenced in the 373-409 range so each Button clearly announces its
purpose to assistive tech.
- Around line 379-388: The current UI uses a single retryMutation.isPending for
all rows, causing every failed row to disable/animate when one retry runs;
change to track pending retries per resource (by id) instead: create a local
state map/set (e.g., pendingRetryIds) and update it in the retry flow (add id on
mutate/start and remove on success/error/settle) or use per-id mutations so each
call has its own isPending; then replace references to retryMutation.isPending
with pendingRetryIds.has(res.id) (and use that same check to add the
"animate-spin" class to <RefreshCw />) and ensure you update this set inside
retryMutation's onMutate/onSettled (or create a per-row useMutation that calls
the same retry API with res.id) so only the clicked row shows disabled/spinning.

In `@frontend/src/pages/Solver.tsx`:
- Around line 216-223: The three "New Session" Buttons (the one shown and the
collapsed-sidebar and desktop-sidebar variants referenced around lines 385-390
and 397-404) are not guarded against duplicate clicks; update each Button that
calls createSessionMutation.mutate() to be disabled while the mutation is in
flight by using createSessionMutation.isLoading (or isLoading/isMutating from
your mutation hook) as the disabled prop (and optionally adjust
className/aria-busy). Ensure you apply the same change to the Button instances
that reference createSessionMutation to prevent multiple rapid session
creations.
- Around line 232-240: The UI treats failed fetches the same as "no data"
because the render logic only checks sessionsLoading and sessions?.length; fix
this by surfacing the fetch error from the data loader (add/propagate a
sessionsError or sessionsFetchError from the API call that populates sessions)
and change the conditional in the history panel render (the block using
sessionsLoading and sessions?.length) to first check for sessionsError and
render an explicit error state (message and retry action) instead of the
empty-state UI; update the same pattern at the other locations you noted (around
the blocks referencing sessionsLoading/sessions at ~317-321, ~421-429, ~556-560)
so error vs empty vs loading are distinct and the resource drawer doesn't fall
back to "Upload documents to begin" on fetch failure.
- Around line 274-291: Icon-only Buttons like the ones rendering Edit2/Trash2
lack accessible names; update each Button (e.g., the one calling
startRenamingSession(sess.id, sess.title) and the delete handler using
deleteSessionMutation.mutate(sess.id)) to provide an accessible name by adding
an aria-label (or aria-labelledby) or include visually hidden text
(screen-reader-only span) describing the action (e.g., "Rename conversation" and
"Delete conversation"); apply the same pattern to other icon-only controls
mentioned (rename/delete/external-link/collapse/send) so all icon buttons expose
clear, unique labels to assistive technologies.

In `@frontend/vite.config.ts`:
- Around line 4-15: The Vite config uses CommonJS __dirname which breaks under
ESM; update the top of the config to derive a directory from import.meta.url
(e.g. use path.dirname(fileURLToPath(import.meta.url))) before using
path.resolve in the resolve.alias entry so the alias "@": path.resolve(...) uses
the ESM-compatible directory value; add the required import from 'url'
(fileURLToPath) and reference the derived __dirname replacement when creating
the alias in defineConfig (affects resolve.alias and any other places relying on
__dirname).

---

Outside diff comments:
In `@frontend/src/pages/Resources.tsx`:
- Around line 82-85: The onSuccess handler currently calls setFile(null) which
only clears React state but leaves the native file input value intact; add a ref
to the file input (e.g., fileInputRef) and in the onSuccess handlers (the one
shown and the other at lines 222-227) set fileInputRef.current.value = '' (or
otherwise reset the input element) in addition to setFile(null), so the browser
will allow selecting the same file again.

---

Duplicate comments:
In `@frontend/src/components/ui/scroll-area.tsx`:
- Line 4: The import path for ScrollArea is incorrect—replace the non-standard
import from "radix-ui" with the official package import so the ScrollArea
primitive resolves correctly; update the import that brings in ScrollArea as
ScrollAreaPrimitive (used in this file) to import from
"`@radix-ui/react-scroll-area`" so subsequent usages of ScrollAreaPrimitive work
without module resolution errors.

In `@frontend/src/components/ui/select.tsx`:
- Line 2: The import in select.tsx uses an incorrect package path "radix-ui";
update the import to the proper Radix package (e.g., change `import { Select as
SelectPrimitive } from "radix-ui"` to import from `@radix-ui/react-select`) so
the `Select` (aliased as `SelectPrimitive`) resolves correctly; ensure the named
import remains `Select as SelectPrimitive` and adjust any other import variants
in this file to the same scoped package.

---

Nitpick comments:
In `@frontend/src/App.tsx`:
- Line 16: Remove the unused import SidebarProvider from the top of the file;
keep TooltipProvider which is actually used to wrap the application routes.
Locate the import statement that includes SidebarProvider (the one that also
imports from "`@/components/ui/sidebar`") and delete SidebarProvider from that
import so only TooltipProvider (and any other used symbols) remain.
- Around line 109-115: The hardcoded quota value 30 is used in the progress
width and display; update the component to read a quota property (e.g.,
displayUser?.quota or displayUser?.questions_quota) and use that for both the
Math.min((displayUser?.questions_used || 0) / quota * 100, 100) calculation and
the displayed string "Quota: X / Y", with a sensible fallback (e.g., 30) if the
quota field is missing; modify references around displayUser and questions_used
in App.tsx so both progress bar style and the span use the same quota variable.

In `@frontend/src/components/AppSidebar.tsx`:
- Around line 73-92: The active-state check uses exact equality
(location.pathname === item.path) so parent nav items in navItems (e.g.,
"Resources") won't be highlighted for nested routes like /resources/123; update
the SidebarMenuButton isActive logic to consider prefix matching (e.g.,
location.pathname.startsWith(item.path)) or a more robust helper that treats "/"
specially and avoids false positives for similarly prefixed paths; change the
comparison where SidebarMenuButton is rendered (the location.pathname ===
item.path expression) to this startsWith-based check and adjust the className
conditional to use the same helper.
- Around line 39-49: Initialize and persist the dark-mode preference: change the
isDark state initialization to read from localStorage (fallback to
document.documentElement.classList.contains('dark') or a system preference) and
update localStorage whenever isDark changes; specifically update the useState
initializer for isDark and add/localize the side-effect in the useEffect that
runs on [isDark] to both add/remove the 'dark' class on document.documentElement
and call localStorage.setItem('theme' or similar, isDark ? 'dark' : 'light'),
keeping the existing class-add/remove logic in the effect and keeping setIsDark
usage unchanged.

In `@frontend/src/components/ui/alert-dialog.tsx`:
- Around line 58-60: The class list in alert-dialog.tsx contains redundant size
variants (data-[size=default]:max-w-xs and data-[size=sm]:max-w-xs) that both
set the same max-width; update the className passed to cn inside the
alert-dialog component to remove the duplicate data-[size=sm]:max-w-xs (or
replace both with a single max-w-xs) so the element uses a single, explicit
max-width rule; locate the string passed to cn in the alert dialog's JSX (the
group/alert-dialog-content class block) and simplify the size-related classes
accordingly.

In `@frontend/src/pages/Login.tsx`:
- Around line 87-94: Add ARIA attributes to the email (and similarly the
password) input to expose validation state to assistive tech: set aria-invalid
to Boolean(errors.email) and set aria-describedby to the id of the error message
(e.g., `${id}-error`), and ensure the error <p> has that same id (e.g.,
"email-error") so screen readers announce it; update the JSX for the Input using
register('email') and the error <p> (errors.email) accordingly and mirror the
same changes for the password input and its error paragraph.

In `@frontend/src/pages/Register.tsx`:
- Around line 85-92: Add ARIA attributes to form inputs to improve screen-reader
validation feedback: for the email Input (where register('email') is used and
errors.email is checked) set aria-invalid={!!errors.email} and aria-describedby
to the id of the error paragraph (e.g., "email-error") when an error exists, and
give the error <p> that id; repeat the same pattern for the password and
confirmPassword Inputs (where register('password') and
register('confirmPassword') are used and errors.password /
errors.confirmPassword are checked) so each input has aria-invalid and
aria-describedby pointing to its corresponding error element.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5fbb3fdf-f843-451a-bb1f-e38523332b76

📥 Commits

Reviewing files that changed from the base of the PR and between 0fe6c8b and 2788222.

⛔ Files ignored due to path filters (8)
  • frontend/bun.lock is excluded by !**/*.lock
  • frontend/i1.png is excluded by !**/*.png
  • frontend/i2.png is excluded by !**/*.png
  • frontend/i3.png is excluded by !**/*.png
  • frontend/i4.png is excluded by !**/*.png
  • frontend/i5.png is excluded by !**/*.png
  • frontend/i6.png is excluded by !**/*.png
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (40)
  • .geminiignore
  • DEPLOY.md
  • frontend/.gitignore
  • frontend/Dockerfile
  • frontend/components.json
  • frontend/nginx.conf
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/components/AppSidebar.tsx
  • frontend/src/components/Layout.tsx
  • frontend/src/components/ui/alert-dialog.tsx
  • frontend/src/components/ui/avatar.tsx
  • frontend/src/components/ui/badge.tsx
  • frontend/src/components/ui/button.tsx
  • frontend/src/components/ui/card.tsx
  • frontend/src/components/ui/dialog.tsx
  • frontend/src/components/ui/dropdown-menu.tsx
  • frontend/src/components/ui/input.tsx
  • frontend/src/components/ui/label.tsx
  • frontend/src/components/ui/progress.tsx
  • frontend/src/components/ui/scroll-area.tsx
  • frontend/src/components/ui/select.tsx
  • frontend/src/components/ui/separator.tsx
  • frontend/src/components/ui/sheet.tsx
  • frontend/src/components/ui/sidebar.tsx
  • frontend/src/components/ui/skeleton.tsx
  • frontend/src/components/ui/table.tsx
  • frontend/src/components/ui/tabs.tsx
  • frontend/src/components/ui/tooltip.tsx
  • frontend/src/hooks/use-mobile.ts
  • frontend/src/index.css
  • frontend/src/lib/utils.ts
  • frontend/src/pages/Login.tsx
  • frontend/src/pages/Register.tsx
  • frontend/src/pages/Resources.tsx
  • frontend/src/pages/Solver.tsx
  • frontend/tsconfig.app.json
  • frontend/tsconfig.json
  • frontend/vite.config.ts
  • todo.md

Comment thread frontend/Dockerfile
Comment on lines +12 to +18
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
# Default nginx config is usually fine for SPAs,
# but for React Router you might need a custom one to handle 404s
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify runtime user and privileged port usage in container config.
rg -n '^\s*FROM\s+nginx|^\s*USER\b|^\s*EXPOSE\b' frontend/Dockerfile
rg -n '^\s*listen\s+' frontend/nginx.conf

Repository: shubhamxdd/pyq-appl

Length of output: 121


🏁 Script executed:

# Check for deployment configs that might reference the frontend service
fd -e 'docker-compose.yml' -e 'docker-compose.yaml' -e '.yaml' -e '.yml' | head -20

Repository: shubhamxdd/pyq-appl

Length of output: 82


🏁 Script executed:

# Check if there are any other references to port 80 in frontend-related configs
rg -i 'port.*80|80.*port' --type yaml --type json frontend/ 2>/dev/null || rg -i 'port.*80|80.*port' . --type yaml --type json 2>/dev/null | head -20

Repository: shubhamxdd/pyq-appl

Length of output: 154


🏁 Script executed:

# Verify the nginx user exists in the alpine image (standard practice)
rg -n '^\s*USER\s' frontend/Dockerfile

Repository: shubhamxdd/pyq-appl

Length of output: 45


🏁 Script executed:

cat docker-compose.yml

Repository: shubhamxdd/pyq-appl

Length of output: 1460


🏁 Script executed:

# Check for any Kubernetes, Terraform, or other deployment configs
fd -type f \( -name '*.yaml' -o -name '*.yml' -o -name '*.tf' -o -name '*.json' \) | xargs rg -l 'frontend|nginx' 2>/dev/null | head -20

Repository: shubhamxdd/pyq-appl

Length of output: 486


🏁 Script executed:

# Verify nginx:stable-alpine image includes nginx user (standard in official images)
# and check frontend/nginx.conf for any other directives that might conflict
cat -n frontend/nginx.conf

Repository: shubhamxdd/pyq-appl

Length of output: 471


Run the runtime image as non-root.

Lines 12–18 leave the Nginx container running as root, which weakens container isolation in production.

Suggested hardening patch
 FROM nginx:stable-alpine
 COPY --from=build /app/dist /usr/share/nginx/html
 # Default nginx config is usually fine for SPAs, 
 # but for React Router you might need a custom one to handle 404s
 COPY nginx.conf /etc/nginx/conf.d/default.conf
-EXPOSE 80
+USER nginx
+EXPOSE 8080
 CMD ["nginx", "-g", "daemon off;"]

Also update frontend/nginx.conf:

listen 8080;
📝 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
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
# Default nginx config is usually fine for SPAs,
# but for React Router you might need a custom one to handle 404s
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
# Default nginx config is usually fine for SPAs,
# but for React Router you might need a custom one to handle 404s
COPY nginx.conf /etc/nginx/conf.d/default.conf
USER nginx
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/Dockerfile` around lines 12 - 18, The container runs Nginx as root;
modify the Dockerfile to create or use a non-root user and switch to it (e.g.,
create a user/group or use the existing nginx user) after copying assets and set
proper ownership of /usr/share/nginx/html so Nginx can serve files without root;
also align the exposed port with the updated nginx.conf (change EXPOSE from 80
to 8080) and keep CMD ["nginx", "-g", "daemon off;"] (nginx.conf will bind to
8080), ensuring USER is set after the files are owned by the non-root user and
before the final CMD.

Comment thread frontend/package.json
"react-hot-toast": "^2.6.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.15.1",
"shadcn": "^4.7.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether `shadcn` is referenced by runtime source files.
rg -nP --type=ts --type=tsx --type=js --type=jsx \
  '(from\s+["'"'"']shadcn["'"'"']|require\(["'"'"']shadcn["'"'"']\))' frontend/src frontend

Repository: shubhamxdd/pyq-appl

Length of output: 91


🏁 Script executed:

# Search for shadcn imports in a simpler way
rg -i 'shadcn' frontend/src frontend --max-count=20

Repository: shubhamxdd/pyq-appl

Length of output: 1951


🏁 Script executed:

# Check the frontend package.json for shadcn usage in scripts
cat -n frontend/package.json | head -50

Repository: shubhamxdd/pyq-appl

Length of output: 1780


Move shadcn to devDependencies—it's a CLI tool with no runtime imports.

shadcn is listed on line 27 as a runtime dependency but has no imports in application code. The bun.lock indicates it provides a CLI executable (bin: { "shadcn": "dist/index.js" }) used for component generation, not runtime functionality. Moving it to devDependencies reduces the production install surface without impact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/package.json` at line 27, The package.json currently lists "shadcn"
as a runtime dependency; move it into devDependencies instead. Edit package.json
to remove the "shadcn": "^4.7.0" entry from the top-level "dependencies" object
and add the same "shadcn": "^4.7.0" entry under "devDependencies" so it remains
available for CLI/component generation but is not installed in production.

Comment thread frontend/src/components/AppSidebar.tsx
Comment on lines +70 to +82
{showCloseButton && (
<DialogPrimitive.Close data-slot="dialog-close" asChild>
<Button
variant="ghost"
className="absolute top-2 right-2"
size="icon-sm"
>
<XIcon
/>
<span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit size to XIcon.

The XIcon defaults to 24×24px but the button uses size="icon-sm", which typically expects a 16×16px icon. Add a size or className prop to the icon for proper visual hierarchy.

🎨 Suggested fix
             <Button
               variant="ghost"
               className="absolute top-2 right-2"
               size="icon-sm"
             >
-              <XIcon
-              />
+              <XIcon className="size-4" />
               <span className="sr-only">Close</span>
             </Button>
📝 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
{showCloseButton && (
<DialogPrimitive.Close data-slot="dialog-close" asChild>
<Button
variant="ghost"
className="absolute top-2 right-2"
size="icon-sm"
>
<XIcon
/>
<span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close>
)}
{showCloseButton && (
<DialogPrimitive.Close data-slot="dialog-close" asChild>
<Button
variant="ghost"
className="absolute top-2 right-2"
size="icon-sm"
>
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/dialog.tsx` around lines 70 - 82, The close icon
currently uses the default 24×24px size while the surrounding Button uses
size="icon-sm" (16×16px); update the XIcon inside the
DialogPrimitive.Close/Button to explicitly match the button by passing a size or
a className (e.g., size={16} or className="w-4 h-4") so the icon scales to the
icon-sm button—locate the XIcon in the showCloseButton block within
DialogPrimitive.Close and adjust its props accordingly.

Comment thread frontend/src/components/ui/dropdown-menu.tsx
Comment on lines +379 to +388
{res.status === 'failed' && (
<Button
variant="ghost"
size="icon"
className="size-9 text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:hover:bg-blue-900/20"
onClick={() => retryMutation.mutate(res.id)}
disabled={retryMutation.isPending}
>
<RefreshCw className={cn("size-4", retryMutation.isPending && "animate-spin")} />
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Track retry state per resource instead of globally.

retryMutation.isPending is shared across the whole table, so retrying one failed document disables and spins every failed row at once.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Resources.tsx` around lines 379 - 388, The current UI uses
a single retryMutation.isPending for all rows, causing every failed row to
disable/animate when one retry runs; change to track pending retries per
resource (by id) instead: create a local state map/set (e.g., pendingRetryIds)
and update it in the retry flow (add id on mutate/start and remove on
success/error/settle) or use per-id mutations so each call has its own
isPending; then replace references to retryMutation.isPending with
pendingRetryIds.has(res.id) (and use that same check to add the "animate-spin"
class to <RefreshCw />) and ensure you update this set inside retryMutation's
onMutate/onSettled (or create a per-row useMutation that calls the same retry
API with res.id) so only the clicked row shows disabled/spinning.

Comment on lines +216 to +223
<Button
onClick={() => createSessionMutation.mutate()}
className="w-full rounded-xl shadow-sm h-11"
variant="default"
>
<Plus className="size-4 mr-2" />
New Session
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the session-creation buttons while the request is in flight.

All three entry points stay clickable during session creation, so rapid taps can create multiple empty sessions.

Suggested fix
         <Button
-          onClick={() => createSessionMutation.mutate()}
+          onClick={() => {
+            if (!createSessionMutation.isPending) createSessionMutation.mutate();
+          }}
+          disabled={createSessionMutation.isPending}
           className="w-full rounded-xl shadow-sm h-11"
           variant="default"
         >

Apply the same guard to the collapsed-sidebar and desktop-sidebar variants too.

Also applies to: 385-390, 397-404

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Solver.tsx` around lines 216 - 223, The three "New
Session" Buttons (the one shown and the collapsed-sidebar and desktop-sidebar
variants referenced around lines 385-390 and 397-404) are not guarded against
duplicate clicks; update each Button that calls createSessionMutation.mutate()
to be disabled while the mutation is in flight by using
createSessionMutation.isLoading (or isLoading/isMutating from your mutation
hook) as the disabled prop (and optionally adjust className/aria-busy). Ensure
you apply the same change to the Button instances that reference
createSessionMutation to prevent multiple rapid session creations.

Comment on lines 232 to +240
{sessionsLoading ? (
<div className="flex justify-center py-8"><Loader2 className="w-6 h-6 animate-spin text-gray-300" /></div>
<div className="space-y-2 p-3">
{[1, 2, 3].map(i => <div key={i} className="h-10 bg-muted/40 animate-pulse rounded-lg" />)}
</div>
) : sessions?.length === 0 ? (
<p className="text-center py-8 text-sm text-gray-400 italic">No history yet.</p>
<div className="py-12 text-center space-y-2 px-6">
<MessageSquare className="size-8 mx-auto opacity-10" />
<p className="text-xs text-muted-foreground">Your chat history will appear here.</p>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle failed fetches separately from empty-state UI.

These new branches only distinguish loading vs. “no data”. When a query fails, the history panel goes blank and the resource drawer falls back to “Upload documents to begin,” which is misleading if the user already has data server-side.

Also applies to: 317-321, 421-429, 556-560

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Solver.tsx` around lines 232 - 240, The UI treats failed
fetches the same as "no data" because the render logic only checks
sessionsLoading and sessions?.length; fix this by surfacing the fetch error from
the data loader (add/propagate a sessionsError or sessionsFetchError from the
API call that populates sessions) and change the conditional in the history
panel render (the block using sessionsLoading and sessions?.length) to first
check for sessionsError and render an explicit error state (message and retry
action) instead of the empty-state UI; update the same pattern at the other
locations you noted (around the blocks referencing sessionsLoading/sessions at
~317-321, ~421-429, ~556-560) so error vs empty vs loading are distinct and the
resource drawer doesn't fall back to "Upload documents to begin" on fetch
failure.

Comment on lines +274 to +291
<Button
variant="ghost"
size="icon"
className="size-7 text-muted-foreground hover:text-primary"
onClick={() => startRenamingSession(sess.id, sess.title)}
>
<MessageSquare className="w-4 h-4 mt-0.5 flex-shrink-0 opacity-70" />
<span className="truncate pr-12">{sess.title}</span>
</button>
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => startRenamingSession(sess.id, sess.title)}
className="p-1.5 text-gray-400 hover:text-blue-500"
>
<Edit2 className="w-3.5 h-3.5" />
</button>
<button
onClick={(e) => { e.stopPropagation(); if(window.confirm('Delete chat?')) deleteSessionMutation.mutate(sess.id); }}
className="p-1.5 text-gray-400 hover:text-red-500"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</>
<Edit2 className="size-3" />
</Button>
<Button
variant="ghost"
size="icon"
className="size-7 text-muted-foreground hover:text-destructive"
onClick={(e) => {
e.stopPropagation();
if(window.confirm('Delete this conversation?')) deleteSessionMutation.mutate(sess.id);
}}
>
<Trash2 className="size-3" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add accessible names to the icon-only controls on this page.

History actions, sheet triggers, sidebar toggles, external-link buttons, and the send button are all icon-only here, so screen readers do not get a usable action name.

Suggested fix
-                <Button variant="ghost" size="icon" className="md:hidden shrink-0">
+                <Button variant="ghost" size="icon" className="md:hidden shrink-0" aria-label="Open chat history">
                   <Menu className="size-5" />
                 </Button>
...
-            <Button variant="ghost" size="icon" className="hidden md:flex" onClick={() => setShowContext(!showContext)}>
+            <Button
+              variant="ghost"
+              size="icon"
+              className="hidden md:flex"
+              aria-label={showContext ? "Hide resource context" : "Show resource context"}
+              onClick={() => setShowContext(!showContext)}
+            >
                <PanelRight className={cn("size-5 transition-colors", showContext ? "text-primary" : "text-muted-foreground")} />
             </Button>
...
-              <Button
+              <Button
                 type="submit"
+                aria-label="Send message"
                 disabled={isStreaming || !question.trim() || selectedResources.length === 0}
                 className="absolute right-2 bottom-2 md:right-3 md:bottom-3 size-9 md:size-10 rounded-xl shadow-lg transition-transform active:scale-90"
               >

Please apply the same pattern to rename/delete/external-link/collapse actions as well.

Also applies to: 348-357, 385-412, 463-480, 502-504, 529-538, 639-645

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Solver.tsx` around lines 274 - 291, Icon-only Buttons like
the ones rendering Edit2/Trash2 lack accessible names; update each Button (e.g.,
the one calling startRenamingSession(sess.id, sess.title) and the delete handler
using deleteSessionMutation.mutate(sess.id)) to provide an accessible name by
adding an aria-label (or aria-labelledby) or include visually hidden text
(screen-reader-only span) describing the action (e.g., "Rename conversation" and
"Delete conversation"); apply the same pattern to other icon-only controls
mentioned (rename/delete/external-link/collapse/send) so all icon buttons expose
clear, unique labels to assistive technologies.

Comment thread frontend/vite.config.ts
Comment on lines +4 to +15
import path from "path"

// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ESM mode and __dirname usage in Vite config
rg -n '"type"\s*:\s*"module"' frontend/package.json
rg -n '\b__dirname\b' frontend/vite.config.ts

Repository: shubhamxdd/pyq-appl

Length of output: 133


🏁 Script executed:

#!/bin/bash
# Check the entire vite.config.ts file for context
cat -n frontend/vite.config.ts

# Verify no other __dirname or path module usages
rg -n '__dirname|path\.' frontend/vite.config.ts

Repository: shubhamxdd/pyq-appl

Length of output: 570


Fix ESM-incompatible __dirname usage in Vite config.

Line 14 uses __dirname, which is not available in ESM modules ("type": "module"). This will fail at startup when Vite loads the config.

Suggested fix
-import path from "path"
+import { fileURLToPath, URL } from "node:url"
 
 // https://vite.dev/config/
 export default defineConfig({
   plugins: [
     react(),
     tailwindcss(),
   ],
   resolve: {
     alias: {
-      "@": path.resolve(__dirname, "./src"),
+      "@": fileURLToPath(new URL("./src", import.meta.url)),
     },
   },
 })
📝 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
import path from "path"
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
import { fileURLToPath, URL } from "node:url"
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/vite.config.ts` around lines 4 - 15, The Vite config uses CommonJS
__dirname which breaks under ESM; update the top of the config to derive a
directory from import.meta.url (e.g. use
path.dirname(fileURLToPath(import.meta.url))) before using path.resolve in the
resolve.alias entry so the alias "@": path.resolve(...) uses the ESM-compatible
directory value; add the required import from 'url' (fileURLToPath) and
reference the derived __dirname replacement when creating the alias in
defineConfig (affects resolve.alias and any other places relying on __dirname).

@shubhamxdd shubhamxdd merged commit 342801b into main May 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant