Repository navigation
Conversation
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One preparation-error recovery regression found.
4b2e055 to
32c31dc
Compare
9f4219b to
e34a270
Compare
32c31dc to
0e35fbc
Compare
82e6257 to
8f05fbe
Compare
8f05fbe to
c2fd25e
Compare
0e35fbc to
7ea5005
Compare
…d Send A failed attachment upload showed the Error's own message, such as "Failed to fetch" or "signal timed out". attachment-draft.ts now maps it by type with userCopy's failureKind: - UploadError keeps its message, which is already written for people - a lost connection: "Upload didn’t finish. Check your connection, then retry." - the five-minute timeout: "Upload took too long. Retry, or remove this file." - anything else: "Upload failed. Retry or remove this file.", now the one fallback shared with ComposerAttachments (which said "Upload failed. Retry or remove it to send.") While a failed upload blocks it, Send stays focusable (aria-disabled) with the tooltip "Retry or remove the failed file to send.", and Send and Enter still send nothing. The failed tile keeps Retry, then Remove, as on base. The error and the actions each take a full row from the tile's inner edge; the error sits 12px below the file row and the actions 12px below the error, 8px apart. The actions never wrap: the tile's min-content width keeps room for both, so a strip too narrow for them scrolls instead. Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: mtoohey <[email protected]>
Choosing an image over 25 million pixels showed "Upload failed. Retry or remove this file." instead of why. prepareAttachment threw plain Errors for its own refusals, and the tile's mapper words any unknown error generically. prepare-attachment.ts now throws a PreparationError for the three refusals it writes for people: the pixel limit, no canvas context, and an image format the browser cannot prepare. The draft shows that message as it does an UploadError's. Every other failure, including an image the browser cannot decode, a canvas that yields no blob, and the WebP worker's own text, keeps the generic copy. A draft-level test runs the real preparation with a stubbed bitmap and canvas context. On the previous commit it fails for the pixel limit and the missing context; it also pins a decode error to the generic text. Signed-off-by: mtoohey <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
c2fd25e to
b267250
Compare
7ea5005 to
1877022
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source review via Wes's account.
Reviewed b267250a27f44ab04940c6bcbcdd6b17ae899ea4 against target/merge-base 1877022492b436d299418ba7ec8e73b065d885f9 (stacked on #3096).
One new P2 is reported inline: known upload recovery instructions are lost in detached Huddle chat because the existing bridge reconstructs errors without their type.
The existing preparation-error P2 is partly repaired. The pixel-limit, missing-canvas and unsupported-format cases now preserve their specific messages in the ordinary composer. However, animated PNG/WebP with an ICC profile or oriented EXIF still reach the plain errors in image-metadata.ts:59–67 through prepare-attachment.ts:40–51. The new mapper replaces these deterministic refusal reasons with generic retry copy, although retrying the unchanged file fails before upload. Extend the typed preparation-error treatment to those known refusals and add a draft-level animated-image regression case. This is residual evidence for the existing finding, not a duplicate inline report.
Source review only; no tests, builds or app execution. The three linked screenshots were inspected, but they do not establish exact-head runtime behavior. Browser layout, keyboard interactions and native cross-window behavior remain unverified.
…fore the Play click (#3446) ## Why `composer-attachments.spec.mjs`, "local previews stay interactive during eager upload and removal collapses the composer", hung once in CI: [11 Nov, Flake check, WebKit 7/9](https://github.com/block/buzz-app/actions/runs/43114573078/job/130840308592), passed on re-run. The "Play video" click stayed in "performing click action" past its 10 s action timeout, the test ran to its 120 s timeout, and the error context holds no page snapshot. This PR is a mitigation. The root cause is not pinned down. ## What the trace shows The test pauses the review viewer's autoplaying 2 s clip with `loop = true; pause()` and then clicks "Play video". The failed attempt's trace (artifact 13898342784) shows: - the viewer opening at 12.07 s and the clip at 1.250 s in the dialog screenshot at 12.52 s; - a blank stage with the clip reset to 0 at 13.17 s under the same blob source; - the clip playing at 1.417 s at 14.08 s, the `loop = true; pause()` evaluate at 14.11 s (paused at 1.583 s), and the Play click at 14.13 s; - a last screencast frame at 14.40 s with the clip back on its first frame. No frame followed in the remaining 110 s and the click never returned. The clip was mid-way and never reached its end after `loop` was set, so a loop seek did not occur. The page froze when the click resumed the paused pipeline. The exact WebKit mechanism is unknown. ## Change Set `playbackRate = 0` instead of `loop`, as `messages.spec.mjs` does since #3058. A zero-rate play turns `paused` false without advancing the clock, so the Play click leaves the clip on its paused frame and the clip can neither end nor loop while the eager-upload assertions run. The Pause button, `paused`, same-element and same-source assertions are unchanged. The second commit corrects the code comment, which first attributed the stall to the loop seek. It now says the cause is not pinned down and that rate 0 removes both the end and the loop seek. No timeouts or assertions were relaxed. No browser cases were added or removed. ## Evidence Local runs on Linux, WebKit, `-g "local previews stay interactive"`. | Scope | Unchanged test | With rate 0 | |---|---|---| | Idle host, `--workers=1 --repeat-each=10` | 10/10 passed, 12.5 to 13.0 s | 10/10 passed, 13.0 to 17.2 s | | Pinned to 4 CPUs shared with 5 busy loops, `--repeat-each=6` | 6/6 passed, 24.9 to 31.2 s | 6/6 passed, 26.5 to 31.9 s | | Whole file, `--workers=2` | | WebKit 10/10, Chromium 10/10 | A scratch copy that waited for `ended` before taking over passed 3/3 with rate 0 and 3/3 with `loop`, so the ended path needs no extra seek. After the comment correction, `user-status.spec.mjs` and this spec passed 11/11 together on WebKit. ## Not reproduced, not run - The stall was not reproduced locally, with or without the change. The contended runs matched the 27 to 29 s of the job's passing repeats and still passed. - Because the mechanism is unknown, this change removes two candidate triggers but is not shown to remove the hang. The test needs watching after it lands. - No human has tested this yet, so `buzz-review-completed` is not set. ## Left alone - The earlier reset of the same video element at about 13 s in the failing run. - This spec's cold per-test dev server. - Open PR #3097 edits other parts of `composer-attachments.spec.mjs`. A trial merge against this change is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: mtoohey <[email protected]> Signed-off-by: Matt Toohey <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]>
…'s tile In detached Huddle chat, a failed upload always showed "Upload failed. Retry or remove this file.". Preparation and upload run in the main window, and the composer bridge passed only the error's message, which the client rebuilt as a plain Error. The attachment draft then saw no UploadError or PreparationError, so it lost messages such as the ffmpeg install instruction and the 25-million-pixel limit. A lost connection and the upload timeout lost their own copy the same way. The owner now adds the failure's identity to a failed upload's result: the UploadError code, a PreparationError, or failureKind's connection or timeout. The client restores a typed error from it, so the draft words the tile as it does in the main window. Only the upload request carries it; other requests reject as before. An unrecognised failure still crosses as text only and stays generic on the tile. composer.test.ts sends each failure through the owner, the client and the attachment draft, then reads the rendered tile. The ffmpeg refusal, both connection forms, the timeout and the pixel limit fail without the fix. An unrecognised Error and a native string stay generic. Signed-off-by: Matt Toohey <[email protected]> Co-Authored-By: Claude Fable 5.2 <[email protected]>
An animated PNG or WebP with a color profile, or one rotated by its metadata, showed "Upload failed. Retry or remove this file.". So did an image file whose structure cannot be read. image-metadata.ts refused them with plain Errors, which the tile's mapper words generically, yet retrying the same file fails the same way before any upload. Those refusals are now PreparationErrors, so the tile shows their own message. Each is reworded for a person, since it is shown as is: - "This animated image has a color profile, which can’t be kept. Export it without one, or choose another image." - "This animated image is rotated by its metadata, which can’t be kept. Export it upright, or choose another image." - "This image file can’t be read. Choose another image." PreparationError moves to image-metadata.ts, the lowest module that throws one, and prepare-attachment.ts re-exports it. The Huddle composer bridge already carries a PreparationError, so the detached composer's tile shows the same messages. Failures with no known cause keep the generic copy: an image the browser cannot decode, a canvas that yields no blob, the WebP encoder's errors, and encoder output that jpeg-metadata.ts rejects. attachment-draft.test.tsx runs the draft's real preparation on an animated PNG and WebP of each kind and on a GIF cut short. composer.test.ts sends an animated PNG through the bridge. All six cases fail without the fix. Signed-off-by: Matt Toohey <[email protected]> Co-Authored-By: Claude Fable 5.2 <[email protected]>
Summary
A failed attachment upload showed the browser's own text in the tile ("Failed to fetch", "signal timed out"), and a blocked Send gave no reason. Upload failures now map to plain copy through the shared helper. Send explains why it is blocked, and the failed tile's spacing is tidied.
Stacked on #3096: this PR targets
errors/user-copy-messages(PR #3096), which adds the shareduserCopy/failureKindhelper used here. Review and merge #3096 first.What changed
Upload failure copy (
attachment-draft.ts,ComposerAttachments.tsx)TypeError)Failed to fetchUpload didn’t finish. Check your connection, then retry.TimeoutError)signal timed outUpload took too long. Retry, or remove this file.Error's own messageUpload failed. Retry or remove this file.Upload failed. Retry or remove it to send.Upload failed. Retry or remove this file.(one shared fallback)UploadErrorBlocked Send (
MessageComposer.tsx)disabled, with no tooltip.aria-disabled="true") with the tooltipRetry or remove the failed file to send.Clicking Send or pressing Enter still sends, publishes and uploads nothing.Failed tile layout (
ComposerAttachments.module.css)Before / After
Final tile at 1280px:
Failed to fetchbecomesUpload didn’t finish. Check your connection, then retry.The error spans the tile and sits 12px from the file row and from the buttons. Retry still comes before Remove, as on base.Hovering Send while the failed file blocks it: the tooltip explains why Send does nothing. Older capture (round 3): the tile there shows Remove before Retry and the earlier spacing. The shipped tile is Retry, then Remove, as in the first image.
An upload that hits its timeout: the
DOMException'ssignal timed outbecomesUpload took too long. Retry, or remove this file.Older capture (round 1): the copy is current, but the tile spacing predates the final layout.Checks
pnpm typecheck: clean on this branch (on top of fix(errors): plain copy for failed history reads and connections #3096).pnpm lint: exit 0, with 30 infos that predate this change.biome formatis clean on the changed files.tests/browser/composer-attachments.spec.mjsare included but not run on this snapshot. On the source branch, a separate browser capture at 1280px measured the final tile: 12px file row to error, 12px error to buttons, and Retry and Remove sharing a top.Known gaps
🤖 Generated with Claude Code