Skip to content

add entitlements and quotas architecture for future stripe integration#131

Merged
InfinityBowman merged 8 commits into
mainfrom
130-gated-feature-access
Dec 23, 2025
Merged

add entitlements and quotas architecture for future stripe integration#131
InfinityBowman merged 8 commits into
mainfrom
130-gated-feature-access

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Dec 22, 2025

Summary by CodeRabbit

  • New Features

    • Admins can grant, update, and revoke user subscriptions (tier + optional expiry) from the UI.
    • Server and middleware now enforce entitlements and quotas for guarded routes (e.g., project creation).
  • Improvements

    • Subscription model replaced tiers with entitlements/quotas; clearer, specific error messages for entitlement/quota failures.
    • UI reflects access status with color-coded badges and conditional project creation controls.
  • Tests

    • Tests updated to seed subscriptions for protected flows.
  • Chores

    • Added plans package exports and updated .gitignore; removed nanoid dependency.

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

@InfinityBowman InfinityBowman linked an issue Dec 22, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 22, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Plans & shared exports
packages/shared/src/plans/types.ts, packages/shared/src/plans/plans.ts, packages/shared/src/plans/index.ts, packages/shared/src/index.ts, packages/shared/package.json
New plans/types module and exports: PLANS, DEFAULT_PLAN, getPlan, isUnlimitedQuota, and type exports (PlanId, EntitlementKey, QuotaKey, Entitlements, Quotas, Plan, Plans). Exposes plans via package exports.
Frontend access utilities
packages/web/src/lib/access.js, packages/web/src/lib/entitlements.js
Add hasActiveAccess/isAccessExpired and entitlement/quota helpers (isSubscriptionActive, getEffectiveEntitlements, getEffectiveQuotas, hasEntitlement, hasQuota).
Backend access utilities
packages/workers/src/lib/access.js, packages/workers/src/lib/entitlements.js
Mirror frontend helpers for server-side use: hasActiveAccess, isAccessExpired, isSubscriptionActive, getEffectiveEntitlements, getEffectiveQuotas, hasEntitlement, hasQuota.
Middleware
packages/workers/src/middleware/requireAccess.js, packages/workers/src/middleware/requireEntitlement.js, packages/workers/src/middleware/requireQuota.js, packages/workers/src/middleware/subscription.js
Add requireAccess, requireEntitlement, requireQuota middleware (attach subscription/entitlements/quotas to context); replace old requireTier/requireFeature with getSubscription helper.
Routes & admin APIs
packages/workers/src/routes/admin.js, packages/workers/src/routes/projects.js
Admin routes: list users with subscription and add POST/DELETE /api/admin/users/:userId/subscription to grant/revoke subscriptions. Projects POST now guarded by requireEntitlement('project.create') and requireQuota('projects.max', getProjectCount, 1) and performs batched inserts.
Validation & config
packages/workers/src/config/validation.js, packages/workers/src/config/stripe.js
Add subscriptionSchemas export for grant validation; remove legacy tier/feature access logic and helpers from stripe config.
Web admin UI & store
packages/web/src/components/admin-ui/UserTable.jsx, packages/web/src/stores/adminStore.js
UI: new Access column, Change Access dialog, formatAccessStatus; Store: grantAccess and revokeAccess public functions and integration with UI.
Subscription hook & project UI
packages/web/src/primitives/useSubscription.js, packages/web/src/components/project-ui/ProjectDashboard.jsx, packages/web/src/components/project-ui/CreateProjectForm.jsx
useSubscription returns entitlements/quotas, hasEntitlement, hasQuota, hasActiveAccess; ProjectDashboard and CreateProjectForm updated to show/disable create actions and surface missing_entitlement and quota_exceeded errors.
Worker middleware usage & tests
packages/workers/src/routes/__tests__/projects.test.js, test helpers
Tests seeded with subscriptions (seedSubscription) to exercise guarded project creation paths; test helpers exported accordingly.
Tooling & minor changes
.cursor/rules/corates.mdc, .cursor/rules/workers.mdc, .gitignore, packages/web/package.json, packages/web/src/primitives/useProject/pdfs.js, packages/web/src/primitives/__tests__/useProject.test.js, packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
Rules updated (plan files prohibited, workers guidance added); .cursor/plans* ignored; removed nanoid dependency and replaced with crypto.randomUUID(); small UI/avatar and test mock updates.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Account linking #53: Modifies UserTable.jsx and admin users paths—may conflict with added Access column and admin subscription endpoints.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective: introducing a new entitlements and quotas architecture throughout the codebase (frontend, backend, shared) to support subscription-based feature gating and quota enforcement for future Stripe integration.
Docstring Coverage ✅ Passed Docstring coverage is 93.55% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 130-gated-feature-access

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
corates-workers-prod c1e8e3e Dec 22 2025, 11:52 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 22, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
corates 8908ef2 Commit Preview URL Dec 23 2025, 04:35 AM

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 between isActive and hasActiveAccess for trialing status.

isActive (lines 40-43) returns true for 'trialing' status, but hasActiveAccess (via checkActiveAccess from @/lib/access.js) only returns true for 'active' status. This could lead to confusing behavior where isActive() is true but hasActiveAccess() is false for 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 with packages/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.status is a valid string and currentPeriodEnd is 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 > 1000000000000 to 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: getEffectiveQuotas is 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 upsertSubscription function correctly preserves the existing subscription ID during updates by omitting the id field 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 PLANS configuration is duplicated verbatim in packages/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/shared package to ensure consistency and reduce duplication.

packages/workers/src/middleware/requireEntitlement.js (1)

18-24: Redundant auth check if used after requireAuth middleware.

The JSDoc states this middleware "must be used after requireAuth middleware," but it still performs its own auth check. If requireAuth already 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 user is 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:

  1. Export the middleware directly (no factory needed), or
  2. Accept a reason parameter 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.details after createDomainError() returns is a code smell. Consider including the details in the initial creation or extending createDomainError to 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 extracting formatAccessStatus to 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 small AccessBadge component would improve readability.


36-656: Component exceeds recommended size - consider refactoring.

At 656 lines, UserTable.jsx is becoming a "God component" coordinating multiple concerns (table rendering, action menus, ban dialog, access dialog, confirm dialog). Per coding guidelines, consider:

  1. Extracting dialogs into separate components (e.g., BanUserDialog, GrantAccessDialog, ConfirmActionDialog)
  2. 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 using createMemo for derived values.

Per coding guidelines, use createMemo for derived values to ensure they update reactively. While these work as arrow functions, createMemo provides 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, and requested. 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:

  1. Fetch plan configuration from a backend endpoint at app initialization
  2. Generate a shared config package that both frontend and backend import
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 839e506 and c1e8e3e.

📒 Files selected for processing (22)
  • .cursor/rules/corates.mdc
  • .cursor/rules/workers.mdc
  • .gitignore
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/lib/access.js
  • packages/web/src/lib/entitlements.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/primitives/useSubscription.js
  • packages/web/src/stores/adminStore.js
  • packages/workers/src/config/plans.js
  • packages/workers/src/config/stripe.js
  • packages/workers/src/config/validation.js
  • packages/workers/src/lib/access.js
  • packages/workers/src/lib/entitlements.js
  • packages/workers/src/middleware/requireAccess.js
  • packages/workers/src/middleware/requireEntitlement.js
  • packages/workers/src/middleware/requireQuota.js
  • packages/workers/src/middleware/subscription.js
  • packages/workers/src/routes/admin.js
  • packages/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.js
  • packages/workers/src/config/validation.js
  • packages/workers/src/middleware/requireAccess.js
  • packages/workers/src/lib/access.js
  • packages/web/src/lib/access.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/workers/src/middleware/requireQuota.js
  • packages/web/src/lib/entitlements.js
  • packages/workers/src/middleware/subscription.js
  • packages/workers/src/lib/entitlements.js
  • packages/workers/src/routes/admin.js
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/primitives/useSubscription.js
  • packages/workers/src/config/plans.js
  • packages/workers/src/routes/projects.js
  • packages/web/src/stores/adminStore.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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.js
  • packages/workers/src/config/validation.js
  • packages/workers/src/middleware/requireAccess.js
  • packages/workers/src/lib/access.js
  • packages/web/src/lib/access.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/workers/src/middleware/requireQuota.js
  • packages/web/src/lib/entitlements.js
  • packages/workers/src/middleware/subscription.js
  • packages/workers/src/lib/entitlements.js
  • packages/workers/src/routes/admin.js
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/primitives/useSubscription.js
  • packages/workers/src/config/plans.js
  • packages/workers/src/routes/projects.js
  • packages/web/src/stores/adminStore.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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.js
  • packages/workers/src/config/validation.js
  • packages/workers/src/middleware/requireAccess.js
  • packages/workers/src/lib/access.js
  • packages/workers/src/middleware/requireQuota.js
  • packages/workers/src/middleware/subscription.js
  • packages/workers/src/lib/entitlements.js
  • packages/workers/src/routes/admin.js
  • packages/workers/src/config/plans.js
  • packages/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.js
  • packages/workers/src/config/validation.js
  • packages/workers/src/middleware/requireAccess.js
  • packages/workers/src/lib/access.js
  • packages/web/src/lib/access.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/workers/src/middleware/requireQuota.js
  • packages/web/src/lib/entitlements.js
  • packages/workers/src/middleware/subscription.js
  • packages/workers/src/lib/entitlements.js
  • packages/workers/src/routes/admin.js
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/primitives/useSubscription.js
  • packages/workers/src/config/plans.js
  • packages/workers/src/routes/projects.js
  • packages/web/src/stores/adminStore.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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.js
  • packages/workers/src/config/validation.js
  • packages/workers/src/middleware/requireAccess.js
  • packages/workers/src/lib/access.js
  • packages/workers/src/middleware/requireQuota.js
  • packages/workers/src/middleware/subscription.js
  • packages/workers/src/lib/entitlements.js
  • packages/workers/src/routes/admin.js
  • packages/workers/src/config/plans.js
  • packages/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 the solid-icons library 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, use createMemo to ensure it updates reactively
For complex state or state objects in SolidJS, use Solid's createStore for 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.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/lib/entitlements.js
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/primitives/useSubscription.js
  • packages/web/src/stores/adminStore.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/lib/entitlements.js
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/primitives/useSubscription.js
  • packages/web/src/stores/adminStore.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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 the solid-icons library 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
Use createMemo for derived values to ensure they update reactively

Files:

  • packages/web/src/lib/access.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/web/src/lib/entitlements.js
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/primitives/useSubscription.js
  • packages/web/src/stores/adminStore.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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.js
  • packages/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 in packages/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/project-ui/ProjectDashboard.jsx
  • packages/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.js barrel export

Files:

  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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.jsx
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/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 in packages/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/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
packages/web/src/stores/**/*.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Solid's createStore for 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.mdc
  • packages/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.mdc
  • packages/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.mdc
  • packages/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.mdc
  • 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:

  • .cursor/rules/workers.mdc
  • packages/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.mdc
  • packages/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. Both account-merge.js (batch operations with db.batch()) and projects.js (request validation with validateRequest()) exist and follow the documented patterns appropriately. No changes needed.


8-25: The guidance on db.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. Using db.transaction() with D1 is not supported and throws an error. The codebase already uses db.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 from config/validation.js and 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') and c.set('validatedQuery')). Zod 4 error handling is properly implemented with unified error mapping via issue.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.

hasEntitlement and hasQuota are correctly implemented as regular functions rather than createMemo. Since they accept parameters, they need to be invoked by consumers within a reactive context, which will properly track the subscription() 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 on currentPeriodEnd correctly allows null, 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.all is 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 inactive rather 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 getSubscription helper, 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 tier parameter, and correctly constructs the request body with timestamps in seconds.


239-252: LGTM!

The revokeAccess function 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 uses createDomainError. For consistency with the rest of the codebase and the requireAuth middleware 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: Verify Infinity serialization for API responses.

Using Infinity for unlimited quotas works internally, but JSON.stringify(Infinity) returns null. 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 to DEFAULT_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 handleGrantAccess function correctly:

  • Parses the datetime-local input 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 use await.

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 getPlan function 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:

  • hasEntitlement checks for explicit true values
  • hasQuota correctly handles Infinity as unlimited and uses appropriate boundary checking

Also applies to: 127-132


11-15: Verify: Free plan allows checklist editing without project creation.

The free plan grants 'checklist.edit': true but 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': false for 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:

  1. Entitlement check (permission)
  2. Quota check (capacity)
  3. 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_FAILED and includes operation context, making debugging easier.


90-97: Code is correct; quota semantics confirmed.

The getProjectCount function correctly counts only projects where createdBy === user.id. The projects.max quota 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.

Comment thread packages/web/src/lib/entitlements.js
Comment thread packages/web/src/primitives/useProject/pdfs.js Outdated
Comment thread packages/workers/src/routes/projects.js Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/web/src/components/admin-ui/UserTable.jsx (2)

349-366: Extract color classes as a constant.

The colorClasses object 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 formatAccessStatus optimization.


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

📥 Commits

Reviewing files that changed from the base of the PR and between c1e8e3e and 33468de.

📒 Files selected for processing (3)
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/lib/entitlements.js
  • packages/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.jsx
  • packages/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 the solid-icons library 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, use createMemo to ensure it updates reactively
For complex state or state objects in SolidJS, use Solid's createStore for 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.jsx
  • packages/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 in packages/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.js barrel 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 the solid-icons library 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
Use createMemo for 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.jsx
  • packages/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 in packages/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 getProjectCount helper properly uses Drizzle's count() 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 requireQuota middleware correctly invokes the getUsage callback with (c, user) parameters (line 22 of requireQuota.js). The getProjectCount function 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 createSignal is 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.

Comment thread packages/web/src/components/admin-ui/UserTable.jsx
Comment on lines +187 to +227
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,
};
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +153 to +173
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,
},
],
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -5

Repository: 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.js

Repository: 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/null

Repository: 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.md

Repository: 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/null

Repository: 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/web/src/components/admin-ui/UserTable.jsx (1)

349-366: Previous feedback still applies: use createMemo for 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 using createMemo would improve performance, especially with many users.

Based on learnings: "Use createMemo for 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 subscriptionId is generated on every call, but per the upsertSubscription logic in packages/workers/src/db/subscriptions.js, when a subscription already exists, the update path ignores the provided id field. While harmless, it's slightly wasteful.

Consider moving UUID generation inside upsertSubscription for 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 EntitlementKey union and Entitlements interface 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 QuotaKey and Quotas. 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 for parseInt.

While parseInt defaults 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 redundant getPlan call.

When the subscription is inactive, getPlan(planId) is already called on line 33, but then getPlan(DEFAULT_PLAN) is called again on line 35. Since you already have plan, you could check if planId === DEFAULT_PLAN and reuse it, though the current approach is also acceptable for clarity.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33468de and 6f4cac8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • packages/shared/package.json
  • packages/shared/src/index.ts
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/plans.ts
  • packages/shared/src/plans/types.ts
  • packages/web/package.json
  • packages/web/src/components/admin-ui/UserTable.jsx
  • packages/web/src/lib/entitlements.js
  • packages/web/src/primitives/useProject/pdfs.js
  • packages/workers/src/lib/entitlements.js
  • packages/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.ts
  • packages/shared/package.json
  • packages/shared/src/plans/plans.ts
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/types.ts
  • packages/workers/src/routes/admin.js
  • packages/web/src/lib/entitlements.js
  • 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/shared/src/index.ts
  • packages/shared/src/plans/plans.ts
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/types.ts
  • packages/workers/src/routes/admin.js
  • packages/web/src/lib/entitlements.js
  • 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/shared/src/index.ts
  • packages/shared/src/plans/plans.ts
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/types.ts
  • packages/workers/src/routes/admin.js
  • packages/web/src/lib/entitlements.js
  • 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/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 the solid-icons library 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, use createMemo to ensure it updates reactively
For complex state or state objects in SolidJS, use Solid's createStore for 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.js
  • 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/lib/entitlements.js
  • 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 the solid-icons library 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
Use createMemo for derived values to ensure they update reactively

Files:

  • packages/web/src/lib/entitlements.js
  • packages/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 in packages/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.js barrel 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 in packages/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 ./plans export 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 type for type-only exports is a good TypeScript practice.

packages/shared/src/plans/plans.ts (1)

67-72: LGTM!

The getPlan function 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.all to 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 Plan and Plans types are well-structured and provide a clear contract for plan configuration.

packages/web/src/lib/entitlements.js (1)

74-79: LGTM!

The hasQuota function correctly handles Infinity for 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 selectedPlan reset 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.

Comment thread packages/shared/src/plans/plans.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (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

📥 Commits

Reviewing files that changed from the base of the PR and between 6f4cac8 and 8908ef2.

📒 Files selected for processing (11)
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/plans.ts
  • packages/shared/src/plans/types.ts
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/web/src/lib/entitlements.js
  • packages/web/src/primitives/__tests__/useProject.test.js
  • packages/workers/src/lib/entitlements.js
  • packages/workers/src/middleware/requireQuota.js
  • packages/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.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/shared/src/plans/index.ts
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/lib/entitlements.js
  • packages/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 the solid-icons library 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, use createMemo to ensure it updates reactively
For complex state or state objects in SolidJS, use Solid's createStore for 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.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/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.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/shared/src/plans/index.ts
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/lib/entitlements.js
  • packages/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.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/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 the solid-icons library 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
Use createMemo for derived values to ensure they update reactively

Files:

  • packages/web/src/primitives/__tests__/useProject.test.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/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.js
  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/shared/src/plans/index.ts
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/lib/entitlements.js
  • packages/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 in packages/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.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/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.js barrel export

Files:

  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/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.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/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 in packages/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.jsx
  • packages/web/src/components/project-ui/overview-tab/OverviewTab.jsx
  • packages/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 type for 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_BASE when 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 because getEffectiveQuotas() always returns a plan's quotas object from the static plan definitions in plans.ts, where all plans ('free', 'pro', 'unlimited') guarantee the 'projects.max' key exists. The fallback mechanism in getPlan() 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.js

The 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'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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 -5

Repository: 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 js

Repository: 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.

@InfinityBowman InfinityBowman merged commit 0070053 into main Dec 23, 2025
4 checks passed
@InfinityBowman InfinityBowman deleted the 130-gated-feature-access branch December 23, 2025 04:39
This was referenced Dec 25, 2025
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gated feature access

2 participants