【PWA事件簿】iPhoneのミニUIを消したくて、Androidを巻き込んだら、上下から謎のAI?に挟み撃ちにされた話
こんにちは、OJappです。
今回は、ここ数日間で私が体験した、「PWA(Progressive Web Apps)の仕様の深淵に触れて脳汁が出た後に、ブラウザの過保護なAI?にキレ散らかした」という、実録デバッグドキュメンタリーをお届けします。
もしあなたが「iOSとAndroidの両方で完璧なPWAを作りたい」と思っているなら、この記事は未来のあなたを救う聖書(あるいは最高のエンタメ)になるはずです。

🛑 すべての始まり:3つの実験と、謎の「人格崩壊」
最初は、アプリの体験を極限まで高めるための、ただの「綺麗で無害な実験」のはずだった。
そんなに難しいことではなく、1時間もあれば終わると予想していた。
当時、私がやりたかったのは次の3点。
- 【iPhone】 PWA起動時に出てくる、あの忌々しい「ミニUI(ナビゲーションバー)」を完全に消し去りたい。➡️ 対策:マニフェストに
scope: "/"をガチガチに仕込むことで解決。 - 【Android】 スプラッシュ画面に出る名前をちょっとお洒落に変えたい。➡️ 対策:
nameを 「${short_name} | Petal」に変更。 - 【PWAの未来】
id(識別子)を使って、名刺のアイコンを変更したときに、ユーザーが再インストールしなくてもホーム画面のアイコンが自動アップデートされるか検証したい。
🚨 実験開始。しかし、世界線が歪み始める。
実機を並めて検証を始めると、奇妙な現象が起きた。
- PC Chrome:
idによる自動アップデートが見事に大成功!脳汁が溢れる。 - iPhone: 安定のApple。
idなんて仕様はハナから無視され、1ミリも何も変わらない(知ってた)。 - Android Chrome: ……え?
なぜか、本番用の「Petal」アプリを開いているはずなのに、中身が実験用の「LAB」ページにすり替わっている。アプリの人格が完全に崩壊した。
🕵️♂️ 原因究明:容疑者 id, name, scope。犯人はお前だ
Androidがなぜ狂ったのか。原因は直近でいじった id、name、scope のどれかしかない。泥臭い大捜査線(簡単3Step)が始まった。
- まず
idを消してみる: ➡️ まだ壊れてる。白。 - 次に
nameを疑う: ➡️ いや、ブラウザがnameごときで同一アプリ判定をするわけがない。白。 - ということは、消去法で
scope一択: ➡️ 黒!!!
原因は、「Petal」と「LAB」のどちらのマニフェストにも、良かれと思って scope: "/" を指定していたことだった。
Android Chromeの仕様は恐ろしく優秀で、scope: "/" があると、「そのドメインの配下にあるすべての場所を、1つの同じアプリの領土である」と認識してしまう。その結果、「Petal」も「LAB」も、裏のトークンが別だろうが何だろうが、すべて「同じ1つのアプリ」として上書き・統合されてしまい、Androidで複数アプリとしてホーム画面に追加することが不可能になってしまったのだ。
💥 ここで最凶の「OS対立」が勃発
ここで、PWA開発史上、最も不条理なトレードオフの構図が完成してしまった。
🍏 iPhoneの言い分:「
scopeをガチガチに書かないと、ミニUI出してブラウザに戻しちゃうぞ!」
🤖 Androidの言い分:「scopeなんて書かれたら全部同じアプリとしてまとめちゃうから、複数インストールさせないぞ!」
「scopeが欲しいiPhone」と「scopeが邪魔なAndroid」。1つの manifest.json で共存させようとすれば、どちらかのOSのユーザー体験を生贄に捧げるしかない。まさに詰みのデスゲーム。
👉 PWA×iOSでできること完全まとめ【2026年版】できること・できないことを一発で理解 の解説が役に立ちます。
🛠️ 解決策:存在しないなら、分ければいいじゃない。
「妥協」の文字はない。私は「iPhoneとAndroidで読み込むマニフェスト(のscope)を動的に出し分ける」という禁断のマルチOSハックに踏み切った。
実装はシンプル。Petalではmanifest.jsonを動的に生成しているので、生成前にユーザーがiOSかを判別して、scopeを出し入れします。
【実際のコード】
const ua = request.headers.get("user-agent") || "";
const isIOS =
/iPhone|iPad|iPod/i.test(ua);
// ③ manifest を生成
const manifest = {
name: `${shortName} | Petal`,
short_name: shortName,
display: "standalone",
start_url: ".",
...(isIOS ? { scope: "/" } : {}),
background_color: "#ffffff",
theme_color: "#ffffff",
icons: [
{ src: app.icon_url, sizes: "192x192", type: "image/png"},
{ src: app.icon_url, sizes: "512x512", type: "image/png"}
]
};
🧟♂️ 最終決戦:ゾンビキャッシュの襲来、そして「謎のAI上下挟み撃ち」へ
「これで完全勝利だ!」と思ったのも束の間、Androidの挙動がどうしても元に戻らない。コードは1ミリも間違っていない。そう、スマホChromeの「狂気的なゾンビキャッシュ」が、昨日バグっていた過去の記憶をガチガチに抱え込んで離さなかったのだ。
Chromeの設定から「サイトデータを個別削除」して脳みそを強制クレンジング。……直った。両OSとも、100点満点、完璧な挙動で動き出した!!!
「やってやったわw」
と、勝利の美酒に酔いしれようとした、その瞬間だった。PWAの構造が「100点満点の正解」に戻り、私のデバッグによる試行回数(エンゲージメント)が限界突破したせいで、ブラウザの過保護なAIたちが一斉に覚醒した。
1. 【上からの強襲】謎の「インストールする?」ポップアップ
LABページでガサガサとマニフェストJSONを書き換えてデバッグしている、まさにその最中に、画面の上からフワッと公式のポップアップが出現。

Chrome「条件が完璧に揃ったで!この素晴らしいLABアプリを今すぐインストールするやろ!?」
私「いや、今コード確認しとるから!なんなら邪魔やわ!!ww」
2. 【下からの奇襲】勝手に豆知識をドヤる引き上げ窓
さらにペタルを開くと、文字をタップすらしていないのに、画面の下からにゅっと「Google検索の引き上げ窓」が乱入。デモ用に表示していた「中田太郎」という名前をスキャンして、謎の辞書データを開き出したのだ。

Chrome「たろう:長男につける名。また、長男のこと。(ドヤァ)」
私「いや、太郎が長男なことくらい知っとるわ!!!いらねぇ!!!ww」
さらにトドメと言わんばかりに、アプリ名の「Petal」を勝手に読み取って『花弁(カテゴリ:食品)』として、美味しそうなバラの花びらの写真付きで引き上げてくる始末。
私「ノイズのない静かなデトックス空間作ってんのに、誰が花弁の食用ルートを開拓したいねん!!!ww」

🏁 結論:Googleがデレすぎたら、CSSのワンパンで黙らせろ
バグを直してPWAとして完璧になりすぎた結果、上下からブラウザの過保護すぎるフルパワーの洗礼(お節介)を浴びるという、最高に贅沢な結末を迎えました。
ちなみに、下からの「太郎の語源窓(タッチして検索)」は、画面全体(<body>など)に以下のCSSを1行仕込むだけで、文字のスキャンを拒否して一発で完全沈黙させることができます。
/* お節介AI出入り禁止の呪文 */
.petal-app-body {
-webkit-user-select: none;
user-select: none;
}
「名刺のトークンを消したい」という小さな実験から、OSの暗黒面にぶち当たり、マニフェスト分離でねじ伏せ、最後はGoogleのAI?を手懐けて(物理で黙らせて)終わるという、大満足のデバッグの旅でした。
現場からは以上です。PWA、やっぱり最高に面白いww
関連記事
PWAのmanifest.jsonの基本は、PWAに必要なmanifest.jsonの基本で整理しています。
iPhoneでPWAがどこまで使えるかは、PWA×iOSでできること完全まとめ【2026年版】もあわせて読むと分かりやすいです。
ホーム画面に追加できない原因は、ホーム画面に追加できない原因と解決方法まとめで解説しています。
ちなみに、今回諦めた「iPhoneの id によるアイコン自動アプデ」ですが、PC Chromeで成功している以上、私の設計思想は100%正しいことが証明されています。Appleがいつかこっそり仕様を追従してきたその瞬間、数行のコードでトークン仕組みごとブチ○すための伏線は、すでに私の脳内に完璧に揃っています。