# fix\(mobile\): size a chat image's frame before its bytes arrive · PR \#10199 · pingdotgg/t3code

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

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

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: media-dims/mobile

Target branch: media-dims/contract

Head commit: cced7aadcbc12ff44f5dc2dd5613e72fb4e34b41

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/10199)

[Commits](https://git.cafe/pingdotgg/t3code/pull/10199/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/10199/diffs?format=markdown)

## Description

---

Stack 2/3 (#10201). Depends on #10198.

## Problem

An assistant image on mobile went through three sizes: full width on first render, the measured width after its own `onLayout`, then the decoded size (up to 480px tall for a portrait screenshot). Each step changed the row's height after LegendList had already placed the rows below it. That's the "scroll area cut off under the composer" and "tool calls render under the media" reports.

## Fix

- `ThreadFeed` hands markdown its content width through `MarkdownImageAvailableWidthContext`, so the placeholder is the right width on the first render instead of after `onLayout`. The context is an upper bound: a list item or blockquote indents its column, and the measured width takes over once known.
- `ThreadMarkdownImage` uses the server's `imageDimensions` (#10198) to size the frame exactly before the first byte arrives. The decoded size still wins once it exists, so a wrong hint can never stick. An image the server couldn't measure keeps the 16:9 slot.
- Padding constants use the mobile 14px rem (`px-3.5` on the bubble, `px-1` on assistant rows).

## Evidence

iPhone 17 Pro simulator on macmini, seeded thread with landscape/portrait/wide screenshots, asset responses delayed 2.5 s through a proxy so the loading phase is observable. Frames captured every ~100 ms during thread open.

**Before** (main): portrait placeholder is 16:9; when the image decodes the row grows ~250px and everything below reflows.
**After**: the frame is portrait from first paint; rows below never move.

![iOS before/after](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/067d737e35aa246a/t3-media-ios-compare.png)

## Verification

- `apps/mobile` typecheck clean; lint on changed files clean (pre-existing effect warnings unchanged).
- Simulator pass above.

Model: Claude Fable 5 · Harness: Claude Code in T3 Code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Size chat image frames before bytes arrive in mobile thread feed
> - `ThreadFeed` derives a markdown content width from the centered content width and row padding, then passes it to `renderFeedEntry` so user and assistant markdown rows have a width estimate before their own `onLayout` fires.
> - `ThreadMarkdownImage` combines the feed-provided width with a server-supplied image size to render an aspect-ratio frame before decode; after decode, decoded dimensions take precedence and the measured layout width constrains the estimate.
> - `readJpeg` now skips later APP1 segments once a rotated EXIF orientation is detected, and `IMAGE_DIMENSIONS_HEADER_BYTES` is raised from 64 KiB to 256 KiB to reach JPEG frame headers after multiple metadata segments.
> - Risk: in [ThreadMarkdownImageView](https://github.com/pingdotgg/t3code/pull/10199/files#diff-f1433714b28719fc0e23737d491c56eecb60b76b849aa6301035d711b646e9e3) the frame resolves only when both a source size and a positive available width exist, otherwise it falls back to the placeholder frame; rows lacking a width context or unknown image size may keep the placeholder longer.
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized cced7aa.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->
