Skip to content

fix(web): add Safari fallback for requestIdleCallback#9137

Open
KanteshMurade wants to merge 1 commit into
makeplane:previewfrom
KanteshMurade:fix-safari-requestidlecallback-clean
Open

fix(web): add Safari fallback for requestIdleCallback#9137
KanteshMurade wants to merge 1 commit into
makeplane:previewfrom
KanteshMurade:fix-safari-requestidlecallback-clean

Conversation

@KanteshMurade
Copy link
Copy Markdown
Contributor

@KanteshMurade KanteshMurade commented May 25, 2026

Description

Fixes a Safari/iOS crash caused by unguarded usage of window.requestIdleCallback in the project layout/gantt view.

Safari does not currently support requestIdleCallback by default, which caused the following runtime error:

TypeError: window.requestIdleCallback is not a function

This PR adds a shared compatibility fallback for:

  • requestIdleCallback
  • cancelIdleCallback

The polyfill is installed during client startup before React hydration, while preserving native behavior in Chrome and Firefox.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

N/A

Test Scenarios

  • Ran web lint checks successfully
  • Verified Safari fallback executes when requestIdleCallback is unavailable
  • Confirmed no unsafe direct usages remain outside shared compatibility layer

References

Closes #9134

Summary by CodeRabbit

  • Chores
    • Enhanced browser compatibility by implementing comprehensive fallback mechanisms for idle task scheduling, ensuring the application maintains consistent and reliable functionality across different browser versions and environments, regardless of native API availability.
    • Improved code organization and maintainability by centralizing and restructuring how the application initializes compatibility features during startup, providing a cleaner approach to managing cross-browser support.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

📝 Walkthrough

Walkthrough

The PR installs a requestIdleCallback/cancelIdleCallback polyfill at app bootstrap to prevent Safari compatibility crashes. idle-task.ts exports requestIdle/cancelIdle wrappers with setTimeout fallbacks; polyfills/index.ts invokes the installer at module load; entry.client.tsx imports polyfills before React hydration.

Changes

Safari requestIdleCallback Polyfill Setup

Layer / File(s) Summary
Idle callback abstraction and polyfill installation
apps/web/core/lib/idle-task.ts
Adds requestIdle/cancelIdle wrappers preferring native APIs and falling back to setTimeout. Exports installIdleCallbackPolyfill to assign these wrappers to window when native APIs are missing. Updates runIdleTask to use requestIdle and accept IdleRequestCallback type.
Polyfill module initialization
apps/web/core/lib/polyfills/index.ts
Imports installIdleCallbackPolyfill, invokes it at module load to install global requestIdleCallback/cancelIdleCallback when absent, and exports default true.
Client entry integration
apps/web/app/entry.client.tsx
Imports and executes the polyfills module for side effects before starting React hydration, ensuring the polyfill is globally available before any component code runs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • makeplane/plane#9134: This PR directly addresses the Safari/iOS crash caused by unguarded window.requestIdleCallback calls in the gantt-layout-loader by installing a global polyfill at app bootstrap.

Possibly related PRs

  • makeplane/plane#9094: Both PRs modify the shared idle-callback plumbing in apps/web/core/lib/idle-task.ts, introducing requestIdleCallback/cancelIdleCallback fallbacks and the updated runIdleTask signature that this PR depends on.

Suggested reviewers

  • sriramveeraghanta
  • codingwolf-at

Poem

🐰 A rabbit hops through browser lands,
Where Safari's caught without its stands,
With polyfills now in place and blessed,
requestIdleCallback passes the test!
No more crashes—all browsers rest.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(web): add Safari fallback for requestIdleCallback' accurately describes the main change—adding a Safari compatibility fallback for requestIdleCallback—which directly addresses the Safari crash issue.
Description check ✅ Passed The PR description covers all required template sections: detailed description of the Safari/iOS crash and fix, Type of Change properly selected (bug fix), Test Scenarios documented, and References linking to issue #9134.
Linked Issues check ✅ Passed The PR fully addresses issue #9134 by implementing a shared requestIdleCallback/cancelIdleCallback polyfill installed during client startup before React hydration, preventing the Safari TypeError and ensuring the project layout view renders correctly.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Safari requestIdleCallback polyfill: entry.client.tsx imports polyfills at startup, idle-task.ts centralizes idle callback handling with fallbacks, and polyfills/index.ts installs the polyfill—no unrelated modifications present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/core/lib/idle-task.ts`:
- Around line 11-37: The fallback implementations and availability checks should
use globalThis instead of window to avoid runtime errors when window is
undefined: update requestIdleFallback and cancelIdleFallback to call
globalThis.setTimeout and globalThis.clearTimeout, and change the runtime checks
in requestIdle and cancelIdle to use typeof globalThis !== "undefined" and
typeof globalThis.requestIdleCallback / globalThis.cancelIdleCallback ===
"function" so you only call globalThis.requestIdleCallback(globalCallback,
options) when available and otherwise return requestIdleFallback(callback) /
cancelIdleFallback(id).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2707e4a8-912b-4ba0-864a-2c084b23bf1d

📥 Commits

Reviewing files that changed from the base of the PR and between e71a8f5 and b6e7368.

📒 Files selected for processing (3)
  • apps/web/app/entry.client.tsx
  • apps/web/core/lib/idle-task.ts
  • apps/web/core/lib/polyfills/index.ts

Comment on lines +11 to +37
const requestIdleFallback = (callback: IdleRequestCallback): number => {
const start = Date.now();

return window.setTimeout(() => {
callback({
didTimeout: false,
timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
});
}, 1);
};

const cancelIdleFallback = (id: number) => {
window.clearTimeout(id);
};

export const requestIdle = (callback: IdleRequestCallback, options?: IdleRequestOptions): number => {
if (typeof window !== "undefined" && typeof window.requestIdleCallback === "function")
return window.requestIdleCallback(callback, options);

return requestIdleFallback(callback);
};

export const cancelIdle = (id: number) => {
if (typeof window !== "undefined" && typeof window.cancelIdleCallback === "function")
return window.cancelIdleCallback(id);

return cancelIdleFallback(id);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use globalThis timers in fallbacks to prevent no-window runtime crashes.

The fallback path is selected when window is unavailable, but both fallbacks still dereference window, which can throw immediately in that environment.

Suggested fix
-const requestIdleFallback = (callback: IdleRequestCallback): number => {
+const requestIdleFallback = (
+  callback: IdleRequestCallback,
+  options?: IdleRequestOptions
+): number => {
   const start = Date.now();
 
-  return window.setTimeout(() => {
+  return globalThis.setTimeout(() => {
     callback({
       didTimeout: false,
       timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
     });
-  }, 1);
+  }, options?.timeout ?? 1);
 };
 
 const cancelIdleFallback = (id: number) => {
-  window.clearTimeout(id);
+  globalThis.clearTimeout(id);
 };
 
 export const requestIdle = (callback: IdleRequestCallback, options?: IdleRequestOptions): number => {
   if (typeof window !== "undefined" && typeof window.requestIdleCallback === "function")
     return window.requestIdleCallback(callback, options);
 
-  return requestIdleFallback(callback);
+  return requestIdleFallback(callback, options);
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/core/lib/idle-task.ts` around lines 11 - 37, The fallback
implementations and availability checks should use globalThis instead of window
to avoid runtime errors when window is undefined: update requestIdleFallback and
cancelIdleFallback to call globalThis.setTimeout and globalThis.clearTimeout,
and change the runtime checks in requestIdle and cancelIdle to use typeof
globalThis !== "undefined" and typeof globalThis.requestIdleCallback /
globalThis.cancelIdleCallback === "function" so you only call
globalThis.requestIdleCallback(globalCallback, options) when available and
otherwise return requestIdleFallback(callback) / cancelIdleFallback(id).

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.

[bug]: Opening a project crashes in Safari/iOS — window.requestIdleCallback is not a function (gantt-layout-loader, unguarded)

1 participant