feat(permissions): Phase 5 — favicon + multi-prompt queueing UX
Two distinct polishes finishing the web-permissions feature:
1. **Favicon next to origin** — both in the page-host prompt overlay
AND each row of the Settings → Permissions list. Uses the same
Google s2 favicon service the search-result cards use
(`https://www.google.com/s2/favicons?domain={host}&sz=32`) with
`onerror` falling back to the globe-emoji SVG data-URI for
unreachable origins. Helps the user recognize the requesting site
at a glance instead of parsing the bare URL.
2. **Multi-prompt queueing UX** — replaces the old "stack vertically,
each its own buttons" model. Now: only ONE prompt is visible at a
time, queued by arrival order; resolving the active prompt
automatically advances the queue. A "+N more pending" badge
surfaces queue depth when the head isn't the only request, so the
user knows more are waiting (rather than silently stacking
off-screen if many concurrent permissions fire). The queue lives
entirely in the renderer (`permissionQueue` array +
`activePermissionPrompt` ref) — no backend changes needed.
Renderer (`app/page/page.js`):
* `renderActivePermissionPrompt()` removes any prior DOM and renders
the head of the queue; called on enqueue (when queue was empty)
and on each respond.
* `updatePendingBadge()` keeps the badge live on the active prompt
without re-rendering the whole thing — fires on every enqueue
while a prompt is already shown.
* Dedup: duplicate `requestId` publishes (re-entrant transitions)
are dropped instead of double-rendering.
* `respond()` shifts the current request off the queue and
re-renders, so the "remember" checkbox state is local to the
prompt that owns it (no cross-prompt leak).
CSS (`app/page/index.html`):
* `.permission-prompt-message` is now flex (favicon | text col).
* New `.permission-prompt-favicon` (20px square, object-fit
contain) and `.permission-prompt-pending-badge` (rounded pill,
inline next to origin).
Settings (`app/settings/settings.js`):
* Each row in the Permissions list gains a 20px favicon to the left
of the origin/label/timestamp column. Same Google s2 service +
globe-emoji fallback. New `.permissions-row-favicon` selector
used by the test.
Tests:
* `tests/desktop/permission-prompt.spec.ts` (was 3/3, now 5/5):
- **Queueing test** drives two synthetic `page:permission-request`
publishes back-to-back via `api.pubsub.publish` (skipping the
flaky-in-headless `getUserMedia` path); asserts only ONE prompt
visible, badge shows "+1 more pending", clicking Deny on the
first reveals the second, badge gone.
- **Favicon test** asserts the prompt's `.permission-prompt-favicon`
img has a Google-s2-domain src matching the requesting host.
- Both tests reset stored decisions first since the earlier
"Remember-this-decision" test persists a 127.0.0.1 deny that
would auto-resolve subsequent prompts.
* `tests/desktop/permissions-settings.spec.ts` (was 3/3, now 4/4):
new test asserts the favicon img renders with the origin-derived
src for each row.
* Regression: page-host-fsm 5/5; build clean.
Phase 5 closes out the web-permissions feature. Tasks doc updated.