What the browser shows once several gated calls can be waiting at once — the human half of §7.1 of the HTTPS connector refactor plan. Static mockup: no daemon behind it, the data is invented, the palette and type are lifted from the shipped approval card so this cannot drift from the real thing.
One horizontal box per pending approval. Connector icon, what is being asked, the badges that decide how carefully you read, and two clocks. Deny is on the row; Allow never is.
Deny is one tap from the row. Allow is never on the row. Denying is fail-closed — the worst case is asking Claude again. Allowing releases data, so it costs a look at the full card, with its preview, its disclosure rows and its PII banner. The same reasoning rules out bulk approve at any list length; bulk deny is offered, including a “deny everything and pause connectors for 15 minutes” panic action.
The hold window (30s) says whether Claude is still on the line. The TTL (15 min) says when the approval dies. They mean different things and must not look alike.
The tab is open and has been for hours. Tier 1 — a service-worker notification fired from the approval stream — needs no push service, no VAPID keys, and nothing leaving the machine. It is the whole answer for a desktop with the page open.
The notification body is a data-release surface. It shows up on a lock screen, in a screen share, on a watch. So: no message body, no file content, no PII match value, no recipient below the detailed level — and a push payload carries no approval data at all. Push sends a content-free tickle; the service worker then fetches the summary from the daemon and builds the text locally, so the push service — Apple's, Google's, Mozilla's — learns the timing and nothing else.
| Tier | Works when | Mechanism | Depends on |
|---|---|---|---|
| 0 · in-page | tab open, no permission | title badge, favicon dot, aria-live announcement | nothing |
| 1 · desktop | tab open, permission granted | stream event → registration.showNotification() | nothing — no push service |
| 2 · web push | tab closed, phone, device asleep | VAPID push → service worker | the browser's push service; HTTPS; on iOS, a Home Screen web app |
| 3 · native | local mode, macOS, tab closed | menu-bar badge, local notification from the daemon | nothing |
Today the decision POST replaces the whole document with “you can close this tab.” Right for one card; wrong for a list someone keeps open all day.
| Step | What happens | Why |
|---|---|---|
| 1 | history.replaceState back to /approvals | Back must not walk into a card that no longer exists. |
| 2 | scroll offset and open groups restored | On a phone this is the difference between “back to my list” and “find your place again”. |
| 3 | toast: “Approved. Claude will get the data on its next attempt.” | Only shown once the hold window elapsed — that is the case where the click was not the end of the story. |
| 4 | focus moves to the next row's Review button; the row is not auto-opened | Auto-advancing into the next card is how a person ends up clicking Allow on muscle memory. |
| 5 | a 409 becomes a toast, not an error: “Already decided elsewhere — auto-accepted by rule Gmail reads” | A rule created anywhere resolves pending approvals out from under an open card. Common, not exotic. |
The best version of this page is one nobody visits. “Seen 3 times this week” is already computed for the card; on the row it becomes a link to the matching Always allow candidate. Every idea that makes a long list more comfortable should be weighed against making the list shorter instead.