Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .changeset/global-nav-studio-retire-rc6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"@object-ui/app-shell": minor
"@object-ui/components": minor
"@object-ui/types": minor
"@object-ui/core": minor
---

Retire the `global_nav` Studio designer surfaces, and track the `@objectstack` family at `17.0.0-rc.6` (objectstack#7100 / objectstack#6888).

## The retirement

`global_nav` was an `ACTION_LOCATIONS` member no running-app surface ever rendered. The console's ⌘K palette (`app-shell/src/chrome/CommandPalette.tsx`) builds its groups from nav items, objects, dashboards, pages, reports, recent items, record search and theme; it holds no reference to `global_nav`, to `actionRendersAt`, or to any action-metadata source. An action declaring `locations: ['global_nav']` therefore never reached a user.

The Studio designer previewed it anyway — a mock frame reading `⌘K · Command palette` with the author's button inside it. That is the sharp edge the maintainer's 2026-08-09 ruling on objectstack#6888 named: an authoring tool promising a surface the product does not have teaches authors, and every AI copying this corpus, to declare dead metadata. `@objectstack/spec` `17.0.0-rc.6` retired the member (7 members → 6) with a named rejection message; this release removes the designer surfaces that outlived it.

- `metadata-admin/previews/ActionPreview.tsx` — the mock command-palette placement frame is gone. The metadata strip above it still ECHOES whatever `locations` the draft declares, deliberately: reporting what a (possibly stale) draft says is honest, whereas the frame CLAIMED the platform renders it.
- `metadata-admin/inspectors/ActionDefaultInspector.tsx` — the `global_nav` entry is gone from `LOCATION_LABELS`. That map is typed `Record< ActionLocation, string >`, so the retirement reached it as a compile error rather than as a silently stale dropdown — the mechanism objectui#3017 installed, firing as designed.
- `metadata-admin/previews/block-config.ts` — the `record:quick_actions` location dropdown no longer offers it, and both locale tables drop the now-orphaned `…option.location.global_nav` key.
- `@object-ui/components`' `action:bar` doc comment is aligned. The component's published enum is `[...ACTION_LOCATIONS]`, so it followed the retirement on its own; only the prose was stale.

`@object-ui/core`'s `ActionEngine.getActionsForLocation` is **unchanged and still answers a literal string match**. Narrowing it to the six live members would put a second rejection point beside the schema's — the tolerant-consumer shape the strict-contract rule forbids, inverted. Enforcement stays where it belongs: the parameter type is now six-membered so no type-correct caller can spell the retired value, and `ActionLocationSchema` rejects it by name at authoring and publish time.

## The dependency move

All 37 `@objectstack/*` declarations across 30 `package.json` files move from `^17.0.0-rc.5` to `^17.0.0-rc.6`, and `pnpm-lock.yaml` resolves one copy of each family package at rc.6. The siblings move with `spec` because `client` / `formula` / `lint` pin it **exactly** — leaving them behind would keep two copies of the spec in the tree, the split brain objectui#3560 called out.

Bumping the pin and repairing the fallout cannot be split: at rc.5 the `Record< ActionLocation, string >` above is missing a key, at rc.6 it has an excess one.

## Breaking, in FROM → TO form

- **`@object-ui/types`' `Theme` now binds the spec's `Theme`, not `ThemeInput`.** rc.6 retired every `…Input` alias and moved the bare name onto the `z.input` side (`X` = `z.input`, `XParsed` = `z.infer`). The runtime shape and this package's exported name are unchanged — `Theme` was, and still is, the AUTHORING shape where `mode` is optional. Re-pointing at `ThemeParsed` would have been the silent swap.
- **`SpecReport` / `SpecReportChart` re-point to `ReportParsed` / `ReportChartParsed`, and `SpecReportInput` / `SpecReportChartInput` to `Report` / `ReportChart`.** Same rename, same rule: each local alias keeps the SIDE it had at rc.5.
- **`@object-ui/types` no longer re-exports `I18nObject`, `LocaleConfig`, `PluralRule`, `DateFormat` or `NumberFormat`** — all five were retired by rc.6. They were dead re-exports here: nothing in this repo imported them from `@object-ui/types` (`@object-ui/i18n`'s formatter vocabulary in `utils/spec-formatters.ts` is locally declared and never bound the spec symbols). `I18nLabel` survives and is unchanged as a name.
- **`I18nLabel` itself widened from `string` to `string | Record< string, string >`** — rc.6 folded the retired `I18nObject`'s per-locale map into it and ships `resolveI18nLabel(label, locale)` as the shared resolver. Every read in this repo that lands in a text slot now goes through that resolver, so an inline map renders its locale instead of `[object Object]`. Reads the compiler cannot see are audited separately in objectui#4163.
- **`@object-ui/types`' `GlobalFilterSchema` derives via `.safeExtend`, not `.extend`.** rc.6's `GlobalFilterSchema` carries a refinement and zod 4 refuses `.extend()` on a refined object outright, which threw at module load. `.safeExtend` is zod's prescribed replacement and KEEPS the refinement, so the spec's cross-field rule now also runs on this package's dialect — which is the intended behaviour, since the pinned divergences widen individual fields and were never meant to switch off a whole-object rule.
46 changes: 46 additions & 0 deletions .changeset/record-picker-sort-limit-empty-text-4167.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
"@object-ui/components": minor
---

`element:record_picker` publishes `sort`, `limit` and `emptyText` as authoring
inputs (objectui#4167).

All three were already READ by the renderer and declared by the contract — the
renderer has passed `sort` into `$orderby` and `limit` into `$top` since the
block existed, and `emptyText` decides the no-rows message — but none of them
appeared in `inputs`, so every layer that reads a manifest said they did not
exist. `packages/components/src/renderers/layout/page.tsx` builds the JSX-page
compiler's prop whitelist from `getKnownTypes()` plus these `inputs`, so writing
any of the three on a JSX page drew an `unknown-prop` warning from
`sdui-parser/src/validate.ts` on a key the renderer then went on to honour.

That is objectui#3407's shape — honoured, undiscoverable — and this is the same
repair objectui#3808 made for `record:details.hideFields` and objectui#3830 made
for `element:record_picker.filter`. `@objectstack/spec` 17.0.0-rc.6 is what made
it actionable: objectstack#5775 declared the three upstream, and the reverse
direction of the console's registry parity gate went red demanding them the
moment the pin moved — a red the previous exemption had predicted in writing and
called "correct and wanted".

Each description documents the renderer's real behaviour rather than restating
the schema, because that is the half an author cannot read off the contract:

- **`sort`** and **`limit`** are both overridden OUTRIGHT by a node-level
`dataSource` binding (`dataSource.sort ?? sort`), not merged with it — so a
node that carries a `dataSource` silently ignores them.
- **`limit`** defaults to 50 in the renderer, not in the schema, and a record
outside the limit cannot be picked at all with nothing in the control to say
more exist.
- **`emptyText`** is published as `string` against a contract of
`string | Record< string, string >`: rc.6 widened it to `I18nLabel`, and this
renderer passes the value straight into a text node with no locale resolution,
so only the plain-string form renders today. The description says so rather
than advertising a shape the renderer drops — the narrowed-type treatment
objectui#3832 describes, with the render-site gap tracked in objectui#4163.

The console's `registry-inputs-spec-parity` suite also drops all twelve of its
off-spec exemptions, which rc.6 obsoleted at once (objectstack#6776 declared
`page:header.recordChrome` / `showStar` / `showCopyId`, `page:accordion.variant`
and `page:tabs.tabStyle`; objectstack#5775 declared the `element:record_picker`
trio and `children` on the four page containers). The forward direction of that
gate now runs with no cover of any kind.
92 changes: 92 additions & 0 deletions .changeset/spec-symbol-collisions-rc6-4167.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
"@object-ui/types": minor
"@object-ui/core": minor
"@object-ui/react": minor
"@object-ui/app-shell": minor
"@object-ui/layout": minor
"@object-ui/fields": minor
"@object-ui/components": minor
"@object-ui/plugin-designer": patch
---

Stop declaring 14 symbols under names `@objectstack/spec` owns at `17.0.0-rc.6`
(objectui#4167, objectstack#4115).

The rc.6 bump published nine names this repo already declared locally, on top of
four that predate it — `check:spec-symbols` reported all thirteen at once, and a
fourteenth (`GlobalFilterSchema`) appeared during the bump itself. Each was
triaged on its own rather than blanket-renamed, because the right answer differs
per symbol: five bind to the spec, three are renamed because the spec's
same-named export means something else, five arrive by derivation, and one is a
declared dialect with a written reason.

**Breaking for importers of `@object-ui/react`, `@object-ui/app-shell` and
`@object-ui/types`** — three exported names changed, because the spec exports the
same name for a *different* thing:

| package | was | now | what the spec's same-named export actually is |
|:--|:--|:--|:--|
| `react` / `app-shell` | `MetadataState` | `MetadataCacheState` | a metadata item's LIFECYCLE state — `'draft' \| 'active' \| 'deprecated' \| 'archived'` (`MetadataStateSchema`, `@objectstack/spec/system`) |
| `react` / `app-shell` | `resolveI18nLabel` | `resolveKeyedI18nLabel` | a resolver for the INLINE per-locale map (`{ en: 'Owner', 'zh-CN': '负责人' }`) against a BCP-47 locale |
| `types` | `DateRangePreset` | `FilterBuilderDateRangePreset` | the thirteen HISTORICAL dashboard filter-bar presets; this one is the filter-builder set, which adds eight FUTURE windows the dashboard schema rejects |

`resolveI18nLabel` is the one where the collision had already started costing
something. rc.6 widened `I18nLabel` from `string` to
`string | Record< string, string >`, so the same authored value now reaches
either resolver — and each answers wrongly, silently, for the other's input: the
keyed one returns `undefined` for `{ en: 'Owner' }` (no `key`, no
`defaultValue`), and the spec's reads `key` / `defaultValue` / `params` as locale
tags. The rc.6 bump PR met this and aliased the spec's import as
`resolveInlineI18nLabel` in five files, with hand-written comments at two of
them. That is a review convention, which is what objectstack#4115 exists to
replace with a rule — so `Keyed` is now the counterpart of that `Inline`, and the
name says which vocabulary it resolves at every call site.

**Eleven keep their names and are now imported or derived from the spec** instead
of re-declared: `DATE_RANGE_PRESETS`, `NavigationMode`, `AddressValue`,
`BreakpointColumnMap`, `BreakpointOrderMap`, `KanbanConfig`, `CalendarConfig`,
`GanttConfig`, plus the three renamed above at their new names.

**Four of the copies were losing information, not just duplicating it.**

- **`GanttConfig` declared six keys and called itself canonical; rc.6's
`GanttConfigSchema` declares seventeen.** The eleven it never mentioned —
`parentField`, `typeField`, `baselineStartField`, `baselineEndField`,
`groupByField`, `resourceView`, `assigneeField`, `effortField`, `capacity`,
`quickFilters`, `autoZoomToFilter` — are all read by
`plugin-gantt/src/ObjectGantt.tsx`, through a local `GanttConfigEx`
intersection that existed only because this type did not carry them. It now
derives from the spec, with `timeSegments` (shift segmentation) as the one
genuinely local extension; the schema is `$loose` upstream, so that key is
legal metadata rather than a second dialect.
- **`GanttConfig.tooltipFields` carried the comment "not part of the upstream
GanttConfigSchema".** It is, as of rc.6, so the key now arrives from the spec.
- **`AddressValue` declared five of the spec's seven parts** — `countryCode` and
`formatted` were missing, under a comment already claiming to be "the part
names of `AddressSchema`". The widget still renders five inputs; binding the
type stops it from asserting the platform cannot store the other two, and makes
the `{ ...address }` write-through say so.
- **`DATE_RANGE_PRESETS` was `Object.keys(PRESET_RANGES)`,** a third copy of a
vocabulary the spec extracted in objectstack#4614 precisely to collapse — its
own doc comment names this module as one of the three. It is now the spec's
array by reference, and the local date-macro bounds table is pinned complete
against it with `satisfies`, so a preset the schema gains without bounds here
is a compile error rather than a filter that validates clean and then selects
nothing.

`NavigationMode` was one hop from the spec already (`NavigationConfig['mode']`);
it is bound directly, with a both-directions type pin that it stays the same type
as the config's own `mode`. `KanbanConfig` / `CalendarConfig` /
`BreakpointColumnMap` / `BreakpointOrderMap` were exact hand copies of `$strict`
schemas and are now re-exports — "still exact" is the argument for binding them,
since a copy with nothing to protect can only drift.

`GlobalFilterSchema` is the one ALLOW entry. It is the same spread-composition
dialect as `SelectOptionSchema` next to it, and it collided only because rc.6's
new refinement forced `.extend()` to be respelled as a `.shape` spread — which
moved a derivation the guard could see into an object literal it deliberately
does not descend into. The dialect is unchanged and its three divergences are
pinned; which side moves on the refinement itself is objectui#4165.

`@objectstack/spec` moves from `devDependencies` to `dependencies` in
`@object-ui/layout`: its public type surface now references the spec.
4 changes: 2 additions & 2 deletions apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@
"@object-ui/providers": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/client": "^17.0.0-rc.5",
"@objectstack/spec": "^17.0.0-rc.5",
"@objectstack/client": "^17.0.0-rc.6",
"@objectstack/spec": "^17.0.0-rc.6",
"@tailwindcss/postcss": "^4.3.3",
"@tailwindcss/typography": "^0.5.20",
"@testing-library/jest-dom": "^7.0.0",
Expand Down
Loading