Skip to content

Conversation

@Lucifer-0612
Copy link
Contributor

@Lucifer-0612 Lucifer-0612 commented Dec 4, 2025

Summary

This PR implements a Saved Repos feature that allows users to bookmark repositories from the OSS Projects list. The feature includes client-side persistence using localStorage (always available) and optional server-side sync via database (feature flag controlled).

Closes #219


🎯 Features Implemented

Client-Side (Always Available)

  • Save/Unsave Repositories: Click star icon to toggle saved state
  • LocalStorage Persistence: Saved repos persist across page reloads
  • Saved Projects Panel: Dedicated side panel to view all saved repos
  • Export to JSON: Download saved repos as JSON file
  • Import from JSON: Restore saved repos from exported file
  • Clear All: Remove all saved repos with confirmation dialog
  • Limit Enforcement: Maximum 100 saved repos per user

Server-Side (Optional - Feature Flag)

  • 🔒 Cross-Device Sync: Sync saved repos across multiple devices
  • 🔒 Conflict Resolution: Merge local and server repos (newer timestamp wins)
  • 🔒 Database Storage: Store in JSONB column with efficient queries
  • 🔒 Protected Endpoints: Authentication required for all API calls
  • 🔒 Feature Flag: FEATURE_SAVED_REPOS_DB controls database sync

📦 Changes Made

Frontend (apps/web)

New Components:

  • src/store/useSavedProjectsStore.ts - Zustand store with localStorage persistence
  • src/components/dashboard/SaveToggle.tsx - Star icon toggle button
  • src/components/dashboard/SavedProjectsPanel.tsx - Side panel UI with export/import

Modified:

  • src/components/dashboard/ProjectsContainer.tsx - Integrated Save column and panel

Backend (apps/api)

New Files:

  • src/services/savedRepos.service.ts - Business logic for saved repos management

Modified:

  • prisma/schema.prisma - Added saved_repos JSONB column to User model
  • src/routers/user.ts - Added tRPC endpoints (getSavedRepos, updateSavedRepos)

Shared (packages/shared)

New Types:

  • types/savedRepos.ts - SavedRepo, SavedReposAction, SavedReposUpdateInput types

Modified:

  • types/index.ts - Export SavedRepo types

Documentation

  • CHANGELOG.md - Feature changelog entry
  • docs/SAVED_REPOS.md - Comprehensive feature documentation
  • docs/PR_PLAN_SAVED_REPOS.md - PR strategy and commit plan
  • docs/QUICKSTART_SAVED_REPOS.md - Quick start guide for developers

🏗️ Technical Implementation

Architecture

Client-Side:

  • State Management: Zustand with persist middleware
  • Storage: localStorage with key oss_saved_repos_v1
  • Serialization: JSON format for export/import
  • Deduplication: Prevents saving same repo multiple times

Server-Side:

  • Database: PostgreSQL with JSONB column
  • ORM: Prisma for type-safe queries
  • API: tRPC with Zod validation
  • Conflict Resolution: Timestamp-based merge strategy

Data Flow

Type Safety

All types are shared between frontend and backend via @opensox/shared package, ensuring end-to-end type safety.


✅ Testing

Automated

  • ✅ TypeScript type checking passed (pnpm tsc --noEmit)
  • ✅ Shared package builds successfully
  • ✅ No lint errors
  • ✅ Clean commit history (9 logical commits)

Manual Testing Recommended

  • Save/unsave repos with star icon
  • Verify localStorage persistence after page reload
  • Open Saved Projects panel and view saved repos
  • Export saved repos to JSON file
  • Import saved repos from JSON file
  • Clear all saved repos with confirmation
  • Test 100 repo limit enforcement
  • Test responsive design on mobile viewport

Database Sync Testing (if feature flag enabled)

  • Set FEATURE_SAVED_REPOS_DB=true in apps/api/.env
  • Run migration: cd apps/api && npx prisma migrate dev
  • Save repos and verify stored in database
  • Test cross-device sync (different browsers)
  • Verify conflict resolution works correctly

🚀 Deployment Plan

Phase 1: Client-Only (Week 1)

  • Deploy frontend changes only
  • No database migration required
  • Risk: Low (localStorage only)
  • Rollback: Revert frontend deployment

Phase 2: Database Backend (Week 2)

  • Deploy backend changes with migration
  • Keep FEATURE_SAVED_REPOS_DB=false initially
  • Monitor database performance
  • Risk: Medium (schema change)
  • Rollback: Set feature flag to false

Phase 3: Gradual Rollout (Week 3+)

  • Enable feature flag for 10% of users
  • Monitor errors and performance
  • Gradually increase to 50%, then 100%
  • Risk: Low (feature flag controlled)
  • Rollback: Disable feature flag

🔄 Rollback Plan

Frontend Issues

git revert <this-pr-commit-hash>
git push origin main

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Save projects locally with per-row save toggles, a Saved Projects panel, and a header badge.
  * Export/import saved projects via JSON and a two-step "Clear All" confirmation.
  * Optional server-backed sync (feature-flag gated) with cross-device merge, conflict resolution favoring newer saves, and a 100-item cap.

* **Bug Fixes**
  * Fixed typos in several blog post titles.

* **Documentation**
  * Added comprehensive guides, quickstart, changelog, and rollout plan for Saved Repos.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

- Create SavedRepo type definition
- Add SavedReposAction and SavedReposUpdateInput types
- Export from shared package index
- Create Zustand store with persist middleware
- Implement actions: add, remove, toggle, clear, setAll, isSaved
- Configure localStorage persistence with key 'oss_saved_repos_v1'
- Add duplicate prevention logic
- Enforce maximum 100 repos limit
- Create star icon toggle button for each repo row
- Implement filled/outline star states
- Prevent event propagation to row click
- Add accessibility attributes (ARIA labels)
- Create side panel for managing saved repos
- Implement export to JSON functionality
- Implement import from JSON functionality
- Add clear all with confirmation (3-second timeout)
- Add empty state and list view
- Include responsive design
- Add 'Save' column as first column in table
- Render SaveToggle component in each row
- Add 'Saved Projects' button with count badge in header
- Add SavedProjectsPanel component
- Manage panel open/close state
- Add saved_repos JSONB column with default '[]'
- Non-breaking change (additive only)
- Supports up to 100 repos per user
- Create getSavedRepos function
- Create mergeSavedRepos with conflict resolution (newer savedAt wins)
- Create updateSavedRepos with add/remove/replace actions
- Enforce maximum 100 repos limit
- Add validation and error handling
- Add getSavedRepos query (protected, feature flag)
- Add updateSavedRepos mutation (protected, feature flag)
- Implement merge logic for sync
- Add Zod validation schemas
- Feature flag: FEATURE_SAVED_REPOS_DB
- Fix z.record() type arguments for Zod compatibility
- Create CHANGELOG entry with feature details
- Create comprehensive feature documentation (SAVED_REPOS.md)
- Add PR plan with commit strategy (PR_PLAN_SAVED_REPOS.md)
- Include quickstart guide (QUICKSTART_SAVED_REPOS.md)
- Add usage guide, API reference, architecture details
- Include troubleshooting and rollout strategy
@vercel
Copy link

vercel bot commented Dec 4, 2025

@Lucifer-0612 is attempting to deploy a commit to the AJEET PRATAP SINGH's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Walkthrough

Adds a Saved Repos feature: client-side persisted Zustand store and UI (SaveToggle, SavedProjectsPanel, ProjectsContainer changes) with import/export/clear; optional server sync behind FEATURE_SAVED_REPOS_DB with a new saved_repos JSONB user column, savedReposService (get/merge/update, max 100), tRPC endpoints, shared types, migration, and docs.

Changes

Cohort / File(s) Summary
Shared types & exports
packages/shared/types/savedRepos.ts, packages/shared/types/index.ts
New types SavedRepo, SavedReposAction, SavedReposUpdateInput; exported from shared types index.
Frontend store
apps/web/src/store/useSavedProjectsStore.ts
New persisted Zustand slice with add/remove/toggle/clear/isSaved and importAndValidate (dedupe, validate, cap 100); persisted to localStorage key oss_saved_repos_v1.
Frontend components
apps/web/src/components/dashboard/SaveToggle.tsx, apps/web/src/components/dashboard/SavedProjectsPanel.tsx
SaveToggle builds SavedRepo payload and toggles store; SavedProjectsPanel shows saved list with Export/Import/Clear and per-item remove.
Frontend integration
apps/web/src/components/dashboard/ProjectsContainer.tsx
Adds Saved Projects header button with badge, Save column in table, per-row SaveToggle, and mounts SavedProjectsPanel.
Backend schema / migration
apps/api/prisma/schema.prisma, migration (add_saved_repos)
Adds saved_repos JSON(Json) column to User model with default [] (JSONB).
Backend service
apps/api/src/services/savedRepos.service.ts
New savedReposService with getSavedRepos, mergeSavedRepos (merge by id, prefer newer savedAt, sort desc), and updateSavedRepos supporting `add
Backend API (tRPC)
apps/api/src/routers/user.ts
New protected endpoints getSavedRepos and updateSavedRepos gated by FEATURE_SAVED_REPOS_DB; updateSavedRepos supports merge flow when localRepos provided.
Docs & guides
CHANGELOG.md, docs/SAVED_REPOS.md, docs/PR_PLAN_SAVED_REPOS.md, docs/QUICKSTART_SAVED_REPOS.md
Added changelog entry and comprehensive docs, PR plan, and quickstart describing usage, migration, feature flag, and rollout.
Content fixes
apps/web/src/data/blogs.ts
Minor string corrections to blog linkText entries (typos fixed).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as SaveToggle / Panel
    participant Store as Zustand (localStorage)
    participant Client as tRPC Client
    participant API as Backend tRPC
    participant Service as savedReposService
    participant DB as Database (User.saved_repos)

    rect rgb(245,245,255)
    User->>UI: click star / open panel
    UI->>Store: toggleProject / import/export/clear
    Store->>Store: add/remove/dedupe/limit (<=100)
    Store->>Store: persist to localStorage
    Store-->>UI: updated savedProjects
    end

    rect rgb(245,255,245)
    Note right of Client: Server sync only when FEATURE_SAVED_REPOS_DB="true"
    Client->>API: getSavedRepos() / updateSavedRepos({action,repos,localRepos?})
    API->>Service: fetch/update saved_repos for user
    Service->>Service: mergeSavedRepos(local, server) (prefer newer savedAt) / apply action
    Service->>DB: persist updated saved_repos (cap 100)
    DB-->>Service: saved_repos
    Service-->>API: saved_repos
    API-->>Client: response
    Client->>Store: optionally replace local state with server state
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • apps/api/src/services/savedRepos.service.ts — merge algorithm, timestamp comparison, cap enforcement, error cases.
    • apps/api/src/routers/user.ts — feature-flag gating, input validation, merge/update control flow.
    • apps/web/src/store/useSavedProjectsStore.tsimportAndValidate correctness (shape validation, dedupe, limit) and persistence keying.
    • apps/web/src/components/dashboard/SavedProjectsPanel.tsx — file import parsing, confirmation UX, and error handling.
    • apps/web/src/components/dashboard/SaveToggle.tsx — payload construction alignment with shared SavedRepo type.

Possibly related PRs

  • #125 — Modifies apps/web/src/components/dashboard/ProjectsContainer.tsx; likely to overlap with layout/header/table integrations.
  • #194 — Edits apps/web/src/data/blogs.ts; overlaps with the blog text corrections made here.
  • #169 — Also touches apps/web/src/data/blogs.ts; related through content updates.

Poem

🐇 I hopped through code and found a star,

Saved a hundred projects near and far,
Burrowed them local, then synced with care,
Now bookmarks sparkle everywhere. ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes align with the Saved Repos feature scope. However, typo fixes in apps/web/src/data/blogs.ts (linkText corrections) are unrelated to the feature and represent scope creep. Remove the typo corrections from blogs.ts as they are unrelated to the Saved Repos feature; consider submitting typo fixes as a separate PR.
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 (3 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 feature being added: a Saved Projects feature with localStorage and optional DB sync, which matches the primary changes across the codebase.
Linked Issues check ✅ Passed All core requirements from issue #219 are fully implemented: local persistence via Zustand store and localStorage, optional DB sync with feature flag, UI components (SaveToggle, SavedProjectsPanel), import/export/clear functionality, and 100-repo limit enforcement.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

@cla-assistant
Copy link

cla-assistant bot commented Dec 4, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

1 similar comment
@cla-assistant
Copy link

cla-assistant bot commented Dec 4, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
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: 10

🧹 Nitpick comments (18)
apps/web/src/data/blogs.ts (1)

23-27: gsoc series title formatting is consistent and clear

normalizing the linkText for the gsoc parts (spacing around “part n”) improves readability without affecting functionality. if you want to be extra precise, you could optionally use the canonical “gsoC” capitalization to match the program branding, but current text is acceptable.

Also applies to: 29-33, 35-39

packages/shared/types/savedRepos.ts (1)

9-9: Avoid using any type in the meta field.

The Record<string, any> type weakens type safety. Consider using Record<string, unknown> for better type checking, or define a specific interface for expected metadata properties.

-  meta?: Record<string, any>; // Extensible metadata
+  meta?: Record<string, unknown>; // extensible metadata
apps/api/prisma/schema.prisma (1)

42-42: LGTM!

The saved_repos JSONB column with an empty array default is appropriate. The naming follows the existing snake_case convention used elsewhere in the schema.

Consider adding a partial index or monitoring query performance if the User table is frequently loaded, as unbounded JSON growth could impact read performance. The 100-repo service-layer limit helps mitigate this.

docs/QUICKSTART_SAVED_REPOS.md (1)

98-98: Clarify the "Clear All twice" behavior.

The instruction "Click 'Clear All' twice (repos should be removed)" implies a confirmation step. Consider clarifying this in the documentation, e.g., "Click 'Clear All', then confirm in the dialog."

apps/web/src/components/dashboard/SaveToggle.tsx (1)

14-14: Use const with arrow function per coding guidelines.

Based on learnings, use const with arrow functions instead of function declarations.

-export default function SaveToggle({ project }: SaveToggleProps) {
+const SaveToggle = ({ project }: SaveToggleProps) => {

Then add at the end of the file:

export default SaveToggle;
apps/web/src/components/dashboard/ProjectsContainer.tsx (1)

19-22: Import order should follow guidelines.

Per coding guidelines, React imports should come first, followed by third-party libraries, then local components, utils, and types.

 import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
+import { useState } from "react";
 import SaveToggle from "./SaveToggle";
 import SavedProjectsPanel from "./SavedProjectsPanel";
 import { useSavedProjectsStore } from "@/store/useSavedProjectsStore";
-import { useState } from "react";
docs/SAVED_REPOS.md (1)

119-136: Type documentation shows any usage.

The meta field documentation shows Record<string, any> which contradicts coding guidelines. Consider documenting with unknown or a more specific type. This also reflects the actual type definition in packages/shared/types/savedRepos.ts.

   savedAt: string; // ISO timestamp
-  meta?: Record<string, any>;
+  meta?: Record<string, unknown>;
 };
apps/api/src/services/savedRepos.service.ts (2)

96-99: Consider truncating instead of throwing on limit exceeded.

The current behavior throws an error if updatedRepos.length > 100, but a friendlier approach might be to truncate to 100 (keeping most recent by savedAt) and return a warning. This prevents data loss if a user tries to sync from multiple devices with accumulated repos.

Alternatively, if throwing is intentional, consider a more informative error that helps users understand how to recover.


18-20: Consider logging user-not-found errors for debugging.

Per coding guidelines, logging errors with context (userId, endpoint, timestamp) aids debugging. A simple log before throwing would help trace issues in production.

         if (!user) {
+            console.error(`User not found: ${userId}`);
             throw new Error("User not found");
         }
apps/web/src/store/useSavedProjectsStore.ts (2)

20-29: Consider enforcing the 100-repo limit client-side.

The backend enforces a 100-repo maximum, but addProject doesn't check this limit. Adding a client-side guard prevents unnecessary API calls and provides immediate user feedback.

             addProject: (project: SavedRepo) =>
                 set((state) => {
-                    // Check if project already exists
+                    // check if project already exists
                     const exists = state.savedProjects.some((p) => p.id === project.id);
                     if (exists) return state;
 
+                    // enforce 100-repo limit
+                    if (state.savedProjects.length >= 100) return state;
+
                     return {
                         savedProjects: [...state.savedProjects, project],
                     };
                 }),

54-54: setAll should validate and dedupe imported data.

When importing from JSON, malformed or duplicate entries could corrupt state. Consider adding validation and deduplication, similar to how addProject handles duplicates.

-            setAll: (projects: SavedRepo[]) => set({ savedProjects: projects }),
+            setAll: (projects: SavedRepo[]) => {
+                // dedupe by id, keep first occurrence, enforce limit
+                const seen = new Set<string>();
+                const deduped = projects.filter((p) => {
+                    if (!p.id || seen.has(p.id)) return false;
+                    seen.add(p.id);
+                    return true;
+                }).slice(0, 100);
+                set({ savedProjects: deduped });
+            },
apps/api/src/routers/user.ts (3)

51-79: Extract duplicated Zod schema for repos.

The repo object schema is duplicated between repos and localRepos. Extract to a shared constant for DRY and easier maintenance.

+const savedRepoSchema = z.object({
+  id: z.string(),
+  name: z.string(),
+  url: z.string(),
+  language: z.string().optional(),
+  popularity: z.enum(["low", "medium", "high"]).optional(),
+  competitionScore: z.number().optional(),
+  savedAt: z.string(),
+  meta: z.record(z.string(), z.unknown()).optional(),
+});
+
 // update user's saved repos with merge logic (feature flag: FEATURE_SAVED_REPOS_DB)
 updateSavedRepos: protectedProcedure
   .input(
     z.object({
       action: z.enum(["add", "remove", "replace"]),
-      repos: z.array(
-        z.object({
-          id: z.string(),
-          name: z.string(),
-          url: z.string(),
-          language: z.string().optional(),
-          popularity: z.enum(["low", "medium", "high"]).optional(),
-          competitionScore: z.number().optional(),
-          savedAt: z.string(),
-          meta: z.record(z.string(), z.any()).optional(),
-        })
-      ),
-      localRepos: z
-        .array(
-          z.object({
-            id: z.string(),
-            name: z.string(),
-            url: z.string(),
-            language: z.string().optional(),
-            popularity: z.enum(["low", "medium", "high"]).optional(),
-            competitionScore: z.number().optional(),
-            savedAt: z.string(),
-            meta: z.record(z.string(), z.any()).optional(),
-          })
-        )
-        .optional(),
+      repos: z.array(savedRepoSchema),
+      localRepos: z.array(savedRepoSchema).optional(),
     })
   )

63-63: Avoid z.any() for meta field.

Using z.any() bypasses type safety. Consider using z.unknown() which is safer and aligns with coding guidelines to avoid any types.

-          meta: z.record(z.string(), z.any()).optional(),
+          meta: z.record(z.string(), z.unknown()).optional(),

Also applies to: 76-76


13-16: Avoid any type annotation on ctx parameter.

Multiple procedure handlers use { ctx }: any which bypasses TypeScript's type checking. Per coding guidelines, avoid any and use proper types or unknown with type guards.

Consider defining a proper context type or using the inferred type from tRPC:

// if tRPC provides a type helper, use it
// otherwise, define the expected shape
interface ProtectedContext {
  user: { id: string };
  db: { prisma: PrismaClient };
}

Also applies to: 19-22, 31-37, 41-46, 82-113

apps/web/src/components/dashboard/SavedProjectsPanel.tsx (4)

66-74: avoid setstate after unmount in clear‑all confirmation

handleClearAll starts a setTimeout that flips showClearConfirm back to false after 3 seconds, but that timeout isn’t cleared on unmount. if the panel is closed (and unmounted) before the timeout fires, react can warn about calling setState on an unmounted component.

this is minor but easy to avoid by tracking the timeout id and clearing it in an effect cleanup:

-    const [showClearConfirm, setShowClearConfirm] = useState(false);
+    const [showClearConfirm, setShowClearConfirm] = useState(false);
+    const clearConfirmTimeoutRef = useRef<number | null>(null);
@@
-        } else {
-            setShowClearConfirm(true);
-            setTimeout(() => setShowClearConfirm(false), 3000);
-        }
+        } else {
+            setShowClearConfirm(true);
+            if (clearConfirmTimeoutRef.current) {
+                window.clearTimeout(clearConfirmTimeoutRef.current);
+            }
+            clearConfirmTimeoutRef.current = window.setTimeout(
+                () => setShowClearConfirm(false),
+                3000
+            );
+        }
     };
+
+    useEffect(() => {
+        return () => {
+            if (clearConfirmTimeoutRef.current) {
+                window.clearTimeout(clearConfirmTimeoutRef.current);
+            }
+        };
+    }, []);

(remember to import useEffect alongside the other react hooks.)


78-106: improve dialog semantics and keyboard accessibility

the panel visually behaves like a modal side‑sheet, but it currently lacks dialog semantics (role="dialog", aria-modal) and does not manage focus (e.g., moving focus into the panel on open, optionally closing on escape).

to align with accessibility guidelines:

  • add role="dialog" and aria-modal="true" to the panel container,
  • link it to the heading via aria-labelledby,
  • optionally move focus to the close button when isOpen becomes true, and
  • consider handling escape key presses to close the panel.

for example:

-            {/* Panel */}
-            <div className="fixed right-0 top-0 h-full w-full sm:w-96 bg-ox-content border-l border-dash-border z-50 flex flex-col">
-                {/* Header */}
-                <div className="flex items-center justify-between p-4 border-b border-dash-border">
+            {/* panel */}
+            <div
+                className="fixed right-0 top-0 h-full w-full sm:w-96 bg-ox-content border-l border-dash-border z-50 flex flex-col"
+                role="dialog"
+                aria-modal="true"
+                aria-labelledby="saved-projects-heading"
+            >
+                {/* header */}
+                <div className="flex items-center justify-between p-4 border-b border-dash-border">
@@
-                        <h2 className="text-lg font-semibold text-text-primary">
+                        <h2
+                            id="saved-projects-heading"
+                            className="text-lg font-semibold text-text-primary"
+                        >
                             Saved Projects
                         </h2>

and, if desired, you can add a useEffect that focuses the close button ref on open and listens for keydown events to close on escape. as per coding guidelines.

Also applies to: 145-203


82-83: replace hardcoded/utility colors with semantic design‑token classes

several classes use tailwind’s palette or generic colors directly (bg-black/50, hover:bg-white/10, bg-blue-500/15, text-blue-500, bg-emerald-500/15, text-emerald-500, hover:bg-red-500/20, text-red-500). the design system asks for semantic token classes (e.g., bg-surface-overlay, text-text-danger, bg-badge-info) instead of appearance‑based colors.

to keep theming consistent and easier to change, please swap these for semantic classes defined in design-tokens.ts / tailwind.config.ts. for example (exact token names may differ in your setup):

-            <div
-                className="fixed inset-0 bg-black/50 z-40"
+            <div
+                className="fixed inset-0 bg-overlay-scrim z-40"
@@
-                        <Badge variant="secondary" className="bg-brand-purple/20 text-brand-purple">
+                        <Badge variant="secondary" className="bg-badge-accent/20 text-badge-accent">
@@
-                            className="bg-ox-purple hover:bg-ox-purple/80 text-text-primary text-sm h-9"
+                            className="bg-button-primary hover:bg-button-primary-hover text-text-on-primary text-sm h-9"
@@
-                            className="bg-ox-purple hover:bg-ox-purple/80 text-text-primary text-sm h-9"
+                            className="bg-button-primary hover:bg-button-primary-hover text-text-on-primary text-sm h-9"
@@
-                                className="p-3 bg-ox-gray/30 rounded-lg border border-dash-border hover:border-brand-purple/50 transition-colors group"
+                                className="p-3 bg-surface-subtle rounded-lg border border-dash-border hover:border-border-accent/50 transition-colors group"
@@
-                                                    className="text-xs bg-blue-500/15 text-blue-500"
+                                                    className="text-xs bg-badge-info/15 text-badge-info"
@@
-                                                    className="text-xs bg-emerald-500/15 text-emerald-500"
+                                                    className="text-xs bg-badge-success/15 text-badge-success"
@@
-                                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-500/20 rounded transition-all"
+                                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-surface-danger/20 rounded transition-all"
@@
-                                        <XMarkIcon className="h-4 w-4 text-red-500" />
+                                        <XMarkIcon className="h-4 w-4 text-icon-danger" />

please adjust token names to the actual ones in your design system. as per coding guidelines.

Also applies to: 95-97, 114-115, 121-122, 158-159, 174-175, 182-183, 194-195, 197-197


20-23: align component declaration with arrow‑function convention

guidelines prefer const arrow functions for components. SavedProjectsPanel is currently a function declaration; behavior is fine, but you can align with the agreed style:

-export default function SavedProjectsPanel({
-    isOpen,
-    onClose,
-}: SavedProjectsPanelProps) {
+const SavedProjectsPanel = ({ isOpen, onClose }: SavedProjectsPanelProps) => {
@@
-}
+};
+
+export default SavedProjectsPanel;

this also makes it easier to attach memoization (React.memo) later if needed. as per coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cdc72d and a0da2ac.

📒 Files selected for processing (14)
  • CHANGELOG.md (1 hunks)
  • apps/api/prisma/schema.prisma (1 hunks)
  • apps/api/src/routers/user.ts (2 hunks)
  • apps/api/src/services/savedRepos.service.ts (1 hunks)
  • apps/web/src/components/dashboard/ProjectsContainer.tsx (6 hunks)
  • apps/web/src/components/dashboard/SaveToggle.tsx (1 hunks)
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1 hunks)
  • apps/web/src/data/blogs.ts (4 hunks)
  • apps/web/src/store/useSavedProjectsStore.ts (1 hunks)
  • docs/PR_PLAN_SAVED_REPOS.md (1 hunks)
  • docs/QUICKSTART_SAVED_REPOS.md (1 hunks)
  • docs/SAVED_REPOS.md (1 hunks)
  • packages/shared/types/index.ts (1 hunks)
  • packages/shared/types/savedRepos.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • packages/shared/types/savedRepos.ts
  • apps/api/src/services/savedRepos.service.ts
  • packages/shared/types/index.ts
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/api/src/routers/user.ts
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • packages/shared/types/savedRepos.ts
  • apps/api/src/services/savedRepos.service.ts
  • packages/shared/types/index.ts
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/api/src/routers/user.ts
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • packages/shared/types/savedRepos.ts
  • apps/api/src/services/savedRepos.service.ts
  • packages/shared/types/index.ts
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/api/src/routers/user.ts
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
**/*[A-Z]*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use PascalCase for component file names (e.g., UserProfile.tsx)

Files:

  • packages/shared/types/savedRepos.ts
  • apps/api/src/services/savedRepos.service.ts
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • packages/shared/types/savedRepos.ts
  • apps/api/src/services/savedRepos.service.ts
  • packages/shared/types/index.ts
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/api/src/routers/user.ts
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • packages/shared/types/savedRepos.ts
  • apps/api/src/services/savedRepos.service.ts
  • packages/shared/types/index.ts
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/api/src/routers/user.ts
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
apps/api/src/**/{services,models,database}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Encrypt sensitive data (passwords, tokens, API keys) before storing in database

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/services/savedRepos.service.ts
  • apps/api/src/routers/user.ts
apps/api/src/**/{database,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Handle database connection failures gracefully with retry logic

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/{database,models,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Always use parameterized queries or ORM methods to prevent SQL injection

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/{services,database}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Wrap database transactions in try-catch blocks with proper rollback on failure

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/{services,repositories}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid N+1 queries; use eager loading or batch queries when fetching related data

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/{services,models}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Validate data against schema before database operations

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/services/savedRepos.service.ts
  • apps/api/src/routers/user.ts
apps/api/src/**/{database,clients,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Use connection pooling for database and external service clients

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/{services,clients,handlers}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Implement timeouts for external API calls to prevent hanging requests

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/api/src/**/{middleware,services,routes,controllers}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Log all critical operations (auth attempts, payment processing, data mutations)

Files:

  • apps/api/src/services/savedRepos.service.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

apps/web/src/**/*.{ts,tsx}: Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts
NEVER use hardcoded hex values (e.g., #5519f7) directly in components; ALWAYS reference colors from the design token system using Tailwind classes
Use semantic color names that describe purpose, not appearance
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobile p-4 (1rem) and desktop p-[60px]
Use appropriate border radius: small elements rounded-lg, medium rounded-xl, large rounded-2xl, buttons rounded-[16px]
Use animation durations: fast duration-100 (0.1s), normal duration-300 (0.3s), slow duration-600 (0.6s)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
apps/web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Optimize images using next/image

apps/web/src/**/*.{tsx,ts}: Use Zustand for global state, located in src/store/
Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Optimize images using next/image
Memoize expensive computations
Define a type when defining const functions

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
apps/web/src/components/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts,jsx,js}: Never use hardcoded hex values directly in components; always reference colors from the design token system using Tailwind classes
Use semantic color names from the design token system that describe purpose, not appearance (e.g., bg-brand-purple, bg-surface-primary, text-text-primary)
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale for section padding: p-4 (1rem) on mobile, p-[60px] on desktop
Use rounded-lg (0.5rem) for small elements, rounded-xl (1rem) for medium elements, rounded-2xl (1.5rem) for large elements, and rounded-[16px] for buttons
Use duration-100 (0.1s) for fast transitions, duration-300 (0.3s) for normal transitions, and duration-600 (0.6s) for slow transitions
Use available custom animations: animate-accordion-down, animate-accordion-up, animate-scrollRight, animate-scrollLeft, animate-customspin, animate-spin-slow, animate-spin-slow-reverse, animate-marquee, animate-marquee-vertical, animate-shine
Prefer functional components with TypeScript
Extract reusable logic into custom hooks
Prefer controlled components over uncontrolled
Include proper aria labels for accessibility
Ensure keyboard navigation works in interactive components
Maintain proper heading hierarchy in page components
Provide alt text for images
Use 'class:' instead of the ternary operator in class tags whenever possible
Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)
Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
apps/web/src/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts}: Use proper TypeScript types and avoid using any type
Use descriptive prop names and define prop types using TypeScript interfaces or types
Name components using PascalCase (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
apps/web/src/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/**/*.{tsx,ts,jsx,js}: Organize imports in order: React → third-party → local components → utils → types
Use absolute imports from @/ prefix when available
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags
Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)
Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/data/blogs.ts
  • apps/web/src/components/dashboard/SaveToggle.tsx
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/web/src/store/useSavedProjectsStore.ts
🧠 Learnings (7)
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{services,repositories}/*.{js,ts} : Avoid N+1 queries; use eager loading or batch queries when fetching related data

Applied to files:

  • apps/api/src/services/savedRepos.service.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Export types from shared package for consistency across apps

Applied to files:

  • packages/shared/types/index.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Remove unused imports

Applied to files:

  • packages/shared/types/index.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Always validate user authentication before processing protected routes

Applied to files:

  • apps/api/src/routers/user.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts} : Use Zustand for global state, located in `src/store/`

Applied to files:

  • apps/web/src/store/useSavedProjectsStore.ts
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Use zustand for global state (located in `src/store/`)

Applied to files:

  • apps/web/src/store/useSavedProjectsStore.ts
🧬 Code graph analysis (4)
apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1)
apps/web/src/store/useSavedProjectsStore.ts (1)
  • useSavedProjectsStore (15-66)
apps/web/src/components/dashboard/SaveToggle.tsx (3)
apps/web/src/types/projects.ts (1)
  • DashboardProjectsProps (1-13)
apps/web/src/store/useSavedProjectsStore.ts (1)
  • useSavedProjectsStore (15-66)
packages/shared/types/savedRepos.ts (1)
  • SavedRepo (1-10)
apps/web/src/components/dashboard/ProjectsContainer.tsx (3)
apps/web/src/store/useSavedProjectsStore.ts (1)
  • useSavedProjectsStore (15-66)
apps/web/src/components/dashboard/SaveToggle.tsx (1)
  • SaveToggle (14-60)
apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1)
  • SavedProjectsPanel (20-207)
apps/web/src/store/useSavedProjectsStore.ts (1)
packages/shared/types/savedRepos.ts (1)
  • SavedRepo (1-10)
🪛 Biome (2.1.2)
apps/api/src/services/savedRepos.service.ts

[error] 76-76: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 77-77: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 83-83: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🪛 LanguageTool
docs/PR_PLAN_SAVED_REPOS.md

[style] ~330-~330: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ecently Saved" section on dashboard? 3. Should we implement folders/tags in this PR or...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~331-~331: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nt folders/tags in this PR or later? 4. Should we add a limit warning when approaching...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~332-~332: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... warning when approaching 100 repos? 5. Should we add a "Share saved repos" feature? ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🔇 Additional comments (14)
apps/web/src/data/blogs.ts (2)

16-21: typo fix in 'online presence' entry looks good

the corrected wording for linkText improves clarity and has no behavioral impact.


53-57: copy edits for remaining titles are correct

fixes to “shouldn't register a company”, “layoff-proof”, and “website design consistent” all read well and keep the data consistent.

Also applies to: 65-69, 77-81

packages/shared/types/savedRepos.ts (1)

12-17: LGTM!

The action union type and update input type are well-defined and provide good type safety for the API contract.

CHANGELOG.md (1)

79-84: API usage example shows undocumented localRepos parameter.

The SavedReposUpdateInput type only defines action and repos fields. The localRepos parameter shown here isn't part of the type definition in packages/shared/types/savedRepos.ts. Either update the type to include this field or correct the example.

packages/shared/types/index.ts (1)

1-3: LGTM!

The new savedRepos export follows the existing pattern and correctly exposes the shared types for cross-app consumption. Based on learnings, this aligns with exporting types from the shared package for consistency across apps.

apps/web/src/components/dashboard/ProjectsContainer.tsx (2)

138-140: LGTM!

The SaveToggle integration is clean. The component handles its own click event propagation prevention and accessibility.


202-205: LGTM!

SavedProjectsPanel integration is correct with proper state binding for open/close behavior.

docs/PR_PLAN_SAVED_REPOS.md (1)

1-356: Well-structured PR plan documentation.

The document provides comprehensive coverage of commit strategy, testing checklist, deployment phases, and rollback procedures. This is valuable for the phased rollout approach.

docs/SAVED_REPOS.md (1)

1-214: Comprehensive feature documentation.

Good coverage of user workflows, developer setup, architecture, conflict resolution, limits, rollout strategy, and troubleshooting. This will help onboard users and developers to the feature.

apps/api/src/services/savedRepos.service.ts (1)

30-50: LGTM!

The merge logic correctly handles conflict resolution using savedAt timestamps and returns results sorted by most recent first.

apps/web/src/store/useSavedProjectsStore.ts (2)

36-50: LGTM!

The toggleProject implementation is clean and handles both add/remove cases correctly with proper duplicate checking.


60-65: LGTM!

Persist configuration is well-structured with appropriate storage key versioning and partialize to only persist relevant state.

apps/api/src/routers/user.ts (2)

40-47: LGTM for feature flag gating.

The feature flag check correctly returns an empty array when disabled, allowing graceful degradation without errors.


89-105: LGTM for merge logic.

The conditional merge when localRepos is provided with replace action correctly implements the conflict resolution strategy described in the documentation.

- Fix import issues in SavedProjectsPanel (add ChangeEvent type import, remove unused SavedRepo, reorder imports)
- Add block scope to switch cases in savedRepos.service to prevent variable leaking
- Fix invalid Tailwind class hover:bg-white-500 to hover:bg-ox-purple/80 in ProjectsContainer
- Add keyboard accessibility (Enter/Space) and aria-labels to buttons
- Add importAndValidate method to store for safe data validation and deduplication
- Replace unsafe popularity type assertion with proper validation in SaveToggle
- Remove nested interactive elements (move onClick to button from div)
- Remove unused Checkbox import from SaveToggle
- Update PR plan with correct issue number (apsinghdev#219)
- Replace hardcoded Windows path with cross-platform placeholder in docs

All changes improve code quality, type safety, accessibility, and documentation.
Copy link
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: 1

🧹 Nitpick comments (6)
apps/web/src/components/dashboard/SaveToggle.tsx (1)

13-47: Consider using arrow function syntax for component definition.

Per coding guidelines, prefer const with arrow functions instead of function declarations.

-export default function SaveToggle({ project }: SaveToggleProps) {
+const SaveToggle = ({ project }: SaveToggleProps) => {
     const { toggleProject, isSaved } = useSavedProjectsStore();
     // ... rest of component
-}
+};
+
+export default SaveToggle;

Additionally, consider extracting isValidPopularity outside the component to avoid recreating it on each render:

+const isValidPopularity = (
+    value: string | undefined
+): value is "low" | "medium" | "high" => {
+    return value === "low" || value === "medium" || value === "high";
+};
+
-export default function SaveToggle({ project }: SaveToggleProps) {
+const SaveToggle = ({ project }: SaveToggleProps) => {
     const { toggleProject, isSaved } = useSavedProjectsStore();
     const saved = isSaved(project.id);
-
-    // Type guard to validate popularity value
-    const isValidPopularity = (
-        value: string | undefined
-    ): value is "low" | "medium" | "high" => {
-        return value === "low" || value === "medium" || value === "high";
-    };

Based on learnings, use const with arrow functions.

apps/web/src/components/dashboard/SavedProjectsPanel.tsx (5)

20-23: Consider using arrow function syntax.

Per coding guidelines, prefer const with arrow functions instead of function declarations.

-export default function SavedProjectsPanel({
-    isOpen,
-    onClose,
-}: SavedProjectsPanelProps) {
+const SavedProjectsPanel = ({
+    isOpen,
+    onClose,
+}: SavedProjectsPanelProps) => {

And at the end of the file:

export default SavedProjectsPanel;

52-56: Clarify success message handling.

When result.success is true but result.error contains a value, it's actually a warning message (e.g., "Imported 100 projects (limited to 100, 5 duplicates removed)"). Consider renaming this field or adjusting the messaging for clarity.

             if (result.success) {
-                alert(result.error || "Projects imported successfully!");
+                const message = result.error 
+                    ? `Import completed: ${result.error}`
+                    : "Projects imported successfully!";
+                alert(message);
             } else {
                 alert(result.error || "Failed to import projects.");
             }

Alternatively, consider updating the store's return type to use warning instead of reusing error for success cases.


70-78: Consider clearing timeout on unmount.

If the component unmounts while the confirmation timeout is active, it could cause a state update on an unmounted component.

+import { useRef, useState, useEffect } from "react";
+
 // Inside component:
+    const clearTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+
+    useEffect(() => {
+        return () => {
+            if (clearTimeoutRef.current) {
+                clearTimeout(clearTimeoutRef.current);
+            }
+        };
+    }, []);
+
     const handleClearAll = () => {
         if (showClearConfirm) {
             clearAllSaved();
             setShowClearConfirm(false);
+            if (clearTimeoutRef.current) {
+                clearTimeout(clearTimeoutRef.current);
+            }
         } else {
             setShowClearConfirm(true);
-            setTimeout(() => setShowClearConfirm(false), 3000);
+            clearTimeoutRef.current = setTimeout(() => setShowClearConfirm(false), 3000);
         }
     };

82-110: Add keyboard escape handler and dialog semantics.

The panel behaves like a modal but lacks keyboard escape support and proper ARIA dialog semantics.

Add keyboard handler and dialog role:

+import { useEffect } from "react";
+
 // Inside component, add escape handler:
+    useEffect(() => {
+        const handleKeyDown = (e: KeyboardEvent) => {
+            if (e.key === "Escape" && isOpen) {
+                onClose();
+            }
+        };
+        document.addEventListener("keydown", handleKeyDown);
+        return () => document.removeEventListener("keydown", handleKeyDown);
+    }, [isOpen, onClose]);

 // Update panel div:
-            <div className="fixed right-0 top-0 h-full w-full sm:w-96 bg-ox-content border-l border-dash-border z-50 flex flex-col">
+            <div
+                role="dialog"
+                aria-modal="true"
+                aria-label="Saved Projects"
+                className="fixed right-0 top-0 h-full w-full sm:w-96 bg-ox-content border-l border-dash-border z-50 flex flex-col"
+            >

As per coding guidelines, ensure keyboard navigation works in interactive components.


196-202: Improve remove button discoverability for keyboard/touch users.

The remove button is only visible on hover (opacity-0 group-hover:opacity-100), making it inaccessible for keyboard navigation and touch devices.

                                     <button
                                         onClick={() => removeProject(repo.id)}
-                                        className="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-500/20 rounded transition-all"
+                                        className="p-1 opacity-0 group-hover:opacity-100 focus:opacity-100 hover:bg-red-500/20 focus:bg-red-500/20 rounded transition-all"
                                         aria-label="Remove from saved"
                                     >

Adding focus:opacity-100 ensures the button becomes visible when focused via keyboard navigation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0da2ac and 1513ef2.

📒 Files selected for processing (7)
  • apps/api/src/services/savedRepos.service.ts (1 hunks)
  • apps/web/src/components/dashboard/ProjectsContainer.tsx (6 hunks)
  • apps/web/src/components/dashboard/SaveToggle.tsx (1 hunks)
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1 hunks)
  • apps/web/src/store/useSavedProjectsStore.ts (1 hunks)
  • docs/PR_PLAN_SAVED_REPOS.md (1 hunks)
  • docs/QUICKSTART_SAVED_REPOS.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/src/components/dashboard/ProjectsContainer.tsx
  • apps/api/src/services/savedRepos.service.ts
  • apps/web/src/store/useSavedProjectsStore.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

apps/web/src/**/*.{ts,tsx}: Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts
NEVER use hardcoded hex values (e.g., #5519f7) directly in components; ALWAYS reference colors from the design token system using Tailwind classes
Use semantic color names that describe purpose, not appearance
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobile p-4 (1rem) and desktop p-[60px]
Use appropriate border radius: small elements rounded-lg, medium rounded-xl, large rounded-2xl, buttons rounded-[16px]
Use animation durations: fast duration-100 (0.1s), normal duration-300 (0.3s), slow duration-600 (0.6s)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
**/*[A-Z]*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use PascalCase for component file names (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
apps/web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Optimize images using next/image

apps/web/src/**/*.{tsx,ts}: Use Zustand for global state, located in src/store/
Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Optimize images using next/image
Memoize expensive computations
Define a type when defining const functions

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
apps/web/src/components/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts,jsx,js}: Never use hardcoded hex values directly in components; always reference colors from the design token system using Tailwind classes
Use semantic color names from the design token system that describe purpose, not appearance (e.g., bg-brand-purple, bg-surface-primary, text-text-primary)
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale for section padding: p-4 (1rem) on mobile, p-[60px] on desktop
Use rounded-lg (0.5rem) for small elements, rounded-xl (1rem) for medium elements, rounded-2xl (1.5rem) for large elements, and rounded-[16px] for buttons
Use duration-100 (0.1s) for fast transitions, duration-300 (0.3s) for normal transitions, and duration-600 (0.6s) for slow transitions
Use available custom animations: animate-accordion-down, animate-accordion-up, animate-scrollRight, animate-scrollLeft, animate-customspin, animate-spin-slow, animate-spin-slow-reverse, animate-marquee, animate-marquee-vertical, animate-shine
Prefer functional components with TypeScript
Extract reusable logic into custom hooks
Prefer controlled components over uncontrolled
Include proper aria labels for accessibility
Ensure keyboard navigation works in interactive components
Maintain proper heading hierarchy in page components
Provide alt text for images
Use 'class:' instead of the ternary operator in class tags whenever possible
Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)
Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
apps/web/src/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts}: Use proper TypeScript types and avoid using any type
Use descriptive prop names and define prop types using TypeScript interfaces or types
Name components using PascalCase (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
apps/web/src/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/**/*.{tsx,ts,jsx,js}: Organize imports in order: React → third-party → local components → utils → types
Use absolute imports from @/ prefix when available
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags
Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)
Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
🧠 Learnings (19)
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Organize imports: react → third-party → local components → utils → types

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Organize imports in order: React → third-party → local components → utils → types

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Remove unused imports

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Remove unused imports

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts} : Use proper TypeScript types and avoid using `any` type

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Use absolute imports from `@/` prefix when available

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Prefer controlled components over uncontrolled

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Prefer controlled components over uncontrolled

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Prefer functional components with TypeScript and use proper TypeScript types, avoid `any`

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Use 'class:' instead of the ternary operator in class tags whenever possible

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Include proper aria labels for accessibility

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Include proper aria labels for accessibility

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Ensure keyboard navigation works in interactive components

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Ensure keyboard navigation works

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Maintain proper heading hierarchy

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*[Cc]omponent.{tsx,ts} : Keep components focused and single-responsibility

Applied to files:

  • apps/web/src/components/dashboard/SaveToggle.tsx
🧬 Code graph analysis (2)
apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1)
apps/web/src/store/useSavedProjectsStore.ts (1)
  • useSavedProjectsStore (16-123)
apps/web/src/components/dashboard/SaveToggle.tsx (3)
apps/web/src/types/projects.ts (1)
  • DashboardProjectsProps (1-13)
apps/web/src/store/useSavedProjectsStore.ts (1)
  • useSavedProjectsStore (16-123)
packages/shared/types/savedRepos.ts (1)
  • SavedRepo (1-10)
🪛 LanguageTool
docs/PR_PLAN_SAVED_REPOS.md

[style] ~330-~330: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ecently Saved" section on dashboard? 3. Should we implement folders/tags in this PR or...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~331-~331: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nt folders/tags in this PR or later? 4. Should we add a limit warning when approaching...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~332-~332: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... warning when approaching 100 repos? 5. Should we add a "Share saved repos" feature? ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.18.1)
docs/QUICKSTART_SAVED_REPOS.md

4-4: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (4)
docs/PR_PLAN_SAVED_REPOS.md (1)

1-366: Well-structured PR plan document.

The documentation is comprehensive, covering commit strategy, testing checklist, deployment phases, rollback procedures, and success metrics. The related issues section correctly references #219.

docs/QUICKSTART_SAVED_REPOS.md (1)

39-50: LGTM!

The quick start guide now correctly uses a generic path placeholder instead of a hardcoded Windows path, making it cross-platform friendly.

apps/web/src/components/dashboard/SaveToggle.tsx (1)

49-64: Good accessibility implementation.

The button properly includes aria-label and aria-pressed attributes, uses semantic HTML, and correctly prevents event propagation to the parent row.

apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1)

29-40: LGTM!

The export function correctly creates a blob, handles the download via a temporary anchor element, and properly cleans up by revoking the object URL.

- Remove unused setAll from store destructuring
- Keep only used hooks: savedProjects, clearAllSaved, removeProject, importAndValidate
Copy link
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: 0

🧹 Nitpick comments (4)
apps/web/src/components/dashboard/SavedProjectsPanel.tsx (4)

20-23: align component definition with arrow-function convention

project rules prefer const arrow functions over function declarations; you could rewrite the component as a typed arrow function (const SavedProjectsPanel: React.FC<SavedProjectsPanelProps> = ({ ... }) => { ... }) to match the rest of the codebase.

as per coding guidelines.


70-77: optional: avoid lingering timeout after unmount

setTimeout(() => setShowClearConfirm(false), 3000) can still fire after the panel unmounts, causing a state update on an unmounted component. it’s harmless in practice but you can store the timeout id in a useRef and clear it in a useEffect cleanup (and before scheduling a new timeout) to make this pattern more robust.


91-110: improve dialog semantics for accessibility

this behaves like a modal side panel; consider adding dialog semantics so assistive tech understands it:

  • add role="dialog" and aria-modal="true" to the panel container.
  • give the h2 an id and reference it via aria-labelledby on the panel.

this will make the structure more accessible without changing behavior.

as per coding guidelines.


175-188: use semantic design-token colors instead of raw blue/emerald utilities

the language and popularity badges use bg-blue-500/15 text-blue-500 and bg-emerald-500/15 text-emerald-500. guidelines ask for semantic classes from the design-token system (e.g., surface/label/accent tokens) rather than palette-based names. consider switching these to the closest semantic badge/text tokens defined in design-tokens.ts / tailwind config for consistency.

as per coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1513ef2 and 091de8c.

📒 Files selected for processing (1)
  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

apps/web/src/**/*.{ts,tsx}: Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts
NEVER use hardcoded hex values (e.g., #5519f7) directly in components; ALWAYS reference colors from the design token system using Tailwind classes
Use semantic color names that describe purpose, not appearance
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobile p-4 (1rem) and desktop p-[60px]
Use appropriate border radius: small elements rounded-lg, medium rounded-xl, large rounded-2xl, buttons rounded-[16px]
Use animation durations: fast duration-100 (0.1s), normal duration-300 (0.3s), slow duration-600 (0.6s)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
**/*[A-Z]*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use PascalCase for component file names (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
apps/web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Optimize images using next/image

apps/web/src/**/*.{tsx,ts}: Use Zustand for global state, located in src/store/
Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Optimize images using next/image
Memoize expensive computations
Define a type when defining const functions

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
apps/web/src/components/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts,jsx,js}: Never use hardcoded hex values directly in components; always reference colors from the design token system using Tailwind classes
Use semantic color names from the design token system that describe purpose, not appearance (e.g., bg-brand-purple, bg-surface-primary, text-text-primary)
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale for section padding: p-4 (1rem) on mobile, p-[60px] on desktop
Use rounded-lg (0.5rem) for small elements, rounded-xl (1rem) for medium elements, rounded-2xl (1.5rem) for large elements, and rounded-[16px] for buttons
Use duration-100 (0.1s) for fast transitions, duration-300 (0.3s) for normal transitions, and duration-600 (0.6s) for slow transitions
Use available custom animations: animate-accordion-down, animate-accordion-up, animate-scrollRight, animate-scrollLeft, animate-customspin, animate-spin-slow, animate-spin-slow-reverse, animate-marquee, animate-marquee-vertical, animate-shine
Prefer functional components with TypeScript
Extract reusable logic into custom hooks
Prefer controlled components over uncontrolled
Include proper aria labels for accessibility
Ensure keyboard navigation works in interactive components
Maintain proper heading hierarchy in page components
Provide alt text for images
Use 'class:' instead of the ternary operator in class tags whenever possible
Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)
Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
apps/web/src/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts}: Use proper TypeScript types and avoid using any type
Use descriptive prop names and define prop types using TypeScript interfaces or types
Name components using PascalCase (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
apps/web/src/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/**/*.{tsx,ts,jsx,js}: Organize imports in order: React → third-party → local components → utils → types
Use absolute imports from @/ prefix when available
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags
Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)
Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
🧠 Learnings (12)
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Organize imports: react → third-party → local components → utils → types

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Organize imports in order: React → third-party → local components → utils → types

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Remove unused imports

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Remove unused imports

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Use absolute imports from `@/` prefix when available

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts} : Use proper TypeScript types and avoid using `any` type

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Ensure keyboard navigation works

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Use absolute imports from `@/` prefix when available

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Prefer functional components with TypeScript and use proper TypeScript types, avoid `any`

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Use zustand for global state (located in `src/store/`)

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Applied to files:

  • apps/web/src/components/dashboard/SavedProjectsPanel.tsx
🧬 Code graph analysis (1)
apps/web/src/components/dashboard/SavedProjectsPanel.tsx (2)
apps/web/src/store/useSavedProjectsStore.ts (1)
  • useSavedProjectsStore (16-123)
apps/web/src/components/ui/badge.tsx (1)
  • Badge (36-36)
🔇 Additional comments (1)
apps/web/src/components/dashboard/SavedProjectsPanel.tsx (1)

24-68: core saved-projects flows look solid

export/import, clear-all with confirmation, and store interactions (importAndValidate, clearAllSaved, removeProject) are wired correctly and respect the invariants enforced in useSavedProjectsStore. no blocking issues here.

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.

[FEATURE] allow users to save filtered repos (like a bookmark)

1 participant