AIレビューで「判定の軸が widget ごとに違う」と指摘されて直しにいったら、直さないのが正解だった、という短い記録です。
写真をスキャンしている間の待機画面があります。イラストを1枚出して「もう少しだけ 待っててね」と書いてあるだけの画面。
そのファイルにこう書いてあった。
// モバイルとデスクトップで別アセットを使い分ける(プラットフォームごとに最適な構図)。
// - モバイル: 縦長 941×1672。画面全体に BoxFit.cover で敷き詰める。
// - デスクトップ: 横長 1672×941。中央に BoxFit.contain で letterbox 込みで配置。
final isDesktop = Breakpoints.isDesktopScreen(context);コメントは「プラットフォームごと」と書いています。コードは isDesktopScreen、つまり画面の幅で判定しています。
一方、同じ画面のシェル(外枠)と、そこから開く保存シートは OS 軸で判定していた。シェルのコメントはこう。
// シェル選択は画面幅ではなく OS 軸。写真のデータソースが OS で決まり
//(desktop=フォルダ、mobile=カメラロール)、モード選択画面も同じ OS 軸なので、
// ここを幅軸にすると軸がねじれる
PlatformUtils.isDesktopOSこのシェル側の理由(写真のデータソースが OS で決まる)は、6月にウィンドウを狭めたデスクトップでフォルダのパスが渡らず写真が出なくなったバグを直したときの判断と同じ。経緯は「画面を幅で出し分けてたら導線が消えた — 分岐軸が2本ねじれていた話」に書いた。
保存シートのほうには「タブレットも OS 軸で判定する。これは仕様」と書いてあります。こちらは OS の種類で判定しています。
同じ機能の中で軸が2つあります。レビューはここを「iPad はモバイルなのかデスクトップなのか、widget ごとに答えが違う」と指摘していた。
揃えようとして、手が止まった
揃える方向は決まっているように見えた。OS 軸のほうには「幅軸にすると軸がねじれる」「これは仕様」と理由まで書いてあって、判断済みだと分かる。だから待機画面も OS 軸に寄せればいい。
コードを触る前に、この isDesktop が何を決めているかを見た。
final image = Image.asset(
// 「? の左が true なら前、false なら後ろ」。幅が広いときだけ横長の絵
isDesktop ? 'assets/UI/scan_desktop.jpg' : 'assets/UI/scan.jpg',
// 枠へのはめ方も一緒に切替。contain=全体を収める / cover=埋めて切る
fit: isDesktop ? BoxFit.contain : BoxFit.cover,
);どっちの絵を出すかを決めています。縦長の絵と横長の絵、2枚あります。
ここで気づいた。iPad を横向きにして、OS 軸で「モバイル」と判定して、縦長の絵を横長の画面いっぱいに敷くと何が起きるか。BoxFit.cover は隙間が出ないように絵を拡大して、はみ出した分を切り落とします。横に広い画面で横幅を埋めるまで縦長の絵を拡大すると、絵の高さは画面の2倍以上になります。上下が大きく切れて、真ん中の帯しか見えません。
つまり、この判定が答えている問いは「どのOSか」ではなく 「どっちの構図の絵が合う画面か」 だった。それは幅で決まります。
判定の側が正しくて、コメントが誤っていた。「プラットフォームごと」と書いてあったが、実際に見ているのは幅だった。
揃えないほうを選んだ
結論としては、2つの軸は別の問いに答えているので、揃えるのが間違いだった。
食い違っているように見えていたのは、同じ名前(isDesktop)で違う質問をしていたから。
なので直したのはコメントのほう。
// これは仕様(軸の訂正): ここは OS 軸でなく幅軸(isDesktopScreen)。
// 元のコメントは「プラットフォームごと」と書いていたが、コードは幅で判定しており、
// 判定が正しくコメントが誤っていた。決めているのは「どちらの構図の絵を出すか」
// であって「どのOSか」ではない=横に広い画面へ縦長の絵を敷けば、OS が何であっても
// 大きく切れる。同じ機能でも画面のシェルは OS 軸で正しく、
// 2つは別の問いに答えている(iPad 横向きでは「モバイルのシェル+横長の絵」が正解)。
// 軸を揃えるのでなく、違う理由をここに書いておく。「呼び出し元から渡す」も採らなかった
レビューの提案にはもう1つあった。「共通ウィジェットは自分で isDesktop を判定せず、呼び出し元から渡す」。複数の画面から使い回される部品が自分の中で勝手にモバイル判定をするのをやめ、その部品を置く側(呼び出し元)から「今はモバイル」と教えてもらう形にする、ということ。
これはこのプロジェクトの規約でもあります。共通の部品が勝手に判定すると、使う側の意図とズレるから。
でも今回は採らなかった。
決めているのが「自分が持っている2枚の絵のどちらを出すか」である以上、呼び出し元に意見はありません。だから判定は中に置いたままにした。
代わりに、ファイルの置き場所は動かした。この待機画面は1つの機能からしか使われていないのに共通置き場(core/)にあったので、機能の中へ降ろした。「共通ウィジェットは自分で判定しない」の適用対象からも外れます。
学び
名前が同じなので同じ質問だと思い込んだ。確かめ方は、判定の名前ではなくその判定の先で何が分岐しているかを読むことだった。
コメントと実装がズレていた時、今回は実装のほうが正しかった。コメントは書いた時点の理解で止まっていて、実装だけがその後の修正を受けていた。
共通置き場にあること自体も前提が違っていた。1箇所からしか使われていない部品は、そもそも共通ではありません。
判断ログには「『ぶつかっている粒度まで割る』(同じ扱いにしていたものが噛み合わなくなったら、噛み合う単位まで分けて扱う)の逆向き=割れているものを無理に束ねない」と書いた。
この記事は2026年7月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。