Fold the connection banner into the notification stack

The offline pill was drawn inside a `flex h-0` container with align-items at
its default stretch, so the pill was stretched to a zero-height box while still
carrying its padding — the "box too small" it was reported as. It is a
persistent notification now, keyed on one id so a poll that keeps confirming
"still offline" replaces the card instead of restacking it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 16:00:56 +02:00
co-authored by Claude Opus 5
parent 44c8825927
commit 6d09500a73
4 changed files with 55 additions and 63 deletions
@@ -0,0 +1,50 @@
import { useEffect, useSyncExternalStore } from "react"
import { ago } from "@/components/Health/queries"
import { connectionStore } from "@/lib/connectionStore"
import { dismiss, notify } from "@/lib/notificationStore"
import { isPortal } from "@/lib/portal"
/** One card, replaced in place, rather than one per poll that confirms it. */
const ID = "connection-offline"
/**
* Says when the installation cannot be reached, and when it was last heard
* from.
*
* Only ever meaningful under a portal: a local install cannot lose contact with
* itself. The screen underneath keeps its last data rather than blanking —
* stale readings with a timestamp are more use than an empty page, which is why
* this leads with when we last heard anything.
*
* Draws nothing of its own: it raises a persistent notification and takes it
* back when the tunnel returns, so being offline is said in the same place as
* everything else the app has to say. Still a component rather than a
* subscription inside the store, because it is what scopes the notice to the
* shell — a wall panel or a kiosk has nobody to read it.
*/
export function ConnectionNotice() {
const connection = useSyncExternalStore(
connectionStore.subscribe,
connectionStore.snapshot,
connectionStore.snapshot,
)
const portal = isPortal()
useEffect(() => {
if (!portal) return
if (!connection.offline) {
dismiss(ID)
return
}
notify(
connection.lastSeen
? `Installation offline — last seen ${ago(connection.lastSeen / 1000)}, reconnecting…`
: "Installation offline — reconnecting…",
"warning",
{ id: ID, persistent: true },
)
}, [portal, connection])
return null
}