add entitlements and quotas architecture for future stripe integration#131
Conversation
WalkthroughAdds a subscription-based access system: plan definitions, entitlement and quota evaluation, middleware to enforce access/entitlement/quota, admin APIs and UI to grant/revoke subscriptions, and updates to project creation and UI to respect entitlements and quotas. Changes
Sequence Diagram(s)sequenceDiagram
participant User as Client/User
participant UI as Frontend UI
participant API as Projects API
participant MW as Middleware (requireEntitlement, requireQuota)
participant DB as Database
User->>UI: Click "Create Project"
UI->>UI: useSubscription() → entitlements/quotas
alt hasEntitlement && withinQuota
UI->>User: Enable Create button
User->>API: POST /api/projects (payload)
API->>MW: run requireEntitlement('project.create')
MW->>DB: getSubscriptionByUserId(user.id)
MW->>MW: hasEntitlement? → ok
API->>MW: run requireQuota('projects.max')
MW->>DB: getProjectCount(user.id)
MW->>MW: hasQuota? → ok
API->>DB: db.batch([insert project, insert owner])
DB-->>API: created
API-->>User: 201 Created + project
else missing entitlement or quota
UI->>User: Disable + show reason OR
API-->>User: 403 Forbidden + reason (missing_entitlement / quota_exceeded)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| 🔵 In progress View logs |
corates-workers-prod | c1e8e3e | Dec 22 2025, 11:52 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
corates | 8908ef2 | Commit Preview URL | Dec 23 2025, 04:35 AM |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/web/src/primitives/useSubscription.js (1)
40-53: Inconsistency betweenisActiveandhasActiveAccessfor trialing status.
isActive(lines 40-43) returnstruefor'trialing'status, buthasActiveAccess(viacheckActiveAccessfrom@/lib/access.js) only returnstruefor'active'status. This could lead to confusing behavior whereisActive()istruebuthasActiveAccess()isfalsefor trialing users.Consider aligning these or documenting the distinction clearly.
Option 1: Update access.js to handle trialing
In
packages/web/src/lib/access.js:export function hasActiveAccess(subscription) { if (!subscription) return false; - if (subscription.status !== 'active') return false; + if (subscription.status !== 'active' && subscription.status !== 'trialing') return false; // ... }
🧹 Nitpick comments (17)
packages/workers/src/lib/access.js (2)
1-38: Code duplication withpackages/web/src/lib/access.js.This file is nearly identical to its web counterpart. While this may be intentional to keep packages independent, consider extracting these shared utilities to a common package (e.g.,
packages/shared) to reduce maintenance burden and ensure consistency.
11-21: Consider adding input validation for the subscription parameter.Per coding guidelines, Zod should be used for schema and input validation on the backend. While the null checks provide basic safety, validating that
subscription.statusis a valid string andcurrentPeriodEndis a valid number (when present) would make this more robust.Optional: Add Zod validation
import { z } from 'zod'; const subscriptionSchema = z.object({ status: z.string(), currentPeriodEnd: z.number().nullable().optional(), }).nullable(); export function hasActiveAccess(subscription) { const parsed = subscriptionSchema.safeParse(subscription); if (!parsed.success || !parsed.data) return false; const sub = parsed.data; if (sub.status !== 'active') return false; if (!sub.currentPeriodEnd) return true; const now = Math.floor(Date.now() / 1000); return sub.currentPeriodEnd > now; }packages/web/src/primitives/useSubscription.js (1)
89-91: Fragile timestamp detection heuristic.The check
timestamp > 1000000000000to distinguish seconds from milliseconds works for dates between 2001 and 2286, but could misinterpret far-future dates in seconds (e.g., year 33658+) as milliseconds. While unlikely in practice, a more explicit approach would be safer.Alternative: Use a documented threshold or expect consistent format
- // Handle both seconds and milliseconds timestamps - const timestamp = typeof endDate === 'number' ? endDate : parseInt(endDate); - const date = timestamp > 1000000000000 ? new Date(timestamp) : new Date(timestamp * 1000); + // Timestamps from API are in seconds; handle string conversion + const timestampSecs = typeof endDate === 'number' ? endDate : parseInt(endDate); + // Sanity check: if timestamp looks like milliseconds (> year 2001 in ms), use directly + const date = timestampSecs > 1e12 ? new Date(timestampSecs) : new Date(timestampSecs * 1000);Or better yet, standardize the API to always return seconds and remove the detection logic.
packages/workers/src/middleware/requireQuota.js (1)
34-47: Minor:getEffectiveQuotasis called twice.
getEffectiveQuotas(subscription)is called on line 35 (inside the quota exceeded branch) and again on line 47. Consider computing it once and reusing:Proposed optimization
+ const quotas = getEffectiveQuotas(subscription); + if (!hasQuota(subscription, quotaKey, { used, requested })) { - const quotas = getEffectiveQuotas(subscription); const limit = quotas[quotaKey]; const error = createDomainError( AUTH_ERRORS.FORBIDDEN, { reason: 'quota_exceeded', quotaKey, used, limit, requested }, `Quota exceeded: ${quotaKey}. Current usage: ${used}, Limit: ${limit === Infinity ? 'unlimited' : limit}, Requested: ${requested}`, ); return c.json(error, error.statusCode); } // Attach subscription and quotas to context c.set('subscription', subscription); - c.set('quotas', getEffectiveQuotas(subscription)); + c.set('quotas', quotas);packages/workers/src/routes/admin.js (1)
572-581: The UUID generation on every request is wasteful during updates.The
upsertSubscriptionfunction correctly preserves the existing subscription ID during updates by omitting theidfield from the.set()call (line 83-92 in subscriptions.js). However, the code generates a new UUID on line 573 in admin.js for every request, which is discarded during updates. Only generate the UUID when actually creating a new subscription (when an existing subscription is not found).packages/workers/src/config/plans.js (1)
7-50: Consider sharing plan configuration between frontend and backend.The
PLANSconfiguration is duplicated verbatim inpackages/web/src/lib/entitlements.js(lines 7-50). This creates a maintenance burden where changes must be synchronized in two places.Consider moving the shared plan configuration to
@corates/sharedpackage to ensure consistency and reduce duplication.packages/workers/src/middleware/requireEntitlement.js (1)
18-24: Redundant auth check if used afterrequireAuthmiddleware.The JSDoc states this middleware "must be used after requireAuth middleware," but it still performs its own auth check. If
requireAuthalready guarantees a user exists, the check at lines 22-24 is defensive but redundant.This is fine for safety, but consider whether the middleware could instead throw an error if
useris missing (indicating incorrect middleware ordering) rather than returning a 401.packages/workers/src/middleware/requireAccess.js (2)
17-17: Consider simplifying the factory pattern or parameterizing it.
requireAccess()takes no parameters but returns a middleware function. Either:
- Export the middleware directly (no factory needed), or
- Accept a
reasonparameter to make the middleware reusable for different access-gated features.Currently, the hardcoded
reason: 'project_creation'at line 32 limits reusability.Option 1: Export middleware directly
-export function requireAccess() { - return async (c, next) => { +export async function requireAccess(c, next) {Option 2: Parameterize for reusability
-export function requireAccess() { +export function requireAccess(reason = 'access_required') { return async (c, next) => { // ... const error = createDomainError( AUTH_ERRORS.FORBIDDEN, - { reason: 'project_creation' }, + { reason },
38-44: Avoid mutating the error object after creation.Mutating
error.detailsaftercreateDomainError()returns is a code smell. Consider including the details in the initial creation or extendingcreateDomainErrorto accept details.Proposed fix
const error = createDomainError( AUTH_ERRORS.FORBIDDEN, - { reason: 'project_creation' }, + { + reason: 'project_creation', + ...(subscription?.currentPeriodEnd && { + expiredAt: subscription.currentPeriodEnd, + expiredAtFormatted: new Date(subscription.currentPeriodEnd * 1000).toISOString(), + }), + }, subscription && isAccessExpired(subscription) ? 'Your access has expired. Please contact support to renew your access.' : 'Active access is required to create projects. Please contact support to request access.', ); - - // Add expiration info if available - if (subscription?.currentPeriodEnd) { - error.details = { - expiredAt: subscription.currentPeriodEnd, - expiredAtFormatted: new Date(subscription.currentPeriodEnd * 1000).toISOString(), - }; - }packages/web/src/components/admin-ui/UserTable.jsx (3)
185-225: Consider extractingformatAccessStatusto a utility file.This pure function has no component dependencies and could be reused elsewhere. Per coding guidelines, consider moving complex logic into separate utility files.
// e.g., packages/web/src/lib/access.js or a new format-utils.js export function formatAccessStatus(user) { // ... current implementation }
347-364: IIFE pattern in JSX works but consider a helper component.The immediately-invoked function expression
{(() => { ... })()}works but is unusual in JSX. A smallAccessBadgecomponent would improve readability.
36-656: Component exceeds recommended size - consider refactoring.At 656 lines,
UserTable.jsxis becoming a "God component" coordinating multiple concerns (table rendering, action menus, ban dialog, access dialog, confirm dialog). Per coding guidelines, consider:
- Extracting dialogs into separate components (e.g.,
BanUserDialog,GrantAccessDialog,ConfirmActionDialog)- Moving action handling logic into a store or primitive
This is not blocking but will improve maintainability.
packages/web/src/components/project-ui/ProjectDashboard.jsx (2)
30-41: Consider usingcreateMemofor derived values.Per coding guidelines, use
createMemofor derived values to ensure they update reactively. While these work as arrow functions,createMemoprovides memoization benefits:Proposed change
+import { createEffect, createSignal, createMemo, onCleanup, For, Show } from 'solid-js'; // ... -const projectCount = () => projects()?.length || 0; +const projectCount = createMemo(() => projects()?.length || 0); -const canCreateProject = () => { - return ( +const canCreateProject = createMemo(() => hasEntitlement('project.create') && hasQuota('projects.max', { used: projectCount(), requested: 1 }) - ); -}; +);
138-149: Extract repeated fallback message to avoid duplication.The same entitlement/quota message logic appears twice (lines 143-146 and 194-197). Consider extracting to a helper or component:
Proposed extraction
const CreateProjectFallbackMessage = () => ( <div class='text-sm text-gray-600'> {!hasEntitlement('project.create') ? 'Project creation not available on your plan' : `Project limit reached (${projectCount()}/${quotas()['projects.max'] === Infinity ? '∞' : quotas()['projects.max']})`} </div> );Also applies to: 190-199
packages/web/src/components/project-ui/CreateProjectForm.jsx (1)
137-141: Consider simplifying quota exceeded message for end users.The quota message displays technical details like
quotaKey,used,limit, andrequested. While comprehensive, this may be too technical for typical users. Consider a more user-friendly message such as "You've reached your project limit. Please upgrade your plan to create more projects."Proposed simplification
} else if (error.details?.reason === 'quota_exceeded') { - const { quotaKey, used, limit, requested } = error.details; showToast.error( 'Quota Exceeded', - `${quotaKey}: Current usage ${used}, Limit ${limit === Infinity ? 'unlimited' : limit}, Requested ${requested}`, + `You've reached your project limit. Please upgrade your plan to create more projects.`, );packages/web/src/lib/entitlements.js (1)
6-51: Plan configuration duplicated between frontend and backend.The comment at line 6 notes this should match the backend, and line 7 suggests it "could be fetched from an API endpoint." This duplication creates a maintenance risk—if plans diverge, users will see inconsistent behavior.
Consider one of these approaches:
- Fetch plan configuration from a backend endpoint at app initialization
- Generate a shared config package that both frontend and backend import
- Add integration tests to verify frontend/backend plan parity
packages/workers/src/routes/projects.js (1)
128-129: Inconsistent timestamp formats across DB, DO, and response.The code uses three different timestamp formats:
- Database (lines 128-129): Unix seconds via
Math.floor(now.getTime() / 1000)- Durable Object (lines 164-165): Milliseconds via
now.getTime()- Response (lines 185-186): Date objects
This inconsistency increases complexity and makes debugging harder. Consider standardizing on one format (preferably Unix seconds at the storage layer) and converting to appropriate formats only at boundaries (API responses).
Also applies to: 164-165, 185-186
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
.cursor/rules/corates.mdc.cursor/rules/workers.mdc.gitignorepackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/lib/access.jspackages/web/src/lib/entitlements.jspackages/web/src/primitives/useProject/pdfs.jspackages/web/src/primitives/useSubscription.jspackages/web/src/stores/adminStore.jspackages/workers/src/config/plans.jspackages/workers/src/config/stripe.jspackages/workers/src/config/validation.jspackages/workers/src/lib/access.jspackages/workers/src/lib/entitlements.jspackages/workers/src/middleware/requireAccess.jspackages/workers/src/middleware/requireEntitlement.jspackages/workers/src/middleware/requireQuota.jspackages/workers/src/middleware/subscription.jspackages/workers/src/routes/admin.jspackages/workers/src/routes/projects.js
💤 Files with no reviewable changes (1)
- packages/workers/src/config/stripe.js
🧰 Additional context used
📓 Path-based instructions (14)
**/*
📄 CodeRabbit inference engine (.cursorrules)
Do not use emojis in code, comments, documentation, or commit messages
Files:
packages/workers/src/middleware/requireEntitlement.jspackages/workers/src/config/validation.jspackages/workers/src/middleware/requireAccess.jspackages/workers/src/lib/access.jspackages/web/src/lib/access.jspackages/web/src/primitives/useProject/pdfs.jspackages/workers/src/middleware/requireQuota.jspackages/web/src/lib/entitlements.jspackages/workers/src/middleware/subscription.jspackages/workers/src/lib/entitlements.jspackages/workers/src/routes/admin.jspackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/primitives/useSubscription.jspackages/workers/src/config/plans.jspackages/workers/src/routes/projects.jspackages/web/src/stores/adminStore.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation
Files:
packages/workers/src/middleware/requireEntitlement.jspackages/workers/src/config/validation.jspackages/workers/src/middleware/requireAccess.jspackages/workers/src/lib/access.jspackages/web/src/lib/access.jspackages/web/src/primitives/useProject/pdfs.jspackages/workers/src/middleware/requireQuota.jspackages/web/src/lib/entitlements.jspackages/workers/src/middleware/subscription.jspackages/workers/src/lib/entitlements.jspackages/workers/src/routes/admin.jspackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/primitives/useSubscription.jspackages/workers/src/config/plans.jspackages/workers/src/routes/projects.jspackages/web/src/stores/adminStore.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/workers/**/*.{js,ts}: Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/middleware/requireEntitlement.jspackages/workers/src/config/validation.jspackages/workers/src/middleware/requireAccess.jspackages/workers/src/lib/access.jspackages/workers/src/middleware/requireQuota.jspackages/workers/src/middleware/subscription.jspackages/workers/src/lib/entitlements.jspackages/workers/src/routes/admin.jspackages/workers/src/config/plans.jspackages/workers/src/routes/projects.js
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Files:
packages/workers/src/middleware/requireEntitlement.jspackages/workers/src/config/validation.jspackages/workers/src/middleware/requireAccess.jspackages/workers/src/lib/access.jspackages/web/src/lib/access.jspackages/web/src/primitives/useProject/pdfs.jspackages/workers/src/middleware/requireQuota.jspackages/web/src/lib/entitlements.jspackages/workers/src/middleware/subscription.jspackages/workers/src/lib/entitlements.jspackages/workers/src/routes/admin.jspackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/primitives/useSubscription.jspackages/workers/src/config/plans.jspackages/workers/src/routes/projects.jspackages/web/src/stores/adminStore.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/workers/src/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.{js,ts}: Use Zod for schema and input validation on the backend
Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/middleware/requireEntitlement.jspackages/workers/src/config/validation.jspackages/workers/src/middleware/requireAccess.jspackages/workers/src/lib/access.jspackages/workers/src/middleware/requireQuota.jspackages/workers/src/middleware/subscription.jspackages/workers/src/lib/entitlements.jspackages/workers/src/routes/admin.jspackages/workers/src/config/plans.jspackages/workers/src/routes/projects.js
packages/web/src/**/*.{jsx,tsx,js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx,js,ts}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Import stores directly where needed instead of passing values through multiple components
When you need to compute a value based on props or state in SolidJS, usecreateMemoto ensure it updates reactively
For complex state or state objects in SolidJS, use Solid'screateStorefor better performance and reactivity
You may create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering
Files:
packages/web/src/lib/access.jspackages/web/src/primitives/useProject/pdfs.jspackages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/primitives/useSubscription.jspackages/web/src/stores/adminStore.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/**/*.{jsx,tsx,js,ts,css}
📄 CodeRabbit inference engine (.cursorrules)
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Files:
packages/web/src/lib/access.jspackages/web/src/primitives/useProject/pdfs.jspackages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/primitives/useSubscription.jspackages/web/src/stores/adminStore.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/**/*.{js,jsx,ts,tsx}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
UsecreateMemofor derived values to ensure they update reactively
Files:
packages/web/src/lib/access.jspackages/web/src/primitives/useProject/pdfs.jspackages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/primitives/useSubscription.jspackages/web/src/stores/adminStore.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/primitives/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Files:
packages/web/src/primitives/useProject/pdfs.jspackages/web/src/primitives/useSubscription.js
packages/web/src/components/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/components/**/*.{jsx,tsx}: Use responsive design principles for UI components
Use Zag.js for UI components and design system
Zag components exist inpackages/web/src/components/zag/*and should be reused; check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Components should be lean and focused and should not implement business logic; move business logic into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns
Files:
packages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/components/**/*.{jsx,tsx,js}
📄 CodeRabbit inference engine (.cursorrules)
Group related components in subdirectories with an
index.jsbarrel export
Files:
packages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Files:
packages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/components/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/components/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Group related components in subdirectories with an index.js barrel export
Use Zag.js for UI components and design system
Zag component exist inpackages/web/src/components/zag/*and should be reused. Check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Components should be lean and focused. They should not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a God component coordinating multiple large concerns
Files:
packages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/stores/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use Solid's
createStorefor complex state or state objects for better performance and reactivity
Files:
packages/web/src/stores/adminStore.js
🧠 Learnings (15)
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/middleware/requireEntitlement.js.cursor/rules/workers.mdcpackages/workers/src/middleware/requireAccess.js
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/middleware/requireEntitlement.js.cursor/rules/workers.mdcpackages/workers/src/middleware/requireAccess.js
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Zod for schema and input validation on the backend
Applied to files:
.cursor/rules/workers.mdcpackages/workers/src/config/validation.js
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Drizzle ORM for database interactions and migrations
Applied to files:
.cursor/rules/workers.mdcpackages/workers/src/routes/projects.js
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Drizzle ORM for database interactions and migrations
Applied to files:
.cursor/rules/workers.mdcpackages/workers/src/routes/projects.js
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/**/*.{js,jsx,ts,tsx} : Use Zod for schema and input validation
Applied to files:
.cursor/rules/workers.mdcpackages/workers/src/config/validation.js
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/workers/migrations/*.sql : All migrations should go in a single file: `packages/workers/migrations/0001_init.sql`. Do NOT create separate migration files since this project is not yet in production
Applied to files:
.cursor/rules/workers.mdc
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/**/*.{js,jsx,ts,tsx} : Each file should handle one coherent responsibility
Applied to files:
.cursor/rules/workers.mdc
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/workers/migrations/0001_init.sql : All migrations should go in a single file: `packages/workers/migrations/0001_init.sql`. Do NOT create separate migration files since this project is not yet in production
Applied to files:
.cursor/rules/workers.mdc
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Applied to files:
.cursor/rules/workers.mdc
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/web/src/stores/**/*.{js,ts} : Use Solid's `createStore` for complex state or state objects for better performance and reactivity
Applied to files:
packages/web/src/components/project-ui/ProjectDashboard.jsx
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to **/* : Do not use emojis in code, comments, documentation, or commit messages
Applied to files:
.cursor/rules/corates.mdc
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : For UI icons, use the `solid-icons` library or SVGs only. Do not use emojis
Applied to files:
.cursor/rules/corates.mdc
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/web/src/**/*.{jsx,tsx,js,ts} : For UI icons, use the `solid-icons` library or SVGs only. Do not use emojis
Applied to files:
.cursor/rules/corates.mdc
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Follow standard JavaScript/SolidJS/Cloudflare best practices
Applied to files:
.cursor/rules/corates.mdc
🧬 Code graph analysis (9)
packages/workers/src/middleware/requireEntitlement.js (3)
packages/workers/src/db/client.js (1)
createDb(9-11)packages/workers/src/db/subscriptions.js (1)
getSubscriptionByUserId(15-23)packages/workers/src/lib/entitlements.js (2)
hasEntitlement(63-66)getEffectiveEntitlements(27-37)
packages/workers/src/middleware/requireAccess.js (3)
packages/workers/src/db/client.js (1)
createDb(9-11)packages/workers/src/db/subscriptions.js (1)
getSubscriptionByUserId(15-23)packages/workers/src/lib/access.js (2)
hasActiveAccess(11-21)isAccessExpired(28-38)
packages/web/src/lib/access.js (1)
packages/workers/src/lib/access.js (4)
hasActiveAccess(11-21)now(19-19)now(36-36)isAccessExpired(28-38)
packages/workers/src/middleware/requireQuota.js (3)
packages/workers/src/db/client.js (1)
createDb(9-11)packages/workers/src/db/subscriptions.js (1)
getSubscriptionByUserId(15-23)packages/workers/src/lib/entitlements.js (4)
hasQuota(77-86)quotas(78-78)getEffectiveQuotas(45-55)limit(79-79)
packages/workers/src/lib/entitlements.js (2)
packages/web/src/lib/entitlements.js (14)
isSubscriptionActive(64-77)now(68-68)getEffectiveEntitlements(84-91)planId(85-85)planId(99-99)DEFAULT_PLAN(53-53)plan(86-86)plan(100-100)getEffectiveQuotas(98-105)hasEntitlement(113-116)entitlements(114-114)hasQuota(127-132)quotas(128-128)limit(129-129)packages/workers/src/config/plans.js (3)
DEFAULT_PLAN(52-52)DEFAULT_PLAN(52-52)getPlan(59-61)
packages/web/src/components/admin-ui/UserTable.jsx (3)
packages/web/src/lib/error-utils.js (1)
handleError(164-193)packages/web/src/lib/access.js (3)
hasActiveAccess(11-22)now(20-20)now(37-37)packages/workers/src/lib/access.js (3)
hasActiveAccess(11-21)now(19-19)now(36-36)
packages/web/src/primitives/useSubscription.js (2)
packages/web/src/lib/access.js (1)
hasActiveAccess(11-22)packages/web/src/lib/entitlements.js (6)
entitlements(114-114)getEffectiveEntitlements(84-91)quotas(128-128)getEffectiveQuotas(98-105)hasEntitlement(113-116)hasQuota(127-132)
packages/workers/src/config/plans.js (2)
packages/web/src/lib/entitlements.js (4)
PLANS(8-51)DEFAULT_PLAN(53-53)planId(85-85)planId(99-99)packages/workers/src/lib/entitlements.js (2)
planId(28-28)planId(46-46)
packages/workers/src/routes/projects.js (5)
packages/workers/src/db/schema.js (4)
user(5-24)user(5-24)projects(73-82)projects(73-82)packages/workers/src/config/validation.js (3)
result(178-178)validateRequest(234-260)error(205-205)packages/workers/src/middleware/requireEntitlement.js (1)
requireEntitlement(18-44)packages/workers/src/middleware/requireQuota.js (1)
requireQuota(20-51)packages/workers/src/middleware/auth.js (1)
getAuth(63-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: corates
- GitHub Check: Workers Builds: corates-workers-prod
🔇 Additional comments (40)
.cursor/rules/workers.mdc (3)
48-51: The examples reference valid route files that correctly demonstrate the documented patterns. Bothaccount-merge.js(batch operations withdb.batch()) andprojects.js(request validation withvalidateRequest()) exist and follow the documented patterns appropriately. No changes needed.
8-25: The guidance ondb.batch()for atomic operations is correct for this project. In Cloudflare D1, batched statements are SQL transactions that either commit all changes or rollback the entire sequence if any statement fails. Usingdb.transaction()with D1 is not supported and throws an error. The codebase already usesdb.batch()correctly across multiple routes. No changes are needed to the documented guidance.Likely an incorrect or invalid review comment.
27-46: Guidance is accurate and properly implemented.All referenced utilities (
validateRequest,commonFields,validateQueryParams) are exported fromconfig/validation.jsand actively used in route files (projects.js,admin.js,members.js). The validation patterns match the documented guidance, with middleware correctly storing validated data in context (c.set('validatedBody')andc.set('validatedQuery')). Zod 4 error handling is properly implemented with unified error mapping viaissue.kind..cursor/rules/corates.mdc (1)
19-19: LGTM!The addition of "plan files" to the emoji prohibition list is consistent with the broader PR changes introducing plan-based configurations and aligns with the .gitignore update for
.cursor/plans*..gitignore (1)
3-4: LGTM!The new
.cursor/plans*ignore rule appropriately excludes IDE-generated plan artifacts from version control, consistent with the existing/docs/plans*rule at line 57.packages/web/src/lib/access.js (1)
1-39: LGTM!The access control helpers are well-documented and correctly implement time-based access validation. The logic properly handles null subscriptions, inactive statuses, permanent access (no expiration), and timestamp-based expiration checks.
As noted in the workers package review, this duplicates
packages/workers/src/lib/access.js- consider a shared package if maintenance becomes burdensome.packages/web/src/primitives/useSubscription.js (2)
70-81: LGTM - Correctly using functions instead of memos for parameterized checks.
hasEntitlementandhasQuotaare correctly implemented as regular functions rather thancreateMemo. Since they accept parameters, they need to be invoked by consumers within a reactive context, which will properly track thesubscription()dependency.
99-126: LGTM - Clean public API surface.The return object is well-organized with clear groupings (Resource, Tier info, Permission checks, Entitlements/quotas, Subscription details). The transition from tier-based to entitlement-based access is cleanly implemented.
packages/workers/src/config/validation.js (1)
111-134: LGTM! Well-structured subscription grant schema.The schema properly validates subscription grant requests with appropriate constraints. The
nullable().optional()chain oncurrentPeriodEndcorrectly allowsnull,undefined, or a positive integer timestamp, which aligns with the use case of unlimited subscriptions having no end date.packages/workers/src/routes/admin.js (4)
29-31: LGTM! Imports are well-organized.New imports for subscription management are correctly sourced and all are used in the file.
144-193: Good performance optimization with parallel fetching.The parallel fetch of accounts and subscriptions using
Promise.allis efficient. The subscription grouping assumes one subscription per user (last one wins if multiple exist), which aligns with typical subscription models where a user has a single active subscription.
267-279: LGTM!The subscription fetch for user details is consistent with the list endpoint approach.
608-650: LGTM! Idempotent revocation with history preservation.The endpoint correctly preserves subscription history by setting status to
inactiverather than deleting. The idempotent behavior (success even if no subscription exists) is appropriate for DELETE operations.packages/workers/src/middleware/subscription.js (1)
1-17: LGTM! Clean refactoring to a focused utility.The simplified module now provides only the
getSubscriptionhelper, which correctly extracts subscription data from context with appropriate defaults. The nullish coalescing operators handle undefined/null values properly.packages/web/src/stores/adminStore.js (2)
205-237: LGTM! Well-implemented subscription grant function.The function follows the existing patterns in the store, includes proper validation for the required
tierparameter, and correctly constructs the request body with timestamps in seconds.
239-252: LGTM!The
revokeAccessfunction is clean and follows the established patterns in the store.packages/workers/src/middleware/requireQuota.js (1)
22-26: Use domain error with correct error type for consistency.The authentication check returns a plain object
{ error: 'Authentication required' }while the quota exceeded error usescreateDomainError. For consistency with the rest of the codebase and therequireAuthmiddleware pattern, use the domain error:Proposed fix
if (!user) { - return c.json({ error: 'Authentication required' }, 401); + const error = createDomainError(AUTH_ERRORS.REQUIRED); + return c.json(error, error.statusCode); }Likely an incorrect or invalid review comment.
packages/workers/src/config/plans.js (2)
44-48: VerifyInfinityserialization for API responses.Using
Infinityfor unlimited quotas works internally, butJSON.stringify(Infinity)returnsnull. Ensure downstream code that serializes quotas to JSON handles this appropriately (e.g., converting to a special string like"unlimited"or a very large number).
59-61: LGTM!The
getPlan()helper correctly handles unknown plan IDs by falling back toDEFAULT_PLAN, preventing undefined access errors.packages/workers/src/middleware/requireEntitlement.js (1)
26-40: LGTM!The implementation correctly:
- Fetches the subscription from the database
- Validates entitlement using the shared
hasEntitlement()helper- Returns a descriptive domain error with appropriate status code on failure
- Attaches subscription and computed entitlements to context for downstream handlers
packages/web/src/components/admin-ui/UserTable.jsx (2)
154-183: LGTM!The
handleGrantAccessfunction correctly:
- Parses the
datetime-localinput to a Unix timestamp in seconds- Handles the optional expiration case (null for permanent access)
- Resets form state after successful grant
- Uses consistent error handling pattern
509-574: LGTM!The Grant Access dialog is well-implemented with:
- Clear plan selection with all three tiers
- Optional expiration date with helpful description
- Proper loading state handling
- State reset on cancel
packages/web/src/components/project-ui/ProjectDashboard.jsx (1)
138-160: LGTM!The conditional rendering for the "New Project" button correctly:
- Gates creation on both entitlement and quota
- Provides informative fallback messages
- Handles offline state and loading state for disabled state
- Displays quota usage with proper Infinity handling
packages/web/src/components/project-ui/CreateProjectForm.jsx (3)
3-4: LGTM: Imports support enhanced error handling.The imports of
showToast,AUTH_ERRORS, and error utility functions are appropriate for the granular entitlement/quota error handling added to this component.Also applies to: 12-12
111-111: LGTM: Toast suppression enables custom error messages.Suppressing the default toast here is correct since the catch block provides granular toast messages for different error types (missing entitlement, quota exceeded, generic errors).
128-147: Inconsistent async handling in error branches.Line 143 awaits
handleError, but line 146 does not. For consistency and to ensure error handling completes before moving to the finally block, both should useawait.Proposed fix
} else { - await handleError(error, { toastTitle: 'Creation Failed' }); + await handleError(error, { toastTitle: 'Creation Failed' }); } } else { - await handleError(error, { toastTitle: 'Creation Failed' }); + await handleError(error, { toastTitle: 'Creation Failed' }); }Likely an incorrect or invalid review comment.
packages/web/src/lib/entitlements.js (5)
55-57: LGTM: Safe plan resolution with fallback.The
getPlanfunction correctly handles invalid or missing plan IDs by falling back to the default plan.
84-91: LGTM: Correct fallback logic for inactive subscriptions.The function correctly returns free plan entitlements when the subscription is inactive, regardless of the subscription's tier field.
98-105: LGTM: Consistent with getEffectiveEntitlements.
113-116: LGTM: Entitlement and quota checks are correct.Both functions implement proper logic:
hasEntitlementchecks for explicittruevalueshasQuotacorrectly handlesInfinityas unlimited and uses appropriate boundary checkingAlso applies to: 127-132
11-15: Verify: Free plan allows checklist editing without project creation.The free plan grants
'checklist.edit': truebut denies'project.create'and sets'projects.max': 0. If users can't create projects, can they meaningfully edit checklists? If not, consider setting'checklist.edit': falsefor the free tier.Can free tier users edit checklists without being able to create projects in the Corates application architecture?packages/workers/src/lib/entitlements.js (5)
6-6: LGTM: Good use of centralized configuration.Importing plan configuration from a dedicated config module avoids duplication and makes it easier to maintain plan definitions.
13-19: LGTM: Clean subscription activity check.The backend implementation is simpler and more maintainable than the frontend version. It assumes timestamps are in Unix seconds, which should be enforced at the schema level.
27-37: LGTM: Correct entitlement resolution.
45-55: LGTM: Consistent with getEffectiveEntitlements.
63-66: LGTM: Entitlement and quota validation logic is correct.The implementation matches the frontend and correctly handles both boolean entitlements and numeric quotas with infinity support.
Also applies to: 77-86
packages/workers/src/routes/projects.js (4)
9-9: LGTM: Imports support new entitlement and quota enforcement.Also applies to: 11-12
104-108: LGTM: Optimal middleware ordering for project creation.The middleware chain correctly enforces:
- Entitlement check (permission)
- Quota check (capacity)
- Request validation
This order fails fast on authorization issues before processing the request body.
190-197: LGTM: Appropriate error handling for creation failures.The catch block correctly wraps database errors with
DB_TRANSACTION_FAILEDand includes operation context, making debugging easier.
90-97: Code is correct; quota semantics confirmed.The
getProjectCountfunction correctly counts only projects wherecreatedBy === user.id. Theprojects.maxquota limits the number of projects a user can create, not the total projects they can access as a collaborator. This aligns with standard SaaS quota patterns where limits apply to resource consumption by the creator (not collaborators). Consider adding a clarifying comment in the function explaining that the quota specifically limits owned/created projects to prevent future misunderstanding.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
packages/web/src/components/admin-ui/UserTable.jsx (2)
349-366: Extract color classes as a constant.The
colorClassesobject is recreated on every render inside the IIFE. Consider extracting it as a module-level constant for better performance.Suggested refactor
At the module level (near the PROVIDER_INFO constant):
const ACCESS_COLOR_CLASSES = { green: 'bg-green-100 text-green-800', red: 'bg-red-100 text-red-800', gray: 'bg-gray-100 text-gray-800', };This also pairs well with the component extraction suggested for the
formatAccessStatusoptimization.
498-563: Consider adding validation for expiration date.The expiration date input accepts any datetime value without validation. Users could accidentally select a date in the past, which might cause confusion. Consider adding validation to ensure the expiration date is in the future, or at minimum, show a warning.
Example validation logic
const handleChangeAccess = async () => { const user = accessDialog(); if (!user) return; const expiration = accessExpiration(); if (expiration) { const date = new Date(expiration); if (date.getTime() < Date.now()) { setError('Expiration date must be in the future'); return; } } setLoading(true); // ... rest of the function };
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/lib/entitlements.jspackages/workers/src/routes/projects.js
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/web/src/lib/entitlements.js
🧰 Additional context used
📓 Path-based instructions (12)
**/*
📄 CodeRabbit inference engine (.cursorrules)
Do not use emojis in code, comments, documentation, or commit messages
Files:
packages/web/src/components/admin-ui/UserTable.jsxpackages/workers/src/routes/projects.js
packages/web/src/**/*.{jsx,tsx,js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx,js,ts}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Import stores directly where needed instead of passing values through multiple components
When you need to compute a value based on props or state in SolidJS, usecreateMemoto ensure it updates reactively
For complex state or state objects in SolidJS, use Solid'screateStorefor better performance and reactivity
You may create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation
Files:
packages/web/src/components/admin-ui/UserTable.jsxpackages/workers/src/routes/projects.js
packages/web/src/components/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/components/**/*.{jsx,tsx}: Use responsive design principles for UI components
Use Zag.js for UI components and design system
Zag components exist inpackages/web/src/components/zag/*and should be reused; check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Components should be lean and focused and should not implement business logic; move business logic into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/**/*.{jsx,tsx,js,ts,css}
📄 CodeRabbit inference engine (.cursorrules)
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/components/**/*.{jsx,tsx,js}
📄 CodeRabbit inference engine (.cursorrules)
Group related components in subdirectories with an
index.jsbarrel export
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/**/*.{js,jsx,ts,tsx}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
UsecreateMemofor derived values to ensure they update reactively
Files:
packages/web/src/components/admin-ui/UserTable.jsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Files:
packages/web/src/components/admin-ui/UserTable.jsxpackages/workers/src/routes/projects.js
packages/web/src/components/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/components/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Group related components in subdirectories with an index.js barrel export
Use Zag.js for UI components and design system
Zag component exist inpackages/web/src/components/zag/*and should be reused. Check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Components should be lean and focused. They should not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a God component coordinating multiple large concerns
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/workers/**/*.{js,ts}: Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/projects.js
packages/workers/src/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.{js,ts}: Use Zod for schema and input validation on the backend
Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/projects.js
🧠 Learnings (2)
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Drizzle ORM for database interactions and migrations
Applied to files:
packages/workers/src/routes/projects.js
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Drizzle ORM for database interactions and migrations
Applied to files:
packages/workers/src/routes/projects.js
🧬 Code graph analysis (1)
packages/web/src/components/admin-ui/UserTable.jsx (1)
packages/web/src/lib/access.js (3)
hasActiveAccess(11-22)now(20-20)now(37-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: corates
- GitHub Check: Workers Builds: corates-workers-prod
🔇 Additional comments (10)
packages/workers/src/routes/projects.js (4)
9-9: LGTM - Imports support new entitlement/quota enforcement.The new imports are correctly added to support the quota counting logic and middleware enforcement.
Also applies to: 11-12
87-97: LGTM - Helper function correctly uses Drizzle ORM count.The
getProjectCounthelper properly uses Drizzle'scount()function and includes a safe fallback for null results.
119-137: Excellent - Drizzle batch properly addresses previous review feedback.The batch insert using Drizzle ORM (
db.batch([db.insert(...), db.insert(...)])) is the correct approach and directly addresses the past review comment about using Drizzle instead of raw D1 statements. The atomicity guarantee of D1 batch operations ensures both the project and member records are inserted together.
104-107: No action required.The
requireQuotamiddleware correctly invokes thegetUsagecallback with(c, user)parameters (line 22 of requireQuota.js). ThegetProjectCountfunction signature matches exactly:async function getProjectCount(c, user), which returns a Promise that resolves to a number as expected.packages/web/src/components/admin-ui/UserTable.jsx (6)
23-26: LGTM!The imports are appropriate and follow the coding guidelines for importing stores and utilities directly where needed.
41-43: LGTM!State initialization using
createSignalis appropriate for this UI dialog state.
257-259: LGTM!The Access column header is correctly added with consistent styling.
273-273: LGTM!The colspan is correctly updated to reflect the new Access column.
432-439: LGTM!The Change Access menu item is well-integrated with appropriate icon choice and clear labeling.
76-89: Add fallback handling for Safari datetime-local input quirks or test with different input type.Date and time input types on Safari is partially supported on 14.1-26.2, and not supported on 3.2-14 Safari versions. While modern Safari (14.1+) does support datetime-local, Safari exhibits behavioral issues including displaying ghost date placeholders when the value is empty and making time inputs non-editable when empty. Additionally, alignment issues occur in Safari 16.5+ and 17.x on both macOS and iOS.
For users on older Safari versions or to avoid these behavioral quirks, consider using a JavaScript feature detection fallback or a datetime picker library that provides consistent behavior across browsers.
| const formatAccessStatus = user => { | ||
| const subscription = user.subscription; | ||
| if (!subscription) return { label: 'Free Plan', color: 'gray', plan: 'free' }; | ||
|
|
||
| const planNames = { free: 'Free', pro: 'Pro', unlimited: 'Unlimited' }; | ||
| const planName = planNames[subscription.tier] || subscription.tier || 'Free'; | ||
|
|
||
| if (hasActiveAccess(subscription)) { | ||
| if (subscription.currentPeriodEnd) { | ||
| const date = new Date(subscription.currentPeriodEnd * 1000); | ||
| return { | ||
| label: `${planName} (expires ${date.toLocaleDateString()})`, | ||
| color: 'green', | ||
| plan: subscription.tier, | ||
| }; | ||
| } | ||
| return { label: `${planName} (no expiration)`, color: 'green', plan: subscription.tier }; | ||
| } | ||
|
|
||
| if (subscription.status === 'inactive') { | ||
| return { label: `${planName} (revoked)`, color: 'red', plan: subscription.tier }; | ||
| } | ||
|
|
||
| if (subscription.currentPeriodEnd) { | ||
| const date = new Date(subscription.currentPeriodEnd * 1000); | ||
| const now = Date.now(); | ||
| if (subscription.currentPeriodEnd * 1000 < now) { | ||
| return { | ||
| label: `${planName} (expired ${date.toLocaleDateString()})`, | ||
| color: 'red', | ||
| plan: subscription.tier, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| label: `${planName} (${subscription.status || 'inactive'})`, | ||
| color: 'gray', | ||
| plan: subscription.tier, | ||
| }; | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Optimize with createMemo for derived values.
This function computes display status for each user but is called inline at line 351, causing it to recalculate on every render. Per coding guidelines: "When you need to compute a value based on props or state in SolidJS, use createMemo to ensure it updates reactively."
For better performance with many users, consider memoizing the access status computation.
Recommended refactor
Instead of calling formatAccessStatus(user) inline, move the computation to be memoized per user, or extract the rendering logic into a separate component that uses createMemo internally:
// Option 1: Extract to a component
function AccessStatusBadge(props) {
const access = createMemo(() => formatAccessStatus(props.user));
const colorClasses = {
green: 'bg-green-100 text-green-800',
red: 'bg-red-100 text-red-800',
gray: 'bg-gray-100 text-gray-800',
};
return (
<span
class={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClasses[access().color]}`}
title={access().label}
>
{access().label}
</span>
);
}
// Then at line 349-366:
<td class='px-6 py-4'>
<AccessStatusBadge user={user} />
</td>This ensures the status is only recomputed when the user object changes, not on every render.
As per coding guidelines: "Use createMemo for derived values to ensure they update reactively."
🤖 Prompt for AI Agents
In packages/web/src/components/admin-ui/UserTable.jsx around lines 187 to 227,
the formatAccessStatus computation is used inline (around line 351) and is
recalculated on every render; wrap the derived value in a SolidJS createMemo (or
extract into a small AccessStatusBadge component that calls createMemo(() =>
formatAccessStatus(user))) so the status is only recomputed when that user's
props change, import createMemo at the top, replace the inline call with
access() in the render, and move any helper rendering classes into that
component/memoized logic to keep the table render lightweight.
| await syncProjectToDO( | ||
| c.env, | ||
| projectId, | ||
| { | ||
| name: name.trim(), | ||
| description: description?.trim() || null, | ||
| createdAt: now.getTime(), | ||
| updatedAt: now.getTime(), | ||
| }, | ||
| ], | ||
| ); | ||
|
|
||
| const newProject = { | ||
| id: projectId, | ||
| name, | ||
| description, | ||
| role: 'owner', | ||
| createdAt: now, | ||
| updatedAt: now, | ||
| }; | ||
| [ | ||
| { | ||
| userId: authUser.id, | ||
| role: 'owner', | ||
| joinedAt: now.getTime(), | ||
| name: creator?.name || null, | ||
| email: creator?.email || null, | ||
| displayName: creator?.displayName || null, | ||
| image: creator?.image || null, | ||
| }, | ||
| ], | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's find the file and check its line count
ls -la packages/workers/src/routes/projects.js 2>/dev/null || echo "File not found at expected path"
# Try to find it if path is different
find . -name "projects.js" -type f 2>/dev/null | head -5Repository: InfinityBowman/corates
Length of output: 197
🏁 Script executed:
#!/bin/bash
# Read the entire file with line numbers to see context around the mentioned lines
cat -n packages/workers/src/routes/projects.jsRepository: InfinityBowman/corates
Length of output: 14080
🏁 Script executed:
#!/bin/bash
# Look for documentation about DO sync strategy or eventual consistency
find . -type f \( -name "*.md" -o -name "*.txt" -o -name "README*" \) | xargs grep -l "Durable Object\|DO sync\|eventual" 2>/dev/null | head -10
# Also search for any queue or background job mechanism
grep -r "queue\|retry\|background" packages/workers/src --include="*.js" --include="*.ts" -l 2>/dev/null | head -5
# Check for any comments about DO eventual consistency in the codebase
grep -r "eventual\|eventually\|consistent" packages/workers/src --include="*.js" --include="*.ts" -B2 -A2 2>/dev/nullRepository: InfinityBowman/corates
Length of output: 958
🏁 Script executed:
#!/bin/bash
# Read the sync flow documentation
cat docs/architecture/diagrams/03-sync-flow.md
echo "---"
# Also check the system architecture
cat docs/architecture/diagrams/02-system-architecture.mdRepository: InfinityBowman/corates
Length of output: 4669
🏁 Script executed:
#!/bin/bash
# Check if there's any async queue or retry mechanism elsewhere
grep -r "Queue\|retry\|exponential\|backoff" packages/workers/src --include="*.js" --include="*.ts" -B2 -A2 2>/dev/null | head -40
# Look for how EmailQueue DO is used (it's mentioned in docs)
grep -r "EMAIL_QUEUE\|EmailQueue" packages/workers/src --include="*.js" --include="*.ts" -B2 -A2 2>/dev/null
# Check if syncProjectToDO is used elsewhere with different patterns
grep -r "syncProjectToDO\|sync.*DO" packages/workers/src --include="*.js" --include="*.ts" -B2 -A2 2>/dev/nullRepository: InfinityBowman/corates
Length of output: 19014
Add retry mechanism to Durable Object sync operations
The syncProjectToDO function (lines 25-43) and similar syncMemberToDO operations silently catch and log errors without retrying. This creates potential inconsistency: project metadata is committed to the database, but if the Durable Object sync fails, the ProjectDoc won't have the data needed for real-time collaboration.
The codebase already implements exponential backoff retry logic in EmailQueue.js with dead letter queues and scheduled alarms. Apply the same pattern to DO sync functions to handle transient failures, or document why eventual consistency is acceptable for this operation.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/web/src/components/admin-ui/UserTable.jsx (1)
349-366: Previous feedback still applies: usecreateMemofor derived access status.The
formatAccessStatus(user)computation inside the IIFE is recalculated on every render. As noted in a previous review, extracting this to a memoized component or usingcreateMemowould improve performance, especially with many users.Based on learnings: "Use
createMemofor derived values to ensure they update reactively."
🧹 Nitpick comments (5)
packages/workers/src/routes/admin.js (2)
572-581: Unnecessary UUID generation on update.A new
subscriptionIdis generated on every call, but per theupsertSubscriptionlogic inpackages/workers/src/db/subscriptions.js, when a subscription already exists, the update path ignores the providedidfield. While harmless, it's slightly wasteful.Consider moving UUID generation inside
upsertSubscriptionfor insert-only scenarios, or conditionally generating it here.
624-636: Consider returning 404 when no subscription exists to revoke.Currently, if a user has no subscription, the endpoint returns success silently. This could mask client bugs where a revoke is attempted on the wrong user. Consider returning a distinct response (e.g.,
{ success: true, message: 'No subscription to revoke' }) or a 404 to indicate no subscription was found.packages/shared/src/plans/types.ts (1)
14-29: Consider deriving interface keys from union types for DRY.The
EntitlementKeyunion andEntitlementsinterface duplicate the same keys. If a key is added to one but not the other, they'll drift. You could use a mapped type to derive the interface from the union:export type Entitlements = Record<EntitlementKey, boolean>;Same applies to
QuotaKeyandQuotas. This ensures type safety if new keys are added.🔎 Proposed refactor
-export interface Entitlements { - 'project.create': boolean; - 'checklist.edit': boolean; - 'export.pdf': boolean; - 'ai.run': boolean; -} +export type Entitlements = Record<EntitlementKey, boolean>; -export interface Quotas { - 'projects.max': number; - 'storage.project.maxMB': number; - 'ai.tokens.monthly': number; -} +export type Quotas = Record<QuotaKey, number>;packages/web/src/lib/entitlements.js (2)
19-23: Specify radix forparseInt.While
parseIntdefaults to base 10 for numeric strings, explicitly specifying the radix improves clarity and avoids edge cases with leading zeros in older environments.🔎 Proposed fix
const endTime = typeof subscription.currentPeriodEnd === 'number' ? subscription.currentPeriodEnd - : parseInt(subscription.currentPeriodEnd); + : parseInt(subscription.currentPeriodEnd, 10);
31-38: Minor optimization: avoid redundantgetPlancall.When the subscription is inactive,
getPlan(planId)is already called on line 33, but thengetPlan(DEFAULT_PLAN)is called again on line 35. Since you already haveplan, you could check ifplanId === DEFAULT_PLANand reuse it, though the current approach is also acceptable for clarity.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (11)
packages/shared/package.jsonpackages/shared/src/index.tspackages/shared/src/plans/index.tspackages/shared/src/plans/plans.tspackages/shared/src/plans/types.tspackages/web/package.jsonpackages/web/src/components/admin-ui/UserTable.jsxpackages/web/src/lib/entitlements.jspackages/web/src/primitives/useProject/pdfs.jspackages/workers/src/lib/entitlements.jspackages/workers/src/routes/admin.js
💤 Files with no reviewable changes (1)
- packages/web/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/workers/src/lib/entitlements.js
- packages/web/src/primitives/useProject/pdfs.js
🧰 Additional context used
📓 Path-based instructions (12)
**/*
📄 CodeRabbit inference engine (.cursorrules)
Do not use emojis in code, comments, documentation, or commit messages
Files:
packages/shared/src/index.tspackages/shared/package.jsonpackages/shared/src/plans/plans.tspackages/shared/src/plans/index.tspackages/shared/src/plans/types.tspackages/workers/src/routes/admin.jspackages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsx
packages/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation
Files:
packages/shared/src/index.tspackages/shared/src/plans/plans.tspackages/shared/src/plans/index.tspackages/shared/src/plans/types.tspackages/workers/src/routes/admin.jspackages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Files:
packages/shared/src/index.tspackages/shared/src/plans/plans.tspackages/shared/src/plans/index.tspackages/shared/src/plans/types.tspackages/workers/src/routes/admin.jspackages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsx
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/workers/**/*.{js,ts}: Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/admin.js
packages/workers/src/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.{js,ts}: Use Zod for schema and input validation on the backend
Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/admin.js
packages/web/src/**/*.{jsx,tsx,js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx,js,ts}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Import stores directly where needed instead of passing values through multiple components
When you need to compute a value based on props or state in SolidJS, usecreateMemoto ensure it updates reactively
For complex state or state objects in SolidJS, use Solid'screateStorefor better performance and reactivity
You may create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering
Files:
packages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/**/*.{jsx,tsx,js,ts,css}
📄 CodeRabbit inference engine (.cursorrules)
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Files:
packages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/**/*.{js,jsx,ts,tsx}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
UsecreateMemofor derived values to ensure they update reactively
Files:
packages/web/src/lib/entitlements.jspackages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/components/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/components/**/*.{jsx,tsx}: Use responsive design principles for UI components
Use Zag.js for UI components and design system
Zag components exist inpackages/web/src/components/zag/*and should be reused; check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Components should be lean and focused and should not implement business logic; move business logic into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/components/**/*.{jsx,tsx,js}
📄 CodeRabbit inference engine (.cursorrules)
Group related components in subdirectories with an
index.jsbarrel export
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Files:
packages/web/src/components/admin-ui/UserTable.jsx
packages/web/src/components/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/components/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Group related components in subdirectories with an index.js barrel export
Use Zag.js for UI components and design system
Zag component exist inpackages/web/src/components/zag/*and should be reused. Check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Components should be lean and focused. They should not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a God component coordinating multiple large concerns
Files:
packages/web/src/components/admin-ui/UserTable.jsx
🧠 Learnings (2)
📚 Learning: 2025-12-19T12:38:18.505Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-19T12:38:18.505Z
Learning: Applies to packages/web/src/**/*.{jsx,tsx,js,ts} : When you need to compute a value based on props or state in SolidJS, use `createMemo` to ensure it updates reactively
Applied to files:
packages/web/src/components/admin-ui/UserTable.jsx
📚 Learning: 2025-12-19T14:49:49.730Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-19T14:49:49.730Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use `createMemo` for derived values to ensure they update reactively
Applied to files:
packages/web/src/components/admin-ui/UserTable.jsx
🧬 Code graph analysis (2)
packages/workers/src/routes/admin.js (4)
packages/workers/src/db/schema.js (4)
subscriptions(109-124)subscriptions(109-124)user(5-24)user(5-24)packages/workers/src/config/validation.js (3)
validateRequest(234-260)subscriptionSchemas(114-134)subscriptionSchemas(114-134)packages/workers/src/db/subscriptions.js (3)
upsertSubscription(63-118)existing(77-77)getSubscriptionByUserId(15-23)packages/shared/src/plans/index.ts (1)
getPlan(18-18)
packages/web/src/components/admin-ui/UserTable.jsx (2)
packages/web/src/lib/access.js (3)
hasActiveAccess(11-22)now(20-20)now(37-37)packages/workers/src/lib/access.js (3)
hasActiveAccess(11-21)now(19-19)now(36-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Workers Builds: corates
🔇 Additional comments (13)
packages/shared/package.json (1)
17-21: LGTM!The new
./plansexport entry follows the established pattern and correctly exposes both type definitions and runtime code from the dist folder.packages/shared/src/index.ts (1)
1-7: LGTM!Clean barrel export pattern that correctly re-exports both the errors and plans modules.
packages/shared/src/plans/index.ts (1)
1-18: LGTM!Well-structured public API with clean separation between type exports and runtime exports. Using
export typefor type-only exports is a good TypeScript practice.packages/shared/src/plans/plans.ts (1)
67-72: LGTM!The
getPlanfunction correctly handles invalid plan IDs by falling back to the default plan, providing a safe default behavior.packages/workers/src/routes/admin.js (3)
144-193: LGTM!Good use of
Promise.allto fetch accounts and subscriptions in parallel, improving response time for the users list endpoint.
267-279: LGTM!Correctly fetches and includes the user's subscription in the detailed user response.
554-570: LGTM!Defensive timestamp normalization that handles both milliseconds and seconds is a good practice for API robustness.
packages/shared/src/plans/types.ts (1)
40-52: LGTM!The
PlanandPlanstypes are well-structured and provide a clear contract for plan configuration.packages/web/src/lib/entitlements.js (1)
74-79: LGTM!The
hasQuotafunction correctly handlesInfinityfor unlimited quotas and provides a clean API for checking quota availability.packages/web/src/components/admin-ui/UserTable.jsx (4)
41-43: LGTM!State signals for the access dialog are properly initialized with sensible defaults.
76-89: LGTM!The 'change-access' action handler correctly pre-fills the dialog with the user's current plan and expiration date.
156-185: State reset is now consistent.The
selectedPlanreset to'free'(line 173) now matches the initial state on line 42, addressing the previous review concern.
498-563: LGTM!The Change Access dialog provides a clean UI for plan selection and optional expiration, with proper loading states and error handling.
…yBowman/corates into 130-gated-feature-access
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/web/src/components/project-ui/CreateProjectForm.jsx (1)
129-148: Consider more user-friendly error messages.The error handling logic is sound, but the quota exceeded message exposes technical details like raw quota keys (
projects.max) and numeric values that may confuse non-technical users.Consider:
- Mapping quota keys to user-friendly labels (e.g.,
projects.max→ "Projects")- Simplifying the message format:
"You've reached your project limit (${used}/${limit}). Please upgrade to create more projects."Example improvement
+const QUOTA_LABELS = { + 'projects.max': 'Projects', + // Add other quota keys as needed +}; + } catch (error) { // Check if this is an entitlement or quota error if (isErrorCode(error, AUTH_ERRORS.FORBIDDEN.code)) { if (error.details?.reason === 'missing_entitlement') { showToast.error( 'Feature Not Available', `This feature requires the '${error.details.entitlement}' entitlement. Please upgrade your plan.`, ); } else if (error.details?.reason === 'quota_exceeded') { const { quotaKey, used, limit, requested } = error.details; + const quotaLabel = QUOTA_LABELS[quotaKey] || quotaKey; + const limitDisplay = isUnlimitedQuota(limit) ? '∞' : limit; showToast.error( 'Quota Exceeded', - `${quotaKey}: Current usage ${used}, Limit ${isUnlimitedQuota(limit) ? 'unlimited' : limit}, Requested ${requested}`, + `You've reached your ${quotaLabel} limit (${used}/${limitDisplay}). Please upgrade your plan to create more.`, );packages/web/src/components/project-ui/ProjectDashboard.jsx (1)
144-147: Consider extracting quota limit message to improve clarity.The nested ternary for displaying quota limits works but could be more readable. Consider extracting to a helper function or memo.
Optional improvement
+const quotaLimitDisplay = createMemo(() => { + const limit = quotas()['projects.max']; + return isUnlimitedQuota(limit) ? '∞' : limit; +}); + <Show when={canCreateProject()} fallback={ <div class='flex flex-col items-end gap-1'> <div class='text-sm text-gray-600'> {!hasEntitlement('project.create') ? 'Project creation not available on your plan' - : `Project limit reached (${projectCount()}/${isUnlimitedQuota(quotas()['projects.max']) ? '∞' : quotas()['projects.max']})` + : `Project limit reached (${projectCount()}/${quotaLimitDisplay()})` } </div> </div> }Also applies to: 195-198
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
packages/shared/src/plans/index.tspackages/shared/src/plans/plans.tspackages/shared/src/plans/types.tspackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/lib/entitlements.jspackages/web/src/primitives/__tests__/useProject.test.jspackages/workers/src/lib/entitlements.jspackages/workers/src/middleware/requireQuota.jspackages/workers/src/routes/__tests__/projects.test.js
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/shared/src/plans/plans.ts
- packages/workers/src/lib/entitlements.js
- packages/workers/src/middleware/requireQuota.js
- packages/shared/src/plans/types.ts
🧰 Additional context used
📓 Path-based instructions (13)
**/*
📄 CodeRabbit inference engine (.cursorrules)
Do not use emojis in code, comments, documentation, or commit messages
Files:
packages/web/src/primitives/__tests__/useProject.test.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/shared/src/plans/index.tspackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/lib/entitlements.jspackages/workers/src/routes/__tests__/projects.test.js
packages/web/src/**/*.{jsx,tsx,js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx,js,ts}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Import stores directly where needed instead of passing values through multiple components
When you need to compute a value based on props or state in SolidJS, usecreateMemoto ensure it updates reactively
For complex state or state objects in SolidJS, use Solid'screateStorefor better performance and reactivity
You may create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering
Files:
packages/web/src/primitives/__tests__/useProject.test.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/lib/entitlements.js
packages/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation
Files:
packages/web/src/primitives/__tests__/useProject.test.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/shared/src/plans/index.tspackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/lib/entitlements.jspackages/workers/src/routes/__tests__/projects.test.js
packages/web/src/**/*.{jsx,tsx,js,ts,css}
📄 CodeRabbit inference engine (.cursorrules)
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Files:
packages/web/src/primitives/__tests__/useProject.test.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/lib/entitlements.js
packages/web/src/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/**/*.{js,jsx,ts,tsx}: For UI icons, use thesolid-iconslibrary or SVGs only. Do not use emojis
Ensure browser compatibility for all frontend code (Safari is usually problematic)
Keep files small, focused, and modular. If a file exceeds a high number of lines, consider refactoring by extracting sub-modules into a folder with index.jsx and helper components, moving complex logic into separate utility files or primitives, or splitting large forms into section components
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
UsecreateMemofor derived values to ensure they update reactively
Files:
packages/web/src/primitives/__tests__/useProject.test.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/lib/entitlements.js
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Files:
packages/web/src/primitives/__tests__/useProject.test.jspackages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/shared/src/plans/index.tspackages/web/src/components/project-ui/CreateProjectForm.jsxpackages/web/src/lib/entitlements.jspackages/workers/src/routes/__tests__/projects.test.js
packages/web/src/primitives/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Files:
packages/web/src/primitives/__tests__/useProject.test.js
packages/web/src/components/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/components/**/*.{jsx,tsx}: Use responsive design principles for UI components
Use Zag.js for UI components and design system
Zag components exist inpackages/web/src/components/zag/*and should be reused; check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state
If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Components should be lean and focused and should not implement business logic; move business logic into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns
Files:
packages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/components/**/*.{jsx,tsx,js}
📄 CodeRabbit inference engine (.cursorrules)
Group related components in subdirectories with an
index.jsbarrel export
Files:
packages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursorrules)
packages/web/src/**/*.{jsx,tsx}: Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Files:
packages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/components/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/src/components/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Group related components in subdirectories with an index.js barrel export
Use Zag.js for UI components and design system
Zag component exist inpackages/web/src/components/zag/*and should be reused. Check the README.md in that folder for a list of existing components before adding new components and when debugging
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context
Do not destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object or wrap them in a function to ensure they are always up-to-date
Components should be lean and focused. They should not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a God component coordinating multiple large concerns
Files:
packages/web/src/components/project-ui/ProjectDashboard.jsxpackages/web/src/components/project-ui/overview-tab/OverviewTab.jsxpackages/web/src/components/project-ui/CreateProjectForm.jsx
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursorrules)
packages/workers/**/*.{js,ts}: Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/__tests__/projects.test.js
packages/workers/src/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.{js,ts}: Use Zod for schema and input validation on the backend
Use Drizzle ORM for database interactions and migrations
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/__tests__/projects.test.js
🧬 Code graph analysis (4)
packages/web/src/components/project-ui/ProjectDashboard.jsx (2)
packages/web/src/primitives/useSubscription.js (4)
useSubscription(20-127)hasEntitlement(70-70)hasQuota(80-81)quotas(63-63)packages/shared/src/plans/plans.ts (1)
isUnlimitedQuota(79-81)
packages/web/src/components/project-ui/CreateProjectForm.jsx (3)
packages/web/src/lib/error-utils.js (2)
isErrorCode(210-212)handleError(164-193)packages/ui/src/zag/Toast.jsx (2)
showToast(132-150)showToast(132-150)packages/shared/src/plans/plans.ts (1)
isUnlimitedQuota(79-81)
packages/web/src/lib/entitlements.js (2)
packages/workers/src/lib/entitlements.js (13)
isSubscriptionActive(13-19)now(17-17)getEffectiveEntitlements(27-37)planId(28-28)planId(46-46)plan(29-29)plan(47-47)getEffectiveQuotas(45-55)hasEntitlement(63-66)entitlements(64-64)hasQuota(77-86)quotas(78-78)limit(79-79)packages/shared/src/plans/plans.ts (3)
DEFAULT_PLAN(60-60)getPlan(67-72)isUnlimitedQuota(79-81)
packages/workers/src/routes/__tests__/projects.test.js (1)
packages/workers/src/__tests__/helpers.js (1)
seedSubscription(161-180)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: corates-workers-prod
- GitHub Check: Workers Builds: corates
🔇 Additional comments (6)
packages/shared/src/plans/index.ts (1)
1-18: LGTM! Clean barrel export pattern.The file is well-structured with clear separation between type exports and runtime configuration exports. The JSDoc comment provides helpful context, and the use of
export typefor type-only exports follows TypeScript best practices.packages/workers/src/routes/__tests__/projects.test.js (1)
182-189: LGTM: Consistent subscription seeding for project creation tests.The subscription setup is consistently applied across all POST /api/projects tests, ensuring users have an active pro-tier subscription before attempting project creation. This aligns with the new entitlement/quota enforcement introduced in this PR.
Also applies to: 235-242, 270-277, 304-311
packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx (1)
162-168: LGTM: Avatar path resolution logic is correct.The implementation properly handles both absolute URLs and relative paths, prepending
API_BASEwhen needed and falling back to the avatar endpoint for users without profile images.packages/web/src/components/project-ui/ProjectDashboard.jsx (2)
37-42: LGTM: Proper gating logic for project creation.The
canCreateProject()computed correctly checks both entitlement and quota availability, ensuring users can only create projects when allowed by their subscription plan.
139-161: No changes needed. The quota object access is safe across all code paths becausegetEffectiveQuotas()always returns a plan's quotas object from the static plan definitions inplans.ts, where all plans ('free', 'pro', 'unlimited') guarantee the 'projects.max' key exists. The fallback mechanism ingetPlan()ensures an invalid plan ID always resolves to a valid default plan configuration.packages/web/src/lib/entitlements.js (1)
1-79: LGTM: Frontend entitlement utilities correctly mirror backend implementation.The entitlement and quota computation functions are well-structured and properly documented. The implementation correctly:
- Falls back to DEFAULT_PLAN when subscription is inactive
- Handles unlimited quotas with
isUnlimitedQuota- Maintains consistency with the backend implementation in
packages/workers/src/lib/entitlements.jsThe defensive type handling in
isSubscriptionActive(lines 19-23) is appropriate for frontend code where data types from APIs may vary, despite being slightly more defensive than the backend version.
|
|
||
| vi.mock('@config/api.js', () => ({ | ||
| getWsBaseUrl: vi.fn(() => 'ws://localhost:8787'), | ||
| LANDING_URL: vi.fn(() => 'https://corates.org'), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and inspect the config file to see how LANDING_URL is exported
fd -t f "api.js" packages/web/src/config 2>/dev/null | head -5Repository: InfinityBowman/corates
Length of output: 96
🏁 Script executed:
#!/bin/bash
# Also search for LANDING_URL exports across the codebase
rg -A 2 "LANDING_URL" packages/web/src/config --type ts --type jsRepository: InfinityBowman/corates
Length of output: 278
Fix mock to match actual export type: LANDING_URL is a constant, not a function.
The actual implementation exports LANDING_URL as a constant (export const LANDING_URL = import.meta.env.VITE_PUBLIC_APP_URL;), but the test mocks it as a function. Update the mock on line 60 to:
LANDING_URL: 'https://corates.org',🤖 Prompt for AI Agents
In packages/web/src/primitives/__tests__/useProject.test.js around line 60, the
test mock incorrectly defines LANDING_URL as a function; the real module exports
LANDING_URL as a constant string. Change the mock entry from a function to a
plain string value (e.g., LANDING_URL: 'https://corates.org') so the mocked
shape matches the actual export.
Summary by CodeRabbit
New Features
Improvements
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.