fix(web): paused sidebar videos keep their place during commands#23127

open3
main
fix/issue-23089
opened2d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 1d ago

Problem

A local MP4 paused in the sidebar reloads when unrelated read-only agent commands complete. A video paused at five seconds resets to the first-frame preview even though its bytes have not changed.

Change

Return an optional revision from the media file's existing open-file stats and use it for the sidebar video's revision. Keep the loaded video source across unchanged revisions and URL re-signing, while retaining real-file updates, active-playback deferral, retry, and stale-source recovery. Older servers retain the existing workspace-change fallback. HTML, PDF, audio, and image refresh behavior is unchanged.

Scope and approval

Fixes #23089 GitCafe: https://git.cafe/pingdotgg/t3code/issues/23089 Triaged bug via the automated issue flow. Reproduced in the real web app on Linux using a fake provider that executes two actual read-only shell commands. The fix changes media refresh state and optional result metadata; signing claims and authorization are unchanged.

Verification

  • Red-first Chromium regression failed on main: a new URL for unchanged paused media replaced its loaded source. It passes on the fixed head, including stale-source fallback and changed revisions.
  • Whole focused files: AssetAccess.test.ts (25), client-runtime assets.test.ts (1), FilePreviewPanel.test.ts (13), and MediaVideoPlayer.browser.test.tsx (1): 40 tests pass.
  • Scoped typechecks: contracts, client-runtime, server, web: pass.
  • Targeted lint: pass with one pre-existing no-inline-schema-compile warning outside the changed test. Scoped Knip exports check and git diff --check: pass.
  • Two independent code reviews: Codex GPT-6.1-Sol and Claude Opus 5.5 returned MERGE; evidence is checked separately below.

Independent proof audit: SUFFICIENT against the six-row matrix. Full evidence index. Before is merge-base 968dd51704441138559a3fe9c348719f2b11e0c6; after is d8d674784e4279e847b450e5908dc29db17a6be0. Real Linux web app, Chrome154, DPR2, 1440×900 and390×844, light and dark. Separate worktrees and isolated state; same MP4 and scripted commands.

Core result: unchanged paused video remains at5s with zero emptied/loadstart events after four read-only turns; baseline resets to0.1s. Real edits and atomic replacements update the preview; active playback defers the update until pause.

Limitations: desktop/Electron and native mobile were not run. Signed/direct PR video callers, ended release, true signed-token expiry/retry, older-server no-revision fallback and remote/tunnel propagation are runtime-unverified. R5 has focused object-URL test coverage; R6 has no dedicated compatibility test. Caller desktop-light frames differ despite equal paused2s event state; no decoded-frame equality is claimed. These gaps were disclosed to the proof auditor.

R1–R3 recordings and full-viewport milestone sheets

Every row below covers R1, R2 and R3 as chapters in the same recording. SHA under Before is 968dd51704441138559a3fe9c348719f2b11e0c6; under After is d8d674784e4279e847b450e5908dc29db17a6be0. The sheets stack four whole frames: initialized, after unchanged paused5 commands, changed-file refresh, active-play edit. They supplement the recordings; a still cannot prove reload absence.

Settings / R1–R3BeforeAfterDiff
1440×900 lightbefore-1440-light.mp4
before-1440-light-sheet.png
968dd51704441138559a3fe9c348719f2b11e0c6
after-1440-light.mp4
after-1440-light-sheet.png
d8d674784e4279e847b450e5908dc29db17a6be0
core-1440-light-diff0.png
core-1440-light-diff16.png
1440×900 darkbefore-1440-dark.mp4
before-1440-dark-sheet.png
968dd51704441138559a3fe9c348719f2b11e0c6
after-1440-dark.mp4
after-1440-dark-sheet.png
d8d674784e4279e847b450e5908dc29db17a6be0
core-1440-dark-diff0.png
core-1440-dark-diff16.png
390×844 lightbefore-390-light.mp4
before-390-light-sheet.png
968dd51704441138559a3fe9c348719f2b11e0c6
after-390-light.mp4
after-390-light-sheet.png
d8d674784e4279e847b450e5908dc29db17a6be0
core-390-light-diff0.png
core-390-light-diff16.png
390×844 darkbefore-390-dark.mp4
before-390-dark-sheet.png
968dd51704441138559a3fe9c348719f2b11e0c6
after-390-dark.mp4
after-390-dark-sheet.png
d8d674784e4279e847b450e5908dc29db17a6be0
core-390-dark-diff0.png
core-390-dark-diff16.png

Actual unchanged-stage event counts (after initial load, four read-only turns):

SettingsBefore emptied / loadstartAfter emptied / loadstartPaused position before → after
1440-light16 / 100 / 00.1s → 5s
1440-dark16 / 80 / 00.1s → 5s
390-light16 / 80 / 00.1s → 5s
390-dark16 / 80 / 00.1s → 5s

The baseline event totals include intermediate source churn and canceled/coalesced loads from completed commands and checkpoints. They are observations from these runs, not a claim of exactly two loads per command. All final after traces retain source-1 through R1; actual changes adopt sources2/3/4, and source4 persists throughout active playback until pause releases it to source5. Stat hashes remain original across the unchanged stage.

R4 caller recordings

R4 sweeps start before the user upload/send, then local and remote markdown playback/seek2/pause, attachment offscreen→visible, expanded autoplay/seek2/pause/close/reopen. The attachment remains muted, controls=false, preload=metadata; markdown players controls=true. Draft recording opens Files→.t3/fixture.mp4 in an unsent draft and seeks/pauses2s. Draft smoke is one equivalent layout at1440/light as the matrix permits. PR container variants cannot be certified by these chat callers.

Core chapter index (visible caption clock)

Recordingpaused0 commandsseek5 / paused5 commandsin-place / atomic editactive edit / pause
after-1440-dark2.4s, 7.5s12.39s, 12.64s, 17.05s21.61s, 34.01s39.3s, 44.43s
after-1440-light2.09s, 6.71s11.35s, 11.62s, 16.2s20.77s, 30.86s35.97s, 41.13s
after-390-dark2.24s, 6.95s11.35s, 11.56s, 16.01s20.67s, 30.25s35.32s, 39.83s
after-390-light2.17s, 6.67s11.13s, 11.32s, 16.25s20.76s, 31.48s36.43s, 41.34s
before-1440-dark2.13s, 7.02s11.94s, 12.23s, 16.95s21.74s, 32.18s37.35s, 42.39s
before-1440-light2.46s, 7.16s11.94s, 12.2s, 16.98s21.89s, 32.72s37.89s, 42.75s
before-390-dark2.15s, 7.09s11.59s, 11.81s, 16.28s20.77s, 30.43s35.7s, 40.22s
before-390-light2.22s, 6.65s11.28s, 11.67s, 16.83s21.79s, 32.2s37.14s, 42.29s

Full caller and draft chapter timestamps: chapters.md.

What else changed

Diff algorithm: per pixel maximum absolute R/G/B channel difference; bright magenta if >0 or >16 8-bit levels (16/255), black otherwise. No alignment, masking, crop, or subthreshold-equality claim. SHA headers themselves differ and are included. Exact sampled times, native frame sizes and differing-pixel counts are in the linked manifest/bundle.

  • Intended R1 difference: baseline transient blank/loading frames and resets to first preview frame; after stays on the selected flower frame at5s. Source IDs differ after baseline churn. This changes video contents/playhead/controls timing, not panel styling. See each core recording’s R1 captions and first two sheet frames.
  • R2: both replace flower with Sintel and duration5.055→52.208333; reset/loading moments vary. The scene near Sintel’s opening is dark; black or spinner stills are transient refresh samples, not proof of failed decoding. Confirm loadedmetadata/ready4 in the event logs and later playback chapter.
  • R3: both retain the long clip while playing, then return to flower on pause. Exact animated frames differ because playback/command completion timing is not frozen. The ended route was not exercised.
  • Sandbox UI: before breadcrumb/project is issue-23089-before; after is issue-23089. Detached baseline versus fix branch labels and sandbox change counts can differ when their panels are visible. Main sidebar is hidden for core/draft. Caller sweeps may show sandbox file/change sidebar; after contains the seven-file +138/−5 fix while baseline has0. No maintainer projects/private threads are seeded.
  • R4 desktop-light: local signed markdown has currentTime2/ready4 in both logs, but the sampled after image resembles the initial flower frame while before shows the2s frame. Native controls differ too. The cause (rendering/capture timing versus player behavior) is not established; do not certify frame equality from these captures.
  • Time labels, relative ages, worked durations, step elapsed captions, source-ID counts, focus/cursor/hover and native control visibility differ. Browser recording start/stream lag differs slightly. Chat scroll follows the same steps but small content/timing differences shift lines. R4 local/remote videos can show briefly loading at different encoded frames. These are unmasked in both diffs.
  • The capture-only bottom caption and SHA headers are added identically but contain different elapsed times/SHAs/source observations. They can cover the lower composer edge. These are not shipped UI changes.
  • No new product labels, colors, layout, typography or controls are introduced by the fix. This statement is based on the seven-file diff; unmasked image deltas are not all attributable to the feature. Non-feature differences above mean strict frozen-clock evidence equality was not achieved and requires PASS2 judgment.

Rebuildable public data and checks

Original: 23089-original.mp4 — SHA256 0cd83d944a6ca7822b4a8306cecc60a36e859b041f6702c6a1ad9ead78924451,5.055s. Changed: 23089-changed.mp4 — SHA256 b670602fa00934ca27c4351bb0efe7ea7a07fae57284e44226025eeed7c51254,52.208333s. Original is MDN flower; changed is W3 Sintel trailer.

public-evidence-bundle.zip contains public-safe provider adaptations, capture scripts, file-operation/stat/hash transcripts, media events with source IDs only, persisted read-only-command outputs, chapter timestamps, rendering manifest, test outputs and SHA256 inventory. chapters.md provides every narrated step/time. sheets-manifest.json lists every extracted frame/time/diff count. No raw signed URLs, credentials, private thread content, databases or server logs are included.

Focused commands (after checkout’s own vp, Vitest5.0.5): cd apps/server && ../../node_modules/.bin/vp test run src/assets/AssetAccess.test.ts (25 pass); cd apps/web && ../../node_modules/.bin/vp test run src/components/media/MediaVideoPlayer.browser.test.tsx (1 pass); cd apps/web && ../../node_modules/.bin/vp test run src/components/files/FilePreviewPanel.test.ts (13 pass). No repo-wide checks. The player test uses generated WebM/object URLs and asserts source retention, revision replacement, revoked-held-URL fallback src/no alert. It does not establish live signed-URL recovery/decoding.

Implemented by Claude Opus 5.5 through Claude Agent SDK; triage, verification and independent review by GPT-6.1-Sol through Codex.

Checks

Some checks failed

Added 3 commits

  • fix(web): paused workspace videos keep their position when the file is unchanged788b9f5
  • test(web): paused workspace videos keep their position across re-signed URLsab3d98a
  • Merge main into fix/issue-23089d8d6747
Synced from GitHub

Approvability

Verdict: Approved at d8d6747

Macroscope's review found this PR approvable — This focused bug fix adds optional file revisions so unchanged signed media URLs no longer reset paused sidebar videos, while real edits still refresh and active playback remains protected. The contract change is additive and the server, client, and player behavior are covered by targeted tests.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB−7 B (−0.1%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB−7 B (−0.6%)2.0 KiB✅
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB0 B (0.0%)29.3 KiB✅
ClaudeLive turn messages220 (0.0%)8✅

Baseline: 159f2b8 · PR result: d8d6747 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 6ea1a2f7-7952-4361-9cab-58bc1999a3d4
📥 Commits

Reviewing files that changed from the base of the PR and between 70e5e384dab96a1d1e4a253ee5a9415ed5d8ce30 and d8d674784e4279e847b450e5908dc29db17a6be0.

📒 Files selected for processing: 7
  • apps/server/src/assets/AssetAccess.test.ts
  • apps/server/src/assets/AssetAccess.ts
  • apps/web/src/components/files/FilePreviewPanel.tsx
  • apps/web/src/components/media/MediaVideoPlayer.browser.test.tsx
  • apps/web/src/components/media/MediaVideoPlayer.tsx
  • packages/client-runtime/src/state/assets.ts
  • packages/contracts/src/assets.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

📝 Walkthrough

Asset URL results now include an optional media revision derived from file metadata. The client carries this revision to video previews, where the player uses it to identify the loaded source. Tests cover revision changes and video behavior during URL re-signing.

Changes: Media revision flow

Layer / File(s)Summary
Asset URL revision contract
packages/contracts/src/assets.ts, packages/client-runtime/src/state/assets.ts
The asset URL result schema and successful client asset state now support an optional revision. The state mapper copies the revision when present.
Compute and issue media revisions
apps/server/src/assets/AssetAccess.ts, apps/server/src/assets/AssetAccess.test.ts
The server derives revisions from device, inode, size, modification time, and change time. It includes revisions in applicable issued asset results. Tests check repeated signing, in-place edits, and atomic replacement.
Use revisions in video previews
apps/web/src/components/files/FilePreviewPanel.tsx, apps/web/src/components/media/MediaVideoPlayer.tsx, apps/web/src/components/media/MediaVideoPlayer.browser.test.tsx
The preview prefers the asset revision and falls back to workspaceMutationId. The player records the loaded source and revision. Browser tests cover unchanged URL re-signing, expired URLs, and revision changes.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant AssetAccess
3 participant AssetUrlState
4 participant WorkspaceVideoPreview
5 participant MediaVideoPlayer
6 AssetAccess-->>AssetUrlState: Return signed URL and revision
7 AssetUrlState-->>WorkspaceVideoPreview: Provide URL result
8 WorkspaceVideoPreview->>MediaVideoPlayer: Pass URL and selected revision

Possibly related issues

  • pingdotgg/t3code#23089 — The issue describes paused local video previews reloading after unchanged command completions. This change adds per-file revisions and tests that unchanged video content remains loaded during URL re-signing.

Suggested reviewers: maria-rcks, t3dotgg

Merge Risk: ⚪ Minimal · up to d8d67

Video source retention appears consistent with preserving paused playback across URL changes. No actionable merge-blocking issue remains after normal checks.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the main user-visible fix: paused sidebar videos keep their playback position during unrelated commands.
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Description checkPassedThe pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the bug, implementation, issue reference, approval basis, focused tests,…
Full details: Description check

Explanation

The pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the bug, implementation, issue reference, approval basis, focused tests, observed results, UI evidence, limitations, and the agent and harness used.

✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.