Skip to content

fix(web): add requestIdleCallback fallback for Safari/iOS#9094

Open
bubacho wants to merge 2 commits into
makeplane:previewfrom
bubacho:fix/safari-request-idle-callback
Open

fix(web): add requestIdleCallback fallback for Safari/iOS#9094
bubacho wants to merge 2 commits into
makeplane:previewfrom
bubacho:fix/safari-request-idle-callback

Conversation

@bubacho
Copy link
Copy Markdown

@bubacho bubacho commented May 16, 2026

Summary

Fixes a runtime crash on Safari/iOS where window.requestIdleCallback is not available.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature
  • Breaking change
  • Documentation update

Problem

On iPhone Safari, opening a project issues page can crash with:
TypeError: window.requestIdleCallback is not a function.

The crash happens in RenderIfVisible and can break project/issue views.

Changes

  • Added a small runIdleTask helper:
    • uses window.requestIdleCallback when available
    • falls back to globalThis.setTimeout(..., 0) otherwise
  • Replaced direct window.requestIdleCallback calls with runIdleTask
  • Fixed browser check to typeof window !== "undefined"
  • Updated IntersectionObserver cleanup to unobserve a stable target reference
  • Removed children from the observer effect dependency list to avoid unnecessary observer recreation

Test scenarios

  1. Open project issues page in iPhone Safari/iOS -> no crash, page renders normally.
  2. Open the same page in Chrome/Firefox -> behavior unchanged.
  3. Trigger scrolling/visibility updates -> lazy render behavior still works.

Screenshots / media

N/A (runtime compatibility fix).

Why this is safe

  • No behavior change for browsers that support requestIdleCallback
  • Graceful fallback for unsupported browsers
  • Keeps lazy rendering logic intact while preventing runtime failures

Summary by CodeRabbit

  • Performance

    • Improved idle-time scheduling to reduce UI jank and provide smoother visibility updates for conditionally rendered content.
    • More efficient height measurements to prevent layout thrashing and improve scroll/column stability.
  • Bug Fixes

    • Fixed intermittent visibility and measurement timing in board/column views so items render more reliably.

Review Change Stack

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 16, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4705747e-57a4-48c1-b2a0-6b6159e9bead

📥 Commits

Reviewing files that changed from the base of the PR and between 73c0d10 and eb0edcd.

📒 Files selected for processing (3)
  • apps/web/core/components/core/render-if-visible-HOC.tsx
  • apps/web/core/components/issues/issue-layouts/kanban/default.tsx
  • apps/web/core/lib/idle-task.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/core/lib/idle-task.ts
  • apps/web/core/components/core/render-if-visible-HOC.tsx

📝 Walkthrough

Walkthrough

Adds a reusable idle-task helper and integrates it into RenderIfVisible: the prop was renamed to useIdleTime; visibility and height updates are scheduled via runIdleTask with cancellation; IntersectionObserver now captures the observed target and unobserves on cleanup; one usage updated.

Changes

Idle task scheduling and observer fixes

Layer / File(s) Summary
Idle task scheduling utility
apps/web/core/lib/idle-task.ts
Adds IdleTaskHandle type and runIdleTask that uses requestIdleCallback with a 300ms timeout or falls back to setTimeout, returning a cancelable handle.
RenderIfVisible: observer integration and prop rename
apps/web/core/components/core/render-if-visible-HOC.tsx, apps/web/core/components/issues/issue-layouts/kanban/default.tsx
RenderIfVisible imports runIdleTask, renames prop to useIdleTime, schedules visibility and height measurements via runIdleTask with cancellation, captures the IntersectionObserver target and unobserves on cleanup; updates one Kanban usage to the new prop name.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 I hop where callbacks wait in line,
I tuck them into idle time,
I cancel when the DOM must race,
I watch, unwatch, and keep the pace,
A rabbit tidies render rhyme.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a requestIdleCallback fallback for Safari/iOS compatibility.
Description check ✅ Passed The description provides detailed context, problem statement, changes made, test scenarios, and safety notes, covering all template sections comprehensively.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

🧹 Nitpick comments (2)
apps/web/core/components/core/render-if-visible-HOC.tsx (2)

26-32: ⚡ Quick win

Harden fallback path to avoid direct window usage.

Line 31 still assumes window exists. If this helper is reused in a non-browser context, it can throw. Prefer globalThis.setTimeout for a truly safe fallback.

Proposed change
 const runIdleTask = (callback: () => void) => {
   if (typeof window !== "undefined" && typeof window.requestIdleCallback === "function") {
     window.requestIdleCallback(callback, { timeout: 300 });
     return;
   }
-  window.setTimeout(callback, 0);
+  globalThis.setTimeout(callback, 0);
 };
🤖 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/components/core/render-if-visible-HOC.tsx` around lines 26 -
32, The fallback in runIdleTask still calls window.setTimeout which can throw in
non-browser contexts; update the fallback to use globalThis.setTimeout (or guard
for typeof globalThis.setTimeout === "function") instead of window.setTimeout,
keeping the existing check for window.requestIdleCallback and leaving
requestIdleCallback usage unchanged; reference the runIdleTask function and the
window.requestIdleCallback branch when making this change.

79-79: ⚡ Quick win

Remove children from observer effect dependencies.

Line 79 unnecessarily ties observer lifecycle to children identity, which can recreate observers on normal rerenders. Keep dependencies to values that affect observer setup.

Proposed change
-  }, [intersectionRef, children, root, verticalOffset, horizontalOffset, useIdletime]);
+  }, [intersectionRef, root, verticalOffset, horizontalOffset, useIdletime]);
🤖 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/components/core/render-if-visible-HOC.tsx` at line 79, The
effect that creates the IntersectionObserver in RenderIfVisibleHOC (the
useEffect that references intersectionRef, root, verticalOffset,
horizontalOffset, useIdletime) should not include `children` in its dependency
array because that causes unnecessary observer teardown/recreation on normal
rerenders; update the dependency array to remove `children` so it only depends
on stable values that affect observer setup (intersectionRef, root,
verticalOffset, horizontalOffset, useIdletime) and ensure any linter warnings
are addressed by keeping referenced variables stable or explicitly documenting
why `children` is excluded.
🤖 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.

Nitpick comments:
In `@apps/web/core/components/core/render-if-visible-HOC.tsx`:
- Around line 26-32: The fallback in runIdleTask still calls window.setTimeout
which can throw in non-browser contexts; update the fallback to use
globalThis.setTimeout (or guard for typeof globalThis.setTimeout === "function")
instead of window.setTimeout, keeping the existing check for
window.requestIdleCallback and leaving requestIdleCallback usage unchanged;
reference the runIdleTask function and the window.requestIdleCallback branch
when making this change.
- Line 79: The effect that creates the IntersectionObserver in
RenderIfVisibleHOC (the useEffect that references intersectionRef, root,
verticalOffset, horizontalOffset, useIdletime) should not include `children` in
its dependency array because that causes unnecessary observer
teardown/recreation on normal rerenders; update the dependency array to remove
`children` so it only depends on stable values that affect observer setup
(intersectionRef, root, verticalOffset, horizontalOffset, useIdletime) and
ensure any linter warnings are addressed by keeping referenced variables stable
or explicitly documenting why `children` is excluded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16539ca9-e76f-4879-bca1-7bd08e2c45ad

📥 Commits

Reviewing files that changed from the base of the PR and between 50a7b47 and 62134f5.

📒 Files selected for processing (1)
  • apps/web/core/components/core/render-if-visible-HOC.tsx

@bubacho bubacho force-pushed the fix/safari-request-idle-callback branch from 62134f5 to d048ed2 Compare May 16, 2026 11:32
@codingwolf-at
Copy link
Copy Markdown

@bubacho A few small suggestions while we're already touching this area:

  • useIdletime could be renamed to useIdleTime for consistency/readability.
  • Since the idle task is async/deferred, it may still execute after unmount or during rapid scroll updates. It might be worth storing the scheduled task id in a ref and cancelling it during cleanup via cancelIdleCallback (with clearTimeout fallback) to avoid stale state updates / queued tasks piling up.
  • runIdleTask also feels generic enough to move into the shared utils folder with a small docstring explaining the function.

@bubacho
Copy link
Copy Markdown
Author

bubacho commented May 19, 2026

@bubacho A few small suggestions while we're already touching this area:

  • useIdletime could be renamed to useIdleTime for consistency/readability.
  • Since the idle task is async/deferred, it may still execute after unmount or during rapid scroll updates. It might be worth storing the scheduled task id in a ref and cancelling it during cleanup via cancelIdleCallback (with clearTimeout fallback) to avoid stale state updates / queued tasks piling up.
  • runIdleTask also feels generic enough to move into the shared utils folder with a small docstring explaining the function.

Thanks for the suggestions — addressed in 73c0d10.

Renamed useIdletime to useIdleTime.
Added cleanup cancellation for scheduled idle work (cancelIdleCallback with clearTimeout fallback) to prevent stale updates / queued tasks after unmount.
Moved runIdleTask to shared utils and added a short doc comment.

@bubacho bubacho force-pushed the fix/safari-request-idle-callback branch from 73c0d10 to eb0edcd Compare May 19, 2026 15:14
@bubacho bubacho requested a review from codingwolf-at May 19, 2026 17:06
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.

3 participants