Skip to content

perf(mobile): pause relative-time clocks while backgrounded - #9886

Merged
nwparker merged 1 commit into
mainfrom
nwparker/perf-mobile-clock-gate
Jul 22, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/perf-mobile-clock-gate

Conversation

@nwparker

@nwparker nwparker commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

What

The mobile useNow hook drives relative-time labels ("2m ago") by re-rendering on a fixed interval — the host list every 30s, Accounts every 60s. It kept ticking while the app was backgrounded and for retained-but-covered routes, forcing re-renders (e.g. a whole host-list render) that nobody could see.

Fix: make useNow AppState-aware — the interval runs only while the app is active, and it ticks once immediately on returning to active so labels are correct on resume. A single shared, ref-counted AppState listener (via useSyncExternalStore) serves all useNow callers rather than one listener per hook instance. An optional enabled flag lets a routed screen (Accounts) also pause while unfocused via its existing focus hook.

ELI5

Your phone kept re-drawing "X minutes ago" timestamps every 30–60 seconds even while the app was in your pocket. Now those clocks pause while the app is backgrounded and instantly refresh when you reopen it.

Proof of zero regression

  • Only relative-time/countdown labels are affected, and they correct immediately on foreground (an explicit tick on the inactive→active transition); the interval value and displayed values while active are unchanged, and the numeric return shape is identical so callers don't change.
  • The shared AppState listener is removed after the last useNow instance unmounts (ref-counted).
  • Tests: use-now.test.ts 3/3 (active updates, background suspension, immediate foreground refresh, focus disable/re-enable, listener cleanup) and full mobile suite green. typecheck / oxlint / oxfmt --check clean.

Proof of perf improvement

Stops the 30s/60s forced re-renders (including whole host-list renders) while the app is backgrounded or the route is covered — low per-tick cost but broad and steady, and fully parked in the background.

Made with Orca 🐋

Co-authored-by: Orca <help@stably.ai>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Updated useNow to pause interval updates when the app is inactive or the hook is disabled, refresh immediately when updates resume, and share one AppState listener across callers. Added tests for timing, enablement, app-state transitions, and listener cleanup. Updated AccountsScreen to enable the clock only while focused.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is useful, but it doesn't follow the required template and omits Screenshots, AI Review Report, Security Audit, and Notes sections. Rewrite it using the repo template: add Summary, Screenshots, Testing checklist, AI Review Report, Security Audit, and Notes, and confirm cross-platform review.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: pausing relative-time clocks while the mobile app is backgrounded.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (3)
mobile/src/hooks/use-now.ts (1)

4-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the non-obvious lifecycle constraints.

  • mobile/src/hooks/use-now.ts#L4-L25: Add a brief Why: comment explaining why AppState ownership is shared and ref-counted.
  • mobile/src/hooks/use-now.test.ts#L5-L21: Explain why the AppState mock must use vi.hoisted.
  • mobile/app/h/[hostId]/accounts.tsx#L44-L53: Explain why reset-label updates are enabled only while this route is focused.

As per coding guidelines, “For non-obvious TypeScript code, add a concise comment explaining why the constraint exists, not how the code works.”

Source: Coding guidelines

mobile/src/hooks/use-now.test.ts (2)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid adding deprecated react-test-renderer coverage.

React 19 deprecates this renderer, and the test suppresses its warning. Port this to the repository’s supported testing-library setup and remove the suppression.

Also applies to: 50-56


95-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test the multi-caller ref-count contract.

This only proves cleanup for one caller. Mount two harnesses, unmount one and assert remove was not called; then unmount the last and assert it was called once.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a46ac347-96d9-4f02-899b-3fac39a2ad1c

📥 Commits

Reviewing files that changed from the base of the PR and between e600600 and ccee97d.

📒 Files selected for processing (3)
  • mobile/app/h/[hostId]/accounts.tsx
  • mobile/src/hooks/use-now.test.ts
  • mobile/src/hooks/use-now.ts

@nwparker
nwparker merged commit a3d6f84 into main Jul 22, 2026
3 checks passed
@nwparker
nwparker deleted the nwparker/perf-mobile-clock-gate branch July 22, 2026 21:13
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
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