Skip to content

Commit d7514fc

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-ytcn0k
2 parents fb1192c + 399df5b commit d7514fc

82 files changed

Lines changed: 4493 additions & 331 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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
refactor(metadata-protocol): one `ObjectStackProtocolImplementation` member body moves out of `protocol.ts` through a host object, the shape the rest of the class follows
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
No behaviour change. `tenantAuthoredWriteRefusal` keeps its docblock and signature on the class; its body now lives in `protocol-tenant-authored-write.ts`, reached through `protocol-host.ts`, a per-instance object that forwards to the class's own members (built in the class's static block, so the class gains no member). The moved body differs from the method body only by `this.` reading `host.`. The published `index.d.ts` (ESM and CJS) is byte-identical to the one `main` builds before this change. Nothing to migrate.

‎.changeset/27842-disabled-actions-served.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ Metadata-protocol: none
1212

1313
- **Served beside the object** (`@objectstack/metadata-protocol`, `@objectstack/rest`). `GET /api/v1/meta/object/:name` serves `disabledActions` beside `item`: the names in `item.actions` that the deployment's activation ledger switches off. `GET /api/v1/meta/object` serves the same answer keyed by object name. The default cached door carries it too, read live on every call and kept out of the ETag, so a read that carries it is answered in full rather than `304 Not Modified`. The key is absent where no ledger answered (no engine, or an engine with no ledger attached), on a draft read, and on every type other than `object`. `item.actions` is unchanged. The protocol reads the engine's `getDisabledActions` through the kernel's service table by name and imports no engine.
1414
- **The fact it serves** (`@objectstack/objectql`). `ObjectQL.getDisabledActions()` returns the names the engine's activation projection switches off, the same projection both dispatch doors consult before they refuse a call, or `undefined` when no ledger is attached. `listDisabledActions()` is unchanged.
15-
- **The refusal sentence** (`@objectstack/objectql`, `@objectstack/runtime`). A switched-off action is still refused 409 `ACTION_DISABLED` at the REST and MCP doors, and the message now reads: "This action is turned off for this deployment. An administrator can turn it on again under Setup › Packaged automation, or you can use another action." It no longer names the ledger table or the ADR. The engine writes those, with the code and the action's name, to an `info` log line each time it refuses one.
15+
- **The refusal sentence** (`@objectstack/objectql`, `@objectstack/runtime`). A switched-off action is still refused 409 `ACTION_DISABLED` at the REST and MCP doors, and the message now reads: "This action is turned off for this deployment. An administrator can turn it on again under Setup › Packaged Actions, or you can use another action." It no longer names the ledger table or the ADR. The engine writes those, with the code and the action's name, to an `info` log line each time it refuses one.

‎.changeset/27966-flow-disabled-plain-sentence.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ fix: a flow an administrator switched off is refused with a plain sentence, the
99
Clause-②: no
1010
Metadata-protocol: none
1111

12-
- **The flow refusal** (`@objectstack/service-automation`). A packaged flow switched off under Setup › Packaged automation is still refused `FLOW_DISABLED`, at every door that runs it: its trigger, a direct run, and a parent flow that calls it as a subflow. The message now reads: "This flow is turned off for this deployment. An administrator can turn it on again under Setup › Packaged automation, or copy it there under a new name." It no longer names the ledger table or the ADR. The engine writes those, with the code and the flow's name, to an `info` log line each time it refuses one. A flow disabled by its own status (`obsolete` / `invalid`) still reads "Flow '…' is disabled", so the two reasons stay distinguishable.
13-
- **One sentence for both** (`@objectstack/types`, `@objectstack/objectql`). `describeTurnedOffPackagedItem('action' | 'flow')` builds the sentence. The action refusal (`ACTION_DISABLED`) reads from it too, and its wording is unchanged. Only the last clause differs: a flow can be copied under a new name, and an action cannot, so the action sentence still offers another action instead.
12+
- **The flow refusal** (`@objectstack/service-automation`). A packaged flow an administrator switched off is still refused `FLOW_DISABLED`, at every door that runs it: its trigger, a direct run, and a parent flow that calls it as a subflow. The message now reads: "This flow is turned off for this deployment. An administrator can turn it on again in its package's Automations in Studio, or copy it there under a new name." It no longer names the ledger table or the ADR. The engine writes those, with the code and the flow's name, to an `info` log line each time it refuses one. A flow disabled by its own status (`obsolete` / `invalid`) still reads "Flow '…' is disabled", so the two reasons stay distinguishable.
13+
- **One sentence for both** (`@objectstack/types`, `@objectstack/objectql`). `describeTurnedOffPackagedItem('action' | 'flow')` builds the sentence. The action refusal (`ACTION_DISABLED`) reads from it too, and its wording is unchanged. The two sentences differ in two clauses: where an administrator turns the item back on (an action under Setup › Packaged Actions, a flow in its package's Automations in Studio), and what can be done meanwhile (a flow can be copied under a new name, and an action cannot, so the action sentence offers another action instead).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(console): Setup → Integrations → Connector Connections asks for the installer's OAuth client when an instance declares `clientSource: 'installer'`
7+
8+
Clause-②: yes
9+
Metadata-protocol: none
10+
11+
An `oauth2` `authorization_code` connector instance whose declaration says `clientSource: 'installer'` takes the OAuth client the administrator registered with the vendor. Connect and Reconnect on such an instance now open a form for its client ID and client secret before the sign-in window opens; Continue begins the connect with `{ clientId, clientSecret }` (the secret omitted for a public client). The secret input is masked, starts empty on every opening and is cleared the moment the connect is sent; the stored secret is never read back. A connected instance shows which client it keeps (`OAuth client ID, secret stored`), and Reconnect prefills that client ID and sends the client again, because the server takes a client on every connect of such an instance. Every other instance connects with no form, as before.
12+
13+
Twelve new keys under `connectorConnections` in all ten `@object-ui/i18n` locales: `clientKeptWithSecret`, `clientKept`, `clientTitle`, `clientIntro`, `clientStored`, `clientStoredSecret`, `clientStoredNoSecret`, `clientResend`, `clientId`, `clientSecret`, `clientSecretHint`, `clientContinue`.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
feat(lint): the publish-time null-guard gate judges a record-triggered flow's start, edge and decision-branch conditions
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: narrowed
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type of the package's public surface is retired: the publish-time null-guard gate now judges three predicate slots of a record-triggered flow that it already judges on validation rules and hook conditions. A flow it now refuses is fixed in the flow's own expression (add the `!= null` test the message names), so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. -->
11+
12+
`os build`, `os validate`, `os lint` and the runtime publish gate now refuse a record-triggered flow (start `triggerType: 'record-…'`) whose start condition, edge `condition` or `decision` branch `expression` applies an ordering (`< <= > >=`) or arithmetic (`+ - * / %`) operator to a nullable declared field of the trigger object with no `!= null` / `== null` / `!isBlank()` test in front of it. The run's `record` and `previous` carry every declared field, nulls included, so such a comparison faults with `no such overload` on the first record where the field is empty, and the run fails there (the write that fired it still lands).
13+
14+
- **Who moves.** A flow condition such as `budget > 200000` on an optional `budget`. Fix: `budget != null && budget > 200000`, or put the `!= null` test in the flow's start condition, which gates every run, so every edge and branch after it may compare freely. `has(record.budget)` is not a guard: the key is always present.
15+
- **Bare names count.** The run spreads the record's fields to top level, so a bare `budget` is read as `record.budget`, unless the flow declares a variable or a node id of that name, or has a node that can bind names nobody declared (a `script`, `connector_action`, `wait`, `subflow` or `map`); those bare names are not judged. At the runtime publish gate bare names are not judged.
16+
- **Not judged.** Flows with no record trigger (an `api` flow's request body, an action's or `map` item's record are not made total), a `screen` field's `visibleWhen`, and `config.condition` on any node but `start`.
17+
- **Fewer findings elsewhere.** A `count` or `sum` roll-up summary is now always valued for this gate (zero children is zero), on validation rules and hook conditions too; `min` / `max` / `avg` roll-ups stay nullable.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-core': patch
3+
---
4+
5+
docs(metadata-core): the `ITEM_KEY_DISCRIMINATORS` docblock no longer says email template resolution is exact with no language-subtag folding
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The docblock ships in the package's type declarations, and it still described the exact `(name, locale)` ladder: the named tag matched exactly, then `en-US`. Over the built-in registry template loader a named locale is now folded against the bundle's tags, then the stack's declared fallback locale is tried, then `en-US`. The docblock now cites `SendTemplateInput.locale` in `@objectstack/spec` as the ladder of record instead of restating its rungs, and keeps the point the table rests on: every rung loads one row at one tag exactly, so `en` and `en-US` stay two distinct rows of one bundle. No code changed.

‎.changeset/29039-mobile-native-shell-mode.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ The ObjectStack mobile app hosts the web Console in a WebView and injects a brid
1313
- **`@object-ui/mobile` gains the bridge type and detection.** `ObjectStackNative` types exactly the members of the shell's bridge contract (`version`, `platform`, `appVersion`, `capabilities`, `safeArea`, `has`, the five calls and the two events). `useNativeHost()` returns it, or `null` outside the shell; it reads the global lazily and re-checks at `DOMContentLoaded`, `load` and a few points after `load`, so a late injection still arrives. `getNativeHost()` is the same read outside React.
1414
- **Typed calls that never throw.** `nativeGetPushToken`, `nativeHaptic`, `nativeOpenExternal`, `nativeNavigate` and `nativeResetServer` check `has(capability)` and that the method exists, and resolve a `NativeCallResult`: `{ status: 'unavailable' }`, `{ status: 'ok', value }`, or `{ status: 'failed', code, message }`.
1515
- **Router wiring.** `useNativeHostRouting({ route, navigate })` posts `ready()` once per page load after the first route has rendered, posts `routeChanged(route)` once per route change, and answers the shell's cancelable `objectstack-native:navigate` window event by moving the router to `detail.route` (an in-app path only) and cancelling the event, so a notification tap does not reload the Console. Outside the shell it installs nothing.
16-
- **The Console uses it.** In the shell the Console posts those events from its router, marks the root element with `data-os-native-shell="ios|android"`, and drops the PWA install manifest. It adds no safe-area padding: the shell already pads its WebView. Without the global the Console behaves exactly as before.
16+
- **The Console uses it.** In the shell the Console posts those events from its router, marks the root element with `data-os-native-shell` set to the shell's platform string (an open set: `ios` and `android` from the mobile app, another value from another shell), and drops the PWA install manifest. It adds no safe-area padding: the shell already pads its WebView. Without the global the Console behaves exactly as before.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/react': patch
3+
---
4+
5+
fix(react): an inline action's `description` locale map shows in the user's language in the param dialog
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
An inline action on a page (`element:button` with `properties.action`) can declare a `description`, the line its param dialog shows under the title. It may be written as a per-locale map such as `{ en: '…', 'zh-CN': '…' }`. Before this change the action text localizer left that map alone, so the dialog received an object instead of a sentence.
11+
12+
What changes:
13+
14+
- `useActionTextLocalizer` now collapses a declared `description` to the active language, the same way it already treats `confirmText`, `successMessage` and `errorMessage`. A plain string comes back unchanged, and a `{ dialect: 'template', source }` envelope comes back as itself.
15+
- It does not read a translation bundle entry for `description`. The param-dialog host still resolves `_actions.<name>.description` over the string it is handed.
16+
- An action that declares no `description` gains no key, so its dialog keeps the generic "Please provide the required information to continue." line.
17+
18+
What to do: nothing. Nothing a page, view or action declares changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
feat(spec): `ExplainAccessRequest` declares `recordIds`, the batch selector the explain wire schema already carries
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
The `security` service contract's `explain(request)` selector, `ExplainAccessRequest`, now declares the optional `recordIds` member. The wire schema `ExplainRequestSchema` already declared it, and both the REST explain route and the reference security service already honour it. A TypeScript caller handing `explain` a batch no longer needs a cast to get past the contract type.
11+
12+
- **The type is the wire schema's own.** `recordIds` is typed as `ExplainRequest['recordIds']` (a list of ids), so the contract and the schema cannot drift; a type-level test holds the two equal.
13+
- **What it means.** The same record-grained question as `recordId`, for several records of one `(object, operation)` pair: mutually exclusive with `recordId`, at least one and at most `EXPLAIN_BATCH_MAX_RECORD_IDS` ids, answered by `ExplainDecision.records` in the same order.
14+
- **Nothing is required of an implementation.** A security service may ignore `recordIds` and answer only the object-level decision, with no `records`; the REST explain route then asks each id singularly, as it already does.
15+
- **No runtime change.** Only the declared type moves. Every request that compiled before still compiles.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/mobile': patch
3+
---
4+
5+
fix(mobile): the native bridge's `platform` is an open set, and a bridge speaking a newer protocol reads as no bridge
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
The ObjectStack desktop shell hosts the Console through the same `window.ObjectStackNative` bridge as the mobile app, and reports a platform other than `ios` or `android`.
11+
12+
- **`ObjectStackNative.platform` widens** from `'ios' | 'android'` to `'ios' | 'android' | (string & {})`. The two known values still autocomplete. Code that switches exhaustively over the old two values now has to handle another string; branch on a capability (`has()`) or the viewport instead of the platform. The Console's root attribute `data-os-native-shell` carries the value verbatim.
13+
- **Bridge protocol version gate.** The bridge's `version` bumps only on a breaking change, and this package speaks version `1`. `getNativeHost()` and `useNativeHost()` now answer `null` for a bridge whose `version` is greater than `1` or not a number, so a page keeps its browser behaviour (no shell mode, no `ready`, the PWA manifest kept) instead of calling a protocol it cannot read.
14+
- **What `ready()` means, written down.** `ready` is posted when the Console has painted its own UI, which may be a loading fallback such as the auth guard's spinner while the session is read on a cold start. The shell lifts its loading cover on it, so the cover hands over to the Console's spinner rather than staying up through the session round-trip. Nothing about when it is posted changes.

0 commit comments

Comments
 (0)