Technical essay · Mobile systems
ownership, not curves
The media transition looked healthy in traces and still felt wrong in the hand. The problem was presentation ownership, not the easing curve.
We had green rows.
The relevant profiler output looked acceptable. The player emitted readyToPlay. Playback progress moved. Posters covered the period before the live surface became presentable. In a recording, the transition looked good enough to stop investigating.
It still felt wrong in the hand.
Landscape media made the lie easiest to see. During the old drag-and-return path, the fullscreen surface was scaled down towards its place in the feed. It pixelated while moving, then corrected its aspect ratio late enough for the eye to catch the snap. The newer close path had better measurements and behaved more like a bridge between two real rectangles. The two return paths therefore disagreed about what object was actually travelling.
Elsewhere, the failure was less polite. A stale surface would survive under the new one. Pixels duplicated. Paging allowed one item’s media to bleed into another’s moment. Z-order became an argument nobody had formally agreed to have. Sometimes the viewer opened on a poster and waited too long for the live frame. Sometimes it found the black between them.
None of these failures was adequately described by “the animation needs tuning.”
That description was appealing because animation was the part we could see moving. We adjusted springs, duration, scale and opacity because those values sat near the visual offence. A better curve could make the transport more elegant. It could not tell us which surface had the right to exist at each point in the interaction.
The difficult part was preventing several surfaces from being responsible for the same media at the same time.
What fooled us
I do not think we were irrational to look at animation first.
The visual model for this interaction is simple. A person taps media in a feed. The media expands into a fullscreen viewer. They swipe through a gallery, perhaps play a video, then drag or tap to dismiss. The media returns to the feed. If the rectangles line up and the transition preserves continuity, the result feels like one object temporarily moving through space.
We initially treated the problem as movement:
- measure the source;
- interpolate towards the viewport;
- preserve aspect ratio;
- choose a spring that does not feel cheap;
- reverse the interpolation on dismissal.
All of that was useful, but one false signal remained: the geometry could be mostly correct while the media underneath it was not.
A screenshot of the settled viewer could look perfect. A recording played at ordinary speed could conceal the brief duplication between source and destination. In our player, readyToPlay meant playback could begin. It did not establish that the first live frame had coherently replaced the poster on the surface the user was looking at.
Playback progress answered a different question too. A player could advance beneath a poster, outside the visible crop, behind a stale surface, or in a view that no longer had presentation authority. We had allowed the poster to conceal that distinction: the interface looked stable while stale or competing surfaces remained alive beneath it. Occlusion changed what the user could see; it did not resolve the lifecycle for us.
The benchmark described readiness. The defect lived in the handoff.
There was no single “media”
Part of the confusion came from speaking as though one media object travelled from the feed into the viewer.
That is how the experience should feel. It is not necessarily what the implementation contains.
The feed had an inline image or player. Transport might use a snapshot, a poster or another temporary surface. The fullscreen viewer had its own paging model and live video surface. Controls and gesture regions did not always share the bounds of the visible pixels. Calling all of this “the media” made the product description pleasantly simple and the engineering model dangerously vague.
The separation that mattered was smaller than a framework: the source, the temporary transport and the steady-state viewer were distinct representations. Presentation, playback and gestures could move between them at different times. Treating those movements as decisions, rather than assuming they followed component mounting, made the failures begin to group around the same handoff.
Duplicated pixels meant visible presentation had overlapped. Invisible playback meant a player had survived after its surface lost presentation. The late aspect-ratio snap meant the moving representation no longer matched the return target. Paging bleed meant the shared host had outlived the item boundary it was serving. A black frame meant neither transport nor viewer could supply a visible frame when presentation changed.
A portal is a temporary ownership transfer
I had thought about the portal mainly as render relocation: content associated with one part of the component tree needed to appear above everything else, so it was rendered into a host with the correct stacking context.
In this interaction, that description was too weak. Moving media into the host also changed who controlled the visible pixels, clipping, gestures and dismissal lifecycle. The feed row could still own the product entity and playback state while the fullscreen viewer owned what the user could see and touch. The portal was functioning as a temporary transfer of presentation ownership, whether or not the implementation named it that way.
That left four useful questions. Who is showing the pixels now? When does the source surrender presentation? What happens if the gesture reverses? Is the return target still valid when presentation comes back?
An animation curve could make the chosen answer move through time. It could not choose the answer for us.
Transport is a phase, not architecture
Our shared host was attractive because it centralised the transport machinery. It also knew too little. It could know that it was presenting a video without knowing which feed item, source measurement, playback session or return target authorised that video. Paging exposed the flaw: item A could still be leaving while item B became active, and the host had enough state to assist both. That was how pixels bled across pages and z-order became ambiguous.
The lesson was not that a shared host was inherently wrong. It was that transport state should not become the architecture of the viewer. Transport had one narrow job: preserve enough spatial and visual continuity to move presentation between stable states. Once settled, the viewer’s paging, playback, chrome, gestures and lifecycle should not remain dependent on an overlay created to animate the handoff.
Dismissal required the inverse transfer. The viewer could not simply shrink until it resembled a feed row and count that as a return. It needed a current target and a representation whose geometry could actually reach it. This was why the old drag path and the newer close path disagreed. One reused the fullscreen surface as though scaling it down returned ownership. The other behaved more like a measured bridge.
They could share an animation and still describe different architectures.
The user swipes the viewer, not the bitmap
Gesture ownership exposed another version of the same mistake.
Landscape media inside a portrait device leaves letterboxed space. Images have edges. Videos have controls. Chrome appears and disappears. If the dismiss or paging gesture is attached only to the visible bitmap, the user’s interaction surface changes according to the aspect ratio of the current item.
That may be consistent with the view hierarchy, but not with the interaction. The user is swiping the viewer, not the bitmap. The gesture region therefore has to include the letterbox and edges when those areas belong to the viewer, while still arbitrating with media controls and horizontal paging. A reversing drag must also restore the same presentation it interrupted. Attaching a gesture detector to the easiest visible child did not settle any of that.
What the handoff had to prove
Our evidence had been biased towards steady state.
We could show that the viewer eventually contained the correct media. We could show acceptable rendering numbers after it settled, playback progress, and the source and destination rectangles. Each observation was useful and still insufficient because the defect lived between readiness and the first visible frame.
For an opening handoff, we needed to prove three things: the source and destination measurements were valid when used; visible presentation neither overlapped nor disappeared during transfer; and the poster left only after a valid live frame existed in the correct viewer.
The return needed the inverse proof, with one complication: the target might no longer be where it started.
The feed may have moved. A row may have been recycled. Insets may have changed. The application may have backgrounded. The device may have rotated. Returning to an old rectangle because it was once correct creates a beautiful animation to a place that no longer exists.
An old rectangle was not evidence merely because it had once been measured. It needed a source, a time and a validity condition.
This was where the distinction between readiness and evidence became useful. A successful automation command did not prove this handoff. A readyToPlay event did not prove it. A screenshot after the animation did not prove it. The scenario needed an explicit interval, application-owned events and visual evidence capable of showing whether the intended transfer occurred.
That gave us a way to reject runs that looked successful while failing to prove the handoff we were changing.
The ownership model
The working rule was straightforward to state and annoying to implement:
There should be exactly one presentation owner per phase.
During opening, a temporary representation could carry visible continuity while the viewer prepared. Once settled, the viewer took presentation. Closing performed the inverse transfer against a freshly resolved feed target. Playback and gesture authority could move at different points, but neither was allowed to move merely because a component mounted or emitted readiness.
Specimen 01
the handoff, named as state
type Owner = "feed" | "transport" | "viewer";
type Phase = "inline" | "opening" | "viewer" | "closing";
type State = {
phase: Phase;
owner: Owner;
liveFrame: boolean;
destinationValid: boolean;
};
if (
state.phase === "opening" &&
event.type === "VIEWER_FRAME_PRESENTED"
) {
return {
phase: "viewer",
owner: "viewer",
liveFrame: true,
destinationValid: true,
};
}
// readyToPlay is evidence, but it does not transfer ownership.The real implementation still had to handle interruption, cancellation, backgrounding, rotation, page changes, media failure and recycled feed rows. The rule did not make those states disappear. It made each state answer the same question: which surface is allowed to present this item now?
Curves still matter
I still care about the curve.
Once the ownership model is correct, timing and motion determine whether the transfer reads as continuous. A drag should remain attached to the finger. A cancelled gesture should recover without suddenly changing behaviour. The media’s crop, scale and corner treatment should evolve coherently.
But those decisions belong after the system can say what is moving, from where, towards what, under whose authority and with which recovery path.
If two surfaces can still contest the same pixels, improving the spring only makes that conflict harder to notice in a clean recording.
There are also limits to this model. “One presentation owner” does not prescribe whether continuity should use a snapshot, shared native player, moved rendering surface or deliberately separate players. It does not remove platform-specific media behaviour. It does not decide how much state should survive backgrounding, whether inline playback continues after return, or how aggressively a virtualised feed should release resources.
That is what changed for me during this work. I stopped asking whether the transition looked native enough and started asking which part of the system had the right to make it so.
After that, readyToPlay went back to being one piece of evidence rather than the verdict. Posters became fallbacks rather than proof. When the interaction still felt wrong, we could ask where presentation had overlapped or disappeared instead of reaching for the damping value again.
What would change my mind
I would weaken this model if failures continued after presentation, playback and gesture authority were made explicit and were better predicted by geometry or readiness alone. I would also revise it if deliberately overlapping presentation surfaces repeatedly handled paging, interruption and return without duplicated or blank frames.