Skip to content

fix(web,mobile): explain missing file access for host videos - #24637

Draft
juliusmarminge wants to merge 3 commits into
mainfrom
takeover/pr-21088
Draft

juliusmarminge wants to merge 3 commits into
mainfrom
takeover/pr-21088

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Host videos blocked by missing file access now explain how to recover instead of offering a retry that cannot succeed. Reachable videos and ordinary failure retries retain their existing behavior. Generated video failure labels are also excluded from thread-find, matching image placeholders. Fixes #21087.

This is a draft: Android resource-link reopen and actual URI-null retention still lack required paired proof.

Takeover

Credits @BGD-1DAY00 and #21088. Rebuilt on current main because the web renderer and test file moved; retained the original approach and shared explanation.
Added the same explanation to mobile video-link previews, including the iOS native alert. Replaced the obsolete assets test with one regression in the existing renderer suite.

Evidence

Fixture matrix · Web index and recordings · Reproduction sources and full output.
Before 3cc31ed27c; after 0b086662f3. Chromium 148 on Linux/Xvfb, DPR1, reduced motion, fixed clock; every still pixel-identical to a second frame 500ms later. Diff0 amplified ×8; DiffT16/255, no masks. Each Before/After column below uses these SHAs.
What else changed: the denied video text and Retry visibility. The denied image and settled reachable/generic/file-panel controls have zero pixel differences. Interaction narration and timestamps are in the linked recordings.

Row / size / theme Before After Diff0 DiffT (16)
W1-W3 · 1440×900 · dark Before After 7887px / 0.609% 4378px / 0.338%
W1-W3 · 1440×900 · light Before After 7949px / 0.613% 4468px / 0.345%
W1-W3 · 390×900 · dark Before After 8480px / 2.416% 4532px / 1.291%
W1-W3 · 390×900 · light Before After 8538px / 2.432% 4641px / 1.322%
W2 · 1440×900 · dark Before After 0px / 0.000% 0px / 0.000%
W2 · 1440×900 · light Before After 0px / 0.000% 0px / 0.000%
W2 · 390×900 · dark Before After 0px / 0.000% 0px / 0.000%
W2 · 390×900 · light Before After 0px / 0.000% 0px / 0.000%
W4 · 1440×900 · dark Before After 0px / 0.000% 0px / 0.000%
W4 · 1440×900 · light Before After 0px / 0.000% 0px / 0.000%
W4 · 390×900 · dark Before After 0px / 0.000% 0px / 0.000%
W4 · 390×900 · light Before After 0px / 0.000% 0px / 0.000%
W6 file · 1440×900 · light Before After 0px / 0.000% 0px / 0.000%

Supplemental web index · Supplement sources/output. W5 uses a disclosed synthetic signing failure and real fallback video; W6 PR uses synthetic cached PR data in the real shell. W7 removes placeholder highlights while authored text remains findable. One desktop-dark fallback pair differs by two shadow pixels at 1/255 (zero above 16); no pixel-zero claim for that pair.

Row Before still After still Before recording After recording Pixel >0 / >16 Differences
W5-1440-light Before After Before After 0 / 0 diff0amp · diffT
W5-1440-dark Before After Before After 2 / 0 diff0amp · diffT
W5-390-light Before After Before After 0 / 0 diff0amp · diffT
W5-390-dark Before After Before After 0 / 0 diff0amp · diffT
W6-PR Before After Before After 0 / 0 diff0amp · diffT
W7-label-query Before After Before After 8951 / 5547 diff0amp · diffT
W7-generic-query Before After Before After 0 / 0 diff0amp · diffT
W7-generic-label-query Before After Before After 1760 / 1760 diff0amp · diffT

Android index, recordings, accessibility and reproduction sources. Android16/API36 Pixel7, 1080×2400 physical, density420 (~411×914dp), fontscale1, light, animations disabled. Same native APK and synthetic fixtures on both revisions; two settled frames 500ms apart. Diff0 amplified and DiffT20/255. Only denied text changes; generic control has minor raster differences. Android direct playback advances on both revisions; pause/resume after remains unverified in this first capture.

Android row Before After Diff0 DiffT20
M1 inline Before After Diff0 DiffT
M2 link modal Before After Diff0 DiffT
M3 generic Before After Diff0 DiffT

M2 scripted open, tap unavailable, close, reopen, close: Before recording · After recording. M1 accessibility remains disabled and replaces “Play” with the name plus explanation. M5 unchanged shared callers have source assertions; remaining mobile lifecycle/route controls are listed explicitly in the index.

Captioned interaction / size / theme Before After
W2-1440-dark Before After
W2-1440-light Before After
W2-390-dark Before After
W2-390-light Before After
W4-1440-dark Before After
W4-1440-light Before After
W4-390-dark Before After
W4-390-light Before After
W6-file Before After
W7-find Before After
M4 Android direct playback Before After

Android supplementary index: paired generic/disconnected, direct pause/resume and local draft loading/reopen. The index includes before/after recordings, observed steps, stability pairs, both diffs, reproducible drivers and explicit rejected captures. Resource-link reopen and actual URI-null retention remain unproved.

Verification log
  • Browser tests: 9 passed in 2 files. Existing renderer tests 6 → 7; main fails the new denied-video regression, fixed head passes. Existing reachable playback coverage passes.
  • Scoped lint and web/mobile typechecks pass; unchanged lint warnings. No parser, regex, dependency patch, wire or permission changes.
  • Independent code review round 1: GPT-6.1 Sol and Claude Opus 5.5 both MERGE; no blocking defects. Original bot feedback about accessibility, thread-find and mobile preview entry points is addressed.
  • Proof audit: web core and file-panel controls passed; independent affected capture rebuild matched with 0 pixels changed. Final web proof audit: SUFFICIENT, W1–W7; 42 accepted stills, 42 diffs and all 36 recordings inspected. Independent rebuild: 0 changed pixels. Final Android proof audit: M1–M3 PASS; direct pause/resume and local loading/reopen PASS. Independent M1 full-frame rebuild: 0 changed pixels without alignment. Required resource-link reopen and actual URI-null retention remain unproved, so this replacement stays draft.
  • Final main integration, scoped rerun, CI: Merged origin/main 453bf01d20 into 67aef9c072; the seven-file patch and source blobs are unchanged from the reviewed/captured head. Focused rerun: 9 tests pass in 2 files; scoped lint has 0 errors and 6 existing warnings. CI: all 25 reported contexts successful on the final head, including all 8 required checks; no pending or failing contexts.
  • Unverified: Android resource-link reopen and actual URI-null retained-player behavior lack required paired proof. iOS simulator not available on Linux VMs. Remote relay/tunnel transports were not separately captured; file-access state is client-side and the captures use real destination session grants.
  • Logs: final tests, final lint, full friction log.
  • Models/harness: Claude Opus 5.5 tests, implementation and reviewer; GPT-6.1 Sol lead, reviewer, evidence and proof audit; T3 Code.
Original description by @BGD-1DAY00

Problem

On a connection without the filesystem:read grant, a host video in chat (![Demo](/abs/demo.mp4)) shows "Video unavailable · Demo" and a "Retry video" button. The client blocks the asset before it asks the server, so Retry can never succeed, and nothing says why. A host image in the same state already says "This connection cannot read host files. Pair again with a new link to view it." Fixes https://git.cafe/pingdotgg/t3code/issues/21087.

Change

ChatMarkdownAssetImage works out the missing-file-access state once and passes the existing MISSING_FILE_ACCESS_MESSAGE to the video as well as the image. MediaVideoPlayer takes an optional unavailableDetail and shows it in its unavailable label, as ChatMarkdownMediaUnavailableLabel already does for images, and the label is marked data-thread-find-ignore like the image one. In that state the video gets no retry handler, so the button goes away. Pairing again grants the scope and the video loads as before.

Mobile had the same gap: ThreadMarkdownVideo now passes the same message, and the mobile tile shows "Video unavailable. ", the wording ThreadMarkdownImage uses. Its accessibility label carries the same text while it is unavailable, so screen readers announce the reason instead of "Play ". The mobile tile never had a retry.

Scope and approval

A small fix for an obvious bug, reported in https://git.cafe/pingdotgg/t3code/issues/21087: the video branch dropped the reason its image sibling shows, and offered an action that cannot succeed. The web and mobile video paths, plus one test.

Verification

  • Found on a macOS desktop (0.1.1-nightly.20261017.3272) paired with a Linux server. The session had no filesystem:read, and the server trace showed no asset request for the video.
  • New browser test in ChatMarkdown.assets.browser.test.tsx: a host video on a connection without file access shows the message and no "Retry video". On main it fails with Video unavailable · DemoRetry video; with this change it passes. vp test run --project browser src/components/ChatMarkdown.assets.browser.test.tsx: 2 passed.
  • vp lint and vp fmt --check on the changed files, and tsc-rs --noEmit in apps/web and apps/mobile: clean (lint shows two existing warnings in LoadedMediaVideo, which this PR does not touch).
  • Dev web app (vp run dev, seeded with migrate-dev-db), with the browser paired by node apps/server/src/bin.ts pair --scope orchestration:read --scope orchestration:operate --scope terminal:operate --scope relay:read, on a thread whose prompt and reply both embed a host video:
Before After
Before: Video unavailable with a Retry video button After: Video unavailable with the missing file access message and no retry
  • Not checked: a packaged desktop build, and the mobile change on a device.

Claude Opus 5.5 via Claude Code, running in T3 Code.

🤖 Generated with Claude Code

juliusmarminge and others added 3 commits October 24, 2026 22:35
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of offering a retry

A chat video pointing at a host file over a connection without file access
showed "Video unavailable" with a Retry button that could never succeed.
Show the existing missing-file-access reason, as images already do, and drop
the retry for that case. Mobile inline tiles and the video link preview
(Android modal and iOS native alert) get the same reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 25, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: A host video without file access shows "Video unavailable" and a Retry that cannot work

1 participant