Skip to content

Fix skeleton loading stuck on Desktop/Capacitor - #490

Merged
2witstudios merged 3 commits into
masterfrom
fix/swr-isPaused-skeleton-loading
Feb 8, 2026
Merged

2witstudios merged 3 commits into
masterfrom
fix/swr-isPaused-skeleton-loading

Conversation

@2witstudios

@2witstudios 2witstudios commented Feb 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Removes isPaused + hasLoadedRef from 7 SWR hooks that were blocking initial fetches on Desktop (Electron) and Capacitor due to async Bearer token retrieval timing
  • Adds revalidateOnFocus: false to usePageTree and useBreadcrumbs as a lightweight replacement for the over-aggressive isPaused guard
  • Adds clearAllSessions() on Layout mount as defense-in-depth against stale editing state from previous app lifecycle

Context

Pages get stuck in skeleton loading state on desktop and mobile apps. Network requests for pages and breadcrumbs don't fire until the user clicks "Retry" after a 12-second timeout. Web browser is unaffected because fetchWithAuth uses synchronous cookie headers there.

The isPaused pattern was designed to prevent tree revalidation during editing, but the invalidateTree() function already has its own editing guard at the call site level, making isPaused a redundant double-guard that introduced timing bugs on non-web platforms.

Files changed

File Change
usePageTree.ts Remove isPaused, hasLoadedRef; add revalidateOnFocus: false
useBreadcrumbs.ts Remove isPaused, hasLoadedRef; add revalidateOnFocus: false
usePermissions.ts Remove isPaused, hasLoadedRef
useTabMeta.ts Remove isPaused, hasLoadedRef (2 SWR calls)
UsageCounter.tsx Remove isPaused
UserDropdown.tsx Remove isPaused (2 SWR calls)
useAiUsage.ts Remove isPaused (2 hooks)
Layout.tsx Add clearAllSessions() on mount

Test plan

  • Desktop cold start with saved tabs: page loads immediately without skeleton hang
  • Capacitor cold start: page loads without skeleton hang
  • Mid-session navigation: content loads without stuck skeleton
  • Editing protection: sidebar tree does NOT refresh mid-typing (verified by invalidateTree() guard)
  • usePageTree.test.ts: 14/14 passing
  • Full apps/web unit tests: all passing

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Added automatic recovery for stuck data-fetch states with limited retry attempts.
  • Refactor

    • Removed conditional pause of revalidation tied to editing/streaming; data fetching now consistently revalidates.
    • Simplified and unified fetch control across multiple components and hooks.
    • Extended breadcrumbs hook to include loading and error status.
  • Chores

    • Purge stale editing sessions on app startup.
  • Tests

    • Updated tests to reflect new SWR configuration and removed editing-based pause scenarios.

…from SWR hooks

The isPaused guard on SWR hooks blocked initial fetches under specific timing
conditions on Desktop (Electron) and Capacitor, where async Bearer token
retrieval in fetchWithAuth introduces delays that don't exist on web. This
caused page tree and breadcrumbs to hang in skeleton state until a 12-second
timeout triggered retry.

Removes isPaused + hasLoadedRef from usePageTree, useBreadcrumbs, usePermissions,
useTabMeta, UsageCounter, UserDropdown, and useAiUsage. The invalidateTree()
editing guard already prevents Socket.IO-triggered revalidations during editing,
making the isPaused double-guard redundant. Adds clearAllSessions() on Layout
mount as defense-in-depth against stale editing state from previous app lifecycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Feb 8, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Removes editing/streaming-driven SWR pause logic across multiple hooks/components, adds a startup session cleanup in Layout, and implements cache-driven self-healing retries in useBreadcrumbs and usePageTree; useBreadcrumbs now returns loading and error state.

Changes

Cohort / File(s) Summary
Components — remove editing pause
apps/web/src/components/billing/UsageCounter.tsx, apps/web/src/components/shared/UserDropdown.tsx
Removed useEditingStore usage and isPaused checks — SWR fetches no longer paused by editing/streaming state.
Layout — session cleanup
apps/web/src/components/layout/Layout.tsx
Added useEditingStore import and an init effect that calls clearAllSessions() on app startup.
Hooks — remove editing pause
apps/web/src/hooks/useAiUsage.ts, apps/web/src/hooks/usePermissions.ts, apps/web/src/hooks/useTabMeta.ts
Deleted useEditingStore dependencies and removed isPaused/onSuccess gating from SWR options so revalidation is no longer blocked by editing state.
Hooks — self-healing & return-shape change
apps/web/src/hooks/useBreadcrumbs.ts, apps/web/src/hooks/usePageTree.ts
Added SWR cache-driven stuck-state detection with 3s debounce and max 2 retries (clear cache + mutate); set revalidateOnFocus: false. useBreadcrumbs now returns { breadcrumbs, isLoading, isError }.
Tests — SWR mocks & expectations
apps/web/src/hooks/__tests__/usePermissions.test.ts, apps/web/src/hooks/__tests__/useBreadcrumbs.test.ts
Removed editing-store mocks/tests; updated SWR mocks to include isValidating and useSWRConfig cache; adjusted assertions for revalidateOnFocus and absence of isPaused.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Component
  participant Hook as useBreadcrumbs/usePageTree
  participant SWR
  participant Cache as "useSWRConfig Cache"
  participant Timer

  Client->>Hook: render / request data
  Hook->>SWR: useSWR(key, fetcher, { revalidateOnFocus:false, ... })
  SWR->>Cache: read(key)
  alt cache has data
    SWR-->>Hook: return data
  else valid key but no data/error and not validating
    Hook->>Hook: detect stuck state (no data, no error, !isValidating)
    Hook->>Cache: delete(key)
    Hook->>Timer: start 3s debounce
    Timer-->>Hook: debounce elapsed
    Hook->>SWR: mutate(key) — trigger refetch
    SWR->>Cache: write(data) / return error
    SWR-->>Hook: return updated state
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I nibbled stale sessions from the ground,
I wake the cache when silence is found,
No more pauses while you type or stream,
Fetches dance back into the beam,
Hooray — fresh state, quick and sound!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 directly addresses the main problem: fixing skeleton loading stuck on Desktop/Capacitor by removing isPaused guards that were blocking initial fetches due to async auth timing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/swr-isPaused-skeleton-loading

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/web/src/hooks/__tests__/useBreadcrumbs.test.ts (1)

125-146: Good coverage of the expanded return shape (isLoading, isError, breadcrumbs).

Loading, error, and empty-array edge cases are all validated. One gap: there's no test exercising the self-healing / stuck-retry logic mentioned in the PR summary and AI summary (the mechanism that uses useSWRConfig cache). If that path has meaningful branching, consider adding a test that simulates a stale cache entry to verify the retry fires.

Also applies to: 148-167, 189-199


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Replace tests that asserted isPaused existence with tests that verify
isPaused is NOT configured, revalidateOnFocus is false, and
dedupingInterval is set correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@2witstudios
2witstudios force-pushed the fix/swr-isPaused-skeleton-loading branch from a98c850 to 5a64dae Compare February 8, 2026 02:59
useBreadcrumbs now uses useSWRConfig() for the self-healing stuck-retry
logic. Add the missing export to the SWR mock so tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@2witstudios
2witstudios merged commit 079882e into master Feb 8, 2026
3 checks passed
@2witstudios
2witstudios deleted the fix/swr-isPaused-skeleton-loading branch March 11, 2026 03:13
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.

1 participant