Skip to content

Commit c54118f

Browse files
authored
Merge branch 'main' into claude/issue-11543-skill-fences-v1
2 parents 2f22053 + d0097af commit c54118f

124 files changed

Lines changed: 7198 additions & 779 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
A stored list view now renders the same on an interface page and on the object page when it carries a legacy `options` bag (objectui#10380).
6+
7+
`@objectstack/spec`'s flattened list view overlay accepts a legacy `options` bag (`options.kanban`, `options.timeline`, `options.map`, …). The view write door judges each block in it key by key with that kind's own schema, and stores what it accepts. The interface page passed the bag to the list renderer and the object page dropped it, so the same saved view could show a timeline grouped by one field on one page and ungrouped on the other, or a map with markers on one page and without them on the other.
8+
9+
- **Object page:** passes the stored bag to the list renderer as well. For each kind the bag carries, the view's own top-level block (`kanban`, `timeline`, …) still wins key by key, which is the precedence the spec declares for the bag.
10+
- **Interface page:** a default binding that the page derives from the object (a kanban lane, a calendar or timeline date, a gallery cover, gantt dates) no longer fills a kind that the bag already carries. Before this change, that derived guess could outrank the view's own declaration. The derived `map` binding already followed this rule.
11+
12+
A stored row whose bag still holds a key the door now refuses is shown as stored, the same way on both pages, until its next save. That save is refused, and the refusal names the key.

‎.changeset/10790-empty-operators-accepted-shape.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ value OR the empty string, and its complement:
2121
Criteria saved in the old shape still open in the builder as the same row, and
2222
opening one rewrites nothing; the builder writes the new shape the next time any
2323
row of that criteria is edited.
24+
25+
**Correction, 2026-10-03 (objectui#10813).** The two shapes above are not what this release
26+
writes. Later in the same release the pair moved to the spec's one 「is empty」 operator:
27+
"Is empty" stores `{ FIELD: { $empty: true } }` and "Is not empty" `{ FIELD: { $empty: false } }`,
28+
whose meaning is the field's declared row of the spec's per-type table rather than "no value OR
29+
the empty string" on every type. Criteria saved in either earlier shape still open as the same
30+
row and are written as `$empty` on their next edit (`.changeset/10813-filter-condition-empty-operator.md`).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': minor
3+
---
4+
5+
The Studio dataset filter builder writes "Is empty" / "Is not empty" as the spec's `{ FIELD: { $empty: true } }` / `{ FIELD: { $empty: false } }` instead of `$exists` (objectui#10813).
6+
7+
`dataset.filter` and `measure.filter` stored the pair as `{ FIELD: { $exists: false } }` / `{ FIELD: { $exists: true } }`. `$exists` is the spec's has-a-value test (`!= null`), so a text value of `''` or a multi-value `[]` was never "empty" there, while the same operator in the sharing-rule widget and in a saved view meant something else. `@objectstack/spec` 17.6.0 admits `$empty` (objectstack#20446), whose meaning is the column's DECLARED row of the spec's per-type table (ruling B on objectstack#20311): null or `''` on a text-like column, null or `[]` on a multi-value one, null alone on every other type. The analytics service answers it from the field's declared type and `multiple` on its SQL and ObjectQL strategies. The spec's own face table declares one face that refuses it: the `driver-memory` analytics (cube) face, the lowest-priority fallback strategy (`MemoryAnalyticsService`), refuses a newly authored "Is empty" / "Is not empty" filter with `INVALID_FILTER` / 400, where it answered `$exists`. The bridge keeps no copy of the table.
8+
9+
The `@objectstack/spec` dependency floor rises from `^17.5.0` to `^17.6.0`, because this package now writes `$empty`, which `@objectstack/spec` 17.5.0 declares staged and refuses.
10+
11+
The read half reads `$empty` back as the pair, with a boolean flag only.
12+
13+
**What moves for a stored filter.** A stored `$exists` no longer opens as "Is empty" / "Is not empty": it opens in the Source tab, with its bytes untouched, and keeps matching what it matched before. Opening it as the pair would let an edit to ANOTHER row rewrite it to `$empty` and move `''` / `[]` across the line, which the read-half invariant (objectui#10257) forbids, and this inspector offers no `exists` row. To move such a filter to the new meaning, remove the row and add "Is empty" again. Readable stores (this repository, objectstack, hotcrm and cloud) hold no `dataset.filter` with `$exists`.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/fields': minor
3+
---
4+
5+
`FilterConditionField` writes "Is empty" / "Is not empty" as the spec's one 「is empty」 operator, `{ FIELD: { $empty: true } }` / `{ FIELD: { $empty: false } }` (objectui#10813).
6+
7+
The widget behind sharing-rule `criteria_json`, `relatedListFilter` and `summaryOperations.filter` used to write its own meaning of 「is empty」: `{ $or: [{ FIELD: { $in: [''] } }, { FIELD: { $null: true } }] }` and its complement `{ FIELD: { $nin: [''], $null: false } }`, i.e. "no value OR `''`" on every field type. The `''` member reached a number or date column as `IN ('')`, and a multi-value column (stored as JSON by the SQL driver) as an `$in` that driver refuses. `@objectstack/spec` 17.6.0 admits `$empty` to `FILTER_OPERATORS` (objectstack#20446), and its meaning is the field's DECLARED row of the spec's per-type table (ruling B on objectstack#20311): null or `''` on a text-like field, null or `[]` on a multi-value field, null alone on every other type. Every evaluator expands it itself (`expandEmptyOperator`), so the widget now writes the same token on every column type and keeps no copy of the table.
8+
9+
The `@objectstack/spec` dependency floor rises from `^17.5.0` to `^17.6.0`, because this package now writes `$empty`, which `@objectstack/spec` 17.5.0 declares staged and refuses.
10+
11+
`kvToCondition` reads `$empty` back as the pair, with a boolean flag only: any other flag stays the raw criteria it is, as every evaluator refuses it.
12+
13+
**What moves for a stored rule.** Reading alone rewrites nothing: a criteria saved in either earlier shape (the objectui#10790 `$or` entry and `$nin` + `$null` pair, or the older `$in: [null, '']` / `$nin: [null, '']`) still opens as the same "Is empty" / "Is not empty" row, and an un-edited rule keeps its stored bytes and keeps matching as before. The next time the criteria is edited, those rows are written as `$empty`, which re-scopes the rule:
14+
15+
- text-like column: no change (both mean null or `''`);
16+
- every other single-valued column (number, boolean, date, datetime, time, select, a single lookup): a row holding `''` is no longer "empty" and becomes "not empty". Since objectstack#20308 the write door stores a cleared number, boolean, date, datetime or time as null, so on those types only a value written before it can hold `''`; a select or lookup can still hold one, and the spec's ruled table does not count it as empty;
17+
- multi-value column (multiselect, checkboxes, tags, or a `multiple: true` select, lookup or user): a row holding `[]` becomes "empty" and leaves "is not empty". On the SQL driver the earlier shape was refused outright, so a rule there starts running;
18+
- the `$in: [null, '']` shapes were refused by every objectstack filter face, so a rule still in them starts running.
19+
20+
Measured on the installed 17.6.0 by-value readers (`@objectstack/formula`'s `matchesFilterCondition` and `ValueDataSource`) over null, an absent key, `''`, `[]` and set values: the `$or` entry and `$empty: true` disagree only on `[]`. Readable stores (this repository, objectstack, hotcrm and cloud) hold no criteria in either earlier shape.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@object-ui/plugin-list': minor
3+
---
4+
5+
The list view's live query sends "Is empty" / "Is not empty" as the spec's `isempty` / `isnotempty` instead of an equality to `null` (objectui#10813). `@objectstack/spec` 17.6.0 and later lower that pair to `$empty`; an earlier reader lowers it to `$null`, as it does the saved view's `is_empty`.
6+
7+
`convertFilterGroupToAST` resolved the pair to `[FIELD, '=', null]` / `[FIELD, '!=', null]`, a null-only test, before `mapOperator` was consulted. The same rule saved into the view is persisted as `is_empty`, which `@objectstack/spec` 17.6.0 lowers to `$empty` (objectstack#20446): so one filter panel answered two record sets, depending on whether the view had been saved. The pair now takes the value-less path like `is_null`, and `mapOperator` gains `isempty` / `isnotempty` arms. The node is `[FIELD, 'isempty', null]`; the spec discards the third slot.
8+
9+
**What moves.** Nothing is stored by this path. Against a 17.6.0 or later server, on a text-like column a row holding `''` is now "empty", and on a multi-value column a row holding `[]` is, matching the server's per-type answer for the saved view. On a `provider: 'value'` list a row with no key at all is now "empty" too: the equality to `null` did not select it there.

‎.changeset/10872-held-public-block-arms.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@
2424
- `record:line_items` is still refused at `type`: the spec has no row for it yet.
2525

2626
⚠️ **Dated note, 2026-10-01 — props written flat on the node — objectui#10872 batch 10.** Later in this same release every member of a public block's row written directly on the node is refused by name on both faces, at its own path, with a message naming `properties.KEY`. So the bullet that says the tolerant face passes such a key unjudged, and that the flat `{ "type": "action:button", "label": "Open details", "actionType": "url", "target": "/users/ada" }` "validates through `objectui validate`", no longer describes the release as a whole: `objectui validate` refuses its `actionType` and `target` by name, as the strict face and `@objectstack/spec`'s page component do, and keeps the node-level `label`, which the spec's page component also declares. The bag spelling still passes both faces. The rest of this entry is kept as the reading of this change.
27+
28+
⚠️ **Dated note, 2026-10-03 — `record:line_items` armed — objectui#10872.** Later in this same release `@objectstack/spec` 17.6.0 carries a `ComponentPropsMap` row for `record:line_items`, and the block is armed from that row (`.changeset/10872-record-line-items-arm.md`). So the bullet "`record:line_items` is still refused at `type`: the spec has no row for it yet" no longer describes the release as a whole. The rest of this entry is kept as the reading of this change.

‎.changeset/10872-public-block-zod-arms.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@
2525
⚠️ **Dated note, 2026-09-30 — the four `page:` containers' child list — objectui#10872 batch 6.** Later in this same release the arms of `page:card`, `page:section`, `page:footer` and `page:sidebar` refuse a node-level `children` and restate `body`, both with a message that names `properties.children`, the member their spec row declares (`.changeset/10872-container-children-channel.md`). So the bullet on a prop written directly on the node, where it says `children` (`page:card`, `page:section`, `page:footer`, `page:sidebar`) is judged against the base's own type and `body` (`page:card`) is refused by the base's objectui#6771 retirement, no longer describes the release as a whole: on those four, both keys are refused by the arm's own refusal. The rest of this entry is kept as the reading of this change.
2626

2727
⚠️ **Dated note, 2026-10-01 — props written flat on the node — objectui#10872 batch 10.** Later in this same release every member of a public block's row written directly on the node is refused by name on both faces, at its own path, with a message naming `properties.KEY`. So the bullet on a prop written directly on the node, where it says such a key "is not judged against the block's row", that `{ "type": "record:details", "columns": "2" }` "passes the tolerant face", and that `disabled` (`element:button`) and `visible` (`record:alert`) are judged against the base's own types, no longer describes the release as a whole: all of those are refused, on both faces, and the message names the bag member. `label` (`element:button`) is the exception, because `@objectstack/spec`'s page component declares a node-level `label` of its own. The rest of this entry is kept as the reading of this change.
28+
29+
⚠️ **Dated note, 2026-10-03 — `record:line_items` armed — objectui#10872.** Later in this same release `@objectstack/spec` 17.6.0 carries a `ComponentPropsMap` row for `record:line_items`, and the block is armed from that row (`.changeset/10872-record-line-items-arm.md`). So the "Not armed in this release" bullet's `record:line_items` item ("the spec has no row for it yet"), and the 2026-09-30 note's "only `record:line_items` is still refused at `type`", no longer describe the release as a whole: none of the held public blocks is still refused at `type`. The rest of this entry is kept as the reading of this change.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
`safeValidateSchema`, and so `objectui validate`, accepts `record:line_items`, the last ADR-0080 public block it refused at `type` (objectui#10872). `@objectstack/spec` 17.6.0 carries the block's `ComponentPropsMap` row, measured at the reads of `LineItemsPanel`, and the arm reads that row by reference.
6+
7+
**Clause-②: yes.** The accept set of `AnyComponentSchema` widens by one `type` literal, and `@object-ui/types/zod` exports one new schema, `RecordLineItemsBlockSchema`. Nothing that parsed before is refused now: every document naming `record:line_items` was refused before.
8+
9+
**What it was.** `record:line_items` is registered by `@object-ui/plugin-form` and curated as platform contract by ADR-0080, and `AnyComponentSchema` carried no arm for it. Every document naming it was refused with `invalid_union` at `type`, whatever the rest of the node said. The arm was held until the spec carried a row for the block.
10+
11+
**What changed, in observable terms.**
12+
13+
- `@object-ui/types/zod` exports `RecordLineItemsBlockSchema`, a member of `PublicBlockComponentSchema`. The strict authoring face (`StrictAnyComponentSchema`) accepts the same documents and is closed to undeclared keys, as on every other arm.
14+
- The block's props live in `properties`, and the bag is the spec row: its members, value types and strictness are the spec's. `relationshipField` and `columns` are required there, and `childObject` is optional. A key the row does not declare is refused inside the bag, by name.
15+
- A row member written on the node instead of in `properties` is refused by name on both faces, at its own path, with a message naming `properties.KEY`, as on every other public block.
16+
- The node's `dataSource` binding is declared as the spec's `ElementDataSourceSchema`, read by reference. The block's registration wraps `ElementDataSourceGate`, which puts `dataSource.object` into `childObject`, so a bound node without `childObject` passes both faces. A binding that the spec refuses is refused at `dataSource`.
17+
- A node-level `children` or `body` is refused by name on both faces. The block renders the grid of child records and reads neither key, and `@objectstack/spec`'s page component refuses a node-level `children` on it too.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
`ObjectKanbanSchema.grouping` is declared on both faces, as `@objectstack/spec`'s `GroupingConfig`, by reference (objectui#11216).
6+
7+
The spec's `object-kanban` row types `grouping` as the list view's own `GroupingConfigSchema`. `ObjectKanban` reads `grouping.fields[0].field` as the fallback for `swimlaneField`. Until this change the key was undeclared on the `object-kanban` arm, so this package and the spec gave three different answers for one document:
8+
9+
| `grouping` value | spec row | strict authoring face (before → now) | tolerant face (before → now) |
10+
|---|---|---|---|
11+
| `{ fields: [{ field: 'owner' }] }` | accepted | refused by name → **accepted** | kept → accepted, kept as authored |
12+
| a padded field name, `{ fields: [{ field: ' x ' }] }` | refused | refused by name → refused at `fields.0.field` | kept → **refused** |
13+
| a bare string, `'owner'` | refused | refused by name → refused at `grouping` | kept → **refused** |
14+
| an empty list, `{ fields: [] }` | refused | refused by name → refused at `fields` | kept → **refused** |
15+
| an undeclared key on the block or on an entry | refused | refused by name → refused by name inside the block | kept → **refused** |
16+
17+
- **Zod mirror.** `grouping: stripImportedDefaults(GroupingConfigSchema).optional()`, which is how `ObjectGridSchema.grouping` spells it. The spec's `order` and `collapsed` defaults are not added to a parsed document.
18+
- **TypeScript.** `grouping?: GroupingConfig`, the spec's authored type. It is the same type as the `object-kanban` row's `grouping`.
19+
20+
**Not changed: the board.** `ObjectKanban` reads `fields[0].field` and nothing else in the block. The registration's input description says so, and it is unchanged.
21+
22+
**Migration.** Write `grouping` as `{ fields: [{ field }] }` with an unpadded field name, or delete it and author `swimlaneField`, which the board reads first.
23+
24+
**minor, not patch.** The member is new in the shipped `.d.ts` and in the zod mirror's `.shape`. The tolerant face now refuses the shapes in the table that it used to keep unjudged.

‎.changeset/11355-small-p1-sites.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,12 @@ Small reader sites stop riding `BaseSchema`'s index signature (objectui#11355, p
1717
- `@object-ui/plugin-map`: the dev warning about flat map keys that a `map` block shadows reads those keys through their own type, not through a `Record` conversion.
1818

1919
**minor, not patch, for `@object-ui/types`.** Both members are new in the shipped `.d.ts` and in the zod mirror's `.shape`, and the mirror now refuses a wrongly typed value for each, where it used to keep one unjudged.
20+
21+
⚠️ **Dated note, 2026-10-03 — `ObjectKanbanSchema.grouping` is now declared — objectui#11216.**
22+
At this change `grouping`, the fallback `ObjectKanban` reads for `swimlaneField`,
23+
was undeclared on both faces, as the first bullet above says. objectui#11216
24+
declares it on both faces in this release, by reference: the TypeScript member is
25+
the spec's `GroupingConfig`, and the zod mirror takes the spec's
26+
`GroupingConfigSchema`, the type the `object-kanban` row gives the key. So the
27+
sentence above that calls `grouping` "still undeclared" does not hold in this
28+
release. The rest of this entry is kept as the reading of this change.

0 commit comments

Comments
 (0)