Skip to content

fix(components): give a failed pending send one owner per fact - #719

Open
zxch3n wants to merge 2 commits into
feat/attachment-draftsfrom
fix/pending-attachment-failure-ui
Open

zxch3n wants to merge 2 commits into
feat/attachment-draftsfrom
fix/pending-attachment-failure-ui

Conversation

@zxch3n

@zxch3n zxch3n commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Stacked on #712 (feat/attachment-drafts). Rendering only.

The problem

The pending row printed the same failure three times — a message-level "Not sent · Attachment upload failed", the record's own error string, and each failed attachment's reason. Buttons sat in a permanently-rendered empty div, and the failed card was not visually distinct from a finished one, so a partly failed message read as a debug panel rather than a message.

One owner per fact

Level Before After
Message status Not sent · Attachment upload failed, in text-destructive Not sent, in text-muted-foreground
Record error loose red <p> under the bubble PendingFailureNotice, and only when no attachment carries a reason
Attachment red status text on an otherwise normal card the failed card is the focus

The record-level reason is not simply dropped: a journal-level failure can occur with every attachment ready, so it still renders — just never alongside a per-attachment reason. One fault is stated once.

Red budget

FAILED_FRAME_CLASS (border-destructive/30 bg-destructive/[0.04]) is the entire red budget, and only a failed card takes it. The icon tile, filename and message status stay neutral; the image card's failure uses a neutral bg-background/55 scrim rather than a red wash. A failed message now has three red things (faint frame, one AlertCircle, one reason line) instead of seven.

Layout

Uploading / ready / failed share one card skeleton — icon slot, name plus one status line, trailing state glyph. The progress bar is a full-width strip flush on the card's bottom edge that always reserves its row, so a card keeps one height across all three states. Measured in the browser: image card 198px and file card 66px in uploading, failed and prepared alike.

Absolute positioning was tried first and was wrong — at bottom-2 the bar overlapped the 40px content row by 2px and left an unrelated 8px gap beneath it.

The three trailing glyphs stay mounted and cross-fade on opacity/scale/blur (0→1, 0.25→1, 4px→0, 300ms), with the transition naming its exact properties. CSS rather than framer-motion: nothing else under chat/ or ai-gui/ pulls that dependency into the conversation's module graph. File cards share getSessionFileIcon with the delivered SessionFileCard, so a draft and its sent form read as one object.

Scope

Rendering layer only. Journal stages, Effect lifetimes, retry/cancel semantics, message ordering and persistence are untouched. PendingMessageRow is exported as a pure component so Storybook and tests drive the states without a workspace runtime.

Verification

  • Full component suite 481 files / 3685 tests pass; typecheck (tsgo), oxlint, lint:i18n, prettier and pnpm docs check (0 errors) all clean.
  • Storybook stories for uploading, failed/retry, narrow failed/retry, record-level failure and prepared; rendered and screenshotted at 720px and 380px in light and dark.
  • The cross-fade's computed opacity / scale / filter and transition-property were read from the live DOM rather than assumed (Tailwind v4 emits the standalone scale property, so a naive transform check looks broken).
  • Ablations. Duplicating the reason and blanket-reddening file cards fail as expected. Three ablations initially passed silently and each earned an assertion: recolouring the message status, dropping the reserved progress row, and a degenerate fixture where the only ready attachment was an image (so a file-card regression was unobservable).

Notes for the reviewer

  • The reserved row is asserted via a data-attachment-progress hook, not a style selector: Progress merges to the same h-1 w-full and is indistinguishable by styling alone.
  • sessions.pendingMessageUploadFailed changed value in en/zh_CN (edited textually — these are flat-key files with duplicates that a JSON round-trip would reorder).
  • The owning Agent Note is updated and its Translation: flipped to pending; the Chinese version of the new section is outstanding.

Pre-existing on the base branch, not touched here

  • chat-composer.tsx:867 renders a bare t('sessions.attachmentDraft') string where sibling branches render a positioned progress <div>.
  • components/chat/AGENTS.md still states "Submit blocks while hasBlockingImages or hasBlockingFiles", but the base branch hard-disabled both (= false).

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb7cbba654

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


Status: proposed
Translation: current
Translation: pending

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the agent-authored translation synchronized

Translate the newly added “Pending failure hierarchy” section into the adjacent Chinese note and mark both copies Translation: current. This commit changes only the English document to pending, while the Chinese counterpart still claims current, leaving the two decision records inconsistent; agent-authored notes must land both language versions together and may not use pending.

AGENTS.md reference: .agents/notes/AGENTS.md:L91-L96

Useful? React with 👍 / 👎.

@zxch3n
zxch3n added this pull request to stack #713 September 15, 2026 03:34
@zxch3n
zxch3n force-pushed the fix/pending-attachment-failure-ui branch from bb7cbba to e5b66a1 Compare September 16, 2026 03:58
@zxch3n
zxch3n force-pushed the fix/pending-attachment-failure-ui branch from e5b66a1 to ba1669a Compare September 16, 2026 04:02
@zxch3n
zxch3n force-pushed the fix/pending-attachment-failure-ui branch from ba1669a to fe742d7 Compare September 16, 2026 07:23
@zxch3n
zxch3n force-pushed the fix/pending-attachment-failure-ui branch from fe742d7 to 53f216b Compare September 16, 2026 07:26
@zxch3n
zxch3n force-pushed the fix/pending-attachment-failure-ui branch from 53f216b to ae84758 Compare September 16, 2026 10:33
@zxch3n
zxch3n force-pushed the fix/pending-attachment-failure-ui branch from ae84758 to eb2dc7e Compare September 16, 2026 10:44
The pending row printed the same failure three times — a message-level
"Not sent · Attachment upload failed", the record's own error string, and
each failed attachment's reason — so a partly failed message read as a
diagnostic dump rather than a message.

Rendering now assigns one owner per fact. The message level keeps one
short neutral status ("Not sent"); the reason belongs to the attachment
that failed, and the record-level reason renders only when no attachment
carries one, so a journal-level failure still explains itself without
ever duplicating a reason. A reason with no card to live on arrives
through PendingFailureNotice in the same frame instead of loose red text
under the bubble.

FAILED_FRAME_CLASS is the whole red budget: only the failed card takes
it, while the icon tile, filename and message status stay neutral. The
image card's failure uses a neutral scrim rather than a red wash.

Uploading / ready / failed share one card skeleton, and the progress bar
is a flush strip that always reserves its row, so a card keeps one height
across all three states (image 198px, file 66px, measured). The trailing
state glyphs cross-fade on opacity/scale/blur in CSS rather than pulling
framer-motion into the conversation's module graph.

Rendering only: journal stages, Effect lifetimes, retry/cancel semantics,
ordering and persistence are untouched.

Model: claude-opus-5
@zxch3n
zxch3n force-pushed the fix/pending-attachment-failure-ui branch from eb2dc7e to 87b1e75 Compare September 16, 2026 10:54

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87b1e75f6a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +203 to +207
<p
className={cn(
'truncate text-[11px]',
failed ? 'text-destructive' : 'text-muted-foreground'
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the only attachment failure reason readable

When an attachment fails on the narrow/mobile layout, this single-line truncate hides most of the reason (including the story's “Network error while preparing the file”), while reasonOnACard suppresses the full record-level notice. The title attribute does not provide a usable disclosure on touch devices, so affected users see only “Not sent” and an incomplete cause before retrying; wrap the failure text or provide a touch/keyboard-accessible way to reveal it. The file-card status at lines 240–246 has the same problem.

Useful? React with 👍 / 👎.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant