# Files changed · PR \#23127 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23127/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(web\): paused sidebar videos keep their place during commands](https://git.cafe/pingdotgg/t3code/pull/23127.md)

Version: 5

Head commit: d8d674784e4279e847b450e5908dc29db17a6be0

Base commit: 70e5e384dab96a1d1e4a253ee5a9415ed5d8ce30

Comparison base: 968dd51704441138559a3fe9c348719f2b11e0c6

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/server/src/assets/AssetAccess.test.ts

Status: modified; +29; −0

```diff
@@ -560,6 +560,35 @@
       }).pipe(Effect.provide(layerTest)),
   );
 
+  it.effect("reports one media revision per file content across re-signed URLs", () =>
+    Effect.gen(function* () {
+      const fs = yield* FileSystem.FileSystem;
+      const path = yield* Path.Path;
+      const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-media-revision-" });
+      const filePath = path.join(root, "recording.mp4");
+      yield* fs.writeFileString(filePath, "original");
+      const input = {
+        resource: {
+          _tag: "media-file" as const,
+          threadId: ThreadId.make("thread-1"),
+          path: filePath,
+        },
+      };
+      const original = yield* issueAssetUrl(input);
+      expect(original.revision).toBeDefined();
+      expect((yield* issueAssetUrl(input)).revision).toBe(original.revision);
+
+      yield* fs.writeFileString(filePath, "in-place edit");
+      const edited = yield* issueAssetUrl(input);
+      expect(edited.revision).not.toBe(original.revision);
+
+      const replacement = path.join(root, "replacement.mp4");
+      yield* fs.writeFileString(replacement, "in-place edit");
+      yield* fs.rename(replacement, filePath);
+      expect((yield* issueAssetUrl(input)).revision).not.toBe(edited.revision);
+    }).pipe(Effect.provide(layerTest)),
+  );
+
   it.effect("keeps in-place edits readable but requires a new URL after atomic replacement", () =>
     Effect.gen(function* () {
       const fs = yield* FileSystem.FileSystem;
```

## apps/server/src/assets/AssetAccess.ts

Status: modified; +14; −0

```diff
@@ -346,6 +346,14 @@
     Effect.orElseSucceed((): ImageDimensions | null => null),
   );
 
+/**
+ * Re-signing an unchanged file must not reload a paused player, so clients compare this instead
+ * of the URL. Stat identity is enough: an in-place write moves mtime/ctime and a replacement
+ * moves the inode, and hashing a large video on every request would be too slow.
+ */
+const mediaFileRevision = (info: OpenMediaFile["info"]) =>
+  [info.dev, info.ino, info.size, info.mtimeNs, info.ctimeNs].join(":");
+
 const finalizeAbsoluteMediaFileAsset = Effect.fn("AssetAccess.finalizeAbsoluteMediaFileAsset")(
   function* (input: {
     readonly requestedPath: string;
@@ -378,6 +386,7 @@
                 : Effect.succeed(null),
               (dimensions) => ({
                 identity: { device: file.info.dev.toString(), inode: file.info.ino.toString() },
+                revision: mediaFileRevision(file.info),
                 dimensions,
               }),
             ),
@@ -403,6 +412,7 @@
       },
       fileName: path.basename(canonicalFile),
       imageDimensions: opened.dimensions,
+      revision: opened.revision,
     };
   },
 );
@@ -504,6 +514,7 @@
   let fileName: string;
   let sourcePath: string | undefined;
   let imageDimensions: ImageDimensions | null = null;
+  let revision: string | undefined;
 
   switch (input.resource._tag) {
     case "media-file": {
@@ -530,6 +541,7 @@
       claims = finalized.claims;
       fileName = finalized.fileName;
       imageDimensions = finalized.imageDimensions;
+      revision = finalized.revision;
       break;
     }
     case "workspace-file": {
@@ -572,6 +584,7 @@
         claims = finalized.claims;
         fileName = finalized.fileName;
         imageDimensions = finalized.imageDimensions;
+        revision = finalized.revision;
         break;
       }
       const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(draftWorkspaceRoot).pipe(
@@ -827,6 +840,7 @@
     expiresAt,
     ...(sourcePath !== undefined ? { sourcePath } : {}),
     ...(imageDimensions !== null ? { imageDimensions } : {}),
+    ...(revision !== undefined ? { revision } : {}),
   };
 });
 
```

## apps/web/src/components/files/FilePreviewPanel.tsx

Status: modified; +6; −3

```diff
@@ -289,10 +289,13 @@
       void refreshAssetUrl().catch(() => undefined);
     },
   });
+  // Older servers send no content revision, so any workspace change counts as one.
+  const revision =
+    (assetUrl._tag === "Success" ? assetUrl.revision : undefined) ?? props.workspaceMutationId;
   const revisionSuffix =
-    props.workspaceMutationId === null
+    revision === null
       ? ""
-      : `${assetUrl._tag === "Success" && assetUrl.url.includes("?") ? "&" : "?"}workspace-revision=${encodeURIComponent(props.workspaceMutationId)}`;
+      : `${assetUrl._tag === "Success" && assetUrl.url.includes("?") ? "&" : "?"}workspace-revision=${encodeURIComponent(revision)}`;
   const latestUrl = assetUrl._tag === "Success" ? `${assetUrl.url}${revisionSuffix}` : null;
 
   return (
@@ -301,7 +304,7 @@
         src={latestUrl}
         sourceFailed={assetUrl._tag === "Failure"}
         label={props.name}
-        revision={props.workspaceMutationId}
+        revision={revision}
         preload="metadata"
         className="flex h-full min-h-0 w-full max-w-5xl items-center justify-center"
         onRetry={refreshAssetUrl}
```

## apps/web/src/components/media/MediaVideoPlayer.browser.test.tsx

Status: added; +79; −0

```diff
@@ -1,0 +1,79 @@
+import { describe, expect, it, vi } from "vite-plus/test";
+
+import { act, render } from "../../../test/render";
+import { MediaVideoPlayer } from "./MediaVideoPlayer";
+
+// No video fixture is checked in, so record about a second of a solid color in the browser.
+async function recordWebm(color: string): Promise<Blob> {
+  const canvas = document.createElement("canvas");
+  canvas.width = 32;
+  canvas.height = 32;
+  const context = canvas.getContext("2d")!;
+  context.fillStyle = color;
+  const stream = canvas.captureStream(0);
+  const [track] = stream.getVideoTracks() as Array<CanvasCaptureMediaStreamTrack>;
+  const recorder = new MediaRecorder(stream, { mimeType: "video/webm" });
+  const chunks: Array<Blob> = [];
+  recorder.ondataavailable = (event) => chunks.push(event.data);
+  recorder.start(100);
+  // Each retry pushes one frame, so the clip grows until enough data has been recorded.
+  await vi.waitFor(
+    () => {
+      context.fillRect(0, 0, 32, 32);
+      track!.requestFrame();
+      expect(chunks.length).toBeGreaterThanOrEqual(10);
+    },
+    { timeout: 5_000 },
+  );
+  const stopped = nextEvent(recorder, "stop");
+  recorder.stop();
+  await stopped;
+  return new Blob(chunks, { type: "video/webm" });
+}
+
+function nextEvent(target: EventTarget, type: string): Promise<void> {
+  return new Promise((resolve) => target.addEventListener(type, () => resolve(), { once: true }));
+}
+
+function videoElement(container: HTMLElement): HTMLVideoElement {
+  const video = container.querySelector("video");
+  if (!video) throw new Error("Expected a video element");
+  return video;
+}
+
+describe("MediaVideoPlayer", () => {
+  // regression: #23089 a paused preview reloaded to its first frame on every re-signed URL
+  it("keeps a paused video's position across re-signed URLs until its revision changes", async () => {
+    const original = await recordWebm("red");
+    // Two object URLs for one blob stand in for re-signing an unchanged file.
+    const signedUrl = URL.createObjectURL(original);
+    const resignedUrl = URL.createObjectURL(original);
+    const changedUrl = URL.createObjectURL(await recordWebm("blue"));
+    const player = (src: string, revision: string) => (
+      <MediaVideoPlayer src={src} label="clip.webm" revision={revision} preload="metadata" />
+    );
+
+    await using screen = await render(player(signedUrl, "content-1"));
+    const video = videoElement(screen.container);
+    if (video.readyState < HTMLMediaElement.HAVE_METADATA) await nextEvent(video, "loadedmetadata");
+    const seeked = nextEvent(video, "seeked");
+    video.currentTime = 0.5;
+    await seeked;
+
+    await screen.rerender(player(resignedUrl, "content-1"));
+    expect(videoElement(screen.container)).toBe(video);
+    expect(video.getAttribute("src")).toBe(signedUrl);
+    expect(video.currentTime).toBe(0.5);
+
+    // An expired held URL falls back to the latest signature instead of failing.
+    URL.revokeObjectURL(signedUrl);
+    const failed = nextEvent(video, "error");
+    await act(() => video.load());
+    await failed;
+    await expect.element(screen.getByLabelText("clip.webm")).toHaveAttribute("src", resignedUrl);
+    expect(screen.container.querySelector("[role=alert]")).toBeNull();
+
+    await screen.rerender(player(changedUrl, "content-2"));
+    expect(videoElement(screen.container).getAttribute("src")).toBe(changedUrl);
+  });
+});
```

## apps/web/src/components/media/MediaVideoPlayer.tsx

Status: modified; +6; −2

```diff
@@ -60,7 +60,7 @@
   const failed = src !== null ? failedSrc === src : sourceFailed;
   const preloaded = preloadedSrc === src || typeof IntersectionObserver === "undefined";
 
-  // Re-signing must not reset the playhead. Changed files refresh once playback pauses.
+  // Re-signing must not reset the playhead. Changed files refresh unless playback is active.
   if (
     playbackSource !== null &&
     playbackSource.revision !== revision &&
@@ -174,7 +174,11 @@
             videoClassName,
           )}
           style={style}
-          onLoadedMetadata={(event) => prepareVideoFirstFrame(event.currentTarget)}
+          onLoadedMetadata={(event) => {
+            prepareVideoFirstFrame(event.currentTarget);
+            // Loaded media holds its source too, so a paused playhead survives re-signing.
+            setPlaybackSource({ src, revision: sourceRevision });
+          }}
           onPlay={(event) => {
             setPlayingVideo(event.currentTarget);
             setPlaybackSource({ src, revision: sourceRevision });
```

## packages/client-runtime/src/state/assets.ts

Status: modified; +2; −0

```diff
@@ -80,6 +80,7 @@
       readonly expiresAt: number;
       readonly sourcePath?: string;
       readonly imageDimensions?: AssetImageDimensions;
+      readonly revision?: string;
     };
 
 export const MISSING_FILE_ACCESS_MESSAGE =
@@ -124,6 +125,7 @@
     ...(result.value.imageDimensions !== undefined
       ? { imageDimensions: result.value.imageDimensions }
       : {}),
+    ...(result.value.revision !== undefined ? { revision: result.value.revision } : {}),
   };
 }
 
```

## packages/contracts/src/assets.ts

Status: modified; +2; −0

```diff
@@ -75,6 +75,8 @@
     TrimmedNonEmptyString.check(Schema.isMaxLength(ASSET_PATH_MAX_LENGTH)),
   ),
   imageDimensions: Schema.optional(AssetImageDimensions),
+  /** Changes only when the file's content may have; the signed URL changes on every mint. */
+  revision: Schema.optional(TrimmedNonEmptyString.check(Schema.isMaxLength(256))),
 });
 export type AssetCreateUrlResult = typeof AssetCreateUrlResult.Type;
 
```
