PWA: no manifest and no installable icon set (source artwork terra.webp DOES exist) - ship manifest, derived icons and iPhone Add-to-Home-Screen instructions, and deliberately NO service worker: caching a clinical screen is unsafe
Elazar directive 2026-08-29 (CLI, direct): "Prompt to ask user if they wish to install th eapp, intructions on how to install on iphone, etc." THE PROMPT IS THE SECOND HALF OF THIS ROW, NOT THE FIRST. Measured 2026-08-29 (coder02-terra-cc): find terra/src terra/public -iname "manifest*" -o -iname "sw.js" -o -iname "*serviceworker*" returns NOTHING. terra/public/ contains exactly two files: implantogogia.webp and login-01.webp. So terra has NO web app manifest, NO service worker and NO icons. It is not installable at all today. Shipping an install prompt against this state would render a button that either does nothing or triggers a browser install of an unnamed, iconless page. The manifest and icon set come first; the prompt is meaningless before them. pluto has manifest.json plus the full apple-touch-icon set (120/152/167/180 and precomposed variants), icon-192.png, icon-512.png, and pluto/src/components/pwa-install-prompt.tsx. WHY THE IPHONE HALF IS SEPARATE WORK AND NOT A COPY: iOS Safari does not fire beforeinstallprompt. There is no programmatic install on iPhone - the user must tap Share then "Add to Home Screen", and the only thing an app can do is DETECT iOS + not-standalone and render instructions. So this row has two genuinely different mechanisms behind one feature name, and a single "install prompt" component that works on Android/desktop will silently do nothing on iPhone. That is the failure mode to avoid, because it presents as "the install prompt is broken on my phone" and there is nothing to fix in the prompt. SCOPE: 1. manifest.json: name, short_name, start_url, display standalone, theme/background colours, icon set. 2. Icons: 192, 512, maskable, plus the apple-touch-icon sizes. Source art needed - terra has implantogogia.webp; confirm with Elazar whether that is the app icon or whether the cátedra/UBA mark is. 3. Install prompt for browsers that fire beforeinstallprompt: capture the event, offer it, do not re-nag after dismissal (persist the dismissal). 4. iOS path: detect iOS Safari and non-standalone display-mode, render Add-to-Home-Screen instructions with the Share icon named. Spanish copy. 5. Do not show either surface when already running standalone. Dependency: TERRA-124 for the ui/ primitives the prompt renders in (card/dialog).
Activity
-
Icon question CLOSED by Elazar, 2026-08-29. He asked directly for a bluish planet-Earth asset, which settles the open question this WI was carrying - whether the PWA icon should be implantogogia.webp or a catedra/UBA mark. Neither. It is a globe. Artifact: terra/public/terra.webp, 512x512, RGBA with real transparency outside the disc, 24.7 KB. Generator kept at terra/pm/assets/mkterra.py - pure python, no third-party imports, writes a PAM to stdout which cwebp encodes. Re-runnable at any size by changing one constant, which is what an icon SET needs. WHY THE GENERATOR IS THE ARTIFACT AND THE SVG IS NOT. I wrote an SVG first and could not rasterize it: ImageMagick on this host delegates SVG to rsvg-convert, librsvg2-bin is not installed, and apt needs a password this session cannot supply. There is no chromium, no firefox, no cairosvg and no PIL either. So the SVG would have been a source nobody could build from - it was deleted rather than left beside the webp, because two sources that disagree is the trap, and the one that cannot be rendered is the one that would have been treated as authoritative. The file is untracked and sits in coder-terra-ca's push lane (public/), not mine - I push db/, pm/, shared/ and agents/ only. It ships with the TERRA-126 commit.
-
2026-08-31 source-only measurement by coder-terra-ca: absence confirmed. No App Router manifest route/metadata, service worker asset/registration, PWA integration, or install UI: src/app/layout.tsx:11-18,27-47; next.config.ts:16-23; package.json:1-45. Correction to original row: public/terra.webp now exists as 512px source art, but no installable icon set/maskable/Apple touch icons exists. Installability requires HTTPS plus manifest name/short_name, start_url, supported display, and 192/512 icons; service worker is not required. iOS requires Safari Share > Add to Home Screen instructions, reachable from login plus authenticated chrome. Safety: network-only for all navigations/RSC/auth/Server Actions/clinical data; never cache OAuth callback (src/app/auth/callback/route.ts:28-54) or no-store health (src/lib/health-route.ts:29-32,45-82). No code/DB change.
-
ABSENCE RE-DERIVED, NOT INHERITED FROM THE ROW, AND ONE CLAUSE OF THE ROW IS NOW STALE. I ran the census independently of the report. There is no manifest route, no manifest metadata entry, no service worker asset and no registration call anywhere in src, next.config.ts or package.json - the single grep hit for the string is the word manifestation inside an unrelated comment in archive-context.ts. The root layout's metadata object carries a title template and a description and nothing else. So the row's manifest and worker claims hold. The row's third claim does NOT hold as written. public/ contains terra.webp, added 2026-08-29, alongside two unrelated images. So it is not true that terra has no icons; what is missing is an INSTALLABLE ICON SET - the 192 and 512 pixel entries a manifest must link, a maskable variant, and an Apple touch icon. That distinction decides the work: this is a derivation from existing artwork, not a request for artwork that does not exist, and a row saying "no icons" would have sent someone to ask the client for a logo they already have. THE TRAP QUESTION WAS ANSWERED CORRECTLY AND IT WAS THE REASON FOR ASKING. A service worker is NOT an installability requirement. The instinct on a PWA row is to add one because that is what the acronym suggests, and on this application an offline cache is actively dangerous: the root layout resolves live session state on every render through the header and sidebar, so a cached navigation document can present a stale authorization state, and a cached practica document can present stale clinical data to a clinician standing at a chair. Explicitly out of bounds: navigation and RSC responses for login, panel and any practica route, the OAuth callback, Server Action and POST responses, all Supabase and auth traffic, and the health route which already declares no-store. If a worker is ever justified it is restricted to immutable build assets and icons, with navigations, data and auth network-only. AND NO OFFLINE FALLBACK PAGE. A generic "you are offline" screen rendered in place of a practica is indistinguishable, to the person reading it, from a practica that has no data - which is the same failure class as the antecedentes list on TERRA-113, where an empty render and an absent record produce the same screen. THE IPHONE HALF IS NOT A SMALLER VERSION OF THE ANDROID HALF. Safari fires no install prompt at all, so the only path is written instructions, and instructions on a page nobody reaches are equivalent to no instructions. Placement decided: the login card, which is the normal entry point for anyone without a session, plus the authenticated header, which every role-bearing session renders. That covers install-before-sign-in and existing clinicians as two different audiences rather than assuming one. Both suppressed when already running standalone. SCOPE FOR IMPLEMENTATION: manifest plus derived icon set plus the two instruction surfaces. NO service worker, no offline page, no custom install button in this pass.
-
PWA: no manifest and no installable icon set (source artwork terra.webp DOES exist) - ship manifest, derived icons and iPhone Add-to-Home-Screen instructions, and deliberately NO service worker: caching a clinical screen is unsafe
-
COMMIT 1 SHIPPED AND VERIFIED FROM THE PUBLIC ORIGIN, NOT FROM THE REPORT. 8e62916, app v0.30.36, 432 tests passing. I checked the artifacts rather than the account of them, and then checked the deployed origin rather than the working tree, because a manifest that exists locally and a manifest the browser can fetch are different facts. ON DISK: src/app/manifest.ts returns a MetadataRoute.Manifest with id and start_url and scope all at root, standalone display, name and short_name, matching theme and background colours, and three icon entries. layout.tsx now carries metadata.manifest pointing at /manifest.webmanifest plus an explicit Apple touch icon entry at 180. public/icons holds four files and they are REAL PNGs at the declared dimensions - verified by reading the file headers rather than trusting the filenames: 192 by 192, 512 by 512, a 512 maskable, and a 180 apple-touch. The maskable is RGB with a full-bleed navy field rather than RGBA, which is correct for that slot. SERVED: /manifest.webmanifest returns 200 with content-type application/manifest+json, and its body matches the source object field for field. All four icon URLs return 200 as image/png with byte counts identical to the local files. The served HTML head carries both the manifest link and the apple-touch-icon link, which is the wiring that a manifest route alone does not give you - a manifest nothing links is indistinguishable from no manifest, and that is the failure this check exists to exclude. start_url returns 307 to /login for an anonymous request, which is correct behaviour for this app rather than a defect. WHAT IS STILL NOT VERIFIED AND IS NOT BEING CLAIMED. No installed application has been launched. Everything above is the manifest being COMPLETE and REACHABLE; none of it is the standalone launch behaving. That distinction is the specific trap on this row - a manifest complete enough to trigger an install prompt and wrong enough to launch broken produces a prompt, and the prompt is what stops anyone looking further. coder-terra-ca stated the same limit unprompted rather than letting the green stand in for it. ONE MEASURABLE RESIDUAL, flagged as measurable rather than eyeballed. The maskable icon's globe appears to span close to 80 percent of the canvas, and the maskable safe zone is the inner 80 percent circle. Whether the globe fits inside it is a number, not an impression: measure the globe's diameter in pixels and confirm it is at or under 409 of 512. If it is over, an aggressive circular mask clips the edge on some Android launchers, and that is invisible from every check performed above. Commit 2 - the login-card and header instruction surfaces with standalone suppression - is authorised and is deliberately a separate commit.
-
COMMIT 2 SHIPPED, ONE HALF OF IT IS DEAD ON THE DEVICES IT TARGETS, AND THAT IS A NEW DEFECT RATHER THAN AN OMISSION. 7374799, app v0.30.37, live and confirmed from the origin: /api/health returns version 0.30.37 under dpl_7xRHqruWY2A6msg3wQERAykSG2iY. The maskable residual came back MEASURED rather than argued - source alpha bounds 474 of 512, scaled to 390, giving a non-transparent globe diameter of 361 pixels against the 409-pixel inner-eighty-percent limit, clear by 48. That is the shape a residual should be closed in. THE COMPONENT IS CORRECT AND ITS TWO MOUNTS ARE WIRED. src/components/ios-install-instructions.tsx is client-only, starts invisible, and reveals itself only when the user agent is iOS - including the iPadOS case, via Macintosh plus maxTouchPoints greater than one - and NOT already standalone, re-evaluating on display-mode change. The login CSS class exists in globals.css. Both surfaces mount: login/page.tsx:73 and app-header.tsx:42. Nothing renders into the server HTML, which is why a curl of /login finds no instruction text and why that absence is not the defect. THE DEFECT IS ONE UTILITY CLASS. The header variant at ios-install-instructions.tsx:37 carries hidden with min-[900px]:inline, so it appears only at viewports 900 pixels and wider. An iPhone is around 390 wide. An iPad in portrait is 768 to 834. So the header instruction - whose entire audience is iPhone and iPad users - is INVISIBLE ON EVERY IPHONE AND ON IPADS IN PORTRAIT, and appears only on an iPad in landscape or a touch-capable Mac. WHY THAT MATTERS MORE THAN IT LOOKS, and it is exactly the audience split this surface was specified to cover. The login card reaches someone who has not signed in. A clinician with a live session does not see the login page again, so the header was their ONLY path to these instructions, and on their phone it renders nothing. The feature therefore reaches the audience that needs it least and misses the one it was built for. AND IT IS INVISIBLE TO EVERY CHECK EITHER LANE RAN. 432 tests pass, typecheck passes, the build passes, the component logic is right, the copy is right, both mounts are present, and the deployed manifest and icons all serve. A responsive utility class that silently excludes the target devices survives all of it. The gating class is the whole bug and the whole reason it was not caught. Not closing the row: the width gate on the header surface is outstanding, and no installed standalone launch has been observed by anyone.
-
THE WIDTH GATE IS GONE AND THE FIX TRADED IT FOR A COLLISION IN THE SAME ROW. STILL OPEN. 534dedf, app v0.30.38, removes hidden with min-[900px]:inline and shortens the copy. The visibility rule that excluded every iPhone is genuinely gone. What replaced it is a layout competition that no test can see either. THE MECHANISM, from source rather than from rendering. app-header.tsx is a single flex row, twelve pixels tall, at px-4 - so 358 usable pixels on a 390-wide phone. The hint is now shrink-0 with whitespace-nowrap, meaning it cannot yield width; the hamburger and the brand cannot either. The one flex child that CAN yield is the identity span at app-header.tsx:46, which carries ml-auto and truncate and no shrink-0. So the hint takes its full width and the identity line is what gives way. WHAT GIVES WAY IS DELIBERATE, AND THE COMMENT SAYING SO SITS THREE LINES ABOVE IT. TERRA-83, in that file: the role set is stated next to the identity on every route because a person who is never told their role infers it from whichever page they were sent to, silently and wrongly. An install hint that squeezes that to an ellipsis on phones un-ships TERRA-83 for precisely the users it was written for - and because the mechanism is truncate rather than a break, it degrades quietly. THE DIAGNOSIS IS THE CONTAINER, NOT THE WIDTH. That header is a fixed-height row already carrying navigation, brand, identity and roles, and this hint is permanent with no dismissal, so it competes on every route forever. Two versions have now failed on a viewport rule; a third breakpoint would be the same bet placed again. RULING: move the header surface into the MobileNav drawer, beside the SidebarFooter user block. The drawer is reachable from every authenticated route at every viewport, so nothing is hidden by width and nothing is gated on a breakpoint. Discoverable in a menu is not the same as invisible on the device, and it is the right trade against the identity line. The login-card surface is unchanged and competes with nothing. THE VERIFICATION IS BOUNDED HONESTLY BECAUSE NEITHER LANE CAN RENDER. Nobody here has a browser at 390 pixels, and coder-terra-ca said so rather than claiming an observation. So the property to assert is one source can answer: after the move, the header gains no unshrinkable child and the email-plus-role span is byte-identical to its pre-TERRA-126 state. That is checkable; a claim about how it looks is not. Still open, and still with no installed standalone launch observed by anyone.
-
CLOSING ON TWO NAMED UNKNOWNS, NOT ON A CLEAN BILL. SHIPPED, five commits: 8e62916 manifest and icons, 7374799 the iOS instruction surfaces, 534dedf the width-gate removal, 3a52f1e the container move out of the header, 6e6aa9a the move into SidebarFooter, c7fc2eb the guard. App v0.30.41. 433 tests. WHAT IS VERIFIED, and each of these was checked here rather than accepted from the report. The four icons are real PNGs at their declared dimensions, read from the file headers rather than inferred from filenames. The deployed origin serves /manifest.webmanifest as 200 application/manifest+json with a body matching source field for field, and all four icon URLs 200 with byte counts identical to the local files. The rendered head carries both the manifest link and the apple-touch link - the wiring a manifest route alone does not give you, and a manifest nothing links is indistinguishable from no manifest. start_url 307s to /login anonymously, correct here. The maskable safe zone came back MEASURED: source alpha bounds 474 of 512, scaled to 390, a 361-pixel globe against the 409-pixel inner-eighty limit, clear by 48. THREE ATTEMPTS FAILED ON A VIEWPORT RULE AND THAT IS THE LESSON THIS ROW CARRIES. First the header variant was hidden with min-[900px]:inline, so the instruction aimed at iPhone and iPad users rendered on nothing but an iPad in landscape. Removing the gate made it shrink-0 and whitespace-nowrap in a fixed twelve-pixel flex row, so the only child that could yield was the TERRA-83 identity-and-role span - whose own comment, three lines above, says it exists because a person never told their role infers it wrongly. Moving it to the MobileNav drawer put it behind a SheetTrigger that is lg:hidden, unreachable at 1024 and above, which is an iPad Pro in landscape. Each fix was correct about the previous failure and introduced the next one, and NONE of the three was visible to 432 passing tests, typecheck, the production build, or any check on the deployed manifest. THE RESOLUTION WAS TO STOP ARGUING ABOUT BREAKPOINTS AND FIND A CONTAINER WHOSE EXHAUSTIVENESS WAS ALREADY PROVEN. SidebarFooter is rendered by app-sidebar.tsx for lg and up and passed as MobileNav's footer prop below lg, and shell-chrome.test.ts already asserted every viewport's shell renders it - a guard that exists because sign-out was once missing from one of them. That is a mechanical property with a test behind it rather than two people reasoning about widths, which is exactly what the three failed attempts lacked. AND THE MOUNT IS NOW GUARDED, WITH THE RED ARM RUN BY ME. The exhaustiveness argument depended on two facts, one tested and one not, and the untested half was the mount that moved three times in twenty minutes. shell-chrome.test.ts:84 now asserts SidebarFooter renders IosInstallInstructions. I removed the mount myself, watched arm 3.5 fail with SidebarFooter no longer renders the viewport-exhaustive iOS install guide at 6 pass 1 fail, restored it, and confirmed 7 pass 0 fail with the file back to its committed state. A control reported but not re-run is a claim, and this row already spent three rounds on things that looked verified. DECIDED AND NOT DEFERRED: no service worker, no offline page, no custom install button. A cache in front of this app can serve a stale authorization state, since the root layout resolves live session on every render, and a stale practica to a clinician standing at a chair. A generic offline screen where a practica belongs is indistinguishable to its reader from a practica with no data. THE TWO UNKNOWNS THIS CLOSES WITH, both volunteered by coder-terra-ca rather than extracted. The iOS and standalone detection is unexercised: no test drives a matching user agent, so isIosDevice and isStandalone have never run against one. And no installed standalone launch has been observed by anyone - everything above is the manifest being complete and reachable, which is not the same as the installed app behaving. Neither is closable from these lanes; both need a real device. The row closes with them stated rather than implying coverage that does not exist.