Skip to content

Commit b87cbca

Browse files
committed
Merge origin/main (1ccb5ba) into claude/issue-11068-object-grid-showfilters-tombstone
2 parents 181b9b6 + 1ccb5ba commit b87cbca

26 files changed

Lines changed: 1718 additions & 236 deletions
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/layout': patch
3+
---
4+
5+
fix(layout): a present navigation label renders as written, with no exception, and an inline locale-map label renders its text (objectui#11201)
6+
7+
A navigation entry's text now follows one rule. A present label is shown as written. An absent label
8+
shows the current, localized label of what the entry opens.
9+
10+
- **Visible change.** An entry whose stored label is its target's machine name (for example
11+
`account`, `sales_overview`) used to be translated, because the sidebar looked that text up as
12+
the object's, view's or dashboard's name. It now shows the name as written, in every locale.
13+
**To fix such an entry:** clear its label so it inherits the target's localized label, or set the
14+
text you want shown. Stored navigation is not converted, and no notice is sent.
15+
- An entry label written as an inline locale map (`{ en: 'Accounts', 'zh-CN': '客户' }`) used to
16+
render as empty text. It now renders the map's text. The sidebar is not told the viewer's locale,
17+
so it reads the `en` entry, then `default`, then any entry, as it already does for an area label.
18+
- `NavigationRenderer`'s `resolveObjectLabel`, `resolveDashboardLabel` and `resolveViewLabel` props,
19+
and the matching arguments of `resolveNavItemLabel`, are no longer read. They are still accepted,
20+
so no caller breaks. An unlabelled entry's localized text comes from `resolveTargetLabel`.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): a Studio pillar never saves one item's document into another while the second is loading, after a package switch, or after visiting a non-editable leaf
6+
7+
Studio's pillars keep the open item's document in an edit buffer and save it as a draft. Three
8+
paths saved a buffer that was not the open item's. Each was reproduced through the real pillars on a writable package:
9+
10+
- **While the next item was loading**, the pillar went on showing the previous item's document
11+
under the new one and took edits on it. A save from there wrote the previous item's document into
12+
the new item. This affected the Interfaces page settings, the Automations enable switch and the
13+
Data pillar's add field. Until the open item's own document is in, the pillars now show it as
14+
loading, offer no editor over the previous item's document, and send no save. The enable switch,
15+
add field, the column reorder, the field count and the "unpublished draft" badge wait for it too.
16+
- **After a package switch**, the Automations and Data pillars kept the previous package's flow or
17+
object open, and an edit saved it into the new package. Both pillars now start fresh on a package
18+
switch and open the new package's own items, as the Interfaces pillar already does. An edit made
19+
less than 1.5 s before the switch is dropped with it.
20+
- **Visiting an object entry in the Interfaces navigation** and then reopening the page just edited
21+
could send an empty document as that page's draft when the page was slow to reload. Opening such
22+
an entry now drops the page's unsent edit, and the page's reload is what comes back.

‎.changeset/7322-object-kanban-component-props.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,5 @@ boards (`columns` + inline `data`, no fetch) that author no `objectName`, which
9595
`ObjectKanbanSchema` declared required **as of 2026-09-06** — objectui#7780's subject,
9696
and that card made it a record-source presence rule on 2026-09-07; their casts stay,
9797
now carrying the reason and the card number.
98+
99+
⏱ **Dated note, 2026-09-30 (objectui#8652).** 「`navigation` is declared on neither face」 in 「Casts this removes」 above was true when written and is falsified by objectui#8652, which declares `navigation` on `ObjectKanbanSchema` on both faces of `@object-ui/types`, by reference to the spec's `NavigationConfig`. The paragraph is kept as written, and is not restated in the present tense.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/types': minor
3+
'@object-ui/plugin-kanban': minor
4+
'@object-ui/plugin-calendar': minor
5+
---
6+
7+
`navigation` is declared on the `object-kanban` and `object-calendar` blocks, by reference to `@objectstack/spec` (objectui#8652). This is the objectui half of the maintainer ruling on that card: the spec declares the key on both element entries from 17.5.0, and objectui now mirrors it.
8+
9+
- `@object-ui/types`: `ObjectKanbanSchema.navigation` and `ObjectCalendarSchema.navigation` are declared on both published faces with the spec's `NavigationConfig` type — the same type the grid, view, gantt and map schemas use. Until now the key was admitted and never examined: `navigation: { mode: 'not-a-mode' }` passed both faces while the spec refused it. It is now refused at `navigation.mode`, and a member the spec does not declare inside the block is refused too. A parse adds no default to the block, so a board or calendar without the key still opens a record in a drawer.
10+
- `@object-ui/plugin-kanban`: the `object-kanban` registration publishes `navigation` in its `inputs`, so the designer offers it.
11+
- `@object-ui/plugin-calendar`: the `object-calendar` and `calendar` registrations publish `navigation` in their `inputs`, and `ObjectCalendar` reads the key without a cast.
12+
13+
What a click does with each member is unchanged. One value has no effect on its own: `page` opens nothing on a board or calendar that no parent view navigates for, and a block written without `mode` resolves to `page`. The published input descriptions say so.

‎apps/console/src/__tests__/registry-inputs-spec-parity.test.ts‎

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ const owedIdsOf = (ledger: Record<string, string>): string[] =>
759759
const OBJECTUI_11111_LEDGER_CAPS = {
760760
unjudgedBlocks: 4, // objectui#11168: object-gantt, object-map, object-timeline, object-tree
761761
offSpecInputs: 0, // objectui#11168 slice 1 retired action:group.name
762-
unpublishedKeys: 5, // objectui#11168: 3 (action:button endpoint + undoable, action:icon endpoint); objectui#8652: 2; objectui#8649: 0 (struck by its landing)
762+
unpublishedKeys: 3, // objectui#11168: 3 (action:button endpoint + undoable, action:icon endpoint); objectui#8652: 0 and objectui#8649: 0 (each struck by its landing)
763763
refusedArms: 2, // objectui#11168: element:definition-list, object-form
764764
memberPins: 4, // objectui#11168: element:definition-list, element:repeater ×3
765765
} as const;
@@ -1411,18 +1411,11 @@ const UNPUBLISHED_EXEMPTIONS: Record<string, string> = {
14111411
// `visible` stood here until objectui#11168 slice 1 measured each against its
14121412
// renderer through the real `SchemaRenderer` and DECLARED all four — the
14131413
// documented exit: a declaration retires its own cover.
1414-
...owedEntries(
1415-
'object-kanban',
1416-
['navigation'],
1417-
'objectui#8652',
1418-
'A SPEC KEY NOT PUBLISHED: 17.5.0 declares `navigation` on this block; objectui#8652 met its unlock with that release and mirrors it as its ruled second step.',
1419-
),
1420-
...owedEntries(
1421-
'object-calendar',
1422-
['navigation'],
1423-
'objectui#8652',
1424-
'A SPEC KEY NOT PUBLISHED: 17.5.0 declares `navigation` on this block; objectui#8652 met its unlock with that release and mirrors it as its ruled second step.',
1425-
),
1414+
// objectui#8652's two entries — `navigation` on `object-kanban` and
1415+
// `object-calendar` — are STRUCK: that card declared the key on both blocks'
1416+
// `inputs` (and on both `@object-ui/types` faces), with a member pin apiece
1417+
// in `MEMBER_PINS`, so each entry went stale and `carries no stale
1418+
// unpublished-key exemption` would refuse it.
14261419
// objectui#8649's nine entries — the field-security triple on `record:details`,
14271420
// `record:highlights` and `record:related_list` — are STRUCK: that card
14281421
// declared all three keys on the three blocks' `inputs`, with the contract's
@@ -2673,6 +2666,10 @@ const MEMBER_PINS: Record<string, MemberPin> = {
26732666
file: 'packages/plugin-calendar/src/__tests__/ObjectCalendar.filterIsNotAConfigSlot-7711.test.tsx',
26742667
pins: 'The members are ObjectQL `$filter` elements, and this renderer adds nothing to that contract and subtracts nothing from it: the authored value reaches `dataSource.find` as `$filter` BY IDENTITY (`toBe`, so a normalising rewrite cannot pass), and the retired `filter.calendar` member spelling yields NO configuration — one authored key read twice with two incompatible meanings is the member-level defect objectui#7711 closed here and objectui#4034 closed on the map. Both member forms are covered (the object form and the array-of-arrays form). The spec side cannot supply this: the `object-calendar` `filter` row is `z.unknown()`, so the wire is the only member contract there is (objectui#7711, registered as a pin by objectui#8176 once objectui#8186 declared the key).',
26752668
},
2669+
'object-calendar.navigation': {
2670+
file: 'packages/plugin-calendar/src/__tests__/calendarNavigationMembers-8652.test.tsx',
2671+
pins: 'The members of the spec\'s `NavigationConfigSchema` block the calendar reads, each driven through a real event click on the real calendar and asserted on what the click DID: an ABSENT key opens a drawer (the renderer\'s own default, the lit control), `none` and `preventNavigation` open nothing with the flag outranking an overlay mode, `new_window` and `openNewTab` open the record page in a new tab with `openNewTab` outranking `page`, and `size` / `width` are one width decision (`width` wins, a bucket resolves through the size table, `auto` lands on the default). The precedence against a parent view is pinned both ways: an OVERLAY mode keeps the click from `onRowClick` / `onEventClick`, any other mode hands it to them. Two further rows pin the description\'s `page` warning — `page`, and a block without `mode`, open nothing on a calendar no parent view navigates for (objectui#8652).',
2672+
},
26762673
'object-calendar.sort': {
26772674
file: 'packages/plugin-calendar/src/__tests__/ObjectCalendar.sortMembersReachTheWire-8171.test.tsx',
26782675
pins: 'The members are `{ field, order }` and those two keys are the whole of what this renderer reads inside one: `{ field, order }` lowers to the `field -> direction` map on `$orderby`, an omitted `order` is ascending rather than a dropped member (the objectui#4022 regression), every member arrives in authored order, and a member with no usable `field` is dropped rather than given an invented one — with an unauthored `sort` reaching the wire as `undefined` as the control. Also carries objectui#7711\'s case transposed: a sort on a field named `calendar` stays a sort and the config still comes from the declared `calendar` container. ⛔ NOT an identity pin, unlike the two `filter` entries: `ObjectCalendar.tsx` writes `$orderby: convertSortToQueryParams(schema.sort)`, which builds a new map, so `toBe` is false about this key — the pin asserts what is read inside the member instead, which is the sharper claim. The spec side cannot supply any of it: the `sort` row is unconstrained (an array, a bare string and a bare number all parse), so the wire is the whole member contract (objectui#8171).',
@@ -2849,6 +2846,10 @@ const MEMBER_PINS: Record<string, MemberPin> = {
28492846
file: 'packages/plugin-kanban/src/__tests__/ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx',
28502847
pins: 'ONE nested position and no more: `schema.grouping?.fields?.[0]?.field` is the FALLBACK source of `swimlaneField`, and that is the entire member contract this board carries for the key. Three rows make it a reading rather than a claim — the swimlane layout appears keyed by `fields[0].field` where without the key there is none; an explicit `swimlaneField` WINS over it; and a second `fields` entry changes nothing, which is what pins the read at `[0]` rather than at "the fields list". The declared description says the rest is inert precisely so the declaration does not recommend a write the renderer cannot honour — this file is what keeps that sentence true. The spec row is `z.unknown()`, so the read site is the whole member contract (objectui#8313).',
28512848
},
2849+
'object-kanban.navigation': {
2850+
file: 'packages/plugin-kanban/src/__tests__/kanbanNavigationMembers-8652.test.tsx',
2851+
pins: 'The members of the spec\'s `NavigationConfigSchema` block the board reads, each driven through a real card click on the real board and asserted on what the click DID: an ABSENT key opens a drawer (the renderer\'s own default, the lit control), `none` and `preventNavigation` open nothing with the flag outranking an overlay mode, `new_window` and `openNewTab` open the record page in a new tab with `openNewTab` outranking `page`, and `size` / `width` are one width decision (`width` wins, a bucket resolves through the size table, `auto` lands on the default). A parent view\'s click handler outranks the whole key, overlay mode included. Two further rows pin the description\'s `page` warning — `page`, and a block without `mode`, open nothing on a board no parent view navigates for (objectui#8652).',
2852+
},
28522853
'object-master-detail-form.cancelText': {
28532854
file: 'packages/plugin-form/src/MasterDetailForm.i18nLabels.test.tsx',
28542855
pins: 'The I18nLabel object arm, resolved at the read site: through the real `SchemaRenderer`, a `properties.cancelText` map under a `zh` UI renders the map\'s `zh-CN` entry on the Cancel button, with `en` listed FIRST so an `en` or first-entry fallback fails the row. Before objectui#10935 the map was a raw Button child and threw "Objects are not valid as a React child". The button renders only when the host supplies `onCancel`, so the row adds one beside the document and asserts the host handler still runs. A plain-string control and the nothing-authored control (`Cancel`) stay green, and the first row asserts the mounted documents pass `safeValidateSchema`. The manifest half (a map is not a `type-mismatch`) is `masterDetailFormI18nLabelManifest.test.ts` in this directory (objectui#10935).',
@@ -4638,9 +4639,8 @@ describe('registry `inputs` vs `@objectstack/spec` ComponentPropsMap (repo-wide)
46384639
// direction — greening a fresh divergence on these two blocks by writing a
46394640
// nineteenth entry instead of declaring the input.
46404641
//
4641-
// TWO is the BOOKED backlog today — objectui#8652's `navigation` pair,
4642-
// ruled at the 17.5.0 bump (the last row of the table below) — and ZERO
4643-
// is what it measured before that bump, not a budget. `object-calendar`
4642+
// ZERO again since objectui#8652 declared the `navigation` pair the 17.5.0
4643+
// bump had BOOKED to it (the last two rows of the table below). `object-calendar`
46444644
// publishes all nine keys its spec row declares; `object-kanban` publishes
46454645
// thirteen of fourteen, and the fourteenth is RULED rather than owed —
46464646
// objectui#8313 emptied the board, objectui#8314 emptied the calendar, and
@@ -4676,6 +4676,9 @@ describe('registry `inputs` vs `@objectstack/spec` ComponentPropsMap (repo-wide)
46764676
// The only step UP this ceiling has taken, and it is a ruling, not
46774677
// headroom: the cap is exactly the two booked entries, and
46784678
// objectui#8652's landing takes it back to 0.
4679+
// 0 objectui#8652 lands: `navigation` is declared on both blocks'
4680+
// `inputs`, each with the `MEMBER_PINS` entry objectui#8212 made
4681+
// part of the same obligation — the booked pair's paired exit.
46794682
//
46804683
// ⚠️ THE LAST STEP IS THE ONLY ONE THAT IS NOT A DECLARATION, and reading
46814684
// it as one would be the wrong lesson. Every step above was this ceiling's
@@ -4729,20 +4732,17 @@ describe('registry `inputs` vs `@objectstack/spec` ComponentPropsMap (repo-wide)
47294732
'a new unpublished-key exemption was added on a block objectui#8176 newly ' +
47304733
'judged — declare the input at its registration site instead; the ' +
47314734
'backlog list is shrink-only',
4732-
).toBeLessThanOrEqual(2);
4735+
).toBeLessThanOrEqual(0);
47334736
// Lower it here when the owning cards land, so the ceiling keeps ratcheting
47344737
// rather than banking the headroom their fixes free up.
47354738
expect(
47364739
backlog.length,
47374740
'the objectui#8176 backlog shrank — lower the ceiling above to match, in ' +
47384741
'the same change that declared the input',
4739-
).toBe(2);
4740-
// …and the two it holds are exactly objectui#8652's booked `navigation`
4741-
// pair, so the ruling's room cannot be spent on a different key.
4742-
expect(backlog.sort(), 'the backlog is not the two booked navigation keys').toEqual([
4743-
'object-calendar.navigation',
4744-
'object-kanban.navigation',
4745-
]);
4742+
).toBe(0);
4743+
// …and it is empty BY NAME, so the room objectui#8652's landing freed
4744+
// cannot be spent on a different key.
4745+
expect(backlog.sort(), 'the backlog is not empty').toEqual([]);
47464746
});
47474747

47484748
it('the one ruled carve-out on a newly judged block is real, and names its exit', () => {
@@ -5598,6 +5598,6 @@ describe('registry `inputs` vs `@objectstack/spec` ComponentPropsMap (repo-wide)
55985598
reasons.filter((reason) => reason.startsWith(`${OWED_PREFIX}${owner}.`)).length,
55995599
]),
56005600
),
5601-
).toEqual({ 'objectui#11168': 13, 'objectui#8652': 2, 'objectui#8649': 0 });
5601+
).toEqual({ 'objectui#11168': 13, 'objectui#8652': 0, 'objectui#8649': 0 });
56025602
});
56035603
});

0 commit comments

Comments
 (0)