Repository navigation
fix(fields): ImageField names a nameless image with the translated fields.image.imageAlt, not the literal 'Image' (objectui#10637) - #10660
Conversation
…elds.image.imageAlt, not the literal 'Image' ImageField read its images with readFileValues(value, 'Image'), and readFileValue puts that fallback name on every value that carries no name of its own (a bare sys_file id, a data: URI). So the alt, the enlarge button's label and the lightbox all stopped at the truthy English word on every locale, and the translated fields.image.imageAlt fallback never ran. Follow the image cell's displayableImagesOf: pass an empty fallback name, so a nameless image reaches the translated alt. A value with its own name keeps it. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…h for @object-ui/fields (objectui#10637) Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
CI red at
|
Contract reviewServed-tier: Read: card objectui#10637 (body, triage ① Derived judgmentsDiff vs merge-base: 3 files, +248 −1. The one source change. Every reader of a view's
The lightbox judgment. The triage binds 「the lightbox takes the same name」. At the head the lightbox's image alt is the same translated The cropper judgment. Asked and present: no fallback name into Done but not asked: only the 8-line explanatory comment at Existing pins edited or deleted: none. The diff deletes one source line only. I grepped every test on Can the new pins fail? Yes on both legs.
② Semver level
Changeset prose, sentence by sentence against the diff:
Docs/README prose added by the diff: none (no ③ Boundary flags
Implemented-by: VERDICT: PASS |
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Re-review at the PR's new head. Read: card objectui#10637 (body, triage ① Derived judgmentsThe diff is unchanged. The one source change. Every reader of a view's
The lightbox judgment. The triage binds 「the lightbox takes the same name」. At the head the lightbox image's alt is the same translated The cropper judgment. Asked and present. No fallback name into Done but not asked. Only the 8-line comment at Existing pins edited or deleted. None; the diff deletes one source line. I re-grepped, on the NEW main Can the new pins fail? Yes on both legs, by construction.
② Semver level
Changeset prose, sentence by sentence against the diff:
Docs/README prose added by the diff: none (no ③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10637
Clause-②: no
What changed
ImageFieldread its images withreadFileValues(value, 'Image').readFileValueputs that fallback name on every value that carries no name of its own (a baresys_fileid, adata:URI), soimg.namewas the truthy English wordImage, and eachimg.name || t('fields.image.imageAlt', ...)in the widget stopped at it on every locale. The translated fallback never ran.packages/fields/src/widgets/ImageField.tsx: the onereadFileValuescall now passes''as the fallback name, as the image cell'sdisplayableImagesOfhas since objectui#10493, plus a comment saying why. No other line of the widget changed: every reader of the view's name already falls through on an empty name.file-value.tsis not touched, per the triage ruling.packages/fields/src/widgets/ImageField.imageAlt-10637.test.tsx(new): the pin..changeset/10637-image-alt-translated.md(new):patchfor@object-ui/fields.Every reader of the name in
ImageField(dispatch H3, measured)For a value with no name of its own:
imgaltImagefields.image.imageAlt, indexed by positionaria-labelfields.image.enlargenamingImagefields.image.enlargeembedding the translated altImageImagefields.image.previewimgaltImageopenCropper)Imageimage-0.pngfor the first imagewithRecentUploads)The lightbox row follows the cell exactly: the lightbox gets no name and uses its own translated fallbacks,
fields.image.imageAltfor the image andfields.image.previewfor the dialog title.ImageLightboxis unchanged. The dispatch's pin shape said the lightbox name would be thefields.image.imageAlttext. Measured, the dialog's accessible name comes from its title, so the pin asserts the translated preview title for the dialog and the translated alt for its image.The cropper row is a side effect of the fix, not its purpose.
image-${index}.pngwas already the widget's fallback inopenCropper, and it was unreachable for the same reason as the translated alt. It was measured with a throwaway probe, never committed, that mocked the cropper dialog to expose itsoutputName: baseImage, afterimage-0.png. A value with its own name readcover.pngon both trees. The name reachesoriginal_nameon the legacy-blob submit path and the oversize and failed-upload messages. It is not pinned.Measured
Base =
f99f9cd.adb443fd139941Base readings from those failures: under
zh, a bare id's alt wasImagewhere the zh pack'sfields.image.imageAltfor index 1 was expected, in read-only and in edit mode. The lightbox dialog's accessible name wasImage. Two bare ids readImage,Image. A data URI readImage. Underenthe alt wasImagewhere the pack givesImage 1. The four rows that passed on the base are the premise row and three controls: a value with its own name, an https URL, and a just-uploaded image stored as a bare id.Every expected string in the pin comes from the imported
enorzhpack, interpolated in the test. None is typed by hand. The premise row asserts that the two packs differ and that the zh strings do not containImage.Tests and gates
The union was run after the final commit, at
d139941. That head mergesorigin/mainat785d3a7, which brought objectui#10653'sfile-value.tschange. ItsnameFromUrledit does not interact with the fallback name.pnpm exec vitest run --maxWorkers=2 packages/fields/: 210 files passed and 1 skipped, 3409 tests passed and 7 skipped, exit 0.pnpm turbo run type-check --filter @object-ui/fields --concurrency=2: 12 of 12 tasks successful, exit 0.@object-ui/fields:type-checkwas a cache miss and executed. The package'stype-checkchainstsconfig.test.json, andtsc -p tsconfig.test.json --listFilesOnlylists the new pin once (control:ImageField.maxSize.test.tsxis also listed once).--no-inline-config, on the two changed source and test files: exit 0, 0 errors, 7 warnings, all inImageField.tsx. They are the same rule set as the base blob: 4@typescript-eslint/no-explicit-any, 1react-hooks/exhaustive-deps, 2react-hooks/preserve-manual-memoization. Their messages differ from the base only by the line numbers they embed, which the 8 added comment lines shift. This narrowed run is a measurement, not a skipped check, on three counts. (1) The population comes fromeslint.config.js, whose**/*.{ts,tsx}block covers both files. (2) The--format jsonoutput lists exactly 2 files. (3) Invariance: the config enables no type-aware parsing (0 hits forprojectService|parserOptions|project:|TypeChecked, against 9 hits forfiles:as a control), and no rule undereslint-rules/importsfs. So this diff cannot move the verdict on any untouched file.pnpm check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:new-line-citations(0 new citations),check:changeset-claims,check:pending-changeset-literals,check:i18n-keys,check:i18n-dead-keys(a report; a grep of its output finds nofields.image.*key, and the same grep does findengine.quickfind.empty, a key it lists),check:unreferenced-sources,node scripts/check-changeset-presence.mjs,node scripts/check-changeset-no-major.mjsandnode scripts/check-changeset-overwrite.mjs. The last five are added to the dispatch's list, derived from the diff: the i18n call sites it sits beside, a new test file, and the changeset workflow's two gates.Round 2: merged
origin/mainat7baede3(objectui#10677, which carries the change for the objectui#10645 test) into the branch with no conflicts, giving headaf4162e. At that head the pin (10 passed),pnpm exec vitest run packages/fields/(211 files passed and 1 skipped, 3419 tests passed and 7 skipped) andpnpm turbo run type-check --filter @object-ui/fields(12 of 12) each exit 0 (os-dev-report round 2, comment5836466253).Acceptance notes
FileValueView.name's doc infile-value.tsgives the image cell as the caller that asks for an empty fallback.ImageFieldnow asks too. The doc stays true, because it names the cell as an example and not as the only caller. It is left alone, because the ruling keepsfile-value.tsout of this change.uploadResultViewnames a resultFilewhen both the result's name and the original name are empty.ImageFieldnever reaches that: its two upload paths always pass a name (the picked file's, or the cropper's).Dispatched by the
domain:uiseat 5 PM, sessionhttps://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy.Generated by Claude Code