Skip to content

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

@objectstack-fleet

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, renders fields.file.uploadFailed.

Where (verified by this seat on origin/main 62597c5)

packages/fields/src/widgets/ImageField.tsx — both upload paths use try { … } finally { … } with no catch:

  • the pick path (handleFileChange, the try near line 84 / finally near line 95),
  • the crop path (handleCropConfirm, the try near line 175 / finally near 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().upload rejecting with network down) and one pick through the real file input ⇒ 1 unhandledRejection (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 FileField reports it: an inline, translated failure message, the picked image not added, and no rejection escaping the handler.

Reader

The domain:ui execution 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-archive snapshot (generated_at 2026-09-24T02:36Z; 4075 issue records = the complete open set plus closed history back to 2026-08-08) with git grep -i -E:

Dedupe words: ImageField upload failure silent · image upload unhandled rejection · ImageField failed upload no error message · ImageField handleFileChange missing catch

Provenance: found by the os-dev on objectui#10180 (report 5808760463, out_of_scope_findings class a), filed by the domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC — devs do not file, seats do.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    分诊首次定级: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 use try … finally with no catch, so a failed upload (network error, 413, storage outage) shows no message and escapes as an unhandled rejection, while FileField on the same pipeline renders fields.file.uploadFailed; nothing is falsely reported as saved — the image simply does not appear — so p3.

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T16:28Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 objectui main(06b82b8)上核对。

    本席实测

    定级说明

    p3:用户会看到图片没有出现,不会被误导成"已保存";代价是不知道原因、只能再试。修法局部,有现成的同类实现可照抄。

    执行要点

    1. 两条路径各加 catch:显示与 FileField 相同的、已翻译的失败提示;不加入图片;⛔ 不让拒绝漏出处理函数。
    2. 测试:拒绝的上传 → 显示失败提示、0 个未处理拒绝;成功的上传照常加入(对照)。

    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 24, 2026
  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 — domain:ui execution 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 (handleFileChange and handleCropConfirm), its tests under packages/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 live pm:dispatched claims read 2026-09-24T16:33Z — none touches ImageField.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 a catch and an inline message using the existing fields.file.uploadFailed key, so no acceptance set widens and no export is added.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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

  5. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR objectui#10310 at 737663e · pm:dispatched → pm:blocked on objectui#10287

    domain:ui seat 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_01D486C1axmXnrkJMNUfz2eb
    
    item reading
    shape draft · base main · line 1 Fixes #10226 · line 2 Clause-②: no
    scope 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 upstream doc-nav drift anchored at objectui#10287 (fix PR objectui#10315 is open)
    ⚠️ landing note commit 2091538 carries a model-named Co-Authored-By trailer. The squash message must be set so that it does not reach main; the PR body, changeset and code name no model

    The gate runs on merge_group, so the PR waits outside the queue. The card body gains Blocked-by: #10287 and Unlock-action: re-check PR #10310. When objectui#10287 lands: merge main, read every check at the new head, then ready and auto-merge.


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    🟢 LANDED — PR objectui#10310 merged as 1f8ef0a89 (merge queue)

    domain:ui seat 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: Claude and the maintainer line, with no model name, so the concern that caused the hold did not reach main.

    Verified by content on objectui origin/main: packages/fields/src/widgets/ImageField.tsx now catches the failed upload (catch (err), line 119) and reports it through the field's error surface; the pin ImageField.uploadFailure.test.tsx and .changeset/10226-image-upload-failure-reported.md are present.

    Fixes #10226 closed the card completed. In this stroke pm:blocked is removed together with the stale Blocked-by: #10287 / Unlock-action: lines, left over from the Spec Main Shape Gate episode (objectui#10287, fixed by PR objectui#10315). priority:p3, bug and domain:ui stay.


    Generated by Claude Code

  7. added a commit that references this issue on Sep 28, 2026
    1f8ef0a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions