Skip to content

feat: redesign repo cards -- owner/repo, timestamps, sort tabs (OPE-157)#279

Merged
DevanshuNEU merged 9 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/repo-cards-redesign
Mar 7, 2026
Merged

feat: redesign repo cards -- owner/repo, timestamps, sort tabs (OPE-157)#279
DevanshuNEU merged 9 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/repo-cards-redesign

Conversation

@DevanshuNEU

@DevanshuNEU DevanshuNEU commented Mar 5, 2026

Copy link
Copy Markdown
Collaborator

Problem

Dashboard shows 5 repos, 3 named 'effect' with no way to tell them apart. No timestamps, no URLs, no subset info. Looks broken.

What changed

Repo cards now show:

  • owner/repo slug from git_url (e.g. Effect-TS/effect)
  • File count + function count (was files only)
  • Relative timestamp: 'Indexed 2h ago' from last_indexed_at
  • Compact status dot instead of pill badge

Sort bar above the grid:

  • Recent (default) -- most recently indexed first
  • Name -- alphabetical
  • Size -- largest repos first
  • Repo count on the right

Type updates:

  • Repository type: added function_count, created_at, last_indexed_at
  • Fields already in backend response, just weren't typed on frontend

Before

Three identical 'effect' cards. No way to distinguish.

After

[Recent]  Name  Size                    5 repos

+-------------------+  +-------------------+
| effect            |  | opencodeintel     |
| Effect-TS/effect  |  | OpenCodeIntel/... |
|                   |  |                   |
| 150 files  3.7K fn|  | 269 files  2.1K fn|
| Indexed 2h ago    |  | Indexed 1d ago    |
+-------------------+  +-------------------+

Files

  • frontend/src/components/RepoList.tsx (rewritten, 230 lines)
  • frontend/src/types.ts (+3 fields)

Closes OPE-157

Summary by CodeRabbit

  • New Features

    • Repository sorting (recent, name, size) via a sort bar/tabs and updated repo count.
    • Per-repo deletion in list and detail views: delete menu, confirmation dialog requiring exact repo name, and in-app delete with success/error feedback.
    • Repo cards show file/function counts, relative "last indexed" times, and a status indicator.
  • Improvements

    • Settings page shows dynamic tier limits and actual repository limits instead of a hard-coded value.

…-157)

Repo cards now show:
- owner/repo slug extracted from git_url (e.g. Effect-TS/effect)
- File count + function count side by side
- 'Indexed 2h ago' relative timestamp from last_indexed_at
- Status dot instead of full badge (cleaner)

Sort bar above the grid:
- Recent (default): sorted by last_indexed_at descending
- Name: alphabetical
- Size: largest file count first
- Repo count shown on right side

Repository type updated with function_count, created_at, last_indexed_at.
These fields already exist in the backend (select *), just weren't typed.

230 lines, same glow effect, premium feel.
@vercel

vercel Bot commented Mar 5, 2026

Copy link
Copy Markdown

@DevanshuNEU is attempting to deploy a commit to the Dev's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 5, 2026

Copy link
Copy Markdown

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds client-side sorting and UI enhancements to the repo list, introduces a per-repo delete workflow with confirmation, threads delete callbacks DashboardHome → RepoListView → RepoList, and makes SettingsPage read maxRepos from usage. Types updated to include repo metadata.

Changes

Cohort / File(s) Summary
Repo list UI & delete flow
frontend/src/components/RepoList.tsx
Major UI additions: icons, DropdownMenu, Dialog, Tabs, Button/Input; helpers parseRepoSlug and timeAgo; StatusDot component; client-side sorting (recent/name/size); per-item delete menu wired to optional onDelete prop and exported DeleteConfirmDialog.
Dashboard wiring
frontend/src/components/dashboard/DashboardHome.tsx, frontend/src/components/dashboard/RepoListView.tsx, frontend/src/components/dashboard/RepoDetailView.tsx
Wires deletion through the dashboard: DashboardHome implements handleDeleteRepo (DELETE /repos/{repoId}, toasts, refetch, clear selection) and passes delete callbacks down (onDeleteRepoRepoList, onDeleteRepoDetailView).
Types
frontend/src/types.ts
Extends Repository with optional function_count?: number, created_at?: string, and last_indexed_at?: string.
Settings usage
frontend/src/pages/SettingsPage.tsx
Replaces hard-coded MAX_REPOS with dynamic maxRepos from useUserUsage; displays tier label and computes available slots from maxRepos.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant RepoListUI as "RepoList / RepoCard"
    participant Dashboard as "DashboardHome"
    participant API
    User->>RepoListUI: open delete menu & request delete
    RepoListUI->>User: show DeleteConfirmDialog (typed repo name)
    User->>RepoListUI: confirm deletion
    RepoListUI->>Dashboard: onDeleteRepo(repoId)
    Dashboard->>API: DELETE /repos/{repoId} (Authorization)
    API-->>Dashboard: 200 OK / error
    Dashboard->>RepoListUI: refetch repos / update state
    RepoListUI-->>User: updated list (repo removed or error shown)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I nibbled the slug and watched the dots glow,

Menus popped open, a careful typed show.
Tap Delete, breathe — the list hopped away,
Carrot crumbs, a cheer, and a bright bouncing day. 🎋

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main UI redesign work: owner/repo slug display, timestamp features, and sort tabs are all prominently featured in the changeset across RepoList, RepoDetailView, and type updates.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/RepoList.tsx`:
- Around line 47-59: StatusDot currently treats any non-'indexed' status as
"Pending", hiding error states; update the component (StatusDot and its status
prop handling) to branch explicitly for 'error' (e.g., set a new isError =
status === 'error') and render a distinct label and CSS classes for error (e.g.,
'text-destructive' and 'bg-destructive' or your design system's error classes),
keep 'Indexed' for isIndexed, and fall back to 'Pending' only for other
statuses; adjust both the outer text color and the inner dot classes and the
displayed text accordingly.
- Around line 125-130: The rendering currently skips showing function counts
when repo.function_count is 0 because of the `> 0` guard; update the conditional
in RepoList.tsx (the block that references repo.function_count and uses the
FunctionSquare icon) to render whenever repo.function_count is present (not
null/undefined) rather than strictly greater than zero so a zero value displays;
i.e., remove the `> 0` check and only guard against null/undefined (e.g., check
repo.function_count != null or !== undefined) so zeroes are shown.
- Around line 19-23: The parseRepoSlug function doesn't handle SSH-style GitHub
URLs (e.g., git@github.com:owner/repo.git); update parseRepoSlug to normalize
the input (strip trailing .git) and match both HTTP(S) and SSH patterns (e.g.,
use a regex that captures either github.com/owner/repo or github.com:owner/repo
or an alternate branch that checks for /^git@github\.com:(.+\/.+)$/), returning
the captured owner/repo or '' if none; reference the parseRepoSlug function and
its cleaned/match variables when making the change.
- Around line 143-159: Replace the custom SortTab component with shadcn/ui Tabs
primitives: remove the SortTab definition and update the sort-mode UI that
currently uses SortTab (the sort mode selection invocation) to use <Tabs
defaultValue={...}> wrapping a <TabsList> with individual <TabsTrigger
value="..."> labels for each sort option; wire each TabsTrigger's onValueChange
or Tabs' onValueChange to the existing sort change handler so current sorting
state and active styling map to Tabs' value, and import Tabs, TabsList,
TabsTrigger from "@/components/ui/tabs". Ensure any props/types tied to SortTab
are migrated to the Tabs value API and that keyboard/accessibility behavior is
provided by the Tabs primitives.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 29d6d626-964f-4ac4-9bed-f804a0dac01a

📥 Commits

Reviewing files that changed from the base of the PR and between c3288da and 3bd3470.

📒 Files selected for processing (2)
  • frontend/src/components/RepoList.tsx
  • frontend/src/types.ts

Comment thread frontend/src/components/RepoList.tsx Outdated
Comment thread frontend/src/components/RepoList.tsx
Comment thread frontend/src/components/RepoList.tsx
Comment thread frontend/src/components/RepoList.tsx Outdated
…ded 'Free' (OPE-158)

Bug: Settings page had const MAX_REPOS = 3 hardcoded and 'Free tier limit'
text for ALL users. Enterprise users saw '5/3 - 0 slots available'.

Fix: Uses useUserUsage hook for actual tier and repo limit.
- 'Free tier limit' -> 'Enterprise tier limit' (dynamic, capitalized)
- '5/3' -> '5/10' for Enterprise (actual limit from backend)
- Removed hardcoded MAX_REPOS constant

Closes OPE-158

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/pages/SettingsPage.tsx`:
- Around line 34-36: The UI mixes sources for repo slot counts (using
repos.length for current and usage.repositories.limit for limit) and defaults to
a realistic free/1 state before usage loads; change the component to derive both
current and limit from the usage payload (useUserUsage ->
usage.repositories.current and usage.repositories.limit) and stop falling back
to repos.length or hard defaults — render a loading/unavailable state while the
usage query is unresolved. Update references to maxRepos and any display that
used repos.length (e.g., the slot counter) to use usage.repositories.current and
usage.repositories.limit, and ensure tier comes from usage.tier (not a default
'free') until usage is loaded. Ensure similar fixes are applied to the other
occurrences noted (around lines with repos/slot rendering).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 944cc07d-f021-4622-9c4e-c331723fd45e

📥 Commits

Reviewing files that changed from the base of the PR and between 3bd3470 and 744b002.

📒 Files selected for processing (1)
  • frontend/src/pages/SettingsPage.tsx

Comment thread frontend/src/pages/SettingsPage.tsx
…g (OPE-159)

Each repo card now has a three-dot menu (visible on hover) with 'Delete repository'.
Clicking shows confirmation dialog: 'This will permanently remove [name] and all
its indexed data. This action cannot be undone.'

Flow: hover card -> click ... -> click Delete -> confirm -> DELETE /repos/{id}
- Toast notification on success/failure
- Repo list refreshes automatically
- If deleted repo was selected, clears selection

Props chain: DashboardHome (handler) -> RepoListView -> RepoList -> RepoCard

Closes OPE-159

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/RepoList.tsx (1)

85-105: ⚠️ Potential issue | 🟠 Major

Avoid nesting the overflow trigger inside the card button.

RepoCard renders the entire card as a <button> and then nests another <button> for the kebab menu. That is invalid interactive nesting and it breaks keyboard/focus behavior.

♿ Suggested fix
-  const cardRef = useRef<HTMLButtonElement>(null)
+  const cardRef = useRef<HTMLDivElement>(null)
...
-    <motion.button
+    <motion.div
       ref={cardRef}
+      role="button"
+      tabIndex={0}
       initial={{ opacity: 0, y: 12 }}
       animate={{ opacity: 1, y: 0 }}
       transition={{ delay: index * 0.04, duration: 0.25 }}
       whileHover={{ y: -2 }}
       onClick={onSelect}
+      onKeyDown={(e) => {
+        if (e.key === 'Enter' || e.key === ' ') {
+          e.preventDefault()
+          onSelect()
+        }
+      }}
       onMouseMove={(e) => {
         if (!cardRef.current) return
         const rect = cardRef.current.getBoundingClientRect()
         setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top })
       }}
...
-    </motion.button>
+    </motion.div>

Also applies to: 127-135

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

In `@frontend/src/components/RepoList.tsx` around lines 85 - 105, The repo card
currently nests the kebab/overflow trigger button inside the outer interactive
motion.button (RepoCard), which creates invalid nested interactive elements and
breaks keyboard/focus; fix by extracting the overflow/kebab trigger out of the
motion.button: wrap motion.button and the kebab trigger in a non-interactive
container (e.g., a div), keep the motion.button (with ref cardRef, onSelect,
onMouseMove, onMouseEnter/onMouseLeave) as the primary click target, and render
the kebab/menu trigger as a sibling button absolutely positioned over the card
(or as a separate focusable element) so it is not nested; update any
handlers/refs that previously relied on nesting (also apply same change to the
other occurrence around lines 127-135).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/dashboard/DashboardHome.tsx`:
- Around line 217-227: The delete flow currently throws a generic Error('Failed
to delete') which hides the API's real error; update the code to preserve
backend error details by calling the shared helper extractErrorMessage(...) when
the response is not ok (e.g. const msg = await extractErrorMessage(response);
throw new Error(msg)) or by passing that message directly to toast.error in the
catch. Locate the delete block around the fetch to `${API_URL}/repos/${repoId}`
in DashboardHome.tsx and replace the generic throw with logic that uses
extractErrorMessage(response) (retain subsequent calls to toast.success,
refreshRepos(), selectedRepo and setSelectedRepo as-is).

---

Outside diff comments:
In `@frontend/src/components/RepoList.tsx`:
- Around line 85-105: The repo card currently nests the kebab/overflow trigger
button inside the outer interactive motion.button (RepoCard), which creates
invalid nested interactive elements and breaks keyboard/focus; fix by extracting
the overflow/kebab trigger out of the motion.button: wrap motion.button and the
kebab trigger in a non-interactive container (e.g., a div), keep the
motion.button (with ref cardRef, onSelect, onMouseMove,
onMouseEnter/onMouseLeave) as the primary click target, and render the
kebab/menu trigger as a sibling button absolutely positioned over the card (or
as a separate focusable element) so it is not nested; update any handlers/refs
that previously relied on nesting (also apply same change to the other
occurrence around lines 127-135).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 91a1f49e-da04-41f4-a2dd-933c7f4b5a7e

📥 Commits

Reviewing files that changed from the base of the PR and between 744b002 and 9ca8913.

📒 Files selected for processing (3)
  • frontend/src/components/RepoList.tsx
  • frontend/src/components/dashboard/DashboardHome.tsx
  • frontend/src/components/dashboard/RepoListView.tsx

Comment thread frontend/src/components/dashboard/DashboardHome.tsx
Three-dot menu in top-right of repo detail header (next to repo name).
Click '...' -> 'Delete repository' -> confirmation dialog -> DELETE API.

Delete is now available in TWO places:
1. Repo card grid (hover card -> three dots)
2. Inside repo detail view (top-right header)

Both use same confirmation dialog and same handleDeleteRepo handler.
After delete, navigates back to repo list automatically.
Both delete dialogs (card grid + detail view) now require typing
the exact repo name before the Delete button enables.

Shared DeleteConfirmDialog component:
- Shows repo name in bold
- Input field with placeholder matching repo name
- Delete button disabled until text matches exactly
- Button text: 'Delete opencodeintel' (includes name)
- Input clears on cancel or close

Reused in RepoList (card grid) and RepoDetailView (detail header).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/components/RepoList.tsx (1)

46-62: Consider handling invalid date strings in timeAgo.

If dateStr is malformed, new Date(dateStr).getTime() returns NaN, which propagates through the comparisons and eventually calls toLocaleDateString() on an invalid date. While backend data should be valid, a guard would make this more robust.

Proposed fix
 function timeAgo(dateStr?: string): string {
   if (!dateStr) return ''
   const now = Date.now()
   const then = new Date(dateStr).getTime()
+  if (Number.isNaN(then)) return ''
   const diff = now - then
   if (diff < 0) return ''
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/RepoList.tsx` around lines 46 - 62, The timeAgo
function should validate the parsed date before using it: replace direct use of
new Date(dateStr).getTime() with const parsed = new Date(dateStr); if
(isNaN(parsed.getTime())) return '' to guard against malformed dateStr, then use
parsed.getTime() for diff and parsed.toLocaleDateString(...) at the end; ensure
the negative-diff and other existing branches remain unchanged so invalid dates
never reach toLocaleDateString.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/components/RepoList.tsx`:
- Around line 46-62: The timeAgo function should validate the parsed date before
using it: replace direct use of new Date(dateStr).getTime() with const parsed =
new Date(dateStr); if (isNaN(parsed.getTime())) return '' to guard against
malformed dateStr, then use parsed.getTime() for diff and
parsed.toLocaleDateString(...) at the end; ensure the negative-diff and other
existing branches remain unchanged so invalid dates never reach
toLocaleDateString.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c34619e1-379d-4585-b8d7-ebf579a3567b

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca8913 and 6354dcc.

📒 Files selected for processing (3)
  • frontend/src/components/RepoList.tsx
  • frontend/src/components/dashboard/DashboardHome.tsx
  • frontend/src/components/dashboard/RepoDetailView.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/dashboard/DashboardHome.tsx

… URLs

StatusDot: added 'failed' status with text-destructive/bg-destructive.
Previously showed failed repos as 'Pending' with pulsing dot.

parseRepoSlug: now matches both HTTPS (github.com/owner/repo) and
SSH (git@github.com:owner/repo) URL formats.

Skipped: function_count > 0 guard (intentional -- 0 before indexing is
misleading), SortTab -> shadcn Tabs (custom component is simpler).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
frontend/src/components/RepoList.tsx (1)

172-177: ⚠️ Potential issue | 🟡 Minor

Render function_count even when it is 0.

The > 0 check hides valid zero values, so some cards lose the new stat even though the backend returned it.

Small fix
-          {repo.function_count != null && repo.function_count > 0 && (
+          {repo.function_count != null && (
             <span className="flex items-center gap-1">
               <FunctionSquare className="w-3 h-3" />
               {repo.function_count.toLocaleString()}
             </span>
           )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/RepoList.tsx` around lines 172 - 177, The UI hides
valid zero values by checking "repo.function_count > 0"; update the render
condition in RepoList.tsx to only test for non-null/undefined so zeros display:
replace the combined check with only "repo.function_count != null" around the
span that uses FunctionSquare and repo.function_count.toLocaleString(), ensuring
0 is rendered while still skipping absent values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/RepoList.tsx`:
- Around line 99-105: The card currently renders a <motion.button> (ref cardRef)
with an inner <button> for the overflow/menu, causing nested button HTML and
conflicting focus/click behavior; change the outer card from a button to a
non-button container (e.g., div or motion.div) when secondary actions are
present, keep the onSelect handler on that container (or move to an inner
focusable element) and render the overflow trigger as its own button with an
accessible aria-label (the menu/icon trigger). Update the component logic in
RepoList to conditionally choose motion.button vs motion.div (or wrap clickable
area in a single interactive element) so the inner menu button is the only
<button>, and ensure keyboard focus/aria-labels are correct for the overflow
control.
- Around line 321-329: The confirmation input in RepoList.tsx (the Input bound
to confirmText via setConfirmText and showing repoName as placeholder) lacks a
programmatic label; add an accessible label by either adding a visually-hidden
<label> tied to that Input (using htmlFor/id) or by supplying a clear aria-label
or aria-labelledby that includes the repoName and explains the action (e.g.,
"Type repository name to confirm deletion: {repoName}"), so screen readers
announce exactly what must be typed before allowing the destructive action.

---

Duplicate comments:
In `@frontend/src/components/RepoList.tsx`:
- Around line 172-177: The UI hides valid zero values by checking
"repo.function_count > 0"; update the render condition in RepoList.tsx to only
test for non-null/undefined so zeros display: replace the combined check with
only "repo.function_count != null" around the span that uses FunctionSquare and
repo.function_count.toLocaleString(), ensuring 0 is rendered while still
skipping absent values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 70eca776-8060-4e17-bca9-700d05a3cb5f

📥 Commits

Reviewing files that changed from the base of the PR and between 6354dcc and 673de1c.

📒 Files selected for processing (1)
  • frontend/src/components/RepoList.tsx

Comment thread frontend/src/components/RepoList.tsx
Comment thread frontend/src/components/RepoList.tsx
Swapped the hand-rolled SortTab pill buttons for shadcn/ui Tabs
(Radix TabsPrimitive). Gets us keyboard navigation, ARIA roles,
and consistent styling from the design system for free.

Removed 18 lines of custom component, added 6 lines of shadcn usage.
@DevanshuNEU

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Mar 7, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vercel

vercel Bot commented Mar 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
opencodeintel Ready Ready Preview, Comment Mar 7, 2026 1:52am

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
frontend/src/components/RepoList.tsx (3)

291-315: ⚠️ Potential issue | 🟠 Major

Confirm deletions against a unique, labeled identifier.

This dialog still keys off repo.name, so typing effect does not distinguish which repo is being deleted when several cards share that name. The field also still has no programmatic label. Use the parsed slug when available and bind that text to the input with aria-label/aria-describedby.

Possible fix
   const [confirmText, setConfirmText] = useState('')
   const repoName = repo?.name || ''
-  const isMatch = confirmText === repoName
+  const repoIdentifier = repo ? parseRepoSlug(repo.git_url) || repoName : ''
+  const isMatch = confirmText === repoIdentifier
@@
         <DialogHeader>
           <DialogTitle>Delete repository</DialogTitle>
           <DialogDescription>
-            This will permanently remove <strong>{repoName}</strong> and all its
+            This will permanently remove <strong>{repoIdentifier}</strong> and all its
             indexed data. This action cannot be undone.
           </DialogDescription>
         </DialogHeader>
         <div className="py-2">
-          <p className="text-sm text-muted-foreground mb-2">
-            Type <strong className="text-foreground">{repoName}</strong> to confirm
+          <p id="delete-repo-confirm-help" className="text-sm text-muted-foreground mb-2">
+            Type <strong className="text-foreground">{repoIdentifier}</strong> to confirm
           </p>
           <Input
+            aria-label={`Type ${repoIdentifier} to confirm deletion`}
+            aria-describedby="delete-repo-confirm-help"
             value={confirmText}
             onChange={(e) => setConfirmText(e.target.value)}
-            placeholder={repoName}
+            placeholder={repoIdentifier}
             autoFocus
           />
         </div>
@@
           <Button
             variant="destructive"
             disabled={!isMatch}
             onClick={() => { setConfirmText(''); onConfirm() }}
           >
-            Delete {repoName}
+            Delete {repoIdentifier}
           </Button>

Also applies to: 320-325

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

In `@frontend/src/components/RepoList.tsx` around lines 291 - 315, The
confirmation is currently keyed to repo?.name (repoName) and the Input lacks an
accessible label, so change the confirmation to use the repository's unique
parsed slug when available (e.g., prefer repo.slug || repo.name) and bind that
value to the Input's value/placeholder and to the comparison (isMatch) used to
enable deletion; also add an accessible label/description to the Input
(aria-label or aria-describedby) so screen readers can identify the required
text. Update references to repoName, isMatch, confirmText, setConfirmText, and
the Input props so the dialog validates against the unique slug and the input is
programmatically labeled; apply the same change to the similar Input usage later
in the file.

173-176: ⚠️ Potential issue | 🟡 Minor

Render zero function counts too.

> 0 hides legitimate zero values, so repos with no indexed functions lose one of the new stats.

Possible fix
-          {repo.function_count != null && repo.function_count > 0 && (
+          {repo.function_count != null && (
             <span className="flex items-center gap-1">
               <FunctionSquare className="w-3 h-3" />
               {repo.function_count.toLocaleString()}
             </span>
           )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/RepoList.tsx` around lines 173 - 176, The current
render in RepoList.tsx uses the condition "repo.function_count != null &&
repo.function_count > 0" which suppresses legitimate zero counts; change the
condition to only check for presence (e.g., "repo.function_count != null") so
that zero values are rendered as "0" alongside the FunctionSquare and the
toLocaleString output, ensuring RepoList displays repos with no indexed
functions; update the JSX that references repo.function_count and keep the
FunctionSquare and toLocaleString usage intact.

100-107: ⚠️ Potential issue | 🟠 Major

Split repo selection from the overflow trigger.

The card is still a <motion.button> with an inner icon <button>, so selection and delete compete for focus/click handling. The trigger also has no accessible name.

Possible fix
-  const cardRef = useRef<HTMLButtonElement>(null)
+  const cardRef = useRef<HTMLDivElement>(null)

-    <motion.button
+    <motion.div
       ref={cardRef}
+      role="button"
+      tabIndex={0}
       initial={{ opacity: 0, y: 12 }}
       animate={{ opacity: 1, y: 0 }}
       transition={{ delay: index * 0.04, duration: 0.25 }}
       whileHover={{ y: -2 }}
       onClick={onSelect}
+      onKeyDown={(e) => {
+        if (e.key === 'Enter' || e.key === ' ') {
+          e.preventDefault()
+          onSelect()
+        }
+      }}
       onMouseMove={(e) => {
         if (!cardRef.current) return
         const rect = cardRef.current.getBoundingClientRect()
         setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top })
       }}
@@
-                  <button
+                  <button
+                    type="button"
+                    aria-label={`Open actions for ${repo.name}`}
                     onClick={(e) => e.stopPropagation()}
-                    className="w-6 h-6 flex items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors opacity-0 group-hover:opacity-100"
+                    className="w-6 h-6 flex items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors opacity-0 group-hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
                   >
                     <MoreVertical className="w-3.5 h-3.5" />
                   </button>
@@
-    </motion.button>
+    </motion.div>

Also applies to: 135-143

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

In `@frontend/src/components/RepoList.tsx` around lines 100 - 107, The repo card
currently uses <motion.button> (cardRef) with an inner icon <button>, causing
selection and overflow/delete to compete; change the outer <motion.button> to a
non-interactive element (e.g., <motion.div> or a div with role="button" and
tabIndex=0) and keep the inner icon as a real <button> with a clear accessible
name (aria-label="Open overflow" or "Delete repository" as appropriate). Update
the outer selection handling (onSelect) to respond to onClick and keyboard via
onKeyDown (Enter/Space) on the new interactive container, and ensure the inner
overflow button calls e.stopPropagation() to prevent triggering onSelect; apply
the same fixes for the duplicate card instance around lines 135-143 (the other
motion.button with onSelect).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/RepoList.tsx`:
- Around line 197-202: The comparator in RepoList.tsx currently uses created_at
as a direct fallback which lets never-indexed repos jump ahead; change the
sorted.sort comparator used when sortMode === 'recent' to first compare
last_indexed_at values (treat missing last_indexed_at as less recent), and only
if last_indexed_at are equal (or both missing) use created_at as a tie-breaker;
update the function using sortMode, sorted.sort, last_indexed_at and created_at
so that last_indexed_at always determines order and created_at is applied
strictly as a secondary comparator.

---

Duplicate comments:
In `@frontend/src/components/RepoList.tsx`:
- Around line 291-315: The confirmation is currently keyed to repo?.name
(repoName) and the Input lacks an accessible label, so change the confirmation
to use the repository's unique parsed slug when available (e.g., prefer
repo.slug || repo.name) and bind that value to the Input's value/placeholder and
to the comparison (isMatch) used to enable deletion; also add an accessible
label/description to the Input (aria-label or aria-describedby) so screen
readers can identify the required text. Update references to repoName, isMatch,
confirmText, setConfirmText, and the Input props so the dialog validates against
the unique slug and the input is programmatically labeled; apply the same change
to the similar Input usage later in the file.
- Around line 173-176: The current render in RepoList.tsx uses the condition
"repo.function_count != null && repo.function_count > 0" which suppresses
legitimate zero counts; change the condition to only check for presence (e.g.,
"repo.function_count != null") so that zero values are rendered as "0" alongside
the FunctionSquare and the toLocaleString output, ensuring RepoList displays
repos with no indexed functions; update the JSX that references
repo.function_count and keep the FunctionSquare and toLocaleString usage intact.
- Around line 100-107: The repo card currently uses <motion.button> (cardRef)
with an inner icon <button>, causing selection and overflow/delete to compete;
change the outer <motion.button> to a non-interactive element (e.g.,
<motion.div> or a div with role="button" and tabIndex=0) and keep the inner icon
as a real <button> with a clear accessible name (aria-label="Open overflow" or
"Delete repository" as appropriate). Update the outer selection handling
(onSelect) to respond to onClick and keyboard via onKeyDown (Enter/Space) on the
new interactive container, and ensure the inner overflow button calls
e.stopPropagation() to prevent triggering onSelect; apply the same fixes for the
duplicate card instance around lines 135-143 (the other motion.button with
onSelect).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 59a4f674-c6d9-4781-8a9f-1b9cf79efffd

📥 Commits

Reviewing files that changed from the base of the PR and between 673de1c and 6de079c.

📒 Files selected for processing (1)
  • frontend/src/components/RepoList.tsx

Comment thread frontend/src/components/RepoList.tsx
…input

Sort: never-indexed repos no longer jump ahead of recently indexed ones.
last_indexed_at is primary sort key, created_at is tiebreaker only.

Accessibility: delete confirmation input now has aria-label for screen readers.

Skipped: function_count > 0 (intentional), motion.button nesting (large
refactor), slug-based confirm (users think in repo names).
@DevanshuNEU DevanshuNEU merged commit d8a66b5 into OpenCodeIntel:main Mar 7, 2026
7 checks 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