You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 93027d8
Browse filesBrowse the repository at this point in the historyBrowse files
feat(client): `packages.publishDrafts(id, { backfill })` sends the opt-in backfill of new fields' defaults
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
-`opts.backfill?: Array<{ object, field }>` is sent in the publish body; the response's `backfillApplied` reports the rows written per field. A field the publish does not add, or one without a literal `defaultValue`, refuses the whole request with a `400` before anything is published.
feat(metadata-protocol): `publishPackageDrafts` takes an opt-in `backfill` that writes the literal `defaultValue` of fields the publish adds into the object's existing rows, as the caller
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
-**New request keys.**`backfill?: Array<{ object, field }>` names fields this publish adds; `context` is the caller's execution context the writes run as. Absent or `[]`, no row is touched.
10
+
-**Judged before anything is published.** Every entry must name an object drafted in the package, a field its draft declares, a field the published object does not have yet, and a literal `defaultValue` (a runtime token such as `NOW()` or `current_user`, or an expression, is resolved per new record and is refused). A malformed entry, a duplicate, or a backfill without a caller context is refused too. All problems come back in one `400 VALIDATION_ERROR`, one `issues[]` entry per problem with its `backfill.N` path, and nothing is promoted or written.
11
+
-**Written after the publish, as the caller.** Once the drafts are live and the new column exists, rows whose value is absent are read and written 200 at a time through `updateManyData` with the caller's context, so permissions, RLS/FLS, validation and record hooks run as on that caller's own edit. A value already stored is never overwritten.
12
+
-**New response key `backfillApplied`:**`{ success, fields: [{ object, field, updated, error?, code? }] }`, one element per requested field. `updated` counts the rows this publish wrote. A field stops at its first failing row and reports the rows written so far with the error; the publish is not undone. On the SQL driver the new column's DEFAULT already fills existing rows, so `updated` is 0 there.
feat(runtime): `POST /packages/:id/publish-drafts` accepts a `backfill` body key and passes it to the protocol with the caller's execution context
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
- The body's `backfill` (`[{ object, field }]`) reaches `publishPackageDrafts` as sent, together with the request's own execution context, so the backfill writes run as the signed-in caller, never as the system. A body without the key sends neither.
10
+
- The protocol's request refusal answers `400 VALIDATION_ERROR` with its `issues[]`, before anything is published, flipped or announced. The `backfillApplied` receipt rides the `200` body.
feat(spec): `PublishPackageDraftsResponseSchema` declares `backfillApplied`, the per-field receipt of the publish door's opt-in backfill
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
-`backfillApplied?: { success, fields: [{ object, field, updated, error?, code? }] }`, present only when the request named a `backfill` and the batch published. Declared so the door's response parse carries it instead of stripping it.
feat(spec): the bound record blocks `record:details`, `record:highlights` and `record:quick_actions` accept an authored `emptyState`, drawn only when the block's `dataSource` binding answers no row
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
-**The key.**`RecordDetailsProps`, `RecordHighlightsProps` and `RecordQuickActionsProps` each gain an optional `emptyState`: the list view's own `EmptyStateSchema` by reference (`{ title, message, icon, link }`), the same shape `object-grid.emptyState` takes. Until now all three rows refused the key, so a page whose binding can legitimately answer no row (for example an environment block on an organisation with no production environment) could only show the block's own placeholder: "record not found", nothing, or an empty action bar.
10
+
-**When it is drawn.** Only when the block carries a node-level `dataSource` binding and that read answers no row. A block with no binding reads the record page's record and never draws it, a loading read draws the loading state, and a failed read draws its error. On `record:quick_actions`, a bound record whose bar resolves no action keeps the existing empty placeholder.
11
+
-**Planned, not yet rendered.** No renderer reads the key yet; each describe opens with `[PLANNED — not yet rendered]`, and the renderer half is objectstack-ai/objectui#12932, which ships once a spec release carrying the key is installable. Until then an authored `emptyState` validates and is not drawn.
12
+
-**No other row moves.** Every other `ComponentPropsMap` row still refuses `emptyState` (`record:related_list`, whose `parentDataSource` has its own declared answer for no row, included), so `os validate` keeps reporting it there as `component-props-unknown-key`. Nothing narrows.
Copy file name to clipboardExpand all lines: content/docs/references/api/protocol.mdx
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2524,6 +2524,7 @@ Installed package with runtime lifecycle state
2524
2524
|**materializeApplied**|`{ success: boolean; inserted: integer; updated: integer; failures: object[] }`| optional | ADR-0086 P2 — aggregate result of publish-time materializers across the batch (e.g. `flow`, whose draft credentials become the live ones), including side-effect failures surfaced by the per-item effects loop. Present ONLY when at least one published item had a registered materializer or a side-effect failure. Best-effort, same contract as `seedApplied`. |
2525
2525
|**probes**|`any`| optional | ADR-0038 L3 post-publish runtime probe report — one real read per published artifact (seeded objects have rows, views are readable, widget dataset selections execute). DELIBERATELY OPAQUE in this contract: the key is declared and carried through verbatim, but its inner shape is intentionally not modeled until a consumer needs a field of it. Present only when something was publishable; probes never fail the publish. |
2526
2526
|**commitId**|`string`| optional | ADR-0067 — id of the commit this publish recorded. Absent when nothing published. |
2527
+
|**backfillApplied**|`{ success: boolean; fields: object[] }`| optional | The opt-in `backfill` request key's receipt: the literal `defaultValue` of each named field this publish added, written into the object's existing rows whose value is absent, page by page, as the caller (permissions, RLS/FLS and record hooks apply as on their own edit). Present ONLY when the request named at least one field and the batch published; absent means nothing was asked. A request that names a field this publish does not add, or one without a literal default, is refused with a 400 before anything is published, so this key never reports a refusal. |
2527
2528
|**unhiddenApps**|`string[]`| optional | ADR-0045 §3 — names of the apps whose `_unpublished` gate this publish cleared (publish = live AND visible; a materialized additive build has no drafts, only this flip). Attached by the REST door, not the protocol helper. Present ONLY when at least one app flipped — on a mid-loop failure it names the apps that DID persist, beside `unhideError` ('s split report). Spelling is a permanent wire contract (see the door's note). |
2528
2529
|**unhideError**|`string`| optional | Present when the ADR-0045 visibility flip failed (wholly or partway): the drafts ARE published, but apps still stored `_unpublished: true` stay externally unobservable. Client-facing text only — undeclared driver text is withheld per ADR-0112; the full cause is in the server log. The route is idempotent: re-run it once the cause is resolved. |
2529
2530
|**rebindError**|`string`| optional | Present when the post-publish `metadata:reloaded` announce failed: everything is published and stored, but boot-cached consumers keep the pre-publish view until re-run or restart (a newly published record-triggered flow does not bind its trigger). Client-facing text only, same ADR-0112 withhold as `unhideError`. |
@@ -2567,6 +2568,13 @@ Installed package with runtime lifecycle state
2567
2568
|**updated**|`integer`| ✅ | Data-plane rows updated across the batch. |
2568
2569
|**failures**|`{ type: string; name: string; error: string }[]`| ✅ | Each item whose data-plane projection did NOT land — named per-item, unlike the single-item door's scalar `error`, because one aggregate boolean over N items would hide WHICH ones never went live. |
Copy file name to clipboardExpand all lines: content/docs/references/ui/component.mdx
+30Lines changed: 30 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1685,6 +1685,7 @@ View filter rule
1685
1685
|**enforceFieldSecurity**|`boolean`| optional | Fold this block's field list through the caller's FIELD-read permissions before rendering, so a field the permission set denies leaves no empty row behind (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — with it off a denied field still arrives masked or stripped, and with it on the server still decides every value. |
1686
1686
|**redactFields**|`string[]`| optional | Field names this block never renders, whatever the permission answer (renderer default: render everything authored). Presentation only, evaluated in the browser after the record is fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set. Neighbours `hideFields`, which is the dedupe channel the renderer also writes to. |
1687
1687
|**requiredPermissions**|`string[]`| optional |[ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through `systemPermissions`, not object actions: `read` or `update` here is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not report `systemPermissions`) renders this block as if they were held — it fails open. |
1688
+
|**emptyState**|`{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string; link?: object }`| optional |[PLANNED — not yet rendered] What this block draws when its `dataSource` binding answers no row: `{ title, message, icon, link }`, the list view's own empty-state shape. Drawn then and only then — never on a block with no `dataSource` (it reads the record page's record instead), never while the bound read is loading, and never in place of the error a failed read shows. Omitted, a binding with no row keeps the block's own placeholder. |
|**headerColor**|`Enum<'muted' \| 'muted/50' \| 'accent' \| 'primary/10' \| 'secondary/10' \| 'destructive/10'>`| optional | Section-header background tint, from the closed six-token vocabulary rendered by objectui's `record:details` header (`muted`\|`muted/50`\|`accent`\|`primary/10`\|`secondary/10`\|`destructive/10`). A value outside the enum is refused at authoring time rather than silently not painting. Omit for an untinted header. |
1706
1707
|**count**|`{ field: string }`| optional | Show how many values a multi-value field holds on the current record in this section's heading: `{ field: 'tags' }`, with `label` writing the `{{ count }}` hole where the number goes (`'{{ count }} tags'`). The field must be a multi-value field the bound object declares — `multiselect`, `checkboxes`, `tags`, or a `select` / `lookup` / `user` / `file` / `image` field with `multiple: true` — and an absent or empty value counts 0. Every locale of an inline label map, and a translated `objects.<object>._sections.<name>.label`, writes the same `{{ count }}` hole. Not allowed beside `group`. The record detail page fills every hole in the heading after translating it. |
1707
1708
1709
+
### Nested Shape: `RecordDetailsProps.emptyState`
1710
+
1711
+
| Property | Type | Required | Description |
1712
+
| :--- | :--- | :--- | :--- |
1713
+
|**title**|`string \| Record<string, string>`| optional | Heading of the empty state |
1714
+
|**message**|`string \| Record<string, string>`| optional | Line of text below the heading |
1715
+
|**icon**|`string`| optional | Icon name drawn above the heading; a name that resolves to no icon draws the default empty-state glyph |
1716
+
|**link**|`{ label: string \| Record<string, string>; url: string }`| optional | The one next step an empty list names: a link the user follows, `{ label, url }` — for example to the how-to page that explains what belongs here when nobody can add a row. It opens a page or an address and runs no action. The console draws it in a list's empty state (the `grid` and `gallery` visualizations) and in an `object-grid` block's, beside the add-record action where one is offered. |
1717
+
1708
1718
### Nested Shape: `RecordDetailsProps.aria`
1709
1719
1710
1720
| Property | Type | Required | Description |
@@ -1757,6 +1767,7 @@ Type: `string`
1757
1767
|**enforceFieldSecurity**|`boolean`| optional | Fold this block's highlight chips through the caller's FIELD-read permissions before rendering, so a field the permission set denies leaves no empty chip behind (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — the record is fetched either way and the server still decides every value. |
1758
1768
|**redactFields**|`string[]`| optional | Field names this block never renders as a chip, whatever the permission answer (renderer default: render every field authored). Presentation only, evaluated in the browser after the record is fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set. |
1759
1769
|**requiredPermissions**|`string[]`| optional |[ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through `systemPermissions`, not object actions: `read` or `update` here is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not report `systemPermissions`) renders this block as if they were held — it fails open. |
1770
+
|**emptyState**|`{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string; link?: object }`| optional |[PLANNED — not yet rendered] What this block draws when its `dataSource` binding answers no row: `{ title, message, icon, link }`, the list view's own empty-state shape. Drawn then and only then — never on a block with no `dataSource` (it reads the record page's record instead), never while the bound read is loading, and never in place of the error a failed read shows. Omitted, a binding with no row keeps the block's own placeholder. |
|**type**|`string`| optional | Override cell renderer type (rare) |
1769
1780
|**readonly**|`boolean`| optional | Render this chip read-only — suppresses inline editing on the highlight card. Use for hook/automation-maintained columns that must not be hand-edited from the record header. |
|**title**|`string \| Record<string, string>`| optional | Heading of the empty state |
1787
+
|**message**|`string \| Record<string, string>`| optional | Line of text below the heading |
1788
+
|**icon**|`string`| optional | Icon name drawn above the heading; a name that resolves to no icon draws the default empty-state glyph |
1789
+
|**link**|`{ label: string \| Record<string, string>; url: string }`| optional | The one next step an empty list names: a link the user follows, `{ label, url }` — for example to the how-to page that explains what belongs here when nobody can add a row. It opens a page or an address and runs no action. The console draws it in a list's empty state (the `grid` and `gallery` visualizations) and in an `object-grid` block's, beside the add-record action where one is offered. |
1790
+
1771
1791
### Nested Shape: `RecordHighlightsProps.aria`
1772
1792
1773
1793
| Property | Type | Required | Description |
@@ -1903,6 +1923,16 @@ Sort field and direction pair
1903
1923
|**inline**|`boolean`| optional | Render in the flow instead of pulling up into the record-header band. The page header sets this itself when it hosts the bar in its own action slot. |
1904
1924
|**variant**|`Enum<'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'>`| optional | Button variant for every action — the Button primitive's own vocabulary; a per-action `variant` on the resolved def wins (renderer default: `default`). |
1905
1925
|**size**|`Enum<'default' \| 'sm' \| 'lg' \| 'icon'>`| optional | Button size for every action — the Button primitive's own vocabulary; a per-action `size` wins (renderer default: `sm`). |
1926
+
|**emptyState**|`{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string; link?: object }`| optional |[PLANNED — not yet rendered] What this block draws when its `dataSource` binding answers no row: `{ title, message, icon, link }`, the list view's own empty-state shape. Drawn then and only then — never on a block with no `dataSource` (it reads the record page's record instead), never while the bound read is loading, and never in place of the error a failed read shows. Omitted, a binding with no row keeps the block's own placeholder. |
|**title**|`string \| Record<string, string>`| optional | Heading of the empty state |
1933
+
|**message**|`string \| Record<string, string>`| optional | Line of text below the heading |
1934
+
|**icon**|`string`| optional | Icon name drawn above the heading; a name that resolves to no icon draws the default empty-state glyph |
1935
+
|**link**|`{ label: string \| Record<string, string>; url: string }`| optional | The one next step an empty list names: a link the user follows, `{ label, url }` — for example to the how-to page that explains what belongs here when nobody can add a row. It opens a page or an address and runs no action. The console draws it in a list's empty state (the `grid` and `gallery` visualizations) and in an `object-grid` block's, beside the add-record action where one is offered. |
0 commit comments