Skip to content

Commit 814230c

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-0i0ziw
# Conflicts: # ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx
2 parents afe8f6f + 2fe4a74 commit 814230c

166 files changed

Lines changed: 4174 additions & 2094 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/app-shell': patch
3+
'@object-ui/console': patch
4+
---
5+
6+
fix(app-shell): a record page on a deployment without the approvals service no longer logs the record-lock read's 501
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
On a deployment that composes no approvals service, such as a fresh `npm create objectstack` scaffold, every record page asks the server whether an approval locks the record, and the server answers `501 NOT_IMPLEMENTED`. That answer is designed: it means there is no lock service, so no approval can lock the record, and the page already treated it that way. But the read went through the SDK client, which logs every non-2xx response as an error before its caller sees it. So each record open printed `HTTP request failed: GET /api/v1/approvals/records/OBJECT/ID/lock -> 501 [NOT_IMPLEMENTED]` in red, which reads like a broken server.
12+
13+
What changes:
14+
15+
- The record page reads the lock directly, the same way it already reads the approval requests, so no client logger runs first. A 501 is read silently.
16+
- After a 501, the page does not ask again for any record until the page is reloaded. The browser's own network line for that first request still appears once, because the server answers the approval-requests read `200` with an empty list and the page cannot tell the service is missing until it asks.
17+
- Any other failure of the lock read still logs, through the same logger the SDK client used: `HTTP request failed: GET URL -> STATUS [CODE]`, with a 404 still demoted to a debug line. The next record open asks again.
18+
19+
What to do: nothing. Nothing a flow, page or object declares changes.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/types': minor
4+
'@object-ui/react': minor
5+
'@object-ui/components': minor
6+
'@object-ui/plugin-calendar': minor
7+
'@object-ui/plugin-gantt': minor
8+
'@object-ui/plugin-grid': minor
9+
'@object-ui/plugin-kanban': minor
10+
'@object-ui/plugin-list': minor
11+
'@object-ui/plugin-map': minor
12+
'@object-ui/plugin-timeline': minor
13+
'@object-ui/plugin-tree': minor
14+
'@object-ui/plugin-view': minor
15+
'@object-ui/plugin-detail': minor
16+
'@object-ui/app-shell': minor
17+
---
18+
19+
feat(spec,ui)!: a record opens one way per object — `navigation.mode` keeps `drawer` (now the default), `page`, `new_window` and `none`; `modal`, `split` and `popover` are retired, and a list page's `recordAction` drops `modal`
20+
21+
Clause-②: no (narrowing)
22+
23+
Metadata-protocol: narrowed (`navigation.mode` refuses `modal`, `split` and `popover`; `interfaceConfig.recordAction` refuses `modal`; an omitted `navigation.mode` defaults to `drawer` instead of `page`)
24+
25+
<!-- adr-0087: registered navigation-mode-overlay-values-removed, ui-record-open-overlay-modes-retired -->
26+
27+
**BREAKING** accept-set narrowing and default change on the record-open surface (`@objectstack/spec`), with the console following it in the same release.
28+
29+
The model, ruled by the maintainer: an object has one default way to open a record — the drawer, which expands to the full page (the same record page at two widths) — and `page` only for an object that declares it. A centred dialog and a popover were extra surfaces for the same record, and the side-by-side pane is each reader's own choice, not something an author fixes for every reader.
30+
31+
- **FROM → TO.** `navigation.mode: 'modal'`, `'split'` or `'popover'` (on a list view, an object's `listViews` entry, or a page list block: `list-view`, `object-grid`, `object-kanban`, `object-calendar`, `object-map`, `object-gantt`, `object-tree`, `object-timeline`) → delete the value; the block takes the `drawer` default. `interfaceConfig.recordAction: 'modal'` → delete the value; the default is `drawer`. Declare `page` only for an object whose record should open as a full page.
32+
- **The one-line fix.** Delete the retired value. `os migrate meta --from 17` lists the edit for existing sources, and `--write` applies it. A stored `sys_metadata` row is converted when it is read (the `navigation-mode-overlay-values-removed` conversion), so a view saved from Studio with one of the three values keeps loading and opens the drawer.
33+
- **What the parse says.** Each retired value is refused by name with its prescription, for example "`navigation.mode: 'modal'` was removed in @objectstack/spec 18.3.0 (ADR-0087 D2) — …", at `defineStack` as `STACK_SCHEMA_INVALID` / 422. TypeScript rejects each at a `NavigationMode` position. A misspelt mode keeps zod's own message.
34+
- **The default.** A navigation block written without `mode` parses to `drawer` (it parsed to `page`). The console never honoured that `page` for a list with no navigation block, because it guessed the surface from the object's field count; that guess is retired too. A list of an object with no authored navigation opens a clicked record in the drawer, whatever its field count, and a mobile viewport still pages. An artifact or stored row that a released toolchain parsed carries the old `page` written out and keeps opening as a full page; delete it to take the drawer.
35+
- **The split pane.** It is now a reader's preference, read by the console's object page from the browser (`os:record-open-mode`, `drawer` or `split`, default `drawer`). It applies only to a record that opens in the drawer; an object or view that declares `page` keeps its declaration.
36+
37+
The console (`@object-ui/*`): `useNavigationOverlay` defaults a block without `mode` to `drawer` and treats `drawer` as the one overlay mode. `NavigationOverlay` keeps the drawer face and the split face the console's object page draws from the preference; its `modal` and `popover` faces, the `popoverTrigger` / `popoverAnchorRef` props and the `useOverlayAnchor` export are removed, because no caller can reach them. The grid, kanban, calendar, gantt and tree renderers drop their `split` and `popover` plumbing. The registered `object-view` renderer opens records in the drawer (a page on a mobile viewport) instead of paging a field-heavy object. The Studio list-view inspector offers the four live modes, and the list page's `recordAction` the three live values.
38+
39+
Migration for code that imported the removed component API: drop `popoverAnchorRef` / `popoverTrigger` from a `NavigationOverlay` call and any `useOverlayAnchor()` call; pass `mode="split"` with `mainContent` for a side-by-side pane.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
fix(app-shell,i18n): the Studio hook editor grants a sandboxed-JS body its capabilities
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A hook body that calls `ctx.api`, `ctx.log` or `ctx.crypto` needs the matching token in `body.capabilities`, or the sandbox throws when the hook runs. The Studio hook editor had no control for that list, so a hook that read data could only be fixed through the metadata API.
11+
12+
- **Studio (Data › Hooks, and the metadata admin's hook editor).** A sandboxed-JS body now shows a "Capabilities" checkbox per `HookBodyCapability` token (`api.read`, `api.write`, `api.transaction`, `crypto.uuid`, `log`), each with the `ctx` call it unlocks. Ticking one writes `body.capabilities`; unticking the last writes `[]`, the spec's default. An expression body shows no control, and switching a body to an expression drops its grant and its `timeoutMs` / `memoryMb` limits, since an expression body refuses those keys. A stored token the spec does not list stays on screen, flagged, until it is unticked. Nothing is inferred from the body's source.
13+
- **`@object-ui/i18n`.** The `studio` namespace gains `engine.inspector.hook.capabilities`, `engine.inspector.hook.capabilitiesHint` and `engine.inspector.hook.capabilityUnknown` in `en` and `zh`.
14+
15+
No metadata key is added or changed, and the sandbox's capability check is as strict as before. What to do: nothing.
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 i18n skill's group table names `fields.{field}.inlineTitle` and `reports.{report}.blocks.{block}.{label,description}`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`skills/objectstack-i18n/SKILL.md` did not name two keys the 18.2 translation bundle already accepts and the runtime already serves, so an AI author writing a bundle from the skill left a master-detail field's inline child-grid heading and a joined report's block headings in the authoring language. The `fields.{field_name}` row now names `inlineTitle` (addressed on the child object that owns the field; untranslated it keeps its authored text, with no fallback to the child's plural label), the `reports` sentence names `reports.{report}.blocks.{block_name}.{label,description}` (keyed by the block's required `name`), and one example row each, spelled as the shipped `app-crm` and `app-showcase` bundles spell them, follows the group table. The eval file gains one prompt covering both keys.
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-platform skill teaches the `demoStaff` `manager` key — another entry's email, written as `manager_id` by the development boot (#26764)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The Demo accounts section of `objectstack-platform/SKILL.md` now names a line manager in its `demoStaff` example and says how the key behaves: `manager` is another entry's `email`; `os dev` writes `sys_user.manager_id` itself once every demo account exists and keeps a manager someone set by hand, so no seed hook writes it; a display name, an address no entry has, the entry itself or a loop is refused at load. One eval case in `evals/config-plugins-ops.json` expects the key and forbids a seed writing `manager_id`. Three restatements elsewhere in the skill were cut against the addition, each rule keeping its other home in the file; the remainder raises the file's token ceiling by the measurement.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(app-shell): the permission-set editor shows the Export column, and Assigned Users no longer covers the grid
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The permission-set editor listed Create, Read, Edit, Delete, Transfer, View All and Modify All, but not Export, so a set that grants `allowExport` looked the same as one that does not. On the Setup page, the Assigned Users panel sat in a strip pinned under the grid, up to 35% of the window tall, so a laptop showed about five object rows and a phone none.
12+
13+
- **Export is a column** (`E`, "Export records"; Chinese: 导出记录) between Delete and Transfer, the order the position summary uses, on the Setup page and in Studio's Access Objects tab. It shows a stored grant and toggles it like Transfer.
14+
- **The bulk buttons leave Export as it is.** Export is an opt-in, high-privilege grant, so R, CRUD and All neither set nor clear it, and the All button's tooltip says so. None still empties the whole row.
15+
- **Assigned Users scrolls with the grid**, after it and the advanced facets, so the grid keeps the page's height. Measured on the Setup page at 1440×900: 9 object rows on screen.
16+
- **On a phone the page scrolls as one.** Below the `md` width the chrome above the grid (header, notices, capabilities, filter, legend) is taller than the screen, so the page body scrolls everything together instead of leaving the grid a box about 120px tall; at 390×844, 12 rows fit once the grid is scrolled up. The filter bar wraps there too.
17+
- No key is declared anywhere, and nothing a set already stores changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/runtime': patch
3+
'@objectstack/rest': patch
4+
---
5+
6+
fix(runtime)!: the runtime dispatcher's `GET /data/:object/:id` refuses a query parameter other than `select` / `expand` with `400 VALIDATION_ERROR`, as `RestServer` does, instead of dropping it and answering `200`
7+
8+
Clause-②: yes (narrowing) — the dispatcher's by-id read refuses a query parameter it dropped before, and `@objectstack/rest` gains one export (`DATA_RECORD_READ_PARAMS`) so both hosts read one set.
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling or stored shape is added, removed, renamed or re-shaped, and no exported declaration is removed or narrowed. `@objectstack/rest` gains one export, `DATA_RECORD_READ_PARAMS`, the closed set its own route already asked. What narrows is one runtime door's query accept set: a parameter the dispatcher's by-id read dropped now answers a located `400 VALIDATION_ERROR`, and the remedy is in the request a caller sends, never a rewrite of a package source or a stored row. The other categories are closed on facts: both packages publish (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
12+
13+
**BREAKING** accept-set narrowing at one runtime dispatcher door, the by-id record read served by the `@objectstack/hono` catch-all and `dispatcher-plugin`.
14+
15+
- **Before.** `GET /api/v1/data/:object/:id?selct=name` answered `200` with every column on the dispatcher: the branch kept `select` and `expand` and silently dropped every other name. `RestServer` refused the same request `400 VALIDATION_ERROR`, so a typo was ignored on one host and refused on the other.
16+
- **Now.** The dispatcher asks the route's closed set, `DATA_RECORD_READ_PARAMS`, imported from `@objectstack/rest`, the same set `RestServer` asks, and refuses any other name `400 VALIDATION_ERROR` with `RestServer`'s sentence: it names each refused parameter and lists `expand, select`. The refusal comes before the read, so it is the same answer for an existing record, a missing id and a row the caller may not read.
17+
- **Unchanged.** `select` and `expand` are read as before. An object the API does not expose is answered its exposure refusal first, before the query is judged: the dispatcher's `404 OBJECT_API_DISABLED` with or without the unknown name, and `RestServer` likewise asks its closed set only after `enforceApiAccess`. The other `/data` routes are not touched.
18+
19+
**The remedy.** Send only `select` and `expand` to the by-id read. `fields` and `populate` are not synonyms on this route (#8039): spell them `select` and `expand`.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec)!: `QueryTransportParamsSchema.select` / `$select` and `GetDataRequestSchema.select` declare the projection the `findData` and `getData` doors accept, instead of any string or list (#26909)
6+
7+
Clause-②: no (narrowing) — the published body-side declarations of the list door's projection (`select`, `$select`, reached through `QueryWithTransportSchema` and `FindDataRequestSchema.query`) and the by-id request's `select` refuse a value naming no field, which they admitted before. The doors already refused it with a 400; nothing is widened and no key is added, removed or renamed.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped and no metadata type or stored shape moves: these are transport request declarations, never stored, and the values they stop admitting are values the Data API doors already refused. A caller names at least one field or leaves the parameter out; there is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing of published transport declarations (`@objectstack/spec/data`, `@objectstack/spec/api`), read by the `POST /data/:object/query` body gate, codegen, the reference docs and clients.
13+
14+
- **Before.** `select` / `$select` admitted any string or list: a blank, whitespace-only or comma-only string, `[]`, and a list with a blank name. `GetDataRequestSchema.select` admitted `[]` and blank names.
15+
- **Now.** A string must name at least one field after the door's own split and trim (published as the pattern `[^\s,]`, the same rule `HttpFindQueryParamsSchema.select` states since #26817). A list must hold at least one name, and no name may be blank. `HttpFindQueryParamsSchema.select` and `GetDataRequestSchema.select` read these rules off `QueryTransportParamsSchema.select` rather than restating them.
16+
- **`POST /data/:object/query`.** That route parses its body with `FindDataRequestSchema`, so an empty `select` / `$select` is now refused at the body schema: still `400`, still before any read, but `VALIDATION_FAILED` with `fields[0].field` naming `query.select` / `query.$select`, where it was `VALIDATION_ERROR` from the list door. A list with a blank name (`["name", ""]`) is likewise `400 VALIDATION_FAILED` at the schema, where it was `400 INVALID_FIELD` (an unknown field). This is how that route already answers a malformed `limit` or `count`.
17+
- **Unchanged.** `GET /data/:object` and `GET /data/:object/:id` still answer an empty `select` with `400 VALIDATION_ERROR` naming it; a named projection (`name,email`, `["name", "email"]`, spaces and empty members between commas trimmed by the door) and an explicit `null` (a withdrawal) are accepted as before.
18+
- **Who sees it.** A caller of `POST /data/:object/query` that branches on the `VALIDATION_ERROR` code for an empty projection, code that parses a query bag with these schemas and sent an empty projection, or a generated client that trusted the wider declaration.
19+
20+
**The remedy.** Send `select` naming at least one field, or leave it out to read every column.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/client': patch
3+
---
4+
5+
fix(client): `data.find` refuses a `select` that names no field before the request, with the list door's own answer, instead of sending `?select=`
6+
7+
Clause-②: no — the calls refused are exactly the ones the list door already refuses (`400 VALIDATION_ERROR`); the caller gets the same `code`, `httpStatus` and message, only without the round trip. Nothing is widened and no call that was served is refused.
8+
Metadata-protocol: none
9+
10+
An empty array is truthy, so `client.data.find(object, { select: [] })` (or `{ fields: [] }`) put `?select=` on the wire and came back as a `400 VALIDATION_ERROR` from a door the caller cannot see. Both `ObjectStackClient.data.find` and `environment(id).data.find` now refuse that call, and a `select` whose every member is blank, before any request:
11+
12+
- The error carries `code: 'VALIDATION_ERROR'`, `httpStatus: 400` and the door's sentence (`Query parameter 'select' names no field; …`), so an existing `err.code` branch fires the same way.
13+
- The parameter is never dropped: an absent projection reads every column, which is the widening the door refuses.
14+
- A `select` naming at least one field is sent exactly as before, blank members included (the door keeps the named ones).

0 commit comments

Comments
 (0)