From a7341397a68572b6d3672bed13b7ca0d23886603 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 6 Sep 2026 21:56:01 +0200 Subject: [PATCH] fix(pwa): restore apple-mobile-web-app-capable so iOS installs standalone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding /rep to an iPad home screen and tapping it opened the main app, not the rep app — while the shortcut URL was still /rep. Cause: Next 16 renders `appleWebApp.capable: true` as the standardised and no longer emits the apple-prefixed name. iOS only reads the apple-prefixed one. Without it the icon is a plain Safari bookmark rather than a standalone app, so tapping it foregrounds Safari on whatever tab was already open — which presents exactly as "it opened the main app instead". This was not specific to /rep. Verified against the running app that /m (Boss Phone) had the same gap, so it has never actually launched standalone on iOS either; /counter likewise. The comment in (counter)/layout.tsx asserting the root layout already supplied this tag was wrong — the root also only set `appleWebApp.capable`. Declared in the root layout so all four surfaces inherit it, and repeated in the (rep) and (mobile) layouts because a child's `metadata.other` replaces the parent's rather than merging into it. Corrected the misleading counter comment. Verified by fetching each route and asserting the rendered head: /rep, /m, /new-order and /dashboard now all emit apple-mobile-web-app-capable="yes" while still resolving their own manifest. Co-Authored-By: Claude Opus 5 --- src/app/(counter)/layout.tsx | 11 +++++++---- src/app/(mobile)/m/layout.tsx | 7 +++++++ src/app/(rep)/rep/layout.tsx | 12 ++++++++++++ src/app/layout.tsx | 17 +++++++++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/app/(counter)/layout.tsx b/src/app/(counter)/layout.tsx index 8cfdf28..ef50b2f 100644 --- a/src/app/(counter)/layout.tsx +++ b/src/app/(counter)/layout.tsx @@ -4,10 +4,13 @@ import { CounterShell } from "./shell"; export const metadata: Metadata = { title: "Counter · WFDASH", manifest: "/manifest-counter.json", - // Standalone display for the installed PWA. iOS reads this from - // via the same value in - // the root layout — duplicating it here so each route segment's - // metadata is self-contained. + // Standalone display comes from `apple-mobile-web-app-capable`, + // which the root layout declares via `metadata.other` and this + // segment inherits (it defines no `other` of its own, so nothing + // overrides it). The previous comment here claimed the root already + // supplied that tag — it didn't: the root only set + // `appleWebApp.capable`, which Next 16 renders under the + // standardised name that iOS ignores. applicationName: "WFDASH Counter", }; diff --git a/src/app/(mobile)/m/layout.tsx b/src/app/(mobile)/m/layout.tsx index 1fd67b3..a19a7d7 100644 --- a/src/app/(mobile)/m/layout.tsx +++ b/src/app/(mobile)/m/layout.tsx @@ -14,6 +14,13 @@ export const metadata: Metadata = { statusBarStyle: "default", title: "Boss", }, + other: { + // Next 16 emits only the standardised `mobile-web-app-capable` for + // `appleWebApp.capable`, and iOS ignores that name — so without + // this the home-screen icon opens as a Safari bookmark rather than + // a standalone app. See the same note in `(rep)/rep/layout.tsx`. + "apple-mobile-web-app-capable": "yes", + }, }; export const viewport: Viewport = { diff --git a/src/app/(rep)/rep/layout.tsx b/src/app/(rep)/rep/layout.tsx index 8fd124d..a6e5c93 100644 --- a/src/app/(rep)/rep/layout.tsx +++ b/src/app/(rep)/rep/layout.tsx @@ -24,6 +24,18 @@ export const metadata: Metadata = { statusBarStyle: "default", title: "Visits", }, + other: { + // `appleWebApp.capable: true` emits the standardised + // `mobile-web-app-capable` in Next 16 — and **iOS only honours the + // apple-prefixed name**. Without this tag the home-screen icon is + // a plain Safari bookmark, not a standalone app: tapping it just + // foregrounds Safari on whatever tab was already open, which looks + // exactly like "it opened the main app instead". + // + // Verified on the running app: only `mobile-web-app-capable` was + // present until this was added. + "apple-mobile-web-app-capable": "yes", + }, }; export const viewport: Viewport = { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 21aa0f0..fe59c85 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -130,6 +130,23 @@ export const metadata: Metadata = { statusBarStyle: "default", title: "WFDASH", }, + other: { + // Next 16 renders `appleWebApp.capable: true` as the standardised + // `` and no longer emits the + // apple-prefixed name — which is the only one iOS actually reads. + // + // Without this, an "Add to Home Screen" icon is a plain Safari + // bookmark rather than a standalone app: tapping it foregrounds + // Safari on whatever tab was already open, which presents as "it + // opened the main app instead of mine". Verified missing on /m and + // /rep against the running app before this was added. + // + // Declared at the root so every surface inherits it (/, /m, + // /counter, /rep). The PWA layouts that define their own `other` + // repeat it, because a child's `other` replaces the parent's + // rather than merging into it. + "apple-mobile-web-app-capable": "yes", + }, openGraph: { type: "website", title: "WFDASH - Business CRM",