Fix mobile/UX bug cluster: video minimize, audio interrupt, scroll reset, redundant box

Four fixes in public/index.html, all reported against recaps.cc on mobile:

- Video minimize no longer shows a black frame on expand. toggleVideoMinimize()
  used to call render(), rebuilding the YouTube iframe inside the display:none
  minimized container, which wedged the IFrame API. Minimize now toggles the
  .results-left.minimized class in place; a !videoMinimized guard on render()'s
  needsMount plus a new ensureYtMounted() (called from the expand paths) keep the
  player from ever being created in a hidden container.

- Background processing no longer interrupts podcast audio or resets the
  transcript scroll. The ~60s relay-credit poll calls render(), which rebuilt the
  <audio> element and chunks-scroll. render() now preserves the live <audio> node
  across the innerHTML swap (replaceWith when the src matches) and restores
  chunks-scroll scrollTop; initPodcastPlayer() is idempotent so the preserved node
  doesn't get duplicate listeners.

- Removed the redundant centered "Processing..." box; the staged pizza-tracker
  breadcrumb already covers that window.

- Added -webkit-overflow-scrolling/overscroll-behavior to .chunks-scroll for the
  mobile can't-scroll-to-top report (best-effort, needs on-device verification).

Ships as 0.2.157. Reviewer pass clean; inline JS syntax checked with node --check.
This commit is contained in:
Keysat
2026-06-15 17:38:32 -05:00
parent 91af0b711e
commit 693bb981ff
4 changed files with 108 additions and 12 deletions
+3 -1
View File
@@ -125,7 +125,7 @@ unsure whether a change is contract-affecting, assume it is and check.
## Current state
**Live on the operator's StartOS box** (app **0.2.156** installed 2026-06-15 + relay **0.2.124**). Note: `recaps.cc` is served from this same box via Start9 Pages + StartTunnel, so a `make install` here updates the public cloud site automatically — there is no separate cloud deploy.
**Live on the operator's StartOS box** (app **0.2.157** installed 2026-06-15 + relay **0.2.124**). Note: `recaps.cc` is served from this same box via Start9 Pages + StartTunnel, so a `make install` here updates the public cloud site automatically — there is no separate cloud deploy.
- **Self-serve purchase COMPLETE — all 5 phases** (`docs/self-serve-purchase-plan.md`). Signed-in cloud users buy Pro/Max themselves: "Pay with Bitcoin" renders an inline Lightning QR on-screen (no redirect); "Pay by card" mints a Zaprite one-time order (the card link shows only when the operator has configured Zaprite). Prepaid 30-day periods; the relay owns tier + expiry; both settle webhooks land at `extendUserTier`. Expiry-reminder emails (7d / 1d / lapsed) ride the existing System SMTP; operator test trigger: `POST /api/admin/reminders/run` with `{test_email}`. Tier cards show the real per-period credit allotment from the relay quota config (this box: Max = 120, Pro = 50).
- **Core-decoupling live** (relay owns cloud tier; `docs/core-decoupling-plan.md`) and **per-tenant subscriptions live** (`docs/per-tenant-subscriptions-plan.md`).
@@ -135,6 +135,8 @@ unsure whether a change is contract-affecting, assume it is and check.
**Also this session — iOS sign-in flake fixed (shipped as 0.2.156, built + installed + verified on the box):** an iPad user hit a spurious "network error" on the first tap of *Send sign-in link*, with the second tap succeeding. Root cause is the classic iOS Safari behavior of dispatching a `POST` onto a pooled keep-alive socket the server/proxy has already closed; unlike a GET it isn't transparently re-sent, so it surfaces as a transport `TypeError`. The existing single 500 ms auto-retry was too quick — it reused the same dead socket. Both sign-in entry points (`public/auth.html` `postWithRetry`, `public/index.html` `fetchWithRetry`) now retry 3× with growing backoff (0 → +400 ms → +1.6 s) to outlast Safari evicting the socket. Frontend-only, no server change; the embedded JS has no test harness. Mitigation not cure — if it ever recurs, confirm via box logs whether `/auth/request-link` is hit once (request never arrived → my diagnosis) or twice (failure on the response path → different bug) before widening the backoff.
**Also this session — mobile/UX bug cluster from the inbox (shipped as 0.2.157, built + installed + verified; reviewer pass clean, no blockers):** four `public/index.html` fixes. (1) **Video minimize → black/needs-refresh:** `toggleVideoMinimize()` called `render()`, which rebuilt the YouTube `#yt-player` iframe inside the `display:none` minimized container and wedged the IFrame API. Now minimize toggles the `.results-left.minimized` CSS class in place (iframe stays mounted); a `!state.videoMinimized` guard on render's `needsMount` + a new `ensureYtMounted()` (called from the expand paths) ensure the player is never created in a hidden container. (2) **Background processing reset transcript scroll + killed podcast audio:** root cause was the ~60s relay-credit poll calling `render()`, which rebuilt the `<audio id="podcast-audio">` and `.chunks-scroll`. `render()` now preserves the live `<audio>` node across the innerHTML swap (`replaceWith` when the src matches — exploits the spec's async "pause on disconnect") and restores `.chunks-scroll` scrollTop; `initPodcastPlayer()` is idempotent (`dataset.inited`) so the preserved node doesn't double its listeners. (3) **Redundant centered "Processing…" box** removed (pizza-tracker breadcrumb already covers that window). (4) **Mobile can't-scroll-to-top:** added `-webkit-overflow-scrolling:touch` + `overscroll-behavior:contain` to `.chunks-scroll` — **best-effort, UNVERIFIED**; it's iOS-Safari-layout-specific and couldn't be reproduced off-device, so it needs an on-iPad check (and a screen recording if it persists). Inline JS syntax verified via `node --check` on the extracted script.
**Pending operator actions:**
1. (optional) Rotate the Gemini key in AI Studio — the purge removed it from the repo, but the key itself is still live. Then delete the pre-purge backup: `rm /Users/macpro/Projects/recap-keyleak-purge-backup.bundle` (it contains the old key).
2. Real-world cloud tests: first on-device Bitcoin purchase (Core tenant → Upgrade → Pay with Bitcoin → badge flips); enable cards (relay "Set Zaprite Connection" + webhook `https://<relay-host>/relay/zaprite/webhook`); eyeball a reminder email (`POST /api/admin/reminders/run` `{test_email}`).