← 一覧に戻る

study-timer-ops

GitHub ↗ HTML 最終push: 2026/9/30 19:45

WIP(現在進行中)

Work In Progress

このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。

現在の状況

(2026-09-30 時点)

フェーズ: iOS 1.3.1 公開済み(9/18)で、例外は収束(IsSubscribedTo は 1.3.1 で 0 件)。Android 1.3.1 は未リリース。9/29 の Firebase 障害によるクラッシュ警告は Google 側の障害で対応不要(解消済み)。

アプリ本体は CodeCommit master b432c8090(タグ iOS1.3.1)。作業ツリーはクリーン(2026-09-30 確認)。

9/29 の Firebase 障害(対応不要):

  • 9/29 09:41〜11:52 JST、Google Analytics for Firebase iOS のサーバー応答(sdk-exp)の不正ペイロードで、SDK 内部が nil キーで落ちた。勉強タイマーは 40 件 / 40 人(09:46〜11:39 JST に集中、以降 0 件)。Google が SDK 更新なしで修正済み(firebase-ios-sdk#16728)。Thomson 系の他 4 アプリも同じ
  • 「不正なサーバーデータに耐える SDK」を Firebase が後日配布する予定。出たら次のリリースで Firebase Unity SDK(13.4.0)を更新する(急がない。DECISIONS.md 2026-09-30)

1.3.0 → 1.3.1 の要点:

  • 原因: Unity IAP 5.1.2(StoreKit 2)で旧 API getSubscriptionInfo() が null → IsSubscribedTo の NullReference → 加入者の全画面の初期化が停止(記録画面に既定値「4月22日 / 75」、プレミアム消失、0001/01/01 の記録で統計崩壊)
  • 修正: 権利判定を取得注文ベースに作り直し(取得完了待ち・再取得・3 日猶予・復元の反映待ち)、画面初期化を課金判定から分離、不正記録の掃除。詳細は CLAUDE.md「課金実装」と ~/cdev/knowledge/unity-iap5-storekit2-migration-pitfalls.md
  • 見送り: 初回起動で権利が取れない場合の復元案内 UI。ミュート時に音が出ない件は仕様のまま(You 判断)

次にやること:

  1. Crashlytics で 1.3.1 の収束確認 → IsSubscribedTo は 1.3.1 で 0 件を確認済み(2026-09-30。1.3.0 のみ 24 件 / 2 人=1.3.0 のまま使っている人)。残りは billing: のパンくずで「取得が空 → 再取得で復旧」がどの程度起きているか
  2. AVFoundationVideoTextureOutput の EXC_BAD_ACCESS を調査(直近 7 日 23 件 / 20 人、すべて 1.3.1。9/29 の障害を除くと最多)。UnityFramework 内の動画再生の実装で落ちている。アプリで動画を使う箇所は未確認なので、OS・機種の偏りと直前のパンくず(画面)から特定する
  3. Android 1.3.1 のリリース(Android 実機での回帰確認が未実施。背景検知の分岐と、共通変更分の動作)
    • 共有ボタンのクラッシュ修正を同梱する(2026-09-25 記録、未着手): Android 12+ で共有(NativeShare)を開いた瞬間に IllegalArgumentException(PendingIntent に FLAG_MUTABLE / FLAG_IMMUTABLE が無い)で落ちる。vitals(6/1〜9/24)でクラッシュ 1 位・101 件 / 79 ユーザー
    • 原因は Assets/Plugins/NativeShare/Android/NativeShare.aar(2021-05 ビルド)。schulte の修正前 AAR とバイト単位で同一。呼び出し元は SettingTab.cs / MemoInputCompleteAdditiveScene.cs
    • 直し方: ~/cdev/schulte-operation/schulte-unity/Assets/Plugins/NativeShare/Android/NativeShare.aar(修正済み、schulte-unity bf0b855)を同じパスに上書き(.meta はそのまま)→ versionCode を上げてビルド → 16KB エミュ kowai16k で共有シートが開き落ちないことを確認。経緯は schulte-operation の .devnotes(2026-09-24〜25)、差し替えたクラスのソースは schulte-operation/patches/nativeshare-flag-mutable/
  4. 75 分に上書きされた記録の問い合わせ対応(トムソンが手動修正を案内)。「曜日ずれ」の再報告があればスクリーンショットを依頼
  5. 1.3.1 の効果測定: レビュー、GA4 の record_saved / app_return、iOS 課金の推移
  6. Firebase の修正版 SDK が出たら Firebase Unity SDK を更新(#16728 で確認。急がない)
  7. その後、CodeCommit → github.com 移行

未決: 初回起動で 3 回とも権利が取れなかった場合の復元案内 UI を次版に入れるか

運用メモ(今回確立):

  • Crashlytics: firebasecrashlytics.googleapis.com/v1alpha を gcloud のユーザートークン+ x-goog-user-project で読む(メモリ crashlytics-api-access 参照)
  • 実機ログは devicectl ... --console を nohup で付けたままにする(kill するとアプリが終了する)
  • Unity バッチビルドは一時的な Assets/Editor/BatchBuildIOS.cs(BuildOptions.AcceptExternalModificationsToPlayer)を -executeMethod で実行し、終了後に削除。Addressables の link.xml と addressables_content_state.bin は実行後に git checkout で戻す
  • TestFlight: Release archive → exportArchive(app-store-connect / upload)。Xcode のビルドキャッシュが効いていれば archive は 1〜2 分
  • Crashlytics のアラートが複数アプリ・同日・同一 Issue ID で来たら、アプリを疑う前に gh search issues --repo firebase/firebase-ios-sdk で SDK・サービス側の障害を確認する。イシューの版別件数は reports/topVersions で取る(firstSeenVersion / lastSeenVersion は直近 7 日の分布ではない)

過去のWIPアーカイブ

(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)

2026-09-30 19:45 時点のスナップショット(2026-09-18 に書いた内容)

(2026-09-18 時点)

フェーズ: iOS 1.3.1 リリース済み(課金判定の例外修正)。ユーザーから「直った」の連絡あり。次は収束確認と Android。

アプリ本体は CodeCommit master b432c8090(タグ iOS1.3.1)。作業ツリーはクリーン。

1.3.0 → 1.3.1 の要点:

  • 原因: Unity IAP 5.1.2(StoreKit 2)で旧 API getSubscriptionInfo() が null → IsSubscribedTo の NullReference → 加入者の全画面の初期化が停止(記録画面に既定値「4月22日 / 75」、プレミアム消失、0001/01/01 の記録で統計崩壊)
  • 修正: 権利判定を取得注文ベースに作り直し(取得完了待ち・再取得・3 日猶予・復元の反映待ち)、画面初期化を課金判定から分離、不正記録の掃除。詳細は CLAUDE.md「課金実装」と ~/cdev/knowledge/unity-iap5-storekit2-migration-pitfalls.md
  • 見送り: 初回起動で権利が取れない場合の復元案内 UI。ミュート時に音が出ない件は仕様のまま(You 判断)

次にやること:

  1. Crashlytics(API で読める)で 1.3.1 の例外が収束したか確認。IsSubscribedTo が消え、新規の上位イシューが無いこと。billing: のパンくずで「取得が空 → 再取得で復旧」がどの程度起きているか
  2. Android 1.3.1 のリリース(Android 実機での回帰確認が未実施。背景検知の分岐と、共通変更分の動作)
    • 共有ボタンのクラッシュ修正を同梱する(2026-09-25 記録、未着手): Android 12+ で共有(NativeShare)を開いた瞬間に IllegalArgumentException(PendingIntent に FLAG_MUTABLE / FLAG_IMMUTABLE が無い)で落ちる。vitals(6/1〜9/24)でクラッシュ 1 位・101 件 / 79 ユーザー
    • 原因は Assets/Plugins/NativeShare/Android/NativeShare.aar(2021-05 ビルド)。schulte の修正前 AAR とバイト単位で同一。呼び出し元は SettingTab.cs / MemoInputCompleteAdditiveScene.cs
    • 直し方: ~/cdev/schulte-operation/schulte-unity/Assets/Plugins/NativeShare/Android/NativeShare.aar(修正済み、schulte-unity bf0b855)を同じパスに上書き(.meta はそのまま)→ versionCode を上げてビルド → 16KB エミュ kowai16k で共有シートが開き落ちないことを確認。経緯は schulte-operation の .devnotes(2026-09-24〜25)、差し替えたクラスのソースは schulte-operation/patches/nativeshare-flag-mutable/
  3. 75 分に上書きされた記録の問い合わせ対応(トムソンが手動修正を案内)。「曜日ずれ」の再報告があればスクリーンショットを依頼
  4. 1.3.1 の効果測定: レビュー、GA4 の record_saved / app_return、iOS 課金の推移
  5. その後、CodeCommit → github.com 移行

未決: 初回起動で 3 回とも権利が取れなかった場合の復元案内 UI を次版に入れるか

運用メモ(今回確立):

  • Crashlytics: firebasecrashlytics.googleapis.com/v1alpha を gcloud のユーザートークン+ x-goog-user-project で読む(メモリ crashlytics-api-access 参照)
  • 実機ログは devicectl ... --console を nohup で付けたままにする(kill するとアプリが終了する)
  • Unity バッチビルドは一時的な Assets/Editor/BatchBuildIOS.cs(BuildOptions.AcceptExternalModificationsToPlayer)を -executeMethod で実行し、終了後に削除。Addressables の link.xml と addressables_content_state.bin は実行後に git checkout で戻す
  • TestFlight: Release archive → exportArchive(app-store-connect / upload)。Xcode のビルドキャッシュが効いていれば archive は 1〜2 分

2026-09-18 16:52 時点のスナップショット(2026-09-05 に書いた内容)

(2026-09-05 時点)

フェーズ: v1.3.0 の実装・レビュー・iOS 実機テスト完了。残りはトムソン向けレポートと申請。

アプリ本体は CodeCommit master に push 済み(最新 1e3782698)。作業ツリーはクリーン。

今日(9/4)完了したこと:

  1. BGM 修正(B1 割り込み復帰 / B1' 再開ボタン / B2 取り消せない停止予約 / B3 他アプリ音声)。実機で再現→修正→再確認済み
  2. v1.3.0 に埋まっていた背景検知リグレッションの発見・修正(iOS で OnApplicationPause が発火せず、バックグラウンド中の経過時間加算・BGM予約・通知予約・広告判定が全て不発だった)。出荷ブロッカー級
  3. Codex による全体レビューと反映(BGM 分+それ以外)。広告イベントのメインスレッド化、課金者への広告表示の是正など
  4. UI 2点(音符の豆腐、完了ダイアログの×)
  5. Firebase Remote Config 6パラメータと GA4 カスタム定義10件を API で登録・検証

次にやること(この順):

  1. トムソン向けレポート 作成・S3 共有済み(URL 送付は You)
  2. TestFlight: 1.3.0(ビルド 1.3.0)配信中(ビルド ID 1c3501f6-5f30-44a6-a960-9115e7cb23c4)。トムソンがレポート内容を承認し、アプリをチェック中。問題なければ審査提出へ
  3. iOS 申請はトムソンが実施(バージョン作成・リリースノート・審査提出とも)。こちらは審査結果と公開のタイミングを受け取る
  4. Android 実機での回帰確認は iOS リリース後(背景検知の分岐で Android は従来どおり OnApplicationPause のはずだが未検証)→ Android 申請
  5. 申請完了後に CodeCommit → github.com へ移行
  6. リリース後、GA4 でイベントが期待どおり届いているか確認

未決: なし

運用メモ(今日確立した手順):

  • Firebase Remote Config は API で操作できる: gcloud auth print-access-token --account=ichirokisanuki@gmail.com + x-goog-user-project: studytimer-10a59 ヘッダ(これが無いとクォータプロジェクト未設定で 403)
  • GA4 Admin API は SA 鍵から自前 JWT でトークンを取る(gcloud のユーザートークンは analytics スコープ不足)。SA ga4-mcp@ichirokisanuki.iam は編集者権限を付与済み
  • 実機の背景挙動を観測するログは idevicesyslog(USB)+ os_log の %{public}s。devicectl --console は背景遷移で切れ、NSLog は syslog 上で <private> に伏せられる
  • .mm だけの変更なら Unity 再生成は不要。Builds/iOS/Libraries/... へコピーして xcodebuild するだけでよい(速い)

2026-09-04 16:00 時点のスナップショット

(2026-09-04 時点)

フェーズ: v1.3.0 の実装完了。残りは Remote Config のパラメータ作成・Android 回帰確認・両OS申請。

アプリ本体は CodeCommit master に対して 2 コミット済み(6418a024d BGM+背景検知の修正 / df487cf1a Crashlytics 設定と Addressables 生成物)。push は未実施。 作業ツリーはクリーン。

今回(9/4)完了したこと:

  1. 3 の BGM 修正(B1 割り込み復帰 / B1' 再開ボタン / B2 取り消せない停止予約 / B3 他アプリ音声)。実機で再現→修正→再確認まで完了
  2. v1.3.0 に埋まっていた背景検知リグレッションの発見と修正(詳細は DEVLOG 2026-09-04)。iOS で OnApplicationPause が発火せず、バックグラウンド中の経過時間加算・BGM 予約・通知予約・広告判定がすべて不発になっていた。iOS=Focus / Android=Pause の分岐で修正
  3. Codex にコードレビューを依頼し、妥当な指摘 2 件を反映
  4. B4(ループ継ぎ目)は実測のうえ対象外と決定

実機での確認済み項目: 背景での経過時間加算(ログで diff 加算を確認)、背景でのBGM切替、割り込みからのBGM復帰、一時停止→再開、イヤホン抜き差し、コールドスタートで他アプリが止まらない、無音モードでの共存。

次にやること(この順):

  1. CodeCommit へ push(AWS_PROFILE=thomson-ik git push origin master)
  2. Firebase コンソールで Remote Config の 6 パラメータを作成(未設定でも既定値で動く)
  3. Android 実機での回帰確認(今回の分岐で Android は従来どおり OnApplicationPause のはずだが未確認)
  4. 両 OS の申請。完了後に CodeCommit → github.com へ移行
  5. リリース後に GA4 のカスタム定義(source / plan / reason / result / background_sec / minutes / focus_min)を登録

未決: なし(イヤホン抜去時の挙動=一時停止、で確定・実装済み)

実機検証の手順メモ(再利用可):

  • Unity バッチビルド → Builds/iOS → xcodebuild -workspace Builds/iOS/Unity-iPhone.xcworkspace -scheme Unity-iPhone -configuration Debug -destination 'generic/platform=iOS' -allowProvisioningUpdates DEVELOPMENT_TEAM=JFBRCYHN24 CODE_SIGN_STYLE=Automatic → xcrun devicectl device install app
  • .mm だけの変更なら Unity 再生成は不要。Builds/iOS/Libraries/... へコピーして xcodebuild するだけで済む(速い)
  • 背景の挙動を観測するログは idevicesyslog(USB)+ os_log の %{public}s を使う。 devicectl --console は背景遷移で切れ、NSLog は syslog 上で <private> に伏せられる
  • 画面操作の自動化は Mac の「iPhone ミラーリング」+ phone-harness(要: iPhone をロックした状態で接続)

2026-09-03 21:26 時点のスナップショット

(2026-09-03 21:26 時点)

フェーズ: v1.3.0 の実装(残り 3 BGM とテスト)。 アプリ本体は CodeCommit master に 7 コミット push 済みで、作業ツリーはクリーン。

完了した実装(順序どおり。詳細は DEVLOG 2026-09-03 21:26):

  1. 記録消失の修正(保存順序の是正・未確定レコードの保険・完了画面の閉じるで記録画面も閉じる)
  2. 復帰時広告の是正(AdConfig: タイマー中は非表示 / 5 分未満は非表示 / 30 分間隔 / 1 日 3 回。Pause のみで判定)
  3. Firebase Remote Config(RemoteConfigManager。キーは return_ad_suppress_while_timer_running / return_ad_min_background_sec / return_ad_min_interval_sec / return_ad_max_per_day / interstitial_min_interval_sec / paywall_after_ad_interval_sec)
  4. イベント計測(AnalyticsUtil。timer_start / timer_complete / record_saved / paywall_view / paywall_select / purchase_complete / app_return)
  5. Crashlytics(CrashlyticsUtil。記録の保存経路にパンくず)
  6. バージョン 1.2.4 → 1.3.0(ProjectSettings 3 行、コミット 7facb6683 push 済み)

次にやること(この順):

  1. 3 の BGM 停止・途切れの修正(notes/2026-09-03_iOS_記録消失とBGM停止の調査.md §B。イヤホン抜去時の挙動は保留のまま observer を足せる形にしておく)
  2. 実機テスト(手順: 記録消失 = 集中 1 分で完了通知をタップし広告表示中にキル → 再起動で記録が残る / 復帰時広告 = タイマー中 6 分離脱で出ない、停止中 1 分で出ない、6 分で出る、直後の 6 分では出ない / Android はキーボード開閉で判定が走らない)
  3. Firebase コンソールで Remote Config の 6 パラメータを作成(未設定でも既定値で動く)。リリース後に GA4 のカスタム定義(source / plan / reason / result / background_sec / minutes / focus_min)を登録
  4. 両 OS の申請。完了後に CodeCommit → github.com へ移行

未決:

  • イヤホン抜去時に BGM を再開するか一時停止するか(3 に着手するときまでに)

環境メモ: Unity のコンパイル確認は Unity -batchmode -nographics -quit -projectPath . -logFile <log> で error CS を grep。Firebase パッケージの import 後は EDM4U の .meta を git checkout -- Assets/ExternalDependencyManager Assets/Firebase/Editor/Firebase.Editor.dll.meta ... で戻し、一時エディタスクリプトから PlayServicesResolver.ResolveSync(true) で Android 依存を解決する(手順は CLAUDE.md 運用メモ)。トムソン側の Pangle 除外と確認事項 5 点は返答待ち。


2026-09-03 19:45 時点のスナップショット

(2026-09-03 19:45 時点)

フェーズ: v1.3.0 の実装(トムソン承認済み)。 別セッションで着手する前提の引き継ぎ。

最初に読むもの(順に):

  1. CLAUDE.md(全体コンテキスト。9/3 の定点観測と AdMob 所見まで反映済み)
  2. notes/2026-09-03_iOS_記録消失とBGM停止の調査.md(修正対象のコード位置と修正方針、再現手順)
  3. 勉強タイマー広告現状レポート_2026-09.html §1・§5(広告制御の現状、閾値案、減収見込み)
  4. 勉強タイマー運用分析とアップデート計画_2026-09.html §9(計画全体)

v1.3.0 の実装項目と順序(iOS 先行。Android も同じ Unity プロジェクト):

  1. 記録消失: TimerTab.MoveToResultScene() で ES3Manager.ResetTimer() より先に FocusHistory.Add を実行して ID を保持。未確定セッションを ES3 に残し起動時に復元。完了画面の閉じるで MemoInputAdditiveScene も閉じる
  2. 復帰時広告: ApplicationBackgroundObserver の OnApplicationFocus 依存を外す(Pause のみで判定)。ReturnAppInterstitialAdManager.Show に「タイマー実行中は出さない」「バックグラウンド滞在 N 秒未満は出さない」「前回広告から M 分」「1 日上限」を追加。結果画面表示中も出さない
  3. BGM: Assets/Plugins/iOS/BackgroundAudioPlayer.mm に AVAudioSession の Interruption / RouteChange observer を追加。StopAudioAfterDelay / StopSubAudioAfterDelay を dispatch_block_t で保持して取り消し可能に。EnableBackgroundAudio.mm を廃止してカテゴリを Playback + MixWithOthers に一本化。復帰時にネイティブ再生を復帰
  4. Firebase Remote Config 導入(2 の閾値、通常広告の間隔、ペイウォール間隔)
  5. イベント計測: timer_start / timer_complete / record_saved / paywall_view / paywall_select / purchase_complete / app_return(background_sec 付き)
  6. Crashlytics(iOS/Android、ANR 含む)
  7. ProjectSettings の bundleVersion を 1.2.4 → 1.3.0

トムソン側の作業(依頼済み・返答待ち): 復帰時ユニットからの Pangle 除外、確認事項 5 点(レポート期間・Pangle 追加時期・各社管理画面の有無・月次レポート・Android 分)。

未決:

  • 記録日付を開始日のままにするか終了日にするか
  • イヤホン抜去時に BGM を再開するか一時停止するか
  • 復帰時広告の閾値初期値(案: タイマー中は非表示 / 5 分未満は非表示 / 前回から 30 分 / 1 日 3 回)

環境メモ: Unity 2022.3.62f2。アプリ本体は study-timer-unity/(CodeCommit、master、3/13 以降コミットなし、作業ツリーに .DS_Store などの雑な差分あり)。Play Console の取得手順は CLAUDE.md、GA4 は .mcp.json のプロジェクト設定。

2026-07-15 21:43 時点のスナップショット

(2026-07-15 21:43 時点)

フェーズ: 運用コンテキストの整備。 アプリ情報・収益・レビュー・データ接続の状況はすべて CLAUDE.md に集約済み。

データソース接続状況:

ソース 状態
App Store Connect(MCP) 済(売上・レビュー・財務)
GA4(プロパティ 347054291) 済(次回セッションで .mcp.json の承認が必要)
Play レビュー API / Android vitals 済
Play 統計CSV(GCS バケット) アカウントレベル権限の反映待ち(403)

次にやること:

  1. GCS 疎通確認(SA play-console-mcp@ichirokisanuki.iam で gs://pubsite_prod_rev_16697972298450990339/stats/installs/ が読めるか)→ 開通したらインストール数 CSV の取得テスト
  2. 残りのコンテキスト受け渡し: 広告構成(ネットワーク・フォーマット)/ 課金プラン設計意図 / アップデート計画

要観測: App Store 評価が 2026H1 に急落中(広告不満が主因)。ANR 率も Google 閾値に接近。アップデート方針検討の材料は CLAUDE.md「レビューデータ」参照。

ROADMAP(計画)

ロードマップ

今週

  • Play Console 統計CSV(GCS)の疎通確認 → インストール数 CSV の取得テストまで完了(2026-07-15)
  • 残りの運用コンテキスト把握: 広告構成・課金設計はコード調査で完了(2026-07-16、CLAUDE.md に記録)。アップデート方針=「仕込み系を先行→収益最大化アプデ」で確定
  • 仕込み系アップデートの内容確定 → v1.3.0 として確定、トムソン承認(2026-09-03)
  • v1.3.0 の実装着手 → 記録消失の修正・復帰時広告の是正・Remote Config・イベント計測・Crashlytics・バージョン 1.3.0 まで完了(2026-09-03、CodeCommit push 済み)
  • v1.3.0 の残り: BGM 停止の修正(B1/B1'/B2/B3)完了。実機テスト完了。あわせて背景検知のリグレッションを発見・修正(2026-09-04)
  • CodeCommit へ push(2026-09-04、最新 1e3782698 まで反映済み)
  • Android 実機での回帰確認(背景検知の分岐が Android 側で従来どおりか)
  • 実装分の実機テスト → 1.3.1 でサブスク加入テスターを使って課金・記録・編集・統計を確認(2026-09-15)。課金まわりは今後も必ず加入済みアカウントで確認する
  • GA4 カスタム定義 10 件を登録(2026-09-04、Admin API で一括登録・検証。リリース前に完了)
  • v1.3.0 のコードレビュー(Codex)と指摘の反映(2026-09-04)
  • iOS の申請 → 1.3.0 公開(2026-09-08)。加入者向けの致命的不具合が発覚し 1.3.1 で修正・公開(2026-09-18 までに、タグ iOS1.3.1)
  • Android 1.3.1 の申請(実機回帰確認の後)
  • Android 1.3.1 に共有ボタンのクラッシュ修正を同梱する(vitals のクラッシュ 1 位。NativeShare の AAR を schulte の修正版に差し替え。詳細は WIP)
  • Crashlytics で 1.3.1 の例外収束を確認(IsSubscribedTo は 1.3.1 で 0 件を確認済み・2026-09-30。残り: billing: パンくずで再取得の頻度)
  • Remote Config の 6 パラメータを作成(2026-09-04、Firebase コンソールではなく REST API で publish・検証)
  • トムソン側: Pangle 除外は様子見に決定(2026-09-05)。公開後にレビュー言及が続けば Pangle → AppLovin の順で除外
  • 公開 2〜4 週間後: レビュー・app_return・AdMob レポートで広告是正の効果と除外要否を判断
  • Play Console の Android vitals で ANR の発生箇所を確認(v1.3.0 の ANR 対策の対象決め)
  • App Store Connect の App Analytics で新規減を「露出」と「転換」に切り分け

今月

  • 広告体験改善アップデートの方針検討 → v1.3.0 の①として確定(2026-09-03)
  • 「記録した時間が保存されず消える」バグの調査 → 原因特定済み(notes/2026-09-03_iOS_記録消失とBGM停止の調査.md)
  • v1.3.0 の実装完了(2026-09-04)
  • Android の申請(iOS は 1.3.1 まで公開済み)
  • 未決事項の確定: 記録日付は開始日のまま(2026-09-03)、復帰時広告の閾値初期値は DECISIONS.md のとおり
  • 未決事項の確定: イヤホン抜去時の挙動(BGM 修正に着手するときまでに)
  • GA4 のカスタム定義はリリース前に登録済み(2026-09-04)
  • v1.3.0 完了後に study-timer-unity を CodeCommit から github.com へ移行(履歴約 15GB の扱いを先に確認)
  • 月次収益 CSV(data/revenue-monthly.csv)の更新フロー確立(AdMob 月次レポートの提供をトムソンに依頼中。課金は財務レポートから取得可)
  • Crashlytics の AVFoundationVideoTextureOutput の EXC_BAD_ACCESS(直近 7 日 23 件 / 20 人、すべて 1.3.1。9/29 の障害を除くと最多)を調査(2026-09-30 に発見)

今四半期

  • ANR 率の定点観測(8/31 時点 0.64%、要注意閾値 0.47% を超過中)→ v1.3.0 の ANR 対策後に再確認
  • Pangle 除外後のクリック率とレビューを見て AppLovin 除外を判断(減収見込み ¥5〜14k/月)
  • 価格・プラン構成の見直し(中高生向けの短期・低価格プラン、リワード型の広告非表示)※Phase 2
  • 復帰時広告の閾値を計測結果で見直し(Remote Config)
  • 起動時に権利が取れなかった加入者への「購入の復元」案内 UI(1.3.1 で見送り。Codex 指摘)
  • 権利判定を IAP 5 の新 API(CheckEntitlement)へ全面移行するか検討(visionworkout の実装を参考)
  • タイマーなしで記録を追加する導線(記録が無い日に「追加」ボタン。優先度低)
  • Firebase が不正なサーバーデータに耐える SDK を出したら、次のリリースで Firebase Unity SDK(現行 13.4.0)を更新(firebase-ios-sdk#16728 で確認。9/29 の障害は Google 側で解消済みのため急がない)

いつか

  • クラッシュ監視の導入検討 → v1.3.0 で Crashlytics を導入(実装済み 2026-09-03。Android のネイティブシンボルアップロードは未設定)
  • Android 機能パリティ: バックグラウンド再生対応、時計背景の表示バグ調査、BGM が等間隔で途切れる問題
  • BGM ループ素材のギャップレス化(MP3 → M4A/CAF 変換、または 2 プレイヤーのクロスフェード)
  • 外部音楽サービス連携の検討(Google Play レビュー要望)
  • AdMob API の接続(広告収益・ネットワーク別実績の自動取得)
  • 教科別ラベル・色分け、複数端末での記録共有(レビュー要望)

DECISIONS(意思決定)

意思決定記録

このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。


2026-09-30: 9/29 の Firebase Analytics サーバー障害には対応せず、SDK 更新は修正版が出てから次のリリースで行う

背景: 9/29 09:41〜11:52 JST に Google Analytics for Firebase iOS のサーバー応答の不備で、勉強タイマーを含む多数のアプリが一斉にクラッシュした(勉強タイマーは 40 件 / 40 人)。原因は Google 側で、Google は SDK の更新なしで修正済み。Firebase は「不正なサーバーデータに耐える SDK」を後日配布する予定。

決定: 緊急リリースも SDK の即時更新もしない。修正版 SDK が出たら、次のリリースで Firebase Unity SDK(現行 13.4.0、Crashlytics SDK は 12.4.0)を上げる。

理由: 障害は 3 時間弱で解消し、再発もない(勉強タイマーの最後の発生は 9/29 11:39 JST)。今 SDK を上げても今回の障害には効かず、Firebase 自身も急ぎの更新は不要と表明している。修正版が出た時点でまとめて更新すれば足りる。

2026-09-18: 課金まわりの再発防止は knowledge + 各プロジェクトの CLAUDE.md で行う(.devnotes には書かない)

背景: 同じ PurchasingManager のコードが gabor / schulte / fasting にもあり、IAP 5 へ上げた瞬間に同じ不具合になる。You は「Unity かつ課金実装のあるプロジェクトの .devnotes に書く」案を出した。

決定: 詳細は ~/cdev/knowledge/unity-iap5-storekit2-migration-pitfalls.md に 1 本、各プロジェクトには毎セッション必ず読まれる CLAUDE.md に短い注意書きを追記する。.devnotes は進行中の作業のときしか読まれないので使わない。

理由: knowledge/ は明示的に検索したときしか読まれず、.devnotes/ も単発作業では読まれない。課金コードを触る場面で確実に目に入るのは CLAUDE.md だけ。

2026-09-15: ミュート(マナースイッチ)中に効果音が鳴らない挙動は仕様のままにする

背景: 1.3.0 の BGM 修正(Apple Music を止めない対応)で、起動時に音声セッションを Playback に切り替えるのをやめた結果、「BGM なし」やタイマー開始前の効果音がマナースイッチに従うようになった。ユーザー報告 1 件。トムソンは「仕様変更・対応不要」に分類。

決定: 1.3.1 では変更しない。問い合わせにはマナースイッチ解除を案内する。

理由: 戻すと Apple Music を止める問題が復活する。中間案(タイマー開始時に必ず Playback へ切り替え)は提示したが、You の判断で据え置き。

2026-09-15: iOS の権利判定は IAP 5 の取得注文ベースにし、取りこぼしには再取得と 3 日の猶予で備える

背景: 旧 API のレシート解析は StoreKit 2 で null を返し、Product.receipt は期限切れの未完了取引にも付いて消えない。実機では起動時の取得が空で復元後に権利が返る事象も観測した。Codex レビュー 3 回で「hasReceipt を有効の根拠にしない」「復元のタイムアウトを成功扱いにしない」等の指摘。

決定: OnPurchasesFetched の注文(Confirmed + 期限内の Pending)とセッション内の購入から権利集合を作る。取得前・失敗時は前回起動の確定結果を 3 日上限で使う。権利が返らなければ 3 秒後に再取得(最大 2 回)。復元は反映を待ち、タイムアウトは失敗扱い。

理由: 課金者を誤って未課金にする方が、解約者が最大 3 日プレミアムを使えるより損失が大きい。IAP 5 の新 API(CheckEntitlement)への全面移行は緊急リリースの範囲外とし、次の大きな改修で検討する。

2026-09-15: 壊れた記録の自動修復は日付 1000 年未満の削除に限定し、75 分に上書きされた記録は手動案内とする

背景: 1.3.0 で 0001/01/01・0 分の記録と、75 分で上書きされた記録が作られた。当初「2000 年以前を削除」としたが、Codex から日付ピッカーに下限が無く正規記録を消し得ると指摘。

決定: 起動時に削除するのは西暦 1000 年より前の記録だけ。pending に残った不正日付も破棄。75 分に上書きされた記録は元の値が残っていないため自動修復せず、問い合わせ時に編集画面での手動修正を案内する。

理由: 確実に壊れていると分かる値(DateTime.MinValue)だけを対象にし、ユーザーの正規データを消さない。

2026-09-05: 広告ネットワーク(Pangle / AppLovin)の除外はいったん様子見とする(トムソン判断)

背景: 9/3 時点では復帰時ユニットからの Pangle 先行除外(減収見込み月 ¥3k)を提案していた。1.3.0 の内容確認後、トムソンから「復帰時広告の是正でストレスのかかるタイミングの広告が減るため、広告内容は気にならなくなるのでは」との見立てが示された。こちらからは Pangle は安価で即戻せるため 1.3.0 公開に合わせて外す案を返した。

決定: 1.3.0 公開時点では広告ネットワークには手をつけない。公開後もレビューで広告内容への言及が続く場合、Pangle → AppLovin の順で外す。判断はトムソン。

理由: メディエーション設定はトムソン側の管理領域で、除外は後からいつでもできる。まず 1.3.0 の是正効果を単独で見たい、という運営側の判断を尊重する。判断材料は公開後のレビューと app_return / AdMob レポート。なお AppLovin は通常広告側なので 1.3.0 の是正では露出が減らない点は伝えてある。

2026-09-04: 課金状態が確定するまで広告を出さない(fail-closed)

背景: IsBillingUser は IAP 初期化前に false を返す実装で、広告側はそれを「無料ユーザー確定」として扱っていた。オフラインやストア応答遅延、初期化失敗の間は課金者にも広告が出る。Codex のレビューで指摘され、コードで裏取りした。

決定: PurchasingManager / PurchasingManagerForAndroid に CanShowAds(初期化済み かつ 非課金)を追加し、広告の表示・ロード判定をすべてこれに差し替える。課金状態が未確定の間は広告を出さない。あわせて初期化完了後に復帰時広告のロードを開始する導線を HomeScene に追加する。

理由: 「課金者は広告なし」はユーザーとの約束なので、判定できないときは出さない側に倒すのが正しい。初期化が失敗するのは基本的にオフライン時で、そのときはどのみち広告を配信できないため収益への実害は小さい。なお導線の追加が無いと、初期化前に呼ばれる初回ロードがスキップされたまま再試行されず広告が一切出なくなるため、セットで必要。

2026-09-04: Codex のレビュー指摘のうち、スキーマ変更を伴うものは v1.3.0 では見送る

背景: Codex から高2件・中2件・低3件の指摘。うち「SQLite の INSERT と ES3 の Pending 保存が原子的でなく二重登録の窓が残る」は、タイマー開始時に sessionId を発番して FocusHistory に一意列として持たせる修正案だった。

決定: 影響の大きい4件(広告イベントのメインスレッド化、課金者への広告表示、Remote Config の bool フォールバック、Dev ビルドのフェッチ順序)のみ v1.3.0 で対応し、sessionId 導入・完了直後1秒の広告抑制・購入途中の計測欠測は次版へ送る。

理由: sessionId は DB スキーマに一意列を足す変更で、既存データの移行も絡む。今回は評価回復とデータ損失の主要因を潰すことが目的で、窓が極小の二重登録のためにスキーマを触るとリスクが釣り合わない。

2026-09-04: バックグラウンド検知は iOS=Focus / Android=Pause に分岐する

背景: v1.3.0 で ApplicationBackgroundObserver から OnApplicationFocus を削除し OnApplicationPause のみにしたが、このアプリはバックグラウンド音声を使うため Unity のトランポリンで UnityGetUseCustomAppBackgroundBehavior() が真になり UnityPause(1) が呼ばれない。iOS では OnApplicationPause が一切発火せず、背景検知が全滅していた(実機で確認)。

決定: #if UNITY_IOS && !UNITY_EDITOR で iOS は OnApplicationFocus、それ以外は OnApplicationPause を使う。

理由: iOS は Focus しか発火しないため他に手段がない。Android は Focus だとキーボード表示やダイアログで誤検知するため Pause のままにする必要がある。両OSの要件が異なるので分岐が唯一の解。iOS 側はコントロールセンター等の短い離脱でも背景扱いになるが、復帰時広告は v1.3.0 で入れた滞在 5 分の閾値で抑制される。

2026-09-04: BGM の音声セッションは「無音=共存 / 実BGM=置換」とし、置換の厳密さは追わない

背景: 他アプリ(Apple Music 等)の再生が止まるのは setActive ではなく Ambient→Playback のカテゴリ変更の瞬間で、MixWithOthers を付けていても一度は割り込む。逆に既に Playback なら変更が起きず共存する。そのため「実BGM=置換」は起動後の初回だけという順序依存になる。

決定: 起動時は Ambient+Mix のままにし(コールドスタートで他アプリを止めない)、実 BGM を鳴らすときだけ Playback + MixWithOthers に切り替える。無音モード(index==5)では切り替えない。実BGMの「置換」が順序依存になる点は許容し、排他 Playback 化+停止時の Ambient 復帰は行わない。

理由: 一番効くのは「無音を選んだユーザーが自分の音楽を聴きながら使える」ことで、それは確実に担保できる。実BGMの置換を厳密にするには排他 Playback と停止時の復帰処理が必要で、実装とテストが増える割に実害が小さい。Codex のレビューでも同じ論点が指摘されたが、上記理由で現状維持を選択。

2026-09-04: BGM のループ継ぎ目(B4)は今回対象外とする

背景: mp3 の実測で、26 ファイル中 24 にギャップレス情報がなく、雨 ambient で 166ms(59秒ごと)、鉛筆 music では素材末尾に 7.3 秒の無音があることを確認した。

決定: v1.3.0 では扱わない。素材の M4A 化・末尾無音トリムは行わない。

理由: You の判断で実害と言うほどではない。素材の作り直しは音源側の作業になり、コード修正とは別レーンで扱う方が適切。

2026-09-03: 記録日付は「開始日」のままとする(v1.3.0 で終了日には変えない)

背景: 記録の日付はタイマー開始ボタンを押した日時(StudyStartDateTime)の日付で保存されており、23:50 開始・0:15 終了の記録は前日に載る。調査メモでは終了日への変更を候補にしていた。

決定: 開始日のまま変更しない。v1.3.0 の記録消失対策は保存順序の是正(SQLite への保存をタイマー状態の消去より先に行う)と未確定レコードの保険に絞る。

理由: 終了日に変えると 0 時をまたいだ分が翌日扱いになり、別の「消えた」に見える副作用が出る。日付境界を早朝にする案は集計側にも手が入るためスコープが広がる。まずは実害のある保存順序だけを直す。

2026-09-03: 次版を v1.3.0 とし、広告是正・不具合修正・仕込みを同梱する(トムソン承認)

背景: App Store 評価の急落に加え、iOS の新規ユーザーが前年同月比 −54%、月間アクティブ −39% と基盤が縮小。トムソンへのアップデート提案が承認された。

決定: iOS/Android 共通の 1.3.0 として、①集中中と短時間離脱時の復帰時広告抑制(閾値は Remote Config で調整可能に)、②記録消失の修正(保存順序の是正)、③BGM 停止の修正(割り込み対応・停止予約の取り消し)、④仕込み(Remote Config・イベント計測・Crashlytics)を入れる。

理由: Android が 1.2.4 を配布済みで番号衝突を避ける。①は評価回復の本命、②③はレビューの★1要因、④は次回以降の広告調整をリリースなしで回すため。

2026-09-03: 広告ネットワーク側は復帰時ユニットの Pangle 除外を先行し、AppLovin は据え置く

背景: AdMob レポートで復帰時ユニットの Pangle CTR 39.5%、通常ユニットの AppLovin 76%。「開いた瞬間 TikTok に飛ぶ」は復帰時ユニットの外部ネットワークが Pangle だけなので出どころが確定した。

決定: まず復帰時ユニットから Pangle を外す(減収見込み月 ¥3k 程度)。AppLovin(通常ユニット ¥14k)はレビューとクリック率の推移を見て判断する。AdMob の「ブロックのコントロール」は Google 配信にしか効かないため、外部ネットワークは各社の管理画面かメディエーション除外で対応する。

理由: Pangle 除外は安く即効性がある。AppLovin 除外は ¥5〜14k と高く、復帰時の苦情には関係しない。

2026-09-03: 復帰時広告の初期条件を「タイマー中は非表示・5分未満は非表示・前回から30分・1日3回」とする

背景: 現状の制御は 60 秒クールダウンのみで、OnApplicationFocus でも発火するため短い割り込みでも広告が出る。何秒で出せば不快度が低いかのデータは無い。

決定: 上記を初期値として Remote Config に載せ、復帰イベントにバックグラウンド滞在秒数を付けて 1 か月計測してから見直す。収益優先なら 3 分、評価優先なら 10 分がレンジ。

理由: 勉強中の離脱(LINE・通知・画面ロック)は数秒〜3 分、「開き直し」は 10 分以上という経験則の中間を取った。

2026-09-03: 指標の「正」を定める(広告は AdMob レポート、表示評価はストアページ)

背景: GA4 の ad_impression が AdMob より 27% 少なく、7月版の「ストア表示評価 4.16」は記述レビュー平均の誤記だった。

決定: 広告の表示・クリック・収益は AdMob レポートを正とし、GA4 は傾向確認用。ストア表示評価は各ストアページの JSON-LD の値(App Store 4.5、Google Play 4.3)を使う。

理由: 計測漏れのある指標で減収見込みを出すと誤差が大きい。表示評価と記述レビュー平均は別物で、混同すると ASO の判断を誤る。

2026-07-15: アプリ本体(study-timer-unity)は ops リポジトリの管理対象外とする

背景: アプリ本体の Unity プロジェクト(独立 git リポジトリ、履歴約15GB)がプロジェクト内にネストされており、GitHub 側 ops リポジトリとの関係を決める必要があった。

決定: .gitignore で study-timer-unity/ を除外し、アプリ本体は従来どおり AWS CodeCommit で管理を継続する。ops リポジトリには運用データ・ドキュメントのみを置く。

理由: 15GB の履歴は GitHub 移行が非現実的。submodule 化は個人開発には運用が重く、分離管理で十分。

2026-07-15: データ接続はサービスアカウント方式で統一し、GA4 はプロジェクト専用 .mcp.json で切り替える

背景: グローバルの ga4 MCP 設定は Football NEXT 向けで、勉強タイマーのプロパティ(347054291)に向ける手段が必要だった。Play Console のデータ接続も未整備だった。

決定: GA4 はプロジェクト直下の .mcp.json にプロパティ 347054291 向けのサーバー定義を追加(グローバル設定は変更しない)。Play Console は専用 SA play-console-mcp を新設して接続。ユーザー OAuth(ADC)方式は不採用。

理由: プロジェクト設定がグローバルより優先されるため他プロジェクトに影響しない。SA 方式はトークン失効がなく長期運用向き。gcloud 標準クライアントは analytics スコープの ADC ログインをブロックする問題もあった(GA 管理者への SA 追加依頼で解決)。

DEVLOG(作業ログ)

開発日誌

このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。


2026-09-30

19:45 - Firebase Crashlytics の一斉クラッシュ警告(9/29)を調査 → Google 側の障害と確定、対応不要

きっかけ: Firebase から「増加傾向にある安定性に関する問題(日付: 2026-09-29)」のメールが Thomson 系 5 アプリ(勉強タイマー / FootballNEXT / BaseballNEXT / Vision Training / channel7)に届いた。どれも例外は [CoreFoundation] -[__NSDictionaryM setObject:forKeyedSubscript:](key cannot be nil)で、勉強タイマー・FootballNEXT・Vision Training の 3 プロジェクトは Crashlytics の Issue ID が完全に同一(181010225a8f7695edf124ff32b9c406)だった。

やったこと:

  • Crashlytics API(メモリ crashlytics-api-access の手順)で勉強タイマーの全 40 件を分析: 40 件 / 40 人(1 人 1 回)。すべて 2026-09-29 00:46〜02:39 UTC(09:46〜11:39 JST)に集中し、以降は 0 件。iOS 17.7〜27.0、21 機種(iPad を含む)、1.3.1 が 39 件・1.3.0 が 1 件、前面 20 / 背面 20。Remote Config の更新履歴は 9/4 の 1 件のみで、こちらの変更は無い
  • スタックの読み: 例外を投げたのは GCD ワーカー上のブロックで、呼び出し元は末尾呼び出しのため欠落し、アプリのコードは一切出ない。全 40 件で別スレッド APMExperimentWorkerQueue が Firebase Analytics の実験取得(APMETaskManager fetchExperiments の応答処理)の最中で、-[GULMutableDictionary dictionary] の dispatch_sync で同じ専用キューの空きを待っていた
  • firebase-ios-sdk の GitHub Issue(#16728 ほか)で裏取り。Firebase 担当者の説明: 9/28 17:41 PDT(9/29 09:41 JST)から、Google Analytics for Firebase iOS が受け取る sdk-exp の応答に不正なペイロードが混ざり、SDK が nil キーで辞書に書き込んで落ちた。19:52 PDT(11:52 JST)に修正のロールアウトが完了し、キャッシュ分は 23:52 PDT(15:52 JST)までに自然解消。SDK の更新は不要。SDK 10.24〜12.18 の多数のアプリで発生した。Firebase ステータスページ(incidents.json)には確認時点で 9/29 前後の掲載が無かった
  • Firebase 側の今後: 不正なサーバーデータに耐える SDK をレビュー後に配布し、急ぎでない更新として推奨する予定。根本原因の報告も予告されている
  • ついでに Crashlytics の直近 7 日を確認(版別の件数は reports/topVersions): IsSubscribedTo は 1.3.1 で 0 件(1.3.0 のみ 24 件 / 2 人)= 1.3.1 の修正が効いている。9/29 の障害を除く最多は AVFoundationVideoTextureOutput の EXC_BAD_ACCESS(23 件 / 20 人、直近 7 日はすべて 1.3.1)

決めたこと: 今回の障害への対応(緊急リリース・SDK の即時更新)はしない。Firebase の耐障害性修正版が出たら、次のリリースで Firebase Unity SDK(現行 13.4.0)を上げる(DECISIONS.md にも記録)

気づき:

  • 複数アプリ・同日・同一 Issue ID のアラートは、アプリのコードより先に SDK・サービス側を疑う。gh search issues --repo firebase/firebase-ios-sdk で数分で裏が取れる
  • スタックにシステムフレームしか無いときは、他スレッドのスタックと queue ラベルに原因が写っていることがある(今回は APMExperimentWorkerQueue)
  • Crashlytics のイシューの firstSeenVersion / lastSeenVersion は直近 7 日の分布ではない。AVFoundation は初出が 1.3.0 でも、7 日窓の 23 件はすべて 1.3.1 だった(最初のまとめで「1.3.0 と 1.3.1 の両方」と誤って書き、版別の件数で訂正した)。版別は topVersions で取る

次回やること: AVFoundationVideoTextureOutput のクラッシュ調査、Firebase SDK 修正版の確認(#16728)。他は WIP.md のとおり

2026-09-18

16:52 - iOS 1.3.0 の致命的不具合の原因調査、v1.3.1 の実装・実機検証・TestFlight・リリース、再発防止(9/14〜9/18 の一連)

最大の成果: 1.3.0 公開 6 日後に発覚した「サブスク加入者だけ全画面が壊れる」不具合を原因特定→修正→検証→リリースまで 1 週間以内で完了し、ユーザーから「直った」の連絡を受けた。

経緯(9/14 夜〜9/15 早朝):

  • App Store に「買ったのにバグが多すぎる」(★1)が付き、トムソン経由でメール報告 8 件(9/8 の 1.3.0 公開以降)。症状は「編集時に 4/22・75分と表示」「60分が75分になる」「年間プレミアムが適用されない、復元しても外れる」「曜日がずれる」「グラフが見えない」「ミュートで音が出ない」
  • 原因調査(コード差分 → GA4 → Crashlytics): GA4 で app_exception が 1.2.3 ではゼロ、1.3.0 のみ 1 日 1,000 件超(59 人 / 723 人)。記録画面のシーンファイルに既定テキスト「4月22日」「75」が保存されていることを発見し、「初期化が途中で止まって既定値が見えている」と推定。Crashlytics API(firebasecrashlytics.googleapis.com/v1alpha、この日に API を有効化)で最上位イシューが PurchasingManager.IsSubscribedTo の NullReferenceException(7 日で 3,487 件 / 90 人、初出 1.3.0)と確定
  • 根本原因: 3 月の Android 課金対応で Unity IAP を 4.12.2 → 5.1.2(StoreKit 2)に上げており、iOS では 1.3.0 が初出荷。旧 API SubscriptionManager.getSubscriptionInfo() は StoreKit 2 でアプリレシートが無いと null を返し、それを無条件に参照して例外。IsBillingUser を呼ぶ全画面(タイマー・記録・統計・設定・広告ロード)の初期化が途中で止まった。パンくずログから、リセット済みでも「終了」が押せて 0001/01/01・0 分の記録が作られ、統計・カレンダーが壊れる派生も確認
  • テストで見逃した理由: 実機・TestFlight ともサブスク未加入のアカウントで確認しており、receipt == null の経路しか通っていなかった
  • トムソンが不具合をシート(No.1〜5)に整理。No.1(4/22・75分)と No.2(プレミアム)が最優先

v1.3.1 の実装(9/15 未明、Codex レビュー 3 回を反映):

  • 権利判定を作り直し: UnityIAPServices.DefaultPurchase().OnPurchasesFetched で取得した注文(Confirmed + 期限・取消が過ぎていない Pending)とセッション内の購入から権利集合を作る。旧 SubscriptionManager と Product.receipt は使わない。取得前・失敗時は前回起動の確定結果を 3 日を上限に使い、権利が返らなければ 3 秒後に再取得(最大 2 回)。CanShowAds は取得完了まで false。復元は反映を待って完了通知(60 秒 / 同期後 15 秒でタイムアウト=失敗扱い)。IsBillingUser は例外を外に出さない
  • 記録画面・タイマー画面の初期化を課金判定から切り離す。編集対象は ID で特定。分数入力は TryParse。タイマー未開始なら記録を作らない
  • 起動時に日付 0001 年の記録を削除。pending の不正日付を破棄
  • カレンダーの日付解析、通知の 0 秒間隔、復元の初期化前 null、広告の多重ロード、統計・設定タブの課金 UI 更新
  • Codex の指摘のうち見送り: 初回起動で 3 回とも権利が取れない場合の復元案内 UI(UX 変更)
  • 実機検証(iPhone 14、サンドボックスの加入テスター): テスターの 3 か月プランが 9/5 に失効していたため年間プランを購入し直し、「購入(復元ファースト)→ 再起動でプレミアム維持(起動 0.2 秒で権利取得)→ 記録 → 編集 → 統計」をログ付きで確認。実機ログで「起動時の取得が空、復元後は権利あり」「期限切れの未完了取引に receipt が付く」を観測し、それぞれ対策に反映
  • 9/15 04:20 TestFlight へアップロード(Upload succeeded、内部テスト配信)。ミュート時に音が出ない件は You の判断で仕様のまま

リリース後(9/18):

  • 審査通過・公開済み。ユーザーから「直った」との連絡
  • コミット b432c8090、タグ iOS1.3.1(抜けていた iOS1.3.0 も付与)を CodeCommit に push
  • 再発防止: ~/cdev/knowledge/unity-iap5-storekit2-migration-pitfalls.md を作成。同じ PurchasingManager を持つ gabor-ops / schulte-operation / fasting-ops の CLAUDE.md に注意書きを追記(visionworkout は IAP 5 の新 API に移行済みで参考実装)。CLAUDE.md を 1.3.1 に更新

気づき:

  • 「複数ユーザーが同じ値を見る」はデータではなくビルドの定数(シーンの既定値)を疑う
  • Crashlytics のイシューは API で読める。Crashlytics.Log のパンくずに「取得した注文の商品 ID・有効期限」を出しておくと失効と取りこぼしを切り分けられる
  • devicectl --console は kill するとアプリごと終了する。ログ接続は nohup で付けたままにする
  • サンドボックスの期間短縮(年 1 時間、体験 3 分、3 か月 15 分)でテスターの権利は数時間で失効する。TestFlight ではサンドボックスアカウントは使えない

次回やること: Crashlytics で 1.3.1 の例外が収束したか確認(IsSubscribedTo が消え、新規イシューが無いか)。Android の 1.3.1 リリース(実機回帰未実施)。75 分に上書きされた記録の問い合わせ対応はトムソンが手動修正を案内。

2026-09-05

12:05 - トムソン向けレポートの共有と、iOS 1.3.0 の TestFlight アップロード

やったこと:

  • トムソン向けレポート(勉強タイマーv1.3.0アップデート内容_2026-09.html)を作成。You のレビューで要旨・タイル・提案の経緯・リリース計画とお願い・収益見込み・Android 実機の行を削り、全体を簡潔に(本文約 2,100 字)。検証表に課金テストを追加
  • S3 で共有: thomson-share スキル(visionworkout-operation のプロジェクト内スキル)の手順で s3://thomsons/勉強タイマーv1.3.0アップデート内容_20260905.html に public-read で PUT、HTTP 200 を確認。URL の送付は You。スキルはこのプロジェクトの .claude/skills/thomson-share/ にもコピーした(ここでも発動するように)
  • iOS 1.3.0 を TestFlight 向けにアップロード: Unity を Development フラグ無しで再生成 → xcodebuild archive(Release)→ -exportArchive(app-store-connect / upload)で Upload succeeded。約 15 分で処理完了(processingState: VALID、内部テスト IN_BETA_TESTING)。内部グループ自動配信のため TestFlight でそのまま入れられる
  • .devnotes と CLAUDE.md に、実機テスト完了(広告表示・記録消失の異常系・復帰時広告の抑制 4 パターン)と上記手順を反映

詰まったこと / 気づき:

  • xcodebuild archive が error: Unexpected duplicate tasks で失敗。 Unity の append 生成を繰り返した結果、GameAssembly の buildPhases に IL2CPP ビルドスクリプトの参照が二重に入っていた(Debug の build では顕在化しない)。pbxproj の各 buildPhases 内の重複行を畳んで解決。Crashlytics の Run Script が 2 つあるのは Unity-iPhone / UnityFramework 用で正常
  • ASC API キーではクラウド署名できない。 -authenticationKey* を渡すと「Your account has roles ""」「No signing certificate iOS Distribution」で Export 失敗(キーの役割が Admin/App Manager でない)。API キーを外して -allowProvisioningUpdates のみにすると、Xcode に残っていた Thomson チームの Apple ID セッションで配布署名+アップロードまで通った
  • TestFlight の内部グループ「App Store Connect Users」は全ビルド自動配信なので、処理完了後にテスター追加なしで You の TestFlight に出る

次回やること: ASC の処理完了確認 → TestFlight で 1.3.0 を実機確認 → App Store 側で 1.3.0 のバージョン作成・審査提出。Android は iOS 公開後

2026-09-04

17:20 - Codex による v1.3.0 全体レビューと反映、UI 2点修正、GA4 カスタム定義の登録

やったこと:

  • UI 2点の修正(コミット 5bba39ea8):
    • BGM 名の先頭の音符が iOS で豆腐(□)になっていた件を修正。文字化けではなくグリフ欠落で、 フォントアセット HiraginoMaruGoProW4 の文字テーブルを検査したところ ♪(U+266A) は含まれるが ♫(U+266B) は含まれていなかった。iOS だけ ♫ を使っていたのが原因。両OSとも ♪ に統一
    • BGM 名テキストの位置を X=-9 に調整(ボタン画像の右端にある ▼ の分だけ右寄りに見えていた)
    • 記録の完了ダイアログの CloseButton を非表示化。同シーンには閉じる処理を持つ暗幕 (Button (Legacy))もあるが元から無効なので、閉じる導線は「実績カレンダーへ」に一本化される
  • Codex に v1.3.0 の BGM 以外の変更をレビューさせ、指摘を反映(コミット 1e3782698)。 高2件・中2件・低3件のうち、影響の大きい4件を採用:
    • 広告イベントがメインスレッド外: MobileAds.RaiseAdEventsOnUnityMainThread が未設定で、 広告クローズ時の SceneManager.LoadScene(ペイウォール)や PlayerPrefs がバックグラウンド スレッドから走っていた。Crashlytics で未処理例外を致命報告にした今、クラッシュ率に直結する
    • 課金者に広告が出る: IsBillingUser が初期化前に false を返すため、オフラインやストア 応答遅延の間は課金者にも広告が出ていた。CanShowAds(初期化済み かつ 非課金)を追加して fail-closed に。あわせて初期化完了後にロードを開始する導線を HomeScene に追加した (これが無いと初回ロードがスキップされたまま再試行されず、広告が一切出なくなる回帰になる)
    • Remote Config の bool フォールバック欠落: ConfigValue.BooleanValue は変換不能値に FormatException を投げ、Apply の先頭で落ちて以降の数値設定が全て反映されなくなる
    • Dev ビルドのフェッチ設定の順序: 設定完了を待たずに Fetch していた
    • 見送り: sessionId による INSERT 原子化(スキーマ変更が必要、次版)、完了直後1秒の広告抑制、 購入途中プロセス終了時の計測欠測
  • GA4 のカスタム定義10件を Admin API で登録(ディメンション5: source / plan / reason / result / bgm_index、指標5: background_sec / minutes / focus_min / break_min / set_count)。 トムソンに ga4-mcp@ichirokisanuki.iam の権限を閲覧者→編集者へ変更してもらい、API で一括登録・検証。 カスタム定義は遡及しないため、リリース前に登録できたのが重要
  • Firebase Remote Config の6パラメータを API で作成(既定値と同値。挙動を変えずリモート調整可能な 状態だけを作った)。ichirokisanuki@gmail.com が Firebase プロジェクト studytimer-10a59 に アクセスできるため、コンソールを開かずに publish・検証できた

気づき:

  • Firebase Remote Config も GA4 Admin API も、コンソールを開かずにここから操作できる。 Remote Config は gcloud auth print-access-token --account=ichirokisanuki@gmail.com + x-goog-user-project: studytimer-10a59 ヘッダが必須(ユーザー ADC はクォータプロジェクト未設定だと 403)。 GA4 Admin API は gcloud のユーザートークンでは analytics スコープが足りないため、 SA 鍵から自前で JWT を作ってトークンを取る必要がある
  • Codex の指摘をそのまま適用すると回帰する場合がある。 「課金状態が確定するまで広告を出さない」を ロード側にも適用した結果、初期化前に呼ばれる初回ロードが永久にスキップされ、広告が出なくなる ところだった。指摘の意図を汲みつつ、副作用まで自分で追う必要がある
  • 「文字化け」に見える現象がフォントのグリフ欠落であることは、フォントアセットの文字テーブルを 直接検査すれば確定できる(m_Unicode: 9834 の有無)

実機テストの結果(You による確認、9/4 夜〜9/5): Codex 修正後のビルドで広告が正常に表示され、閉じてもクラッシュしないことを確認。 前セッション実装分も確認済み: 記録消失の異常系(終了→フェード/広告中にキル→再起動で記録が復元される)と、 復帰時広告の抑制(タイマー中は出ない / 短い離脱は出ない / 閾値超えで出る / 直後は間隔で抑制)の 4 パターン。 これで v1.3.0 の iOS 側の実機テストは完了。

次回やること: トムソン向けのアップデート内容レポート作成 → iOS 申請。Android の回帰確認は iOS リリース後。

16:00 - v1.3.0 の BGM 修正(項目3)と、v1.3.0 に埋まっていた背景検知リグレッションの発見・修正

最大の成果: 出荷したら深刻だったリグレッションを実機検証で発見し修正した。

やったこと(アプリ本体 study-timer-unity、CodeCommit master に 2 コミット):

  • 実機検証環境を構築: Unity バッチビルド → Xcode ビルド(Thomson team JFBRCYHN24 の自動署名)→ devicectl で iPhone 14 に dev ビルドを投入。画面操作は Mac の「iPhone ミラーリング」+ phone-harness(OCR・HIDタップ)、ログは devicectl --console と USB の idevicesyslog を併用
  • B4(ループの継ぎ目)を実測: mp3 をデコードして継ぎ目の無音を測定。26 ファイル中 24 に Xing/LAME ヘッダがなくギャップレス再生できない。雨 ambient 166ms(59秒ごと)、鉛筆 ambient 616ms、鉛筆 music は素材末尾に 7.3 秒の無音。You 判断で今回は対象外
  • B1/B1'/B2/B3 を BackgroundAudioPlayer.mm ほかで修正(コミット 6418a024d):
    • 割り込み(AVAudioSessionInterruptionNotification)と経路変更(RouteChangeNotification)の observer を追加。割り込み終了で自動再開
    • 再生状態を musicActive/subActive(鳴っているべきか)と userPausedMusic/userPausedSub(ユーザーが止めたか)に分離 → 割り込み後も再開ボタンが効く(B1')
    • イヤホン抜去は pausedByRouteDisconnect を立てて一時停止し、再接続かユーザー再開まで自動再開しない
    • StopAudioAfterDelay / StopSubAudioAfterDelay を dispatch_block_t 化して CancelScheduledPlayback で取り消せるように(B2)
    • 起動時に Playback へ強制するのをやめ、実 BGM を鳴らすときだけ Playback + MixWithOthers に切替(B3・コールドスタート)
  • Codex(gpt-5.6-sol、xhigh)にコードレビューを依頼し、4 指摘のうち妥当な 2 件を反映(フォアグラウンド復帰時の再開保険、イヤホン抜去フラグ)。残る 2 件は設計論点として You 判断で現状維持
  • バックグラウンド検知の全滅を発見・修正(同コミット)。詳細は下記

発見したリグレッション(v1.3.0 で作り込んでいた):

前セッションで ApplicationBackgroundObserver から OnApplicationFocus を削除し OnApplicationPause のみにしたが、その根拠「iOS は applicationWillResignActive で UnityPause(1) が呼ばれる」が誤りだった。生成された UnityAppController.mm の実装は:

UnitySetPlayerFocus(0);                            // OnApplicationFocus は無条件で発火
if (!UnityGetUseCustomAppBackgroundBehavior()) {
    UnityPause(1);                                 // 背景音声アプリでは呼ばれない
}

このアプリは UIBackgroundModes=audio なので UnityPause(1) が呼ばれず、iOS では OnApplicationPause が一度も発火しない。削除した OnApplicationFocus が唯一の背景検知手段だった。結果、iOS で「バックグラウンド中の経過時間の加算」「BGM の予約」「ローカル通知の予約」「復帰時広告の判定」がすべて不発になっていた。

修正: iOS=OnApplicationFocus / Android=OnApplicationPause のプラットフォーム分岐。Android は従来どおりなのでキーボード誤検知の問題も再発しない。

気づき(再利用価値が高い):

  • devicectl --console はバックグラウンド遷移でデタッチし、Unity の Debug.Log も遷移前後で取りこぼす。 背景の挙動を観測するには使えない
  • idevicesyslog(USB)は切れないが、NSLog の値が <private> に伏せられる。 os_log の %{public}s で出すネイティブ関数を挟めば全部残る。この経路を作って初めて「OnApplicationPause が1行も出ない」=背景検知が死んでいる、と確定できた
  • ログの「出ていない」を根拠にする前に、ログ経路自体の生存確認(対照キャプチャ)が要る。 今回は途中で 2 回誤った結論に飛んだ(IsPauseTimer 残留説、確認ダイアログのキャンセル漏れ説)。いずれも欠落ログを根拠にしたのが原因
  • B1 の真因はハンドラではなく setActive:YES の失敗だった。 アラームを止めた直後は割り込み元がセッションを握っており「Session activation failed」になる。失敗中に play しても無音のままなので、成功するまでリトライする必要がある。コードだけ見ると「ShouldResume で play を呼ぶ」で正しく見えるため、実機ログがなければ発見できなかった
  • 他アプリの音が止まるのは setActive ではなく setCategory(Ambient→Playback のカテゴリ「変更」)の瞬間。 MixWithOthers を付けていても変更時は一度割り込む。逆に既に Playback なら変更が起きず共存する。このため「実BGM=置換」は起動後の初回だけという順序依存になる(You 判断で現状維持)
  • Unity の C# を変更すると IL2CPP 再生成でビルドが長い。.mm だけの変更なら Builds/iOS 配下へコピーして xcodebuild するだけで済む

次回やること: .devnotes 反映後に CodeCommit へ push(未実施)。Firebase コンソールで Remote Config の 6 パラメータ作成。Android 実機での回帰確認(分岐により従来どおりのはずだが未確認)。両 OS 申請。

2026-09-03

21:26 - v1.3.0 の実装(記録消失・復帰時広告・Remote Config・イベント計測・Crashlytics・バージョン)

やったこと(アプリ本体 study-timer-unity、CodeCommit master に 6 コミット push 済み):

  • 着手前に unity リポジトリの作業ツリーを掃除: .gitignore を Unity 標準に整備し、追跡されていた Logs / UserSettings / .DS_Store 41 件を追跡から外した(37cec3faa)
  • 1. 記録消失の修正(85d8892e6): TimerTab.MoveToResultScene で SQLite への保存を ResetTimer より先に実行し ID を記録画面へ渡す。未確定レコード(PendingFocusHistory)を ES3 に保持して起動時に存在確認・再登録、保存直後に落ちた場合は ID を再利用して二重登録を防ぐ。完了画面の「閉じる」で記録画面も閉じる
  • 2. 復帰時広告の是正(cd514257f): AdConfig を新設(タイマー中は非表示 / 滞在 5 分未満は非表示 / 前回広告から 30 分 / 1 日 3 回)。ReturnAppInterstitialAdManager.Show に判定を追加し、記録画面・完了画面・ペイウォール・チュートリアル表示中も抑制。ApplicationBackgroundObserver は OnApplicationPause のみで判定
  • 4. Firebase Remote Config(d6c992208): SDK 13.4.0 の RemoteConfig パッケージをインポート、RemoteConfigManager で AdConfig の 6 値を上書き(不正値・範囲外は既定値へ)。通常インタースティシャルの間隔(既定 0)と広告後ペイウォールの間隔(既定 7 日)も AdConfig 経由に
  • 5. イベント計測(dbaef6f94): AnalyticsUtil を新設し timer_start / timer_complete / record_saved / paywall_view / paywall_select / purchase_complete / app_return を送信。ペイウォールの 8 導線に source を付与。purchase_complete は「このセッションで購入を開始した」ときだけ送る
  • 6. Crashlytics(babdd1af7): SDK 13.4.0 の Crashlytics パッケージをインポート(NDK 版、ANR も収集)。CrashlyticsUtil を新設し、記録の保存経路にパンくずログ。ReportUncaughtExceptionsAsFatal = true
  • 7. バージョン: ProjectSettings を 1.2.4 → 1.3.0、iPhone ビルド番号 1.3.0、AndroidBundleVersionCode 29(未コミット)
  • 各ステップで Unity 2022.3.62f2 のバッチモードでコンパイル確認(エラーなし)。実機テストは後回し
  • 別 PC の作業コピーと App Store Connect を確認し、「7 か月前の iOS アップデート」= 1.2.3(2026-01-31 公開)が CodeCommit に入っていることを確認。CodeCommit・この Mac・別 PC はすべて同じ地点だった

決めたこと: 記録日付は開始日のまま(DECISIONS.md)。3 の BGM 修正は後回し(順序変更)

気づき:

  • iOS は applicationWillResignActive で UnityPause(1) が呼ばれるため、コントロールセンター程度の割り込みでも OnApplicationPause が発火する。調査メモの「OnApplicationFocus が直接原因」は iOS では不正確で、短い離脱の除外は滞在秒数の閾値で行う必要がある。OnApplicationFocus の削除が効くのは Android(キーボード開閉)
  • Firebase の .unitypackage をバッチモードで import すると、EDM4U の DLL が「無効」状態の .meta で上書きされ、コンパイルエラーで Version Handler が動けない循環になる。コミット済みの .meta を git checkout で戻せば解消する
  • EDM4U はバッチモードで自動解決しない。一時的なエディタスクリプトから PlayServicesResolver.ResolveSync(true) を呼ぶと同期で解決できる。解決のたびに管理ブロック内の namespace "com.unity3d.player" が消えるので、本体の android {} ブロックに置いた
  • CodeCommit への push は AWS_PROFILE=thomson-ik が必要(default プロファイル無し)
  • GA4 でカスタムイベントのパラメータを分析に使うには、リリース後にカスタム定義の登録が必要

次回やること: 7 のコミット → 3 の BGM 修正(イヤホン抜去時の挙動は保留)→ 実機テスト → 両 OS 申請。Firebase コンソールに Remote Config の 6 パラメータを作成。CodeCommit → github.com への移行は v1.3.0 完了後

19:45 - 運用分析9月版・広告分析・iOS不具合調査、アップデート提案の合意

やったこと:

  • 運用分析レポート9月版(勉強タイマー運用分析とアップデート計画_2026-09.html)を作成。ASC・GA4・Play Reporting API・Play GCS・Apple 財務レポートを再取得。iOS 月間アクティブ 8月 7,750(前年同月比 −39%)、新規 −54%、広告表示/セッション 0.7 → 1.0 回、クリック率 10% → 31%、iOS 課金 7月会計期 ¥146k(1月 ¥218k)、Android ANR 0.64%(閾値 0.47% 超過)
  • App Store 表示評価の誤記を訂正(7月版の 4.16 は記述レビュー平均。ストア表示は 4.5 / 6,174件)。Google Play は 4.3。Android は 1.2.4 配布済みなので次版は 1.3.0
  • iOS の「記録した時間が消える」「BGM が止まる/途切れる」の原因をコードから特定し notes/2026-09-03_iOS_記録消失とBGM停止の調査.md に記録(保存順序の逆転、復帰時広告との重なり、割り込みハンドラ欠如、取り消せない停止予約、カテゴリ競合、MP3 ループ継ぎ目)
  • トムソン提供の AdMob レポート(iOS、約31日分)を分析し 勉強タイマー広告現状レポート_2026-09.html を作成。復帰時ユニット(AdMob+Pangle)が収益の 63%。外部入札ネットワークの CTR が 35〜76%、TikTok 遷移は復帰時ユニットの Pangle で確定。打ち手ごとの減収見込みを算出(Pangle 除外 ¥3k、AppLovin 除外 ¥5〜14k、復帰時広告の条件変更 ¥20〜30k)
  • トムソンへのアップデート提案(復帰時広告の是正・悪質広告の排除・記録消失とBGM停止の修正)の文面をレビュー → 承認された
  • Play Console のデータ取得スクリプト(stdlib のみ、SA の JWT を openssl で署名)を作成。手順は CLAUDE.md に記録
  • data/ios-iap-monthly.csv(財務レポート由来 2026-04〜07 会計期)と data/admob-ios-by-adsource-2026-08.csv を追加

決めたこと: DECISIONS.md 参照(v1.3.0 の内容、Pangle 先行除外、復帰時広告の初期条件、指標の正)

気づき:

  • GA4 の ad_impression は AdMob より 27% 少ない(計測漏れ)。広告指標は AdMob レポートを正とする
  • AdMob の「ブロックのコントロール」は Google 配信にしか効かない。Pangle・AppLovin のクリエイティブは各社の管理画面かメディエーション除外でしか絞れない
  • 復帰時広告の制御は 60 秒クールダウンだけで、OnApplicationFocus でも発火するためコントロールセンターの開閉でも出る
  • 広告収益はアクティブユーザー 1 人あたり月 ¥7.4。年間プラン 1 本(Partner Share ¥2,040)が 276 人分。広告で課金見込み客を失うのは割に合わない、という説明が使える
  • Apple 財務レポートは会計月(4-4-5週)。GA4 の in_app_purchase(iOS のみ自動)は課金の先行指標になる

次回やること: v1.3.0 の実装(WIP.md の順序で)。トムソン側の Pangle 除外と確認事項の返答待ち

2026-07-15

21:43 - 運用コンテキスト整備とデータソース接続(7/14夜からの継続セッション)

やったこと:

  • リポジトリ整備: study-timer-unity/(アプリ本体、独立gitリポジトリ)を .gitignore に追加し、ops リポジトリと分離
  • 収益データ整備: 収益レポートHTMLから35ヶ月分を抽出し data/revenue-monthly.csv を作成。累計約435万円、iOS 94%、2024-11 の iOS 課金開始が転換点
  • App Store Connect MCP 疎通確認。売上レポートは app_id 絞りだと IAP 行が漏れる・finance report は Apple 会計月(2026-03=暦2025年12月)という罠を発見し CLAUDE.md に記録。finance report と収益CSVの突合一致(¥128,962 ≒ ¥128,963)で課金列が Partner Share ベースと判明
  • App Store レビュー473件を分析: 2026H1 に評価急落(半期平均 3.72、2026-05 は 2.83)。主因は広告不満(言及率 15%→41%)。セッション中割込・復帰時広告頻度・TikTok飛ばし・不快クリエイティブが具体点
  • Google Play レビュー・基本情報を記録(評価 3.86。バックグラウンド再生不可など Android 固有指摘)
  • GA4 接続: プロパティ 347054291。プロジェクト専用 .mcp.json で切替(グローバルは Football NEXT 用のまま)。GA 管理者に SA 追加を依頼し 7/15 完了、runReport 200 確認
  • Play Console 接続: 専用 SA play-console-mcp 新設、androidpublisher / playdeveloperreporting 有効化。アプリ権限追加でレビュー API・Android vitals 開通。実測: クラッシュ率28日加重 約0.5%、ANR率 約0.43%(要注意閾値 0.47% に近い)
  • 統計CSV用 GCS バケットはアカウントレベル権限の反映待ち(自動監視を仕掛け済み)

気づき:

  • 収益ピーク(2026-01 ¥328k)後の減少と評価急落が同時期。広告圧と評価のトレードオフが顕在化している
  • gcloud 標準クライアントでは analytics スコープの ADC ログインがブロックされる。自前 OAuth クライアントで回避可能だが、今回は GA 管理者への SA 追加で解決

次回やること:

  • GCS 疎通確認 → インストール数 CSV 取得テスト
  • 残りコンテキスト受け渡し: 広告構成 / 課金設計 / アップデート計画

最近のコミット

README

study-timer-ops

概要

(記入予定)

セットアップ

(記入予定)

使い方

(記入予定)