Found while fixing framework#4774 / framework#4891 (the app-showcase seeded data: URIs into an image field, which ADR-0104 rejects). Removing the bad seed data surfaced this: with a correct value the cover still would not render, so the gallery has no working path for the platform's current file contract.
The contract, backend side
Since ADR-0104 D3 wave 2 the stored value of a file / image / avatar / video / audio field is an opaque sys_file id (FileReferenceIdValueSchema — /^[A-Za-z0-9_-]{1,64}$/). On read, the engine expands it in place into the rich form (ObjectQLEngine.resolveFileReferences, packages/objectql/src/engine.ts ~3857):
Resolve file-field id references to their expanded FileValueSchema form (ADR-0104 D3 wave 2). A file/image/… value stored as an opaque sys_file id string is enriched, in place, to { id, name, size, mimeType, url } — url derived from the stable /files/:fileId resolver, never stored.
So a conforming record arrives at the client with cover as an object carrying url, not a string.
What the gallery does
packages/plugin-list/src/ObjectGallery.tsx:
const coverField = gallery?.coverField ?? schema.imageField ?? 'image';
// …
const hasAnyCover = useMemo(
() => items.some((it) => typeof (it as any)[coverField] === 'string' && !!(it as any)[coverField]),
[items, coverField],
);
// …
const imageUrl = item[coverField] as string | undefined;
Both reads assume the field value is the URL string. Against the expanded shape:
hasAnyCover is false (the value is an object, not a string), so showCoverArea is false and the cover area collapses for the whole gallery;
- were it shown,
imageUrl would be an object and '<img src={…}>' would fall to the onError letter-avatar.
Net: for a spec-correct image value the cover never appears. The only values that render today are exactly the ones ADR-0104 retired — an inline data: URI or an external URL — which is why this stayed invisible: the showcase was feeding it a data: URI.
coverField is documented in the spec as "Attachment/image field to display as card cover" (packages/spec/src/ui/view.zod.ts ~376), so image fields are the intended input, not an edge case.
Suggested direction
Resolve the cover through the value shape rather than assuming a string — accept { url } (the expanded read form), a bare string (legacy inline/external, still valid during the dual-mode window), and treat an opaque id with no expansion as "no cover" rather than a broken src. hasAnyCover has to use the same resolver or the area keeps collapsing.
Worth checking the same assumption in the sibling paths that thread imageField around — packages/plugin-list/src/ListView.tsx (~1319, ~1570), packages/app-shell/src/views/ObjectView.tsx (~1518), packages/plugin-view/src/ObjectView.tsx (~696) — and any avatar/attachment cell renderer with the same shape.
Repro
showcase_task.cover is Field.image() and TaskViews binds gallery: { coverField: 'cover' }.
- Upload a cover on a task (which is how a managed file is meant to come into existence), so
cover holds a real committed sys_file id.
- Open the task Gallery view — no cover renders.
Observed against framework main @ 6bc93dc.
Filed unassigned.
Found while fixing framework#4774 / framework#4891 (the app-showcase seeded
data:URIs into animagefield, which ADR-0104 rejects). Removing the bad seed data surfaced this: with a correct value the cover still would not render, so the gallery has no working path for the platform's current file contract.The contract, backend side
Since ADR-0104 D3 wave 2 the stored value of a
file/image/avatar/video/audiofield is an opaquesys_fileid (FileReferenceIdValueSchema—/^[A-Za-z0-9_-]{1,64}$/). On read, the engine expands it in place into the rich form (ObjectQLEngine.resolveFileReferences,packages/objectql/src/engine.ts~3857):So a conforming record arrives at the client with
coveras an object carryingurl, not a string.What the gallery does
packages/plugin-list/src/ObjectGallery.tsx:Both reads assume the field value is the URL string. Against the expanded shape:
hasAnyCoverisfalse(the value is an object, not a string), soshowCoverAreais false and the cover area collapses for the whole gallery;imageUrlwould be an object and'<img src={…}>'would fall to theonErrorletter-avatar.Net: for a spec-correct
imagevalue the cover never appears. The only values that render today are exactly the ones ADR-0104 retired — an inlinedata:URI or an external URL — which is why this stayed invisible: the showcase was feeding it adata:URI.coverFieldis documented in the spec as "Attachment/image field to display as card cover" (packages/spec/src/ui/view.zod.ts~376), so image fields are the intended input, not an edge case.Suggested direction
Resolve the cover through the value shape rather than assuming a string — accept
{ url }(the expanded read form), a bare string (legacy inline/external, still valid during the dual-mode window), and treat an opaque id with no expansion as "no cover" rather than a brokensrc.hasAnyCoverhas to use the same resolver or the area keeps collapsing.Worth checking the same assumption in the sibling paths that thread
imageFieldaround —packages/plugin-list/src/ListView.tsx(~1319, ~1570),packages/app-shell/src/views/ObjectView.tsx(~1518),packages/plugin-view/src/ObjectView.tsx(~696) — and any avatar/attachment cell renderer with the same shape.Repro
showcase_task.coverisField.image()andTaskViewsbindsgallery: { coverField: 'cover' }.coverholds a real committedsys_fileid.Observed against framework
main@ 6bc93dc.Filed unassigned.