Skip to content

72 offline fixes#73

Merged
InfinityBowman merged 8 commits into
mainfrom
72-offline-fixes
Dec 16, 2025
Merged

72 offline fixes#73
InfinityBowman merged 8 commits into
mainfrom
72-offline-fixes

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Dec 16, 2025

Summary by CodeRabbit

  • New Features

    • Avatar caching for offline use with background pruning
    • Image compression utilities for avatar uploads
  • Improvements

    • Customizable avatar fallback styling and centralized Avatar component usage
    • Offline-first avatar integration with cache cleanup on sign-out/delete
    • Debounced, verified online detection with quieter error handling and smarter reconnect behavior
  • UI

    • Updated avatar presentation and chevron icon for improved visuals

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

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

coderabbitai Bot commented Dec 16, 2025

Warning

Rate limit exceeded

@InfinityBowman has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 54 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between ae9c5e8 and 9d1d706.

📒 Files selected for processing (1)
  • packages/web/src/primitives/useOnlineStatus.js (2 hunks)

Walkthrough

Adds an IndexedDB-backed avatar cache and integrates it into auth and UI; makes Avatar fallback styling configurable; adds image compression utilities; and adds online/offline guards plus reconnection intent management to WebSocket/connection primitives.

Changes

Cohort / File(s) Summary
Avatar UI component
packages/ui/src/zag/Avatar.jsx
Makes fallback avatar class configurable via a local fallbackClass(); adds overflow-hidden and appends props.class to root; sets referrerPolicy="no-referrer" on <img>.
Avatar cache primitives (new)
packages/web/src/primitives/avatarCache.js
New IndexedDB-backed avatar cache API: getCachedAvatar, cacheAvatar, removeCachedAvatar, fetchAndCacheAvatar, getAvatarWithCache, clearAvatarCache, pruneExpiredAvatars. Handles fetch, compression, blob→dataURL, rate-limiting, failure tracking, and pruning.
Auth store integration
packages/web/src/api/better-auth-store.js
Loads/prunes cache at startup; loads cached avatar on init; caches avatar after online login; augments returned user objects with cached avatar data URL; clears cached avatar and cache on sign-out/deleteAccount.
UI avatar adoption
packages/web/src/components/Navbar.jsx, packages/web/src/components/project-ui/tabs/OverviewTab.jsx, packages/web/src/components/project-ui/AddMemberModal.jsx
Replaces manual avatar markup with centralized Avatar component usage; swaps inline chevron SVG for FiChevronDown in Navbar.
Image compression utility + Profile integration
packages/web/src/lib/imageUtils.js, packages/web/src/components/profile-ui/ProfilePage.jsx
Adds compressImageBlob and compressImageFile; ProfilePage now uses compressImageFile(file, { maxSize, quality }) instead of an inline compressor.
Connection / WebSocket resilience
packages/web/src/primitives/useProject/connection.js, packages/web/src/primitives/useProject/index.js, packages/web/src/primitives/useNotifications.js, packages/web/src/components/UserYjsProvider.jsx
Adds online/offline guards and reconnection intent (shouldConnect/shouldBeConnected); suppresses noisy errors while offline; introduces destroy(), getShouldReconnect(), setShouldReconnect() on the connection manager; uses destroy() in disconnect flow and reconnects when back online.
Online status debounce & verification
packages/web/src/primitives/useOnlineStatus.js
Debounced online/offline detection with connectivity verification (HEAD /api/health) before marking online; adds timers, cancellation logic, and cleanup.
Notifications connectivity handling
packages/web/src/primitives/useNotifications.js
Adds shouldConnect gating, online/offline listeners, suppressed logging while offline, backoff/reset handling for reconnect attempts.

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI Component
  participant Auth as Auth Store
  participant Cache as avatarCache
  participant DB as IndexedDB
  participant Net as Network

  UI->>Auth: request user (init / login)
  Auth->>Cache: getAvatarWithCache(userId, imageUrl)
  Cache->>DB: read cached entry by userId
  alt cached & valid
    DB-->>Cache: dataURL
    Cache-->>Auth: dataURL
    Auth-->>UI: user + cached image
  else no valid cache
    Cache->>Net: fetch image URL
    alt fetch success
      Net-->>Cache: blob
      Cache->>Cache: compress / blob→dataURL
      Cache->>DB: put {userId, dataURL, cachedAt}
      Cache-->>Auth: dataURL
      Auth-->>UI: user + fetched image
    else fetch failure
      Cache-->>Auth: null
      Auth-->>UI: user (no cached image)
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files needing careful review:
    • packages/web/src/primitives/avatarCache.js — IndexedDB open/upgrade, concurrency, fetch/backoff, blob→dataURL correctness, pruning.
    • packages/web/src/api/better-auth-store.js — merging cached avatar into user shape and lifecycle handling on sign-out/delete.
    • Connection primitives (useProject/connection.js, useNotifications.js, UserYjsProvider.jsx) — event listener lifecycle, reconnection intent semantics, destroy() usage.
    • packages/web/src/lib/imageUtils.js and ProfilePage integration — compression correctness and filename/MIME handling.

Possibly related PRs

Poem

🐰 I hopped through IndexedDB with care,
Saved tiny faces for journeys to share,
A snazzy fallback dons a brand-new hat,
I squeezed a snap, then cached the chat,
Tiny pixels snug and fair. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '72 offline fixes' references issue #72 and broadly describes offline-related improvements, which aligns with the comprehensive changes across avatar caching, connectivity handling, and offline-first behavior throughout the changeset.

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

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

cloudflare-workers-and-pages Bot commented Dec 16, 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 9d1d706 Commit Preview URL Dec 16 2025, 09:16 PM

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: 0

🧹 Nitpick comments (4)
packages/web/src/components/UserYjsProvider.jsx (1)

51-54: Missing reconnection logic when coming back online.

The offline guard prevents connection attempts, but unlike useNotifications.js and connection.js, there's no online event listener to retry connectToProject when the network returns. Projects skipped during offline will remain disconnected.

Consider adding online/offline event handling similar to the pattern used in other files in this PR:

+  // Handle online event to reconnect
+  function handleOnline() {
+    if (userProjects().length > 0) {
+      userProjects().forEach(project => {
+        if (!connections[project.id]?.connected) {
+          connectToProject(project.id);
+        }
+      });
+    }
+  }
+
+  if (typeof window !== 'undefined') {
+    window.addEventListener('online', handleOnline);
+  }

And clean up in onCleanup.

packages/web/src/api/better-auth-store.js (1)

143-158: Consider memoizing the user object with cached avatar.

The current implementation creates a new object on every user() call when cachedAvatarUrl() exists. While acceptable for typical usage, you could memoize this with createMemo if user() is called frequently in reactive contexts.

+  const userWithAvatar = createMemo(() => {
+    const currentUser = isOnline() ? sessionUser() : cachedUser();
+    if (currentUser && cachedAvatarUrl()) {
+      return { ...currentUser, image: cachedAvatarUrl() };
+    }
+    return currentUser;
+  });
+
   const user = () => {
-    if (isOnline()) {
-      const currentUser = sessionUser();
-      // When online, prefer cached avatar data URL (works offline, faster loading)
-      if (currentUser && cachedAvatarUrl()) {
-        return { ...currentUser, image: cachedAvatarUrl() };
-      }
-      return currentUser;
-    }
-    // When offline, return cached user with cached avatar
-    const cached = cachedUser();
-    if (cached && cachedAvatarUrl()) {
-      return { ...cached, image: cachedAvatarUrl() };
-    }
-    return cached;
+    return userWithAvatar();
   };
packages/web/src/primitives/avatarCache.js (2)

39-49: Consider implementing cache expiry using the cachedAt index.

The cachedAt index is created but never utilized. Without cache expiry logic, the IndexedDB can grow unbounded over time as avatars are cached for different users. Consider adding a cleanup mechanism that removes entries older than a threshold (e.g., 30 days).

Would you like me to generate a cache cleanup function that prunes old avatar entries?


62-69: Consider adding size validation for cached avatars.

Large avatar images could result in substantial IndexedDB storage usage. Consider adding a size check before caching (e.g., skip caching if blob.size > 500KB) or compressing/resizing the image before storing.

// Optional: Skip caching very large avatars
const MAX_AVATAR_SIZE = 500 * 1024; // 500KB
if (blob.size > MAX_AVATAR_SIZE) {
  console.warn('Avatar too large to cache:', blob.size);
  return null;
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98ae7a6 and 0c36cb6.

📒 Files selected for processing (10)
  • packages/ui/src/zag/Avatar.jsx (1 hunks)
  • packages/web/src/api/better-auth-store.js (6 hunks)
  • packages/web/src/components/Navbar.jsx (2 hunks)
  • packages/web/src/components/UserYjsProvider.jsx (2 hunks)
  • packages/web/src/components/profile-ui/ProfilePage.jsx (1 hunks)
  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx (2 hunks)
  • packages/web/src/primitives/avatarCache.js (1 hunks)
  • packages/web/src/primitives/useNotifications.js (4 hunks)
  • packages/web/src/primitives/useProject/connection.js (5 hunks)
  • packages/web/src/primitives/useProject/index.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/components/Navbar.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
  • packages/web/src/primitives/avatarCache.js
  • packages/ui/src/zag/Avatar.jsx
  • packages/web/src/primitives/useProject/connection.js
  • packages/web/src/components/UserYjsProvider.jsx
  • packages/web/src/api/better-auth-store.js
  • packages/web/src/primitives/useNotifications.js
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
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
Import stores directly where needed instead of passing values through multiple components
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 (when scoped to a feature)
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 (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

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
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/components/Navbar.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
  • packages/web/src/primitives/avatarCache.js
  • packages/web/src/primitives/useProject/connection.js
  • packages/web/src/components/UserYjsProvider.jsx
  • packages/web/src/api/better-auth-store.js
  • packages/web/src/primitives/useNotifications.js
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

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

Prefer using config files rather than hardcoding values

Files:

  • packages/web/src/primitives/useProject/index.js
  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/components/Navbar.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
  • packages/web/src/primitives/avatarCache.js
  • packages/ui/src/zag/Avatar.jsx
  • packages/web/src/primitives/useProject/connection.js
  • packages/web/src/components/UserYjsProvider.jsx
  • packages/web/src/api/better-auth-store.js
  • packages/web/src/primitives/useNotifications.js
**/{index,index.js,index.jsx,index.ts,index.tsx}

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

Group related components in subdirectories with an index.js barrel export

Files:

  • packages/web/src/primitives/useProject/index.js
packages/web/src/components/**/*.{js,jsx,ts,tsx}

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

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
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

Files:

  • packages/web/src/components/project-ui/tabs/OverviewTab.jsx
  • packages/web/src/components/Navbar.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
  • packages/web/src/components/UserYjsProvider.jsx
🧠 Learnings (1)
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Better-Auth for authentication and user management

Applied to files:

  • packages/web/src/api/better-auth-store.js
🧬 Code graph analysis (3)
packages/web/src/components/project-ui/tabs/OverviewTab.jsx (1)
packages/ui/src/zag/Avatar.jsx (1)
  • Avatar (5-38)
packages/web/src/primitives/avatarCache.js (1)
packages/landing/src/components/DefaultSeo.jsx (1)
  • imageUrl (5-5)
packages/ui/src/zag/Avatar.jsx (3)
packages/ui/src/zag/Clipboard.jsx (2)
  • api (30-30)
  • api (98-98)
packages/ui/src/zag/Dialog.jsx (2)
  • api (33-33)
  • api (132-132)
packages/ui/src/zag/PasswordInput.jsx (1)
  • api (22-22)
⏰ 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 (17)
packages/web/src/components/UserYjsProvider.jsx (1)

96-105: LGTM!

The error suppression when offline prevents console spam, and the connection state is still properly updated regardless of online status.

packages/web/src/primitives/useNotifications.js (3)

25-36: LGTM!

Good implementation of connectivity intent tracking with shouldConnect. The offline guard prevents unnecessary WebSocket attempts when offline, and the flag ensures proper reconnection behavior when coming back online.


108-129: LGTM!

Well-structured online/offline event handling. Resetting reconnectAttempts in handleOnline provides good UX by avoiding excessive delays after network recovery. Canceling pending reconnection timeouts in handleOffline prevents wasted attempts.


154-159: LGTM!

Proper cleanup of event listeners in onCleanup prevents memory leaks.

packages/web/src/primitives/useProject/connection.js (3)

26-48: LGTM!

Good implementation of online/offline event handling with proper intent tracking via shouldBeConnected. The pattern correctly pauses reconnection attempts when offline and resumes when online.


53-59: Good handling of offline state in connect().

Setting shouldBeConnected = true even when returning early due to offline status ensures that handleOnline will trigger a connection attempt when network is restored.


133-140: LGTM!

The destroy() function properly cleans up event listeners to prevent memory leaks, which is essential since these listeners are registered at the module level.

packages/web/src/primitives/useProject/index.js (1)

118-122: LGTM!

Correctly using destroy() instead of disconnect() ensures that the online/offline event listeners registered in connection.js are properly cleaned up, preventing memory leaks when the hook unmounts.

packages/web/src/components/profile-ui/ProfilePage.jsx (1)

109-110: LGTM!

The updated comment provides clearer documentation about the avatar URL priority, aligning with the broader avatar caching strategy introduced in this PR.

packages/ui/src/zag/Avatar.jsx (1)

26-32: LGTM! Clean implementation of configurable fallback styling.

The fallbackClass helper provides a sensible default while allowing customization. This pattern is consistent with how other props are accessed in the component.

packages/web/src/components/project-ui/tabs/OverviewTab.jsx (1)

112-117: LGTM! Good refactor to use the centralized Avatar component.

The Avatar component is correctly integrated with appropriate props for the member context. The custom fallbackClass provides consistent styling with the project's blue theme.

packages/web/src/components/Navbar.jsx (1)

121-131: LGTM! Clean refactor to use Avatar component and FiChevronDown icon.

The Avatar component is correctly integrated with reactive accessors (user()?.image, user()?.name), and the chevron icon replacement simplifies the template. The aria-hidden="true" is appropriately set for the decorative icon.

packages/web/src/api/better-auth-store.js (2)

90-98: LGTM! Avatar cache initialization on module load.

The async avatar loading on init correctly handles the case where a cached user exists. The race between this and session fetch is benign since both paths result in a valid cached avatar URL.


360-363: LGTM! Proper avatar cache cleanup on signout.

The signout flow correctly clears both the reactive signal and the IndexedDB cache. Not awaiting clearAvatarCache() is acceptable here since it's cleanup that doesn't affect the signout flow.

packages/web/src/primitives/avatarCache.js (3)

1-14: LGTM! Well-structured avatar caching module with IndexedDB.

Good use of singleton pattern for database instance, with proper error handling for environments where IndexedDB is unavailable.


157-190: LGTM! Robust avatar fetching with proper CORS handling.

The distinction between relative and absolute URLs for credential handling is correct. External avatar providers (like Google) require no-cors or no credentials, while internal API endpoints need authentication. Error handling gracefully falls back to returning null.


199-220: LGTM! Good offline-first pattern with cache fallback.

The function correctly prioritizes cached data when offline and falls back to cache on fetch failure. This ensures avatar availability in degraded network conditions.

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: 2

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/components/UserYjsProvider.jsx (1)

98-107: Inconsistent online status check—use isOnline() instead of navigator.onLine.

Line 100 uses navigator.onLine while the rest of the file (lines 54, 157) uses the isOnline() hook. This creates inconsistency in how online status is determined.

Apply this diff to use the same online status source:

     ws.onerror = () => {
       // Suppress error logging when offline to prevent console spam
-      if (navigator.onLine) {
+      if (isOnline()) {
         console.error(`Project ${projectId} WebSocket error`);
       }
🧹 Nitpick comments (3)
packages/web/src/components/profile-ui/ProfilePage.jsx (1)

11-13: Consider importing default compression settings from the shared utility.

The constants AVATAR_MAX_SIZE and AVATAR_QUALITY duplicate the defaults in imageUtils.js. While this works, you could simplify by omitting the options (to use defaults) or exporting the defaults from imageUtils.js for consistency.

packages/web/src/api/better-auth-store.js (1)

147-162: Spreads in user() create new object references on every call.

The spread operator in user() creates a new object on each invocation when cachedAvatarUrl() is set. In SolidJS, this can trigger unnecessary re-renders in consuming components since object identity changes. Consider memoizing the merged user object with createMemo to stabilize references.

+ import { createSignal, createRoot, createEffect, createMemo } from 'solid-js';
...
- const user = () => {
-   if (isOnline()) {
-     const currentUser = sessionUser();
-     // When online, prefer cached avatar data URL (works offline, faster loading)
-     if (currentUser && cachedAvatarUrl()) {
-       return { ...currentUser, image: cachedAvatarUrl() };
-     }
-     return currentUser;
-   }
-   // When offline, return cached user with cached avatar
-   const cached = cachedUser();
-   if (cached && cachedAvatarUrl()) {
-     return { ...cached, image: cachedAvatarUrl() };
-   }
-   return cached;
- };
+ const user = createMemo(() => {
+   if (isOnline()) {
+     const currentUser = sessionUser();
+     if (currentUser && cachedAvatarUrl()) {
+       return { ...currentUser, image: cachedAvatarUrl() };
+     }
+     return currentUser;
+   }
+   const cached = cachedUser();
+   if (cached && cachedAvatarUrl()) {
+     return { ...cached, image: cachedAvatarUrl() };
+   }
+   return cached;
+ });
packages/web/src/primitives/avatarCache.js (1)

89-113: Transactions aren't explicitly completed before resolving.

The IndexedDB transactions complete automatically when all requests succeed and go out of scope, which works for reads. However, for writes in cacheAvatar, you may want to wait for tx.oncomplete to ensure data persistence before the promise resolves, especially for critical cache operations.

 export async function cacheAvatar(userId, dataUrl) {
   if (!userId || !dataUrl) return;

   try {
     const db = await getDb();
     const tx = db.transaction(AVATAR_STORE_NAME, 'readwrite');
     const store = tx.objectStore(AVATAR_STORE_NAME);

     await new Promise((resolve, reject) => {
       const request = store.put({
         userId,
         dataUrl,
         cachedAt: Date.now(),
       });
-      request.onsuccess = () => resolve();
+      tx.oncomplete = () => resolve();
       request.onerror = () => reject(request.error);
+      tx.onerror = () => reject(tx.error);
     });
   } catch (err) {
     console.error('Error caching avatar:', err);
   }
 }

Also applies to: 120-140

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c36cb6 and e91b0cd.

📒 Files selected for processing (5)
  • packages/web/src/api/better-auth-store.js (6 hunks)
  • packages/web/src/components/UserYjsProvider.jsx (5 hunks)
  • packages/web/src/components/profile-ui/ProfilePage.jsx (3 hunks)
  • packages/web/src/lib/imageUtils.js (1 hunks)
  • packages/web/src/primitives/avatarCache.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/web/src/lib/imageUtils.js
  • packages/web/src/components/UserYjsProvider.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
  • packages/web/src/api/better-auth-store.js
  • packages/web/src/primitives/avatarCache.js
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
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
Import stores directly where needed instead of passing values through multiple components
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 (when scoped to a feature)
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 (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

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
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/lib/imageUtils.js
  • packages/web/src/components/UserYjsProvider.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
  • packages/web/src/api/better-auth-store.js
  • packages/web/src/primitives/avatarCache.js
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

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

Prefer using config files rather than hardcoding values

Files:

  • packages/web/src/lib/imageUtils.js
  • packages/web/src/components/UserYjsProvider.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
  • packages/web/src/api/better-auth-store.js
  • packages/web/src/primitives/avatarCache.js
packages/web/src/components/**/*.{js,jsx,ts,tsx}

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

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
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

Files:

  • packages/web/src/components/UserYjsProvider.jsx
  • packages/web/src/components/profile-ui/ProfilePage.jsx
🧠 Learnings (3)
📚 Learning: 2025-12-16T18:28:31.657Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-16T18:28:31.657Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : When you have complex state or state objects, use Solid's `createStore` for better performance and reactivity

Applied to files:

  • packages/web/src/components/UserYjsProvider.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use Solid's createStore for complex state or state objects for better performance and reactivity

Applied to files:

  • packages/web/src/components/UserYjsProvider.jsx
📚 Learning: 2025-12-16T18:27:52.770Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-16T18:27:52.770Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Better-Auth for authentication and user management

Applied to files:

  • packages/web/src/api/better-auth-store.js
🧬 Code graph analysis (3)
packages/web/src/components/profile-ui/ProfilePage.jsx (1)
packages/web/src/lib/imageUtils.js (1)
  • compressImageFile (80-88)
packages/web/src/api/better-auth-store.js (1)
packages/web/src/primitives/avatarCache.js (6)
  • getCachedAvatar (89-113)
  • dataUrl (267-267)
  • dataUrl (301-301)
  • pruneExpiredAvatars (332-361)
  • fetchAndCacheAvatar (215-277)
  • clearAvatarCache (312-326)
packages/web/src/primitives/avatarCache.js (1)
packages/web/src/lib/imageUtils.js (1)
  • compressImageBlob (17-69)
⏰ 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 (8)
packages/web/src/components/UserYjsProvider.jsx (2)

4-4: LGTM: Online status hook integrated correctly.

The import and usage of useOnlineStatus follows SolidJS conventions correctly—isOnline is properly used as a signal accessor throughout most of the code.

Also applies to: 18-18


53-56: Guard prevents unnecessary connection attempts when offline.

The early return when offline is correct and prevents wasted resources. However, note that this does not set any "intent to connect" flag, so if the app goes offline mid-connection attempt, there's no automatic retry mechanism beyond the new reconnection effect added later.

packages/web/src/components/profile-ui/ProfilePage.jsx (1)

7-7: Good refactor to centralized image compression utility.

The migration from a local compression function to the shared compressImageFile utility improves maintainability and follows the DRY principle. The options object correctly passes maxSize and quality matching the utility's expected signature.

Also applies to: 152-155

packages/web/src/api/better-auth-store.js (1)

364-367: LGTM on cache cleanup.

The avatar cache cleanup in both signout and deleteAccount is consistent. Fire-and-forget pattern for clearAvatarCache() is acceptable here since the operations don't depend on completion.

Also applies to: 654-655

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

17-69: Well-structured compression utility.

The implementation correctly handles aspect ratio preservation, resource cleanup via URL.revokeObjectURL, and error propagation. The Promise-based API integrates well with async workflows.

packages/web/src/primitives/avatarCache.js (3)

35-62: Good singleton pattern for IndexedDB.

The database initialization uses a proper singleton pattern with dbInitPromise to prevent race conditions when multiple callers request the database simultaneously. The upgrade logic correctly creates the object store and index.


215-277: Solid offline-first avatar fetching implementation.

The function handles URL resolution (relative vs absolute), CORS credential handling, rate limiting with exponential backoff, and graceful compression fallback. The separation of concerns between internal API calls and external avatar sources is well thought out.


332-361: LGTM on cache pruning implementation.

The cursor-based approach for pruning expired entries is correct and efficient. Using the cachedAt index with IDBKeyRange.upperBound properly selects entries older than the expiry threshold.

Comment on lines +155 to +165
// Reconnect to projects when coming back online
createEffect(() => {
if (isOnline()) {
const projects = userProjects();
for (const project of projects) {
if (!connections[project.id]?.connected) {
connectToProject(project.id);
}
}
}
});
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

Risk of race conditions and connection thrashing in reconnection effect.

This effect could cause issues:

  1. Race with initial connection: If isOnline() changes while fetchUserProjects is still running (line 152 effect), both might attempt to connect to the same projects simultaneously.
  2. Connection thrashing: If isOnline() toggles rapidly (e.g., flaky network), this will repeatedly call connectToProject without debouncing or backoff.
  3. Redundant attempts: The effect checks !connections[project.id]?.connected, but if a connection is in progress (not yet connected), it will attempt another connection.

Consider one of these solutions:

  • Add a debounce delay (e.g., 1-2 seconds) before attempting reconnection
  • Track "connecting" state separately from "connected" to prevent duplicate attempts
  • Add backoff logic for repeated failures
  • Coordinate with the fetchUserProjects effect to avoid concurrent connection attempts

The PR summary mentions "shouldConnect/getShouldReconnect/setShouldReconnect API" in connection.js—consider leveraging that intent management here as well.

🤖 Prompt for AI Agents
In packages/web/src/components/UserYjsProvider.jsx around lines 155-165, the
reconnect effect can race with the initial fetch and thrash connections; modify
it to (1) wait a short debounce (1-2s) before acting, (2) consult and set a
shouldReconnect/shouldConnect flag from connection.js instead of unconditionally
calling connectToProject, (3) treat connections[project.id] with three states
(idle/connecting/connected) and skip projects in the connecting or connected
state, and (4) implement simple exponential backoff for repeated connect
failures (increment retry count on failure and delay next attempt by backoff)
and coordinate with the fetchUserProjects effect by checking a "projectsLoading"
flag to avoid concurrent connection attempts.

Comment thread packages/web/src/lib/imageUtils.js
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: 0

🧹 Nitpick comments (2)
packages/ui/src/zag/Avatar.jsx (1)

31-31: Consider using a memo for the class computation.

While the current string concatenation should work, wrapping the class logic in createMemo would be more defensive against reactivity edge cases and aligns better with SolidJS best practices for computed values.

Apply this diff if you want to make the class computation more explicitly reactive:

+  const rootClass = createMemo(() => `overflow-hidden ${props.class || ''}`);
+
   return (
-    <div {...api().getRootProps()} class={`overflow-hidden ${props.class || ''}`}>
+    <div {...api().getRootProps()} class={rootClass()}>
packages/web/src/components/project-ui/AddMemberModal.jsx (1)

220-225: LGTM! Consider memoizing the selected user for minor optimization.

The Avatar component integration is correct and maintains consistent styling. The slightly larger size (w-10 vs w-8 in search results) provides good visual hierarchy.

For a minor optimization, you could memoize the selected user to avoid repeated signal access:

+  const user = createMemo(() => selectedUser());
+
   <Show when={selectedUser()}>
     <div class='bg-blue-50 border border-blue-200 rounded-lg p-3 flex items-center justify-between'>
       <div class='flex items-center gap-3'>
         <Avatar
-          src={selectedUser().image}
-          name={selectedUser().displayName || selectedUser().name || selectedUser().email}
+          src={user()?.image}
+          name={user()?.displayName || user()?.name || user()?.email}
           class='w-10 h-10 rounded-full'
           fallbackClass='flex items-center justify-center w-full h-full bg-blue-600 text-white font-medium'
         />
         <div>
           <p class='text-gray-900 font-medium'>
-            {selectedUser().displayName || selectedUser().name || 'Unknown'}
+            {user()?.displayName || user()?.name || 'Unknown'}
           </p>
-          <p class='text-gray-500 text-sm'>{selectedUser().email}</p>
+          <p class='text-gray-500 text-sm'>{user()?.email}</p>
         </div>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e91b0cd and bb8ae16.

📒 Files selected for processing (2)
  • packages/ui/src/zag/Avatar.jsx (1 hunks)
  • packages/web/src/components/project-ui/AddMemberModal.jsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/ui/src/zag/Avatar.jsx
  • packages/web/src/components/project-ui/AddMemberModal.jsx
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

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

Prefer using config files rather than hardcoding values

Files:

  • packages/ui/src/zag/Avatar.jsx
  • packages/web/src/components/project-ui/AddMemberModal.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
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
Import stores directly where needed instead of passing values through multiple components
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 (when scoped to a feature)
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 (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

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
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/components/project-ui/AddMemberModal.jsx
packages/web/src/components/**/*.{js,jsx,ts,tsx}

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

packages/web/src/components/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis
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

Files:

  • packages/web/src/components/project-ui/AddMemberModal.jsx
🧬 Code graph analysis (1)
packages/web/src/components/project-ui/AddMemberModal.jsx (2)
packages/ui/src/zag/Avatar.jsx (1)
  • Avatar (5-44)
packages/web/src/components/profile-ui/ProfilePage.jsx (1)
  • user (33-33)
⏰ 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 (3)
packages/ui/src/zag/Avatar.jsx (2)

26-28: LGTM! Good extensibility pattern.

The fallbackClass() function correctly provides a sensible default while allowing customization through props. This aligns well with the usage in AddMemberModal where different fallback styles are needed.


35-41: referrerPolicy='no-referrer' is a necessary security fix for OAuth avatar images.

The addition of referrerPolicy='no-referrer' solves a known issue where Google and other OAuth providers block avatar images with the default referrer policy. This is standard practice across component libraries and required for proper avatar loading from social login providers.

packages/web/src/components/project-ui/AddMemberModal.jsx (1)

178-183: LGTM! Good refactoring to use the shared Avatar component.

The replacement of manual avatar markup with the Avatar component improves consistency across the application. The prop values are appropriate, and the blue fallback styling aligns well with the UI context.

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 (4)
packages/web/src/primitives/useOnlineStatus.js (2)

28-46: Clear timeout in finally block to prevent resource leak.

If fetch throws (e.g., network error), the timeout is never cleared and will fire 3 seconds later attempting to abort an already-finished request. While harmless, this is a minor resource leak.

     async function verifyConnectivity() {
       try {
         // Use a lightweight endpoint - just check if we can reach our API
         // Falls back to checking if fetch works at all
         const controller = new AbortController();
         const timeoutId = setTimeout(() => controller.abort(), 3000);
 
-        await fetch('/api/health', {
-          method: 'HEAD',
-          signal: controller.signal,
-          cache: 'no-store',
-        });
-
-        clearTimeout(timeoutId);
-        return true;
+        try {
+          await fetch('/api/health', {
+            method: 'HEAD',
+            signal: controller.signal,
+            cache: 'no-store',
+          });
+          return true;
+        } finally {
+          clearTimeout(timeoutId);
+        }
       } catch {
         return false;
       }
     }

60-67: Subtle race condition with async setTimeout callback.

If handleOnline is invoked again while await verifyConnectivity() is in-flight, a new timer is set. When the original callback resumes and executes onlineDebounceTimer = null (line 66), it overwrites the new timer's reference. If cleanup runs during that window, the new timer won't be cleared.

Consider capturing the timer ID and only nullifying if it matches:

       onlineDebounceTimer = setTimeout(async () => {
+        const currentTimer = onlineDebounceTimer;
         // Verify we're actually online before updating state
         const actuallyOnline = await verifyConnectivity();
         if (actuallyOnline) {
           setIsOnline(true);
         }
-        onlineDebounceTimer = null;
+        // Only clear if we're still the active timer
+        if (onlineDebounceTimer === currentTimer) {
+          onlineDebounceTimer = null;
+        }
       }, ONLINE_CONFIRM_DELAY_MS);
packages/web/src/lib/imageUtils.js (2)

17-18: Add input validation for compression options.

The function should validate that maxSize is positive and quality is within the valid range [0, 1] to prevent unexpected behavior. Per coding guidelines, consider using Zod for schema validation.

Add validation at the beginning of the function:

 export function compressImageBlob(blob, options = {}) {
   const { maxSize = DEFAULT_MAX_SIZE, quality = DEFAULT_QUALITY } = options;
+  
+  if (maxSize <= 0) {
+    return Promise.reject(new Error('maxSize must be greater than 0'));
+  }
+  
+  if (quality < 0 || quality > 1) {
+    return Promise.reject(new Error('quality must be between 0 and 1'));
+  }

   return new Promise((resolve, reject) => {

85-86: Consider validating input file type.

To provide better error messages and prevent processing non-image files, consider checking that the input file has an image MIME type before attempting compression.

 export async function compressImageFile(file, options = {}) {
+  if (!file.type.startsWith('image/')) {
+    throw new Error(`Invalid file type: ${file.type}. Expected an image file.`);
+  }
   const compressedBlob = await compressImageBlob(file, options);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ae16 and 9f65b14.

📒 Files selected for processing (2)
  • packages/web/src/lib/imageUtils.js (1 hunks)
  • packages/web/src/primitives/useOnlineStatus.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
For UI icons, use the solid-icons library or SVGs only. Do not use emojis
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Prefer using config files rather than hardcoding values
Use Zod for schema and input validation

**/*.{js,jsx,ts,tsx}: Do not use emojis in code, comments, or documentation
Follow standard JavaScript/SolidJS/Cloudflare best practices
Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability
Keep files small, focused, and modular. Extract sub-modules into a folder (e.g., ComponentName/ with index.jsx and helper components). Move complex logic into separate utility files or primitives. Split large forms into section components
Each file should handle one coherent responsibility
Use Zod for schema and input validation

Files:

  • packages/web/src/primitives/useOnlineStatus.js
  • packages/web/src/lib/imageUtils.js
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

packages/web/src/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx and helper components, or move complex logic into separate utility files or primitives
Each file should handle one coherent responsibility
Group related components in subdirectories with an index.js barrel export
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
Import stores directly where needed instead of passing values through multiple components
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 (when scoped to a feature)
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 (e.g., () => props.name) to ensure they are always up-to-date
Use createMemo for derived values based on props or state to ensure reactive updates
Use Solid's createStore for complex state or state objects for better performance and reactivity
Create reusable logic in primitives (hooks) that can be shared across components to keep components clean and focused on rendering
Components should be lean and focused. Do not implement business logic; move that into stores, utilities, or primitives
Never have a component act as a 'God component' coordinating multiple large concerns

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
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 ...

Files:

  • packages/web/src/primitives/useOnlineStatus.js
  • packages/web/src/lib/imageUtils.js
**/*.{js,jsx,ts,tsx,json,yaml,yml,toml}

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

Prefer using config files rather than hardcoding values

Files:

  • packages/web/src/primitives/useOnlineStatus.js
  • packages/web/src/lib/imageUtils.js
🧬 Code graph analysis (1)
packages/web/src/lib/imageUtils.js (3)
packages/web/src/primitives/avatarCache.js (1)
  • blob (256-256)
packages/web/src/components/project-ui/ChartSection.jsx (2)
  • img (48-48)
  • canvas (46-46)
packages/workers/src/routes/google-drive.js (1)
  • maxSize (242-242)
⏰ 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 (3)
packages/web/src/primitives/useOnlineStatus.js (1)

70-103: Well-structured debouncing and cleanup logic.

The offline handling with double-check of navigator.onLine, proper timer cleanup in onCleanup, and clean event listener management follow SolidJS best practices. The defensive double-check before setting offline state is a good safeguard against false positives.

packages/web/src/lib/imageUtils.js (2)

5-7: LGTM!

The default compression settings are sensible for avatar images—256px provides adequate resolution while keeping file sizes small, and 0.85 JPEG quality balances visual quality with compression.


25-28: Previous issue resolved.

The null check for canvas context has been properly added, addressing the prior review comment.

Comment thread packages/web/src/lib/imageUtils.js Outdated
@InfinityBowman InfinityBowman merged commit d7318e4 into main Dec 16, 2025
1 of 3 checks passed
@InfinityBowman InfinityBowman deleted the 72-offline-fixes branch December 16, 2025 21:14
@github-actions github-actions Bot restored the 72-offline-fixes branch December 16, 2025 21:14
InfinityBowman added a commit that referenced this pull request Dec 17, 2025
@InfinityBowman InfinityBowman deleted the 72-offline-fixes branch December 19, 2025 20:29
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.

offline fixes

1 participant