PrivacyFence · design mockup · not shipped code

The pending-approval list

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.

Preview

1 · The list, three approvals deep

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.

127.0.0.1:8787/approvals
PrivacyFence live Notifications: on Settings
3 approvals pending
Send email to alice@example.com Write PII 0:24 Gmail·gmail_send_message ·Claude is still waiting — 24s “Sending the Q3 summary you asked me to draft.” Seen 3 times this week — always allow calls like this
Claude asked for 2 things Claude Code · started 14:02
Read “Q3 plan (final).docx” Read 12:41 left Drive·drive_read_file ·Claude has moved on — decide anyway “Checking what the Q3 plan says about headcount before I answer.” Seen 1 time this week
Post message in #product-eng Write ×2 0:47 left Slack·slack_send_message ·Expires in 47s “Letting the team know the plan doc moved.” Identical call requested twice — one decision covers both
Recently decidedView audit log
Read Calendar “Thu 11:00 — 1:1”approved · released2 min ago
Send Telegram message to Daddenied8 min ago
375 × 812
PrivacyFence live
3 pending
Send email to alice@… WritePII Gmail·still waiting — 24s “Sending the Q3 summary you asked me to draft.”
Read “Q3 plan (final).docx” Read12:41 Drive·Claude has moved on

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.

2 · Two clocks, seven states

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.

waiting · inside the hold window
Claude is still waiting — 24s0:24
Deciding now costs nothing: the tool call is still blocked and Claude continues in the same turn.
pending · hold window elapsed
Claude has moved on — decide anyway, then ask it to continue
Not an error. The decision still works; the user just has to nudge Claude, because it will not re-call on its own.
expiring · under 60 seconds
Expires in 47s0:47
Countdown only appears under five minutes, danger colour under one.
expired
Expired — Claude was told no
Greys out, then removes itself after ten seconds. Fail-closed, and said out loud.
auto_accepted · a rule now covers it
Auto-accepted by rule Gmail reads
Any rule created anywhere re-evaluates every pending approval. A row that vanishes must say why.
decided_elsewhere
Approved on another device · 4s ago
The tab stays open for hours. Every silent disappearance is a bug in trust.
released · Claude re-called and got the data
✓ Read Drive “Q3 plan” — approved · released
Closes the gap where the human has approved and, for a while, nothing appears to happen.
stream down
Can't reach PrivacyFence — this list may be out of date.
A page claiming “nothing pending” on a dead stream is worse than a page admitting it does not know.

3 · Notifications, and what they are allowed to say

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.

detail: minimal — the org-mode default
PrivacyFencenow 1 approval pending Open PrivacyFence to review.
detail: standard — the local-mode default
PrivacyFencenow Gmail — send email Write · PII detected · Claude is waiting
a burst, coalesced — never one buzz per row
PrivacyFencenow 3 approvals pending Gmail, Drive, Slack · one ask from Claude Code
phone, locked — org mode, Home Screen web app
9:41 Friday 28 August
PRIVACYFENCEnow 1 approval pending Tap to review.

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.

TierWorks whenMechanismDepends on
0 · in-pagetab open, no permissiontitle badge, favicon dot, aria-live announcementnothing
1 · desktoptab open, permission grantedstream event → registration.showNotification()nothing — no push service
2 · web pushtab closed, phone, device asleepVAPID push → service workerthe browser's push service; HTTPS; on iOS, a Home Screen web app
3 · nativelocal mode, macOS, tab closedmenu-bar badge, local notification from the daemonnothing

4 · After a decision, back to the list

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.

StepWhat happensWhy
1history.replaceState back to /approvalsBack must not walk into a card that no longer exists.
2scroll offset and open groups restoredOn a phone this is the difference between “back to my list” and “find your place again”.
3toast: “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.
4focus moves to the next row's Review button; the row is not auto-openedAuto-advancing into the next card is how a person ends up clicking Allow on muscle memory.
5a 409 becomes a toast, not an error: “Already decided elsewhere — auto-accepted by rule Gmail readsA 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.