Skip to content

Commit 434b014

Browse files
committed
Merge origin/main into claude/project-thread-fiwblf
Conflicts in three skill files main also edited (#26536, #26569): kept main's root-run console:build wording and its ninth required context (Test UI), kept this branch's removal of the objectui-only lines and the retired SHA-stamp drift bullet (no .objectui-sha pin or build-console.sh exists any more). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvE77Ed7MKdzPxJTQQ
2 parents 27f8315 + c8ed4c9 commit 434b014

226 files changed

Lines changed: 11177 additions & 3834 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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@object-ui/react': patch
3+
'@object-ui/plugin-detail': patch
4+
'@object-ui/app-shell': patch
5+
---
6+
7+
fix(plugin-detail): the record page remembers "Show empty fields" per user and object
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
On a record page, each section's "Show N empty fields" row used to open that one section only, and the choice was gone after a reload or on the next record. A reader who wanted the whole record clicked once per section, every time.
13+
14+
What changes:
15+
16+
- In the Console, the choice is now the signed-in user's, per object. One click shows the empty fields in every section of that object's record page. The choice holds on the next record of the same object, after a reload and on another device. Other objects keep their own choice.
17+
- It is stored in a `sys_user_preference` row per object, key `ui.record.<object>`, with the value `{ "showEmptyFields": true }` or `false`. The row sits beside the list page's `ui.list.<object>` rows and loads in the same single preference read.
18+
- `@object-ui/react` adds `RecordPagePreferencesProvider`, `useRecordPagePreferences` and the `RecordPagePreference` and `RecordPagePreferencesStore` types. A host uses them to hand its store to the record page's sections.
19+
- With no store (no signed-in user, or a host that provides none), each section keeps the toggle in its own state, as before.
20+
21+
What to do: nothing. Nothing a view or object declares changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
feat(i18n): a lazy `studio` namespace for the Studio designer's strings; the `designer.*` and `perm.*` keys move into it
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **New subpath `@object-ui/i18n/namespaces/studio`.** The Studio designer's two-locale string set becomes a namespace of this package, as the maintainer ruled on objectstack#25922 (6431032267, Q2 = A; kept by 6431234244). `en` is resident; `zh` is its own chunk, fetched the first time a Chinese lookup asks for it; every other language reads `en`. Exports: `studioString(key, lang)` (synchronous; answers from `en` while a catalogue is in flight), `loadStudioCatalogue(lang)`, `isStudioCatalogueLoaded(lang)`, `resolveStudioLanguage(lang)`, the `useStudioCatalogue(lang)` hook (re-renders its caller when a fetched catalogue lands), `STUDIO_NAMESPACE`, `STUDIO_FALLBACK_LANGUAGE`, `STUDIO_LANGUAGE_CODES` and the `StudioCatalogue` type. Additive: nothing existing changes.
11+
- **First slice of the move.** Every key under `designer.*` and `perm.*` (the object/field designer and the permission designer) now lives in the namespace instead of `@object-ui/app-shell`'s private table. Studio reads the same text in `en` and `zh`; a Chinese viewer sees these labels in English for the moment the `zh` catalogue takes to load, and a non-Chinese viewer no longer downloads these keys' Chinese text with the console.
12+
- **Not in the eager catalogue.** `locales/en.ts` gains no key, and the namespace is not one of the ten packs, so `all-locales-key-parity` does not read it; its own parity (every `en` key has a `zh` row with the same `{placeholders}`) is a test of this package.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/types': minor
4+
'@object-ui/plugin-calendar': minor
5+
'@object-ui/plugin-gantt': minor
6+
'@object-ui/plugin-list': minor
7+
'@object-ui/plugin-view': minor
8+
---
9+
10+
refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: the flat `object-calendar` and `object-gantt` field-mapping keys leave the published schemas; producers emit the `calendar` / `gantt` block (#25923)
11+
12+
Clause-②: yes (narrowing)
13+
Metadata-protocol: narrowed
14+
15+
<!-- adr-0087: not-required (no-migration-prescription) No `@objectstack/spec` key, object definition, stored row or stored shape moves: the spec's `ComponentPropsMap['object-calendar']` and `['object-gantt']` rows already refuse every key this change takes off `@object-ui/types`, so no metadata that the spec accepts carries them, and `objectstack migrate meta` has nothing to rewrite. What narrows is the `@object-ui/types` mirror of those rows: its TypeScript interfaces, its tolerant zod mirrors and, for `object-calendar`, the strict authoring face derived from them, which now refuses the five flat keys by name as the spec does. The affected party is a TypeScript author or a hand-written node, reached by the compiler and by `objectui validate`, and the renderers still read the flat spelling a host hands them. One drawing outcome moves: `getGanttConfig` validates a `gantt` block and never validated the flat spelling, so a pre-door stored view whose `options.gantt` carries a value the spec refuses now shows the refusal screen instead of drawing; that row is outside what the spec accepts, and its conversion belongs to the stored-row census of #25923, not to tolerance in a consumer. The other categories are closed on facts: the five packages publish (not unpublished); no ADR-0087 id covers this change (not registered or already-registered); and the narrowed symbols are zod-backed metadata shapes (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
16+
17+
`@object-ui/types` published, on `ObjectCalendarSchema` and `ObjectGanttSchema`, a flat spelling of the field mapping that the spec refuses on the node. A declared flat key is one the strict authoring face derives from, so `objectui validate` accepted the `object-calendar` spelling the spec refuses. The keys only the host writes now live on each renderer package's own hand-off type (ui/AGENTS.md Commandment #0.1), and the producers write the block.
18+
19+
- **`@object-ui/types`**: `ObjectCalendarSchema` (TypeScript and zod) no longer declares `startDateField`, `endDateField`, `titleField`, `colorField` or `allDayField` flat; they are written in its `calendar` block. The `dateField` / `endField` tombstones stay and now prescribe `calendar.startDateField` / `calendar.endDateField`. `ObjectGanttSchema` no longer declares the 29 `GanttConfig` keys flat (`startDateField`, `endDateField`, `titleField`, `progressField`, `dependenciesField`, `colorField`, `viewMode`, `summaryExtent`, `interactions` and the rest), nor the legacy `dependencyField` alias or the `search` / `searchableFields` pair; they are written in its `gantt` block (the pair is not authored at all). `staticData`, `filter`, `sort`, `gantt` and the other node keys stay. For `object-gantt` the strict authoring face already refused every flat key (its arm is the `properties` bag), so the narrowing there is the published TypeScript and zod export.
20+
- **`@object-ui/plugin-calendar`**: `ObjectCalendarFlatFields` and `ObjectCalendarHandoff` type the flat read `getCalendarConfig` keeps for a host that mounts the renderer directly. The renderer is unchanged: the `calendar` block wins, and the flat spelling is read only without it.
21+
- **`@object-ui/plugin-gantt`**: `ObjectGanttFlatFields` (derived from `GanttConfig`, plus the alias and the search pair) and `ObjectGanttHandoff` type the flat read the same way. The renderer is unchanged.
22+
- **`@objectstack/spec`**: the `object-calendar` and `object-gantt` flat-key refusals no longer say `ObjectView` / `ListView` emit the flat spelling; they hand the binding off in the block, and the flat spelling is only a stored-document fallback the renderer still reads. The prescription itself is unchanged.
23+
- **`@object-ui/plugin-list`** and **`@object-ui/plugin-view`**: `ListView` and `ObjectView`'s `generateViewSchema` emit `calendar: { … }` and `gantt: { … }` on the node they build instead of the flat keys. They emit the block only where the renderer's flat branch drew: a calendar once `startDateField` is declared, a gantt once both dates are, with `titleField` floored at `'name'` as that branch floored it. A gantt binding carrying a value the spec's `GanttConfigSchema` refuses (a pre-door stored `options.gantt`, say) goes out nested too, and now shows the renderer's refusal screen where the unvalidated flat branch drew it.
24+
25+
**BREAKING** for TypeScript code or hand-written nodes that write the flat keys on these two node types.
26+
27+
- **Before.** `const node: ObjectCalendarSchema = { type: 'object-calendar', objectName: 'event', startDateField: 'start' }` compiled, and the `object-calendar` strict face accepted it.
28+
- **Now.** The flat key is a compile error, and `objectui validate` refuses it by name on `object-calendar` (it already did on `object-gantt`). A renderer handed a flat node by a host still draws it.
29+
- **Fix.** Move the keys into the block: FROM `{ type: 'object-calendar', startDateField: 'start', titleField: 'name' }` TO `{ type: 'object-calendar', calendar: { startDateField: 'start', titleField: 'name' } }`, and FROM `{ type: 'object-gantt', startDateField: 's', endDateField: 'e', progressField: 'p' }` TO `{ type: 'object-gantt', gantt: { startDateField: 's', endDateField: 'e', titleField: 'name', progressField: 'p' } }` (the `gantt` block requires `titleField`). Code that hands a flat node straight to `<ObjectCalendar>` or `<ObjectGantt>` types it as `ObjectCalendarProps['schema']` or `ObjectGanttProps['schema']`, which carry the flat keys.
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 grouping and column summaries move into two sibling hooks
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Two adjacent blocks of the `ObjectGrid` component move verbatim out of `ObjectGrid.tsx`. The grouping (the group value formatter, the date-family group faces, the server group headers and row pages, and the grouped data) goes to `useObjectGridGrouping` (`objectGridGrouping.ts`), together with `temporalGroupHeading`, which draws a date group's heading. The cross-page fan-out query, the resolved match total and the column summaries go to `useObjectGridSummaries` (`objectGridSummaries.ts`). `ObjectGrid` calls each hook where its block stood, so its hook order, its queries 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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/components': patch
3+
---
4+
5+
fix(components): the `filter-builder` renderer calls `onChange` with the `FilterGroup` that `FilterBuilderSchema.onChange` declares
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`FilterBuilderSchema.onChange` declares `(filter: FilterGroup) => void`, but the `filter-builder` renderer typed its own `onChange` as `(event: any) => void` and called it with a form-style `{ target: { name, value } }` event. A handler written from the declaration read `.logic` and `.conditions` off that event and got `undefined`, and the `any` kept the compiler from reporting the mismatch. The renderer now takes the schema's own `onChange` type and passes the group it holds, so the declaration is the contract on both sides. No in-repo host bound the event shape. A host that read `event.target.value` reads the argument itself instead.
11+
12+
The builder's own rows also admit the opt-in `exists` / `notExists` operators, which a `FilterGroup` cannot carry. The renderer no longer forwards a host's `extraOperators` to the builder, since `FilterBuilderSchema` declares no such key, so the node never offers them. The `FilterGroup` zod mirror refuses them in an authored `value`. If a stored value that skipped that validation still carries one, the renderer does not call `onChange` and logs a console error naming the operator, instead of handing on a group the declared type does not describe.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
fix(spec)!: every filter door refuses a `{ $now, addDays }` anchor that carries any other key, as `NowAnchorSchema` already declares, instead of running it on the `addDays` reading alone
6+
7+
`NowAnchorSchema` is closed: `{ $now: true, addDays: -30, subDays: 7 }` fails to parse on its own. But the comparand-type walk every filter door runs (`normalizeFilterComparandTypes`, through `FilterConditionSchema` and `parseFilterAST`) judged an anchor by its `addDays` alone, so the extra key was kept, every executor ignored it, and the query answered the `addDays` reading with no word. The walk now refuses an anchor with any key other than `$now` and `addDays`, with the existing `INVALID_FILTER` / 400 envelope, and the refusal names the key: `it carries an undeclared key "subDays" — the anchor takes $now and addDays only (there is no subDays: a negative addDays subtracts)`.
8+
9+
Clause-②: no (narrowing) — every filter door refuses a `$now` anchor carrying a key beyond `$now` / `addDays`, which it ran before. Nothing is widened.
10+
11+
Metadata-protocol: narrowed (a filter saved through `FilterConditionSchema` may no longer carry a key beside `$now` and `addDays` on a now-anchored comparand)
12+
13+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed or renamed: NowAnchorSchema declared exactly $now and addDays since it shipped, and this aligns the walk with that declaration. An extra key never had a meaning at any executor, so there is no rewrite that keeps what the author meant; the author decides, and the refusal names the key. -->
14+
15+
**BREAKING** accept-set narrowing at the filter comparand-type walk (`@objectstack/spec`), served at every filter door.
16+
17+
- **Before.** `engine.find()` / `count()` / `update()` in object or array form, `aggregate()`'s `where`, `aggregations[i].filter`, `having`, and a filter saved through `FilterConditionSchema` all kept `{ $now: true, addDays: N, subDays: M }` or `{ …, note: 'x' }`. The query ran on `addDays` alone.
18+
- **Now.** Each of them answers `INVALID_FILTER` / 400 (a save fails its parse), naming the undeclared key.
19+
- **Unchanged.** A well-formed anchor, `{ $now: true, addDays: <integer | { $field }> }`, and its resolved form (`$now` holding the pinned ISO instant) run as before at every door. The refusals for a missing or malformed `addDays` keep their wording.
20+
- **Who sees it.** An author who wrote an extra key on an anchor. That key was ignored before, so the rows answered were the `addDays` rows.
21+
22+
**The remedy.** Drop the extra key. To move the instant back, give `addDays` a negative value; to read the offset off a column, use `addDays: { $field: 'numeric_column' }`.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
---
4+
5+
fix(plugin-detail): a related list whose read fails says so instead of showing "No related records found"
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
When a record page's related list could not read its rows (a refused read, a server error, a network failure), it drew its ordinary empty state, "No related records found", and only logged the failure. A reader was told a record had no related rows when the console simply could not read them, and on a Related tab the list was also folded into the stack's line of empty lists.
11+
12+
- **A failed read draws one load-failed line on the list's card**, with a Retry button. It reads "Couldn't load records" with the server's own sentence when the server answered with one, or the generic "Something went wrong while loading this data" when it did not.
13+
- **A refused read uses the console's shared no-access wording**, the same as a list view: "You don't have access. You don't have permission to view these records." The server's text is not shown for a refused read. A signed-out session and an object whose API is disabled get the list view's sentences for those cases too; the API-disabled case offers no Retry, because retrying cannot change it.
14+
- **Both fetch paths.** The adapter path and the legacy raw-URL path both draw the line. On the raw-URL path a non-2xx answer is now a failed read; it used to be parsed as a result with no rows.
15+
- **The stack fold leaves a failed list in its own row.** A failed list never reports itself as empty, so it is not folded into the empty-lists line, and its header shows no record count.
16+
- **Retry clears the failure and reads the list again.**
17+
18+
What to do: nothing.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
---
4+
5+
fix(plugin-detail): a record highlight chip with no translation is captioned by the field's declared label instead of nothing
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The highlight strip under a record's title drew an empty caption over every chip whose field had no entry in an i18n bundle and no label on the strip entry. That is every chip on a synthesized record page of an object without a bundle, which includes every AI-built app: the strip showed two bare values side by side with nothing saying which field each one was.
11+
12+
The chip caption now climbs the same ladder the record's details body uses: the bundle's translation, then the strip entry's own label, then the label the object declares for the field (an inline locale map is read in the session language), then the field name. A translated label and an authored strip label still win as before. The caption also updates when the object definition arrives after the record.
13+
14+
What to do: nothing.
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): the standalone `/forms/:name` route shows select option labels in the language the user chose
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** A user whose chosen language differed from the browser's (a zh-CN user on an en-US browser) saw the form's field labels in their language and every select option in the browser's. The route read the form view, the object schema and the record with a bare `fetch`, so the server localized the schema, option labels included, by the browser's `Accept-Language`.
11+
- **Now.** The internal form route (`/forms/:name` and `/apps/:appName/forms/:name`) sends its API requests through the console's authenticated fetch, the same channel the in-app record form (`?form=ID`) reads the object schema over. That fetch sends the session's language as `Accept-Language`, so the server answers option labels in the language the user chose. An option the translation bundle does not cover keeps its authored label. No second translator is added.
12+
- **Unchanged.** The public anonymous form `/f/:slug` keeps its bare `fetch`.

0 commit comments

Comments
 (0)