From 87d9aad48759879bb4470349212798fa61c66160 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:38:36 +0800 Subject: [PATCH] fix: fix presentation and audited ownership invariants (#338) * Fix presentation and frontend ownership invariants * Fix audited backend ownership invariants --- deploy/release-app.sh | 29 ++ src/client/components/living/Comic.tsx | 1 - src/client/components/living/ComicTooltip.tsx | 32 +- src/client/components/living/Couch.tsx | 18 +- src/client/components/living/Metrics.tsx | 5 +- src/client/components/living/PawnDetail.tsx | 13 +- src/client/components/living/RouteTree.tsx | 9 +- .../components/living/ViewerOverview.tsx | 19 +- .../components/living/participant-color.ts | 10 + src/client/components/living/primitives.tsx | 8 +- src/client/media/host-sfu-route.ts | 4 +- src/client/media/video-frame-proof.ts | 46 +- src/client/media/viewer-playback.ts | 16 + src/client/media/viewer-presentation.ts | 303 +++++------- src/client/media/viewer-quality-evidence.ts | 41 +- src/client/media/viewer-sfu-route.ts | 9 +- src/client/pages/HostPage.tsx | 302 ++++++------ src/client/pages/ViewerPage.tsx | 463 +++++++++++------- src/client/sfu/publisher.ts | 5 + src/client/styles.css | 145 ++++-- src/client/ui/copy.ts | 6 +- src/client/ui/icons.tsx | 1 + src/client/webrtc/stats.ts | 13 +- src/client/webrtc/viewer-peer.ts | 1 + src/server/hybrid-media-router.ts | 26 +- src/server/room-route-controller.ts | 9 +- src/server/room-store.ts | 5 +- tests/copy.test.ts | 4 + tests/hybrid-media-router.test.ts | 87 ++++ tests/living-components.test.ts | 71 ++- tests/metric-cells.test.ts | 23 +- tests/release-app-recovery.sh | 132 +++++ tests/release-app.test.ts | 24 + tests/room-route-controller.test.ts | 46 ++ tests/room-store.test.ts | 25 +- tests/route-transition.test.ts | 87 ++++ tests/route-tree.test.ts | 15 + tests/sfu-client.test.ts | 55 +++ tests/video-frame-proof.test.ts | 27 +- tests/viewer-peer-generation.test.ts | 25 + tests/viewer-playback.test.ts | 21 + tests/viewer-presentation.test.ts | 201 +++++++- tests/viewer-quality-evidence.test.ts | 86 +++- 43 files changed, 1791 insertions(+), 677 deletions(-) create mode 100644 src/client/components/living/participant-color.ts create mode 100644 src/client/media/viewer-playback.ts create mode 100644 tests/release-app-recovery.sh create mode 100644 tests/release-app.test.ts create mode 100644 tests/viewer-playback.test.ts diff --git a/deploy/release-app.sh b/deploy/release-app.sh index 950aa31..573f088 100644 --- a/deploy/release-app.sh +++ b/deploy/release-app.sh @@ -16,6 +16,7 @@ lock='/opt/screener/deploy.lock' public_origin="${SCREENER_PUBLIC_ORIGIN:-}" descriptor="$(realpath -e -- "$1")" stage='' +release_owned=0 cutover_started=0 old_release='' @@ -72,6 +73,29 @@ cleanup_stage() { rm -rf --one-file-system -- "$resolved" } +cleanup_release() { + if [ "$release_owned" -ne 1 ] || { [ ! -e "$release" ] && [ ! -L "$release" ]; }; then + return + fi + if [ -n "$stage" ] && [ -e "$stage" ]; then + return + fi + local resolved parent base active + resolved="$(realpath -m -- "$release")" + parent="$(dirname -- "$resolved")" + base="$(basename -- "$resolved")" + active="$(readlink -f -- "$current" 2>/dev/null || true)" + if [ "$parent" != "$release_root" ] || [ "$base" != "$release_id" ] || \ + [ "$active" = "$resolved" ] || [ -L "$release" ] || [ ! -d "$release" ]; then + printf 'refusing to remove unexpected release: %s\n' "$resolved" >&2 + return 1 + fi + if ! rm -rf --one-file-system -- "$resolved"; then + return 1 + fi + release_owned=0 +} + wait_for_health() { local body='' for _ in $(seq 1 120); do @@ -102,6 +126,9 @@ recover() { systemctl start screener.service || ok=0 wait_for_health || ok=0 fi + if [ "$ok" -eq 1 ]; then + cleanup_release || ok=0 + fi cleanup_stage printf 'deployment_failed=%s recovery_ok=%s current=%s active=%s\n' \ "$code" "$ok" \ @@ -267,6 +294,7 @@ old_asset="$(OLD_RELEASE="$old_release" "$node" --input-type=module --eval ' process.stdout.write(match[1]); ')" +release_owned=1 mv -- "$stage" "$release" stage='' test -d "$release" @@ -310,6 +338,7 @@ fi test -z "$(journalctl -u screener.service --since "$cutover_since" -p warning --no-pager --output=cat)" asset_sha="$(sha256sum "$release/dist/client/$main_asset" | awk '{print $1}')" +release_owned=0 cutover_started=0 trap - ERR HUP INT TERM EXIT printf 'deployment=ok revision=%s release=%s previous=%s artifact_sha=%s manifest_sha=%s asset=%s asset_sha=%s health_ms=%s firewall_sha=%s\n' \ diff --git a/src/client/components/living/Comic.tsx b/src/client/components/living/Comic.tsx index adc11f8..eaebf71 100644 --- a/src/client/components/living/Comic.tsx +++ b/src/client/components/living/Comic.tsx @@ -899,7 +899,6 @@ ${rmBlock( - diff --git a/src/client/components/living/ComicTooltip.tsx b/src/client/components/living/ComicTooltip.tsx index edc36d4..f28df3c 100644 --- a/src/client/components/living/ComicTooltip.tsx +++ b/src/client/components/living/ComicTooltip.tsx @@ -11,9 +11,16 @@ // Styling in styles.css under "comic tooltip" / "glyph draw-in". SSR-safe: // handlers only run in the browser. -import { useEffect, useRef, useState, type ReactNode } from "react"; +import { + isValidElement, + useEffect, + useRef, + useState, + type ReactNode, +} from "react"; import { Comic, type ComicKind } from "./Comic"; import { HintComic, isHintKind, type HintKind } from "./hints"; +import { isCopyKey, say } from "../../ui/copy"; const LONG_PRESS_MS = 500; const TOUCH_HIDE_MS = 1500; @@ -49,6 +56,23 @@ export function ComicTooltip({ const [comicMounted, setComicMounted] = useState(false); const [liveAlign, setLiveAlign] = useState(null); const interactionOpen = hoverOpen || focusOpen || touchOpen; + const disabledTrigger = + isValidElement<{ + disabled?: boolean; + "aria-label"?: string; + label?: string; + title?: string; + }>(children) && + children.props.disabled === true; + const rawDisabledTriggerLabel = disabledTrigger + ? children.props["aria-label"] ?? + children.props.label ?? + children.props.title + : undefined; + const disabledTriggerLabel = + rawDisabledTriggerLabel && isCopyKey(rawDisabledTriggerLabel) + ? say(rawDisabledTriggerLabel) + : rawDisabledTriggerLabel; const mountComic = () => { if (comicUnmountTimer.current !== null) { @@ -163,7 +187,9 @@ export function ComicTooltip({ return ( { pickAlign(); if (event.pointerType !== "touch") { @@ -250,9 +276,9 @@ export function ComicTooltip({ }} onKeyDown={(event) => { if (event.key === "Escape") { + setHoverOpen(false); setTouchOpen(false); setFocusOpen(false); - (document.activeElement as HTMLElement | null)?.blur?.(); } else if ((event.target as HTMLElement).matches(":focus-visible")) { mountComic(); setFocusOpen(true); diff --git a/src/client/components/living/Couch.tsx b/src/client/components/living/Couch.tsx index 2b5ff77..964d14d 100644 --- a/src/client/components/living/Couch.tsx +++ b/src/client/components/living/Couch.tsx @@ -2,17 +2,7 @@ // self carries the green pointer, the host wears a crown (topology only). import { Glyph } from "../../ui/icons"; import { useCopy } from "../../ui/copy"; - -const PAWN_COLORS = [ - "var(--pawn-1)", "var(--pawn-2)", "var(--pawn-3)", "var(--pawn-4)", - "var(--pawn-5)", "var(--pawn-6)", "var(--pawn-7)", "var(--pawn-8)", -]; - -export function pawnColor(key: string): string { - let hash = 0; - for (const char of key) hash = (hash * 31 + char.charCodeAt(0)) | 0; - return PAWN_COLORS[Math.abs(hash) % PAWN_COLORS.length]!; -} +import { participantColor } from "./participant-color"; export function PawnSvg({ color, crown }: { color: string; crown?: boolean }) { return ( @@ -80,7 +70,7 @@ export function Couch({ : `${entry.name} · ${stateLabel}`; const inner = ( <> - +