Skip to content

Commit b404379

Browse files
committed
Merge origin/main into claude/project-thread-q8q83k
Claude-Session: https://claude.ai/code/session_01JQwxbZfKtKX5FguJoubPfK Co-authored-by: Claude <noreply@anthropic.com>
2 parents cc7fa9b + f95d0db commit b404379

82 files changed

Lines changed: 4802 additions & 362 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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/plugin-designer': patch
3+
---
4+
5+
refactor(plugin-designer)!: retire six zero-consumer exports (the collaboration provider and its status indicator, three canvas hooks and the property editor)
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/plugin-designer#CollaborationProvider, @object-ui/plugin-designer#ConnectionStatusIndicator, @object-ui/plugin-designer#useClipboard, @object-ui/plugin-designer#useMultiSelect, @object-ui/plugin-designer#useCanvasPanZoom, @object-ui/plugin-designer#PropertyEditor) the retired operands are React components and hooks in a published ui package's entry that nothing in the workspace or in hotcrm imported; no packages/spec schema, registered node type, ADR-0087 conversion surface or stored shape names them, so there is nothing for `objectstack migrate meta` to rewrite -->
11+
12+
**BREAKING** for a host that imports any of these six names from `@object-ui/plugin-designer`. None had an importer anywhere in the workspace or in hotcrm, so the Console and every `@object-ui/*` package are unaffected. Every module stays in the package (the four designers keep using the hooks and the property panel internally); only the entry stops publishing them. The designers, their `…Props` types, `useUndoRedo`, `useDesignerHistory`, `useConfirmDialog`, `ConfirmDialog`, `Minimap`, `VersionHistory`, the app pages and the metadata pages stay exported unchanged.
13+
14+
What leaves, and what to write instead (none has a replacement export):
15+
16+
- FROM `CollaborationProvider`, `ConnectionStatusIndicator` TO no replacement: render the designers without the wrapper; real-time co-editing is no longer offered from outside the package.
17+
- FROM `useClipboard`, `useMultiSelect`, `useCanvasPanZoom` TO no replacement: the designer canvases already copy/paste, multi-select and pan/zoom on their own; keep a local copy of the hook if you used it in your own canvas.
18+
- FROM `PropertyEditor` TO no replacement: the designers render their own property panel; for a form of your own, use `SchemaRenderer` with a form schema.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/plugin-gantt': patch
3+
---
4+
5+
refactor(plugin-gantt)!: retire nineteen zero-consumer exports (the quick-filter bar, the resource-workload grid, the workload math and the shift-band helpers)
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/plugin-gantt#QuickFilterBar, @object-ui/plugin-gantt#QuickFilterBarProps, @object-ui/plugin-gantt#QuickFilterField, @object-ui/plugin-gantt#QuickFilterOption, @object-ui/plugin-gantt#QuickFilterLabels, @object-ui/plugin-gantt#QuickFilterDef, @object-ui/plugin-gantt#ResourceWorkload, @object-ui/plugin-gantt#ResourceWorkloadProps, @object-ui/plugin-gantt#computeWorkload, @object-ui/plugin-gantt#WorkloadColumn, @object-ui/plugin-gantt#WorkloadOptions, @object-ui/plugin-gantt#ResourceCell, @object-ui/plugin-gantt#ResourceLoad, @object-ui/plugin-gantt#normalizeDependencies, @object-ui/plugin-gantt#normalizeShiftSegments, @object-ui/plugin-gantt#shiftDayStart, @object-ui/plugin-gantt#bandAt, @object-ui/plugin-gantt#ShiftSegmentsConfig, @object-ui/plugin-gantt#ShiftBandConfig) the retired operands are React components, helper functions and their TypeScript types in a published ui package's entry that nothing in the workspace or in hotcrm imported; no packages/spec schema, registered node type, ADR-0087 conversion surface or stored shape names them, so there is nothing for `objectstack migrate meta` to rewrite -->
11+
12+
**BREAKING** for a host that imports any of these nineteen names from `@object-ui/plugin-gantt`. None had an importer anywhere in the workspace or in hotcrm, so the Console and every `@object-ui/*` package are unaffected. Every module stays in the package (`ObjectGantt` still renders the quick-filter bar, the resource view and the shift bands from them); only the entry stops publishing them. `ObjectGantt`, `ObjectGanttRenderer`, `GanttView`, the task, dependency, link and marker types, `normalizeTaskType`, `parseHHMM` and the `NormShiftSegments` / `NormShiftBand` types that `GanttViewProps.shiftSegments` takes stay exported unchanged.
13+
14+
What leaves, and what to write instead (none has a replacement export):
15+
16+
- FROM `QuickFilterBar`, `QuickFilterBarProps`, `QuickFilterField`, `QuickFilterOption`, `QuickFilterLabels`, `QuickFilterDef` TO no replacement: declare `gantt.quickFilters` on the `object-gantt` node and let `ObjectGantt` draw the bar.
17+
- FROM `ResourceWorkload`, `ResourceWorkloadProps`, `computeWorkload`, `WorkloadColumn`, `WorkloadOptions`, `ResourceCell`, `ResourceLoad` TO no replacement: set `gantt.resourceView: true` with `assigneeField` (and optionally `effortField` / `capacity`) and let `ObjectGantt` render the workload grid.
18+
- FROM `normalizeShiftSegments`, `shiftDayStart`, `bandAt`, `ShiftSegmentsConfig`, `ShiftBandConfig` TO no replacement: declare `gantt.timeSegments`; to drive `GanttView` directly, pass `shiftSegments` already in the `NormShiftSegments` shape.
19+
- FROM `normalizeDependencies` TO no replacement: give each `GanttTask` you pass to `GanttView` a `dependencies` array of ids or `{ id, type }` objects (`GanttDependency[]`).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/core': patch
3+
'@object-ui/app-shell': patch
4+
---
5+
6+
fix(core): date macros in a filter resolve on the workspace timezone's day, so an authored view's `{today}` and a reader's panel `{today}` name the same day
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
- **Before.** `resolveDateMacros` read the browser's clock. A list view's authored filter is resolved in the browser before it is sent, while a reader's filter-panel rule is sent with the macro unresolved and the server resolves it in the workspace `localization.timezone`. When the browser and the workspace were on different days, `close_date between ['{today}', '{today}']` authored on a view asked for one day and the same rule from the panel asked for the other; both on one list sent both days and matched nothing.
12+
- **Now.** The calendar tokens (`today`, `yesterday`, `tomorrow`, the week, month, quarter and year bounds, `N_days` / `N_weeks` / `N_months` / `N_years`) resolve on the day of the display zone `LocalizationProvider` declares from the served `localization.timezone`, the zone the server uses. The instant tokens (`now`, `N_minutes`, `N_hours`) stay on the real instant. With no workspace zone known, the browser's clock decides, as before. The list's authored filter is resolved again when the workspace zone arrives after the first render.
13+
- No signature changes: `resolveDateMacros(filter, now?)` keeps its parameters and reads the zone the date faces already read. Every surface that resolves macros through it (lists, kanban, calendar, gantt, timeline, map, tree, gallery, related lists, grids, dashboards, charts, compare-to, calendar `initialDate`) now names the workspace day too.

‎.changeset/29153-field-help-on-inline-help-text.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ Metadata-protocol: none
1010
Nothing changes in what a metadata author can write. What changes is the served, translated read of an object: `translateObject` (the resolver behind `GET /api/v1/meta/object/:name` and every other localized object read) now serves a field's `objects.OBJECT.fields.FIELD.help` translation on the field's own `inlineHelpText` when the field authors one.
1111

1212
- **Before.** The translation was served on `description` only. The i18n extractor scaffolds that entry from `help ?? inlineHelpText ?? description`, and the console draws a field's help line from `inlineHelpText || description`, so a field that authored `inlineHelpText` showed its source-language text in every locale and its translation never appeared.
13-
- **Now.** A field that authors a non-empty `inlineHelpText` is served the translation there, and on `description` as before. A field without one (absent, or an empty string, which the console reads as absent) gains no `inlineHelpText` key; its translation stays on `description`, which the console already draws.
13+
- **Now.** A field that authors a non-empty `inlineHelpText` is served the translation there, the text the entry was scaffolded from, and keeps its source-language `description`. A field without one (absent, or an empty string, which the console reads as absent) gains no `inlineHelpText` key; its translation stays on `description`, which the console already draws.
1414
- **Unchanged.** A locale with no `help` entry for the field, and the source locale, serve the authored text. Each slot keeps a value that diverged from the packaged declaration (a tenant's own edit), judged per slot by the same rule that already guards `description`. Nothing is ever served on a `help` key.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/console': patch
3+
---
4+
5+
fix(console): an internal form refuses a write the caller holds no grant for before it draws a field
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A member who may read an object but not create it could open a form link (`/forms/NAME` or `/apps/APP/forms/NAME`), type the whole record, and learn only on Submit that the server refuses it, losing the typing. The internal form page now asks the caller's grant as soon as the form has loaded: a create form the create grant, an edit form (`?recordId=`) the update grant, through the console's affordance map (`createFormFields`, `editFormFields`). Refused, the page draws the submit-time refusal's own localized sentence ("You don't have permission to save this record.") and no field. With the grant, the form is unchanged.
11+
12+
The app-less `/forms/NAME` route now sits under the same permissions layout as `/home` and `/apps/APP/*`, so both form URLs read the one `/auth/me/permissions` answer the console already holds, and a hop from Home or an app asks nothing again. The URL is unchanged. The anonymous `/f/SLUG` route is unchanged. No metadata key and no server change.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os migrate meta --stored` lists a stored dashboard that declares one global filter key twice
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** `DashboardSchema` refuses a dashboard whose `globalFilters` repeats a filter key (its `name`, or its `field` when it has no `name`), and the dashboard save door answers 422 `INVALID_METADATA`. A dashboard already stored in that shape still loads with `_diagnostics` reading `valid: false`, but the v18 stored-row preview had no detector for it, so `os migrate meta --stored` named no such row before the upgrade.
11+
- **Now.** The preview lists each stored dashboard that repeats a key, under the TODO id `ui-dashboard-global-filter-duplicate-name-refused` (the refusal's upgrade guide entry), at the second declaration's `name` (or `field`) — the site the parse refuses — naming the key and every index. The detector calls the refinement's own predicate, so a row is listed exactly when the parse refuses it. Nothing rewrites it: which definition keeps the key only the author knows.
12+
- **Not changed.** What any door accepts or refuses. Distinct keys, and distinct names over one `field`, are not listed.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
perf(app-shell): the app sidebar and recent rows label their items by name
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Two label readers still downloaded a whole metadata list to name one row:
11+
12+
- Inside an app, a navigation entry with no label of its own read the whole object registry (`GET /api/v1/meta/object`, with the `/meta/view` list it is composed with) on every page whose route had not loaded it: the sidebar named it, and so did the closed command palette, which names every entry to build its lists. Both now resolve labels with `useNavTargetLabel({ byName: true })`: the entry's object by name, which the app bundle answers, and a dashboard entry the bundle does not hold by name too.
13+
- A recent dashboard, page or report row (Home, the command palette, the sidebar's recent list) read its type's whole list. Each row now asks its own item through the provider's by-name read (`useReadItemsByName`, beside `readObjectsByName`).
14+
15+
Once something else has loaded a list, these readers read it and ask nothing more. The labels shown are unchanged, in every language, and an item that has not landed shows its machine name, as before.
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/lint': minor
4+
---
5+
6+
feat(spec): an inline action can carry `description`, the line its param dialog shows under the title
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
An action declared inline on a page (`element:button`'s `properties.action`, typed `InlineAction`) can now say what it does. Before, `description` on it was refused as an unknown key (`unrecognized_keys`), so a page action that collects params opened its dialog with the generic line and nothing about what confirming would do.
12+
13+
- **The key.** `InlineActionSchema` picks `ActionSchema`'s own `description`: a `template` slot (a bare string is shorthand for `{ dialect: 'template', source }`, with `{{ record.* }}` holes) or an inline locale map (`{ en, "zh-CN" }`), each value such a sentence. The `InlineAction` type gains it too. Nothing else is picked: `icon`, `variant`, `body`, `cancelText` and `submitText` stay unknown keys on an inline action.
14+
- **Judged like a registered action's.** `objectstack validate`, `compile` and the publish gate now judge an inline `description` with the same rules as a registered one: a `${…}` or single-brace token, a hole over a root the runner does not bind, a hole holding logic or an unknown formatter, and a blank sentence are each an error, named at `… (element:button) · action description` (or `description.<locale>` inside a locale map).
15+
- **Where it shows.** The console's `element:button` already forwards `description` to the action runner, and the param dialog draws it under its title. One gap remains on the console side: its inline copy localizer collapses `confirmText`, `successMessage` and `errorMessage` to the user's language but not yet `description`, so until it does, an inline locale map on `description` reaches the dialog unresolved.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/objectql': minor
3+
---
4+
5+
fix(objectql)!: a `user` field's declared `deleteBehavior` takes effect when the user is deleted (#29253)
6+
7+
Clause-②: no (narrowing) — deleting a user that a `user` field declaring `restrict` (or `set_null` with `required: true`) holds is refused with `409 DELETE_RESTRICTED`, where it succeeded before; a declared `cascade` now deletes the rows holding the user, and a declared `set_null` now clears them. Nothing is widened.
8+
Metadata-protocol: semantic
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: a `deleteBehavior` the spec already accepts on a `user` field is now enforced by the delete walk the way it is on a `lookup`. The refusal is the walk's own DELETE_RESTRICTED, so there is no stored value for a conversion to rewrite; an author who did not mean the declaration removes it or changes it. -->
11+
12+
**BREAKING** for an app that declares `deleteBehavior` on a `user` field: the declared value now takes effect when the user is deleted.
13+
14+
A `user` field accepts `deleteBehavior`, but the referential delete walk only read `master_detail`, `lookup` and `tree` fields, so the value did nothing: deleting a user that a `restrict` user field held was not refused, a `cascade` user field did not delete the rows holding that user, and every holder kept the deleted user's id.
15+
16+
- **Now.** A `user` field that declares a `deleteBehavior` joins the walk and reads it as a `lookup` reads its own. `cascade` deletes the rows holding the deleted user. `set_null` clears the field; a `multiple: true` field loses only that member, and an emptied set is `[]`. `restrict` refuses the delete with `409 DELETE_RESTRICTED`, naming the holder's object. A `set_null` on a `required` user field is refused the same way, as on a required `lookup`. A field that names no `reference` still points at `sys_user`, because the type fixes the target.
17+
- **Unchanged.** A `user` field that declares no `deleteBehavior` (the spec's parse adds none) stays out of the walk: deleting the user leaves the field as it was. `lookup`, `master_detail` and `tree` behave as before.
18+
- **Which deletes.** Every user delete: better-auth deletes the user (admin remove-user, self delete-user) through the engine by id, and the generic data routes refuse a caller's own `sys_user` delete. A `restrict` refusal reaches the auth caller as `409 DELETE_RESTRICTED`, and the credential rows deleted before it are rolled back.
19+
20+
- **Rows stored before #9784.** Until then the spec's parse wrote `deleteBehavior: 'set_null'` on every field type, `user` included, so an object stored in that era can carry a `set_null` on a `user` field that no author wrote. That value now takes effect: the field is cleared when the user is deleted, and on a `required` field the delete is refused.
21+
22+
**The fix.** If you declared `deleteBehavior` on a `user` field only as documentation, remove it or change it to what you want enforced. Nothing to do for a `user` field that declares no `deleteBehavior`.

0 commit comments

Comments
 (0)