Skip to content

Commit 02bc68c

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-ybjm3h
2 parents 62c90d6 + 83c4bd2 commit 02bc68c

130 files changed

Lines changed: 7266 additions & 1589 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+
'@object-ui/i18n': patch
3+
---
4+
5+
feat(i18n): the Studio inspectors' page block, action, dataset, flow node, app navigation and dashboard widget strings move into the `studio` namespace
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Second slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). The 520 keys under `engine.inspector.pageBlock.*`, `engine.inspector.action.*`, `engine.inspector.dataset.*`, `engine.inspector.flowNode.*`, `engine.inspector.appNav.*` and `engine.inspector.widget.*` now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged in both `en` and `zh`. Studio reads the same strings; a Chinese viewer sees these labels in English for the moment the `zh` catalogue takes to load, as with the first slice. No export changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
refactor(types)!: `object-form` and `object-calendar` declare the four spec keys their renderers read, and `@object-ui/types/zod` renames its legacy `ActionSchema` to `CRUDActionSchema` (#25923)
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: narrowed
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No `@objectstack/spec` key, object definition, stored row or stored shape moves. The four keys are members the spec's `ComponentPropsMap['object-form']` and `['object-calendar']` rows already declare, now declared by reference on the `@object-ui/types` mirror of those rows, so `objectui validate` refuses exactly the values the spec refuses and `objectstack migrate meta` has nothing to rewrite. The renamed zod const validates a legacy `type: 'action'` node that is not metadata and has no stored representation; the affected party is a JavaScript or TypeScript consumer importing it by name, reached by its own compiler or bundler. `runtime-export-retired` would be the natural category, but it reads only a package's main entry and refuses an operand declared in a `*.zod.ts`, and this export lives on the `./zod` subpath in `crud.zod.ts`. The package publishes (not unpublished); no ADR-0087 id covers this change (not registered or already-registered); and the symbols are zod-backed (not runtime-interface-only or type-surface-only). -->
11+
12+
`@object-ui/types` mirrors `@objectstack/spec` schemas so that `objectui validate` and `os validate` give one verdict. Over every schema a mirror pairs with (same-name exports, and the `ComponentPropsMap` row of each node type), eleven spec keys were neither declared nor refused on objectui's side. Four of them are read by a renderer, and those are now declared:
13+
14+
- **`ObjectFormSchema`** (TypeScript and zod) declares `contentLayout` and `confirmOnDiscard`, the `ComponentPropsMap['object-form']` members by reference. `ModalForm` reads `contentLayout`; `ModalForm` and `DrawerForm` read `confirmOnDiscard`.
15+
- **`ObjectCalendarSchema`** (TypeScript and zod) declares `locale` and `loading`, the `ComponentPropsMap['object-calendar']` members by reference. `plugin-calendar` reads both off the node. They are node keys, not the calendar field-name keys that live in the `calendar` block.
16+
17+
The other seven are the spec form-view entry's keys on `FormFieldSchema`, which is objectui's runtime form field, a different layer under the same name. `normalizeSectionField` in `@object-ui/plugin-form` translates between the two layers, and each key's handling is now recorded beside the mirrors.
18+
19+
The legacy `type: 'action'` node's zod schema was exported as `ActionSchema`, the name of `@objectstack/spec/ui`'s action metadata schema, with which it shares no vocabulary. It is now `CRUDActionSchema`, beside `CRUDDialogSchema` and `CRUDComponentSchema`; what it accepts is unchanged. Its TypeScript twin keeps the deprecated `ActionSchema` name in `@object-ui/types`.
20+
21+
**BREAKING** for code that imports the zod const by its old name, and for nodes that carry a value the spec refuses on the four keys.
22+
23+
- **Before.** `import { ActionSchema } from '@object-ui/types/zod'` resolved to the legacy action node's schema. `ObjectFormSchema.safeParse({ type: 'object-form', objectName: 'account', mode: 'create', contentLayout: 'tabs' })` succeeded and kept the value, which stacked the sections.
24+
- **Now.** The old import name is gone. The zod mirrors refuse `contentLayout: 'tabs'`, a non-boolean `confirmOnDiscard` or `loading`, and a non-string `locale`, at the key's own path, as the spec does.
25+
- **Fix.** FROM `import { ActionSchema } from '@object-ui/types/zod'` TO `import { CRUDActionSchema } from '@object-ui/types/zod'`. Write `contentLayout` as `'simple'` or `'tabbed'`.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/plugin-grid': patch
3+
---
4+
5+
refactor(plugin-grid): `ObjectGrid`'s row-height sync, column state and mobile affordances move into three sibling hooks
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Three adjacent blocks at the top of the `ObjectGrid` component move verbatim out of `ObjectGrid.tsx`. The effect that follows a changed `schema.rowHeight` goes to `useObjectGridRowHeightSync` (`objectGridRowHeight.ts`). The column-state persistence (the storage key, the `columnState` seed, the sync of a host-supplied `columnState` and `saveColumnState`) goes to `useObjectGridColumnState` (`objectGridColumnState.ts`). Pull to refresh and the card-view breakpoint go to `useObjectGridMobile` (`objectGridMobile.ts`). `ObjectGrid` calls each hook where its block stood, so its hook order, its stored column state and its rendered output do not change. No name is renamed, and the package entry is unchanged.
11+
12+
What to do: nothing.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/cli': minor
4+
---
5+
6+
feat(spec): a joined report's block headings and a master-detail field's inline grid heading are translatable
7+
8+
Clause-②: yes
9+
Metadata-protocol: added
10+
11+
Two authored, user-visible labels had no translation slot, so a zh-CN user read them in the authoring language and `os i18n check` could not see the gap:
12+
13+
- **A joined report's blocks.** The `reports` translation group declares `blocks`, keyed by the block's `name`: `reports.<report>.blocks.<block>.label` / `.description`. `translateReport` overlays them on each block of the served report (`GET /meta/report`), which is what the joined renderer draws above each block.
14+
- **A field's `inlineTitle`.** `FieldTranslationSchema` declares `inlineTitle`: `objects.<child>.fields.<field>.inlineTitle`, on the child object whose master-detail (or lookup) field declares the heading. `translateObject` overlays it on the served child object (`GET /meta/object`), which is what the console copies onto the parent's form as the inline grid's heading.
15+
16+
Both follow the packaged-base rule (ADR-0029 D9.2a): a string an org re-authored on top of the packaged declaration keeps its text. An authored `inlineTitle` with no entry keeps its authored text; the child object's plural label is never substituted for it.
17+
18+
`os i18n extract` scaffolds both keys and `os i18n check` counts them: a block's `label` / `description` under the report bucket, `inlineTitle` under the field bucket. A block without a `label`, or a field without an `inlineTitle`, is recorded without being demanded.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
---
4+
5+
fix(plugin-detail): `record:path` leaves out a stage the record's status option rules out, instead of ticking it
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
Ruling-ref: #26581 ruling `6442747364` letter A, with its guardrail
10+
11+
A path ticked every stage before the current one by position, so a stage the record could never have stood on read as passed: an order contract that skips legal review by type showed 「审查中 ✓」 after approval. The status field's option already says which records it applies to. Its `visibleWhen` means "offered only when TRUE (else omitted)", and the server refuses a write of a value whose predicate is false. So `record:path` now omits, on both the desktop and the mobile row, any stage whose matching option declares a `visibleWhen` that is FALSE for the record.
12+
13+
- Stages match options by value, so authored `stages[]` are filtered the same way as stages derived from the picklist.
14+
- A predicate that reads the acting user (`current_user`, `user`, `ctx` or `os`) hides nothing. It says who may pick the value, not which route the record takes.
15+
- A predicate that cannot be evaluated keeps its stage, as an option list keeps an option (render side fail-open, ADR-0137 D3).
16+
- The stage the record stands on is always drawn.
17+
18+
No new key and no new read: the predicate is evaluated against the record and object definition the record page already holds.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/formula': minor
3+
'@objectstack/objectql': minor
4+
---
5+
6+
feat(formula): `label(record.<field>)` reads a select field's option label in the reader's locale, so a record name composed in a formula renders `v2 · 清稿` to a zh-CN reader and `v2 · Clean` to an en-US one (#26608)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
A select field stores its option's value, so a formula `nameField` such as `'v' + string(record.version_no) + ' · ' + record.kind` printed `v2 · clean` to every reader. Wrap the field in `label()` and the name prints the label the select control shows that reader.
12+
13+
- **The function (`@objectstack/formula`).** `label(record.<field>)` is a new CEL stdlib function, advertised in `CEL_STDLIB_FUNCTIONS`. It takes exactly `record.<field>`; any other argument is refused when the expression compiles. It reads only data: the new `EvalContext.optionLabels` (field → stored value → label, type `EvalOptionLabels`), which the caller resolves in the reader's locale before evaluating. An empty value reads `''`; a value no option declares reads as itself; a field with no options, a multi-value select, and an evaluation whose context carries no `optionLabels` fail loudly.
14+
- **Where it answers (`@objectstack/objectql`).** The engine pins the map for every read and write it serves formula fields on: `find`, `findOne`, and the insert and update responses. The labels are the object's declared `options` with the i18n service's bundle laid over them by `translateObject`, the function `GET /meta/object/:name` serves the object with, in `ExecutionContext.locale` (the request's `Accept-Language`). The field-level key `objects.<object>.fields.<field>.options.<value>` wins over a shared picklist's `picklists.<picklist>.options.<value>`, and both over the declared label. No new keys and no new bundle. A read with no locale, an engine with no i18n service, and an i18n service that throws all read the declared labels.
15+
- **Stored values.** A stored computed field (a non-formula field with an `expression`) is computed once, on the write, so it stores the declared label whatever the writer's locale. Search never indexes formula fields, so what search indexes is unchanged.
16+
- **`setI18nService`** accepts the optional `getTranslations`, `getFallbackLocale` and `getDefaultLocale` the engine now reads. An `II18nService` already carries all three.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/plugin-auth': minor
4+
---
5+
6+
feat(spec,plugin-auth): a `demoStaff` entry names its line manager, and the development boot writes it
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
A `demoStaff` entry declares one optional key, `manager`: the `email` of another entry of the same `demoStaff` list, compared without regard to case. It is named by address because the address identifies an account everywhere the platform provisions one; a display name is a label a person can edit. Before, the entry was strict and refused the key, so a fresh demo's `manager` approval rung reached nobody until someone ran Setup → Users → Set Manager by hand.
12+
13+
`defineStack` (and so `os validate`) refuses, with `STACK_CROSS_REFERENCE_INVALID` 422, a `manager` that is the address of no entry, an entry that names itself, and managers that form a loop. A manager written as a display name is refused at the entry's path, saying it takes an address. `reportsTo`, `managerEmail`, `managerId` (and so `manager_id`) and `supervisor` are pointed to `manager`.
14+
15+
On an `os dev` boot, `@objectstack/plugin-auth` links each declared manager in a second pass, once every entry has its account and still before the application seed runs. Each link goes through the same checks as `POST /api/v1/auth/admin/set-user-manager`. A link is written only when the account has no manager, so a manager someone set by hand is left as it is on every later boot; a manager cleared by hand reads as no manager and is written again. A link that cannot be written (the manager's own entry was not provisioned, or the set-manager checks refuse it) is reported on the boot log under the subordinate's address. `DemoStaffOutcome` gains `managersLinked` and `managersKept`.
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/service-automation': patch
4+
'@object-ui/app-shell': patch
5+
---
6+
7+
feat(automation): a paused screen says whether another screen can follow, and the screen-flow runner reads Next or Submit and counts steps from it
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
A screen-flow wizard's primary button read Submit on every step, so a requester believed the whole wizard was submitted on step 1, and no step counter could be drawn: the paused screen carried nothing about the screens after it, and a flow can branch after any screen, so only the engine can tell.
13+
14+
- **`@objectstack/spec` — `ScreenSpec.progress`** (optional, `ScreenProgressSpec`) on the paused screen a run serves, and its wire spelling on `TriggerFlowResponseSchema`. `nextScreen` is `'none'` (no path out of this screen reaches another one: the last step), `'certain'` (every path pauses on another screen first) or `'possible'` (a branch, a fault path, a loop, a pause that serves no screen, or a `subflow` decides — the engine does not guess). `step` (1-based) and `stepCount` are present only when every path agrees on them. Nothing a metadata author writes changes.
15+
- **`@objectstack/service-automation`** fills it from the flow graph of the run when a `screen` node pauses (`readScreenProgress`). A screen that never pauses is not counted; one the run's caller may already have answered counts as possible, never certain. A screen inside a child run (`subflow`) carries only `nextScreen` when its own graph shows another screen, and no `progress` when it does not, because the parent may continue with screens of its own.
16+
- **`@object-ui/app-shell` — `FlowRunner`** labels the flat step's button `common.next` while another screen is certain or possible and `common.submit` on the last, and draws `form.stepOf` beside the flow's name only when both `step` and `stepCount` are served. A screen served without `progress` keeps Submit and no counter, as before.
17+
18+
What to do: nothing. A custom runner that reads the paused screen can read `progress` the same way.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/service-print': patch
3+
---
4+
5+
fix(service-print): a server print works under the console's default Content Security Policy
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Every server print (`POST /api/v1/print/records`, `printRecordToFile`) failed on a deployment that keeps the console's default CSP, answering "The print engine failed … page.waitForFunction: EvalError: Evaluating a string as JavaScript violates the following Content Security Policy directive". The Chromium driver waited for the print page with a string predicate, which playwright-core evaluates with `eval` in the page on every poll; the console's `script-src` carries no `'unsafe-eval'` unless `OS_PAGE_REACT_ENABLED` is on, so the second poll was refused.
11+
12+
The driver now hands the wait a function predicate. playwright-core evaluates a function's text once, inside the DevTools call that starts the wait, which the page's CSP does not govern, and each poll calls the function it produced. The wait means exactly what it meant: a root carrying `data-print-page` and an idle console (`window.__objectui.idle`), held for three polls in a row, polled every 100 ms within the render's one budget. The console CSP is unchanged.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/components': patch
3+
'@object-ui/types': patch
4+
---
5+
6+
fix(ui): a `destructive` action block confirms through the destructive button (#26665)
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The four action blocks (`action:button`, `action:icon`, `action:group`, `action:menu`) now forward their `variant` to the action runner. `@objectstack/spec` spells the value in two dialects, each on its own surface: the blocks' rows take the Button primitive's vocabulary plus `primary`, and `ActionSchema`, which the runner dispatches, takes `primary | secondary | danger | ghost | link`. The forward translates: `destructive` becomes `danger`, `primary` / `secondary` / `ghost` / `link` pass through, and `default` / `outline` forward nothing. So a block declared `variant: 'destructive'` with a `confirmText` now asks its question through the destructive confirm button, where before it asked through a neutral one.
12+
13+
`UIActionSchema.variant` (`@object-ui/types`) now takes the block rows' vocabulary by reference (`ActionButtonProps['variant']`), which adds `link`, a value those rows already accept. `danger` stays refused there, as the block rows refuse it.

0 commit comments

Comments
 (0)