Skip to content

Commit c271c6a

Browse files
committed
feat(spec): object.imageField is live — the record chrome draws it at the objectui pin
The liveness row flips planned -> live, citing objectui's PageHeaderRenderer and recordPictureUrl at the .objectui-sha pin 9dfaca6. The describe and TSDoc drop the pending-renderer sentence, objects.mdx moves with them, and the showcase field zoo declares imageField: 'f_image'. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 75ddcd1 commit c271c6a

4 files changed

Lines changed: 19 additions & 10 deletions

File tree

‎content/docs/data-modeling/objects.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export const Account = ObjectSchema.create({
6868
| :--- | :--- | :--- | :--- |
6969
| `nameField` | `string` | optional | The stored field used as the record display name, e.g. `'name'` or `'title'` (ADR-0079). The deprecated alias `displayNameField` is still accepted. |
7070
| `titleFormat` | `string` | optional | Deprecated (ADR-0079 → `nameField`). Render-only title template (e.g. `'{{record.name}} - {{record.code}}'`); an explicit `nameField` takes precedence |
71-
| `imageField` | `string` | optional | The record's picture: names a field of this object whose type is `image` or `avatar` — any other name is refused when the object is validated or published. It is the one object-level declaration the record page header is to draw beside the title once the renderer reads it; no renderer draws it yet. |
71+
| `imageField` | `string` | optional | The record's picture: names a field of this object whose type is `image` or `avatar` — any other name is refused when the object is validated or published. It is the one object-level declaration the record page header draws beside the title; a record whose field is empty shows no picture. |
7272
| `highlightFields` | `string[]` | optional | Most-important fields in priority order — default list columns, cards, previews, detail highlight strip (ADR-0085; formerly `compactLayout` — the old spelling was retired and is now rejected) |
7373
| `stageField` | `string \| false` | optional | Linear lifecycle field; `false` declares the status field non-linear and suppresses stage heuristics (ADR-0085) |
7474

‎examples/app-showcase/src/data/objects/field-zoo.object.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ export const FieldZoo = ObjectSchema.create({
2929
pluralLabel: 'Field Zoo',
3030
icon: 'shapes',
3131
description: 'One field of every supported type — exhaustive data-layer coverage.',
32+
// The record's picture: the record page header draws `f_image` beside the
33+
// title. It must name an `image` or `avatar` field of this object; anything
34+
// else is refused at validate/publish. Not seeded, like `showcase_task.cover`
35+
// (see the note in `../seed/index.ts`): a stored image value is a managed
36+
// `sys_file` id, so it comes from an upload, and a specimen without one shows
37+
// no picture rather than a placeholder.
38+
imageField: 'f_image',
3239

3340
fields: {
3441
// ── Core text ───────────────────────────────────────────────────────

‎packages/spec/liveness/object.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@
3232
"note": "ADR-0079 canonical record-title pointer; read by data/display-name.ts resolveDisplayField/resolveRecordDisplayName, objectql search displayField, and plugin-approvals."
3333
},
3434
"imageField": {
35-
"status": "planned",
36-
"verifiedAt": "2026-10-01",
35+
"status": "live",
36+
"verifiedAt": "2026-10-05",
3737
"evidenceScope": "cross-repo",
38-
"evidence": "packages/spec/src/data/object.zod.ts#refuseNonPictureImageField (the AUTHORING judgement only: a name the object does not declare, or a field of any type but `image` / `avatar`, is refused at parse, so at the defineStack, `os validate` and metadata-save doors alike; a value it ACCEPTS has no reader yet); objectui @31971ff1: packages/components/src/renderers/layout/containers.tsx (the record chrome resolves the header title from `nameField` and reads no image key — zero `imageField` hits there, control `nameField` 4 hits)",
39-
"note": "PLANNED, carrier objectui#11383 (the record chrome draws the record's picture from the object's `imageField` in the record page header), which is the seam's acceptance: flip to `live` citing that reader at the `.objectui-sha` pin that carries it. Declared by ruling A on objectstack-ai/hotcrm#1199 — one object-level declaration every record detail page reads, not a `page:header` prop, and no initials/placeholder avatar when the field is empty. ZERO consumers measured: in this repo `imageField` had 0 hits repo-wide before this key (control `nameField` hits), and objectui @31971ff1 has no object-level `imageField` read (its only `imageField` hits are the gallery view's legacy option, a different key on a different schema). Not `authorWarn`'d, on the `app.branding.logo` precedent and README rule 1: a picture pointer is display metadata, an author who sets it today loses nothing and is not misled about behaviour, and the value takes effect the moment the carrier lands with no re-authoring. The key's own describe says the renderer is pending."
38+
"evidence": "packages/spec/src/data/object.zod.ts#refuseNonPictureImageField (the AUTHORING judgement: a name the object does not declare, or a field of any type but `image` / `avatar`, is refused at parse, so at the defineStack, `os validate` and metadata-save doors alike); objectui @9dfaca654311: packages/components/src/renderers/layout/containers.tsx#PageHeaderRenderer (the record chrome reads `objectSchema.imageField` off the record context at :2392 and hands the picture to the record chip's `icon` slot at :2449); objectui @9dfaca654311: packages/components/src/renderers/layout/containers.tsx#recordPictureUrl (:1458, resolves the served row's value of that field to the URL drawn)",
39+
"note": "The record's picture, ruling A on objectstack-ai/hotcrm#1199: one object-level declaration every record detail page reads, not a `page:header` prop. Two halves, both live. The spec judges the pointer at authoring time (`refuseNonPictureImageField`): only a declared `image` / `avatar` field of the same object is accepted. objectui's record chrome draws it at the `.objectui-sha` pin 9dfaca654311, which carries objectui PR 11619 (merge c096f03279): `PageHeaderRenderer` reads the object's `imageField`, `recordPictureUrl` resolves the row's value (an expanded `{ url }`, a bare `sys_file` id served from the storage download path, a legacy URL string, or the first resolvable entry of a `multiple` list), and the picture goes in the record chip's `icon` slot beside the title, an `avatar` drawn round and cropped, an `image` drawn whole. An empty or unresolvable value draws nothing, never initials or a placeholder, and a field the reader may not see arrives masked, so nothing is drawn for it either. The server masks the pointer itself for such a reader (packages/metadata-core/src/object-schema-fls-references.ts). Declared in the reference app by examples/app-showcase/src/data/objects/field-zoo.object.ts (`imageField: 'f_image'`)."
4040
},
4141
"titleFormat": {
4242
"status": "live",

‎packages/spec/src/data/object.zod.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2253,12 +2253,14 @@ const ObjectSchemaBase = strictObject(
22532253
* - A record whose field is empty shows no picture. The reader draws nothing
22542254
* in its place — no initials, no placeholder avatar.
22552255
*
2256-
* The reader is objectui's record chrome, and it does not read the key yet:
2257-
* the liveness ledger holds this row `planned` until it does, so an authored
2258-
* value is accepted, stored and served, and takes effect when the renderer
2259-
* lands with no re-authoring.
2256+
* The reader is objectui's record chrome (`PageHeaderRenderer` in
2257+
* `packages/components/src/renderers/layout/containers.tsx`): it resolves the
2258+
* record's value of the named field and draws it in the record chip's icon
2259+
* slot beside the title, an `avatar` round and cropped, an `image` whole. The
2260+
* liveness ledger row `object.imageField` cites that reader at the objectui
2261+
* pin it was read at.
22602262
*/
2261-
imageField: z.string().optional().describe('The record\'s picture: names the field the record page header (record chrome) is to draw beside the title — one object-level declaration every record detail page reads, not a per-page header prop. Must name a field of this object whose type is `image` or `avatar`; any other name is refused. A record whose field is empty shows no picture (no initials or placeholder). Pending renderer: the record chrome does not draw it yet.'),
2263+
imageField: z.string().optional().describe('The record\'s picture: names the field the record page header (record chrome) draws beside the title — one object-level declaration every record detail page reads, not a per-page header prop. Must name a field of this object whose type is `image` or `avatar`; any other name is refused. A record whose field is empty shows no picture (no initials or placeholder).'),
22622264
/**
22632265
* [ADR-0085] Semantic role: the object's most important fields, in priority
22642266
* order (the first entry wins wherever only one field fits, e.g. child-record

0 commit comments

Comments
 (0)