-
Notifications
You must be signed in to change notification settings - Fork 155
feat: Add Saved Projects feature with localStorage persistence and optional DB syncFeature #238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- 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
|
@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. |
WalkthroughAdds 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 Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
|
1 similar comment
|
|
There was a problem hiding this 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 clearnormalizing the
linkTextfor 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 usinganytype in themetafield.The
Record<string, any>type weakens type safety. Consider usingRecord<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 metadataapps/api/prisma/schema.prisma (1)
42-42: LGTM!The
saved_reposJSONB 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: Useconstwith arrow function per coding guidelines.Based on learnings, use
constwith 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 showsanyusage.The
metafield documentation showsRecord<string, any>which contradicts coding guidelines. Consider documenting withunknownor a more specific type. This also reflects the actual type definition inpackages/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 bysavedAt) 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
addProjectdoesn'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:setAllshould validate and dedupe imported data.When importing from JSON, malformed or duplicate entries could corrupt state. Consider adding validation and deduplication, similar to how
addProjecthandles 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
reposandlocalRepos. 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: Avoidz.any()for meta field.Using
z.any()bypasses type safety. Consider usingz.unknown()which is safer and aligns with coding guidelines to avoidanytypes.- meta: z.record(z.string(), z.any()).optional(), + meta: z.record(z.string(), z.unknown()).optional(),Also applies to: 76-76
13-16: Avoidanytype annotation onctxparameter.Multiple procedure handlers use
{ ctx }: anywhich bypasses TypeScript's type checking. Per coding guidelines, avoidanyand use proper types orunknownwith 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
handleClearAllstarts asetTimeoutthat flipsshowClearConfirmback tofalseafter 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
useEffectalongside the other react hooks.)
78-106: improve dialog semantics and keyboard accessibilitythe 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"andaria-modal="true"to the panel container,- link it to the heading via
aria-labelledby,- optionally move focus to the close button when
isOpenbecomes true, and- consider handling
escapekey 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
useEffectthat focuses the close button ref on open and listens forkeydownevents to close on escape. as per coding guidelines.Also applies to: 145-203
82-83: replace hardcoded/utility colors with semantic design‑token classesseveral 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 conventionguidelines prefer
constarrow functions for components.SavedProjectsPanelis 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
📒 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.tsapps/api/src/services/savedRepos.service.tspackages/shared/types/index.tsapps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/api/src/routers/user.tsapps/web/src/components/dashboard/ProjectsContainer.tsxapps/web/src/store/useSavedProjectsStore.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoidany
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 insrc/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.tsapps/api/src/services/savedRepos.service.tspackages/shared/types/index.tsapps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/api/src/routers/user.tsapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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.tsapps/api/src/services/savedRepos.service.tspackages/shared/types/index.tsapps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/api/src/routers/user.tsapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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.tsapps/api/src/services/savedRepos.service.tsapps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/components/dashboard/SaveToggle.tsxapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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.tsapps/api/src/services/savedRepos.service.tspackages/shared/types/index.tsapps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/api/src/routers/user.tsapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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.tsapps/api/src/services/savedRepos.service.tspackages/shared/types/index.tsapps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/api/src/routers/user.tsapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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.tsapps/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
anytype; useunknownfor truly dynamic data and narrow with type guards
Files:
apps/api/src/services/savedRepos.service.tsapps/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 inapps/web/src/lib/design-tokens.tsandapps/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
Usefont-sansfor standard UI text (Geist Sans) andfont-monofor code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobilep-4(1rem) and desktopp-[60px]
Use appropriate border radius: small elementsrounded-lg, mediumrounded-xl, largerounded-2xl, buttonsrounded-[16px]
Use animation durations: fastduration-100(0.1s), normalduration-300(0.3s), slowduration-600(0.6s)
Files:
apps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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 insrc/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.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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 inapps/web/src/lib/design-tokens.tsandapps/web/tailwind.config.ts
Files:
apps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/components/dashboard/SaveToggle.tsxapps/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 usinganytype
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.tsxapps/web/src/components/dashboard/SaveToggle.tsxapps/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.tsxapps/web/src/data/blogs.tsapps/web/src/components/dashboard/SaveToggle.tsxapps/web/src/components/dashboard/ProjectsContainer.tsxapps/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 goodthe corrected wording for
linkTextimproves clarity and has no behavioral impact.
53-57: copy edits for remaining titles are correctfixes 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 undocumentedlocalReposparameter.The
SavedReposUpdateInputtype only definesactionandreposfields. ThelocalReposparameter shown here isn't part of the type definition inpackages/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
savedReposexport 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
savedAttimestamps and returns results sorted by most recent first.apps/web/src/store/useSavedProjectsStore.ts (2)
36-50: LGTM!The
toggleProjectimplementation 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
localReposis provided withreplaceaction 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.
There was a problem hiding this 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
constwith 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
isValidPopularityoutside 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
constwith 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.successis true butresult.errorcontains 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
warninginstead of reusingerrorfor 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-100ensures the button becomes visible when focused via keyboard navigation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.tsxapps/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 inapps/web/src/lib/design-tokens.tsandapps/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
Usefont-sansfor standard UI text (Geist Sans) andfont-monofor code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobilep-4(1rem) and desktopp-[60px]
Use appropriate border radius: small elementsrounded-lg, mediumrounded-xl, largerounded-2xl, buttonsrounded-[16px]
Use animation durations: fastduration-100(0.1s), normalduration-300(0.3s), slowduration-600(0.6s)
Files:
apps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/web/src/components/dashboard/SaveToggle.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoidany
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 insrc/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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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 insrc/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.tsxapps/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.tsxapps/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 inapps/web/src/lib/design-tokens.tsandapps/web/tailwind.config.ts
Files:
apps/web/src/components/dashboard/SavedProjectsPanel.tsxapps/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 usinganytype
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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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-labelandaria-pressedattributes, 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
There was a problem hiding this 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 conventionproject rules prefer
constarrow 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 auseRefand clear it in auseEffectcleanup (and before scheduling a new timeout) to make this pattern more robust.
91-110: improve dialog semantics for accessibilitythis behaves like a modal side panel; consider adding dialog semantics so assistive tech understands it:
- add
role="dialog"andaria-modal="true"to the panel container.- give the
h2anidand reference it viaaria-labelledbyon 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 utilitiesthe language and popularity badges use
bg-blue-500/15 text-blue-500andbg-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 indesign-tokens.ts/ tailwind config for consistency.as per coding guidelines.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 inapps/web/src/lib/design-tokens.tsandapps/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
Usefont-sansfor standard UI text (Geist Sans) andfont-monofor code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobilep-4(1rem) and desktopp-[60px]
Use appropriate border radius: small elementsrounded-lg, mediumrounded-xl, largerounded-2xl, buttonsrounded-[16px]
Use animation durations: fastduration-100(0.1s), normalduration-300(0.3s), slowduration-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, avoidany
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 insrc/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 insrc/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 inapps/web/src/lib/design-tokens.tsandapps/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 usinganytype
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 solidexport/import, clear-all with confirmation, and store interactions (
importAndValidate,clearAllSaved,removeProject) are wired correctly and respect the invariants enforced inuseSavedProjectsStore. no blocking issues here.
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)
Server-Side (Optional - Feature Flag)
FEATURE_SAVED_REPOS_DBcontrols database sync📦 Changes Made
Frontend (apps/web)
New Components:
src/store/useSavedProjectsStore.ts- Zustand store with localStorage persistenceModified:
src/components/dashboard/ProjectsContainer.tsx- Integrated Save column and panelBackend (apps/api)
New Files:
Modified:
prisma/schema.prisma- Addedsaved_reposJSONB column to User modelShared (
packages/shared)New Types:
types/savedRepos.ts- SavedRepo, SavedReposAction, SavedReposUpdateInput typesModified:
types/index.ts- Export SavedRepo typesDocumentation
CHANGELOG.md- Feature changelog entrydocs/SAVED_REPOS.md- Comprehensive feature documentationdocs/PR_PLAN_SAVED_REPOS.md- PR strategy and commit plandocs/QUICKSTART_SAVED_REPOS.md- Quick start guide for developers🏗️ Technical Implementation
Architecture
Client-Side:
oss_saved_repos_v1Server-Side:
Data Flow
Type Safety
All types are shared between frontend and backend via
@opensox/sharedpackage, ensuring end-to-end type safety.✅ Testing
Automated
pnpm tsc --noEmit)Manual Testing Recommended
Database Sync Testing (if feature flag enabled)
FEATURE_SAVED_REPOS_DB=trueinapps/api/.envcd apps/api && npx prisma migrate dev🚀 Deployment Plan
Phase 1: Client-Only (Week 1)
Phase 2: Database Backend (Week 2)
FEATURE_SAVED_REPOS_DB=falseinitiallyPhase 3: Gradual Rollout (Week 3+)
🔄 Rollback Plan
Frontend Issues