Skip to content

fix(app): retain raw media bytes up to 256 MiB for artifact previews - #55018

Merged
kitlangton merged 1 commit into
devfrom
large-media-preview
Oct 13, 2026
Merged

kitlangton merged 1 commit into
devfrom
large-media-preview

Conversation

@kitlangton

Copy link
Copy Markdown
Contributor

Why

Opening a workspace video or other binary media file larger than 25 MiB (such as a 38.4 MB arcade-update-draft.mp4) falls back to the binary placeholder (Binary file · 38.4 MB) instead of rendering in the media viewer. Even though serverSDK.api.file.read already receives the file as a Uint8Array, fileContentFromBytes previously encoded binary media to a base64 string on the main thread and blobUrlFromContent / bytesFromContent decoded that base64 string back into a Uint8Array, which required a tight 25 MiB cap (MAX_MEDIA_BYTES = 25 * 1024 * 1024).

What Changes

Input Before After
arcade-update-draft.mp4 (38.4 MB) fileContentFromBytes drops bytes (> 25 MiB) and ArtifactView renders Binary file · 38.4 MB fileContentFromBytes keeps bytes: Uint8Array with mimeType: "video/mp4", and ArtifactVideo plays the video directly from a Blob URL
Binary media up to 256 MiB Main-thread bytesToBase64 -> base64ToBytes round-trip (up to 25 MiB) or placeholder (> 25 MiB) Zero-copy Uint8Array stored on FileContent.bytes; blobUrlFromContent builds the Blob directly and bytesFromContent returns an independent slice for worker transfer
Binary media above 256 MiB Binary file · <size> placeholder Binary file · <size> placeholder
flowchart LR
  Read["serverSDK.api.file.read (Uint8Array)"] --> FromBytes["fileContentFromBytes"]
  FromBytes -->|"< 256 MiB media"| Direct["FileContent { type: 'binary', bytes, mimeType }"]
  FromBytes -->|"> 256 MiB media"| Placeholder["FileContent { type: 'binary', size }"]
  Direct --> BlobUrl["blobUrlFromContent -> new Blob([view], { type })"]
  Direct --> ViewerBytes["bytesFromContent -> bytes.slice() (for worker transfer)"]
Loading

Demo

Comparison of opening arcade-update-draft.mp4 (38.4 MB) in the file viewer before (origin/dev, b4a57f6849) and after (large-media-preview):

large-media-preview-demo.mp4

Scope

Covers binary media retention in FileContent, cache byte accounting in approxBytes, blob/byte extraction in packages/gui-extensions/src/file/artifact.ts, and raising MAX_MEDIA_BYTES to 256 MiB. Streaming range requests for multi-gigabyte media remain a separate follow-up.

Verification

bun script/sdk-docs.ts
bun run lint:changed
bun run check
cd packages/app && bun test src/workspaces/files/artifact.test.ts src/workspaces/files/content-cache-accounting.test.ts
cd packages/gui-extensions && bun test src/file/artifact.test.ts
cd packages/app && PLAYWRIGHT_PORT=3419 bunx playwright test e2e/regression/review.spec.ts
cd packages/app && PLAYWRIGHT_STORYBOOK_PORT=3294 bunx playwright test --config playwright.components.config.ts extension-
  • sdk-docs: 155 declarations, 645 members, and 190 parameters documented; 0 problems.
  • lint:changed: 0 problems in 8 changed files.
  • bun run check: 35/35 workspace packages passed tsgo typecheck.
  • Unit tests (packages/app and packages/gui-extensions): 22/22 passed.
  • Playwright E2E (e2e/regression/review.spec.ts): 21/21 passed, including the 38.4 MB arcade-update-draft.mp4 video playback and theatre overlay regression case (verified to fail against origin/dev).
  • Component tests (extension-host.spec.ts, extension-graph.spec.ts): 25/25 passed.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Media files over 25 MB (up to 256 MiB) now play in the file viewer instead of showing the Binary file placeholder. I ran the updated review.spec.ts video case on the base, where it fails and the viewer shows Binary file · 38.4 MB, and with this PR, where it passes and the video renders. The unit tests and typecheck pass in packages/app and packages/gui-extensions.

The cache stays bounded: one large entry pushes the other cached files out of the 20 MiB LRU. bytesFromContent returns a copy, so viewers that move the buffer to a worker still behave as before.

Public API: FileContent in the private @opencode/gui-extensions SDK gains an optional bytes?: Uint8Array, and media now comes through bytes with empty content instead of base64 content + encoding: "base64". Only the in-repo helpers (contentBytes, bytesFromContent, blobUrlFromContent) read it, and they are all updated. No server, SDK, plugin or config surface changes.

Small cleanup: the FileContent type in packages/app/src/runtime/server/types.ts is no longer used and could be deleted.

Before

After

@kitlangton
kitlangton merged commit 46e4695 into dev Oct 13, 2026
10 of 11 checks passed
@kitlangton
kitlangton deleted the large-media-preview branch October 13, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant