Skip to content

fix: モバイルでタブ切り替え時のオフライン誤検知を防止 (#137) - #150

Draft
kuu13580 wants to merge 1 commit into
developfrom
fix/issue137_mobile-tab-offline
Draft

fix: モバイルでタブ切り替え時のオフライン誤検知を防止 (#137)#150
kuu13580 wants to merge 1 commit into
developfrom
fix/issue137_mobile-tab-offline

Conversation

@kuu13580

@kuu13580 kuu13580 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

概要

Issue #137 の修正。モバイルでブラウザのタブを切り替えて戻ると、実際はオンラインなのにオフラインモードになってしまう(自動判定の誤検知)事象を防ぐ。

根本原因

  • タブ復帰時 visibilitychange(visible) で evaluateNetwork() が実行される
  • このとき RTT しきい値は初回値の 5000ms。モバイルの無線復帰直後は /health への fetch RTT がこれを超えやすく、rtt-exceeded で誤オフライン判定される
  • iOS Safari 等で online イベントが発火しないケースでは visibilitychange だけが頼りになり、厳しい初回しきい値がそのまま効いてしまう

修正内容

  • evaluateNetwork(store, { allowRetry }) オプションを追加
  • オンライン→オフラインへ反転しそうな場合のみRESUME_RETRY_DELAY_MS(1500ms) 待機してから緩和しきい値(10000ms)で 1度だけ再判定 してから状態を確定する
    • 待機中に navigator.onLinefalse になった場合はオフライン確定(ブラウザ確定の信号を優先)
    • navigator-offline(ブラウザがオフライン確定)は再判定せず即オフライン
  • main.tsxvisibilitychange / online ハンドラで { allowRetry: true } を指定
  • 起動時・offline イベントの挙動は従来通り(変更なし)
  • ユーザーが手動設定する強制オフライン (forcedOffline) は別経路のため影響なし

テスト

networkDetection.test.ts に allowRetry のケースを追加:

  • 初回 rtt-exceeded でも再判定でオンラインなら誤オフライン判定しない
  • 再判定でも失敗すれば最終的にオフライン判定する
  • 待機中に navigator.onLine が false になればオフライン確定
  • navigator-offline は再判定せず即オフライン
  • 以前オフラインだった場合は再判定しない

確認

  • pnpm run type-check
  • pnpm run lint:check
  • pnpm run format:check
  • pnpm test ✅ (15 files / 139 tests pass)

🤖 Generated with Claude Code

Summary by CodeRabbit

Bug Fixes

  • タブ復帰や再接続時のネットワーク状態判定を改善し、誤ったオフライン判定を減らしました。判定失敗時に一度待機してから再評価する仕組みを追加しました。

Tests

  • ネットワーク検出のリトライロジックと各種エッジケースに関するテストを拡充しました。

モバイルでタブを切り替えて戻った際、visibilitychange でのネットワーク
判定が初回しきい値(5000ms)で実行され、無線復帰遅延により実際はオンライン
でも誤ってオフライン判定されることがあった。

- evaluateNetwork に allowRetry オプションを追加
- タブ復帰(visibilitychange)/再接続(online)時、オンライン→オフラインへ
  反転しそうな場合に少し待ってから緩和しきい値(10000ms)で1度だけ再判定
- navigator-offline はブラウザ確定のため再判定せず即オフライン
- 強制オフライン(forcedOffline)は従来通り別経路で維持

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kuu13580
kuu13580 requested a review from a team as a code owner June 13, 2026 20:28
@kuu13580
kuu13580 requested review from bobtaroh and koki-fore June 13, 2026 20:28
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

タブ復帰や再接続時にネットワーク状態が誤検知される問題に対処するため、RTT判定のしきい値をパラメータ化し、特定の条件下で遅延後に緩和したしきい値での再判定を行うリトライ機構を実装。イベントハンドラからこのオプションを指定して利用。

Changes

ネットワーク誤検知対策とリトライ機構

Layer / File(s) Summary
定数とRTTしきい値パラメータ化
frontend/src/lib/networkDetection.ts
RESUME_RETRY_DELAY_MS定数とdelay関数を追加し、checkNetworkStatusthresholdMs引数を加えてRTT判定のしきい値を動的に指定可能にした。
evaluateNetwork リトライ実装
frontend/src/lib/networkDetection.ts
EvaluateNetworkOptions型とallowRetryフラグを導入し、navigator-offline以外のオフライン判定時に遅延後に緩和しきい値で再判定するリトライ経路を追加。
onlineとvisibilitychangeイベント統合
frontend/src/main.tsx
onlineイベントとvisibilitychange(visible状態)でevaluateNetwork呼び出しにallowRetry: trueを付与してリトライ機構を有効化。
リトライシナリオのテスト
frontend/src/lib/networkDetection.test.ts
useFakeTimers/afterEachを導入し、初回失敗後の成功、再判定での失敗、navigator.onLineの変化、既存のoffline状態など複数シナリオを検証。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 ウサギは網を見張る鋭い目で、
タブ休眠の瞬間も見逃さぬ。
リトライ待機、しきい値の調和、
ネットは不安定でも、誠実に再判定!
一千五百ミリ秒の優雅な再チェック 🌐✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは「モバイルでタブ切り替え時のオフライン誤検知を防止」として、変更の主な目的を明確かつ簡潔に表現しており、ファイル一覧やノイズなく、スキャン時に主要な変更内容を理解できる。
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue137_mobile-tab-offline

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.

@github-actions

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 47ad644):

https://tabi-share-8ef6b--pr150-fix-issue137-mobile-qrpkqgtf.web.app

(expires Sat, 20 Jun 2026 20:29:38 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 9f2a87ede127df7673322845e34cf22c1372d720

@kuu13580
kuu13580 marked this pull request as draft June 13, 2026 20:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@frontend/src/lib/networkDetection.ts`:
- Around line 107-123: Concurrent evaluateNetwork calls can race so a delayed
retry may overwrite a newer result; add a per-call sequence guard to ensure only
the latest invocation writes state. Implement by generating a
monotonically-incrementing token (e.g. local seq number or timestamp) at the
start of evaluateNetwork, store it in a module-scoped variable like
currentEvalSeq, and capture it in the async flow; before applying the final
result (after delay/recheck and before updating isOfflineAtom or any store
writes), compare the captured token with currentEvalSeq and bail out if it no
longer matches. Update only inside the block where token matches so older/slow
evaluations cannot overwrite newer ones; keep existing logic around
options.allowRetry, RESUME_RETRY_DELAY_MS, checkNetworkStatus and
navigator.offLine unchanged except for this guard.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 07af1b07-646f-4759-8868-c53693025254

📥 Commits

Reviewing files that changed from the base of the PR and between 7c57ad7 and 47ad644.

📒 Files selected for processing (3)
  • frontend/src/lib/networkDetection.test.ts
  • frontend/src/lib/networkDetection.ts
  • frontend/src/main.tsx

Comment on lines +107 to +123
export const evaluateNetwork = async (
store: ReturnType<typeof createStore>,
options: EvaluateNetworkOptions = {}
): Promise<void> => {
const wasPreviouslyOffline = store.get(isOfflineAtom);
const result = await checkNetworkStatus();
let result = await checkNetworkStatus();

// タブ復帰・再接続時の誤検知対策:
// それまでオンラインだったのに navigator.onLine は true のままヘルスチェックだけ
// 失敗した場合(モバイル無線の復帰遅延など)、少し待ってから緩和しきい値で再判定する。
// navigator-offline はブラウザ確定のオフラインなので再判定しない。
if (options.allowRetry && !wasPreviouslyOffline && result.isOffline && result.reason !== 'navigator-offline') {
await delay(RESUME_RETRY_DELAY_MS);
result = navigator.onLine
? await checkNetworkStatus(undefined, RELAXED_RTT_THRESHOLD_MS)
: { isOffline: true, reason: 'navigator-offline' };
}

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 | ⚡ Quick win

並行実行で古いネットワーク判定が上書きされる競合があります。

Line 118-123 の待機付き再判定により、evaluateNetwork が重複起動すると完了順が逆転し、古い呼び出しが後から isOfflineAtom を上書きできます(online / visibilitychange の同時発火経路で再現可能)。最新リクエストのみ反映するガードを入れてください。

差分案
+let latestEvaluateRequestId = 0;
+
 export const evaluateNetwork = async (
   store: ReturnType<typeof createStore>,
   options: EvaluateNetworkOptions = {}
 ): Promise<void> => {
+  const requestId = ++latestEvaluateRequestId;
   const wasPreviouslyOffline = store.get(isOfflineAtom);
   let result = await checkNetworkStatus();
@@
   if (options.allowRetry && !wasPreviouslyOffline && result.isOffline && result.reason !== 'navigator-offline') {
     await delay(RESUME_RETRY_DELAY_MS);
     result = navigator.onLine
       ? await checkNetworkStatus(undefined, RELAXED_RTT_THRESHOLD_MS)
       : { isOffline: true, reason: 'navigator-offline' };
   }
+
+  // より新しい evaluateNetwork が開始済みなら、この結果は破棄する
+  if (requestId !== latestEvaluateRequestId) return;
 
   const changed = wasPreviouslyOffline !== result.isOffline;
🤖 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 `@frontend/src/lib/networkDetection.ts` around lines 107 - 123, Concurrent
evaluateNetwork calls can race so a delayed retry may overwrite a newer result;
add a per-call sequence guard to ensure only the latest invocation writes state.
Implement by generating a monotonically-incrementing token (e.g. local seq
number or timestamp) at the start of evaluateNetwork, store it in a
module-scoped variable like currentEvalSeq, and capture it in the async flow;
before applying the final result (after delay/recheck and before updating
isOfflineAtom or any store writes), compare the captured token with
currentEvalSeq and bail out if it no longer matches. Update only inside the
block where token matches so older/slow evaluations cannot overwrite newer ones;
keep existing logic around options.allowRetry, RESUME_RETRY_DELAY_MS,
checkNetworkStatus and navigator.offLine unchanged except for this guard.

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