Skip to content

Commit 2c6ff0a

Browse files
committed
Merge origin/main into claude/project-thread-06v1io
Claude-Session: https://claude.ai/code/session_01L6bhgVeV5GgzaH9Ny16aSx Co-authored-by: Claude <noreply@anthropic.com>
2 parents a04a2c7 + f7188b5 commit 2c6ff0a

253 files changed

Lines changed: 8476 additions & 2178 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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/components': patch
3+
---
4+
5+
refactor(components)!: retire the unused `ViewSkeleton` dispatcher and `ShimmerSkeleton`
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/components#ViewSkeleton, @object-ui/components#ViewSkeletonVariant, @object-ui/components#ViewSkeletonDispatchProps, @object-ui/components#ShimmerSkeleton, @object-ui/components#ShimmerSkeletonProps) the retired operands are React components and their prop types in a published ui package's entry that nothing in the workspace imported; no packages/spec schema, no registered node type, no ADR-0087 conversion surface and no stored shape names them, so there is nothing for `objectstack migrate meta` to rewrite -->
11+
12+
**BREAKING** for a host that imports either component from `@object-ui/components`. Both were published from the package entry and had no importer anywhere in the workspace, so the Console and every `@object-ui/*` package are unaffected. The other skeletons in `view-skeleton.tsx` stay exported unchanged: `GridSkeleton`, `KanbanSkeleton`, `FormSkeleton`, `ListSkeleton`, `ChartSkeleton` and the `ViewSkeletonProps`, `GridSkeletonProps` and `KanbanSkeletonProps` types.
13+
14+
What leaves, and what to write instead:
15+
16+
- FROM `ViewSkeleton` TO the skeleton for the variant you passed: `variant: "grid"` is `GridSkeleton`, `"kanban"` is `KanbanSkeleton`, `"form"` is `FormSkeleton`, `"list"` is `ListSkeleton`, `"chart"` is `ChartSkeleton`, each taking the same remaining props.
17+
- FROM `ViewSkeletonVariant` TO a local union of the variants you render, or no type at all once you call the skeleton components directly.
18+
- FROM `ViewSkeletonDispatchProps` TO `GridSkeletonProps` or `KanbanSkeletonProps` (which carry `columns`; `KanbanSkeletonProps` carries `cardsPerColumn`), or `ViewSkeletonProps` for the others.
19+
- FROM `ShimmerSkeleton` TO `Skeleton` from `@object-ui/components`, or a `div` carrying the `animate-shimmer` utility that `@object-ui/app-shell/styles.css` still declares.
20+
- FROM `ShimmerSkeletonProps` TO `React.HTMLAttributes<HTMLDivElement>`, which it aliased.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-data, -automation and -platform teach ownership queues and assignment rules (ADR-0142 phase 2), one eval each
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The `queue` and `assignment_rule` metadata types, the `queues` / `assignmentRules` stack keys, the reserved `queue` column, the claim / release / move verbs, the `queue` sharing recipient and the `queue` approver type shipped in the spec and the docs, but no published skill taught them — `objectstack-platform` listed the two keys without a sentence, and no eval exercised any of it. An agent authoring an app from the skills could route an ownerless record only by hand-rolling it: a per-record picklist, a round-robin counter field, or a hook that picks an owner.
11+
- **Now.** `objectstack-data` gains *Ownership queues — park a record for a team to claim* (the injected `queue` column, a compiled `defineQueue` example, claim / release / move in one sentence each) and `rules/security.md` the `queue` sharing recipient and the `current_user.queues` placeholder; `objectstack-automation` gains *Assignment rules — route a new record to a queue or a person* (a compiled `defineAssignmentRule` example with `criteria`, `assignTo: { type: 'queue' }`, `strategy` and `load`, and the `queue` approver pointer); `objectstack-platform` says what `queues` and `assignmentRules` are and that both need `sharing` in `requires`. One eval per skill asserts the declared shape and refuses the hand-rolled alternatives.
12+
- Every file stays at or under its token ceiling: the new text is paid for by folding sentences the same skill's `rules/**` and `references/**` already hold, and by tightening existing eval model answers. No ceiling moves, no frontmatter changes, no schema, key or behaviour changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
objectui: the guides teach only what ships. `guides/project-setup.md`'s sample
9+
`package.json` no longer installs `@object-ui/plugin-ai` (retired; `18.2.0` was its
10+
last release), the AI row of `guides/page-builder.md`'s plugin catalog lists
11+
`plugin-chatbot` alone, and the README's package snapshot drops `plugin-ai` from the
12+
plugin list (18, not 19) and `vscode-extension` from the tooling line (the extension
13+
left the workspace). `evals/project-setup.json` case 1 (the full Vite + React setup)
14+
now fails an answer that installs the retired package.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/plugin-gantt': patch
3+
---
4+
5+
docs(plugin-gantt): the `object-gantt` registration no longer teaches the retired view-level `api` source
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The `data` input said `{ provider: 'api', read }` "reads through that request", and the `filter` input listed "an `api` request" among the record sources the gantt reads. The spec refuses that provider by name and the gantt has no reader for it: a gantt carrying it requests neither URL.
11+
- **Now.** `data` names the two providers the gantt reads, `{ provider: 'value', items }` and `{ provider: 'object', object }`, and says that rows a server computes reach the gantt as an object whose plugin driver computes them, never as a request URL in the view. `filter` names the object and inline rows.
12+
- **Unchanged.** Text only: no input, schema, renderer behaviour or export moves. The page element's `dataSource: { provider: 'api', read, path? }` binding is a different door and is untouched.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/skills': patch
4+
'@object-ui/plugin-form': minor
5+
'@object-ui/types': minor
6+
'@object-ui/i18n': patch
7+
'@object-ui/console': patch
8+
---
9+
10+
feat(spec,plugin-form)!: `ObjectForm` lays a form out and no longer picks its container; the `object-form` block drops `formType: 'split'` and `SplitForm` retires, while the block keeps opening `drawer` and `modal` forms itself
11+
12+
Clause-②: no (narrowing)
13+
14+
Metadata-protocol: narrowed (the `object-form` block's `formType` refuses `split`; its `splitDirection`, `splitSize`, `splitResizable` and `sections[].pane`, the `object-master-detail-form` block's `sections[].pane`, and `FormViewSchema.splitDirection`, are tombstones)
15+
16+
<!-- adr-0087: not-required (already-registered form-view-presentation-types-removed, ui-form-view-type-layout-only) 18.3.0 is unreleased, so this change widens those two entries in place: the conversion now strips splitDirection and reaches object-form page blocks, four new RETIRED_KEYS_BY_MAJOR rows record the tombstoned keys, and the D3 entry names the block -->
17+
18+
**BREAKING** accept-set narrowing on the `object-form` page block and the form view (`@objectstack/spec`), and a removed export (`@object-ui/plugin-form`). ADR-0050, the renderer half of #27155.
19+
20+
The model: a form's `formType` says how it lays out its sections. Where it opens is the caller's. On the `object-form` block the caller is the block itself, so `drawer` and `modal` stay there as the two containers the block opens; `split` drew two panes and had no other reader, so it leaves with its renderer.
21+
22+
- **FROM → TO (block).** `formType: 'split'` → `formType: 'tabbed'` (one tab per section) or delete the value for `simple`; delete each section's `pane` and the block's `splitDirection` / `splitSize` / `splitResizable`. To edit child records beside the parent, use an `object-master-detail-form` block. `formType: 'drawer'` and `formType: 'modal'` are unchanged. The `object-master-detail-form` block's parent `sections` share the shape, so delete each section's `pane` there too.
23+
- **FROM → TO (form view).** `splitDirection` on a form view → delete it. It joins `splitSize` and `splitResizable`, which left in the same release.
24+
- **The one-line fix.** Delete the retired value and keys. `os migrate meta --from 17` lists the edits for existing sources, and `--write` applies them. A stored page or view row is converted when it is read (the `form-view-presentation-types-removed` conversion now reaches `object-form` page blocks, the section `pane` of `object-master-detail-form` blocks, and `splitDirection` too), so a block saved with `split` keeps loading as a `simple` form.
25+
- **What the parse says.** `formType: 'split'` is refused by name with its prescription ("'split' was removed from the `object-form` `formType` enum in @objectstack/spec 18.3.0 (ADR-0050) — …"); each tombstoned key is refused on every `formType`. TypeScript rejects each at the authoring site.
26+
- **`@object-ui/plugin-form`.** `ObjectForm` renders the `simple`, `tabbed` and `wizard` layouts only; a `formType` of `drawer` or `modal` on a node handed straight to `<ObjectForm>` now renders the inline form, and the host opens its own drawer or dialog around it (the console's New and Edit dialogs already mount `ModalForm` themselves). The registered `object-form` block routes `drawer` and `modal` through the new internal `ObjectFormContainer`, which hands the node to `DrawerForm` or `ModalForm` as before. `SplitForm`, `SplitFormProps` and `SplitFormSchema` are no longer exported, and the block no longer publishes the `splitDirection`, `splitSize` and `splitResizable` inputs. FROM `import { SplitForm } from '@object-ui/plugin-form'` → TO a `TabbedForm`, or the `form` renderer's `fieldPanes` for a host that composes panes itself.
27+
- **`@object-ui/types`.** `ObjectFormSchema.formType` is `'simple' | 'tabbed' | 'wizard' | 'drawer' | 'modal'`, and its `splitDirection` / `splitSize` / `splitResizable` are the spec's tombstones by reference; the section `pane` member is gone.
28+
- **Console.** Studio's page-block inspector no longer offers `Split` for an `object-form` (its `engine.inspector.pageBlock.option.formType.split` string is removed from `@object-ui/i18n`), the record page passes no split keys, and a view whose `type` is not one of the three form layouts is no longer taken for a form (`@object-ui/console`).
29+
- **Supersedes one line of the form view `type` entry (#27155).** That entry says `splitDirection` stays declared; in this release it leaves too.
30+
- **Unchanged.** `drawerSide`, `drawerWidth`, `modalSize` and `modalCloseButton` keep their readers: the block's drawer and modal containers. The enforce-or-remove question for `drawerSide` / `drawerWidth` on form views stays on #27153.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/i18n': minor
4+
'@objectstack/platform-objects': minor
5+
---
6+
7+
feat(spec): `currencyMode: 'record'` + `currencyField` — a record-mode amount points at a sibling ISO 4217 code field (ADR-0144 D2)
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
A currency amount (or a `formula` with `returnType: 'currency'`) can now carry a currency per record. `currencyConfig.currencyMode` gains a third value, `record`, and `currencyConfig.currencyField` names the field on the same object that holds each record's ISO 4217 code. The amount itself stays a bare number (ADR-0104 D1).
13+
14+
```ts
15+
fields: {
16+
currency_code: Field.text({ label: 'Currency', valueDomain: 'iso_4217_currency' }),
17+
amount: Field.currency({ label: 'Amount', currencyConfig: { currencyMode: 'record', currencyField: 'currency_code' } }),
18+
}
19+
```
20+
21+
- `currencyField` is required under `record`, refused under `dynamic` and `fixed`, and never empty; `defaultCurrency` is refused under `record`.
22+
- The object refuses a `currencyField` that names no other declared field, a field that is not `text`, or a `text` field without `valueDomain: 'iso_4217_currency'`. Each refusal is located at `fields.FIELD.currencyConfig.currencyField` and lists the object's candidate code fields.
23+
- `dynamic` and `fixed` configs parse exactly as before; an app that never writes `record` sees no change.
24+
25+
⛔ Not released alone: this ships in the same release as the record-mode aggregate refusals and the per-row display.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
'@object-ui/components': patch
4+
'@object-ui/app-shell': patch
5+
'@object-ui/i18n': patch
6+
---
7+
8+
fix(plugin-detail,app-shell,components): the record page's related switch decides whether the column exists, its panel switches are one segmented control in the header's action row, a 1440 px window with the sidebar is three columns, and the related order and body-table placement read the object's `relatedLists` and field groups
9+
10+
Clause-②: no
11+
Metadata-protocol: none
12+
13+
The synthesized default record page (an object with no assigned page). Nothing an author writes changes meaning: the two read points below are keys the spec already declares.
14+
15+
- **The related switch.** With the related column switched off there is no related column and no 「Related」 rail toggle, in every column tier. With it on and no room for it (the two-column tier), the rail toggle opens and closes it. Before, the switch did nothing in the two-column tier and the rail still opened the column.
16+
- **One segmented control in the header row.** The two panel switches are now one control with an icon and a label per segment (Timeline, Related column). It is drawn at the right end of the record header's action row, after `⋯`, behind a vertical hairline, in both column tiers. A segment that is on is filled with the accent tint; one that is off is muted text, so the state reads in the dark theme too. The crumb row keeps only the ways back and is not drawn when there is none. The shortcuts are unchanged (`⌘⇧.` / `⌘⇧,`); they now also match a key event that carries the typed character (`.` / `>`, `,` / `<`) but no physical key code.
17+
- **Thresholds.** Three columns from a 1120 px container, two from 760 px, the tabs below (were 1280 / 1024). Measured on the console: the expanded sidebar is 256 px, so a 1440 px window is 1184 px of record page (three columns), and a 1024 px window is 768 px (two columns). `RECORD_PAGE_THREE_COLUMN_MIN_WIDTH` and `RECORD_PAGE_TWO_COLUMN_MIN_WIDTH` carry the new values and their derivation.
18+
- **Related order.** The default order of the related cards, the Related tab and the body tables is the object's `relatedLists` first, then the derived order. That order already reached the page through `deriveRelatedLists`; the stale comment that said it did not is gone, and a mounted pin now holds it.
19+
- **Body-table placement.** A field group whose `relatedList` names a body table's relationship (a child object name, or `object.field`) places that table at the group's position among the field groups. Other body tables stay after the first field section.
20+
- **Restore default order.** Once a reader has moved or hidden a related card, the foot of the related column (and of the Related tab) offers 「Restore default order」 on the same line as 「N hidden · Show」. It lays the default order back, shows every card, and records the current default as the one the arrangement is based on. The reader's stored arrangement keeps its shape. The hint shown when the author's default changed stays as it was.
21+
- **`@object-ui/components`.** `PanelToggles` is the segmented control. `PageHeaderTrailingContext` (new export) is the host slot at the end of `page:header`'s action row, and `PageHeaderActions` takes a `trailing` node for it. No metadata key reaches it.
22+
- **`@object-ui/plugin-detail`.** `relatedListEntryNames` (new export) is the one reading of a related-list entry; app-shell's related-list order and the Studio editors import it from here.
23+
- **`@object-ui/i18n`.** New `detail.relatedRestoreDefaultOrder` in the ten packs.
24+
25+
What to do: nothing.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'@objectstack/objectql': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
fix(objectql)!: a plugin driver on an external datasource answers every read correctly or refuses: the engine applies the whole query to the rows the driver returns, up to 1000 of them (#27277)
7+
8+
Clause-②: yes (two codes join ERROR_CODE_LEDGER under `@objectstack/objectql`: `EXTERNAL_WRITE_REFUSED` and `EXTERNAL_ROW_CAP_EXCEEDED`, both 422)
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped. A read on a plugin-driver datasource that matches more than 1000 rows is now refused with a 422 that names its own remedy (narrow the filter, or mirror the data with DataSync); there is no metadata for a migration to rewrite. -->
12+
13+
**BREAKING** accept-set narrowing for reads on a plugin-driver external datasource (`@objectstack/objectql`). Only a datasource with both of these is affected: `schemaMode` is not `managed`, and `driver` names a driver a server plugin published (not a built-in id: memory, sqlite, sqlite-wasm, postgres, mysql, mongodb, turso). A managed datasource and an ADR-0015 federated database on a built-in driver are unchanged.
14+
15+
- **Before.** The engine passed the whole query to the plugin driver and served whatever came back. A driver that ignored part of the filter, the sort or the paging answered the wrong rows, and nothing said so.
16+
- **Now.** The engine holds such a driver behind a correctness floor. The driver is sent `where` and `orderBy`, which it may push down, and `limit: 1001`. It is never sent `offset` or `fields`. The engine then applies the whole query to the rows it gets back (`applyDriverQueryInMemory`): filter, sort (nulls last, and an `id` tie-break on a paged read), offset and limit, and projection. A driver that returned every row still answers the right subset.
17+
- `findOne` and `count` are answered from the same read.
18+
- `aggregate` is hidden, so the engine aggregates in memory over that read.
19+
- The filter is evaluated by the engine's own walker (the one `having` uses). `$like` and `$ilike` now work in its `where` position. It is held to the spec tables `FILTER_LOGIC_CASES`, `FILTER_TEXT_CASES`, `PAGINATION_*_CASES` and `TEMPORAL_CASES`.
20+
- **Row cap.** A read that matches more than 1000 rows is refused with `EXTERNAL_ROW_CAP_EXCEEDED` (422), whose message says to narrow the filter or mirror the data into a real object with DataSync.
21+
- **Writes pass through, by id.** The floored driver does not offer `updateMany` or `deleteMany`, so a by-id write under a precondition goes through `update`, and a multi-row predicate update or delete is refused.
22+
- A remote refusal for the request's own sake (400, 404, 409, 412 or 422) is `EXTERNAL_WRITE_REFUSED` (422). Its message is the remote's own, and `details.remoteStatus` carries the status.
23+
- Any other failure is `ERR_DATASOURCE_UNAVAILABLE` (503, `reason: 'remote'`): no answer, a 5xx, or a 401/403/407/429 credential or rate-limit condition. The cause goes to the server log at `warn` and is never sent to the caller.
24+
- **One instance.** The engine stores the floored driver in place of the driver, so `getDriverByName`, `datasource()` and the routing for an object all return the same instance. `unwrapExternalDriverFloor` answers the driver behind it.
25+
- **Re-registration.** A datasource definition registered again without `driver` keeps the driver id stored before it, so the floor stays on.
26+
- **Not covered.** A `where` with a `ReadableIds` leaf is refused (ADR-0139 D3), because the engine has no semi-join over a plugin driver's rows.
27+
28+
**The remedy.** If a read now meets the cap, narrow its filter, or mirror the data with a `mapping` pulled by a `job` and read the real object.
29+
30+
New exports from `@objectstack/objectql`: `applyDriverQueryInMemory`, `ExternalWriteRefusedError`, `ExternalRowCapExceededError`, their `*_CODE` constants, `EXTERNAL_ROW_CAP`, `EXTERNAL_WRITE_REFUSED_STATUSES`, `needsExternalDriverFloor`, `unwrapExternalDriverFloor`, and the types `DriverQueryInMemory`, `DriverQueryInMemoryOptions`, `ExternalWriteOperation` and `DatasourceUnavailableReason`. `DatasourceDef` gains an optional `driver`.

0 commit comments

Comments
 (0)