Filed unassigned while implementing #3318 (PR #5429). ⛔ Not claimed. Out of that card's scope, and deliberately not fixed there because the correct shape is a contract decision rather than a mechanical edit.
The gap
#3318 moved the grid widget's per-cell aria-invalid off the non-focusable td and onto each cell's own control, so a required-but-empty cell now announces itself to assistive tech. That was wired for the cell controls GridField renders directly:
- text / number / currency / date / datetime / time — the
Input - select — the
SelectTrigger - lookup —
LookupField, via its published error slot (it already sets aria-invalid on its own focusable trigger)
file columns were left out. A required file cell still gets the red ring and the title, but no element in the cell carries aria-invalid. Same defect class as the card that was just closed, one cell type short of complete.
Why it was not fixed in PR #5429
FileCell (packages/fields/src/widgets/FileField.tsx) takes a closed prop set — value, onChange, disabled, multiple, accept, maxSize, aria-label, data-cell — with no validity slot. Delivering the state means changing that component's contract, and there is a real choice in how:
- Give
FileCell an error slot mirroring LookupField, so the grid passes the published slot and the component decides its own carrier. Consistent with how the lookup cell was wired, and with the error slot on FieldWidgetPropsSchema. - Give it a narrower boolean
invalid prop — smaller, but a second spelling for a state the codebase already names error.
(1) looks right on the contract-first reading (AGENTS.md #0.1 — one spelling, fixed at the producer), but FileCell is a cell-level helper rather than a full field widget, so it is worth a moment's thought rather than an assumption. That is why this is a card and not a drive-by edit.
Reproducing
Configure a grid column with type: 'file' and required: true, leave the cell empty on a real row, and inspect the cell: the td gets bg-destructive/5 ring-destructive/50 and data-testid="line-items-invalid-{row}-{name}", and nothing in the subtree has aria-invalid="true".
Note on the registry sweep
The widget-aria-invalid-registry-e2e.test.tsx sweep does not catch this and is not expected to: it configures the grid with text/number columns, so the row it renders delivers correctly and grid sits legitimately in the positive guard. This is a per-column-type gap inside a widget that passes, which is a level of detail that sweep does not reach.
Related
Generated by Claude Code
Filed unassigned while implementing #3318 (PR #5429). ⛔ Not claimed. Out of that card's scope, and deliberately not fixed there because the correct shape is a contract decision rather than a mechanical edit.
The gap
#3318 moved the grid widget's per-cell
aria-invalidoff the non-focusabletdand onto each cell's own control, so a required-but-empty cell now announces itself to assistive tech. That was wired for the cell controlsGridFieldrenders directly:InputSelectTriggerLookupField, via its publishederrorslot (it already setsaria-invalidon its own focusable trigger)filecolumns were left out. A requiredfilecell still gets the red ring and thetitle, but no element in the cell carriesaria-invalid. Same defect class as the card that was just closed, one cell type short of complete.Why it was not fixed in PR #5429
FileCell(packages/fields/src/widgets/FileField.tsx) takes a closed prop set —value,onChange,disabled,multiple,accept,maxSize,aria-label,data-cell— with no validity slot. Delivering the state means changing that component's contract, and there is a real choice in how:FileCellanerrorslot mirroringLookupField, so the grid passes the published slot and the component decides its own carrier. Consistent with how the lookup cell was wired, and with theerrorslot onFieldWidgetPropsSchema.invalidprop — smaller, but a second spelling for a state the codebase already nameserror.(1) looks right on the contract-first reading (AGENTS.md #0.1 — one spelling, fixed at the producer), but
FileCellis a cell-level helper rather than a full field widget, so it is worth a moment's thought rather than an assumption. That is why this is a card and not a drive-by edit.Reproducing
Configure a grid column with
type: 'file'andrequired: true, leave the cell empty on a real row, and inspect the cell: thetdgetsbg-destructive/5 ring-destructive/50anddata-testid="line-items-invalid-{row}-{name}", and nothing in the subtree hasaria-invalid="true".Note on the registry sweep
The
widget-aria-invalid-registry-e2e.test.tsxsweep does not catch this and is not expected to: it configures the grid with text/number columns, so the row it renders delivers correctly andgridsits legitimately in the positive guard. This is a per-column-type gap inside a widget that passes, which is a level of detail that sweep does not reach.Related
aria-invalid—— #3306 全注册表守卫实测的账本 #3318 / PR fix(fields): drive the grid widget's per-cell aria-invalid from form-level failure #5429 — the per-cell delivery this completesfilecolumns a real upload control in the cellGenerated by Claude Code