Repository navigation
Assess composer-to-message attachment continuity #10251
Description
Activity
Independent Astra-medium assessment: defer the shared-element attachment flight. No implementation or PR is warranted in this bounded motion pass.
Inspected clean upstream main
eee05575ebd514db36f61d7eb05d2258a10c96bd, in isolated worktree/Users/saphid/.t3/worktrees/t3code-attachment-motion-20260906. The dirty primary and previous Stash overlay were not modified.Evidence:
- ChatView.tsx#L6657 copies draft attachment IDs into the optimistic message. However, attachmentUploadQueue.ts#L550 submits the separately issued
upload.attachmentId. A naivelayoutId={attachment.id}loses continuity on acknowledgement. - Existing image continuity is already carefully managed: ChatView.tsx#L2920 preloads server images before releasing blob previews; the subsequent handoff maps images by message and image order. It does not supply a persistent visual-transfer identity for images and files.
- ChatView.tsx#L6677 anchors the first message or scrolls to the end before clearing the draft. MessagesTimeline.tsx#L818 renders a LegendList with its own end-follow and visible-position maintenance. An offscreen destination has no usable shared-element box, and scroll anchoring can change destination geometry after the send state update.
- ChatView.tsx#L6919 restores a failed send into an untouched draft, revoking optimistic blob URLs and creating retry previews. Keeping outgoing image nodes alive for a reverse transition would add another lifetime owner.
The current Motion layout guide requires matching rendered elements and scroll-container accounting. Those APIs do not solve the identity, virtualized destination, or preview lifetime boundaries above. Main has no declared Motion dependency; the previous overlay's installed Motion version is not evidence of compatibility on main. No Motion API was added, so export/type verification is not applicable.
Overlap check: open-title searches for attachment work surfaced #8786 and #9928 (attachment preservation), while the broader search surfaced #10234–#10236 (composer/context presentation). This is a partial overlap check, not a claim that no other related PR exists. The main implementation itself already provides non-spatial preview continuity.
Revisit only with an explicit visual identity mapping through upload acknowledgement, a visible-destination fallback, and a capture covering immediate acknowledgement, failed send/retry, history scrolling, draft promotion, rapid sends, and reduced motion. The Human Interface Craft guidance on feedback/continuity (p10), access (p11), and a useful signature moment (p13) favors preserving those boundaries over adding flight now.
Verification: read-only source/contract-path assessment; both worktree creation and clean
git status --porcelainreturned exit 0. No executable changes, tests, independent cross-provider code review, or live visual captures were performed. No performance or runtime regression claim is made. Assessment complete; implementation deferred, with no PR or capture requested for unchanged behavior.- ChatView.tsx#L6657 copies draft attachment IDs into the optimistic message. However, attachmentUploadQueue.ts#L550 submits the separately issued
Triage (cloud agent)
Verdict: defer / reject for this pass. Shared-element attachment flight from composer → optimistic message is not small or safe. Do not implement. Independent review of current
mainagrees with the existing Astra-medium comment.Evidence
Identity is not continuous. Composer attachments are minted with a local
randomUUID()(ChatComposer.tsx). The optimistic user message copies that localattachment.id(ChatView.tsx~6657). The turn payload submits the separately issuedupload.attachmentId(getUploadedAttachmentsinattachmentUploadQueue.ts~550). AlayoutId={attachment.id}(or any shared-element key on the draft id) loses continuity on acknowledgement, when the server message replaces the optimistic one.Continuity already exists, and it is non-spatial. Optimistic blob previews are handed off by message id + image order; server images are preloaded before blob revoke (
handoffAttachmentPreviews, the preload effect inChatView.tsx~2915+, andcreateMessageAttachmentPreviewProjectorinsession-logic.ts). That is preview-lifetime continuity, not a persistent visual-transfer identity for images or files.Source and destination boxes do not match. Composer resting tiles are
size-7(28px); expanded tiles areh-16 w-16(64px square). Timeline images areaspect-[4/3]in a two-column grid (max-w-[420px]). A shared-element would morph size and aspect, not hop between matching geometry.The destination is virtualized and moved on send.
MessagesTimelinerenders a LegendList with end-follow and visible-position maintenance. Send either anchors the first message orscrollToEnd()before clearing the draft. An offscreen row has no usable shared-element box; scroll can change destination geometry after the send state update. Motion’s layout APIs require matching rendered elements and scroll-container accounting; they do not solve this.Failure / retry adds another lifetime owner. A failed send removes the optimistic message, revokes its blob URLs, and clones new blob URLs for retry (
cloneComposerImageForRetry). Keeping outgoing image nodes alive for a reverse flight would add a third owner besides the composer draft and the existing handoff.No Motion primitive on main. Web depends on
@formkit/auto-animate(sidebar only). There is nomotion/framer-motiondependency. Adding shared-element layout here would be a new dependency or a custom FLIP — not reuse of a local primitive — and fights the repo rule against GPU-pegging animation.Surfaces
- Clients: Web + desktop (wraps web). Mobile is a separate RN strip (
ComposerAttachmentStrip) with no matching optimistic/handoff path; a flight would need an explicit per-client decision. - Entry points: Chat send only. Not Settings / command palette / a keybinding.
- Providers / contracts / connection modes: UI-only; no adapter or wire change. Identity mapping would be a later, larger change.
- Reverse states: Failed send, retry, and draft restore are blockers, not follow-ups.
Overlap
Siblings #10248–#10255 are distinct motion-audit items (stash, decision banners, task badge, completion→diff, sidebar, command palette, reduced-motion disclosures). Related but different: #8786 / #9928 (attachment data preservation), #9253 (unified previews), #10234 (inline context chips). None implement spatial handoff.
Revisit only with
- A durable visual identity through local UUID → minted
attachmentId→ server ack. - A visible-destination fallback (no flight when the row is virtualized / offscreen).
- A capture covering immediate acknowledgement, failed send/retry, history scrolling, draft promotion, rapid sends, and reduced motion.
- An explicit mobile decision (defer or separate native work).
Until then, keep the existing preload/handoff. No PR.
Assessment only. No code, branch, or PR from this triage.
- Clients: Web + desktop (wraps web). Mobile is a separate RN strip (
- addedvia-triageFiled through npx t3 triageFiled through npx t3 triageenhancementRequested improvement or new capability.Requested improvement or new capability.
on Sep 6, 2026 Thanks for taking the time to report this and provide the details. We revisited it during the orchestrator V2 cleanup.
This issue requested an assessment with explicit permission to defer. Two recorded independent assessments already concluded defer/no implementation; current uploaded attachment identity and preview/retry ownership still support that decision. No implementation remains authorized by its acceptance criteria.
I’m closing this because the requested assessment is complete. The decision was to defer the animation; it was not implemented.
Closure means the requested assessment is complete, not that shared-element animation was implemented.
Assess shared-element attachment handoff from composer to optimistic message. Only build if identity, scrolling/virtualization, final geometry and pending/failure semantics can be preserved with a small change. Otherwise record evidence for deferral.
Requested by Alex after the T3 UI motion audit. First record an independent Astra-medium accept/revise/reject verdict with source evidence. Accepted changes should end in small focused PRs; do not implement a rejected suggestion.
Use the installed shadcn-motion-ui skill and Human Interface Craft guidelines (feedback, relationship, continuity, accessibility, signature moments). Consult current official docs, reuse local primitives, verify installed API compatibility and add exact source references. Respect native platforms, reduced motion, interruptions, responsiveness, focus, and performance. No continuous repaint loops or framework rewrites.
Acceptance:
Do not change live user data, the dirty primary checkout, existing Stash worktree, or unrelated work. No merge or deployment is requested.