CLAUDE.md "Audio jitter buffering" section was the longest in the file
(~70 lines of restated rules). Collapse to a 5-rule list with pointers
to memory + tickets for the deep dives. The old structure restated
content that already lived in feedback_audio_jitter_buffer_strategy
and the per-defect tickets.
Telemetry + tickets sections compressed similarly. Same content,
fewer words, plus explicit pointers so a future session knows where
to look for the canonical version of each rule.
Ticket 0001 grows a Cross-references section pointing back to
CLAUDE.md + memory + the pinning test.
Ticket 0002 (firefox camera) updated with the signal-log diagnosis:
every attempt shows "The request is not allowed by the user agent or
the platform in the current context." = NotAllowedError fired
instantly, no permission prompt. Conclusion: firefox has a remembered
"Block" for the origin. picked.cam= is empty so it isn't the saved-
deviceId case. Lays out two prongs: user-side permission reset
(immediate) and code-side NotAllowedError handling (next pass) so the
page surfaces a user-friendly notice instead of just logging the error
text. Cross-refs to CLAUDE.md telemetry section + the catch site.
docs/tickets/README.md gets a "See also: CLAUDE.md" pointer up top so
the loop closes from the ticket index back to the shared context.
Memory side wired bidirectionally: chromium-decoder-anchor and
audio-jitter-buffer-strategy now point at CLAUDE.md + ticket 0001.
- SPEAKER_PLAYOUT_DELAY_SEC bumped to 0.7s; mesh peers no longer
carry inbound audio so the worklet cushion absorbs host-wiggle
on the SFU path unaided.
- New "Chromium decoder anchor" section pins the load-bearing
hidden muted <audio> requirement so a future refactor can't
silently remove it.
- New "Audio receive path — SFU only" section documents the
always-SFU contract + ground rules for any future mesh
reintroduction.
- New "Telemetry — signal-server CLIENT_LOG" section documents
how to grep /var/log/zebra-spaces-signal.log via SSH, the
per-tick line shape, the one-shot session fingerprint, and the
per-service /version endpoints on cors-proxy.uncloseai.com.
- New "Defect tracking — docs/tickets/" section documents the
resolution loop (telemetry → failing test → fix → stamp →
deploy → close).
Captures the 2026-06-04 lesson stack so future readers don't repeat the
"playoutDelayHint=4 should cushion the listener" mistake.
Key points documented:
- jitterBufferTarget ignored for high-bitrate stereo Opus on FF Android
(verified side-by-side: voice 1.8s avg, video 4s, music 0.21s, same target)
- userland AudioWorklet is the reliable cushion
- sticky-started re-arm — emit silence on brief drains, only re-fill after
~267ms sustained silence; otherwise every 2.67ms hiccup tears down playback
- role-aware buffer depth: listener 4s, others 0.5s, mesh always 0.5s
- worklet retarget on role-change (postMessage, not rebuild)
- UI gating: "connecting — buffering 4s" until first started message
- audio priority='high' at sender keeps mic ahead of video keyframe bursts
- HTTP /stream pull is the fallback path (recently un-deadlocked)
The previous .row rule unconditionally pinned column 2 at 1fr, which
stretched whichever child happened to land there. On the screenshot
that meant the 'log out' button got stretched and wrapped its label
across two lines, and the trailing note overlapped buttons it was
supposed to describe.
New rules (now also documented in CLAUDE.md as a style guide so future
authoring is consistent):
- default .row: grid-auto-columns: max-content (everything packs at
its natural width, no stretch)
- :has(> :first-child + input/select): template 'auto 1fr', input grows
- :has(> input/select:first-child): template '1fr', input fills, rest pack
- .row > .note: auto-drops to its own line under the buttons/inputs via
grid-column: 1 / -1
- .row > label: white-space: normal, so long checkbox labels wrap
Applied to zebra-spaces, chat, zebra-audio. CLAUDE.md "Web style
guide — form-row patterns" table lists every supported shape so new
rows reuse the primitive instead of inventing custom layouts.
Two fixes in one — the immediate layout bug from the screenshot (timeline
sliding into column 1 with controls eating ~80% of the viewport) and
the architectural rule that all zebra page layout uses grid.
Layout bug (was: when cameras-col gets .hidden + the global .hidden
utility's display:none !important, the grid auto-placement promoted
.timeline into column 1 and .controls into column 2 → controls took
the 1fr middle track). Fix:
- explicit grid-column: 1/2/3 on cameras-col / timeline / controls so
each child stays in its assigned column regardless of siblings going
display:none
- .cameras-col uses 'display: grid !important' to override the global
.hidden util, then only its content (h2 + #cameras) goes display:none
via separate selectors when the .hidden class is present
- controls track clamped to min(360px, 50vw) so a wide window can't
let the side panel eat the screen-share area
Grid-only refactor:
- every flex container converted: .timeline, .cameras-col, .game-tabs,
.screen-tile, .screen-meta, .tap-play, .camera-tile, .row,
.mod-actions, .invite-banner, .invite-actions, .notice-banner
- chat.html + zebra-audio.html same treatment (.row, .field-row,
.share-box .copy-row, the inline H2 style, .mode-toggle, .dot)
- inline 'style=flex:1' on inputs/meters in chat.html replaced with
'style=width:100%'
- now zero 'display: flex' / 'inline-flex' across all five zebra pages
- CLAUDE.md documents the grid-only rule under web-page authoring
Every live RTCPeerConnection is locked to the SDP it was created with.
Page JS reloads don't reach into existing PCs; only fresh negotiation
does. So when a deploy changes fmtp / rtcp-fb / SFU codec registration,
the user just leaves the space and re-enters — no full tab reload
needed.
Caught during the NACK+transport-cc+usedtx rollout: hard refresh wasn't
required, leave+enter was enough.
reportPath used to print 'you A:B ↔ peer C:D' in path-status and the log,
exposing both participants' real IP addresses to anyone glancing at the
screen. Show DIRECT vs RELAYED and the candidate types
(host/srflx/relay) only; addresses are gone. CLAUDE.md gains a
'Web UI privacy — never display peer IPs' rule so this does not regress.
Add web/host-your-own.html and link it from the chat and how-it-works headers
and footers. make stamp now stamps it alongside the other pages. CLAUDE.md:
turn the deploy flow into an explicit push-BOTH-repos reminder (zebra-report
source + www.unturf.com served) and list host-your-own.html as a deployed page.