Skip to content

canary - #6971

Merged
TinyKitten merged 10 commits into
canaryfrom
dev
Sep 14, 2026
Merged

canary#6971
TinyKitten merged 10 commits into
canaryfrom
dev

Conversation

@TinyKitten

Copy link
Copy Markdown
Member

概要

dev の内容を canary へ昇格する。含まれる変更は #6970 / #6969 / #6966 / #6965 の 4 件と、v10.15.1 リリース(master からの同期)。semver は 10.15.0 → 10.15.1 に上がる。

変更の種類

  • バグ修正
  • 新機能
  • リファクタリング
  • ドキュメント
  • CI/CD
  • その他

変更内容

  • iOSで変位ゲートにより測位配信が途絶える区間を補完測位で更新し続けるようにする (iOSで変位ゲートにより測位配信が途絶える区間を補完測位で更新し続けるようにする #6969)
    • iOS は distanceInterval = 10CLLocationManager.distanceFilter)だけが配信を決め、timeInterval が無視されるため、地下鉄で座標が張り付くと locationAtom が凍結していた
    • 補完測位フック useLocationHeartbeat を追加し、継続測位が LOCATION_HEARTBEAT_STALE_THRESHOLD(10 秒 = Android が変位に依らず確保している更新間隔)を超えて途絶えたときだけ getCurrentPositionAsync を 1 回叩く
    • src/screens/Main.tsx では NEEDS_LOCATION_HEARTBEAT が真のときだけ FxLocationHeartbeat をマウントする(レンダーレス、表示への影響なし)
  • 地下鉄の電波環境をGPXに記録できるようにする (地下鉄の電波環境をGPXに記録できるようにする #6966)
    • GPX の <extensions> に独自要素 trainlcd:accuracy を追加し、ウェイポイントごとの水平精度(m)を記録・再生できるようにした
    • 副都心線経由の実走ログ assets/gpx/FLinerSeibu.gpx を追加(開発者向けフィクスチャ。require() されないため Metro のバンドルには入らない)
    • setLocation の地下鉄分岐(lineType === Subway かつ isAccuracyStable が false)を実データで踏めるようになった
  • Firebase Test LabのRoboテスト専用ワークフローを追加し、未配線のMaestroフローを削除 (Firebase Test LabのRoboテスト専用ワークフローを追加し、未配線のMaestroフローを削除 #6970)
    • .github/workflows/test_lab_robo.yml を新規追加。Build Android Canary が上げた app-devRelease アーティファクトを run-id 指定で取得するだけでビルドはしない
    • 未配線で動作しない Maestro フロー Flow.yaml を削除
  • 成果物をFable 5.1にローカルレビューさせるスキルを追加 (成果物をFable 5.1にローカルレビューさせるスキルを追加 #6965)
    • .claude/skills/review-with-fable/SKILL.md を追加。アプリのコードには触れていない
  • v10.15.1 リリースぶんの masterdev 同期 (v10.15.1🎉 #6955 / dev<-master #6968)
    • semver 10.15.1、iOS MARKETING_VERSION / Android versionName を更新

テスト

  • npm run lint が通ること
  • npm test が通ること
  • npm run typecheck が通ること

各変更は dev へマージされる前にレビューと CI を通過済み。

canary で確認したいこと

  • iPhone で地下鉄区間(東京メトロ副都心線・都営大江戸線など)を走り、最寄り駅・到着判定・ヘッダーが 10 秒程度の間隔で更新され続けるか。これが iOSで変位ゲートにより測位配信が途絶える区間を補完測位で更新し続けるようにする #6969 の主眼で、実機の iOS でしか再現しない
  • 上記の補完測位が前景・走行中に限って働き、バックグラウンドや停止中にバッテリーを余計に食っていないか
  • Android 側の測位挙動に退行が無いか(NEEDS_LOCATION_HEARTBEAT が偽なのでフックはマウントされない想定)

関連Issue

#6970#6969#6966#6965

スクリーンショット(任意)

UI 変更なし: 含まれる変更は測位パイプライン(src/hooks/** / src/utils/** / src/constants/**)、CI ワークフロー、ドキュメント、開発者向け GPX フィクスチャのみです。src/screens/Main.tsx の差分はレンダーレスな FxLocationHeartbeat を条件付きでマウントするだけで、画面レイアウトや表示文言には変化がありません。

🤖 Generated with Claude Code

https://claude.ai/code/session_01PezZM56jUs2qMBnpru5kTS

TinyKitten and others added 9 commits September 12, 2026 08:46
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153nzW9kBfL71DcfeQR3beo
* iOSで変位ゲートにより測位配信が途絶える区間を補完測位で更新し続けるようにする

iOSはtimeIntervalが無視され変位ゲート(distanceFilter=10m)だけが配信を決めるため、
地下鉄のようにGPSを失い測位が同じ座標へ張り付く区間では配信が完全に止まり、位置・
最寄り駅・到着判定・ヘッダーがまとめて凍結する。Androidは変位ゲートが0でtimeInterval
が効くため同じ区間でも10秒ごとに届く。これが「Androidでは地下鉄でも更新されるのに
iPhoneでは更新されない」の原因。

変位ゲートを0へ戻す選択は取らない。iOSでは約1Hz配信となり電池を著しく消費するため
実車検証を経て10mが選ばれている(#6470)。代わりに、継続測位の配信が10秒途絶えたときだけ
getCurrentPositionAsyncで測位を取りに行く補完測位をiOSにだけ追加し、Androidが変位に
依らず確保している更新間隔を下回らないようにする。地上走行中は10mを数秒で超えるため
一度も発火せず、#6470が狙った停車中の取りこぼし対策も維持したまま補える。

取得した測位はhandleTrackingLocationへ通すため、重複排除・精度フィルタ・EMA・速度
フィルタの扱いは継続測位と完全に同じになる。応答が返らない取得でガードが解けず補完が
止まらないよう、間隔3回ぶんを超えた要求は見切る。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sZFS43kKpMv5HjadBkF3x

* 補完測位の取得間隔を実測10秒に直し前景・権限あり・非省電力に限定する

ローカルレビュー(Fable)で見つかった4点への対応。

1. 取得間隔が実測20秒だった。固定間隔のタイマーで点検すると、補完測位が取り込んだ
   測位が途絶タイマーを巻き戻すぶん必ず1回空振りし、実際の取得が閾値の2倍まで開く。
   点検を固定間隔から「最後の配信から途絶時間が経つ時刻」への置き直しへ変更し、
   応答を受け取った時点でも次の点検を張り直す。取得間隔を実物の
   handleTrackingLocationごと測る回帰テストを別ファイルで追加した(固定間隔の実装では
   60秒あたり3回しか取得せず落ちる)。

2. 背景で誤発火していた。expo-locationのタスクは前景でだけ測位を即時報告し、それ以外は
   deferredUpdatesInterval(10秒)ぶん貯めてから報告するため、背景では正常時も配信間隔が
   途絶時間以上になり地上でも発火する。加えて一発取得に使われるCLLocationManagerは
   allowsBackgroundLocationUpdatesを立てないので背景では測位を受け取れない。
   useIsAppActiveで前景に限定する。

3. 前景権限が無い利用者(「許可せずに開始」)で取得の失敗を繰り返していた。effect開始時に
   getForegroundPermissionsAsyncで確認し、未許可なら点検を張らない。effectは前景復帰の
   たびに張り直されるため、あとから許可された場合もそこで拾う。

4. 省電力測位プロファイル中は補完測位を動かさない。このプロファイルはiOSで停車中の
   測位休止(pausesUpdatesAutomatically)を許可しており(#6395)、補完測位はその休止を
   そのまま打ち消すため。電池優先という設定の意図を尊重し、省電力中は凍結を許容する。
   選択肢(受け入れて明記/間隔を伸ばす/無効化)のうち無効化をリポジトリオーナーが選択。

あわせて、抽出したuseLocationProfileの参照安定性(崩れると測位タスクが再レンダーごとに
停止→再開する)と、遅延返答の取り込み・端末省電力モードの分岐にテストを追加した。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sZFS43kKpMv5HjadBkF3x

* 補完測位の初回点検を最後の配信時刻に合わせ時計巻き戻しで点検が飛ぶのを防ぐ

CodeRabbitの指摘への対応。権限確認後の初回スケジュールが常に途絶時間ぶん待っていたため、
既に途絶した状態で前景へ戻ったとき(地下でアプリを開き直した等)に取得までさらに10秒遅れて
いた。待つかどうかの判断をcheckへ一本化し、初回は即座にcheckへ入れる。未配信(0)のときだけ
effect開始時刻を基準にして途絶時間ぶん待ち、起動直後に継続測位と一発取得が二重に走るのを
避ける。

あわせて、経過時間が負になる場合(端末の時計の巻き戻し)を途絶として扱う。残り時間として
使うと巻き戻し幅ぶん点検が先送りされ、補完測位が止まる。handleTrackingLocationが
CLOCK_ROLLBACK_TOLERANCE_MSで同じ事故を防いでいるのと同じ理由。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sZFS43kKpMv5HjadBkF3x

* 経過時間の計測を単調増加時計へ移し端末時計の変更で補完測位が乱れないようにする

CodeRabbitの指摘への対応。補完測位の途絶判定・保留見切りはどちらも「経過時間」の判定
なのに端末の時計(Date.now)で測っていた。時計が巻き戻ると残り時間が巻き戻し幅ぶん伸びて
点検が止まり、進むと保留中の要求を早く見切って重複要求を出す。

src/utils/monotonicNow.ts を追加し、記録側(handleTrackingLocation)と判定側
(useLocationHeartbeat)を同じ時計へ揃える。React Nativeはglobal.performanceを必ず
用意するため、performance.nowを使い、無い環境ではDate.nowへ退避する。

あわせて、記録の公開APIを「時刻」から「経過時間」へ変えた(getLastTrackedLocationAtMs
→ getMsSinceLastTrackedLocation)。時刻を返すと呼び出し側が別の時計と引き算できてしまい、
同じ事故を再発させられるため。未配信はnullで表す。

フォールバックでDate.nowが使われる環境では単調性が保証されないため、負の経過時間を
「基準が信用できない」として扱う既存のガードは残す。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sZFS43kKpMv5HjadBkF3x

* 保留中に経過時間が負になっても補完測位の要求を重ねないようにする

CodeRabbitの指摘への対応。取得の応答を待っている間に経過時間が負になると
(monotonicNowがDate.nowへフォールバックした環境での時計の巻き戻し)、保留の判定から
外れて1件目の応答を待たないまま2件目の要求が走っていた。

負でも「取得中」であることは変わらないので保留として扱う。ただし残り時間をそのまま
使うと見切りが巻き戻し幅ぶん先送りされる(応答が返らない要求で補完測位が止まる)ため、
待ち時間は見切り時間で頭打ちにする。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sZFS43kKpMv5HjadBkF3x

* 補完測位の見切りを時計に依存しないタイマーへ移す

CodeRabbitの指摘への対応。保留の見切りを経過時間の計算で行っていたため、
monotonicNowがDate.nowへフォールバックした環境で時計が巻き戻ったまま応答が返らないと、
経過時間が負のままで見切りが成立せず、補完測位が止まる可能性があった。

要求のたびに見切りタイマー(setTimeout)を張り、応答時とアンマウント時に解除する。
見切り時は要求識別子を進めてから次の点検へ入るので、見切った要求が後から返っても
現役の要求のガードは触られない。保留中の点検は何もしない(次を置くのは応答のfinallyか
見切りタイマー)ため、保留の長さを決めるのは時計ではなくタイマーだけになる。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sZFS43kKpMv5HjadBkF3x

---------

Co-authored-by: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Essentials

Run ID: 0048e242-c9fb-4718-a0c7-ab3a1ad70a3b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@TinyKitten
TinyKitten merged commit 3048496 into canary Sep 14, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants