Skip to content

Commit d710e84

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-eq5jj6
# Conflicts: # ui/content/docs/guide/architecture.md # ui/content/docs/utilities/index.md # ui/content/docs/utilities/meta.json # ui/packages/vscode-extension/README.md # ui/packages/vscode-extension/package.json # ui/packages/vscode-extension/src/__tests__/export-to-react-compiles.test.ts # ui/packages/vscode-extension/src/providers/HoverProvider.ts # ui/scripts/__tests__/check-doc-component-types.test.ts # ui/scripts/__tests__/check-doc-links.test.ts # ui/scripts/markdown-test-inputs.mjs
2 parents 9b423d3 + 6abcb72 commit d710e84

240 files changed

Lines changed: 6085 additions & 10225 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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/types': minor
4+
'@object-ui/plugin-list': minor
5+
'@object-ui/app-shell': minor
6+
'@object-ui/plugin-view': patch
7+
'@object-ui/plugin-map': patch
8+
'@object-ui/console': patch
9+
'@objectstack/rest': patch
10+
'@objectstack/metadata-protocol': patch
11+
'@objectstack/lint': patch
12+
---
13+
14+
feat(spec)!: retire the list view's `options` bag; stored bags fold onto the top-level blocks, and the renderers read the blocks alone (#25923)
15+
16+
Clause-②: no (narrowing)
17+
Metadata-protocol: narrowed
18+
19+
<!-- adr-0087: registered list-view-options-bag-folded, ui-list-view-options-bag-folded -->
20+
21+
A flattened list view overlay carried every per-kind block twice: as the top-level `kanban`, `calendar`, `gallery`, `timeline`, `gantt`, `map`, `tree` and `chart`, and again under a legacy `options` bag the top-level block won over per key. Each renderer re-implemented that merge. The bag is retired, and each block has one spelling.
22+
23+
- **`@objectstack/spec`**: the list overlay's `options` is a removed-key tombstone. The view write door refuses a body that carries it, by name, with the prescription to move each `options.KIND` block to `KIND`. The ADR-0087 D2 conversion `list-view-options-bag-folded` folds a stored bag onto the top-level blocks before a row is served (the top-level value wins per key; for `chart`, whose block replaced the bag whole, a bag beside a block is dropped), and `os migrate meta --from 17` lists the same edits for a source. A bag it cannot fold (an `options.grid`, a non-object block, a bag on a form view) is left in place with a TODO. The D3 entry `ui-list-view-options-bag-folded` names what the fold cannot decide: the bag was judged with every key optional, so a folded block can still miss a key its block requires. The page `list-view` component row shares the tombstone.
24+
- **`@object-ui/types`**: `ListViewSchema.options` takes the spec's tombstone by reference, so every published door refuses a bag at `options` with the spec's text, and the TypeScript face has no bag. `NamedListView.options` is a `?: never` tombstone that names the top-level blocks. The dataset-chart filter refusal reads the `chart` block alone, and the page node's flat `options` is refused with the bag's own text rather than pointed at `properties.options`.
25+
- **`@object-ui/plugin-list`**: `ListView` reads each kind from its top-level block alone: the render branches, the capability gate, the projection and expand collectors and the fetch effect's dependencies no longer read `options`. The `list-view` registration publishes no `options` input.
26+
- **`@object-ui/app-shell`**: the object page relays each per-kind block at the top level of the list schema, held by value per block so an equal rebuild keeps its identity, and no longer forwards a stored bag. The interface page sends one `map` block (its derivation with the authored keys laid over) and no bag. The Studio view preview and the list-view inspector no longer fold a bag; the server does. `storedLegacyOptions` is removed.
27+
- **`@object-ui/plugin-view`**: the host list schema carries the active view's blocks at the top level instead of under `options`.
28+
- **`@object-ui/plugin-map`**: two development warnings name the view's `map` block instead of `options.map`.
29+
- **`@object-ui/console`, `@objectstack/rest`, `@objectstack/metadata-protocol`, `@objectstack/lint`**: test pins only (the registry parity row, the write door's refusal and served fold, and the lint finding), no runtime change.
30+
31+
**BREAKING** for a list view body, a page `list-view` node or a `ListView` host that writes `options`.
32+
33+
- **Before.** `{ type: 'kanban', columns: ['name'], options: { kanban: { groupByField: 'stage' } } }` was accepted at the view write door and drew a board laned on `stage`, and `ListView` read the bag beside the top-level blocks.
34+
- **Now.** The write door and every `@object-ui/types` door refuse `options` by name, and `ListView` reads no bag. A view already stored with one is served folded onto its top-level blocks and renders from them. On the object page those blocks now pass through the same relay defaults as a block written top-level (the kanban `titleField: 'name'` floor, for one), so a folded row can draw differently from its bag where the bag left such a key unset. No reachable source stores a bag (census: 0 rows), and `ObjectView.storedRowParity-10380` pins the folded rendering.
35+
- **Fix.** Move each block to the top level: `{ type: 'kanban', columns: ['name'], kanban: { groupByField: 'stage', columns: ['name'] } }`, keeping the top-level value where both set a key, and add any key the block requires that the bag never had (a kanban's `columns`, a gantt's `titleField`, a chart's `dataset` and `values`). A grid's settings are top-level keys of the view. Run `os migrate meta --from 17` for a source, with `--write` to apply the edits it can prove.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/driver-memory': patch
3+
---
4+
5+
fix(driver-memory)!: retire the legacy `default` export, which a CommonJS `require()` could not reach as its types described
6+
7+
The package entry exported a legacy `{ id, version, onEnable }` object as its `default`. It is not a kernel plugin (the kernel contract is `init` / `start` / `destroy`), and the CommonJS build could only put it on `require('@objectstack/driver-memory').default`, while the published declarations said `export default` (`@arethetypeswrong/cli` reported `FalseExportDefault`). It leaves the published entry. Every named export stays. Nothing in this repository imported the default, and no example, scaffold template, doc page or skill names the package as a string entry of a stack's `plugins` array.
8+
9+
Clause-②: no (narrowing)
10+
Metadata-protocol: none
11+
12+
<!-- adr-0087: not-required (runtime-export-retired @objectstack/driver-memory#default) One runtime object leaves a published package entry; no key of any metadata schema, no object definition and no stored shape is added, removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. A TypeScript consumer meets the break at its own import, and the line below names the door to use instead. -->
13+
14+
**BREAKING** (a runtime export is removed). It ships as `patch` because the metadata protocol does not move. There is no staged window.
15+
16+
- FROM `default` TO `new DriverPlugin(new InMemoryDriver(), 'memory')` from `@objectstack/runtime`, or `new InMemoryDriver()` handed to whatever registers your drivers.
17+
18+
A stack config that names `'@objectstack/driver-memory'` as a string entry of `plugins` loaded that object and registered an `InMemoryDriver` through it. After this change that entry loads the module without a default and registers no driver, so put `new DriverPlugin(new InMemoryDriver(), 'memory')` in the `plugins` array instead.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the objectstack-ui report example declares its download in `exportOptions` under the base object's `allowExport`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`rules/dashboards.md` 〈Report Configuration〉 taught a report's `dateRange` and not its `exportOptions`: the example now declares `exportOptions: { formats: ['csv', 'xlsx'] }` (optional `includeHeaders` / `fileNamePrefix`) and the note says who may download is the base object's permission-set `allowExport` and that a `joined` report takes neither key, paid for by folding the `dateGranularity` and `drilldown` restatements that the same file already teaches under Server-side date bucketing and Drilldown.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/console': patch
4+
'@object-ui/i18n': patch
5+
---
6+
7+
feat(app-shell): Studio orders an object's related lists, and a field group can place a related table
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
- **Object settings, `relatedLists`.** The Settings tab lists the object's inbound relationships (each `lookup` / `master_detail` field of a child object that points at it) as an ordered list: drag a row or use the move buttons to reorder, and add a relationship from the picker. An entry is the child object's name, or `object.field` when that child points at the object through more than one field. Relationships no entry names follow, greyed and not draggable, in the default order (owned children first, then lookups). The list orders only: a relationship hidden with `relatedList: false` is listed and marked hidden, and the switch stays on the field. A preview shows the record page's resulting order, split into body tables and side cards. Removing the last entry removes the key.
13+
- **Every write is checked first.** An entry that names no inbound relationship is never written, since `defineStack` and the publish door refuse it. A stored one keeps its row, marked, and the next change to the list leaves it out. Nothing is written until the object list has loaded.
14+
- **Field groups.** The group inspector in the form layout designer has a "Place a related table here" picker that writes the group's `relatedList` from the same relationships. It is withheld while the group still holds fields, and it does not offer a relationship another group already places. In the designer, a group that places a table is drawn in its place as that table and accepts no dropped fields.
15+
- **Shared ordered list.** The highlight fields editor and the related lists editor draw the same draggable, keyboard-movable rows.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/types': patch
3+
'@object-ui/permissions': patch
4+
'@object-ui/plugin-form': patch
5+
'@object-ui/i18n': patch
6+
---
7+
8+
fix(plugin-form): the record edit dialog draws a record the caller may read but not write as read-only, says why, and offers no Save
9+
10+
Clause-②: no
11+
Metadata-protocol: none
12+
13+
On a record shared to the caller for reading only (sharing, ownership or row-level security), or held by a pending approval that locks it, the console's edit dialog used to open with every field live; the user filled it in and the server refused the save as a whole. The dialog now asks the server about the row itself:
14+
15+
- **The row's verdict.** `RecordFieldAccess` (`@object-ui/types`) carries the record projection's `writable`, which `DataSource.getRecordFieldAccess` already passes through. Below `RecordFieldAccessScope` (`@object-ui/permissions`), `writable: false` refuses `write` on every field of the record and `check(object, 'update')` with the reason `record-not-writable`; `read`, `create`, other objects and other records keep the enclosing answer, and an object grant the provider already refuses keeps its own reason. `writable: true` and an absent `writable` (an older server) change nothing.
16+
- **The approval lock.** The projection leaves out the approvals record lock, which the server judges on the values a save sends. `RecordFieldAccessScope` takes `lockedBy: 'approval'` for a lock the host reads, answered the same way with the reason `record-locked-by-approval`, and `ModalForm` takes the same `lockedBy` prop. The console's edit dialog passes it from the approvals read and rule the record page uses.
17+
- **What the form draws.** Every field gate (`gateFormFields`, the outbound write gate, the record page's inline edit) reads the scoped answer with no call-site change. `closedFormAffordance` reports `record` or `approval` for such a row, the form's notice says "You don't have permission to edit this record. The fields are read-only." (new key `form.noPermissionToEditRecord` in every pack) or the record page's approval-lock sentence, and `ModalForm` and `DrawerForm` offer no Save on it.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/plugin-form': minor
4+
'@object-ui/types': minor
5+
---
6+
7+
feat(spec)!: a form view's `buttons` block holds `submit` and `cancel` only; `buttons.reset` is retired, because no form ever drew a reset button
8+
9+
Clause-②: no (narrowing)
10+
11+
Metadata-protocol: narrowed (`FormViewSchema.buttons.reset` is a tombstone)
12+
13+
<!-- adr-0087: registered form-view-buttons-reset-removed, ui-form-view-buttons-reset-retired -->
14+
15+
**BREAKING** accept-set narrowing on the form view surface (`@objectstack/spec`), ADR-0049 enforce-or-remove.
16+
17+
The console draws a form's Submit and Cancel buttons on every entry point (the form page, the New and Edit dialogs, the record form page, master-detail forms). None of them drew a reset button, and `buttons.reset.label` was never read. The one renderer fold that read `buttons.reset.show` passed it to ObjectForm's clear-after-save flag, so `show: true` emptied the form after a successful save instead of adding a button.
18+
19+
- **FROM → TO.** `buttons: { submit, cancel, reset }` → `buttons: { submit, cancel }`. No button replaces it: Cancel leaves the form without saving. `reset.show: true` used to clear the form after each save on ObjectView and the record form page; a converted form no longer clears after a save. A form that should clear after a save declares `submitBehavior: { kind: 'continue' }`, which ObjectForm and the console form page honour.
20+
- **The one-line fix.** Delete `buttons.reset`. `os migrate meta --from 17` lists the edit for existing sources, and `--write` applies it. A stored `sys_metadata` row is converted when it is read (the `form-view-buttons-reset-removed` conversion), so a form view saved with the key keeps loading.
21+
- **What the parse says.** "`view.form.buttons.reset` was removed in @objectstack/spec 18.3.0 (ADR-0049) — no form ever drew a reset button, and its label was never read. …", at every form view door (a view's `form` and `formViews.*`, a form view item record, a flattened form overlay) and at `defineStack` as `STACK_SCHEMA_INVALID` / 422. TypeScript rejects the key at the authoring site.
22+
- **Unchanged.** `buttons.submit` and `buttons.cancel` parse and render as before.
23+
24+
The renderer (`@object-ui/plugin-form`, `@object-ui/types`): `ObjectForm` no longer folds `buttons.reset.show` onto `showReset`, and the `ObjectFormSchema.buttons` mirror (type and zod) drops `reset`. The flat `showReset` prop itself is unchanged.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
fix(objectql): temporal-comparand and text-operator filter refusals state what to write inside the 500-character client cap
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`POST /api/v1/data/OBJECT/query` refuses a comparand a temporal field cannot read (`{ "created_date": { "$gte": "last_30_days" } }`, a year outside the field's range, an impossible day, a non-ISO spelling, a relative-date placeholder resolved outside the years) and a text operator aimed at a field that never holds a string (`{ "amount": { "$contains": "5" } }`) with `400 INVALID_FILTER`, in `where`, `aggregations[i].filter` and `having`. A client reads at most 500 characters of that message, and these refusals printed the prescription last, after the consequence, so it was often cut off. Each refusal now reads: what was written and why it is not a value of its kind, then what to write instead, then "NOT applied" and what the value would have done. The `datetime` prescription now names the relative-date placeholder first (`"{30_days_ago}"`), and the year-range prescriptions are shorter. The cap, the truncation, the code and the status are unchanged, and so is the set of filters refused.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills/objectstack-ui): `pages.md` 〈Inline metadata views〉 states the embed scope `os build` enforces — this package or a package of the same artifact it declares in `manifest.dependencies`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The build-lint sentence said a `metadata` fence's referenced object-rule / flow / permission set "must exist in this package". `docs/metadata-embed-ref` judges an embed against the package that ships the doc plus the packages of the same artifact that it declares in `manifest.dependencies` (direct only; one-way), so the rule the skill teaches now matches: an embed may name such a declared package's item, and anything else fails the build. No platform behaviour changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
"@objectstack/skills": patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
objectstack-automation: integrations are declared, not coded. The `connector_action`
9+
node row no longer calls a connector "pre-built"; a new SKILL.md section states the
10+
declarative-connector-first rule (a vendor system over HTTP is a `connectors:` entry
11+
with `provider: 'openapi' | 'mcp' | 'rest'`, a packaged spec and `auth.credentialRef`,
12+
dispatched by `connector_action`; a vendor callback is an `api` flow), the five cases
13+
that justify platform code, and that a document the loader cannot take yet is a gap to
14+
name — never a reason to write a plugin. `references/integration-recipes.md` carries one
15+
recipe per archetype with the author / administrator credential hand-off and today's
16+
limits (`connectorConfig.input` is sent as written; no `oauth2`; no vendor-signed
17+
callbacks). `evals/integrations-declared.json` adds the DocuSign, Stripe-read and
18+
vendor-without-spec cases; the DocuSign case fails any answer that proposes a plugin.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/objectql': patch
4+
---
5+
6+
fix(objectql): a currency field written as an object, `{ value, currency }`, is refused with a sentence that says a currency value is a bare number
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) a refusal of a written VALUE at the record validator's numeric arm gains a second sentence: no authorable key, spelling or stored shape of metadata moves, and no schema, type or export changes. `packages/spec` gains one sentence in the built-in validation-message catalog (`invalid_number_currency_object`, a rendering variant of the existing `invalid_number` wire code, which is unchanged), and the accept set of the write door is the same before and after. A stored row keeps whatever it holds. The other categories are closed on facts: the packages publish (not `unpublished`); no ADR-0087 id covers a write-door value check (not `registered` / `already-registered`); and the change is a message, not a TypeScript declaration (not `runtime-interface-only` / `type-surface-only`). -->
12+
13+
The `{ value, currency }` shape was retired by ADR-0104 D1. A record write that still sends it to a `currency` field was refused with only "Amount must be a number", which does not say what to write instead.
14+
15+
- **`@objectstack/objectql`**: when a `currency` field's value is a plain object, the `invalid_number` refusal now reads: "Amount is a currency amount: write it as a bare number (ADR-0104), such as 10, not an object such as { value, currency }. A currency field declares its currency in currencyConfig: the fixed mode's defaultCurrency, or under the dynamic mode (the default) the tenant default currency". The wire code stays `invalid_number`, with no new key on the error, so no client contract moves. A non-number string, an array, and an object on a `number` field keep the plain sentence.
16+
- **`@objectstack/spec`**: the validation-message catalog carries that sentence as `invalid_number_currency_object`, in en, zh-CN, ja-JP and es-ES. A deployment can override it under `validation.field.invalid_number_currency_object`, like any other built-in message.
17+
18+
What to do: nothing. Writes that were accepted are still accepted, and writes that were refused are still refused with the same code.

0 commit comments

Comments
 (0)