Skip to content

Commit 07bf21f

Browse files
spec(liveness): object.imageField is live — the record chrome draws it at the objectui pin (#21824)
Part of #21765 Clause-②: no Seam: `spec:ObjectSchema.imageField` → `renderer:objectui record chrome (page:header, containers.tsx)` **Why `Part of` and not a closing line.** The card's item 3 stays open: whether Studio's object form offers `imageField`. Its `omit` row lives in this repo, and a written rule says the row is stale once the key is enforced. The same rule hands the offer itself to a decision, and the gate refuses the row's deletion unless that decision is made in the same change (measured below). So item 3 goes back to the seat as an open question. This PR does items 1 and 2, and adds the declaring example the director seat's unblock asked for. ## The reader, measured at the pin `.objectui-sha` on `main` is `9dfaca654311`. `git merge-base --is-ancestor c096f032793d 9dfaca654311` exits 0 in the objectui clone, so the pin carries objectui PR 11619's merge. In objectui at `9dfaca654311`, `packages/components/src/renderers/layout/containers.tsx`: - `:1474` `PageHeaderRenderer`. On its record-context branch (`objectSchema` and `data` from the record context), `:2392` reads `objSchema?.imageField`. - `:1458` `recordPictureUrl(value)` resolves the served row's value of that field. It takes the expanded `{ url }` form, a bare `sys_file` id (served from `/api/v1/storage/files/`), a legacy URL string, or the first entry of a `multiple` list that resolves. - `:2449` `icon={recordPicture}` puts the picture in the record chip's `icon` slot beside the H1. An `avatar` field is drawn round and cropped, an `image` field whole in a rounded square. An empty value draws nothing: no initials, no placeholder. ## What changed 1. **Liveness row** `packages/spec/liveness/object.json` `imageField`: `planned` → `live`, `verifiedAt` 2026-10-05, `evidenceScope: cross-repo`. The evidence has three parts: the authoring judgement (`object.zod.ts#refuseNonPictureImageField`), plus `objectui 9dfaca654311` `containers.tsx#PageHeaderRenderer` and `#recordPictureUrl` with the lines above. The note is rewritten to what is true now. Both halves are live, the carrier is gone, and the note names the reference-app declaration. 2. **Describe and TSDoc** `packages/spec/src/data/object.zod.ts`: - The `.describe()` drops "Pending renderer: the record chrome does not draw it yet." Its "is to draw" becomes "draws". - The TSDoc paragraph that said the reader "does not read the key yet" now names the reader (`PageHeaderRenderer`, the record chip's icon slot). No shape, refusal or error text moves. 3. **Hand-written doc** `content/docs/data-modeling/objects.mdx` → Display: "is to draw ... once the renderer reads it; no renderer draws it yet" becomes "draws beside the title". The row also gains the describe's own sentence: an empty field shows no picture. 4. **Regenerated by the repo's tooling, not by hand.** `pnpm --filter @objectstack/spec check:generated` named two stale artifacts, `check:docs` and `check:liveness`. `--fix` regenerated exactly those two: - `content/docs/references/{api/metadata,data/object,system/migration}.mdx`; - `liveness/state-counts/object.md`, where `object` moves to live 51 / planned 1. 5. **Declaring example** `examples/app-showcase/src/data/objects/field-zoo.object.ts`: `imageField: 'f_image'`, on the existing `Field.image()`. This is the object and field the pin-bump smoke drew with a temporary edit. - Not seeded. The showcase seeds the field zoo, but a stored `image` value is a managed `sys_file` id. The seed's own note on `showcase_task.cover` (`src/data/seed/index.ts`, ADR-0104) says why a seed cannot honestly mint one. 6. **Changeset** `.changeset/21765-object-image-field-live.md`: `@objectstack/spec` `patch`, `Clause-②: no`. It is text only. ## Item 3: Studio's object-form `omit` row (measured, not decided) - **Where it lives:** in this repo. The row is `packages/spec/src/system/metadata-form-zod-reconciliation.test.ts:394-400`, in the group "Declared, not enforced yet — no offer until it is enforced" (`:386`). The form it excuses is `packages/spec/src/data/object.form.ts`. - **The written rule** is at `:307-309`: "The not-enforced-yet rows hold only while the verdict does. Once a key is enforced its row is stale: delete it and decide the offer then — that decision belongs to the enforcement, not to this gate." - **Deleting the row alone, measured** on the committed state with `scripts/ablation-replace.mjs` in wrap mode: - The anchor hit 1 → 0, blob `26f47a279c33` → `bb160bb1b747`. - The run went **1 failed / 75 passed**: `object: every top-level key the author may write is offered, or its omission is recorded`, which printed "object.(root): accepted by the Zod but unauthorable in the form — offer it, or add a root ledger entry that records why it is not offered: expected [ 'imageField' ] to deeply equal []". - Restore: blob == HEAD (`26f47a279c33`) and `git diff HEAD` empty. Baseline before the probe: 76/76. - **Why it is left unchanged.** The rule's first half cannot land without its second half, and the rule calls the second half a decision. Neither way out is mechanical: - A form row repeats the call `#19331` made for 45 keys. It also takes the four `metadata-forms.generated.ts` catalogs in `packages/platform-objects`, with authored `zh-CN` / `ja-JP` / `es-ES` leaves. Those files are outside this claim's file surface. - A recorded reason fits no existing class. The ruled classes admit only their ruled key lists. - **Consequence while it is open:** from this PR's landing, the row's reason says "liveness verdict `planned`" while the ledger says `live`. The test reads liveness for no class except the three ruled ones, so it stays green. The stale reason is what the open question is about. ## Tests and gates (tree `80a7677773`, the final commit) - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2`: **615 files passed, 18360 passed / 1 todo**, `VERDICT command-exit 0`. - `pnpm --filter @objectstack/spec run typecheck`: exit 0, "52 file(s) / 246 error(s) / 135 pinned signature(s) held". - `pnpm --filter @objectstack/example-showcase verify`: `os validate` "Validation passed", `tsc --noEmit` clean, **32 files / 399 tests passed**, `VERDICT command-exit 0`. The dependency closure was built first (`turbo run build --filter=@objectstack/example-showcase^...`, 60/60). - `check:liveness`: exit 0. `object` reads 52 classified (live 51, planned 1); the planned one is `externalSharingModel`. The repo total reads 1000 live · 1 experimental · 1 live-elsewhere · 108 dead · 8 planned = 1118. The repo-local evidence path and the `#refuseNonPictureImageField` anchor resolve. The `objectui:` paths are attributed and counted, never resolved: that is the gate's boundary. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived **107** commands from 9 paths against merge base `75ddcd1b4`. `--ran` reconciles **107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN**, and every one exited 0. - Lint, a measured narrowing: - `eslint --no-inline-config --format json` on the two changed TS files: **2 files, 0 errors, 0 warnings**. - The population is `eslint.config.mjs:971` (`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`). - No type-aware linting (`eslint.config.mjs:328`, no `parserOptions.project`), so this diff cannot move a verdict on an untouched file. - Served-object check on a fresh showcase backend (my own port, torn down after): - `GET /api/v1/meta/object/showcase_field_zoo` serves `imageField: 'f_image'` with `fields.f_image.type: 'image'`. That is the `objectSchema` the reader reads. - I uploaded a 1×1 PNG through `/api/v1/storage/upload/presigned` + `complete` and set `f_image` to its id. The record then serves `f_image` as `{ id, name, size, mimeType, url: '/api/v1/storage/files/…' }`, the form `recordPictureUrl` takes first. That URL answers 200 `image/png` after the redirect, with bytes identical to the upload. - Field-consumer warning, an `os validate` A/B on the declaration. Without it, `f_image` carries "declared but nothing in this stack reads or displays it" (71 warnings). With it, that warning goes and nothing else moves (70). - No ablation or reverse verification applies: this PR authors no behaviour. It moves a ledger row and text, and adds one declaration the parse already accepts. ## Acceptance notes - **Browser check: NOT MEASURED.** It needs a console build at the pin, about 9 minutes of the shared box. The pin-bump smoke on PR #21800 already drew this picture on `showcase_field_zoo` / `f_image` at this same pin. This PR makes that temporary declaration permanent, and the served-object check above covers what the reader reads. - `packages/spec/CHANGELOG.md` still says `imageField` "is accepted, stored and served but nothing draws it". That entry is release-owned and was true when it shipped, so it is not touched. - The `omit` row's `why` text is stale from this PR's landing; see item 3 above. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a0176ef commit 07bf21f

9 files changed

Lines changed: 35 additions & 15 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
`ObjectSchema.imageField`'s description no longer says the renderer is pending: the record page header draws the picture
6+
7+
Clause-②: no
8+
9+
The console's record page header now reads `imageField`: it draws the named `image` / `avatar` field's value in the record chip beside the title, an `avatar` round and cropped, an `image` whole, and a record whose field is empty shows no picture. The `.describe()` text that `os validate`, the JSON Schema and the reference docs carry dropped its last sentence, "Pending renderer: the record chrome does not draw it yet.", and now says the header draws the picture rather than is to draw it.
10+
11+
Text only: no key, schema shape, refusal, error code or status moves. An `imageField` you already authored takes effect as it is, with nothing to re-author.

‎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

‎content/docs/references/api/metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ Metadata query with filtering, sorting, and pagination
960960
| **nameField** | `string` | optional | [ADR-0079] Canonical primary title field — the stored field used as the record display name (e.g. "name", "title"). |
961961
| **displayNameField** | `string` | optional | [DEPRECATED → nameField] Field to use as the record display name (e.g., "name", "title"). Accepted as an alias for nameField. |
962962
| **titleFormat** | `string \| { dialect: 'template'; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField. Placeholders may be written `{{field}}` or `{field}` — the title renderers treat the two as equivalent, normalizing `{{field}}` to `{field}` before substituting; neither spelling is judged at parse time. |
963-
| **imageField** | `string` | optional | 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. |
963+
| **imageField** | `string` | optional | 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). |
964964
| **highlightFields** | `string[]` | optional | [ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. |
965965
| **stageField** | `string \| false` | optional | [ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. |
966966
| **editMode** | `Enum<'modal' \| 'page'>` | optional | Edit-interaction intent for records of this object: 'modal' opens the edit form as a dialog over the current view; 'page' navigates to a dedicated full-page edit route. Absent = the renderer picks its own default (objectui defaults to modal). Cross-renderer intent, not pixel styling (family). |

‎content/docs/references/data/object.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const result = ApiMethod.parse(data);
164164
| **nameField** | `string` | optional | [ADR-0079] Canonical primary title field — the stored field used as the record display name (e.g. "name", "title"). |
165165
| **displayNameField** | `string` | optional | [DEPRECATED → nameField] Field to use as the record display name (e.g., "name", "title"). Accepted as an alias for nameField. |
166166
| **titleFormat** | `string \| { dialect: 'template'; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField. Placeholders may be written `{{field}}` or `{field}` — the title renderers treat the two as equivalent, normalizing `{{field}}` to `{field}` before substituting; neither spelling is judged at parse time. |
167-
| **imageField** | `string` | optional | 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. |
167+
| **imageField** | `string` | optional | 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). |
168168
| **highlightFields** | `string[]` | optional | [ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. |
169169
| **stageField** | `string \| false` | optional | [ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. |
170170
| **editMode** | `Enum<'modal' \| 'page'>` | optional | Edit-interaction intent for records of this object: 'modal' opens the edit form as a dialog over the current view; 'page' navigates to a dedicated full-page edit route. Absent = the renderer picks its own default (objectui defaults to modal). Cross-renderer intent, not pixel styling (family). |

‎content/docs/references/system/migration.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ Create a new object
341341
| **nameField** | `string` | optional | [ADR-0079] Canonical primary title field — the stored field used as the record display name (e.g. "name", "title"). |
342342
| **displayNameField** | `string` | optional | [DEPRECATED → nameField] Field to use as the record display name (e.g., "name", "title"). Accepted as an alias for nameField. |
343343
| **titleFormat** | `string \| { dialect: 'template'; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField. Placeholders may be written `{{field}}` or `{field}` — the title renderers treat the two as equivalent, normalizing `{{field}}` to `{field}` before substituting; neither spelling is judged at parse time. |
344-
| **imageField** | `string` | optional | 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. |
344+
| **imageField** | `string` | optional | 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). |
345345
| **highlightFields** | `string[]` | optional | [ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. |
346346
| **stageField** | `string \| false` | optional | [ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. |
347347
| **editMode** | `Enum<'modal' \| 'page'>` | optional | Edit-interaction intent for records of this object: 'modal' opens the edit form as a dialog over the current view; 'page' navigates to a dedicated full-page edit route. Absent = the renderer picks its own default (objectui defaults to modal). Cross-renderer intent, not pixel styling (family). |
@@ -628,7 +628,7 @@ Create a new object
628628
| **nameField** | `string` | optional | [ADR-0079] Canonical primary title field — the stored field used as the record display name (e.g. "name", "title"). |
629629
| **displayNameField** | `string` | optional | [DEPRECATED → nameField] Field to use as the record display name (e.g., "name", "title"). Accepted as an alias for nameField. |
630630
| **titleFormat** | `string \| { dialect: 'template'; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField. Placeholders may be written `{{field}}` or `{field}` — the title renderers treat the two as equivalent, normalizing `{{field}}` to `{field}` before substituting; neither spelling is judged at parse time. |
631-
| **imageField** | `string` | optional | 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. |
631+
| **imageField** | `string` | optional | 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). |
632632
| **highlightFields** | `string[]` | optional | [ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. |
633633
| **stageField** | `string \| false` | optional | [ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. |
634634
| **editMode** | `Enum<'modal' \| 'page'>` | optional | Edit-interaction intent for records of this object: 'modal' opens the edit form as a dialog over the current view; 'page' navigates to a dedicated full-page edit route. Absent = the renderer picks its own default (objectui defaults to modal). Cross-renderer intent, not pixel styling (family). |

‎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/liveness/state-counts/object.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them.
1212

1313
| Type | live | exp | elsewhere | dead | planned | classified |
1414
|---|---|---|---|---|---|---|
15-
| `object` | 50 | 0 | 0 | 0 | 2 | 52 |
15+
| `object` | 51 | 0 | 0 | 0 | 1 | 52 |

‎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)