Repository navigation
ImageField: a failed image upload is silent to the user and escapes as an unhandled promise rejection — FileField shows fields.file.uploadFailed for the same failure #10226
Description
Activity
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actions分诊首次定级:
priority:p3·bug·domain:ui·pm:queue—— 图片上传失败时用户得不到任何提示,错误还以未处理的 Promise 拒绝漏出组件Path:
packages/fields/src/widgets/ImageField.tsx(handleFileChange与handleCropConfirm)Triage: lands in
@object-ui/fields⇒domain:ui,bug,priority:p3,pm:queue; rationale: both upload paths usetry … finallywith nocatch, so a failed upload (network error, 413, storage outage) shows no message and escapes as an unhandled rejection, whileFileFieldon the same pipeline rendersfields.file.uploadFailed; nothing is falsely reported as saved — the image simply does not appear — so p3.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T16:28Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 objectuimain(06b82b8)上核对。本席实测
ImageField的两条上传路径都是try { … } finally { … },没有catch。与卡面一致。最近一次改动(PR fix(fields,plugin-form): a wizard keeps the file whose upload was still running when Next was pressed (objectui#10180) #10225,finding(plugin-form): leaving a WizardForm step while an upload is in flight loses the attachment outright — the step unmount ends the upload, soNext(not Save) is the act that drops it, and objectui#10166 deliberately does not cover it #10180)也没有加上。- 复现(拒绝的上传 → 1 个
unhandledRejection、DOM 里没有失败文字)是 dev 的读数,本席未重跑。
定级说明
p3:用户会看到图片没有出现,不会被误导成"已保存";代价是不知道原因、只能再试。修法局部,有现成的同类实现可照抄。
执行要点
- 两条路径各加
catch:显示与FileField相同的、已翻译的失败提示;不加入图片;⛔ 不让拒绝漏出处理函数。 - 测试:拒绝的上传 → 显示失败提示、0 个未处理拒绝;成功的上传照常加入(对照)。
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Sep 24, 2026 objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 —
domain:uiexecution seat 3
Session:session_01D486C1axmXnrkJMNUfz2eb
Branch:claude/issue-10226-image-upload-failure-reported
Worktree:objectui-issue-10226
Domain:domain:ui
Seat:domain:ui#3
File surface:packages/fields/src/widgets/ImageField.tsx(handleFileChangeandhandleCropConfirm), its tests underpackages/fields/src, one.changeset/10226-…md(stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus(default judgement tier) —dispatch-gates.mjs --repo objectstack-ai/objectui --tier …answers 「Derive 'objectstack-ai/objectui' from a checkout OF 'objectstack-ai/objectui' — this script exists only in 'objectstack-ai/objectstack'」 ⇒ no path-derived mandate; tier is this seat's judgement.
Clause-②: no
Thread-read: 5818050257
Serial constraints cleared: open PRs and livepm:dispatchedclaims read 2026-09-24T16:33Z — none touchesImageField.tsx. Its last change was objectui#10180 (PR objectui#10225, merged).Maintainer instruction this session: 「红的为什么不修?为什么不继续派发」. That covers the continued dispatch; this card is from the lane queue under the usual full order. Why
Clause-②: no: it adds acatchand an inline message using the existingfields.file.uploadFailedkey, so no acceptance set widens and no export is added.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 10226, "status": "done", "branch": "claude/issue-10226-image-upload-failure-reported", "pr": "https://github.com/objectstack-ai/objectui/pull/10310", "session": "session_01D486C1axmXnrkJMNUfz2eb (mode:subagent, the parent's id from the claim and the harness attribution)", "premise_still_valid": true, "summary": "Premise re-derived on origin/main 8b1f066. Both ImageField upload paths (handleFileChange, handleCropConfirm) were try/finally with no catch, and both are fire-and-forget: React ignores the promise onChange returns, and ImageCropperDialog calls onConfirm without awaiting it. Reproduced red before any edit: 1 unhandledRejection ('network down') and no failure text, on the picker path, the multi-select path and the crop path. Hypotheses 1-3 all hold. fields.file.uploadFailed is present in all 10 locale packs; the instrument for that is all-locales-key-parity. The fix mirrors FileField's useFileUploads. Each pick is caught on its own, the failed image is not added, successful picks of a multi-select still land, oversize rejections are kept, and the message is the same key, arguments and defaultValue FileField uses. It goes through a module-level uploadFailedMessage(t, name, err), in the maxSizeError shape. On a crop failure the original image stays and the dialog closes, so the message shows in the error row. Assignee not touched; it was already os-project-manager. Worktree removed after the PR opened.", "tests": "Head 737663e. RED on unmodified 8b1f066 source: new ImageField.uploadFailure.test.tsx gave 'Tests 3 failed | 2 passed (5)'; each failure case showed 'expected [ Error: network down ] to have a length of +0 but got 1' plus 'Unable to find an element with the text: Failed to upload ...'; both success controls passed. GREEN (exit codes written to files; os-verify-lock VERDICT command-exit 0): pnpm --filter @object-ui/fields type-check (tsc --noEmit && tsc -p tsconfig.test.json) exit 0, after building the closure with pnpm --filter '@object-ui/fields^...' build (exit 0). pnpm exec vitest run packages/fields/ exit 0: 'Test Files 179 passed | 1 skipped (180)', 'Tests 3010 passed | 7 skipped (3017)'. The two ImageField test files: 'Tests 11 passed (11)'. eslint on the 2 touched files (--format json): 0 errors; ImageField.tsx has 7 warnings against 6 on base (one new react-hooks/preserve-manual-memoization on handleCropConfirm; lint.yml sets no --max-warnings). check:i18n-keys, check:i18n-dead-keys, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:new-line-citations (0 new), check:control-bytes, check:test-path-roots, check:unreferenced-sources, check-changeset-presence, check-changeset-no-major: all exit 0 on 737663e. Control-byte self-scan of the 3 files: 0 hits. ABLATION on committed head 737663e, trap-guarded: ImageField.tsx checked out from the 8b1f066 blob. On disk, uploadFailed went 4 → 0 and 'catch (err)' went to 0. Result 'Tests 3 failed | 2 passed (5)', the same 3 failures. Restored with git checkout HEAD -- PATH; hash equals the HEAD blob and git diff HEAD is 0 lines. No build or dist leg: the test imports ./ImageField source directly. NARROWING DECLARED: repo-wide pnpm lint, full pnpm test and all other check:* families are left to CI. Lint ran on touched files only. The three-part proof of that narrowing (population, file count, invariance) was not produced, so treat lint as NOT MEASURED beyond those 2 files. CI state at report time: not read (in_progress assumed); Spec Main Shape Gate red is upstream objectui#10287 per dispatch.", "mcp_calls": "0", "api_writes": "2 relay writes, as objectstack-fleet[bot] via with-fleet.sh --via auto → dispatch: (1) pr_create → POST /repos/objectstack-ai/objectui/pulls (draft, #10310), relay run 36031855254 success; (2) comment → POST /repos/objectstack-ai/objectui/issues/10226/comments (this report). Each relay stroke is one POST /repos/objectstack-ai/objectstack/dispatches. Plus git push of the branch (not REST). Zero label writes, zero PR-body PATCH.", "open_questions": [], "out_of_scope_findings": [ "not class a/b/c, in Acceptance notes only: for a non-Error rejection, the upload-failure message reads 'undefined' because of (err as Error).message. FileField has the same behaviour, and ImageField copies it on purpose so the two match. carrier: 承接者:无", "not class a/b/c, in Acceptance notes only: FileField and ImageField each build the fields.file.uploadFailed call separately. The file-size-guard.ts rationale for sharing would apply here too, but moving it touches FileField.tsx, which is outside the claimed surface. carrier: 承接者:无", "not class a/b/c, in Acceptance notes only: ImageCropperDialog's title and description are hard-coded English and do not go through t(). carrier: 承接者:无" ], "deviations": [ "Commit 2091538 has 'Co-Authored-By: Claude Opus 5.5' (it followed the harness attribution reminder). os-dev.md asks for model-free trailers. The later commits 01e0ca6 and 737663e use 'Co-Authored-By: Claude'. History was not rewritten.", "File surface kept to the claim: ImageField.tsx, ImageField.uploadFailure.test.tsx (new), .changeset/10226-image-upload-failure-reported.md." ], "files_changed": ["packages/fields/src/widgets/ImageField.tsx", "packages/fields/src/widgets/ImageField.uploadFailure.test.tsx", ".changeset/10226-image-upload-failure-reported.md"], "head_sha": "737663e" }
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actions✅ ACCEPT — PR objectui#10310 at
737663e·pm:dispatched→pm:blockedon objectui#10287domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T17:43Z. Reviewed against the diff, the tree and CI.Implemented-by: claude/issue-10226-image-upload-failure-reported Reviewed-by: session_01D486C1axmXnrkJMNUfz2ebitem reading shape draft · base main· line 1Fixes #10226· line 2Clause-②: noscope 3 files, inside the claimed surface contract review PASS at 737663e, recorded on the PR, at tier (read off the reviewer's transcript)CI green at head except Spec Main Shape Gate, the upstreamdoc-nav drift anchored at objectui#10287 (fix PR objectui#10315 is open)⚠️ landing notecommit 2091538carries a model-namedCo-Authored-Bytrailer. The squash message must be set so that it does not reachmain; the PR body, changeset and code name no modelThe gate runs on
merge_group, so the PR waits outside the queue. The card body gainsBlocked-by: #10287andUnlock-action: re-check PR #10310. When objectui#10287 lands: mergemain, read every check at the new head, then ready and auto-merge.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actions🟢 LANDED — PR objectui#10310 merged as
1f8ef0a89(merge queue)domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T19:22Z. Merged 2026-09-24T19:11Z.The PR had entered the merge queue before this seat converted it back to draft. That conversion was over a model-named trailer on one branch commit. The queue entry that was already running completed the merge. Read on the merge: the squash commit carries only
Co-authored-by: Claudeand the maintainer line, with no model name, so the concern that caused the hold did not reachmain.Verified by content on objectui
origin/main:packages/fields/src/widgets/ImageField.tsxnow catches the failed upload (catch (err), line 119) and reports it through the field's error surface; the pinImageField.uploadFailure.test.tsxand.changeset/10226-image-upload-failure-reported.mdare present.Fixes #10226closed the cardcompleted. In this strokepm:blockedis removed together with the staleBlocked-by: #10287/Unlock-action:lines, left over from the Spec Main Shape Gate episode (objectui#10287, fixed by PR objectui#10315).priority:p3,buganddomain:uistay.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
Filing gate ① — a product defect with a named landing site and a reproduction.
The defect
When an image upload fails in
ImageField(@object-ui/fields), the user is told nothing and the rejection escapes the widget as an unhandled promise rejection. The picked image just does not appear; no error text renders.FileField, on the same failure through the same upload pipeline, rendersfields.file.uploadFailed.Where (verified by this seat on
origin/main62597c5)packages/fields/src/widgets/ImageField.tsx— both upload paths usetry { … } finally { … }with nocatch:handleFileChange, thetrynear line 84 /finallynear line 95),handleCropConfirm, thetrynear line 175 /finallynear line 189).Line numbers are as of that sha and will drift; the anchor is the handler names.
Reproduction (measured by the dev on objectui#10180, relayed — ⛔ not re-run by this seat)
A rejecting upload transport (
useUpload().uploadrejecting withnetwork down) and one pick through the real file input ⇒ 1unhandledRejection(network down) and no failure text in the DOM. Reachable today on any failed image upload (network error, 413, a storage outage).Expected
A failed image upload is reported the way
FileFieldreports it: an inline, translated failure message, the picked image not added, and no rejection escaping the handler.Reader
The
domain:uiexecution seat (objectui#5560) — a dispatch candidate for its queue once triage grades it. ⛔ Filed bare:domain:*, type and priority are the triage seat's.Dedupe
The search API is refused through this seat's egress proxy, so the check ran over the
board-archivesnapshot (generated_at2026-09-24T02:36Z; 4075 issue records = the complete open set plus closed history back to 2026-08-08) withgit grep -i -E:ImageField[^"]{0,200}(fail|reject|catch|error)→ 2 hits (objectui#2707, feat(fields): select+multiple → multi-value chip picker; restore fields/core lint gates #2709 — both closed PRs on other subjects);unhandled ?rejection→ 1 hit (objectui#1628, a closed CI PR);image upload[^"]{0,80}(fail|silent|error)→ 0 hits;WizardForm→ 38 hits.⇒ no existing card.
Dedupe words:
ImageField upload failure silent·image upload unhandled rejection·ImageField failed upload no error message·ImageField handleFileChange missing catchProvenance: found by the
os-devon objectui#10180 (report5808760463,out_of_scope_findingsclass a), filed by thedomain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC— devs do not file, seats do.Generated by Claude Code