Skip to content

Commit b5b1eab

Browse files
committed
Merge origin/main into claude/issue-27908-action-audit-surface
Resolves sys_audit_log's action select: main labels every option (#27855), and this branch adds action_invocation, so the new value joins main's labelled list as { label: 'Action Invocation', value: 'action_invocation' }, with the en bundle's option label to match. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
2 parents c6228a9 + 9f13ee8 commit b5b1eab

330 files changed

Lines changed: 15338 additions & 2550 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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
fix(plugin-audit)!: the compliance ledger's deployment-level rows (no `tenant_id`) are served to the platform administrator alone, in every tenancy posture, `single` included (#26750)
7+
8+
Clause-②: no (narrowing) — under `single`, a non-system reader of `sys_audit_log` that is not the platform administrator (an organization administrator, the holder of a set that grants the ledger read, a holder of `view_all_audit_log`) is no longer served the ledger rows that carry no `tenant_id`; under a wall, the shipped sets' row scope already excluded them. Nothing is widened.
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export is added, removed or renamed: one runtime read rule on sys_audit_log changes which stored rows a reader who is not the platform administrator is served, and the view_all_audit_log description gains one sentence. There is nothing for a migration to rewrite and no tombstone. -->
12+
13+
**BREAKING** read narrowing on `sys_audit_log` (`@objectstack/plugin-audit`), on every engine read of the ledger: the data API's list, by-id, count and aggregate, and the Setup views and record history built on them.
14+
15+
- **Before.** The ledger has no organization column (ADR-0131 D7); the organization a row is about is the attribution field `tenant_id`, and a row about a deployment-level action carries none: a GLOBAL settings change, a user-import run, a platform-admin standing change, a user account the platform administrator creates or resets through the admin endpoints, a system write about an object with no organization column. Under a wall the shipped row policy `sys_audit_log_org` kept those rows from every organization reader. Under `single` no wall is enforced and that policy is stripped, so every reader whose sets grant the ledger read was served them, and a holder of `view_all_audit_log` was served them past the parent-record gate too.
16+
- **Now.** In every posture, a ledger row with no `tenant_id` is served only to the platform administrator: the carried `PLATFORM_ADMIN` posture rung, which the unscoped `admin_full_access` grant derives. Every other non-system reader is served only the rows about an organization. Which organization's rows stays the row policy's answer, unchanged. The same holds under a wall for a set that authors its own ledger row policy or grants the superuser read bypass on the ledger: its holder is not the platform administrator.
17+
- **Unchanged.** The rows stay stored. A system-context read and a context-less programmatic read still return every row. The platform administrator reads every row as before, on a request that carries the `PLATFORM_ADMIN` rung, which the request's authorization context resolves for the administrator. A context built outside that resolution carries no rung and is served the rows about an organization only (fail-closed, never wider): for example, an automation run with `runAs: 'user'` whose run context seeds no posture. The parent-record gate and the field-level redaction of the snapshots are unchanged. A walled deployment on the shipped sets serves every reader the same rows as before.
18+
- **The capability text.** The `view_all_audit_log` description (`PLATFORM_CAPABILITIES`, `@objectstack/spec`) gains one sentence saying the same for `single`.
19+
20+
**Upgrade note for `single` operators who granted the ledger to someone who is not the platform administrator.** An auditor role (a set granting `sys_audit_log` read, with or without `view_all_audit_log`), an organization administrator, or a `viewer_readonly` holder no longer sees deployment configuration history: global settings changes, user-import runs, platform-admin standing changes, and the user accounts the platform administrator creates or resets through the admin endpoints. The organization's own record history and tenant-scope settings changes are still served. A person who must read deployment configuration history needs the platform administrator's grant (`admin_full_access`); no narrower grant serves those rows. A job that exports the full ledger reads it in system context and is unaffected.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/components': patch
3+
'@object-ui/plugin-list': patch
4+
'@object-ui/plugin-detail': patch
5+
'@object-ui/i18n': patch
6+
---
7+
8+
fix(ui): four KPI tiles sit two across on a phone, a calendar's footer says its count is the loaded dates', and the record not-found page says the record may also be one you cannot read
9+
10+
Clause-②: no
11+
Metadata-protocol: none
12+
13+
Three console rows from a full HotCRM browser pass:
14+
15+
- **A layout `grid` with a bare `columns` of 4 or more draws two tiles across on a phone** (below `sm`), then the authored count from `md`. A home page's KPI strip (`columns: 4` over `object-metric` tiles) took a row per tile and filled the first screen; it now reads as the dashboard's own phone layout for metric tiles. A bare 2 or 3 still stacks on a phone and pairs from `sm`, and an authored breakpoint object (`columns: { xs: 1, md: 4 }`) is drawn as written.
16+
- **A calendar view's footer says `N records loaded for these dates`.** The calendar fetches the window of dates it draws, so the footer's count is that window's records, not the view's total; it read "N records", the words a grid uses for the whole view. A grid, a board and a calendar over rows handed in whole keep "N records". New count family `list.calendarRecordCount` in all ten packs.
17+
- **The record not-found page says the record "does not exist, may have been deleted, or you may not have access to it".** The server answers a record the reader may not see the same as a missing one (`404 RECORD_NOT_FOUND`, so existence is never revealed), so the page cannot tell the two apart and now names both. `empty.recordNotFoundDescription` and `detail.recordNotFoundDescription` change in all ten packs.
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): an admin can open a read-only package's app in Studio to look at it
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The app bar's Edit app pencil and the app switcher's 「Edit APP in Studio」 row were drawn only for an app whose package Studio lists as writable (objectui#12615), so a code or installed package's app (HotCRM, for one) offered no way into Studio at all. The same metadata authors now get a browse entry for such an app: an eye in the app bar, 「Open app in Studio」, and 「Open APP in Studio」 in the switcher, landing where the pencil would, in Studio's read-only posture. A writable package's app keeps the pencil and 「Edit APP in Studio」. The console's own apps, a package the list does not carry and a list not yet read still draw neither.
12+
13+
`useStudioEntryGate` answers the gate with a posture (`edit` or `view`) from the same server `writable` verdict; `useStudioDesignGate` is its `edit` half, so the sidebar's Design entry and a page's pencil are unchanged. Two keys are added in all ten locales: `topbar.openAppInStudio` and `layout.appSwitcher.openAppInStudio`.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): Delete flow in the Automations pillar's ⋯ menu
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
A flow authored in Studio could only be deleted from Setup › Metadata. An open flow of a writable package now has a ⋯ menu in its header with Delete flow. It asks in the console's in-app dialog, naming the flow by its label and `flow/NAME`, then discards the flow's pending draft and deletes the flow through the same `DELETE /meta/flow/:name` door Setup uses, at the package Studio writes the flow's drafts to. The rail is re-read, the pillar returns to its empty state without opening another flow, and a toast names the deleted flow. A refused delete shows the server's sentence and keeps the flow open. A read-only package's flow has no menu. Two keys are added to the studio namespace (en, zh): `engine.studio.auto.deleteFlow` and `engine.studio.auto.flowDeleted`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/core': patch
3+
'@object-ui/components': patch
4+
'@object-ui/plugin-grid': patch
5+
'@object-ui/plugin-detail': patch
6+
'@object-ui/data-objectstack': patch
7+
'@object-ui/app-shell': patch
8+
---
9+
10+
fix(console): a packaged action switched off for this deployment is no longer offered as a live button
11+
12+
Clause-②: yes (widening)
13+
Metadata-protocol: none
14+
15+
An administrator can switch a packaged action off in Setup › Packaged automation. The action door then refuses it, but the console kept offering the button on the record header, in action bars and in list rows, so a click could only fail. The object reads now serve the names of the switched-off actions beside the object (#27842), and the console stops offering those actions everywhere it already hides an action whose capability the user lacks: the record header, action bars, record quick actions, list and related-list toolbars, row and bulk actions. Turning the action back on in Setup brings the button back.
16+
17+
`@object-ui/core` adds four exports for this: `isActionSwitchedOff(action)`, `markSwitchedOffActions(schema, served)`, `markSwitchedOffActionsByObject(objects, served)` and `ACTION_SWITCHED_OFF_KEY`. The data layer stamps a switched-off action entry under that symbol key, which survives a spread and never reaches JSON, so an action handed back to a metadata write never carries it.
18+
19+
What to do: nothing. A backend that does not serve the names hides nothing, as before.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
'@object-ui/i18n': patch
4+
'@object-ui/app-shell': patch
5+
---
6+
7+
feat(plugin-detail)!: the record page's related panels lose collapse — the header opens the whole list, ⠿ + ⋯ on hover, ⋯ holds Hide alone, empty panels fold into one line, and the stored arrangement drops `collapsed`
8+
9+
Clause-②: yes (narrowing)
10+
Metadata-protocol: none
11+
12+
<!-- adr-0087: not-required (runtime-interface-only ui/packages/plugin-detail/src/synth/relatedArrangement.ts#RelatedArrangement, ui/packages/plugin-detail/src/synth/relatedArrangement.ts#ResolvedRelatedArrangement, ui/packages/plugin-detail/src/synth/relatedArrangement.ts#RelatedArrangementEdit, ui/packages/i18n/src/locales/en.ts#TranslationKeys) a reader's per-browser layout preference and its edit vocabulary, and the key type of the console's own translation catalogue: no Zod schema, no spec declaration and no stored metadata reads them, so the compiler is the channel that reaches every caller; the stored preference moves to version 2 and a version-1 record reads as no choice -->
13+
14+
The record page's related column is a stack of same-shaped panels (record page v4).
15+
16+
- **The panel header** is the title, then the count chip. The header row (or the chip) opens the panel's whole list; on hover the title's end shows › and its tooltip reads "View all N". On hover or focus the right end shows ⠿ (drag to reorder; focused, ⌥↑ / ⌥↓ move the panel), + and ⋯, which now holds Hide and nothing else. There is no collapse and no ⤢, and nothing is reserved on the left.
17+
- **Empty panels** (0 rows) fold into one line after the others: "N more empty lists · name · … · Expand". Expand draws them again in their places. The Related tab of an authored page keeps its own line.
18+
- **The compact row's first column** skips the foreign key that points back at the record on the page: a product's line items, whose name field is the product, are named by their next column.
19+
- **The stored arrangement** (`record.related.arrangement`, per reader × object) is `{ v, order, hidden, basedOn, seen }`. `collapsed` is removed with no compatibility read: a reader's earlier record reads as no choice, and the panels show in the default order.
20+
21+
**BREAKING** (`@object-ui/plugin-detail` and `@object-ui/i18n`, TypeScript only): `@object-ui/i18n`'s exported `TranslationKeys` loses the four `detail.relatedCard*` keys named below (a `t()` call with one of them still compiles, its key being a `string`, and reads the key back); and `RelatedArrangement` and `ResolvedRelatedArrangement` no longer carry `collapsed`, `RelatedArrangementEdit` no longer has the `collapse` kind, and `resolveRelatedArrangement` / `editRelatedArrangement` no longer take a last `defaultCollapsed` argument. The only caller is the console's own arranged column. A caller still passing one gets a compile error naming it; the fix is to drop it, since a panel has no collapsed state to set. `RELATED_ARRANGEMENT_RECORD_VERSION` (2) is new.
22+
23+
- The i18n keys `detail.relatedCardMoveUp`, `detail.relatedCardMoveDown`, `detail.relatedCardCollapse` and `detail.relatedCardCount` leave every pack; `detail.relatedPanelViewAll`, `detail.relatedPanelReorder`, `detail.relatedPanelReorderHint` and `detail.relatedEmptyFold` arrive in all ten.

‎.changeset/27545-record-pane-v4.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@object-ui/plugin-grid': patch
3+
'@object-ui/plugin-list': patch
4+
'@object-ui/app-shell': patch
5+
'@object-ui/i18n': patch
6+
---
7+
8+
feat(plugin-grid,plugin-list,app-shell,i18n): record pane v4 in the console — a record opens beside the list by default, ⤢ maximizes it in place on the same URL, and the full page opens only from the row's ↗ and the record header's ⋯ (#27545)
9+
10+
Clause-②: yes (widening)
11+
Metadata-protocol: none
12+
13+
No metadata key is added, removed or read differently: every change is in how the console opens a record from a list, and in the reader's own preference. The mechanics (the split face, the non-modal drawer, maximize, the list's narrowed faces) shipped in the change before this one.
14+
15+
- **`@object-ui/app-shell`**
16+
- The object page opens a record beside the list unless the reader prefers the drawer (Display menu); a reader who prefers the full page gets it from a row click. Below a 1000 px content area the pane is a non-modal drawer over the list.
17+
- The list is one element whatever the pane does, so it keeps its scroll. A click on another row swaps the pane's root record. The open row is tinted, follows ↑ ↓ and scrolls into view.
18+
- A `?recordId=` change (a row click, a step) re-renders none of the list: the router's setter and the list's handlers no longer change identity with the URL.
19+
- The bar's ⤢ maximizes the pane in place and no longer routes; nested levels share it. The full page opens from the row's ↗ and from the record header's `⋯`, whose last host item is 「Open as full page」. 「Collapse to drawer」 is drawn only on a page the ↗ opened.
20+
- A step keeps the record on screen until the next one arrives, and ↑ ↓ warm the related counts of both neighbours.
21+
- **`@object-ui/plugin-list`**
22+
- The Display menu's Open records section draws a third segment, Full page, when the host passes the new optional `recordOpen.selectPage`; a host that passes none sees two segments, as before, but now in the order Split | Drawer (it was Drawer | Split), since Split is the default. `recordOpen.preference`, an input the section only reads, also accepts `'page'`. `setPreference` still takes `'split' | 'drawer'`, so a host's setter keeps its type. The note says a record's ↗ always opens the full page.
23+
- Beside a record pane the record-count bar no longer prints the count (the console prints it under the list); the rest of the row stays.
24+
- **`@object-ui/plugin-grid`**: narrowed beside a pane, the row-actions column keeps its header's accessible name but draws no label, so it is only as wide as its ⋮.
25+
- **`@object-ui/i18n`**: `list.recordOpenPage` in all ten packs, and `list.recordOpenNote` reworded in all ten.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/service-storage': patch
4+
---
5+
6+
fix(spec)!: `GetPresignedUrlRequestSchema.size` declares a whole, non-negative byte count, and `filename` / `mimeType` declare that they are non-empty, the accept set the presigned-upload door enforces (#27598)
7+
8+
Clause-②: no (narrowing) — the published declaration of the presigned-upload request body refuses a negative or fractional `size` and an empty `filename` or `mimeType`, which it admitted before. Nothing is widened and no key is added, removed or renamed.
9+
Metadata-protocol: none
10+
11+
<!-- 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: a REST request declaration no metadata type embeds stops admitting a negative or fractional `size` and an empty `filename` or `mimeType`. The door already refused all but the fraction, and a fractional size never named a byte count a stored file can hold. A caller sends the file's whole byte length, name and MIME type; there is nothing for a migration to rewrite and no tombstone. -->
12+
13+
**BREAKING** accept-set narrowing of a published request declaration (`@objectstack/spec/api`), read by the `POST /api/v1/storage/upload/presigned` door, codegen, the reference docs and clients.
14+
15+
- **Before.** `size` was any number, and `filename` / `mimeType` were bare strings, so `-1`, `1.5` and `''` parsed. The door refused a negative size and an empty name or type with a 400, but accepted a fractional size: it minted an upload URL and a pending file record that no upload could complete, because the complete door compares the held byte count, always whole, with the declared one.
16+
- **Now.** `size` is `int().min(0)`, and `filename` / `mimeType` carry a minimum length of 1; their descriptions say so. A parse of any of those values fails on that key. The door (`@objectstack/service-storage`) also refuses a fractional `size` with `400 INVALID_REQUEST`, before any record is written, with the message `size must be the file size in bytes: a non-negative whole number, sent as a JSON number`.
17+
- **Unchanged.** A negative, non-numeric, `NaN` or infinite size, and an empty or missing `filename` or `mimeType`, get the same 400 as before, in the same order as before. `0` is still a legal empty file. The SDK's `storage.getPresignedUrl` takes this schema's input type, which stays `number` / `string`.
18+
- **Who sees it.** Code that parses a presigned-upload body with this schema and sent one of the refused values, a generated client that trusted the wider declaration, and a caller that sent a fractional size to the door.
19+
20+
**The remedy.** Send the file's length in whole bytes (`File.size` already is), and its name and MIME type; an unknown type is sent as `application/octet-stream`.

0 commit comments

Comments
 (0)