From 397ea5f0cfe2dc2af32f25a2e22f076cb4ceb9a1 Mon Sep 17 00:00:00 2001 From: Shannon Atkinson Date: Tue, 25 Aug 2026 15:34:30 -0700 Subject: [PATCH 1/4] style: shared token spine, and a demo install worth photographing PHASE 1 of the restyle. Two pieces of groundwork the seven remaining directions depend on. TOKENS. Twelve values are numerically identical across the app and the website. Six are one brand fact spelled twice and are now marked SPINE on both sides, with the reason no build-time single source is reachable: the Vite-Tailwind and Astro-Tailwind pipelines share no build step. 60-30-10 is written out as explicit section labels so a new saturated colour is a visible decision rather than a quiet addition. One pair was a COINCIDENCE, not a fact: the app's --warn ("reconnecting, degraded, near clip") and the website's --color-cross ("active crosspoint ONLY") had landed on the same literal. Unrelated meanings, and folding them is the mistake -- a designer retuning a crosspoint would move an operator's degraded-stream colour. Marked NOT SPINE on the website side. An earlier pass also nudged the hue by 1 degree to make them numerically distinct; that is reverted, because changing a visual value to encode a non-visual fact does not survive a rounding pass and spends a real colour on a comment's job. DEMO SEED. scripts/demo-seed.sh brings up three live programmes on one SRT port, a 1080p/720p/480p ladder with destinations on different rungs, thirteen destinations across six kinds, live audio with moving envelopes, and five real multitrack recordings. Screenshots of an empty install cannot show what this product is for, and the two bugs in #514 and #515 are invisible without it. Every URL is .invalid and every key is obviously fake; the script runs gitleaks on its own fixture and refuses if it fires. Platform destinations are created DISABLED on purpose -- enabled they sit in failed/reconnecting and photograph as a broken product. Claude-Session: https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL --- scripts/demo-seed.fixture.json | 418 +++++++++++++++++++ scripts/demo-seed.sh | 347 ++++++++++++++++ scripts/demo_seed_driver.go | 713 +++++++++++++++++++++++++++++++++ ui/e2e/capture.config.ts | 15 +- ui/e2e/capture.spec.ts | 306 +++++++++++++- ui/src/index.css | 57 ++- web/src/styles/global.css | 49 ++- 7 files changed, 1875 insertions(+), 30 deletions(-) create mode 100644 scripts/demo-seed.fixture.json create mode 100755 scripts/demo-seed.sh create mode 100644 scripts/demo_seed_driver.go diff --git a/scripts/demo-seed.fixture.json b/scripts/demo-seed.fixture.json new file mode 100644 index 00000000..f7008be0 --- /dev/null +++ b/scripts/demo-seed.fixture.json @@ -0,0 +1,418 @@ +{ + "_": [ + "The demo installation, as data. scripts/demo-seed.sh stands it up and", + "scripts/demo_seed_driver.go applies it; neither of them decides what is in", + "it, which is why this file exists separately from both.", + "", + "TWO RULES GOVERN EVERY VALUE BELOW, and they are not style preferences.", + "", + "1. NO URL HERE MAY RESOLVE. Every hostname is under .invalid, which RFC", + " 2606 reserves so that it can never be delegated. A demo seed that", + " publishes to a real ingest is a broadcast nobody meant to make, and the", + " person running it would find out from their audience.", + "", + "2. NO VALUE HERE MAY RESEMBLE A CREDENTIAL. This repository runs gitleaks", + " over its whole history and has already published an advisory about", + " stream keys escaping into logs. The keys below are deliberately", + " repetitive, low-entropy and self-describing so that a human reading a", + " screenshot, a log line or this file cannot mistake one for the real", + " thing -- and so the scanner does not have to be told to ignore them.", + " scripts/demo-seed.sh runs gitleaks against this file when it is", + " installed, rather than trusting that the rule above was followed.", + "", + "The platform destinations are created DISABLED and that is the honest", + "consequence of rule 1: an enabled destination pointing at a name that", + "cannot resolve spends the whole capture in failed/reconnecting, and a", + "dashboard of red cards is a photograph of a broken product. Disabled they", + "read 'Stopped' -- configured, armed, not started -- which is the state an", + "install is genuinely in ten minutes before a show. Everything that CAN run", + "without leaving the machine does run: the ingests, the rendition ladder,", + "the archive and compliance file destinations, the podcast mix, the", + "meters." + ], + + "settings": { + "recording": { + "enabled": false, + "segmentSeconds": 600, + "maxGb": 500, + "maxAgeHours": 8760, + "minFreeGb": 1 + }, + "preview": { "enabled": true }, + "playout": { "enabled": true } + }, + + "sources": [ + { + "name": "Main Programme — Studio A", + "video": { "lavfi": "testsrc2=size=1920x1080:rate=30", "kbps": 6000 }, + "audio": [ + { + "label": "Host mic", + "role": "mic", + "language": "en", + "lavfi": "sine=frequency=180:sample_rate=48000,volume=volume='0.40+0.28*sin(2*PI*t/4.3)':eval=frame" + }, + { + "label": "Co-host mic", + "role": "commentary", + "language": "en", + "lavfi": "sine=frequency=430:sample_rate=48000,volume=volume='0.30+0.24*sin(2*PI*t/6.7)':eval=frame" + }, + { + "label": "Music bed (licensed)", + "role": "music", + "lavfi": "sine=frequency=980:sample_rate=48000,volume=volume='0.22+0.10*sin(2*PI*t/11.0)':eval=frame" + }, + { + "label": "Crowd / room", + "role": "other", + "lavfi": "sine=frequency=2400:sample_rate=48000,volume=volume='0.10+0.07*sin(2*PI*t/8.1)':eval=frame" + } + ], + "renditions": [ + { + "name": "1080p — primary", + "width": 1920, "height": 1080, "fps": 30, + "videoBitrate": 6000, "maxrateKbps": 6600, "bufsizeKbps": 12000, + "encoder": "libx264", "preset": "ultrafast", "gopSeconds": 2, + "note": "Full-resolution tier. YouTube and the contribution feed take this one." + }, + { + "name": "720p — secondary", + "width": 1280, "height": 720, "fps": 30, + "videoBitrate": 3000, "maxrateKbps": 3300, "bufsizeKbps": 6000, + "encoder": "libx264", "preset": "ultrafast", "gopSeconds": 2, + "note": "The tier most platforms actually want. Twitch and Facebook share it." + }, + { + "name": "480p — mobile", + "width": 854, "height": 480, "fps": 30, + "videoBitrate": 1200, "maxrateKbps": 1400, "bufsizeKbps": 2400, + "encoder": "libx264", "preset": "ultrafast", "gopSeconds": 2, + "note": "Low-bitrate tier for constrained uplinks and the compliance log." + } + ], + "destinations": [ + { + "name": "YouTube — main channel", + "kind": "rtmp", "platform": "youtube", + "url": "rtmp://a.rtmp.ingest.demo.invalid/live2", + "streamKey": "demo-not-a-real-key-demo-not-a-real-key", + "rendition": "1080p — primary", + "enabled": false, + "audioBitrate": 192, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": true, "gain": 0.8 }, + { "track": 3, "enabled": true, "gain": 0.45 } + ], + "normalize": "loudnorm", + "sampleRate": 48000, + "loudness": { "targetLufs": -14, "truePeakDb": -1 } + } + }, + { + "name": "Twitch — primary", + "kind": "rtmp", "platform": "twitch", + "url": "rtmp://lhr.contribute.demo.invalid/app", + "streamKey": "demo-not-a-real-key-demo-not-a-real-key", + "rendition": "720p — secondary", + "enabled": false, + "audioBitrate": 160, + "profile": { + "mode": "matrix", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": false, "gain": 1.0 }, + { "track": 3, "enabled": true, "gain": 0.4 } + ], + "matrix": [ + { "track": 0, "channel": 0, "out": 0, "gain": 1.0 }, + { "track": 0, "channel": 0, "out": 1, "gain": 1.0 }, + { "track": 1, "channel": 0, "out": 0, "gain": 0.85 }, + { "track": 1, "channel": 0, "out": 1, "gain": 0.85 }, + { "track": 3, "channel": 0, "out": 0, "gain": 0.4 }, + { "track": 3, "channel": 1, "out": 1, "gain": 0.4 } + ], + "normalize": "loudnorm", + "sampleRate": 48000, + "loudness": { "targetLufs": -14, "truePeakDb": -1 }, + "excludeRoles": ["music"] + } + }, + { + "name": "Facebook Live — page", + "kind": "rtmp", "platform": "facebook", + "url": "rtmps://live-api-s.demo.invalid:443/rtmp/", + "streamKey": "demo-not-a-real-key-demo-not-a-real-key", + "rendition": "720p — secondary", + "enabled": false, + "audioBitrate": 128, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": false, "gain": 1.0 }, + { "track": 3, "enabled": true, "gain": 0.4 } + ], + "normalize": "loudnorm", + "sampleRate": 48000, + "loudness": { "targetLufs": -16, "truePeakDb": -1 }, + "excludeRoles": ["music"] + } + }, + { + "name": "Kick — mobile tier", + "kind": "rtmp", "platform": "kick", + "url": "rtmp://ingest.demo.invalid/live", + "streamKey": "demo-not-a-real-key-demo-not-a-real-key", + "rendition": "480p — mobile", + "enabled": false, + "audioBitrate": 128, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": true, "gain": 0.7 }, + { "track": 3, "enabled": true, "gain": 0.35 } + ], + "normalize": "auto", + "sampleRate": 48000 + } + }, + { + "name": "Partner CDN — custom RTMP", + "kind": "rtmp", "platform": "custom", + "url": "rtmp://origin.partner-cdn.demo.invalid/live", + "streamKey": "demo-not-a-real-key-demo-not-a-real-key", + "rendition": "1080p — primary", + "enabled": false, + "audioBitrate": 192, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": true, "gain": 0.8 }, + { "track": 3, "enabled": true, "gain": 0.45 } + ], + "normalize": "auto", + "sampleRate": 48000 + } + }, + { + "name": "Master control — SRT contribution", + "kind": "srt", "platform": "custom", + "url": "srt://master-control.demo.invalid:9001?mode=caller&latency=200000", + "rendition": "1080p — primary", + "enabled": false, + "audioBitrate": 256, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": true, "gain": 1.0 }, + { "track": 3, "enabled": true, "gain": 1.0 } + ], + "normalize": "off", + "sampleRate": 48000 + } + }, + { + "name": "Archive — as-transmitted master", + "kind": "file", "platform": "custom", + "url": "archive-main-full-mix.mkv", + "rendition": "1080p — primary", + "enabled": true, + "audioBitrate": 256, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": true, "gain": 1.0 }, + { "track": 3, "enabled": true, "gain": 1.0 } + ], + "normalize": "off", + "sampleRate": 48000 + } + }, + { + "name": "Compliance log — 480p", + "kind": "file", "platform": "custom", + "url": "compliance-log.mkv", + "rendition": "480p — mobile", + "enabled": true, + "audioBitrate": 96, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": true, "gain": 1.0 }, + { "track": 3, "enabled": true, "gain": 1.0 } + ], + "normalize": "auto", + "sampleRate": 48000 + } + }, + { + "name": "Podcast mix — mics only", + "kind": "file", "platform": "custom", + "url": "podcast-mix-mics-only.mkv", + "rendition": "720p — secondary", + "enabled": true, + "audioBitrate": 192, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 }, + { "track": 2, "enabled": false, "gain": 1.0 }, + { "track": 3, "enabled": false, "gain": 1.0 } + ], + "normalize": "loudnorm", + "sampleRate": 48000, + "loudness": { "targetLufs": -16, "truePeakDb": -1 }, + "excludeRoles": ["music"] + } + } + ] + }, + + { + "name": "Commentary Booth — Studio B", + "video": { "lavfi": "testsrc2=size=1280x720:rate=30", "kbps": 3000 }, + "audio": [ + { + "label": "Commentary A", + "role": "commentary", + "language": "en", + "lavfi": "sine=frequency=320:sample_rate=48000,volume=volume='0.34+0.24*sin(2*PI*t/5.1)':eval=frame" + }, + { + "label": "Commentary B (es)", + "role": "commentary", + "language": "es", + "lavfi": "sine=frequency=760:sample_rate=48000,volume=volume='0.26+0.20*sin(2*PI*t/7.9)':eval=frame" + } + ], + "renditions": [], + "destinations": [ + { + "name": "YouTube — Spanish commentary", + "kind": "rtmp", "platform": "youtube", + "url": "rtmp://b.rtmp.ingest.demo.invalid/live2", + "streamKey": "demo-not-a-real-key-demo-not-a-real-key", + "enabled": false, + "audioBitrate": 128, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": false, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 } + ], + "normalize": "auto", + "sampleRate": 48000 + } + }, + { + "name": "Archive — commentary booth", + "kind": "file", "platform": "custom", + "url": "archive-studio-b.mkv", + "enabled": true, + "audioBitrate": 192, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 1.0 } + ], + "normalize": "off", + "sampleRate": 48000 + } + } + ] + }, + + { + "name": "Vertical Cut — 9:16", + "video": { "lavfi": "testsrc2=size=720x1280:rate=30", "kbps": 2500 }, + "audio": [ + { + "label": "Programme mix", + "role": "clean", + "language": "en", + "lavfi": "sine=frequency=250:sample_rate=48000,volume=volume='0.36+0.26*sin(2*PI*t/3.7)':eval=frame" + }, + { + "label": "Music bed (licensed)", + "role": "music", + "lavfi": "sine=frequency=1400:sample_rate=48000,volume=volume='0.20+0.12*sin(2*PI*t/9.3)':eval=frame" + } + ], + "renditions": [], + "destinations": [ + { + "name": "Kick — vertical", + "kind": "rtmp", "platform": "kick", + "url": "rtmp://ingest.demo.invalid/live", + "streamKey": "demo-not-a-real-key-demo-not-a-real-key", + "enabled": false, + "audioBitrate": 128, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": true, "gain": 0.7 } + ], + "normalize": "auto", + "sampleRate": 48000 + } + }, + { + "name": "Archive — vertical clean", + "kind": "file", "platform": "custom", + "url": "archive-vertical-clean.mkv", + "enabled": true, + "audioBitrate": 160, + "profile": { + "mode": "simple", + "tracks": [ + { "track": 0, "enabled": true, "gain": 1.0 }, + { "track": 1, "enabled": false, "gain": 1.0 } + ], + "normalize": "auto", + "sampleRate": 48000, + "excludeRoles": ["music"] + } + } + ] + } + ], + + "_recordings": [ + "CONTIGUOUS ON PURPOSE: each entry starts exactly where the previous one", + "ended. The library groups segments into sessions by the gap between them,", + "so five recordings an hour apart are five one-segment sessions -- which is", + "what a recorder that crashed four times looks like, not what a two-night", + "archive looks like. Two runs here: last night in three segments, and the", + "night before in two." + ], + "recordings": [ + { "startedAgoMinutes": 1290, "minutes": 10 }, + { "startedAgoMinutes": 1280, "minutes": 10 }, + { "startedAgoMinutes": 1270, "minutes": 6 }, + { "startedAgoMinutes": 4020, "minutes": 10 }, + { "startedAgoMinutes": 4010, "minutes": 4 } + ] +} diff --git a/scripts/demo-seed.sh b/scripts/demo-seed.sh new file mode 100755 index 00000000..b04aa4a7 --- /dev/null +++ b/scripts/demo-seed.sh @@ -0,0 +1,347 @@ +#!/usr/bin/env bash +# +# Stand up a believable polyemesis installation locally, and photograph it. +# +# ./scripts/demo-seed.sh seed, capture, leave it running +# ./scripts/demo-seed.sh --no-capture seed only, then hand it over +# ./scripts/demo-seed.sh --out DIR write the screenshots somewhere else +# ./scripts/demo-seed.sh --down remove everything this created +# +# WHY THIS EXISTS. capture.spec.ts drives a real browser against a real server +# and waits for real state before every shot, which is the right design and is +# worth nothing pointed at an empty install. Every install available to point it +# at IS empty -- the OVH box has one source, no destinations and no recordings -- +# so the committed screenshots are of a product with nothing in it. This builds +# the thing worth photographing: three programmes, a rendition ladder, +# destinations across every platform the product supports, live multitrack +# audio, and a recording library. +# +# WHAT IT WILL NOT DO. +# +# NOTHING REACHES A REAL PLATFORM. Every destination URL in the fixture is +# under .invalid, which RFC 2606 reserves so it can never be delegated, and +# the platform destinations are created DISABLED. A demo seed that publishes +# to a real ingest is a broadcast nobody meant to make. +# +# NOTHING RUNS AGAINST YOUR DATA. The server this starts uses a data +# directory THIS SCRIPT CREATES, under $TMPDIR, and refuses to reuse one it +# finds already there without --reset. That is the answer to "run by someone +# on a machine that may already have data": there is nothing to collide with, +# because none of it is where anything else lives. The seeder carries a +# second, independent refusal for the --base case -- see refuseIfOccupied in +# demo_seed_driver.go. +# +# IT LEAVES NOTHING BEHIND. --down removes the containers, the network and +# the data directory. That is the whole of what this creates. +# +# Nothing here runs in CI. It builds an image, renders half a gigabyte of media +# and takes several minutes. + +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +FIXTURE="$ROOT/scripts/demo-seed.fixture.json" +DRIVER="$ROOT/scripts/demo_seed_driver.go" + +PORT="${DEMO_PORT:-8098}" +SRT_PORT="${DEMO_SRT_PORT:-6000}" +WORK="${DEMO_WORK:-${TMPDIR:-/tmp}/polyemesis-demo-seed}" +DATA="$WORK/data" +STAGE="$DATA/.demo-seed" +# docs/media is where capture.spec.ts has always written and where +# docs/media/README.md says the shots live. Overridable because the verification +# run for a change to this script should not overwrite committed artefacts to +# prove it worked. +OUT="${SHOT_DIR:-$ROOT/docs/media}" + +IMAGE="${IMAGE:-polyemesis-demo-seed:local}" +NET="polyemesis-demo-net" +SRV="polyemesis-demo-server" +PUB_PREFIX="polyemesis-demo-pub" + +CAPTURE=yes +RESET=no +DOWN=no +BASE="" + +while [ $# -gt 0 ]; do + case "$1" in + --out) OUT="$2"; shift 2 ;; + --no-capture) CAPTURE=no; shift ;; + --reset) RESET=yes; shift ;; + --down) DOWN=yes; shift ;; + --base) BASE="$2"; shift 2 ;; + -h|--help) sed -n '2,40p' "$0"; exit 0 ;; + *) echo "unknown option: $1" >&2; exit 2 ;; + esac +done + +# Everything this script starts is a container or a directory it made, so +# teardown is exactly those two things. No pkill: there are no host processes to +# kill, and the ones the previous generation of this script tried to kill had +# stopped existing when it moved to containers. +teardown() { + # Named one at a time rather than piped into xargs: BSD xargs has no -r, so + # an empty list would run `docker rm -f` with no arguments and print an error + # on the ordinary path where there is nothing to remove. + local name + while read -r name; do + [ -n "$name" ] && docker rm -f "$name" >/dev/null 2>&1 + done < <(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -E "^${PUB_PREFIX}-|^${SRV}\$" || true) + docker network rm "$NET" >/dev/null 2>&1 || true +} + +if [ "$DOWN" = yes ]; then + echo "==> removing the demo install" + teardown + command rm -rf "$WORK" + echo " containers, network and $WORK are gone" + exit 0 +fi + +command -v docker >/dev/null || { echo "docker is required" >&2; exit 1; } +docker info >/dev/null 2>&1 || { echo "docker is not running" >&2; exit 1; } +command -v go >/dev/null || { echo "go is required (it runs the seeder)" >&2; exit 1; } + +# THE FIXTURE IS SCANNED BEFORE IT IS USED, not after it is committed. +# +# It carries stream keys, and this repository already has a published advisory +# about stream keys escaping. The keys in it are deliberately unconvincing, and +# "deliberately" is a claim that has to be checked by the thing that will fail +# CI rather than by the person who wrote them. Skipped, loudly, when gitleaks is +# not installed: a missing local tool should cost a warning, not the run. +if command -v gitleaks >/dev/null 2>&1; then + if gitleaks detect --no-git --source "$FIXTURE" -c "$ROOT/.gitleaks.toml" >/dev/null 2>&1; then + echo "==> fixture is clean under gitleaks" + else + echo "the demo fixture trips gitleaks. Its fake keys must not resemble real" >&2 + echo "ones -- make them more obviously fake, do not allowlist them." >&2 + gitleaks detect --no-git --source "$FIXTURE" -c "$ROOT/.gitleaks.toml" -v 2>&1 | tail -20 >&2 + exit 1 + fi +else + echo "==> gitleaks not installed; fixture not scanned locally (CI still will)" +fi + +# One password for the seeder and for Playwright's auth setup, generated per +# run. They each used to carry their own literal, and when the two disagreed the +# seeder created the account and the browser then failed on a missing