Skip to content

Conversation

@brendan-kellam
Copy link
Contributor

@brendan-kellam brendan-kellam commented Dec 17, 2025

TODO:

  • Fix status filtering
  • Fix nav with search filter

Summary by CodeRabbit

  • New Features

    • Added debounced search functionality with keyboard shortcut support to repositories table.
    • Implemented URL-driven pagination, enabling sharable bookmarkable page states.
    • Enhanced status filtering with improved UI controls.
  • Improvements

    • Added loading indicator for pending searches.
    • Improved repositories listing with better pagination controls and search experience.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

Walkthrough

This PR refactors the repositories listing page to implement URL-driven pagination and search, introduces a new InputGroup UI component system, updates repository data fetching with filter and pagination support, and restructures test data injection to create per-repo job records with randomized statuses.

Changes

Cohort / File(s) Summary
Test Data Injection
packages/db/tools/scripts/inject-repo-data.ts
Reduces NUM_REPOS from 100,000 to 1,000; adds constants for per-repo permission and indexing jobs (NUM_PERMISSION_JOBS_PER_REPO and NUM_INDEXING_JOBS_PER_REPO, both 10,000); restructures creation loop to generate repoPermissionSyncJob and repoIndexingJob records per repo with randomized statuses, completion states, and error messages.
Repositories Page & Data Fetching
packages/web/src/app/[domain]/repos/page.tsx
Refactors ReposPage to accept searchParams; implements skip/take pagination and URL query filtering; updates getReposWithLatestJob to accept filter object (skip, take, search, status) and return object with repos and totalCount; adds where clause for name/displayName search and updates sorting by indexedAt and name.
Repositories Table Component
packages/web/src/app/[domain]/repos/components/reposTable.tsx
Migrates from internal React-Table filtering to URL query parameter-driven state; introduces debounced search, URL-synced pagination, and status filtering; updates component signature to accept currentPage, pageSize, totalCount, initialSearch, and initialStatus props; replaces inline input with InputGroup component; adds loading indicator for pending searches.
UI Components
packages/web/src/components/ui/input-group.tsx
Introduces new InputGroup component system with subcomponents: InputGroup (wrapper), InputGroupAddon (addon container with alignment variants), InputGroupButton (button wrapper), InputGroupText (text adornment), InputGroupInput (input element), and InputGroupTextarea (textarea element); all use class-variance-authority for variant-based styling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • File cohort spread and heterogeneity: Changes span test data generation, page-level data fetching logic, table component refactoring, and new UI component library—each with distinct concerns and no repetitive patterns.
  • State management pattern shift: Table component migration from internal React-Table state to URL-driven state requires careful validation of query parameter handling, debouncing, and page reset logic on search/filter changes.
  • Data fetching changes: Updates to getReposWithLatestJob introduce new parameter structure and return shape; verify where clause logic, totalCount computation, and sorting behavior (especially nulls-first for indexedAt).
  • Component signature breaking change: ReposTable props expanded significantly; confirm all call sites properly supply new pagination and filter initialization props.
  • New UI component integration: InputGroup and related subcomponents introduce styling via cva; verify variant combinations work as intended and integrate cleanly with existing design system.

Possibly related PRs

Suggested labels

sourcebot-team

Suggested reviewers

  • msukkari

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the changeset: implementing pagination and URL-driven search/filtering to improve performance on the /repos page.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bkellam/paginated-repos-SOU-158

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.

@brendan-kellam
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

✅ 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/[domain]/repos/components/reposTable.tsx (1)

151-156: Typo: "Lastest" should be "Latest".

     {
         accessorKey: "latestJobStatus",
         size: 150,
-        header: "Lastest status",
+        header: "Latest status",
         cell: ({ row }) => getStatusBadge(row.getValue("latestJobStatus")),
     },
🧹 Nitpick comments (3)
packages/db/tools/scripts/inject-repo-data.ts (1)

37-91: Inconsistent indentation breaks code style.

Lines 37-91 use 4-space indentation while the surrounding code uses 8-space indentation (inside the run function body). This appears to be an accidental de-indent.

packages/web/src/app/[domain]/repos/page.tsx (1)

90-98: Use Prisma.QueryMode enum for type safety.

The mode: 'insensitive' string literal works but using the Prisma enum provides better type safety and IDE support:

 const whereClause: Prisma.RepoWhereInput = {
     orgId: org.id,
     ...(search && {
         OR: [
-            { name: { contains: search, mode: 'insensitive' } },
-            { displayName: { contains: search, mode: 'insensitive' } }
+            { name: { contains: search, mode: Prisma.QueryMode.insensitive } },
+            { displayName: { contains: search, mode: Prisma.QueryMode.insensitive } }
         ]
     }),
 };
packages/web/src/app/[domain]/repos/components/reposTable.tsx (1)

306-326: Cleanup function may prematurely hide the loading indicator.

The cleanup function sets setIsPendingSearch(false) which runs on every re-render before the new effect runs. This could cause a brief flicker where the loader disappears and reappears. Consider only clearing the pending state when the navigation completes:

     useEffect(() => {
         setIsPendingSearch(true);
         const timer = setTimeout(() => {
             const params = new URLSearchParams(searchParams.toString());
             if (searchValue) {
                 params.set('search', searchValue);
             } else {
                 params.delete('search');
             }
             params.set('page', '1');
             router.replace(`${pathname}?${params.toString()}`);
             setIsPendingSearch(false);
         }, 300);
         
-        return () => {
-            clearTimeout(timer);
-            setIsPendingSearch(false);
-        };
+        return () => clearTimeout(timer);
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [searchValue]);

The pending state will be reset when the navigation completes and the component re-renders with new data.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b36e550 and 09d05a5.

📒 Files selected for processing (4)
  • packages/db/tools/scripts/inject-repo-data.ts (2 hunks)
  • packages/web/src/app/[domain]/repos/components/reposTable.tsx (4 hunks)
  • packages/web/src/app/[domain]/repos/page.tsx (2 hunks)
  • packages/web/src/components/ui/input-group.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/web/src/components/ui/input-group.tsx (4)
packages/web/src/lib/utils.ts (1)
  • cn (24-26)
packages/web/src/components/ui/button.tsx (1)
  • Button (56-56)
packages/web/src/components/ui/input.tsx (1)
  • Input (22-22)
packages/web/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
packages/web/src/app/[domain]/repos/page.tsx (5)
packages/web/src/lib/utils.ts (1)
  • isServiceError (438-444)
packages/web/src/lib/serviceError.ts (1)
  • ServiceErrorException (16-20)
packages/web/src/app/[domain]/repos/components/reposTable.tsx (1)
  • ReposTable (281-481)
packages/web/src/actions.ts (1)
  • sew (44-57)
packages/web/src/withAuthV2.ts (1)
  • withOptionalAuthV2 (43-67)
🔇 Additional comments (5)
packages/web/src/app/[domain]/repos/page.tsx (2)

81-86: The status parameter is accepted but not used in filtering.

The status parameter is passed to getReposWithLatestJob but the whereClause doesn't filter by it. This aligns with the PR TODO "Fix status filtering" but worth noting for tracking.


43-47: The isFirstTimeIndex logic looks correct for single-job fetch.

The filter on repo.jobs works correctly since take: 1 fetches only the latest job. The logic correctly identifies repos that haven't been indexed yet but have a pending/in-progress job.

packages/web/src/components/ui/input-group.tsx (1)

11-37: Well-designed InputGroup wrapper with comprehensive styling.

The use of CSS has-[] selectors for focus/error state propagation and alignment variants is a clean approach that keeps the components loosely coupled while maintaining visual cohesion.

packages/web/src/app/[domain]/repos/components/reposTable.tsx (2)

300-304: Nice UX: keyboard shortcut for search focus.

The / hotkey for focusing the search input is a good developer-friendly UX pattern commonly seen in documentation sites and GitHub.


272-288: Clean URL-driven state management.

The prop-based approach with currentPage, pageSize, totalCount, initialSearch, and initialStatus properly separates server-provided state from client-side URL manipulation. This aligns well with Next.js App Router patterns.

Comment on lines +131 to +145
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn(
"flex-1 h-10 px-3 py-2 text-base md:text-sm rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-transparent placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Height mismatch between InputGroup and InputGroupInput.

InputGroup sets h-9 (36px) on line 18, but InputGroupInput applies h-10 (40px) on line 139. This could cause the input to overflow the container or create visual inconsistency.

 <Input
   data-slot="input-group-control"
   className={cn(
-    "flex-1 h-10 px-3 py-2 text-base md:text-sm rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-transparent placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
+    "flex-1 h-full px-3 py-2 text-base md:text-sm rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-transparent placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
     className
   )}
   {...props}
 />
📝 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
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn(
"flex-1 h-10 px-3 py-2 text-base md:text-sm rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-transparent placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
)
}
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn(
"flex-1 h-full px-3 py-2 text-base md:text-sm rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-transparent placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
)
}
🤖 Prompt for AI Agents
packages/web/src/components/ui/input-group.tsx around lines 131 to 145: the
input height class h-10 on InputGroupInput conflicts with the parent
InputGroup's h-9 set earlier, causing visual mismatch; change the
InputGroupInput class to use h-9 (or remove the explicit height and let it
inherit the parent's height) so the control matches the container height, and
update any padding or alignment if needed to keep vertical centering consistent.

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.

2 participants