Skip to content

Commit d99a631

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-g5q3wk
2 parents adfc306 + a20824f commit d99a631

313 files changed

Lines changed: 19587 additions & 6946 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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os lint` tells an author that a field's `externalId: true` does nothing
6+
7+
Clause-②: no (a liveness-ledger re-grade: no schema, accept set, served body or error code moves; only the advisory `liveness-dead-property` warning on an authored `externalId: true` starts, and the flag's description is reworded)
8+
Metadata-protocol: none
9+
10+
The shipped liveness ledger (`liveness/field.json`) graded a field's `externalId` flag `live` on the strength of one renderer module that was deleted for having no importers. Re-measured across `packages/**` and `ui/**`, nothing reads the flag: the Studio field inspector edits it and a metadata summary copies it, and no upsert matches on it. The row is now `dead`, so `os lint` warns on a field that sets `externalId: true` (the default `false` is not warned). The flag's schema description, and the reference pages generated from it, no longer say it drives upserts.
11+
12+
- **What to do.** Remove the flag. To match seed records on a field, name the field in the seed dataset's `externalId` (`name` when it is left out). For an import, name it in the mapping's `upsertKey`.
13+
- **Unchanged.** Parsing (the key is still accepted, default `false`), seed loading, imports and every upsert.
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 rest of the Studio inspectors and the package, form and editor strings move into the `studio` namespace
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Third slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). The remaining `engine.inspector.*` keys and every `engine.packages.*`, `engine.form.*` and `engine.edit.*` key (551 in `en`, 560 in `zh`) now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged. The nine `engine.packages.form.help.*` rows stay `zh`-only: their English is the producer's own help text, read through `tOptional`. 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 earlier slices. No export changes.
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 column generator, column post-processing and Actions column move into three sibling modules
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The last three blocks of the move-only split leave `ObjectGrid.tsx` verbatim. `generateColumns`, which turns the authored columns or the object's fields into column drafts, goes to the hook `useObjectGridGenerateColumns` (`objectGridGenerateColumns.tsx`). The column post-processing (field-type enrichment, the fold onto the declared column type, and the user's persisted widths and order) goes to `resolveObjectGridPersistedColumns` (`objectGridPersistedColumns.ts`). The Actions column and the pinned column sets go to `resolveObjectGridActionsColumn` (`objectGridActionsColumn.tsx`). `ObjectGrid` calls each where its block stood, so its hook order, its columns 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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/core': patch
4+
---
5+
6+
fix(spec): `TranslationItemSchema.name` no longer tells authors to name a translation item after its locale tag
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The `name` describe said a translation item was "conventionally the locale code (`zh-CN`)", but the metadata save door's item-name grammar refuses `zh-CN` (uppercase and a hyphen), so an author or agent following the describe got a 400 `INVALID_REQUEST` on the first save. The describe now says a translation item is named in snake_case like every metadata item (e.g. `zh_cn`), with the BCP-47 tag in `locale`. The save door and the runtime sync's read-side name fallback for rows stored before `locale` became required are unchanged; the sync's skip warning now points authors at `locale` only.
12+
13+
`@objectstack/core`: the authored-translation sync's no-locale skip warning now tells authors to set `locale` to the item's BCP-47 tag (e.g. `zh-CN`), instead of suggesting they name the item after it.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/platform-objects': minor
4+
'@objectstack/service-messaging': minor
5+
---
6+
7+
feat(spec)!: a single-word language name (`Chinese`, `English`, `Japanese`) is refused as a locale
8+
9+
The 18.x locale shape refused `zh_CN`, a padded value and a multi-word name such as `Chinese (Simplified)`, but its 2–8 letter language subtag still admitted any single word of that length. An author who wrote `locale: 'Chinese'` got a green save, and the strings were stored under a locale no session ever asks for, so they never rendered. The refusal text already said a language name is refused.
10+
11+
The language subtag is now 2–3 letters, the ISO 639 codes every tag in use starts with (BCP-47 reserves 4 letters and registers no 5–8 letter language). Everything else in the shape is unchanged: hyphen-separated 1–8 letter-or-digit subtags (`en`, `zh-CN`, `zh-Hans-CN`, `es-419`, `yue-HK`), and case is not judged. The one shape moves in all three places that hold it:
12+
13+
- `LocaleSchema` (`@objectstack/spec/system`): a `translation` item's `locale`, translation bundle keys, `i18n.defaultLocale` / `fallbackLocale` / `supportedLocales`, a seed dataset's `locale` scope and the seed loader's `locale`.
14+
- `SYS_USER_LOCALE_TAG_PATTERN` (`@objectstack/platform-objects/identity`): the `locale_bcp47_shape` rule on a user's own `sys_user.locale` column.
15+
- `LOCALE_TAG_SHAPE` (`@objectstack/service-messaging`): the read side that picks a recipient's notification locale, byte-identical to the column rule.
16+
17+
Clause-②: no (narrowing) — the locale shape refuses values it used to accept. Nothing is widened.
18+
19+
Metadata-protocol: narrowed (a locale whose language subtag is 4–8 letters, in practice a language name, is refused on translation items, bundle keys, the i18n configuration and seed datasets)
20+
21+
<!-- adr-0087: not-required (already-registered translation-locale-non-tag-refused) the 18.x D3 entry already prescribes rewriting a language name by hand, and its conversion already lists such a stored row as a TODO through LocaleSchema; this diff narrows the same shape and corrects that entry's subtag length -->
22+
23+
**BREAKING** for an author or user who wrote a language name as a locale.
24+
25+
- **Before.** `defineTranslation({ name: 'qa_x', locale: 'Chinese', … })` parsed and its strings never rendered; `PATCH` of a user's `locale` to `Japanese` was stored, and that user's notifications fell to the delivery ladder's floor.
26+
- **Now.** The value is refused at `locale` (or at the bundle key) with a message naming the tag shape, and the user's write is refused by the column's `locale_bcp47_shape` rule.
27+
- **Fix.** Write the tag, not the name: `Chinese` → `zh-CN`, `English` → `en`, `Japanese` → `ja-JP`. A language name has no single tag it can have meant, so `os migrate meta --stored` lists a stored translation row as a TODO (D3 entry `translation-locale-non-tag-refused`) and you rewrite it by hand. A user whose stored `locale` is already a name such as `Japanese` keeps loading: their notifications fall to the deployment default, and saving that value again is refused until it is replaced with a tag.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): `os dev`'s in-place organization-ownership upgrade names what stopped it, and the exact command that carries on
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
When the organization-ownership boot guard refuses a development database, `os dev` copies it and runs the ceremony on it in place. When that upgrade did not finish, it could exit with 「The upgrade did not run to a report」 and no cause.
11+
12+
What changes:
13+
14+
- The ceremony's own refusal is printed in its own words, also when the terminal forces colour (`FORCE_COLOR`): the refusal lines are read with colour stripped, and the `✗` glyph is no longer printed twice.
15+
- An apply that stopped with no refusal of its own (an uncaught error, a crash, a signal) says how it ended and prints the end of its log, without the kernel's boot narration.
16+
- A report file still holding the apply's reservation text, or one that does not parse, is read as "no report", with the cause, never as "a blocker (above)" with nothing above. A blocked report that names no blocker prints the log's cause too.
17+
- A stale `organization_id` the plan gives the column-drop fate is upgraded as before, but no longer silently: before the apply runs, `os dev` prints per table how many stamped values the drop discards, and names the copy that keeps them.
18+
- Each failed upgrade ends on the exact command that carries on: `os migrate organization-ownership --dev --database-url URL --apply --plan-file PLAN --backup-taken`, with the database and the plan the upgrade wrote.
19+
20+
The copy taken before the upgrade is kept, as before. A development database with a stale `organization_id` column on `sys_flow_credential` and `sys_secret` (objects that no longer declare it) is upgraded and starts under `os dev`, now pinned by a test. The production ceremony is unchanged.
21+
22+
What to do: nothing.
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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/cli': minor
4+
'@objectstack/platform-objects': minor
5+
---
6+
7+
feat(spec): a greyed action says why — `disabledReason` beside `disabled`
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
`ActionSchema` gains one optional key, `disabledReason`: the sentence a greyed action shows to say why it is unavailable, beside the `disabled` predicate that greys it. An action that does not declare it parses exactly as before, and the console keeps its default reason, "Not available for this record".
13+
14+
```ts
15+
defineAction({
16+
name: 'submit_for_approval',
17+
label: 'Submit for approval',
18+
objectName: 'clm_contract',
19+
disabled: "record.legal_review_status != 'approved'",
20+
disabledReason: 'An approved legal review is required before the contract enters approval.',
21+
});
22+
```
23+
24+
- **An `I18nLabel`.** A plain string or an inline locale map (`{ en, 'zh-CN' }`). A blank string is refused at parse: `''` or whitespace only. Omit the key to keep the default. The locale-map arm is the `InlineLocaleMapSchema` every `I18nLabel` takes, so a map is not checked for blank values. `disabledMessage`, `disabledTooltip`, `disabledHint` and `disabledText` are renamed onto it.
25+
- **Translated with the copy keys.** The slot is `objects.OBJECT._actions.ACTION.disabledReason` (`globalActions.ACTION.disabledReason` for an action with no `objectName`), beside `confirmText` and `successMessage`. `translateAction` overlays it, and `os i18n extract` (`@objectstack/cli`) scaffolds it for an action that declares one and seeds nothing for one that does not.
26+
- **Display only.** It does not change what `disabled` evaluates to and is never read as a condition: `os validate` checks `disabled`, not `disabledReason`. It is not access control either. A call that reaches the server anyway is refused by the action's own rules (its body or hook, `requiredPermissions`), never by this text.
27+
- **Studio.** The action editor's Placement section gains a "Disabled Reason" row under "Disabled", translated in `@objectstack/platform-objects` for zh-CN, ja-JP and es-ES.
28+
29+
Nothing renders the key yet. The console's greyed-action surfaces (the record header, the record section bar, the declared actions bar and the four `action:*` renderers) read it in the follow-up change. Until then the liveness ledger marks it `planned` with `authorWarn`, so `os validate` warns an author who declares it that nothing shows it yet.

0 commit comments

Comments
 (0)