Skip to content

Commit ace180a

Browse files
committed
Merge origin/main into claude/project-thread-0jat8q; regenerate the ADR index
docs/adr/README.md conflicted with main's regeneration (ADR-0143 now Accepted). It is resolved by node scripts/adr-index.mjs, not by hand: 145 records, 140 numbers, Accepted 127, Proposed 4. Claude-Session: https://claude.ai/code/session_01Utp6CbTZrFqRYNgjSaSbN5 Co-authored-by: Claude <noreply@anthropic.com>
2 parents 12cc227 + 8d79bbd commit ace180a

131 files changed

Lines changed: 6995 additions & 796 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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/components': minor
3+
'@object-ui/plugin-form': minor
4+
'@object-ui/console': minor
5+
---
6+
7+
fix(ui): a form view's `layout: 'horizontal'` draws each label beside its field, a tabbed form keeps it, and the form page draws `type: 'tabbed'` as tabs (#26843)
8+
9+
Clause-②: no
10+
Metadata-protocol: semantic
11+
12+
A form view's `layout: 'horizontal'` reached every console form but drew nothing different, and the form page ignored `type: 'tabbed'`:
13+
14+
- **`layout: 'horizontal'`** now places each field's label in a fixed column beside its control from the `sm` breakpoint up; below `sm` (a phone) the field stays stacked, label above control, as `vertical` draws it. The description and validation message stay under the control. `vertical`, and a form view with no `layout`, draw exactly as before. Before, the form renderer read the key only to align its action row.
15+
- **A tabbed form** (`type: 'tabbed'`) now keeps an authored `layout: 'horizontal'` on every tab. It used to draw its fields `vertical` whatever the form view said.
16+
- **The form page** (`/forms/:name`, and the public `/f/:slug`) now draws a `type: 'tabbed'` form view as one tab per section: `defaultTab` opens first, `tabPosition` places the strip, and a section hidden by its `visibleWhen` draws no tab. A submit carries every section's fields, the closed tabs' included, and a submit refused for a missing field opens the tab that holds it. A tabbed form view with a single section draws it stacked, as before. The page also draws `layout: 'horizontal'` rows. Before, it drew every section stacked and every label above its field.
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/service-i18n': minor
4+
---
5+
6+
feat(spec): a translation bundle may leave out `apps.APP.label`, so a module package can translate only the navigation ids it contributes into another package's app
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
A module that contributes navigation into an app it does not own (`manifest.navigationContributions`) can now ship those labels in its own bundle, carrying only `apps.<app>.navigation.<contributed id>.label`. Before, `apps.APP.label` was required, so that bundle failed `defineStack`, `os validate` (`translations.0.<locale>.apps.<app>.label`) and `tsc` (TS2741 on `TranslationData`), and the only way through was to restate the owning package's app label in every module pack.
12+
13+
- **What is accepted.** `apps.APP.label` is optional on every schema built from the app translation shape (`TranslationData`, `TranslationBundle`, `TranslationItem`, the platform and served bundles). `apps.APP.navigation.ID.label` stays required: it is the string being translated.
14+
- **What the runtime does.** Each package's bundle is deep-merged per locale, so the owning package's app label survives a module bundle that leaves it out, in either load order. The merge in `@objectstack/service-i18n` now also skips a key whose value is `undefined`: a bundle written in TypeScript or JavaScript can spell the optional label as `label: undefined`, and that bundle no longer erases the owner's label when it loads second.
15+
- **What lint does.** Nothing changes. `translation-target-unknown` already accepts, under an app the stack only contributes into, the contributed ids alone.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics): a `compareTo` dataset answer also returns the time buckets that are empty in both windows (#27111)
6+
7+
Clause-②: no — no input is refused or newly accepted and no export or type changes; a `compareTo` dataset answer gains the rows for the time buckets no record fell in, in either window, which it used to leave out
8+
Metadata-protocol: none
9+
10+
The empty time-bucket fill for a date-dimension series did not run when the selection carried a `compareTo`, so a period-over-period trend still dropped a month that held no record in either window, and a chart drew the months on either side of it as neighbours. `queryDataset` (and `POST /api/v1/analytics/dataset/query`) now fills a `compareTo` answer by the same rule as a plain series:
11+
12+
- **Which buckets.** The primary window decides: every bucket of the time axis's own `dateRange`, never the shifted one. A bucket that holds records in either window already comes back, with the same values as before; only a bucket empty in both windows is added. An axis with no `dateRange` of its own (the comparison anchored on another, window-only date) gets only the gaps between its first and last bucket filled.
13+
- **Their values.** The same values an empty group reads, for the measure and for its `<measure>__compare` column alike: `0` for `count` and `sum`; `avg`, `min` and `max` stay unset (the column is absent from the row). A derived measure is computed over them as over any row.
14+
- **Not filled.** As for a plain series: a selection with a `limit` or `offset`, an `order` on a measure or on a `__compare` column, a grid with two bucketed time dimensions, and a fill that would leave more than 2,000 rows answer exactly as before.
15+
16+
This supersedes the "or a `compareTo`" in the **Not filled** line of the #26893 note in the same release: a `compareTo` answer is filled too, as above.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@object-ui/plugin-kanban': patch
4+
---
5+
6+
fix(spec): `translateObject` serves `validations[].message` in the reader's language
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
Under a translated locale, the server's refusal of a write that breaks a validation rule carried the translated message, while `GET /api/v1/meta/object` served the same rule's authored message. A client explaining a rule from the served object, such as the board's tooltip on a lane a rule closes, showed one sentence in the authoring language and the refusal of the same move another, in the reader's.
12+
13+
`translateObject` now overlays each rule's `message` from `objects.OBJECT._validations.RULE.message`, the key the rule evaluator refuses a write with (`objectValidationMessageKey`). It walks the same locale chain as the other overlays, and a `conditional` rule's branches are translated by their own names. A rule the bundle has no message for, or one with no name, keeps its authored message, as the refusal does. Field, action and list-view translations are unchanged.
14+
15+
`@object-ui/plugin-kanban`: a source comment on the closed-lane tooltip now says the served message is translated. No behaviour change.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
fix(plugin-detail): the record page's in-place edit reads the stored row's field locks. `record:details` and `record:highlights` now ask the server's record-scoped field projection (`DataSource.getRecordFieldAccess`, `GET /data/{object}/{id}/field-access`) when the page's inline-edit session may edit, so a field permission whose `when` locks a field only from some state onward disables it only on the rows it names, as the record form's edit dialog already does. The answer is asked again after a write to the record. A data source without the method keeps the row-blind `/auth/me/permissions` answer, and a list view's column-wide inline edit keeps it too.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/app-shell': minor
4+
---
5+
6+
feat(spec)!: a form view's `type` is its layout only — `simple` (the default), `tabbed` or `wizard`; `split`, `drawer` and `modal` are retired, and the split-only `sections[].pane`, `splitSize` and `splitResizable` keys go with them
7+
8+
Clause-②: no (narrowing)
9+
10+
Metadata-protocol: narrowed (`FormViewSchema.type` refuses `split`, `drawer` and `modal`; `FormSectionSchema.pane`, `FormViewSchema.splitSize` and `FormViewSchema.splitResizable` are tombstones)
11+
12+
<!-- adr-0087: registered form-view-presentation-types-removed, ui-form-view-type-layout-only -->
13+
14+
**BREAKING** accept-set narrowing on the form view surface (`@objectstack/spec`), ADR-0050.
15+
16+
The model: `type` says how the form lays out its sections. Where a form opens is decided by whoever opens it, so `drawer` and `modal` were never the form's to declare, and a side-by-side split is a reader's preference since `navigation.mode` dropped `split`.
17+
18+
- **FROM → TO.** `type: 'split'` → `type: 'tabbed'` (one tab per section) or delete the value for `simple`; delete each section's `pane` and the form's `splitSize` / `splitResizable`; declare `subforms` to edit child records beside the parent. `type: 'drawer'` or `type: 'modal'` → delete the value for `simple`, or write the layout you want. A list opens a clicked record as its `navigation.mode` says (the drawer by default), and the console's New and Edit entries open the record form themselves, sized by the form's `modalSize`.
19+
- **The one-line fix.** Delete the retired value and the three split-only keys. `os migrate meta --from 17` lists the edits for existing sources, and `--write` applies them. A stored `sys_metadata` row is converted when it is read (the `form-view-presentation-types-removed` conversion), so a form view saved with one of the three values keeps loading as a `simple` form.
20+
- **What the parse says.** Each retired value is refused by name with its prescription, for example "'modal' was removed from the form view `type` enum in @objectstack/spec 18.3.0 (ADR-0050) — …", 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. `pane`, `splitSize` and `splitResizable` are refused on every layout. TypeScript rejects each at the authoring site. A misspelt value keeps zod's own message.
21+
- **Unchanged.** `splitDirection`, `drawerSide`, `drawerWidth` and `modalSize` stay declared: the console reads `modalSize` for its record dialog, and the React `ObjectForm` block contract still takes the other three from this schema until its renderer follows.
22+
23+
The console (`@object-ui/app-shell`): Studio's New View form offers the three form layouts only. It offered all six, and a form view created with `split`, `drawer` or `modal` would now be refused when saved.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
fix(studio): the dataset inspector stores no guessed measure currency
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Picking the "Currency" display format for a dataset measure with no currency chosen used to store `currency: 'USD'`, and the currency picker pre-selected USD. A measure's own `currency` outranks the source field's fixed currency and the tenant default, so a measure saved that way showed `$` on a tenant whose default is another currency. The inspector now guesses nothing:
11+
12+
- **Over a `currency` field** the picker opens on "Inherit — field or organization default" and the saved measure carries no `currency` key, so it resolves the field's fixed currency, then the tenant default. A numeral format with no code over such a field reads back as Currency · inherit.
13+
- **Over any other field** there is nothing to inherit, so the picker offers no inherit row and asks for a code; the format kind stays Currency until one is picked.
14+
- **A stored `currency`**, `'USD'` included, is explicit authoring and is left as written: no migration, no rewrite on load.
15+
16+
`@object-ui/i18n` gains `engine.inspector.dataset.currencyInherit` and `engine.inspector.dataset.currencyChoose` in the studio namespace (en, zh).
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/metadata-protocol': minor
4+
'@objectstack/rest': patch
5+
'@object-ui/data-objectstack': patch
6+
'@object-ui/app-shell': patch
7+
'@object-ui/i18n': patch
8+
---
9+
10+
feat(spec): a draft's per-key provenance records writes made through the MCP build door, and the OAuth client that made them
11+
12+
Clause-②: yes (widening)
13+
Metadata-protocol: added
14+
15+
A pending draft's `draftChanges` (the strict draft read's per-key provenance) could tell Studio's AI copilot from a person in Studio and from any other caller, but not an AI agent outside the console building through a deployment's MCP build door. Such a write was recorded as `api`, and nothing said which agent it was.
16+
17+
- **Spec.** `MetadataAuthoringSurfaceSchema` gains `mcp`: an AI agent outside the console writing on a person's behalf through the MCP build door. A new `MetadataAuthoringClientSchema` (`{ id, name? }`) is the OAuth client the person consented to. A `draftChanges` entry carries it as an optional `client`, present only beside `by: 'mcp'`. `SaveMetaItemRequestSchema` gains an optional `client`, stated only by the MCP build door, in-process, from the token it verified.
18+
- **The save.** A draft save with `surface: 'mcp'` stamps `by: 'mcp'` and its `client` on every key path it changes. A `client` beside any other surface, or of the wrong shape, is refused `400 INVALID_REQUEST` before anything is written. `actor` is still the person the agent writes for.
19+
- **The HTTP doors.** `?surface=mcp` is refused `400 VALIDATION_ERROR`, as `?surface=copilot` is: only the MCP build door may state it. No HTTP door reads a `client`.
20+
- **Studio.** The properties' draft chip and source line mark an `mcp` write as they mark the copilot's, and name the client: 「由 MCP 客户端「Claude」在 2 分钟前作为草稿加入」 / "Added as a draft by the MCP client "Claude" 2 min ago". A client that registered no name is shown as an unnamed MCP client, never by its id. The pending-changes sheet's drill-in lists which AI tools drafted part of the item, in all ten console locales.
21+
22+
Nothing to do for existing callers: a save that states no surface is still recorded as `api`, and an entry without `client` reads as before. The MCP build door starts stating `mcp` and its client in a later change.

‎.claude/skills/pm-dispatch/references/platform-readings.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,9 @@
385385
- 强制档不因临缺授权降档:临缺非退役,退役唯维护者裁;不达档起隔离达档子代理或等档。
386386
- 本车道强制多是过宽的回忆:`dispatch-gates.mjs --tier PATH` 逐路径现推,路径线是下限非放行。
387387
- 该脚本只答自己那棵树:姊妹仓路径回 absent from this tree,姊妹仓的档位与条款②只能手推。
388-
- required checks 的名单是每仓事实,objectstack 九个:
388+
- required checks 的名单是每仓事实,objectstack 十个:
389389
`TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` · `Build Core` · `Lint UI` ·
390-
`Governed Surface Queue Guard` · `Type Check UI` · `Test UI` · `Build & E2E UI`。
390+
`Governed Surface Queue Guard` · `Type Check UI` · `Test UI` · `Build & E2E UI` · `Live E2E UI`。
391391
- ⭐ 本表的 `mustName` 声明 `statesTheSet` ⇒ 必须逐字等于注册表,加注册行不跟改本表即红。
392392
- ⇒ 门绿即名单逐字齐全的读数;⛔ 但数量词未钉:实测 七→六 照绿,只能手跟改。
393393
- ⛔ 聚合命令同样不作判定:`check:type-check-debt` 可在包级 typecheck 绿时红。

0 commit comments

Comments
 (0)