Skip to content

Commit e9480d9

Browse files
committed
Merge origin/main into claude/project-thread-0jat8q; regenerate the ADR index
main landed ADR-0145 (e9c88f7, amended in 753a14e) after this branch's last regeneration, so the index's Summary block and table tail conflicted. node scripts/adr-index.mjs rewrites docs/adr/README.md on the merge result; nothing in it is hand-resolved. Counts: 146 records, 141 numbers, Accepted 128, Proposed 4, Total 146, with both the ADR-0144 and ADR-0145 rows. node scripts/adr-index.mjs --check reports the index current. Claude-Session: https://claude.ai/code/session_01Utp6CbTZrFqRYNgjSaSbN5 Co-authored-by: Claude <noreply@anthropic.com>
2 parents 775d8c0 + 753a14e commit e9480d9

583 files changed

Lines changed: 30807 additions & 20308 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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/fields': patch
3+
'@object-ui/app-shell': patch
4+
'@object-ui/plugin-detail': patch
5+
'@object-ui/plugin-list': patch
6+
'@object-ui/i18n': patch
7+
---
8+
9+
feat(console): ownership queues in the Console — a queue picker reading both sources, a Claim button the server decides, and an "In a queue" list filter
10+
11+
Clause-②: no
12+
Metadata-protocol: none
13+
14+
The server half of ownership queues shipped without a Console face. Now:
15+
16+
- **Queue pickers list both sources.** The flow designer's approver Value for a `queue` approver, and a sharing rule's `queue` recipient, list the queues an installed app declares (`GET /api/v1/meta/queue`) together with the organization's own `sys_queue` records, one list by machine name, and store the name. The approver picker follows the binding the server publishes (`registry-and-data`); with no data adapter, or a server that publishes another binding, it keeps the text box.
17+
- **Claim.** A record sitting in a queue (no owner, a `queue` named) shows **Claim** in its header only when the explain engine's answer for a transfer of that record carries an admitting `queue_claim` rule; no answer means no button. Claim sends `{ owner_id: <self> }` through the normal update, and a lost race (`409 CONCURRENT_UPDATE`) is reported as "Someone else claimed this record first."
18+
- **In a queue.** A list of an object served with the injected `queue` column gains an **In a queue** toggle that narrows the fetch (and an export) to `owner_id` empty and `queue` set. Which of those rows come back is still the server's read filter.
19+
- **Docs.** A new authoring guide, *Queues and assignment rules*, covers declaring a queue and an assignment rule, the `load` requirement, what a parked record is and who sees it, how a claim is spelled, and that a queue is not a messaging audience; the approvals, sharing-rule and RLS pages link to it.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@object-ui/types": minor
3+
"@object-ui/i18n": minor
4+
"@object-ui/cli": minor
5+
---
6+
7+
refactor(ui)!: retire `@object-ui/plugin-ai` and its three node types
8+
9+
Clause-②: no (narrowing)
10+
Metadata-protocol: narrowed
11+
12+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/plugin-ai#*) the retired operands are React components of a published ui package that nothing in the workspace loaded; no packages/spec schema, no ADR-0087 conversion surface and no stored shape names them, so there is nothing for `objectstack migrate meta` to rewrite, and the three node literals are refused by name on the zod face -->
13+
14+
**BREAKING.** `@object-ui/plugin-ai` is retired: `18.2.0` is its last published version, and it gets no further release. The console registered it neither eagerly nor lazily and no workspace package imported it, so the `ai-form-assist`, `ai-recommendations` and `nl-query` node types it registered rendered nowhere in the product.
15+
16+
What leaves, and what to do:
17+
18+
- FROM `@object-ui/plugin-ai` TO nothing: drop the `@object-ui/plugin-ai` import and dependency. A host that still wants the component keeps its own copy and registers it under its own type key.
19+
- Page nodes of type `ai-form-assist`, `ai-recommendations` or `nl-query`: remove them from the page. `safeValidateSchema` and `objectui validate` now refuse each of the three literals by name at `type`, with that remedy; before, the page rendered the "Unknown component type" panel in their place whenever the package was not loaded.
20+
- `@object-ui/types`: `AIFormAssistSchema`, `AIRecommendationsSchema`, `NLQuerySchema`, `AIConfig`, `AIFieldSuggestion`, `AIRecommendationItem`, `NLQueryResult`, `AIProvider` and `AIModelType` are `@deprecated` and now `never`, and `AnySchema` has no member for the three literals. The zod names (`AIFormAssistSchema` and the rest under `@object-ui/types/zod`, `AIComponentSchema` included) are `@deprecated` `z.never()` schemas that accept nothing. Remove every reference. The names stay importable until the tombstone sunset rule decides when they leave the published face.
21+
- `@object-ui/i18n`: the `ai.*` message block (`ai.nlQuery.*`, `ai.formAssist.*`, `ai.recommendations.*`) is gone from all ten packs. A host that registered overrides for those keys can delete them.
22+
- `@object-ui/cli`: `objectui check` no longer lists the three literals as known component types.
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.

‎.changeset/26080-readme-contributing-links.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
'@object-ui/layout': patch
1212
'@object-ui/mobile': patch
1313
'@object-ui/permissions': patch
14-
'@object-ui/plugin-ai': patch
1514
'@object-ui/plugin-calendar': patch
1615
'@object-ui/plugin-charts': patch
1716
'@object-ui/plugin-chatbot': patch
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/spec': patch
3+
---
4+
5+
docs(spec): the liveness ledger's `_note` paths follow eleven closed 2026-06 audits into `docs/audits/archive/2026-06/`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The `_note` of `liveness/{action,agent,dashboard,field,flow,hook,object,report,skill,tool,view}.json` named its seeding audit as `docs/audits/2026-06-<type>schema-property-liveness.md`. Those audits are closed and now live under `docs/audits/archive/2026-06/`, so each note names the new path, and `liveness/README.md` says where the series sits. No status, evidence or accepted shape changes: only the path text inside notes moves.
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+
'@objectstack/spec': patch
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
fix(spec): the `newTabUrl` path refusal is registered as an ADR-0087 D3 semantic entry, and the action form labels the field "New-tab path"
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: registered ui-action-new-tab-url-path-refused -->
12+
13+
Supersedes, in the same release, the ADR-0087 disposition of the `newTabUrl` narrowing note (`fix(spec)!: an action's newTabUrl is a path on this backend and must start with /`), which declared `not-required (no-migration-prescription)` while carrying a remedy. That narrowing is now registered: D3 semantic entry `ui-action-new-tab-url-path-refused` (protocol 18) names the surface (an `action` whose `newTabUrl` does not start with `/`), the two remedies (write the backend path, or drop `opensInNewTab` / `newTabUrl` and declare a `type: 'url'` action with `target: 'https://…'` and `openIn: 'new-tab'`), and why neither is applied mechanically: an external URL names no path on this backend, and the url-action rewrite changes the action's type, drops its handler and replaces an auth-enforcing endpoint with a page the browser opens directly. So `os migrate meta` and the upgrade guide carry it as a structured TODO, and a stored row holding such a value behaves exactly as that note says (loads unchanged and registered, counted `invalid` with a `[metadata_spec_invalid]` boot line, served with `_diagnostics`, next save refused until rewritten). No accept set changes here.
14+
15+
The action form's `newTabUrl` field label now says path in every locale: "New-tab path" (en), "新标签页路径" (zh-CN), "新しいタブのパス" (ja-JP), "Ruta de pestaña nueva" (es-ES).
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'@objectstack/formula': minor
3+
'@objectstack/plugin-email': minor
4+
'@objectstack/plugin-auth': minor
5+
'@objectstack/spec': minor
6+
'@objectstack/service-messaging': minor
7+
'@objectstack/service-automation': minor
8+
'@object-ui/core': minor
9+
'@object-ui/react': minor
10+
'@object-ui/plugin-grid': minor
11+
---
12+
13+
feat(formula, plugin-email, messaging, automation, ui)!: a template `currency` hole with no argument formats in the organization's default currency, and with none the render refuses it; nothing formats in US dollars by default any more (#26939)
14+
15+
Clause-②: no (narrowing) — a mail send or render (`sendTemplate`, `renderTemplate`, and the messaging email and inbox channels on them), a flow text slot (notify, http, screen, end) and a templated formula evaluation refuse a `{{ amount | currency }}` hole with no argument when no default currency reaches the render, which they formatted in US dollars before. Authoring is unchanged: the hole is still accepted at save and publish. Nothing is widened.
16+
Metadata-protocol: narrowed
17+
Skills-docs: updated here — `content/docs/automation/email-templates.mdx`, `content/docs/automation/flows.mdx` and `content/docs/data-modeling/formulas.mdx` state the tenant default and the refusal; no skill under `skills/**` taught the dollar default (the formula skill names the formatter grammar only), so no skill line changes.
18+
19+
<!-- adr-0087: registered template-currency-no-argument-tenant-default -->
20+
21+
**BREAKING** semantic change of the `currency` formatter's no-argument form (`@objectstack/formula`), served by every template dialect: mail templates, flow text slots, and action / bulk-action confirm and success copy.
22+
23+
- **Before.** `{{ amount | currency }}` formatted the amount in US dollars (`$1,234.50`) whatever currency the amount was in and whatever the organization's default currency was.
24+
- **Now.** It formats in the organization's default currency, the `localization.currency` setting (Setup → Localization) that a currency field and a dashboard measure already display in: `CN¥1,234.50` for an `en-US` reader and `¥1,234.50` for a `zh-CN` reader when the default is CNY. Every server-side read goes through `resolveLocalizationContext` (`@objectstack/core`); the UI reads `useLocalization().currency`.
25+
- **With no default currency.** The render refuses the hole by name and names the remedy: set the default, or write `currency:CODE` (a mail template's refusal also names `currency:@path`). A mail send or render fails with `TEMPLATE_INVALID` and nothing is sent; a messaging delivery records it as a permanent failure. A flow node with such a hole in a text slot fails with the refusal, which no fault edge routes. An action's confirm or success sentence is shown as written, the existing fallback for a sentence the template engine refuses.
26+
- **How each host hands the default over.** `SendTemplateInput.currency` / `RenderTemplateInput.currency` (`@objectstack/spec/contracts`), filled by the messaging email and inbox channels from the delivery's organization, and by the auth mails (verification, password reset, invitations, magic link, notices) from the organization the mail names or, for identity mail, the deployment-wide setting (`@objectstack/plugin-auth`, once, on the email service it wires); `AutomationContext.currency`, seeded once at run setup from the run's tenant (`AutomationEngine.setTenantCurrencyResolver`, which the automation plugin wires), persisted with a suspended run and inherited by a subflow; `EvalContext.currency` in `@objectstack/formula`, a reserved channel like `locale`, never bound as a root; `ActionRunner.setTenantCurrency` in `@object-ui/core`, installed by the React action providers.
27+
- **Unchanged.** `{{ amount | currency:EUR }}` formats in the code it names, with or without a default. A variable, field or root named `currency` is a value, never the default. Compile, `validateExpression` and the publish gates accept the no-argument form as before. A template with no no-argument currency hole needs no default.
28+
- **Who sees it.** An organization with a default currency other than USD sees its own code where it saw dollars. An organization with no default currency that sends a mail, runs a flow or shows an action sentence with a no-argument `currency` hole now gets the refusal instead of dollars.
29+
30+
**The remedy.** None where the amount is in the organization's default currency: set it in Setup → Localization. Where the amount is in one fixed currency, name it in the hole: `{{ amount | currency:USD }}`. Where a mail template shows each record's amount in that record's own currency, read the code from the send's data: `{{ amount | currency:@currency_code }}`.
31+
32+
**Supersedes, in the same release.** The unreleased notes of the mail `currency:@path` and hole-grammar changes (`26660-currency-code-path-formula`, `26660-currency-code-path-plugin-email`, `26660-mail-hole-grammar-formula`, `26660-mail-hole-grammar`) describe a `currency` hole formatting in US dollars, and the first three say a hole with no argument still does. That was true when they were written; this entry moves it, and in this release a no-argument hole formats in the organization's default currency as described above.

0 commit comments

Comments
 (0)