diff --git a/.changeset/4918-stale-strip-mode-prose.md b/.changeset/4918-stale-strip-mode-prose.md
deleted file mode 100644
index 5f16c6e8b..000000000
--- a/.changeset/4918-stale-strip-mode-prose.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
----
-
-Comment-only truthfulness fix in `@object-ui/plugin-detail`'s spec-parity tests. The
-`relationshipValueField` block in `recordRelatedListInputs.spec-parity.test.ts` and the
-`hideFields` block in `recordDetailsInputs.spec-parity.test.ts` each narrated strip mode
-as a present-tense fact about the installed `@objectstack/spec` — that an undeclared
-top-level key parses green and is silently dropped from `data`. The props schemas were
-closed upstream, so an undeclared key now draws a named `unrecognized_keys` refusal, and
-both comments read as current while being false against the installed pin. Both now state
-the pin-independent verdict (an undeclared top-level key is never authoring surface) and
-note that the contract expresses it two ways depending on the installed pin, pointing at
-`specRefusesUnknownTopLevelKeys` in the sibling `recordHighlightsInputs.spec-parity.test.ts`
-as the behavioural probe. This also settles `recordDetailsInputs` disagreeing with itself,
-its section-key block having already taken the pin-aware disposition. No assertion changed
-and no published behaviour changes.
diff --git a/.changeset/4935-element-filter-comment-residue.md b/.changeset/4935-element-filter-comment-residue.md
deleted file mode 100644
index caebf8b3b..000000000
--- a/.changeset/4935-element-filter-comment-residue.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
----
-
-Comment-only cleanup in `@object-ui/components`: the `elements.tsx` header note no
-longer lists `element:filter` among the heavier interactive elements said to live in
-their owning plugins. No such file ever existed for it, and the element was retired at
-element grain upstream (ADR-0049), so the type is gone from `PageComponentType`
-entirely. No published behaviour changes.
diff --git a/.changeset/4947-tombstone-judge-convergence.md b/.changeset/4947-tombstone-judge-convergence.md
deleted file mode 100644
index 7f6937b24..000000000
--- a/.changeset/4947-tombstone-judge-convergence.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
----
-
-Test-infrastructure convergence, no shipped code touched: the last two local copies of
-the ADR-0087 D2 tombstone criterion — in `packages/plugin-detail`'s
-`recordDetailsInputs` spec-parity test and `packages/app-shell`'s `block-config`
-preview test — now import `@object-ui/test-support`'s shared judge instead of
-spelling out `unwrap()._def.type === 'never'` by hand. Both copies carried the
-structural channel alone; the shared judge OR-s it with the `[REMOVED]` description
-channel, so neither channel can go quietly permissive on its own, and every
-tombstone-aware gate in the repo now moves together when the criterion changes.
diff --git a/.changeset/action-param-degrade-predicate-5654.md b/.changeset/action-param-degrade-predicate-5654.md
deleted file mode 100644
index 2fc5978fe..000000000
--- a/.changeset/action-param-degrade-predicate-5654.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-**Behaviour change:** a `master_detail` action param with no reference target now
-receives the same "paste a record id" placeholder and help text a targetless
-`lookup` param has had since objectui#3405. Previously it degraded to a plain
-text input exactly like `lookup` did, but silently — the user was shown an
-unexplained empty box that wanted a bare record id.
-
-The cause was two hand-maintained answers to one question. `paramToField()`
-performs the degradation over RESOLVED widget keys (`lookup`, `master_detail`),
-while `ActionParamDialog` decided who gets the hints with its own literal over
-RAW param spellings (`'lookup' || 'reference'`). Neither set contained the
-other: `master_detail` degraded with no hints, and `reference` was a copy of an
-alias-table row the adapter folds to `lookup` before it tests membership.
-
-`paramToField` now exports `paramDegradesWithoutTarget(param)` and both the
-adapter's own fallback branch and the dialog's two hint readers ask it, so "who
-degrades" and "who gets hints after degrading" are one member set answered once
-(objectui#5654; same convergence shape as objectui#4770 / #4790 / #4815).
-
-Params that already had the hints keep them, including the `reference` spelling;
-a param that renders as text without having degraded (an unknown type) still
-gets no picker hints.
diff --git a/.changeset/action-runtime-same-origin-only-5702.md b/.changeset/action-runtime-same-origin-only-5702.md
deleted file mode 100644
index 1340b7143..000000000
--- a/.changeset/action-runtime-same-origin-only-5702.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-**Behaviour change:** the console action runtime (`useConsoleActionRuntime`) now
-builds its authenticated fetch with `sameOriginOnly: true`, matching the
-`provider: 'api'` data-source lane (`ConsoleShell`). A metadata `type: 'api'`
-action whose resolved target is a different origin than the page is fetched
-through the bare global fetch: the platform Bearer token, `X-Tenant-ID`, and
-`Accept-Language` are no longer attached (objectui#5702, maintainer ruling
-2026-08-22). Previously the Bearer rode to any off-origin target whose URL
-contained `/api/`, and `X-Tenant-ID` rode to every off-origin target
-unconditionally.
-
-Same-origin actions — including every relative target in a same-origin
-deployment — are unchanged, and off-origin requests still execute (pass-through,
-not a refusal). An off-origin integration that legitimately needs the platform
-bearer declares itself explicitly or proxies same-origin.
-
-Note for split-host setups: `apiHandler` prefixes relative action targets with
-`VITE_SERVER_URL`. When that is set to an origin different from the page's,
-those action requests are off-origin and no longer carry credentials. The
-committed dev and starter configurations are unaffected: since objectui#5745
-they ship `VITE_SERVER_URL` empty and reach a split-host backend through the
-Vite `/api` dev proxy, so their action requests are same-origin.
diff --git a/.changeset/active-org-clear-removal-failure-5731.md b/.changeset/active-org-clear-removal-failure-5731.md
deleted file mode 100644
index aacb03bc7..000000000
--- a/.changeset/active-org-clear-removal-failure-5731.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-'@object-ui/auth': patch
----
-
-`ActiveOrganizationStorage.clear()` now verifies that the persisted key is actually
-gone and, when it is not, both reports the failure and stops `get()` answering from
-the surviving value — instead of swallowing the failed removal (objectui#5731).
-
-`clear()` nulled `_memoryValue` and then removed the persisted key inside a
-`try`/`catch` that discarded any failure. Since objectui#5703 `get()` prefers a
-NON-NULL `localStorage` read and only falls through to `_memoryValue`, so the two
-halves of `clear()` were not equally strong: nulling memory always sticks, while a
-removal that did not stick left the key readable and the read order preferred it.
-Sign-out is one of `clear()`'s five callers, so the failure mode was "sign-out does
-not stick", and it was silent — the cleared organization went back on the wire as
-`X-Tenant-ID` on every subsequent request.
-
-The removal is now judged by a READ-BACK rather than by catching the throw, which is
-both narrower and wider in the right directions. Wider: a wrapped or proxied
-`localStorage` whose `removeItem` is a silent no-op never throws and leaves identical
-residue, and is now covered. Narrower: SSR and the partitioned-iframe browser where
-every operation throws have nothing readable to resurrect, were already safe, and are
-not reported as failures.
-
-A key whose removal could not be verified is quarantined in memory for the rest of the
-page-load: `get()` skips the persisted branch for it and answers from `_memoryValue`,
-which `clear()` has just nulled and which a later `set()` refills with the value that
-write was meant to persist. The quarantine is released as soon as a removal on that key
-sticks. An unstamped `X-Tenant-ID` is a documented state of the edge contract
-(objectui#5279); a re-stamped signed-out organization is not.
-
-The failure is not thrown and not returned. All five call sites — sign-out's
-`purgeSignedOutClientCaches`, `switchOrganization`, `deleteOrganization`,
-`leaveOrganization`, and the session-user purge that runs on the SIGN-IN path — arrive
-after the transition they follow up on has already happened, and none can act on a
-storage failure; a `boolean` every caller ignores would read as handled when it is not.
-So the invariant is restored inside `clear()` and the failure is reported to the
-console.
-
-A working `localStorage` behaves exactly as before: the removal sticks, nothing is
-quarantined, nothing is reported, and a non-null persisted read is still authoritative.
-`set()`'s swallowed write failure is deliberately untouched — that swallow is
-objectui#5703's memory fallback, and it is the correct kind, because the memory copy
-upholds `set()`'s postcondition where nulling memory could not uphold `clear()`'s.
diff --git a/.changeset/active-org-storage-memory-fallback-5703.md b/.changeset/active-org-storage-memory-fallback-5703.md
deleted file mode 100644
index eef9655d9..000000000
--- a/.changeset/active-org-storage-memory-fallback-5703.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/auth': patch
----
-
-`ActiveOrganizationStorage.get()` now prefers a non-null `localStorage` read and falls
-back to the in-memory value otherwise, instead of returning the `localStorage` read
-unconditionally — so the fallback is reachable in the browser state it was written for
-(objectui#5703).
-
-`set()` already swallowed a failed `localStorage.setItem` into `_memoryValue`, but
-`get()` only consulted `_memoryValue` when the READ itself threw. There is a real
-browser state where the read does not throw and the fallback is nonetheless the only
-copy: `localStorage` present and readable but rejecting writes — Safari private
-browsing, and any quota-exhausted origin, where `setItem` throws `QuotaExceededError`.
-In that state the active org was stored and could not be read back, measured as
-`_memoryValue = org-42` alongside `get() = null`.
-
-The cost was silent and lasted the whole session rather than the documented first-boot
-window: `createAuthenticatedFetch` reads `get()`, so `X-Tenant-ID` was never stamped on
-any request. Per the edge contract documented on objectui#5279 that header is a routing
-hint a reader falls through on — the framework scopes from the session — so no row
-visibility rode on this; what was missing is the tenant-routing input, on every request.
-`switchOrganization` also appeared to succeed while the client-side stamp never
-followed.
-
-Sign-out is unaffected, and that is the half worth stating: the new fallback fires
-exactly when the `localStorage` read is null, which is the state `clear()` leaves
-behind. It answers `null` there because `clear()` nulls `_memoryValue` too. That
-property is now pinned by test rather than relied upon, so a future `clear()` that only
-removed the persisted key fails a test instead of quietly re-stamping a cleared org.
-
-A non-null persisted read still wins over the memory value, so a working `localStorage`
-behaves exactly as before.
diff --git a/.changeset/adapter-meta-type-singular-4940.md b/.changeset/adapter-meta-type-singular-4940.md
deleted file mode 100644
index a96ab6451..000000000
--- a/.changeset/adapter-meta-type-singular-4940.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-'@object-ui/data-objectstack': patch
----
-
-`ObjectStackAdapter.getApp` and `getPage` now address the `app` / `page` metadata
-types in the singular, matching the other twelve `client.meta.*` call sites in this
-file (objectui#4940).
-
-`getApp` (`getItem('apps', …)`) and `probeAppAccess` (`getItem('app', …)`) addressed
-the same metadata type sixty lines apart, and only `probeAppAccess`'s comment argued
-its singular spelling was deliberate — the plural site was silent. Both plural sites
-resolved today only because the server folds plural → singular
-(`RestServer.metaTypeSingular` via `PLURAL_TO_SINGULAR` from `@objectstack/spec/shared`,
-confirmed by reading both the mapping and the by-name route handler that calls it), so
-this is consistency restoration rather than a behavior change — nothing a user hits was
-broken, and nothing a user hits changes.
-
-`appAccessProbe.test.ts` (objectui#4252's local pin for this same spelling) is extended
-with two new cases asserting `getApp`/`getPage` pass the singular type to
-`client.meta.getItem`, so a future revert to the plural spelling fails a test instead of
-depending on the server-side fold staying in place.
diff --git a/.changeset/adr-0057-d10-citation-attribution-5202.md b/.changeset/adr-0057-d10-citation-attribution-5202.md
deleted file mode 100644
index e30834a07..000000000
--- a/.changeset/adr-0057-d10-citation-attribution-5202.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
----
-
-Traceability only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. The change is comment text at seven live source sites; no
-executable line moves, and `git diff -U0` carries zero non-comment added or removed lines.
-
-`server enforces, client is courtesy` was cited at those seven sites as a bare
-**`ADR-0057 D10`**. The substantive claim is correct and is unchanged here; what was missing
-is the **framework qualifier**. In this repository the bare string resolves to
-`docs/adr/0057-console-ai-chat-one-conversation-docked.md` — a document about console AI chat
-docking, which contains no `D10` at all and is the one a reader greps first. The intended
-anchor is the *framework's* ADR-0057, whose D10 decides *"Setup-nav surfacing follows the
-capability (ADR-0029 K2); the object stays open"*.
-
-Each site now carries the disambiguation already shipped by the two authorities in this
-repository — `docs/adr/0036-field-conditional-rules.md:91` and
-`packages/core/src/evaluator/fieldRules.ts:38` — rather than a third phrasing:
-`the framework's ADR-0057 D10 — framework numbering; this repo's own ADR-0057 is an
-unrelated document`.
-
-Three sites are deliberately left byte-untouched, all three already correct:
-`packages/data-objectstack/src/appAccessProbe.test.ts:25` (a verbatim quotation of the
-objectstack#8013 ruling, and about the capability/nav gate — the one family D10 really does
-decide), plus the two authorities above.
diff --git a/.changeset/ai-chat-public-share-base-4482.md b/.changeset/ai-chat-public-share-base-4482.md
deleted file mode 100644
index 3ad06d025..000000000
--- a/.changeset/ai-chat-public-share-base-4482.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`AiChatPage`'s public share-link base now resolves through the one console-mount
-resolver instead of a private copy of it (objectui#4482).
-
-The page built `publicShareBase` itself — read the injected ``, take its
-pathname, trim trailing slashes, concatenate `${origin}${base}/s` — which was the third
-independent implementation of the mount resolution `resolveConsoleUrl` centralizes.
-objectui#4472 had just deleted the other two on that rule; this was the surviving
-sibling. Its output was correct, so nothing a user hits was broken and nothing a user
-hits changes: measured over the base-href matrix, the deleted builder and
-`resolveConsoleUrl('s')` return identical URLs for every shape the console is served in
-— `/_console/` (the only href the framework CLI injects), `/` root mounts, `./` portable
-builds, nested mounts, and no `` at all.
-
-The `/s` resolution now lives beside its three siblings as `resolvePublicShareBase()`,
-which keeps the one thing a bare `resolveConsoleUrl('s')` call would drop: with no DOM
-it returns `undefined` rather than a URL built from an origin that does not exist, so
-`ShareDialog` applies its own fallback. It deliberately takes no `baseURI` argument —
-the mount is only ever carried by the injected ``, and a resolver with no
-other input cannot be pinned by a test that steers something production never reads.
-
-`resolvePublicShareBase.browser.test.tsx` pins the resolved base against a real injected
-`` element for each deployment shape, plus a structural case asserting no other
-app-shell file reads the `` tag — so a fourth copy fails a test rather than
-waiting for mount semantics to change under it.
diff --git a/.changeset/ai-studio-accessor-5577.md b/.changeset/ai-studio-accessor-5577.md
deleted file mode 100644
index dca947932..000000000
--- a/.changeset/ai-studio-accessor-5577.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`features.aiStudio` is now read through one `isAiStudioEnabled()` accessor instead
-of being spelled inline at two call sites (objectui#5577).
-
-`features.marketplace` already had a documented accessor whose docblock is where the
-fail-open doctrine is written down — *"Fails OPEN (`!== false`): a runtime predating
-`/api/v1/runtime/config`, or one whose config fetch failed, keeps the default `true`"*,
-plus the "never infer this from the shape of a failure" warning. `features.aiStudio`
-had no such sibling: `ChatDock` read `getRuntimeConfig().features.aiStudio !== false`
-and `HomePage` read `getRuntimeConfig().features?.aiStudio !== false`, so one doctrine
-had two spellings and neither reader could cite it.
-
-The two spellings were not equivalent. `ChatDock`'s omitted the optional chain, and
-against a runtime-config snapshot whose `features` is absent that read is a TypeError
-rather than a fail-open — the exact shape that crashed 29 tests across four suites in
-PR #5575 before it was corrected. Measured here: no live path can currently deliver
-such a snapshot to `ChatDock` (the module's singleton constructs `features` on every
-write and exports no setter, and no suite mounts the dock's default body under a
-partial stand-in), so this closes a reachable-by-construction crash rather than a live
-one — and it closes it at the source by leaving no inline read to get wrong.
-
-`isAiStudioEnabled()` is an internal module export, matching `isMarketplaceEnabled()`:
-neither is re-exported from `src/index.ts`, so the package's published `exports` surface
-is unchanged.
diff --git a/.changeset/alias-closure-relative-miss-5386.md b/.changeset/alias-closure-relative-miss-5386.md
deleted file mode 100644
index f67ab4d32..000000000
--- a/.changeset/alias-closure-relative-miss-5386.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
----
-
-Tests only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. No package `src/` is touched; the only file changed is
-`examples/console-starter/test/vite-alias-closure.test.ts`, and that example is private.
-
-Makes the alias-closure walker record an unresolvable **relative** import instead of
-dropping it.
-
-`computeClosure()` walks two kinds of specifier. The bare-specifier branch pushed a miss
-onto `unresolvable`; the relative branch dropped one with no record at all. So
-`expect(closure.unresolvable).toEqual([])` was not a weak assertion for relative imports
-— it was a structurally empty one. It could not fail no matter how many relative
-specifiers the walk failed to follow, and the `filesWalked` floor was the only signal
-that anything had gone wrong.
-
-That is how two earlier conversions to explicit extensions (objectui#4538, objectui#5214)
-each truncated this walk while landing green: the floor had enough slack to absorb both,
-and only went red once app-shell — the largest package — converted and took the count
-under 500. The direct symptom was being swallowed one branch away the whole time.
-
-Measured on `main` at the time of this change, with the resolver ablated to its
-pre-objectui#5357 behaviour to reproduce that regression class: 275 relative specifiers
-dropped, `filesWalked` 1245 to 402, packages reached 29 to 22 — and `unresolvable` still
-reporting `[]`. With this change the same ablation fails the suite with all 275 named,
-each alongside the file that imports it.
-
-The miss is recorded only for specifiers that are *meant* to be modules — no extension,
-or one of the JS/TS emitted extensions. `ts.preProcessFile` also reports `./styles.css`,
-`./data.json` and `./logo.svg`, which `resolveModule` cannot resolve by design, so
-recording those identically would fail the suite for a reason that is not a defect.
-Assets and Vite resource specifiers (`?raw`, `?url`, `?inline`) are skipped into a
-separate `nonModuleSkipped` list — explicitly, and observably, rather than by accident.
-
-Four fixtures pin the class against the real walker so a future edit cannot silently
-re-blind the branch: `./Foo.js` resolving through to the `Foo.tsx` on disk, planted
-unresolvable modules of both spellings being named, assets staying out of `unresolvable`
-while still being accounted for, and the classifier's boundary cases.
diff --git a/.changeset/analytics-error-branch-mapping-5663.md b/.changeset/analytics-error-branch-mapping-5663.md
deleted file mode 100644
index e0809a768..000000000
--- a/.changeset/analytics-error-branch-mapping-5663.md
+++ /dev/null
@@ -1,51 +0,0 @@
----
-'@object-ui/data-objectstack': patch
----
-
-`ObjectStackAdapter.queryDataset` now maps a failed dataset query by the server's
-ADR-0112 error `code`, not by the HTTP status, so an unknown dataset and an
-unauthenticated session stop being reported as a missing analytics capability
-(objectui#5663).
-
-Two unrelated conditions answer **404** on `POST /api/v1/analytics/dataset/query`:
-the runtime dispatcher's `ROUTE_NOT_FOUND` when the route was never mounted, and
-the route's own `NOT_FOUND` when `body.datasetName` matches no saved dataset. The
-mapping tested `res.status === 501 || res.status === 404` and called all of it
-"the analytics capability is not installed", so every unknown dataset produced a
-banner telling the operator to install `@objectstack/service-analytics` and mount
-`AnalyticsServicePlugin`. Measured live on a prod tenant, that banner was shown on
-four HotCRM Executive Overview widgets while the analytics service was installed
-and answering — the real condition was an installed `app.objectstack.hotcrm` at
-1.3.0 whose datasets ship in 2.2.2, i.e. a package upgrade, the opposite corner of
-the system from the remedy the banner named.
-
-Three conditions now get three answers, each keyed on the code the framework
-declares for it:
-
-- `NOT_IMPLEMENTED` (501, route mounted with no analytics service) and
- `ROUTE_NOT_FOUND` (404, route not mounted) keep the existing
- `AnalyticsNotInstalledError` and its copy — one remedy, one message.
-- `NOT_FOUND` (404, unknown `datasetName`) throws the new
- `AnalyticsDatasetNotFoundError` (`ANALYTICS_DATASET_NOT_FOUND`), naming the
- dataset and pointing at the installed app's version rather than at the server.
-- `UNAUTHENTICATED` (401, `enforceAuth`) throws the new
- `AnalyticsUnauthenticatedError` (`ANALYTICS_UNAUTHENTICATED`), which says the
- request was refused before it ran and therefore says nothing about the
- capability.
-
-The banner also used to print the server's own message in a parenthetical while
-contradicting it in the headline — it quoted `Dataset "opportunity_metrics" not
-found.` under a headline claiming a missing capability. That is now structurally
-impossible rather than merely fixed: the headline is a pure function of `code` and
-the parenthetical is a verbatim quote of `message`, both read off the same
-response, and a test walks every branch asserting each message carries its own
-headline and none of the others'.
-
-Additive only. `AnalyticsNotInstalledError` keeps its `code`, its copy and its
-constructor signature (it gains an optional third `serverCode` argument and a
-`serverCode` field), so consumers matching `ANALYTICS_NOT_INSTALLED` — including
-the metadata-admin dataset preview — are unaffected. A 404 carrying a code this
-client does not recognise, such as the analytics cube gate's `CUBE_NOT_FOUND`, now
-keeps its server detail instead of being relabelled as a missing capability; a 404
-or 501 carrying no code at all is still read as the capability being absent, since
-the route's own `NOT_FOUND` always ships a code.
diff --git a/.changeset/app-shell-page-header-subtitle-4761.md b/.changeset/app-shell-page-header-subtitle-4761.md
deleted file mode 100644
index 01333ab79..000000000
--- a/.changeset/app-shell-page-header-subtitle-4761.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-The console's `` spells its secondary line `subtitle`, the same key the other `PageHeader` in this repo uses (objectui#4761).
-
-This repository has two components named `PageHeader`. `@object-ui/layout`'s is
-the renderer for the authored `page:header` / `page-header` node and converged
-on `subtitle` in objectui#3789, because `subtitle` is the key
-`@objectstack/spec/ui`'s `PageHeaderProps` declares. `@object-ui/app-shell`'s —
-the console's own title row, drawn by `ObjectView` and `ObjectDataPage` —
-spelled the very same concept `description` and had no `subtitle` at all. Both
-rendered correctly; the defect was one concept carrying two key names one
-package apart, the objectstack#4115 shape moved up a layer. An author reading
-one component to learn the other was being taught a key the contract does not
-have.
-
-**Not a breaking change, measured rather than assumed.** The convergence is a
-plain rename with no alias, because this component is not on the published
-surface:
-
-| gauge | result |
-|---|---|
-| exports of `dist/index.d.ts`, through the TypeScript checker | 226 symbols; `PageHeader` and `PageHeaderComponentProps` are not among them (controls: `AppShell` reachable, a nonsense name not) |
-| `exports` map | declares exactly `.` and `./styles.css` |
-| Node resolving `@object-ui/app-shell/layout`, `…/dist/layout/PageHeader.js`, `…/src/layout/PageHeader.js` | `ERR_PACKAGE_PATH_NOT_EXPORTED` for all three, while the declared entry resolves |
-| in-repo call sites | 2, both inside this package (`ObjectView.tsx`, `ObjectDataPage.tsx`) |
-| emitted declarations that change | `dist/layout/PageHeader.d.ts` only — `dist/index.d.ts` and `dist/layout/index.d.ts` are byte-identical across the change (`8c886251…`, `f9f4862b…`, both legs) |
-
-No supported specifier reaches the prop, so there was nothing to keep
-compatible, and a renderer-side `description` alias would have been exactly the
-second dialect AGENTS.md #0.1 forbids — the layout side had just finished
-retiring one. Out-of-repo consumers cannot be enumerated from this repository;
-what can be, and is, is the set of import paths through which one could have
-reached this component, which is empty.
-
-Rendered output is unchanged: same element, same classes, same position. The
-patch tier is a declaration that the tarball moved, not a claim that a consumer
-must act.
-
-`packages/app-shell/src/layout/__tests__/PageHeader.subtitle.test.tsx` is the
-pin the card asked for. It asserts the subtitle on the DOM a reader gets (a
-`
`, in the title block, after the `
`), that `description` now draws
-nothing and is rejected by the compiler, and — the assertion that actually goes
-red if either side drifts again — that both packages' `PageHeaderComponentProps`
-declare `subtitle`.
diff --git a/.changeset/approvals-inbox-cell-remount-5348.md b/.changeset/approvals-inbox-cell-remount-5348.md
deleted file mode 100644
index 4a11753a5..000000000
--- a/.changeset/approvals-inbox-cell-remount-5348.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-'@object-ui/console': patch
----
-
-The Approvals Inbox stops remounting every row on every render.
-
-`ApprovalsInboxPage` declared `RequestCell`, `RecordCell` and `InlineActions`
-inside its own component body. React identifies a component by the identity of
-its function, so each render produced three brand-new component *types* and React
-unmounted and remounted every row's subtree instead of updating it — and the page
-holds its clock in state and ticks it every 60s, so this fired on a timer whether
-or not anyone was touching the page (objectui#5348).
-
-Two consequences were reproduced against `origin/main` before the fix, in
-`apps/console/src/pages/system/ApprovalsInboxPage.cellIdentity.test.tsx`:
-
-- **Transient subtree state is discarded.** Focus placed on a row's Approve
- button moved to `` on the next clock tick.
-- **Input is silently swallowed.** A pointer sequence that spans a re-render —
- press, tick, release — left the confirmation dialog unopened: the captured node
- had been replaced, so React's delegated listener never saw the click. This is
- the failure objectui#5211 hit and worked around at its call site
- (`Unable to find role="alertdialog"`).
-
-The three cells are now at module scope beside `StatusBadge`, which was moved
-there for the same reason and already carries the explanation. Everything they
-closed over is passed in: `RequestCell` and `InlineActions` take the page's
-scoped translator, and `RecordCell` takes `href: string | null` — one prop rather
-than two, so the objectui#5211 readable/unreadable decision and the URL cannot be
-handed in disagreeing with each other.
-
-The verification asserts the consequence, not the placement. A test that checks
-the three functions now sit at module scope stays green for a refactor that moves
-them and introduces a fourth inline component beside them; these cases compare
-DOM-node identity for all three cells across a clock tick, which no remount can
-pass, and re-drive the swallowed click.
-
-That guard is load-bearing because lint cannot supply one here.
-`react-hooks/static-components` exists for exactly this class and is `error` in
-this repo via the plugin's recommended set, yet it reports nothing on this page:
-measured on `origin/main`, an arrow-form inner component injected into
-`ApprovalsInboxPage` and used in JSX produced **zero** reports, while the same
-shape in a ten-line file produced two. The rule's analysis bails out on this
-component, which is how three of them shipped.
diff --git a/.changeset/approvals-raw-payload-gate-5553.md b/.changeset/approvals-raw-payload-gate-5553.md
deleted file mode 100644
index 3cd1ed264..000000000
--- a/.changeset/approvals-raw-payload-gate-5553.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-'@object-ui/console': patch
----
-
-The Approvals Inbox no longer shows a business approver the submitted record's raw
-row JSON.
-
-The detail drawer's "Raw data (JSON)" panel rendered on `payload != null` alone — no
-principal check of any kind — so every approver could expand (and one-click copy) the
-complete raw snapshot: `id`, `created_by`, `updated_by`, `owner_id`,
-`organization_id`, bare lookup ids, and **the fields the object's metadata declares
-`hidden: true`**. Reported from a live EHR deployment on 17.1.0
-(objectstack-ai/objectstack#10734), where that declaration is a patient-data control.
-The app author had no legitimate lever to remove the panel — field `hidden`, view
-columns, app navigation, permission sets and env vars are all ineffective against it —
-so the remedies available in the field were patching the shipped bundle or injecting
-CSS.
-
-The panel is now gated on `holdsStudioAccess`, reused verbatim from the console's
-`studioEntry` module: `studio.access` is a declared platform-scope capability that a
-tenant org owner does not hold by design, and it already reaches the browser in
-`systemPermissions[]` from `/api/v1/auth/me/permissions`. Nothing new is served,
-computed or made authorable — no new config key, no new i18n copy, and the panel is
-byte-for-byte unchanged for the platform operator it was written for. A business
-approver keeps the structured record summary, the approval chain, the activity feed
-and the decision actions; only the raw snapshot is gone.
-
-The gate reads the RAW `systemPermissions` signal and fails **CLOSED**, inverted from
-`usePermissions().hasCapabilities`. That hook fails open on purpose — hiding a
-holder's button while the server still refuses the write is the worse outcome for an
-action. This panel has the opposite stake, since the measured defect is a non-holder
-seeing it, so every not-a-reported-grant answer denies: no provider mounted, a backend
-predating ADR-0066 that omits the field, the resolver's `catch` path that answers `200`
-with no `systemPermissions` at all, and a reported empty array. A deployment whose
-permission layer just failed must not be the one that leaks the snapshot.
-
-`ApprovalsInboxPage.rawPayloadGate.test.tsx` pins all four verdicts. Because the
-acceptance condition is that something does *not* render — which an empty render
-reproduces perfectly — every denial case also asserts the drawer it denies inside, and
-the `studio.access` case drives the same fixture through the same helper and finds the
-panel. `created_by` and `organization_id` are the witnesses: both are in the page's
-`PAYLOAD_SYSTEM_KEYS`, so the summary card already drops them and their values can
-reach the DOM only through the raw panel. Ablating the gate (restoring the bare
-`payload != null` condition) turns the three denial cases red on exactly that
-assertion and leaves the holder case green.
-
-Out of scope, tracked separately: trimming the summary by object metadata, and the
-server-side residual that sends the unfiltered snapshot to the client at all.
diff --git a/.changeset/approvals-step-progress-vertical-5554.md b/.changeset/approvals-step-progress-vertical-5554.md
deleted file mode 100644
index 8bccea37e..000000000
--- a/.changeset/approvals-step-progress-vertical-5554.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-'@object-ui/console': patch
-'@object-ui/app-shell': patch
----
-
-The approval step progress bar is a vertical stepper, so long flows stop
-clipping their tail steps.
-
-Both occurrences were a single non-wrapping flex row whose steps were each
-`shrink-0`. A flex row's min-content width is the sum of its non-shrinkable
-items, so the bar's intrinsic width grew without bound with step count and
-label length. On a live 17.1.0 project a real 6-step flow with ordinary CJK
-step names measured **1070px inside a 527px container** (objectui#5554).
-
-The two hosts failed differently, and neither failure was recoverable by the
-reader:
-
-- **`ApprovalsInboxPage`** (the inbox detail drawer) — the bar itself was not
- scrollable, so the nearest scroller was the drawer *panel*. Reaching steps
- 4-6 meant dragging the drawer's own horizontal scrollbar, which pushed the
- record card, the activity timeline and the action buttons off-screen and left
- a near-blank panel.
-- **`RecordApprovalsPanel`** (the record page's approvals panel) — this one
- carried `overflow-x-auto`, so it scrolled itself rather than its container.
- Better, but the tail steps still sat behind a scroll gesture with no visible
- affordance.
-
-In both, readers took the clipped bar for the end of the data; the reporting
-customer acceptance tester said so verbatim. Widening the window does not help:
-the drawer is fixed-width, and clipping was identical at 1440x900 and 1920x1000.
-
-Both now render as a column: one row per step, a badge-and-rail gutter, and a
-label that may wrap. Width is capped by the container at every step count and
-every label length, which also suits both hosts' tall-and-narrow aspect. The
-rail segment below each step keeps the tint rule the horizontal connector used
-— it is coloured by the step it leads *into*.
-
-**Always vertical, with no step-count or measured-width threshold**, because
-the overflow is driven by intrinsic content width (labels x count), not by
-count alone: three 16-character CJK labels already crowd a 527px drawer, so any
-count threshold picks a cutoff that is wrong for some real flow, and a measured
-one reintroduces a viewport-dependent branch. The card's requirement is a fix
-that cannot break at an untested viewport or flow length, and a layout with no
-breakpoint and no measurement is the form that satisfies it. Horizontal-with-
-scroll was ruled out for both occurrences: it leaves steps behind a gesture.
-
-Pinned in `ApprovalsInboxPage.stepProgressVertical.test.tsx` and
-`RecordApprovalsPanel.stepProgressVertical.test.tsx`. "The stepper renders" is
-green against the broken code too — every step was always in the DOM, and the
-clipping was layout — so the suites assert the property the defect names
-instead: no row is `shrink-0`, every label is `min-w-0` and none is
-`whitespace-nowrap`, nothing in the subtree is an `overflow-x` scroller, and no
-axis, overflow or width-pinning class carries a breakpoint prefix (so there is
-no viewport with untested behaviour). The reported failing regime is exercised
-directly with the reporter's own six CJK labels, and a 2/5/6/12-step sweep pins
-that the layout classes are byte-identical across all four, so no count
-threshold can put some other flow length back on the old path.
-
-The two steppers are kept identical by hand rather than extracted to a shared
-component: they live in different packages, and deduplicating them is a
-refactor with its own surface. Filed separately.
diff --git a/.changeset/approver-display-name-5414.md b/.changeset/approver-display-name-5414.md
deleted file mode 100644
index f44198385..000000000
--- a/.changeset/approver-display-name-5414.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-'@object-ui/app-shell': minor
-'@object-ui/i18n': minor
----
-
-The approval panel identifies the pending approver by name, not by a truncated raw id.
-
-A record waiting on a position rendered its approver as `positi…ager` — the
-engine reference `position:sales_manager`, 22 characters, past the identity
-formatter's 14-character truncation arm and middle-truncated to fit its chip. The
-step names beside it were human prose; the one line answering *who is holding
-this record* was an internal identifier, and not even a complete one. The same
-reference reached the admin-override confirm dialog un-truncated, so a paragraph
-of plain governance prose ended `— position:sales_manager` (objectui#5414).
-
-Both surfaces now resolve the reference before rendering, in three tiers, most
-authoritative first. The server's own `pending_approver_names` wins whenever it
-answers, and a backend that resolves its own slate costs the record page no extra
-request. Otherwise the console reads the directory row the spec's approver
-binding names — `sys_position.label` gives `Sales Manager` / `销售经理` — and,
-for a position, who fills the seat (`Sales Manager · Zhang Wei, Li Na`). With no
-adapter and no row, the machine name still prettifies into prose rather than
-truncating. The raw reference stays on hover, which is where an internal
-identifier belongs.
-
-An unstaffed position is surfaced rather than hidden: `销售经理(暂无在岗人员)`
-is actionable where `positi…ager` is not, and it is the motivating rescue case
-for the admin-override path. Staffing is deliberately tri-state — a
-`sys_user_position` read the viewer is not permitted to make leaves the seat's
-staffing UNKNOWN and says nothing, because "I could not look" is a different
-claim from "nobody holds it" and only one of them is safe to print on a
-governance surface.
-
-Two locale keys are added across all ten packs: `approvalsInbox.approverUnstaffed`
-and `approvalsInbox.approverNameSeparator`. The separator is a translated
-punctuation key rather than `Intl.ListFormat`, which was measured on this tree
-joining `['张伟','李娜']` into `张伟李娜` for `zh` — two names run together with
-no separator, reading as one person's name.
-
-The directory-backed kinds and their value columns are read from
-`@objectstack/spec`'s `APPROVER_VALUE_SOURCES` rather than restated, so a new
-approver type is covered the day the spec publishes it. Id-valued kinds
-(`user` / `team` / `department`) keep the existing middle-truncation: a row id
-has no prose to recover, and that arm is objectui#3461's answer, not this card's
-defect.
diff --git a/.changeset/approver-identities-read-positions-5424.md b/.changeset/approver-identities-read-positions-5424.md
deleted file mode 100644
index 2137b2b8c..000000000
--- a/.changeset/approver-identities-read-positions-5424.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-'@object-ui/app-shell': patch
-'@object-ui/console': patch
----
-
-fix(approvals): derive approver identities from `positions`, not the retired `user.roles` (objectui#5424)
-
-Framework ADR-0090 D3 renamed the session's `roles` key to `positions` with no
-deprecation window, and the protocol-17 session face emits no `roles` key at
-all. Three client sites still read it:
-
-- **`sharedUserFeeds.approverIdentities`** — the bell badge, the bell's
- Approvals tab and Home's To-do card. It read nothing else, so it sent **no
- `role:` identity at all**: an approval addressed to a position rather than to
- a person matched nothing and vanished from all three surfaces, silently.
-- **`approvalsApi.buildApproverIdentities`** — "My Pending" and the
- Approve/Reject enablement. It also splits the scalar `user.role`, so it
- degraded rather than dying: it still yielded `role:user` while dropping every
- business position name (`manager`, `finance_approver`, …).
-- **`AppContent`'s expression user** — forwarded a `roles` key that was always
- `undefined` into every CEL predicate context. Removed; `positions` and
- `isPlatformAdmin` were already forwarded correctly beside it.
-
-The retired spelling is **not** kept as a fallback — pairing the two is what
-ADR-0090 D3 forbids, and `packages/auth/src/types.ts` says so on the
-declaration.
-
-`AuthGuard`'s `requiredRoles` gate (the fourth surviving reader) is deliberately
-untouched: it is a semantics decision, not a rename, and is deferred to a
-maintainer ruling.
diff --git a/.changeset/appsidebar-deprecated-5720.md b/.changeset/appsidebar-deprecated-5720.md
deleted file mode 100644
index 404094e32..000000000
--- a/.changeset/appsidebar-deprecated-5720.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`AppSidebar` is now `@deprecated` — use `UnifiedSidebar` instead.
-
-A census (objectui#5720) found `AppSidebar` has no in-repo mount point
-(`ConsoleLayout` renders `UnifiedSidebar`, not this component) and no
-downstream consumer visible anywhere across this org's GitHub-visible
-repositories. It stays exported — from the package barrel and the published
-`dist/index.d.ts` — because `@object-ui/app-shell` is a public npm package
-(`publishConfig.access: "public"`) and an external consumer outside this org
-is structurally invisible to that census; that is why it is deprecated
-rather than deleted outright. No behavior change in this release — the
-component still renders exactly as before. Its admin nav cluster is a
-near-duplicate of `UnifiedSidebar`'s and has already drifted from it (it
-gates only `sys-marketplace` on the workspace-admin flag, where
-`UnifiedSidebar` gates the whole cluster); that divergence is not being
-reconciled, since the component is scheduled for removal rather than kept
-in parity — see objectui#5817 for the removal plan.
-
-Migration: replace any `AppSidebar` usage with `UnifiedSidebar` from the same
-package.
diff --git a/.changeset/authguard-positions-5424.md b/.changeset/authguard-positions-5424.md
deleted file mode 100644
index 438aa5d16..000000000
--- a/.changeset/authguard-positions-5424.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@object-ui/auth": minor
----
-
-`AuthGuard`'s `requiredRoles` now matches against `user.positions` — the one published spelling (framework ADR-0090 D3) — keeping the `user.role` scalar fallback only for sessions that carry no `positions` key at all (guest / legacy identities). This restores position-holder admission on protocol-17 deployments (where the retired `roles` key is never emitted) and closes the matching over-admission: a coarse scalar `role` no longer passes a gate whose position the server says the user lacks. Preview mode now emits `positions: [role]` instead of the retired `roles` key — it was the last producer — so preview identities are shaped like protocol-17 sessions for every `positions` consumer. The hand-copied `roles?: string[]` mirror key is removed from the client `AuthUser` type; breaking only for TypeScript consumers that compiled against `AuthUser['roles']` (read `positions` instead — no runtime payload ever carried `roles` at protocol 17). Maintainer ruling 2026-08-22 on objectui#5424.
diff --git a/.changeset/built-moment-transition-5799.md b/.changeset/built-moment-transition-5799.md
deleted file mode 100644
index b42946326..000000000
--- a/.changeset/built-moment-transition-5799.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@object-ui/app-shell': minor
----
-
-The built-moment transition (#5799): a build conversation that has produced a WHOLE-APP build auto-routes to `/studio//interfaces` — on live completion and on reopening the conversation — after idempotently re-keying the thread under the `app::build` cache key the Studio dock resolves, so the workbench's right rail continues the SAME conversation. Cold start keeps the full-page surface; the dock's 以完整页面打开 door carries a sticky per-conversation opt-out so the one sanctioned way back is never bounced straight to Studio.
diff --git a/.changeset/calendar-explain-network-escape-5438.md b/.changeset/calendar-explain-network-escape-5438.md
deleted file mode 100644
index 18359f993..000000000
--- a/.changeset/calendar-explain-network-escape-5438.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
----
-
-Tests only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. No package `src/` is touched; the only file changed is
-`packages/plugin-calendar/src/object-calendar-renderer.propsContract.test.tsx`.
-
-Stops `object-calendar-renderer.propsContract.test.tsx` from making four real outbound
-connections to `127.0.0.1:3000` while it stays green.
-
-`happy-dom`'s default document origin is `http://localhost:3000/`, so a relative-URL
-`fetch` that nothing intercepts resolves against that origin and leaves the process for
-real. The escaping request is `@object-ui/plugin-detail`'s record-level explain probe
-(`useRecordEditable`, `POST /api/v1/security/explain`): the file's one DEFAULT-navigation
-`onEventClick` case opens `ObjectCalendar`'s overlay drawer, which renders
-`RecordDetailDrawer` → `DetailView`, which gates its Edit/Delete CTAs on a per-record
-write verdict. With no `apiFetch` on the `SchemaRendererProvider` this file wraps every
-case in, the hook's `apiFetch ?? fetch` fallback reaches the bare global `fetch` — and the
-hook fails open on the resulting connection failure, so the escape was invisible to every
-existing assertion.
-
-Same defect class as objectui#3339 (`plugin-detail`, closed by PR #4105), objectui#5225
-(`plugin-report`) and objectui#5280 (`plugin-dashboard` `DatasetWidget`) — a new consumer
-of an already-diagnosed hook, not a new root cause. Fixed the way #4105 settled it:
-`installExplainDouble()` answers the probe from a recording double instead of the
-network, installed for every case in the file (not only the one that reaches it, since
-the probe is `DetailView`'s own wiring and invisible from this file's schema authoring).
-`visible: true` reproduces the pre-fix fail-open behaviour exactly, so no existing
-assertion changes meaning.
-
-A new pinned test — the file's counter-probe — opens the DEFAULT-navigation drawer and
-asserts the double is actually reached, twice (`update` and `delete`), with the expected
-URL and request body. Confirmed load-bearing by reverse verification: with the double
-temporarily disabled, the same case brings back all four `ECONNREFUSED` and fails that
-new assertion (`explainCalls` stays empty instead of reaching length 2).
diff --git a/.changeset/calendar-readme-schema-keys-5045.md b/.changeset/calendar-readme-schema-keys-5045.md
deleted file mode 100644
index eaf0ed083..000000000
--- a/.changeset/calendar-readme-schema-keys-5045.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-'@object-ui/plugin-calendar': patch
----
-
-`README.md`'s "Schema API / CalendarView" block described a `CalendarViewSchema`
-that does not exist. Measured against the interface itself
-(`packages/types/src/complex.ts`) and its zod mirror: `events` — the schema's
-only required key besides `type` — was published as `events?`, so a reader
-following the README omits it and TypeScript rejects the node; `defaultDate` was
-`string` where the schema says `string | Date`; and `onDateClick` was listed as a
-schema key when it is a `CalendarViewProps` **component** prop, sending readers
-to a different package's surface for a key `calendar-view` does not have (the
-schema's key is `onDateChange`). The block also listed 6 of the schema's 13 keys
-with nothing saying it was a summary (objectui#5045).
-
-The block now carries the requiredness the schema declares, names itself a
-partial summary of `CalendarViewSchema`, and adds the author-facing
-`defaultView` / `view` / `views` / `editable` / `date`. It also states plainly
-what the registered `calendar-view` renderer actually reads — it builds events
-from the node's `data` array and drops an authored `events` key (objectui#4433) —
-so the corrected requiredness does not itself become a new wrong instruction.
-
-This is a documentation fix to a file `plugin-calendar` publishes to npm, which
-is why it carries a version: the npm landing page only picks up the correction
-on a release. No behaviour, export, type, or `dist` byte changes. The pin test
-added alongside it publishes nothing.
diff --git a/.changeset/calendar-view-event-rename-5044.md b/.changeset/calendar-view-event-rename-5044.md
deleted file mode 100644
index 0ba8d30bf..000000000
--- a/.changeset/calendar-view-event-rename-5044.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-'@object-ui/plugin-calendar': patch
----
-
-`@object-ui/plugin-calendar` now exports the `CalendarView` component's runtime event
-type as **`CalendarViewEvent`**, and keeps `CalendarEvent` as a **`@deprecated` alias**
-of it. **Non-breaking:** the alias is a working re-export denoting the same type, so
-code importing `CalendarEvent` from this package keeps compiling unchanged — nothing is
-removed and no behaviour changes.
-
-Why: `@object-ui/types` exports its own `CalendarEvent`, the AUTHORING event
-(`id: string`, `start` / `end` accept ISO strings with `end` required, plus
-`description`), while this package's was the runtime event (`id: string | number`,
-`start: Date`, `end?: Date`). Neither is assignable to the other, and IDE auto-import
-chose between the two identical names essentially at random — the wrong pick surfaced as
-a remote `TS2322` about `Date` rather than as a wrong import, which is how this package's
-own README example stayed uncompilable through an earlier import-path fix. The authoring
-type keeps the canonical `CalendarEvent` name; the runtime type gets the self-describing
-one (objectui#5044, following the `ObjectCalendarProps` -> `ObjectCalendarComponentProps`
-rename in objectui#4650).
-
-Write `CalendarViewEvent` in new code.
diff --git a/.changeset/calendar-view-mode-agenda-retired-5740.md b/.changeset/calendar-view-mode-agenda-retired-5740.md
deleted file mode 100644
index f50f849b4..000000000
--- a/.changeset/calendar-view-mode-agenda-retired-5740.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-'@object-ui/types': minor
----
-
-`'agenda'` leaves `CalendarViewMode` and the zod `CalendarViewModeSchema`
-(objectui#5740 — the value-level residue of objectui#5667's key-level
-convergence of `CalendarViewSchema` on the registered `calendar-view`
-renderer's measured read set; ADR-0049 enforce-or-remove).
-
-The union declared a value nothing enforced: the registered renderer's `view`
-input declares `enum: ['month','week','day']`, `resolveAuthoredView` resolves
-any off-enum value — `'agenda'` included — to `undefined` (the component's
-`'month'` default), and `CalendarView` renders no agenda view. An author
-writing the type-legal, zod-valid `view: 'agenda'` got a month calendar with
-no error or warning. No in-repo, example, or catalog app authors
-`view: 'agenda'` (measured during objectui#5667's sweep and re-measured for
-this change, including the objectstack tree).
-
-**This narrows the accept set — unlike #5667's key retirements, which created
-no new rejections.** `view` is a declared key, and declared keys are validated
-even under `.passthrough()`, so `view: 'agenda'` is now a **validation error
-that previously parsed green** (an `invalid_value` issue on the `view` path,
-offering `month`/`week`/`day`). Undeclared keys still pass through unchanged.
-Breaking on the published zod surface; ships as `minor` per this repo's
-version-alignment policy (majors track `@objectstack`).
-
-The runtime boundary is unchanged: an off-union `view` in raw metadata still
-falls back to the component's `'month'` default at the renderer, and the
-registry input already declared the three-value enum. Docblocks, the schema
-reference table, and the zod `describe` no longer teach an `'agenda'`
-fallback.
diff --git a/.changeset/calendar-view-schema-converge.md b/.changeset/calendar-view-schema-converge.md
deleted file mode 100644
index 61f9a3c1d..000000000
--- a/.changeset/calendar-view-schema-converge.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/types': minor
-'@object-ui/plugin-calendar': patch
----
-
-`CalendarViewSchema` (TS interface and zod mirror) converges on the registered
-`calendar-view` renderer's measured read set (objectui#5667, maintainer ruling
-option A — the renderer is authoritative).
-
-**Breaking for consumers of the published type** (deliberate; per-repo policy
-breaking changes ship as `minor` — the fixed group's `major` tracks
-`@objectstack`):
-
-- Nine inert keys are retired: `events` (the interface's only required key,
- which the renderer deliberately drops — objectui#4433), `defaultView`,
- `defaultDate`, `date`, `views`, `editable`, `onEventCreate`,
- `onEventUpdate`, `onDateChange`. None had a read site on the authored-node
- path and no measured app authors them (ADR-0049 enforce-or-remove).
-- The type now declares what the renderer actually reads: `data`, `titleField`,
- `startDateField`, `endDateField`, `allDayField`, `colorField`, `view`,
- `currentDate`, `allowCreate`, `className`, plus the two host-only function
- hatches it forwards (`onEventClick`, `onViewChange`).
-- Practical radius, measured: `BaseSchema` carries an index signature and the
- zod `BaseSchema` is `.passthrough()`, so nodes still authoring retired keys
- neither fail to compile nor get rejected at validation — they are simply no
- longer declared, documented, or type-checked. The material accept change is
- that zod no longer **requires** `events`: a `{ "type": "calendar-view" }`
- node without it now validates (previously the one key validation demanded
- was the one key guaranteed to do nothing).
-
-Runtime renderer behaviour is unchanged. `@object-ui/plugin-calendar`'s README
-and `content/docs/api/schema-reference.md` are repaired to the converged
-surface in the same change, so no copy of the old contradiction survives.
diff --git a/.changeset/canvas-design-run-5800.md b/.changeset/canvas-design-run-5800.md
deleted file mode 100644
index 18e7c4938..000000000
--- a/.changeset/canvas-design-run-5800.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@object-ui/app-shell': minor
----
-
-设计⇄运行 on the Interfaces canvas (#5800): a two-state switch in the canvas header flips the SAME renderer between design (selection + inspector + design overlays) and an interactive runtime (click 新建, enter records) — ADR-0080's design=run pivot made visible; selection context survives the round trip. The topbar's 打开应用 teleport is retired (run mode is the in-workbench way to try the app), and the topbar's app detection now matches the pillar's (draft-app fallback, re-resolved on draft saves and the metadata-refresh pulse) so a deep-link to /access can no longer claim the package has no app while /data shows one.
diff --git a/.changeset/catalog-layout-props-sweep-4891-4890.md b/.changeset/catalog-layout-props-sweep-4891-4890.md
deleted file mode 100644
index 92f13f187..000000000
--- a/.changeset/catalog-layout-props-sweep-4891-4890.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-'@object-ui/components': minor
----
-
-`stack` now reads its spacing from `gap` and nothing else — the undeclared
-`spacing` key it also accepted is gone (objectui#4890).
-
-`StackSchema extends Omit`, whose spacing key is `gap`.
-`spacing` was declared by nothing: not the TypeScript interface, not the zod
-mirror, not the renderer's own `inputs` registration. `stack.tsx` read it anyway,
-as `schema.gap ?? (schema as any).spacing ?? 2` — and the `as any` is the whole
-story, since it existed to get past the type system saying the key was not there.
-A lenient consumer leg does not stay in the consumer: it becomes a second
-de-facto contract that producers write to, and 135 nodes across 39 files of the
-shipped schema catalog did exactly that. Every one of them rendered correctly, so
-nothing ever pointed at it, while the examples went on teaching the key to every
-author who copied them.
-
-The trap it was one edit away from springing: `flex` — semantically a `stack`
-with a `direction` — never read `spacing`, so re-typing any of those nodes would
-have dropped the spacing to the default silently. Fixed at the producer
-(AGENTS.md #0.1): those nodes now author `gap`, carrying the same value, and the
-alias is deleted rather than legalised into `StackSchema`, where it would only
-have been a second name for `gap`.
-
-**If you author `spacing` on a `stack`**, rename it to `gap`; the value and the
-rendering are unchanged. A `stack` still carrying `spacing` now renders the
-default gap, exactly as a `flex` always did.
-
-Also in the same sweep, and visible only in the published example catalog rather
-than in any package API: 140 catalog nodes that were already `flex` / `stack` /
-`container` stopped hand-writing their own declared props in `className`
-(`items-center` → `align`, `justify-between` → `justify`, `gap-2` → `gap`,
-`flex-wrap` → `wrap`, `p-4` → a container's `padding`) — 231 tokens in all
-(objectui#4891). Breakpoint-prefixed overrides and everything decorative stay in
-`className`, because the props are not responsive. Both facts are ratcheted in
-`examples/schema-catalog/test/layout-props-conversion.test.tsx`.
diff --git a/.changeset/chart-height-chain-5451.md b/.changeset/chart-height-chain-5451.md
deleted file mode 100644
index 3ab583677..000000000
--- a/.changeset/chart-height-chain-5451.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@object-ui/plugin-charts': patch
----
-
-`ObjectChart`'s wrapper div now carries `h-full`, keeping the height chain intact from a dashboard grid cell's declared height down to the element Recharts measures. Previously the chain died at the plain auto-height wrapper: `height: 100%` on the chart container computed to `auto`, Recharts measured a permanent zero, and only the `CHART_MIN_HEIGHT` floor (#5503) kept dashboard charts visible — at a fixed floor height instead of filling the cell (#5451). Under auto-height parents `h-full` resolves to `auto`, so non-dashboard hosts are unchanged.
diff --git a/.changeset/chat-transport-memo-4187.md b/.changeset/chat-transport-memo-4187.md
deleted file mode 100644
index 93978f70c..000000000
--- a/.changeset/chat-transport-memo-4187.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-'@object-ui/plugin-chatbot': patch
----
-
-`useObjectChat` no longer rebuilds its `DefaultChatTransport` on every render
-(objectui#4187).
-
-The transport `useMemo` listed the caller's `body` and `headers` in its dep list.
-Both are object props and every caller passes a fresh literal each render — the AI
-page's chat pane builds its `body.context` inline — so the memo never hit and a
-transport was constructed on every render of every chat surface, which during a
-streaming turn is once per token batch.
-
-`body` and `headers` are now read through refs inside
-`prepareSendMessagesRequest`, the idiom this hook already uses for the live model
-(`modelRef`) and the handoff conversation id (`parentConvRef`), and they are gone
-from the dep list. Unlike memoizing at each call site, a future caller cannot
-undo it.
-
-No user-visible behaviour changes: `@ai-sdk/react` keeps the transport in a ref
-and re-keys its `Chat` only on `chat`/`id` (verified against the installed
-4.0.68), which `useObjectChat` passes neither of, so the message thread was never
-at risk — the rebuild was pure waste. The one real difference is *when* the two
-values are sampled: a send now reads them at send time, so it observes the values
-of the most recent render instead of those of the last render that happened to
-rebuild the transport. That is never staler than before, and it is pinned by
-`useObjectChat.transportIdentity.test.tsx`.
diff --git a/.changeset/check-schema-positive-marker-and-skip-count.md b/.changeset/check-schema-positive-marker-and-skip-count.md
deleted file mode 100644
index 84f3ac9f6..000000000
--- a/.changeset/check-schema-positive-marker-and-skip-count.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-'@object-ui/cli': minor
----
-
-`objectui check` judges a file's `type` only when the file is recognisable as an ObjectUI schema, and reports how many it declined to judge.
-
-A root `type` was treated as a component key wherever it appeared. `type` heads at
-least seven unrelated JSON vocabularies, and the most common of them is
-`package.json`'s `"type": "module"` — so the first line a user saw running
-`objectui check` in their own project was a warning about their own package
-manifest. Measured at this repository's root: 46 warnings, 45 of them
-`package.json` (objectui#5127).
-
-A file now enters type judgement only when its root carries a structural key
-declared on `BaseSchema` — `children`, `body`, `className`, `placeholder`,
-`style`, the `visible`/`hidden`/`disabled` predicate family, `testId`,
-`ariaLabel`. Every other root-`type` vocabulary — JSON Schema's `"array"`, an
-`.eslintrc.json`'s `"commonjs"`, a package manifest's `"module"` — is simply
-never judged. The key set is read out of the node contract rather than invented,
-and it is closed: it grows only when `BaseSchema` grows.
-
-A list of filenames to exclude was the alternative and was rejected: it is a
-second hand-maintained list of the shape objectui#5115 had just finished
-deleting, and it can only ever enumerate the foreign vocabularies someone already
-thought of. This is a positive marker instead.
-
-Because the marker narrows what is checked, the command now also reports the
-count of files that had a root `type` and no marker, together with the marker
-keys that opt one back in. That number is the coverage this gate gives up until
-schema files are recognisable, and printing it is what keeps the loss visible
-rather than silent. The `.yaml`/`.yml` half of the scan is unchanged — it was
-never type-judged, before this change or after it. Exit codes are untouched: a
-JSON parse failure remains the only thing that fails the run.
-
-No public `$schema` URL is introduced. An earlier revision also admitted a file
-whose root `$schema` had an `objectui.org` host; the maintainer ruled against
-minting that identifier (2026-08-20, objectui#5127), so the structural key is the
-only marker. Because the matching was host-based rather than literal, that arm
-can be added later without invalidating a single file.
diff --git a/.changeset/cli-validate-field-widget-namespace-5449.md b/.changeset/cli-validate-field-widget-namespace-5449.md
deleted file mode 100644
index 2984a2ef4..000000000
--- a/.changeset/cli-validate-field-widget-namespace-5449.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-'@object-ui/types': patch
-'@object-ui/cli': patch
----
-
-`objectui validate` now refuses a form field whose widget id names a namespace
-other than `field:`, matching the verdict `@object-ui/core`'s `validateSchema`
-has given since objectui#5375 (objectui#5449).
-
-The CLI reaches `FormFieldSchema` through `safeValidateSchema`, and that schema
-declared `type` and `widget` as bare optional strings — so a field typed
-`ui:password` validated clean while the runtime validator rejected the same
-document with `UNRESOLVABLE_FIELD_WIDGET_NAMESPACE`. The CLI is the surface an
-author actually runs before shipping, so it was the one handing out the false
-green: an author did exactly the diligence objectui#5375 asks for and still
-shipped metadata that renders a secret into a plain text box.
-
-A `superRefine` on `FormFieldSchema` now states the rule, mirroring core's
-precedence (`widget` before `type`), the key it blames, its error code and its
-message verbatim, so the two entry points cannot describe one defect two ways.
-
-**This rejects documents that previously validated.** Only colon-qualified
-field widget ids outside the `field:` namespace are affected — `field:`-prefixed
-ids and bare names such as `password` still pass, registered or not. A field
-carrying, say, `type: 'ui:password'` must be rewritten as `password` or
-`field:password`; it never rendered as a password box in any case.
-
-Which of the repo's authoring-time validators is canonical remains open
-(objectui#4631) — this states the rule on the zod side rather than unifying
-them.
diff --git a/.changeset/cloud-connection-bind-failure-i18n-5054.md b/.changeset/cloud-connection-bind-failure-i18n-5054.md
deleted file mode 100644
index 68a88aac2..000000000
--- a/.changeset/cloud-connection-bind-failure-i18n-5054.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@object-ui/app-shell': patch
-'@object-ui/i18n': patch
----
-
-A cloud-connection bind failure now reads in the user's language whichever clock
-noticed it (objectui#5054).
-
-One abandoned device approval could be noticed by either of two clocks, and the
-Cloud Connection panel had a different answer for each. When the panel's own
-`expires_in` deadline fired first it rendered `cloudConnection.errors.expired` —
-translated in all ten packs. When the SERVER noticed first, `/bind/poll` answered
-HTTP 400 with `message: 'Device authorization failed: expired_token'`; `getJson`
-threw a bare `Error` carrying only that sentence, and the catch rendered it
-verbatim. Same user, same failure, two languages, decided by which clock got
-there first — visible on a zh console as the same abandoned approval reading
-Chinese or English depending on whether the tab sat open past `expires_in`.
-
-`getJson` now carries the envelope's `declaredCode` and `code` across its throw,
-and a single closed map turns the two RFC 8628 outcomes a user can actually cause
-into console copy: `expired_token` → the existing `cloudConnection.errors.expired`,
-`access_denied` → a new `cloudConnection.errors.accessDenied` added to all ten
-locale packs. `declaredCode` is read first, because ADR-0112 keeps the upstream
-spelling there — `code` is `DEVICE_CODE_FAILED` for both.
-
-Every other code is unchanged: `invalid_grant`, and anything upstream invents
-next, still render the wire `message`, which stays the single source of truth for
-failures this console has no copy for. No API, export or resolver was widened.
diff --git a/.changeset/clusterradius-unit-doc-5020.md b/.changeset/clusterradius-unit-doc-5020.md
deleted file mode 100644
index 00f5731ef..000000000
--- a/.changeset/clusterradius-unit-doc-5020.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-'@object-ui/plugin-map': patch
----
-
-`ObjectMapProps.clusterRadius`'s JSDoc said "in pixels"; `clusterMarkers` has
-always used it as a coordinate-degree grid cell edge (`radius / 2 ** zoom`,
-divided into the marker's `[lng, lat]` degrees), not a screen-space radius —
-a host tuning clustering granularity by the documented unit would get a
-completely different result than intended (objectui#5020).
-
-No behavior, default, or name changes: clustering, the >100-visible-marker
-auto-threshold, and tap-through zoom are unaffected, and `clusterRadius` has
-no call sites outside `plugin-map/src` today (re-confirmed repo-wide,
-including `apps/`, `examples/`, and the `objectstack` spec/server repo — the
-default of `50` is what runs everywhere). This is a doc-comment correction
-only, bringing the JSDoc in line with the README's already-correct wording
-(post objectui#5002).
-
-The latitude-anisotropy trade-off (a degree grid distorts east-west as
-latitude rises) is a known design trade-off, not part of this fix.
diff --git a/.changeset/componentinput-reexport-4972.md b/.changeset/componentinput-reexport-4972.md
deleted file mode 100644
index 910e0a734..000000000
--- a/.changeset/componentinput-reexport-4972.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-'@object-ui/core': minor
-'@object-ui/types': minor
----
-
-`ComponentInput` is now declared once and re-exported, instead of restated in three
-places (objectui#4972).
-
-`@object-ui/core`'s `ComponentInput` (`registry/Registry.ts`) and `@object-ui/types`'
-plugin-scoped `ComponentInput` (`plugin-scope.ts`, published as `PluginComponentInput`)
-were structural copies of the interface in `@object-ui/types`' `base.ts`. Both are now
-re-exports of that one declaration, which is the disposition objectui#4580 ruled for the
-identical shape — *a structural copy would reproduce the defect the moment either side
-moved* — and the way `core/src/types/index.ts` already handles `SchemaNode`.
-
-Either side had already moved. `base.ts` declared thirteen keys; both copies declared
-nine, so `min` / `max` / `step` / `placeholder` were missing from **the copy every
-component registration actually imports**. Those four keys were unwritable at any real
-registration — a plain TypeScript error at the call site — while `ComponentInputSchema`
-(the zod schema) and `ComponentMeta.inputs` both accepted them. The publication face
-advertised four keys the authoring face rejected. Measured over the repository, no
-registration had tried to write one yet, so nothing a user hits was broken today; what
-changes is that the four keys become writable, and there is no longer a second
-declaration for the next widening to miss.
-
-`ComponentInput`'s arm vocabulary (`ComponentInputControlType`) was already a single
-declaration imported by all three sites (objectui#3832); this converges the rest of the
-interface.
-
-Measured, not assumed: `@object-ui/core`'s published entry `dist/index.d.ts` is
-byte-identical across the change (sha256 `f6494f80…`, both legs). That gauge is reported
-here only with its control — a probe that added a *required* key to `ComponentInput` left
-the same file byte-identical, because `dist/index.d.ts` is a 63-line barrel of
-`export *` lines that names `ComponentInput` zero times. The gauge that can actually fail
-is the emitted declaration file: `dist/registry/Registry.d.ts` changes, as does
-`@object-ui/types`' `dist/plugin-scope.d.ts`, and those two files are the *only* emitted
-declarations that change in either package.
-
-`WidgetInput`'s union-arm capability is deliberately untouched — a different gate path
-and a separate judgment.
diff --git a/.changeset/componentrendererprops-reexport-4594.md b/.changeset/componentrendererprops-reexport-4594.md
deleted file mode 100644
index 52cad5745..000000000
--- a/.changeset/componentrendererprops-reexport-4594.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-'@object-ui/core': minor
----
-
-`ComponentRendererProps` is now declared once and re-exported, instead of
-hand-declared a second time in `@object-ui/core` (objectui#4594).
-
-`@object-ui/core`'s `ComponentRendererProps` (`src/types/index.ts`) was a
-non-generic interface typing `schema` as `SchemaNode`, while
-`@object-ui/types`' declaration of the same name is generic —
-`ComponentRendererProps< TSchema extends BaseSchema = BaseSchema >` with
-`schema: TSchema`. Same name, both exported from their package entry, from two
-packages the same consumers import together: which declaration a call site got
-depended on which package it reached for, and the two disagree about whether a
-primitive node is admissible. Core's is now a re-export of types', which is the
-disposition objectui#4580 ruled for `SchemaNode` two lines above it in the same
-file, and objectui#4972 for `ComponentInput` — *a structural copy would
-reproduce the defect the moment either side moved*.
-
-**Published-surface effect, and the reason it is not neutral.** Resolved
-through the TypeScript checker from `core/dist/index.d.ts` over a clean rebuild
-of both legs, `ComponentRendererProps` as reached through `@object-ui/core`
-moves from non-generic with
-`schema: BaseSchema | string | number | boolean | null | undefined` to
-`ComponentRendererProps` with `schema: TSchema`, defaulting to
-`BaseSchema`. `schema` therefore **narrows** back to the object form — core's
-copy had silently widened when objectui#4608 made core's `SchemaNode` a
-re-export of types' union — and the type gains a parameter. **Nothing imported
-it**, on either side, re-verified repo-wide on the merged ref, so no call site
-can observe either move; the narrowing is recorded here because it is a change
-to a published type, not because a consumer is affected.
-
-A compile-time pin now holds the reconciliation from
-`@object-ui/react` — the only position that resolves both packages through
-`node_modules` — alongside the existing `SchemaNode` one. It is a test-only
-addition and emits nothing, so `@object-ui/react` takes no bump of its own.
diff --git a/.changeset/confirm-card-terminal-5695.md b/.changeset/confirm-card-terminal-5695.md
deleted file mode 100644
index 7d3d67a83..000000000
--- a/.changeset/confirm-card-terminal-5695.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@object-ui/plugin-chatbot': minor
-'@object-ui/app-shell': patch
-'@object-ui/i18n': patch
----
-
-The 确认修改 (confirm changes) card now carries a UI-owned terminal state after approval (#5695): `detectReplayOutcome` lifts the confirm-replay envelope (`replay_*` tool results) into 应用中 / 已生效 / 已暂存为草稿(含内联发布)/ 未生效(含 publishError 首行), rendered on the original card across the live, hydration/share, and localStorage-cache converters. A failed in-turn publish no longer rehydrates as an ordinary draft card with a live Publish button — the UI-rendered refusal is the layer a model cannot narrate over. New `console.ai.changesApplying/Applied/Drafted/Failed` keys in all ten locale packs.
diff --git a/.changeset/console-action-dispatch-envelope-5611.md b/.changeset/console-action-dispatch-envelope-5611.md
deleted file mode 100644
index a7e8555f2..000000000
--- a/.changeset/console-action-dispatch-envelope-5611.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
----
-
-Internal: the console's admin-override notice now travels on a declared dispatch
-type instead of a cast (objectui#5611). No published API and no runtime
-behaviour changes, so this changeset declares "no release" rather than a bump —
-`@object-ui/app-shell` source moved, nothing it exports did.
-
-`overrideNotice` is the safety copy shown once, ahead of a privileged admin
-override that finalises an approval step over approvers who have not acted. Its
-producer (`DeclaredActionsBar`) reached its reader (`useConsoleActionRuntime`'s
-param-collection dialog) through a `dispatch as ActionDef` cast on one side and
-`action?: any` on the other, so nothing declared the key anywhere and the two
-could drift apart in silence — rename it on either side and the notice stops
-appearing with every test still green, because each side's suite spells the key
-itself.
-
-Both ends now share one declaration: `ConsoleActionDispatch`
-(`ActionDef & { overrideNotice?: string }`), a HOST-composed envelope that lives
-at the seam, in the one package where producer and reader both live. The cast is
-gone and both param-collection handlers narrow off `any` — which is what puts
-those functions under the compiler at all.
-
-The published `ActionDef` deliberately does NOT declare the key (maintainer
-ruling 2026-08-22): it is the authored-metadata mirror, and `overrideNotice` is
-the first key no author supplies, so declaring it there would make an unenforced
-key legally writable in metadata. That prohibition still holds exactly as
-written — `ActionDef` and `ACTION_DEF_KEYS` are unchanged.
-
-`@object-ui/core` is NOT untouched, and the reason is a separate declaration:
-the same ruling's item 4 adds `HOST_DISPATCH_ACTION_KEYS` to the key inventory
-so the dev-mode warning stops calling the host-composed key unknown. That change
-carries its own changeset (`host-dispatch-action-keys-5611.md`) and its own
-patch bump; this one remains the app-shell half, which publishes nothing.
diff --git a/.changeset/console-boot-request-dedup-5544.md b/.changeset/console-boot-request-dedup-5544.md
deleted file mode 100644
index 44270d6e8..000000000
--- a/.changeset/console-boot-request-dedup-5544.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-'@object-ui/types': patch
-'@object-ui/app-shell': patch
-'@object-ui/console': patch
----
-
-The console's cold load no longer asks `/api/v1/runtime/config` or
-`/auth/me/localization` twice (objectui#5544).
-
-Two pairs of boot callers were racing each other for the same URL, with no shared
-provider between them, so no guard inside either component could see the other:
-
-- `GET /api/v1/runtime/config` — the pre-React branding script inlined in
- `apps/console/index.html` (it runs during HTML parse so the tab title and
- favicon are the operator's before the bundle is fetched) and
- `initRuntimeConfig()`. Measured ×2 on prod and on staging. This is the
- expensive one: the console `await`s `initRuntimeConfig()` before
- `createRoot().render()`, so the duplicate sat on the critical path to first
- paint, and at the control plane's ~0.5–1.4 s for this endpoint it also pushed
- boot concurrency further past the server's pool knee.
-- `GET /api/v1/auth/me/localization` — `seedTenantLanguage()` on a device's true
- first visit and `LocalizationFetchProvider` on every boot. The seed keeps
- running past its 500 ms race by design and the provider mounts the moment that
- race resolves, so on a first visit the two overlap. Measured ×2 on staging.
-
-`@object-ui/types` gains `sharedGetJson()`: callers that ask for the same GET
-while one is already in flight join that request instead of starting another. It
-shares the in-flight promise and nothing else — the entry is deleted the instant
-the request settles, so there is no cache, no TTL and no stale window, and a
-caller arriving after settle fetches fresh exactly as before. Rejections fan out
-to every sharer with the status intact (`LocalizationFetchProvider`'s retry
-policy still sees its own 503), each caller receives its own copy of the parsed
-body, and only GETs are eligible — a non-GET is refused rather than quietly
-rewritten.
-
-Requests that differ in credentials mode or headers keep separate identities, so
-the console's two deliberate `auth/get-session` calls — one Bearer-only with the
-cookie omitted to detect a stale token, then one through the cookie — stay two
-requests. Collapsing those would have destroyed the signal the first one exists
-to read.
-
-No component receives anything different: same payloads, same errors, one fewer
-round trip.
diff --git a/.changeset/console-form-container-specs-one-declaration-5596.md b/.changeset/console-form-container-specs-one-declaration-5596.md
deleted file mode 100644
index 6f199b8c8..000000000
--- a/.changeset/console-form-container-specs-one-declaration-5596.md
+++ /dev/null
@@ -1,68 +0,0 @@
----
-'@object-ui/app-shell': patch
-'@object-ui/console': patch
----
-
-The two form CONTAINER contracts now have ONE declaration each, derived from
-`@objectstack/spec`, and the console reads them instead of its own copies.
-
-objectui#5542 converged the LEAF of this contract — the field spec — and left the
-two containers above it untouched, because converging them was a bigger call than a
-mechanical import. `FormSectionSpec` and `FormViewSpec` were each hand-declared
-twice under the same names, once in `packages/app-shell`'s `SchemaForm.tsx` and once
-in `apps/console`'s `FormPage.tsx`. Unlike the leaf — whose console copy was a clean
-subset — these two had **already drifted, in both directions**, so neither copy was a
-subset of the other and there were two live answers to "what may an author write":
-
-- `FormSectionSpec` — app-shell declared `description` / `visibleWhen` / `visibleOn`;
- the console declared none of them. The console's `columns` admitted the string arm
- (`'1' | '2' | '3' | '4'`); app-shell's took numbers only.
-- `FormViewSpec` — the console declared `label` / `groups` / `sharing` /
- `submitBehavior`; app-shell stopped at `type` plus `sections`.
-
-The drift is decided by asking the **contract**, not by picking a side. `columns`
-does admit the string arm (`FormSectionSchema.columns` unions `z.enum(['1','2','3','4'])`
-with the four numeric literals, folded to a number by its own transform), so
-app-shell's numbers-only declaration was rejecting metadata the platform accepts —
-objectui#5040's own symptom, not a deliberate narrowing. `label` on the form view is
-the opposite answer: `FormViewSchema` **rejects** it (`unrecognized_keys`, measured
-against the installed `@objectstack/spec` 17.0.0), because a form config is titled,
-not labelled. The value that read actually finds is the VIEW's identity label, which
-arrives on the `ExpandedViewItem` envelope or beside the config on a flattened
-runtime overlay — so it is declared on `FormPage.tsx`'s own `FormViewBody`, next to
-the body it unwraps, rather than smuggled onto the form contract.
-
-Both types are therefore **derived from the spec's own `FormSection` / `FormView`
-with named narrowings** — the repo's sanctioned form for a spec-shaped local type
-(`scripts/check-spec-symbol-derivation.mjs`) — rather than restated. Every key the
-two layers agree on comes from the spec and cannot fall behind it; the four positions
-where this layer is deliberately narrower are each named in an `Omit` list and
-restated once next to its reason: `fields` keeps the converged 26-key leaf (deriving
-it would silently re-open #5542), and `label` / `description` / `visibleWhen` /
-`visibleOn` keep the shapes this repo's renderers and evaluators actually consume
-rather than the spec's `I18nLabel` and `ExpressionInput`. `apps/console`'s
-`submitBehavior` union — previously hand-written under the comment "Mirrors the spec
-FormView.submitBehavior union" — is now read back off the shared type, making the
-mirror structural. `@object-ui/app-shell` re-exports both names from its package root
-(type-only, erased at build — nothing is added to the bundle), because a type that
-cannot be imported is a type that gets retyped.
-
-The pins are what make future drift loud, and each half is pinned on both sides.
-`form-spec.containers.test.tsx` and `FormPage.viewSpec.test.ts` compare the
-non-narrowed half of each type against the spec's own symbol, so re-hand-writing
-either declaration fails `type-check` the day the spec moves rather than years later
-when someone reads two files side by side — and the console's pins read both types
-back out of the **exported** `buildSections` signature rather than naming them, so a
-re-inlined local copy fails even if it agrees on every key on the day it is written.
-Their liveness controls are what stop them being phantom checks: the removed copies
-are pinned NOT equal to the shared types (proving the `Equal` helper still
-discriminates), the renderer's honoured `RenderableSection` is pinned not equal
-either (so the authored-document and honoured-row types cannot be collapsed again),
-and an undeclared key is still rejected (so the derivation smuggled in no index
-signature or `any`). Every narrowing carries a matching negative pin, so "derived"
-cannot quietly become "widened to whatever the spec says".
-
-Behaviour is unchanged — the runtime always accepted these keys. The vitest halves
-prove it: a section spelling its column count as the string `'3'` lays out identically
-to the numeric `3` on both sides, and a section carrying the keys only one side used
-to declare builds the same rows.
diff --git a/.changeset/console-form-field-spec-one-declaration-5542.md b/.changeset/console-form-field-spec-one-declaration-5542.md
deleted file mode 100644
index 59b1a9fdc..000000000
--- a/.changeset/console-form-field-spec-one-declaration-5542.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-'@object-ui/app-shell': patch
-'@object-ui/console': patch
----
-
-The form-field authoring contract now has ONE declaration, and the console reads it
-instead of its own copy.
-
-objectui#5040 was not a missing key. It was that **two hand-written descriptions of
-one contract drifted**, and nothing could notice, because each was only ever checked
-against itself. PR #5537 converged the two app-shell descriptions into
-`views/metadata-admin/form-spec.ts`. A **third** survived in `apps/console`:
-`FormPage.tsx` declared its own nine-key `interface FormFieldSpec`, under the same
-name, in a different package — so the same failure mode stayed fully available.
-
-Measured key by key before choosing a route, because the two honest outcomes are
-"same contract, import it" and "genuinely narrower layer, rename it and pin the
-subset". The console's copy was a strict subset — 9 of the shared type's 26 keys,
-every one identical in type, none console-only — and it sat in a position that
-describes an **authored document**: `FormSectionSpec.fields`, read straight off the
-`/meta/view/:name` payload, the same spec `FormView` metadata-admin renders (both
-files even spell the same six-member `type` union and call the element type
-`FormFieldSpec`). The narrow, renderer-honoured shape is a different type that
-already exists in that file, `RenderableField`. So this was one contract described
-twice, and the console's description was wrong about the document: legal metadata —
-`visibleWhen`, `dependsOn`, `type`, `options`, `immutable`, the recursive `fields`,
-and ten more keys — was undeclared there. That is #5040's own symptom, "the type
-rejects the configuration the runtime accepts", which no runtime test can see.
-
-`@object-ui/app-shell` therefore re-exports `FormFieldSpec` from its package root
-(type-only, erased at build — nothing is added to the bundle), and `FormPage.tsx`
-imports it and deletes the local declaration. Reachability is the load-bearing half:
-a type that cannot be imported is a type that gets retyped, and retyped copies drift.
-`form-spec.ts` itself is untouched.
-
-`FormPage.fieldSpec.test.ts` is the pin that makes future drift loud. It reads the
-field-spec type back out of the **exported** `buildSections` signature rather than
-naming it, so re-inlining a local `interface FormFieldSpec` fails `type-check` even
-if the copy agrees on every key on the day it is written — which is exactly what did
-not happen to the copy this change removes. Its liveness controls are what stop it
-being a phantom check: the removed nine-key shape is pinned NOT equal to the shared
-type (so the `Equal` helper is proven to still discriminate), `RenderableField` is
-pinned not equal to it either (so the honoured-row and authored-document types cannot
-be collapsed again), and an undeclared key is still rejected (so the import did not
-smuggle in an index signature). Behaviour is unchanged: the runtime always accepted
-these keys, and the vitest half proves the same rows are built.
diff --git a/.changeset/console-formpage-runtime-default-seed-5727.md b/.changeset/console-formpage-runtime-default-seed-5727.md
deleted file mode 100644
index d69e013e0..000000000
--- a/.changeset/console-formpage-runtime-default-seed-5727.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-'@object-ui/console': patch
----
-
-The console's form routes no longer seed a RUNTIME `defaultValue` into the
-control, so the server-side resolution the declaration asks for actually happens
-(objectui#5727).
-
-`readPrefill` in `apps/console/src/components/FormPage.tsx` seeded every declared
-default unconditionally. A `defaultValue` may be a literal, or an *instruction*
-the server resolves per insert — a `DEFAULT_VALUE_TOKENS` token (`NOW()`,
-`current_user`) or a CEL Expression envelope. Seeding one of those literally put
-the text `NOW()` into a datetime input on both `/forms/:name` and the public
-`/f/:slug` route, and submitting it sent that string as the field's value —
-which is neither absent nor null, so `ObjectQL.applyFieldDefaults` never resolved
-the declared default and the column stored the token text instead of a timestamp.
-
-The seed is now guarded by `isRuntimeDefault` from `@object-ui/core` — the same
-published classifier `@object-ui/plugin-form`'s `schemaDefaults.ts` guards its
-seeding with, and the one this renderer already reads once removed (through
-`isServerOwnedValue`) for the create-mode `required` carve-out. A runtime default
-leaves the key ABSENT rather than empty, because absent is precisely the case the
-engine resolves.
-
-Nothing else about the prefill precedence moves: a literal default still seeds, a
-stored record value still wins over a default, and an explicit `prefill_=`
-param still wins over both — including for a field whose default is a runtime
-token, since a value a producer supplies is not a declaration awaiting
-resolution.
diff --git a/.changeset/console-formpage-visible-predicates-5594.md b/.changeset/console-formpage-visible-predicates-5594.md
deleted file mode 100644
index 60c95e1a1..000000000
--- a/.changeset/console-formpage-visible-predicates-5594.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-'@object-ui/console': patch
----
-
-The console's standalone form renderer now evaluates conditional field visibility.
-
-`apps/console/src/components/FormPage.tsx` is a **second, independent form renderer**
-— its own `buildSections`, its own JSX — and it serves both the public
-`/f/:slug` route and the internal `/forms/:name` route. It read neither spelling of
-the FormView field visibility predicate: a repo-wide grep for a `visibleWhen` /
-`visibleOn` *read* inside that file returned zero. So a field an author conditioned on
-`record.priority == 'urgent'` — legal, spec-strict metadata that `@objectstack/spec`
-normalises to `visibleWhen` (ADR-0089), and that the metadata-admin designer both
-authors and honours — rendered unconditionally on both routes. Fail-open and silent:
-the author saw the field always, with no diagnostic.
-
-objectui#2212 recorded this exact symptom and PR #2214 fixed it — in a **different
-chain**: `ModalForm` → `resolveFormViewLayout` → `@object-ui/plugin-form`
-`sectionFields.ts` → `@object-ui/components` `renderers/form/form.tsx`. `FormPage.tsx`
-is on that chain at no point, and #2212's regression pin lives with the chain it fixed,
-so nothing in the suite could see this copy. One contract, two implementations, each
-only ever checked against itself.
-
-The wiring is **#2212's ruling applied verbatim** rather than a second predicate
-semantics invented for this renderer, because two form renderers disagreeing about what
-`visibleWhen` *means* would be a worse defect than one renderer ignoring it. The
-predicate goes through the canonical engine — `evalFieldPredicate` (`@object-ui/core`,
-`evaluator/fieldRules.ts`) — so the accepted wire shapes (bare CEL string and
-`{ dialect, source }`), the bound scope (`record.*` = the live input values, `previous.*`
-= the stored record an edit form started from), and the fail-open-but-loud behaviour on
-an unevaluable predicate are the shared ones by construction. Resolution is
-canonical-first, `visibleWhen ?? visibleOn`, matching both sibling readers:
-`sectionFields.ts` and app-shell's `readVisibility`.
-
-Two things deliberately did **not** change. A field hidden by its predicate still
-submits its value — conditional visibility is a rendering rule in both renderers, and
-making it a submit-payload rule would be a new contract decided once for both, not
-invented in the second one. And `FormPage` is **not** folded onto the plugin-form chain:
-the second-renderer question is real, but it belongs with the #5596 convergence track,
-not with a predicate that is dead today.
-
-`FormPage.visibleWhen.test.tsx` is the regression pin, and it lives next to *this*
-renderer on purpose — a pin that cannot see the second copy is how the first gap
-survived. With the fix reverted and the pin in place the suite reports
-`11 failed | 1 passed (12)`; the one green is the control that has to be green (a field
-with no predicate still renders), without which every "the field is absent" assertion
-would be equally satisfied by a renderer that draws nothing at all.
diff --git a/.changeset/console-lazy-docs-portal-5467.md b/.changeset/console-lazy-docs-portal-5467.md
deleted file mode 100644
index 9b44325e8..000000000
--- a/.changeset/console-lazy-docs-portal-5467.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-'@object-ui/console': patch
----
-
-The console's `/docs` portal is code-split for real: its four pages leave the eager closure instead of only pretending to.
-
-`AppContent.tsx` lazy-imports `DocsLayout` / `DocsSlug` / `DocPage` for the
-app-scoped `/apps/:packageId/docs` tree (ADR-0048). `App.tsx` imported the same
-three statically for the platform portal at `/docs` (ADR-0046 section 6), so all
-of them sat in the eager graph regardless and the `import()` moved nothing —
-three `INEFFECTIVE_DYNAMIC_IMPORT` warnings on every `vite build`
-(objectui#5467). A static import on either side silently defeats the split for
-both, and the only signal is a build warning that fails nothing.
-
-`App.tsx` now reaches all four docs pages through `lazy()` behind `Suspense`,
-matching the pattern `AppContent.tsx` already uses. `DocsIndex` joins them even
-though it carried no warning: `AppContent` renders `AppDocsIndex` at that slot,
-so nothing imported `DocsIndex` dynamically, but left static it alone would keep
-`DocShell`, `use-book-data` and `book-nav` eager and the portal would only
-half-leave the closure.
-
-Measured on this branch with the `dist/eager-closure.json` gauge added by
-objectui#5324, both builds exiting 0:
-
-| | before | after |
-|---|---|---|
-| `INEFFECTIVE_DYNAMIC_IMPORT` warnings | 46 | 44 |
-| eager closure, gzipped | 3,881,609 B | 3,870,058 B |
-| eager chunks | 58 | 52 |
-
-Six chunks leave the eager closure: `plugin-markdown` (4,212 B gz),
-`CreateViewDialog` (3,617 B), `use-book-data` (1,966 B), `DocShell` (476 B),
-`componentRegistry` (99 B), and `src` (129,555 B), the last of which rolldown
-folds into the entry chunk rather than dropping — which is why the entry chunk
-grows from 25,910 to 154,378 B gzipped while the closure as a whole shrinks by
-11,551 B. The entry stays far under that budget's 350 KB line, and the eager
-closure is the number a page load actually pays.
-
-What does NOT move is `vendor-markdown`, 164,708 B gzipped and the reason this
-looked like a bigger win than it is. Three eager chunks import it statically,
-and only one of them was this portal: `plugin-chatbot` reaches it directly, and
-`packages/fields`' `MarkdownContent` — lazy in source — is folded into the
-eagerly imported `ui-components` chunk by the `advancedChunks` group that claims
-every `packages/fields` module. That is objectui#5325's mechanism, not this
-card's, and it is why the saving here is 0.30% rather than 4%.
diff --git a/.changeset/console-preboot-branding-origin-5660.md b/.changeset/console-preboot-branding-origin-5660.md
deleted file mode 100644
index d642b3dd3..000000000
--- a/.changeset/console-preboot-branding-origin-5660.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@object-ui/console': patch
----
-
-The console's pre-boot branding script now resolves its server origin from
-`VITE_SERVER_URL` — the same variable every module-side consumer reads — instead of
-`window.__CONSOLE_SERVER_URL`, a global nothing in this repository ever set
-(objectui#5660).
-
-The two callers of `GET /api/v1/runtime/config` share one in-flight request, and
-that sharing keys on the FULL URL. So the origin was half of the contract: the
-inline script in `index.html` read one spelling and `src/main.tsx` read another, and
-whenever they disagreed the two never found each other. A same-origin production
-build hid it completely — both spellings collapse to `''` — so the split surfaced
-only in a dev pointing the console at a separate server, where it cost two requests
-to two different servers and let pre-boot branding paint from the wrong one.
-
-The pre-boot fetch is kept, not deleted: it is the request the module side JOINS.
-`sharedGetJson()` can only hand an earlier request to a later caller, and this
-script is the earlier one by construction — it runs during HTML parse, before the
-bundle is fetched, while `initRuntimeConfig()` is awaited before
-`createRoot().render()`. Deleting it would not remove a request; it would move the
-single remaining one later, onto the critical path to first paint, and leave the
-page's empty `` and favicon unbranded until React mounts.
-
-Vite substitutes its HTML env token only when the variable is set and leaves it
-verbatim otherwise, so the unset case is read as same-origin `''` rather than
-allowed to reach the URL as a path segment.
diff --git a/.changeset/console-studio-builder-decorative-lazy-5486.md b/.changeset/console-studio-builder-decorative-lazy-5486.md
deleted file mode 100644
index e557f79f0..000000000
--- a/.changeset/console-studio-builder-decorative-lazy-5486.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-'@object-ui/console': patch
----
-
-`registerStudioComponents.tsx` no longer claims a code split it never had: `studio:builder` imports `BuilderLanding` directly instead of through a `lazy()` that deferred nothing.
-
-The registration wrapped `import('@object-ui/app-shell')` in `lazy()` behind a
-`Suspense` fallback — naming the same barrel the line above it imports
-statically for `registerAppComponent`, and the same barrel `App.tsx` pulls
-`BuilderLanding` from to render the standalone `/studio` landing full-screen.
-Either reason alone makes the `import()` unable to move a module into another
-chunk (objectui#5486).
-
-**This moves no modules and is not a bundle improvement.** `BuilderLanding` was
-already in the eager graph via `App.tsx` and still is. Measured on
-`dist/eager-closure.json`, both builds exiting 0: the eager closure holds the
-same 52 chunks with the same names, and the only difference is 130 B gzipped
-(413 B raw) off the entry chunk — the deleted `lazy()`, `Suspense` and fallback
-text themselves, 0.003% of a 3,875 KB closure. Nothing leaves the closure,
-because nothing could.
-
-What it does fix is honesty. The old code told every reader the builder was
-deferred, and it emitted an `INEFFECTIVE_DYNAMIC_IMPORT` warning on every
-console build — the console's count of those drops from 44 to 43, with the 43
-remaining ones all belonging to the `packages/fields` barrel (objectui#5325).
-A permanent warning that fails nothing is how a team learns to skim past build
-warnings, and a decorative `lazy()` is how the next reader learns something
-false about the chunk graph.
-
-The `lazy()` shape is not the mistake. The sibling `registerAccountComponents.tsx`
-lazy-imports `./pages/system/ProfilePage`, a specifier nothing else pulls in
-statically, and is genuinely deferred; it is untouched. Making the *builder*
-genuinely lazy would mean taking `App.tsx` off the static import too, changing
-how `/studio` mounts, and it only pays if app-shell's own graph cleaves behind
-the barrel — a separate measured card, not folded in here.
diff --git a/.changeset/console-user-preference-request-budget.md b/.changeset/console-user-preference-request-budget.md
deleted file mode 100644
index a629873f7..000000000
--- a/.changeset/console-user-preference-request-budget.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
----
-
-Tests only: pin the console `sys_user_preference` request budget across one
-mount (objectui#5544). Three distinct preference keys, one read each, and every
-consumer still receives its own row. No runtime code changes, nothing to release.
diff --git a/.changeset/crud-guide-declarative-view-binding-5446.md b/.changeset/crud-guide-declarative-view-binding-5446.md
deleted file mode 100644
index 936ad20c9..000000000
--- a/.changeset/crud-guide-declarative-view-binding-5446.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
----
-
-Test-only change: extended `packages/plugin-grid/src/__tests__/guideCrudAppRenders.test.tsx`
-to measure the `content/docs/guide/building-crud-app.md` rewrite for objectui#5446 (a
-docs-only fix — the guide now teaches the declarative `dataSource: { object, view }`
-binding instead of the `view` / `data.queryParams` keys `ObjectGrid` never read). No
-published package behaviour changes.
diff --git a/.changeset/current-user-default-prefill-5683.md b/.changeset/current-user-default-prefill-5683.md
deleted file mode 100644
index b519b0fb6..000000000
--- a/.changeset/current-user-default-prefill-5683.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@object-ui/permissions': minor
-'@object-ui/plugin-form': minor
----
-
-Create forms pre-fill the `current_user` defaultValue token with the acting user (#5683). `PermissionContextValue` gains `userId` (from `/me/permissions`; `null` = unknown), and the create-form seeding resolves `defaultValue: 'current_user'` on `user` / `lookup→sys_user` fields to that id — the same value the engine stamps at insert, so the pre-fill is a preview of the server's own resolution, not a second default contract. Unknown user (no provider / anonymous / role-based provider) seeds nothing and keeps the omit-and-let-the-engine-resolve behavior. `NOW()` and CEL defaults stay server-owned.
diff --git a/.changeset/dashboard-declare-honoured-inputs.md b/.changeset/dashboard-declare-honoured-inputs.md
deleted file mode 100644
index c0129d95e..000000000
--- a/.changeset/dashboard-declare-honoured-inputs.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@object-ui/plugin-dashboard': minor
----
-
-`dashboard` now publishes the authoring inputs its renderer already honours — `widgets`, `label`, `description`, `header`, `globalFilters`, `dateRange`, `refreshInterval` — so `validateTree`, the generated `sdui.manifest.json` and `sdui-intrinsics.d.ts` stop warning authors off keys that work (previously only `columns`/`gap`/`className` were published, and every other honoured key drew `unknown-prop`). Each declared key is accepted by the spec's strict `DashboardSchema`, so the manifest never offers a key the save gate refuses. The legacy `title` spelling and the retired `aria` key stay deliberately unpublished and are pinned as such; the `schema.title || schema.label` fallback read is unchanged, so documents in the wild keep rendering their header title.
diff --git a/.changeset/dashboard-expandable-family-convergence-5692.md b/.changeset/dashboard-expandable-family-convergence-5692.md
deleted file mode 100644
index b7fcc7a8c..000000000
--- a/.changeset/dashboard-expandable-family-convergence-5692.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-`plugin-dashboard`'s two private copies of the reference-bearing field family now
-read `@object-ui/core`'s published `EXPANDABLE_FIELD_TYPES` instead of restating
-it — `LOOKUP_TYPES` in `recordFields.tsx` and the inline disjunction inside
-`computeLookupExpand` in `ObjectDataTable.tsx`. Both carry the identity pin the
-already-converged consumers carry, so a member-identical private copy fails
-rather than quietly re-forking the table.
-
-Two behaviour changes on the dashboard table's `$expand`, in opposite directions:
-
-- **A `tree` column is now expanded.** A self-referencing hierarchy field is
- reference-bearing and a member of the shared family, so its cell renders the
- parent record's display name instead of a bare id — the same treatment the form
- and grid roads already gave it.
-- **A `reference`-typed column is no longer expanded.** Measured before removing
- it: `reference` is absent from `@objectstack/spec`'s closed `FieldType`
- vocabulary and is refused by `FieldSchema.safeParse`, so no spec-compliant
- object schema can declare a field whose stored type is `reference`. Dropping it
- is a no-op on real data; the spelling is a legacy dialect alias on the
- action-param surface, folded to `lookup` before any field-type data is read.
-
-`EXPANDABLE_FIELD_TYPES` itself is unchanged — the measurement did not license
-widening a published shared set.
diff --git a/.changeset/dashboard-header-collapse-5812.md b/.changeset/dashboard-header-collapse-5812.md
deleted file mode 100644
index 9c0d11f88..000000000
--- a/.changeset/dashboard-header-collapse-5812.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-`DashboardRenderer` no longer emits an empty dashboard header wrapper. The
-wrapper used to render whenever `header` was declared, while each of its
-children — title, description, actions — was additionally suppressible. With
-the console page chrome present (`hideHeaderText`, set because the chrome
-already renders the dashboard's title and description) and no `header.actions`,
-every child evaluated falsy and the DOM still received
-``: zero children, yet a full grid row
-(measured 64px) plus `mb-4` of dead band above the filter bar, on every console
-dashboard page (objectui#5812, measured on HotCRM 17.1.0).
-
-The three children are now computed first and the wrapper renders only if one
-of them survives. Nothing else changes: a standalone embed (no chrome) renders
-title and description exactly as before, and declared `header.actions` keep the
-wrapper alive even under the chrome, since the chrome renders text only. Authors
-needed this — dropping `header` from the metadata to reclaim the pixels would
-have cost the standalone embed its title, which is what `header` is for.
diff --git a/.changeset/dashboard-self-contained-unreachable-arm-4620.md b/.changeset/dashboard-self-contained-unreachable-arm-4620.md
deleted file mode 100644
index 7d8e70c29..000000000
--- a/.changeset/dashboard-self-contained-unreachable-arm-4620.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-`DashboardRenderer` drops the unreachable `DatasetWidget` fork from its self-contained
-(card-less) branch, leaving that branch to render `SchemaRenderer` unconditionally
-(objectui#4620).
-
-`isSelfContained` is defined as `widget.type === 'metric' && !datasetBound`, and the
-`isSelfContained` arm of `renderedNode` then forked on `datasetBound` a second time. The
-`datasetBound` side of that inner fork could never execute: reaching it required
-`isSelfContained` to be true, which requires `!datasetBound`. Behaviour is unchanged —
-the removed arm never ran, and the reachable fork in the Card branch (the one that gives
-a dataset-bound metric its title and border chrome) is untouched.
-
-The cost was to readers, not to users: the shape read as "both branches handle
-dataset-bound widgets" when only one can, and a previous PR mirroring this fork onto
-`DashboardGridLayout` had to pay for the reachability argument before it could decline to
-copy the dead limb. A comment now names the invariant in place so the arm is not re-added.
diff --git a/.changeset/dashboard-stable-empty-rows-4629.md b/.changeset/dashboard-stable-empty-rows-4629.md
deleted file mode 100644
index 9fc980654..000000000
--- a/.changeset/dashboard-stable-empty-rows-4629.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-`ObjectDataTable` and `ObjectPivotTable` now use a module-scope frozen empty for
-"no rows yet" instead of a fresh array literal per render (objectui#4629).
-
-Both spelled the resolved row list as `Array.isArray(rawData) ? rawData : []`, so
-whenever `rawData` was a truthy non-array — a provider-config `data`, or a `bind`
-path that resolves to an object — the fallback produced a NEW array identity on
-every render. In `ObjectDataTable` that value keys the `derivedColumns` memo, so
-every column was re-derived (`buildFieldMeta`, a fresh `cell` closure, the
-`isSystemField` pass, the `fieldLabel` lookups) and then discarded by the
-`finalData.length === 0` early return. In `ObjectPivotTable` the value is handed
-straight to `PivotTable`, where it keys the cross-tabulation memo, so the pivot
-rebuilt its row/column sets, bucket map and totals on every render over no rows
-at all.
-
-Nothing rendered wrong before or after; this is wasted work in the empty window,
-plus the live `react-hooks/exhaustive-deps` warning the conditional raised. It is
-the same module-scope frozen empty `data-table.tsx` adopted for its own
-`EMPTY_ROWS` (objectui#4618), applied to the `provider: 'object'` siblings.
diff --git a/.changeset/dashboard-table-header-one-spelling-5425.md b/.changeset/dashboard-table-header-one-spelling-5425.md
deleted file mode 100644
index 8a0de721b..000000000
--- a/.changeset/dashboard-table-header-one-spelling-5425.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-A dashboard table's auto-derived column headers spell a field key the same way every other path in the `table` widget family does.
-
-`ObjectDataTable` derives headers on two paths — from the author's declared
-`columns`, and from the object schema when no columns were declared. The
-declared path (and the static `data-table` half of the same widget family)
-already used `humanizeFieldKey`, whose docstring names it "the single home for
-the convention, because both halves of the `table` widget family need it and
-they must agree". The auto-derived path carried a third, inline spelling that
-split camelCase but never turned `_` into a space, so it left a raw underscore
-on screen. Measured over the same object's columns:
-
-```
-path close_date needs_analysis
-object-bound, AUTO-DERIVED (before) Close_date Needs_analysis
-object-bound, AUTO-DERIVED (after) Close Date Needs Analysis
-object-bound, DECLARED columns Close Date Needs Analysis
-static `data-table`, no columns Close Date Needs Analysis
-```
-
-One dashboard can hold all three widgets over one object, so a single field key
-rendered under two spellings — the defect class objectui#5425 rules out. The odd
-path adopts the shared convention rather than the convention gaining a fourth
-dialect. camelCase keys are unaffected (`unitPrice` read `Unit Price` before and
-after — the coincidence that kept the snake_case divergence unnoticed), and a
-translated header still wins: only the fallback handed to `fieldLabel` changed.
-
-Dimension MEMBER labels are untouched by this. The same card reported dashboard
-members rendering a prettified enum instead of the picklist's translated label,
-measured on 17.1.0; re-measured on this branch it no longer reproduces — the
-analytics label net shipped in 17.5.0 routes every non-metric dataset dimension
-through the field's declared options and the locale bundle. That behaviour had
-no test stated in the card's terms and now has one, over the four dashboards the
-card measured, including the property that a bar axis and a pivot header cannot
-disagree about one stored value.
diff --git a/.changeset/dashboard-table-measure-desc-first-5845.md b/.changeset/dashboard-table-measure-desc-first-5845.md
deleted file mode 100644
index 6eb74642b..000000000
--- a/.changeset/dashboard-table-measure-desc-first-5845.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-'@object-ui/plugin-dashboard': minor
----
-
-Dashboard `table` widget — a numeric **measure** column now sorts **descending
-on the first click** (objectui#5845).
-
-The sortable headers shipped in objectui#5827 started every column ascending,
-which left the card's own motivating complaint — the largest industry rendering
-last — two clicks away. For a measure in an analytics widget the question a
-click asks is "who is biggest", so that column now cycles
-**descending → ascending → the dataset's own order**.
-
-Unchanged: a **dimension** column, and a measure whose values are not numbers
-(a `min()`/`max()` over a text field), keep **ascending → descending → the
-dataset's own order** — the idiom the console's own DataTable uses. The
-first-click direction is decided by the same measure-and-all-values-are-numbers
-classification that decides right-alignment, so it follows the column's *role*
-rather than "it looks like digits": a digit-keyed dimension (a year, a quarter)
-still starts ascending.
-
-Blanks still sort **last in both directions** — that arm is now what a measure
-column's very first click runs — and `aria-sort` reports the direction actually
-applied.
diff --git a/.changeset/dashboard-table-widget-polish-5827.md b/.changeset/dashboard-table-widget-polish-5827.md
deleted file mode 100644
index 9714c2330..000000000
--- a/.changeset/dashboard-table-widget-polish-5827.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-'@object-ui/plugin-dashboard': minor
----
-
-Dashboard `table` widget (dataset-backed, flat grouped table) — four rendering
-fixes so it reads as a table rather than a raw dump (objectui#5827):
-
-- **Numeric columns right-align**, header and cells. `tabular-nums` was already
- applied to every cell and could not do its job while the digits were flushed
- left. A column qualifies when it is a MEASURE and every non-null value in it
- is a number — the declared column `type` cannot decide it, because the
- analytics executor stamps `type: 'number'` on every measure regardless of the
- aggregate, so a `min()`/`max()` over a text field would have been
- right-aligned on the strength of a label. Dimensions stay left-aligned even
- when their bucket keys are digits: a grouping axis is not a quantity.
-- **Sortable headers.** Each header is a keyboard-reachable button that cycles
- ascending → descending → back to the dataset's own order, with the console's
- existing sort indicator and `aria-sort` on the `th`. Blanks sort last in
- BOTH directions. The sort is client-side over the rows already fetched — the
- widget issues one `queryDataset` call and paginates nothing, so no round trip
- is involved.
-- **The empty-dimension bucket sorts last** in the default order instead of
- floating to the top. It keeps its `—` label; only its position moves.
-- **Row hover feedback on every row**, not only on a drillable one. A drillable
- row keeps its stronger accent fill.
-
-The CSV export now follows the order the table is showing, which is the same
-"the CSV is the table's data" convention its cell text already followed.
-Drill-through is unaffected: rows are reordered as `(row, incoming index)`
-pairs, so a drill still resolves through the server's parallel raw-value
-sidecar.
-
-Unchanged: the cross-tab (a `pivot` with ≥2 dimensions) renderer, the KPI and
-chart paths, and every non-dataset table in the console.
diff --git a/.changeset/dashboard-title-locale-writeback-5428.md b/.changeset/dashboard-title-locale-writeback-5428.md
deleted file mode 100644
index 849b85b70..000000000
--- a/.changeset/dashboard-title-locale-writeback-5428.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-'@object-ui/plugin-designer': patch
-'@object-ui/app-shell': patch
----
-
-A widget title stored as an inline per-locale map is editable again in both dashboard
-authoring surfaces, and a save writes back only the active locale's entry
-(objectui#5428).
-
-`@objectstack/spec` widened `I18nLabel` from `string` to `string | Record` at
-17.0.0-rc.6, so a stored widget title may be an inline per-locale map while both
-authoring panels edit a title in ONE single-line input. Writing the input's value back
-as the whole value would collapse every other locale on the first keystroke, so both
-surfaces took the same conservative branch: show a map-valued title resolved, and make
-it READ-ONLY.
-
-That branch could not lose data, but it rested on a premise the spec had already
-invalidated — "nothing can reach this path from stored metadata yet, `I18nLabel` was
-plain `string` through rc.5" — stated sixty lines below a comment in the same file
-documenting the rc.6 widening that makes a stored map reachable. Both could not hold.
-The pinned spec is 17.0.0. What the read-only branch did in practice from rc.6 onward
-was not protect an unreachable path: it denied an author the ability to edit a widget
-title in their own locale.
-
-objectui#5301's maintainer ruling settled the write rule for the sibling surface — a
-save replaces only the active locale's entry and preserves the others — and
-`@object-ui/i18n` ships it as `setLocalized`, co-located with `pickLocalized` because
-the read and the write have to agree. Both panels now adopt it:
-
-- `@object-ui/plugin-designer`'s `DashboardEditor` widget property panel;
-- `@object-ui/app-shell`'s `DashboardWidgetInspector` in metadata-admin.
-
-A plain-string title keeps saving as a plain string, so the common path is unchanged.
-An edit made in a locale the stored map does not carry ADDS an entry under that locale
-rather than overwriting the entry the display fell back to.
-
-The pins are preservation pins, not "the input is editable" pins: at both surfaces a
-keystroke on a map-valued title must leave every other locale's entry byte-identical.
-Reverse-verified by mutating each write back to the flattening form and confirming those
-assertions go red at both surfaces.
-
-Not a multi-locale editor: an author still reaches only the entry for the locale they
-are in. Authoring every locale from one panel remains an open product question. The
-stale deferrals both comments carried pointed at objectui#4163, which closed as
-completed on 2026-08-15 while the placeholders were still in the tree; they are replaced
-with the rule that is actually in force rather than re-pointed at another tracker.
diff --git a/.changeset/dashboard-widget-dataset-stale-cast-5067.md b/.changeset/dashboard-widget-dataset-stale-cast-5067.md
deleted file mode 100644
index ba87f15ec..000000000
--- a/.changeset/dashboard-widget-dataset-stale-cast-5067.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
----
-
-Internal only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared.
-
-`DashboardRenderer.tsx`'s dataset-bound check (`const datasetBound = !!(widget as
-any).dataset`) drops its `as any` (objectui#5067). The comment's stated reason —
-"the bundled DashboardWidget type gains `dataset` only after objectui bumps
-`@objectstack/spec`" — is stale: the repo already carries `@objectstack/spec@17.0.0`,
-whose `DashboardWidget` declares `dataset`, and `packages/types/src/complex.ts`'s
-`DashboardWidgetSchema` inherits it through its `extends Omit,
-…>` (`dataset` is not in the `Omit` list). `widget.dataset` type-checks directly; the cast
-was pure redundant widening, and a harmful one — after `as any`, `.dataset` reads as
-`any`, so a future spec change to that key's shape would not go red here.
-
-**No runtime behaviour changes.** `!!(widget as any).dataset` and `!!widget.dataset`
-evaluate identically for every input; `as any` is a compile-time-only annotation. Proven
-with a reverse check: temporarily typo'ing the property to `widget.datasetTypoXYZ` turns
-`pnpm --filter @object-ui/plugin-dashboard type-check` red with `TS2339: Property
-'datasetTypoXYZ' does not exist on type 'DashboardWidgetSchema'`, confirming the removed
-cast was suppressing real type coverage rather than papering over a genuine gap.
-
-The ADR-0021 point the original comment made — a dataset-bound widget renders through
-the governed `queryDataset` path (`DatasetWidget`) instead of the inline object-aggregate
-schema — is unchanged and kept; only the now-false justification for the cast is rewritten.
diff --git a/.changeset/data-table-header-declared-key-5351.md b/.changeset/data-table-header-declared-key-5351.md
deleted file mode 100644
index c0b154de2..000000000
--- a/.changeset/data-table-header-declared-key-5351.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-'@object-ui/core': minor
-'@object-ui/components': minor
-'@object-ui/plugin-dashboard': minor
-'@object-ui/plugin-detail': minor
----
-
-`data-table` reads the declared `header`; the producers translate `label` into it.
-
-`TableColumn` declares `header: string` and does not declare `label`. The
-renderer's column normalization nonetheless read `header: col.header || col.label`,
-so the same key had one spelling the type admits and one only the runtime did.
-That alias is gone (objectui#5351), and the translation it used to perform happens
-once at each producer instead: metadata vocabulary in, adapter vocabulary out.
-
-**This narrows what `data-table` accepts, so read this if you author `data-table`
-nodes by hand.** A column spelled `{ label: 'Stage', accessorKey: 'stage' }` on a
-directly authored `data-table` now renders a **headerless** column over live
-cells. Spell it `header` — the key `TableColumn` has always declared. Columns
-reaching `data-table` through `object-data-table`, `object-grid` or a related
-list are unaffected: those producers resolve `header` for you from the spec's
-`ListColumnSchema.label`, so every spelling they accepted before they still
-accept.
-
-`@object-ui/core` gains `columnHeader()` alongside `columnIdentity()` — the reader
-producers use to cross that boundary. It is adapter-first (`header` wins over
-`label`), so an author who addressed the table directly is never overwritten.
-
-`object-data-table` also gains a fix from the same move: a column carrying a
-`label` used to render a **blank** header there even while the alias existed,
-because the widget's field-meta enrichment overwrote the authored `label` before
-the adapter ever saw it. `{ field: 'stage', label: 'Stage' }` now renders "Stage".
-
-The sibling `accessorKey: col.accessorKey || col.name` alias is **unchanged** here
-and still resolves. Retiring it is objectui#5120's remaining step, which is
-gated on two published skill guides that teach that spelling.
diff --git a/.changeset/date-range-default-range-binding-4984.md b/.changeset/date-range-default-range-binding-4984.md
deleted file mode 100644
index 60e729853..000000000
--- a/.changeset/date-range-default-range-binding-4984.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-'@object-ui/types': patch
----
-
-`DashboardComponentSchema.dateRange.defaultRange` is now bound to
-`DateRangeDefaultRange` from `@objectstack/spec/ui` instead of restating it as a
-hand-written 14-member union (objectui#4984).
-
-The union was byte-faithful to the spec — all 14 members, same order — so nothing
-a user hits changes today. What was missing is the tie that keeps it faithful:
-`resolveDashboardFilterDefs` takes `Pick`, so this union is what typechecks every TS-constructed dashboard, and
-a preset the spec ADDS would have been a legal document that objectui's own types
-said could not exist — the "narrower than the contract it implements" shape whose
-consequence in objectui#4163 was that the bad reads were invisible to `tsc`.
-
-No gate reported it: `check:spec-symbols` rule 1 matches by NAME and an inline union
-on an interface member has no symbol to collide with, while rule 2's claim heuristic
-was waved through by the `SpecGlobalFilter` reference a few lines above. Binding makes
-the file's existing "Aligned with @objectstack/spec" comment structural rather than
-prose.
-
-The emitted `.d.ts` collapses the inline union to the imported alias; the published
-type surface is unchanged — measured with the TypeScript checker over the emitted
-declarations (679 reachable exports from `dist/index.d.ts`, 22 from `dist/complex.d.ts`,
-and `defaultRange` resolving to the same 14 string-literal members before and after).
diff --git a/.changeset/dead-refresh-callback-objectview.md b/.changeset/dead-refresh-callback-objectview.md
deleted file mode 100644
index 6f077fa36..000000000
--- a/.changeset/dead-refresh-callback-objectview.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
----
-
-Internal only, no released behaviour change: `plugin-view`'s `ObjectView` no
-longer declares a `handleRefresh` callback that nothing referenced.
-
-The callback bumped `refreshKey` but was never passed to the toolbar, exposed on
-a handle, or wired to any control — it advertised a refresh entry point the
-component does not have, which cost objectui#4549 a detour to rule out. The
-reachable refresh paths are unchanged (`onMutation` auto-subscribe, delete, bulk
-delete, form success), and the real toolbar Refresh button continues to live in
-`plugin-list`'s `ListView`, reached through `renderListView`.
diff --git a/.changeset/default-children-retired-5051.md b/.changeset/default-children-retired-5051.md
deleted file mode 100644
index 0605f03d1..000000000
--- a/.changeset/default-children-retired-5051.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-'@object-ui/types': minor
-'@object-ui/core': minor
-'@object-ui/components': minor
----
-
-The register-meta key `defaultChildren` is retired (objectui#5051).
-
-It was declared in four places, produced in eleven, and read in **none**. The designer's
-drop path builds a new node from its twin key only — `PageDesigner.tsx`,
-`props: paletteItem?.defaultProps ?? {}` — with no `children:` line, so a palette item
-that declared `defaultChildren` dropped an **empty** node and the declared children never
-materialised. Nothing rendered the wrong thing; an entire declaration surface was simply
-inert, which is the declared-but-unenforced shape ADR-0049 targets. Per the maintainer
-ruling of 2026-08-19, the key is removed rather than wired up; if designer
-default-children UX is ever product-wanted it returns as its own designed card.
-
-**If you author plugins against the published register-meta table, drop the key.** It is
-gone from `skills/objectui/guides/plugin-development.md`, which had been teaching it. A
-meta that still declares it stays *valid*: `ComponentMetaSchema` is a plain `z.object`,
-and measured on zod 4.4.3 that STRIPS unknown keys rather than rejecting them — so the
-key is silently dropped from the parse output instead of failing validation. TypeScript
-authors get the loud signal instead: all three `ComponentMeta` declarations
-(`@object-ui/types` `base.ts` and `plugin-scope.ts`, `@object-ui/core` `Registry.ts`) no
-longer offer it, so re-declaring it is now a compile error.
-
-**No runtime behaviour changes in either direction.** No code path read the key before
-this change, and the eleven producers that set it (`sidebar.tsx` x10, `span.tsx`) were
-feeding a reader that did not exist. Dropping a `span` or any of the ten sidebar types
-into the designer produces exactly the node it produced yesterday.
-
-Two suites keep it retired, one per package: `packages/types` pins the zod twin (the key
-is absent from the parse output, with a surviving sibling asserted present through the
-same parse as the control) plus the two TS twins with `@ts-expect-error`, and
-`packages/core` pins the registration surface the eleven producers were written against.
-Both are compile-time-enforced through each package's chained `tsconfig.test.json`.
diff --git a/.changeset/default-view-agenda-retired-5784.md b/.changeset/default-view-agenda-retired-5784.md
deleted file mode 100644
index ae59ebf29..000000000
--- a/.changeset/default-view-agenda-retired-5784.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-'@object-ui/types': minor
----
-
-`'agenda'` leaves `defaultView` on all three of its declaration faces
-(objectui#5784 — the `defaultView` sibling of objectui#5740's retirement on
-`CalendarViewSchema.view`; ADR-0049 enforce-or-remove): the
-`ObjectCalendarSchema` TS interface (an inline union `#5740`'s
-`CalendarViewMode` narrowing could not reach), the zod `ObjectCalendarSchema`,
-and the list-view `calendar` config's objectui-only `defaultView` extension.
-All three are now `['month', 'week', 'day']`.
-
-The declarations admitted a value nothing enforced: `ObjectCalendar`'s props
-declare `defaultView?: 'month' | 'week' | 'day'`, its schema read casts to the
-same three values, and `CalendarView` renders no agenda view. An author
-writing the type-legal, zod-valid `defaultView: 'agenda'` on an
-`object-calendar` node or in a list view's `calendar` config got a month
-calendar with no error or warning. The spec side already agrees:
-`@objectstack/spec`'s `ObjectCalendarProps.defaultView` is
-`['month', 'week', 'day']`. No in-repo, example, or catalog app authors
-`defaultView: 'agenda'` (measured for objectui#5784 with positive controls,
-including the objectstack tree — its only `agenda` token is the Agenda
-job-scheduler library).
-
-**This narrows the accept set: an author who writes `defaultView: 'agenda'`
-will now be refused at validation.** `defaultView` is a declared key, and
-declared keys are validated even under `.passthrough()`, so
-`defaultView: 'agenda'` is a **validation error that previously parsed
-green** (an `invalid_value` issue on the `defaultView` /
-`calendar.defaultView` path, offering `month`/`week`/`day`). Undeclared keys
-still pass through unchanged. Breaking on the published zod surface; ships as
-`minor` per this repo's version-alignment policy (majors track
-`@objectstack`).
-
-The runtime boundary is unchanged: `ObjectCalendar` still resolves an
-off-union raw `defaultView` away to its `'month'` default. Docblocks and both
-zod `describe` strings now teach the three-value set.
diff --git a/.changeset/delete-dead-release-workflow-5405.md b/.changeset/delete-dead-release-workflow-5405.md
deleted file mode 100644
index b1c00f27a..000000000
--- a/.changeset/delete-dead-release-workflow-5405.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
----
-
-CI/docs only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared.
-
-Deletes `.github/workflows/release.yml`, a workflow that never ran once in this
-repository's history, and removes the two places on
-`content/docs/guide/ci-cd-pipeline.md` that documented it as a release path.
-
-It triggered on `push: tags: ['v*']`. No tag matching that glob has ever existed here:
-of the 2896 tags on the remote, every one is a Changesets per-package tag
-(`@object-ui/@`) and not one begins with `v`. Nothing creates tags except
-the Changesets action, so the trigger had no way to fire.
-
-It is obsolete rather than misconfigured, which matters because the two have opposite
-fixes. The workflow parsed and registered fine (GitHub lists it `active`) and would fire
-if a `v*` tag were pushed — the job it would do, "Create GitHub Release", is simply
-already being done by `changeset-release.yml`, whose Changesets action publishes GitHub
-Releases tagged `@object-ui/@`. Its own npm publish step was still
-commented out under "Uncomment the following steps when ready to publish to npm" while
-the repo has been publishing to npm through Changesets for months, and its one-version
-model (a single `v` for the whole repo, pointing at the root `CHANGELOG.md`)
-never matched the 39-package fixed group this repo actually releases.
-
-The doc edit is not optional housekeeping: `scripts/__tests__/ci-cd-pipeline-doc.test.ts`
-pins that page to `.github/workflows/` in both directions, so a page still naming
-`release.yml` after the file is gone fails "never names a workflow file that does not
-exist".
-
-No package `src/` is touched, so no `@object-ui/*` package changes behaviour and there is
-nothing here for a consumer to upgrade to.
diff --git a/.changeset/dom-leak-sweep-components-5574.md b/.changeset/dom-leak-sweep-components-5574.md
deleted file mode 100644
index ce389acf8..000000000
--- a/.changeset/dom-leak-sweep-components-5574.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
----
-
-Tests only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. The one file changed is
-`packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx`, which
-`packages/app-shell/tsconfig.json` excludes from the build program
-(`"exclude": [… "**/*.test.tsx"]`).
-
-Measured rather than asserted, because emitted-output behaviour varies per package:
-`packages/app-shell/dist` was built twice from a cleared `dist/` **and** a cleared
-`tsconfig.tsbuildinfo` — once at the `origin/main` version of that file, once at this
-branch's version — and sha256-compared. 862 emitted files, 431 of them `.d.ts`, every
-hash equal. (The first attempt at this measurement read as an empty `dist/`: `tsc` is
-`composite`, so with the build info left in place it skipped the emit entirely and
-produced no output to compare. Clearing the build info is part of the measurement,
-not a detail.)
-
-Widens the objectui#3291 DOM-leak canary sweep to `packages/components/src/renderers/**`.
-That family — 158 registry-reachable types across five namespaces — was outside the
-gate's discovery entirely, which is why `ui:grid`'s leak had to be found by hand. The
-first run records a ledger: 119 of 158 targets leak, in eight measured shapes, every
-renderer named. Nothing is skipped or allow-listed; the per-target assertion stays exact
-set equality in both directions, so a renderer fix cannot go green until its ledger row
-is deleted in the same change.
diff --git a/.changeset/dom-leak-sweep-fixture-depth-5630.md b/.changeset/dom-leak-sweep-fixture-depth-5630.md
deleted file mode 100644
index d471f6afd..000000000
--- a/.changeset/dom-leak-sweep-fixture-depth-5630.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
----
-
-Test-only: deepen the `element:definition-list` / `element:repeater` /
-`element:metadata_viewer` fixtures in the DOM-leak sweep
-(`packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx`) past their
-empty-state placeholder branch, so their clean reading covers the real,
-populated markup instead. No renderer source changed and no published
-behaviour changes.
diff --git a/.changeset/dts-build-exit-code-5370.md b/.changeset/dts-build-exit-code-5370.md
deleted file mode 100644
index b6fe0bc6d..000000000
--- a/.changeset/dts-build-exit-code-5370.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
----
-
-Build tooling only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. No package `src/` is touched: `@object-ui/layout`'s `vite build`
-now exits non-zero when the declaration step reports type errors, instead of printing them
-and exiting 0 (objectui#5370). The typings and the JavaScript it emits are unchanged.
diff --git a/.changeset/dts-explicit-extensions-5365.md b/.changeset/dts-explicit-extensions-5365.md
deleted file mode 100644
index 73de8c9d3..000000000
--- a/.changeset/dts-explicit-extensions-5365.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-'@object-ui/components': patch
-'@object-ui/layout': patch
----
-
-The typings both packages publish now carry an explicit extension on every relative specifier, so a consumer on `moduleResolution: nodenext` can follow them.
-
-`vite-plugin-dts` emits one declaration file per source file, and TypeScript
-copies a module specifier into the declaration verbatim. `export * from './ui'`
-therefore shipped extensionless in `dist/index.d.ts` — 21 such re-exports in
-`@object-ui/components`, 7 in `@object-ui/layout`, 128 across the two emitted
-trees. Node16/NodeNext resolution does not extension-search a relative
-specifier, so the compiler could follow none of the hops and every symbol they
-carried read as absent from the package:
-
-```
-error TS2305: Module '"@object-ui/components"' has no exported member 'Badge'.
-```
-
-Measured on `@object-ui/app-shell`, the largest consumer and the one that pulls
-in both packages: 880 TS2305 across 162 files (864 from `components`, 16 from
-`layout`), plus 215 TS7006 as fallout from the imports that stopped resolving.
-On `@object-ui/fields`, 178 TS2305 and 57 TS7006. Both are zero now.
-
-The emitted `.js` never had the defect — rolldown resolves the same specifier
-away — which is why `pnpm check:esm-specifiers`, whose verdict is about
-specifier-preserving `.js` builds, correctly never scanned either package. The
-fix is therefore in the declaration EMIT (`scripts/vite-dts-explicit-extensions.ts`,
-shared by both `vite.config.ts` files), not in the sources: the same source line
-produces a clean `.js` and a broken `.d.ts`, so no source edit can express the
-difference. The rewriter resolves each specifier against the source tree the
-output mirrors — a file hop becomes `./x.js`, a directory hop `./x/index.js` —
-throws on anything it cannot resolve, and after the build re-parses the emitted
-declarations to assert every relative specifier both carries an extension and
-names a file the build really emitted.
-
-`packages/fields` takes the `nodenext` pin as a result — the same two lines
-`packages/react` has carried since objectui#4538 — so the property is enforced by
-the compiler on the consumer side rather than by review. `packages/app-shell`
-does not: it type-checks clean without the pin and still shows 23 errors with it,
-none of them from these two packages. That residue is filed separately.
diff --git a/.changeset/eager-closure-budget-5324.md b/.changeset/eager-closure-budget-5324.md
deleted file mode 100644
index 230d59670..000000000
--- a/.changeset/eager-closure-budget-5324.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
----
-
-CI and build tooling only — this publishes nothing, declared explicitly with an empty
-frontmatter rather than left undeclared. No package `src/` is touched.
-
-The console "performance budget" now weighs the **eager closure** instead of one chunk.
-
-`.github/workflows/performance-budget.yml` gzipped `apps/console/dist/assets/index-*.js`
-and compared it against a 350 KB line. Measured on `77f846a8b`, that chunk is 25,910 bytes
-gzipped, while the closure it statically pulls in — every chunk the browser must fetch and
-parse before the app renders — is 3,881,609 bytes across 58 of 507 chunks. The gate passed
-on 0.67% of the payload it claimed to govern, and `advancedChunks` routes vendor and
-workspace code into named chunks on purpose, so most regressions land outside the entry
-chunk. objectui#5266 is the worked example: 89 KiB gzipped added to every page load, landing
-in `vendor-objectstack-*.js`, structurally invisible here (objectui#5324).
-
-`emitEagerClosureReport` in `apps/console/vite.config.ts` walks rolldown's own
-`chunk.imports` from the entry chunks — static edges only, because the dynamic edge is the
-lazy boundary — gzips the bytes actually written to disk, and writes
-`dist/eager-closure.json`. `scripts/check-eager-closure-budget.mjs` applies the ceiling.
-The split is deliberate: a size ceiling enforced inside `vite build` would fail every
-Vercel preview and every local build, which is how a budget gets switched off rather than
-fixed. Exit codes are distinct — `1` over budget (a verdict about the bundle), `2` no
-trustworthy measurement (a verdict about the gauge) — so a broken gauge is never reported
-as a clean bundle, and vice versa.
-
-Every check in that path is a counter-probe, because this gate's failure mode is silent: a
-walk that finds too little, a stale report, an absent field read as zero all produce a
-SMALL number, and a budget reads a small number as good news. So the build refuses to
-publish a figure unless `react-dom` is inside the closure and at least one chunk is outside
-it, and the checker refuses a report whose totals disagree with its own chunk list, whose
-version it does not recognise, or that has collapsed to its entry chunk — that last one
-being precisely the gauge this replaces.
-
-The ceiling is 3,960,000 gzipped bytes: today's measurement plus 78,391 bytes of headroom.
-It passes on current `main`, and the headroom is deliberately narrower than the 89 KiB
-regression the gate exists to catch, so a repeat of objectui#5266 fails it (verified: the
-baseline plus 89 KiB comes out 12.4 KB over). Both constraints are asserted in
-`scripts/__tests__/check-eager-closure-budget.test.ts`, not merely argued in a comment.
-
-This is a truthful current-state ceiling, not a target. 3.79 MB gzipped before first render
-is a bad payload and the honest long-term line is far below it; lowering it is a separate
-decision with its own work behind it. The entry-chunk budget and its 350 KB line are
-unchanged — replacing a blind gauge is not licence to drop the check that was already there.
diff --git a/.changeset/esm-load-comment-mask-5382.md b/.changeset/esm-load-comment-mask-5382.md
deleted file mode 100644
index 70abee6d0..000000000
--- a/.changeset/esm-load-comment-mask-5382.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
----
-
-CI tooling only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. No package `src/` is touched.
-
-Fixes the comment mask in `scripts/check-node-esm-load.mjs` (objectui#5382). Leg 1 of
-the gate blanked comments out of every source with two ordered regexes and then matched
-specifiers in the result. The block-comment pass ran first and had no notion of already
-being inside a `//` line, so a slash-star sequence occurring in ordinary line-comment
-prose — a package glob, a path pattern, a wildcard import, all of them ordinary here —
-opened a comment that ran to the next closing delimiter anywhere in the file and blanked
-every line between, live code included.
-
-Re-measured on `main` at 478ec54ce over the 805 emitted sources of the 13
-specifier-preserving packages: the mask found 2132 relative specifiers and the TypeScript
-parser found 2133. The one it could not see is a real `import` in
-`packages/app-shell/src/preview/DraftChangesPanel.tsx`, hidden by a line comment naming
-the `@objectstack` chunk group by glob two lines above it.
-
-That direction is the bad one. Since `SPECIFIER_DEBT` emptied, leg 1 is a hard
-requirement rather than a ratchet, so a blind spot in it is somewhere a regression can
-sit permanently while the run reports clean and only the nightly load leg can see the
-consequence.
-
-Leg 1 now reads each file exactly as written and takes its module edges from
-`check-phantom-dependencies.mjs`'s shared TypeScript scanner — the same one
-`check-package-self-import.mjs` uses, so three gates cannot drift apart on what a module
-edge is. Comments, strings, template literals and regex literals stop being questions
-this gate has an opinion about. Reported line numbers stay the compiler's: the specifier
-literal is located inside the statement the parser already identified, which matters
-because `tsc` reports this class at the specifier and the statement opens on a different
-line for 255 of 2066 relative specifiers here.
-
-The gate's verdict is unchanged — 0 findings before, 0 findings after — because the
-newly visible import already carries its `.js` extension. What changed is that it is now
-visible. The whole cheap leg went from 0.71s to 2.43s.
-
-`readTsconfig()` in the same script strips comments with the same kind of
-context-unaware regex and is a separate live instance of this class. It is deliberately
-untouched here and remains open as objectui#5367.
diff --git a/.changeset/evaluateexpression-jsdoc-links-5580.md b/.changeset/evaluateexpression-jsdoc-links-5580.md
deleted file mode 100644
index d8167b195..000000000
--- a/.changeset/evaluateexpression-jsdoc-links-5580.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-'@object-ui/core': patch
----
-
-Both `evaluateExpression` references in the `ExpressionEvaluator.registerFunction`
-JSDoc are now qualified, so each resolves to the entity it means (objectui#5580).
-
-`ExpressionEvaluator.ts` declares two things spelled `evaluateExpression`: the method
-on `ExpressionEvaluator` (bare expression, throws) and the module-level export
-(context bag, fail-soft, delegating to `evaluate`). The `registerFunction` block
-referred to both under the one spelling, four lines apart.
-
-The prose link was not merely ambiguous, it was bound wrong. Measured with
-`checker.getSymbolAtLocation` on the pre-fix source, `{@link evaluateExpression}`
-resolved to the module-level `FunctionDeclaration` — the fail-soft one — inside the
-sentence that calls it *"the throwing sibling"*. The neighbouring `{@link evaluate}`
-binds to the method, but only because no module-level `evaluate` exists to outrank
-it, so the rule "an unqualified link resolves to the enclosing class's member" does
-not hold here. The link is now `{@link ExpressionEvaluator.evaluateExpression}`,
-which the checker resolves to the `MethodDeclaration`.
-
-The `@example`'s final line is the module-level export — its second parameter is a
-context bag and the `${...}` wrapper only resolves on the `evaluate` path — but it sat
-two lines below calls that establish `evaluator.` as the receiver, and a `.d.ts` hover
-carries no import to disambiguate. It now names the module-level export and shows the
-import it needs.
-
-This is prose only: the diff is confined to a block comment and no declaration moves.
-It is scored `patch` rather than the empty-frontmatter form because the block is
-emitted into what npm ships — measured, this edit moves both
-`dist/evaluator/ExpressionEvaluator.d.ts` and `dist/evaluator/ExpressionEvaluator.js`
-(this package builds with a bare `tsc`, which preserves comments in the JS emit), and
-the ten changed lines in that JS are all comment lines.
-
-`registerFunction-jsdoc-links.test.ts` pins the binding against the checker rather
-than asserting it in prose, since a `{@link}` that binds to the wrong entity is
-indistinguishable in source from one that binds right.
diff --git a/.changeset/filecell-error-slot-5431.md b/.changeset/filecell-error-slot-5431.md
deleted file mode 100644
index c6a890b5e..000000000
--- a/.changeset/filecell-error-slot-5431.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-'@object-ui/fields': minor
----
-
-**API addition (public-surface widening):** `FileCell` — the compact upload
-control `@object-ui/fields` exports for line-item grid cells — gains the
-published optional `error?: string` slot, mirroring `LookupField` and
-`FileField`: the same validation slot `@objectstack/spec/ui`'s
-`FieldWidgetPropsSchema` declares and `FieldWidgetComponentProps` names
-(objectui#3222). When set, `FileCell` puts `aria-invalid` on its own focusable
-picker button; the message text stays with the host (objectui#5431).
-
-`GridField` now passes that slot for a required-but-empty `file` cell — the one
-cell type objectui#3318's per-cell `aria-invalid` delivery left out. Before
-this, a required `file` cell flagged only the visual ring and `title` on the
-`td`; no element in the cell subtree announced the state, so assistive tech was
-told nothing (a wrapper-only mark is exactly what objectui#5223 forbids). Text,
-number, select, and lookup cells were wired in PR #5429; `file` cells now
-behave identically.
diff --git a/.changeset/flow-resume-result-5417.md b/.changeset/flow-resume-result-5417.md
deleted file mode 100644
index 5de280435..000000000
--- a/.changeset/flow-resume-result-5417.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-'@object-ui/app-shell': minor
-'@object-ui/i18n': minor
----
-
-A screen flow's resume result reaches the user — on both outcomes (objectui#5417).
-
-A dogfood walkthrough reported that a refused `resume` and a successful one
-"render identically: the dialog closes and the page is unchanged", leaving no
-gesture that distinguishes "created" from "rejected". Re-measured against `main`
-before any change, one half of that was already fixed — `interpretFlowResponse`
-reads the ADR-0112 envelope, and `FlowRunner`'s `toast.error` has carried its
-prose since the `400 FLOW_FAILED` classification landed in `17.6.0`, five minors
-after the version the report was measured on. There was no interpreter bug and
-no un-consolidated fourth call site. Three gaps in the RUNNER's disposition were
-real, and they are what changed:
-
-- **A terminal failure no longer closes the dialog.** The reason it closed is
- unchanged and is not reversed: on a `FLOW_FAILED` the engine has already
- consumed the suspension, so a resubmit can only reach "No suspended run" and
- must not be offered. Closing was one way to withhold that dead retry and the
- expensive one — the user had just typed a form they could no longer see, and
- the engine's sentence names a value that left the screen with it. The dialog
- now stays open with the submit affordance withdrawn: the flat footer swaps
- Submit for Close, and an `object-form` step drops its Save (which also stops a
- second click from duplicating the record it had already persisted).
-- **The refusal has a second, non-expiring carrier.** The toast stays — it is
- viewport-fixed, so it still reaches a user scrolled past a tall step's header
- — and an inline destructive `Alert` (`role="alert"`) now holds the same
- sentence inside the dialog, beside the values that produced it. A retryable
- refusal (`INVALID_SCREEN_INPUT`, transport, 5xx) keeps Submit live as before,
- and its banner clears as soon as the user starts editing.
-- **A successful run invalidates what the flow WROTE, not just what the user is
- looking at.** Both hosts answered `onComplete` with
- `notifyDataChanged({ objectName: })`, so a flow that
- created a quote from an Opportunity page never told the related list that
- would now contain it — the record did not appear until a manual reload. The
- runner cannot know which objects a flow touched, so it emits
- `{ objectName: '*' }`: the same scope, for the same stated reason, that the
- record page's manual ⟳ already uses. Everything mounted refetches in place
- over the invalidation bus, with no remount.
-
-The runner's copy now goes through `@object-ui/i18n` instead of being hardcoded
-English: a new `flowRunner` namespace (`title`, `submitting`, `saveAndContinue`,
-`nextStep`, `completed`) in all ten packs, plus reuse of
-`common.{loading,cancel,close,submit}` and `wizard.missingRequired`. The
-server's own refusal sentence is still passed through untranslated — it is prose
-the automation engine composed for a human, not copy with a key.
diff --git a/.changeset/form-control-renderers-dom-props-5632.md b/.changeset/form-control-renderers-dom-props-5632.md
deleted file mode 100644
index c13febb1a..000000000
--- a/.changeset/form-control-renderers-dom-props-5632.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-'@object-ui/components': patch
----
-
-**Behaviour change:** the eighteen renderers whose host element is a form
-control no longer forward their whole prop bag to it. They route it through a
-form-control DOM declaration — the same `pickDomProps` mechanism `grid`, `flex`,
-`stack`, `container` and `text` were converged on — so an authored schema key
-becomes an HTML attribute only if the contract declares it one (objectui#5632,
-the `BARE_SPREAD_MINUS_NAME` slice of objectui#5574).
-
-The eighteen: `action:button`, `action:icon`, `ui:button`, `ui:checkbox`,
-`ui:combobox`, `ui:date-picker`, `ui:email`, `ui:file-upload`, `ui:input`,
-`ui:input-otp`, `ui:password`, `ui:radio-group`, `ui:sidebar-menu-button`,
-`ui:slider`, `ui:sonner`, `ui:switch`, `ui:textarea`, `ui:toggle`.
-
-What this stops reaching the DOM: the renderer's own declared props, consumed
-off `schema` to render the control AND spread onto the element a second time as
-attributes HTML does not define, plus the authored node's SDUI metadata and the
-flattened `props` container. Measured across `examples/schema-catalog`, rendered
-through the real `SchemaRenderer`: 284 illegitimate attributes over 287
-form-control nodes — `button[label]` 140, `button[icon]` 25, `input[inputtype]`
-23, `input[label]` 19, `toggle[label]` 14, `radio-group[options]` 8,
-`date-picker[placeholder]` 7, `file-upload[label]` 7, `file-upload[buttontext]`
-7, `checkbox[label]` 6, `textarea[label]` 6, `toggle[arialabel]` 6,
-`switch[label]` 4, `password[label]` 3, `email[label]` 2, `radio-group[direction]`
-2, and six singletons. The same probe reads 0 after, with `grid`'s 26 nodes at 0
-both times as the control and an unchanged node census across the two runs.
-
-`name` and `disabled` are DELIBERATELY still forwarded. Both are legal on a form
-control and neither was ever part of the leak — the ledgered shape for this
-group is thirteen attributes, not fourteen, precisely because HTML defines
-`name` on these hosts. The whitelist this group uses is therefore the shared
-SDUI one PLUS those two, not the bare `toDomProps` the container renderers take:
-that would have stripped the form-serialization key off every control and
-silently re-enabled every disabled one, and no gate in the repo would have gone
-red for it.
-
-Nothing an author writes renders differently: every leaked key was already being
-read off `schema` and applied. `id`, `role`, `tabIndex`, `className`, `style`,
-event handlers and the open `data-*` / `aria-*` families are unchanged.
-
-Anything that read one of the leaked attributes off the DOM — a CSS attribute
-selector such as `[label="Save"]`, or a test asserting `inputtype` on a rendered
-`ui:input` — must read the schema instead. No `@object-ui` code did; this is
-called out because the attributes were externally visible while they lasted.
diff --git a/.changeset/form-designer-namespaced-field-type-4838.md b/.changeset/form-designer-namespaced-field-type-4838.md
deleted file mode 100644
index 88b03f1e6..000000000
--- a/.changeset/form-designer-namespaced-field-type-4838.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-Studio's form designer canvas now emits the namespaced `FormField.type` spelling
-instead of passing `objectDef.fields[x].type` through raw (objectui#4838).
-
-Per the maintainer ruling on that card, a bare spec-type name (`markdown`,
-`html`, `richtext`, …) is **not** a legal `FormField.type` — one widget, one
-legal spelling, the namespaced widget id. `ObjectFormDesigner` was the measured
-producer of the bare spelling: it handed a raw object-metadata type straight to
-`isWideFieldType`, a helper whose vocabulary is `FormField.type`. It now
-normalizes through `mapFieldTypeToFormType`, the one place that widget decision
-is made.
-
-User-visible effect: a `repeater` field is finally laid out full-row on the
-canvas, matching the runtime form. `repeater` is a spec `FieldType` that
-resolves to the wide `field:grid` widget, but bare `repeater` is not one of
-`WIDE_FIELD_TYPES`' bare members, so the raw pass matched nothing — the canvas
-showed it at normal width while the real form spanned it. Fields whose spec name
-doubles as a widget id (`textarea`, `markdown`, `html`, `richtext`, `grid`) are
-unaffected; they matched under both spellings.
-
-The two tolerant consumers this makes look redundant are deliberately left
-alone, each scheduled under its own follow-up with deprecation care: the
-`field:`-prefix fallback in `renderFieldComponent`, and `WIDE_FIELD_TYPES`' dual
-spellings. Removing a tolerance is the consumer-tightening half, and other
-producers have not been normalized yet.
diff --git a/.changeset/form-reset-notification-channels-5235.md b/.changeset/form-reset-notification-channels-5235.md
deleted file mode 100644
index 6a2d2c3f1..000000000
--- a/.changeset/form-reset-notification-channels-5235.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@object-ui/components': patch
----
-
-The form renderer now keeps a `defaultValues` reset off `onChange` and off the
-`form_change` `onAction` for **every** caller — including one that memoizes the
-callback (objectui#5235).
-
-"A record landing is not a user edit" was already this file's documented,
-pinned behaviour, but two of the three channels delivered it by accident of
-React's effect ordering: every layout DESTROY runs before any layout CREATE, so
-a caller passing a fresh callback each render had its value subscription torn
-down before the reset and re-established after. The guarantee was therefore
-delivered by the callback's *identity changing*. Wrap the same callback in
-`React.useCallback` — taught everywhere as a semantically neutral performance
-optimization — and the identity stays put, the effect never re-runs, the
-subscription survives the reset, and the whole loaded record comes back to the
-host as if the user had typed it: the false "the user edited this" signal
-objectui#2968 was filed about, in a form no type, doc or call site warned about.
-
-The reset now states what those two channels report, the way `onDirtyChange`
-already did (it computes its payload against the freshly installed baseline and
-calls the host outright). Callers passing inline arrows see byte-identical
-behaviour; callers who memoize stop receiving a phantom edit.
-
-Not a contract change: whether a value channel *should* report a programmatic
-reset stays open in objectui#5235. This only removes the answer's dependence on
-caller identity.
diff --git a/.changeset/formatpercent-comment-citations-4596.md b/.changeset/formatpercent-comment-citations-4596.md
deleted file mode 100644
index 6e68a460d..000000000
--- a/.changeset/formatpercent-comment-citations-4596.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
----
-
-Comments only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared (objectui#4596).
-
-Why the empty form is the right declaration here: both changed comments are **internal
-reasoning**, not consumer-visible API documentation. One sits inside `formatMeasure`'s
-body; the other is a test-file header. No released behaviour changes, and no declaration
-changes — `dist/utils/dataset-format.d.ts` is byte-identical across this change
-(measured: sha `be5f5938…`, 10,580 bytes on both sides), so nothing a consumer types
-against or reads on hover moves.
-
-Stated honestly, because an earlier draft of this note got it wrong: the shipped
-JavaScript **does** change. `tsconfig.base.json` sets `removeComments: false`
-deliberately and `@object-ui/core` builds with a plain `tsc`, so a body comment is
-emitted — `dist/utils/dataset-format.js` goes from 14,716 to 15,457 bytes, and it is the
-only one of the package's 180 dist files that moves. The test file is excluded from the
-build program (`src/**/__tests__/**`) and never reaches `dist` at all. Bytes moving is
-not the criterion; released behaviour and consumer-visible surface are, and neither does.
-
-Three comments cited `formatPercent` as the live example of the divide-by-100 percent
-route. Each was accurate when written and stopped being true when objectui#4590 landed:
-`formatPercent` renders through `style: 'percentPoints'` with no division. The comments
-now argue the route on its own merits without the expired citation, and record what
-replaced it — no caller in this repo takes the divide-by-100 route today. `formatPercent`
-was the last one; the two remaining `style: 'percent'` sites hand `Intl` a FRACTION, which
-is that style's own contract, and the route otherwise survives only where a test builds it
-in order to show it disagreeing.
-
-The measured argument underneath is unchanged, and the tie / extreme-magnitude pins that
-keep the percentage-points route honest are untouched. The `27,581 of 1,200,013` figure
-now names the grid it came from — objectui#4576's tie-dense grid, 0.005 steps to 2,000,
-precisions 0/1/2, on `formatMeasure`'s call shape — so it stops reading as a discrepancy
-against objectui#4590's `27,577 of 1,200,003`, which is the same grid re-measured through
-`formatPercent`.
diff --git a/.changeset/formfieldspec-dependson-5040.md b/.changeset/formfieldspec-dependson-5040.md
deleted file mode 100644
index 183a8fc65..000000000
--- a/.changeset/formfieldspec-dependson-5040.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-metadata-admin: `FormFieldSpec` declares `dependsOn`, and the widget half reads
-the same declaration instead of its own copy of it (objectui#5040)
-
-`FormFieldSpec` — the authoring type for a metadata-admin form layout, the
-element type of `FormSectionSpec.fields[]` — did not declare `dependsOn`.
-`widgets.tsx` held a second, inline description of the same object as
-`WidgetProps.fieldSpec`, and that one did, because two registered widgets read
-it as their primary configuration: `field-selector` resolves
-`dependsOn || reference || 'objectName'` to decide whose field catalog to
-offer, and `dynamic-config` uses it to pick a sub-schema out of
-`WidgetContext.dynamicSchemas`. One value travelling down one channel,
-described twice, disagreeing on the one key that decides what those widgets
-show — so
-
-```ts
-{ field: 'fields', widget: 'field-selector', dependsOn: 'objectName' }
-```
-
-the only configuration that makes `field-selector` work, was a `TS2353` for
-anyone who typed their spec. It survived because in-repo specs reach the form
-through `as any` / loose types, so the authoring type was never asked.
-
-No runtime behaviour changes: `MetadataField` already handed `dependsOn`
-through and both widgets already read it. What changes is the type face — it
-now admits what the runtime has always accepted. The two descriptions are one
-declaration, extracted to a leaf module
-(`views/metadata-admin/form-spec.ts`) that both halves import, because
-`SchemaForm.tsx` imports `./widgets.js` and a back-edge would close a cycle.
-`SchemaForm.tsx` re-exports `FormFieldSpec` and `VisibilityPredicate`, so every
-existing importer is unaffected.
-
-`dependsOn` is `string | string[]` here, deliberately **not**
-`@object-ui/types`' wider canonical `DependsOnInput`, which also admits
-`{ field, param }` objects: both readers index `[0]` and use the result as a
-field name, so the wider shape would be a type that lies. Converging the two is
-its own decision, pinned as a refusal rather than taken silently.
diff --git a/.changeset/formpage-conditional-rule-surfaces-5627.md b/.changeset/formpage-conditional-rule-surfaces-5627.md
deleted file mode 100644
index 6f9028e01..000000000
--- a/.changeset/formpage-conditional-rule-surfaces-5627.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-'@object-ui/console': patch
----
-
-`FormPage` — the console's own form renderer, serving both the public `/f/:slug`
-route and the internal `/forms/:name` one — now honours the three conditional-rule
-surfaces it still dropped after objectui#5594: section-level `visibleWhen` /
-`visibleOn`, and the object-level field rules `visibleWhen` / `readonlyWhen` /
-`requiredWhen` (objectui#5627).
-
-This is the second form renderer in the repo, and it honoured exactly one of the
-four surfaces the sibling chain does. A section an author conditioned away rendered
-in full — heading and every control — on both routes including the anonymous one.
-The object-level half was worse than fail-open hiding: `readonly` was whatever the
-static flag said, so a field a `readonlyWhen` should have locked stayed editable and
-paired with the server's fail-closed unbound-scope behaviour into "the user edits,
-the save reports success, and the value never lands".
-
-Both halves evaluate through the SHARED machinery rather than a fourth consumer-side
-copy of the rule semantics: `@object-ui/core`'s `resolveFieldRuleState` for the three
-field rules — which brings the settled rulings with it, including the `serverOwnedValue`
-carve-out that keeps a create form from requiring a producer-owned control (#4069 /
-#4085) — and its `evalFieldPredicate` for the section predicate, with the canonical-first
-`visibleWhen ?? visibleOn` read every sibling reader spells.
-
-Visibility stays a RENDERING rule at both granularities: a hidden section's fields
-still submit their values, exactly as a hidden field's have since #5594.
diff --git a/.changeset/formpage-maxlength-override-5595.md b/.changeset/formpage-maxlength-override-5595.md
deleted file mode 100644
index 337dd53ad..000000000
--- a/.changeset/formpage-maxlength-override-5595.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-'@object-ui/console': patch
----
-
-`buildSections` now honours a FormView field's `maxLength` override instead of always
-taking the object's ceiling (objectui#5595).
-
-The function merges a form's field overrides with the target object's field definitions,
-and its own docstring states the rule: *"Field-level FormField overrides take precedence
-over object defaults."* Every key in the loop is built that way — `override.label ??
-def.label`, `override.required ?? def.required`, `override.placeholder ?? def.placeholder`
-— except one, which read `def.maxLength` unconditionally. So an author who set a tighter
-per-form limit (a short public intake form over a column whose object-level ceiling is
-generous) got the generous one.
-
-The failure was silent in the worst direction: no diagnostic, no warning, and the form
-still submits, so the symptom is a value the author believed the input refused being
-accepted. It is load-bearing rather than cosmetic — the merged row reaches the DOM at two
-`maxLength={field.maxLength}` sites, the `textarea` arm and the default `input type="text"`
-arm.
-
-`override.maxLength ?? def.maxLength` — `??` rather than `||`, matching the sibling keys,
-so an explicitly declared `0` stays a value the author wrote rather than falling through
-to the column's ceiling. This narrows only what the input allows; the object's storage
-ceiling still decides at submit time, so nothing that was accepted before is now rejected
-anywhere but at the keyboard.
-
-Why it survived: the console's local `FormFieldSpec` did not declare `maxLength` at all
-until objectui#5542, so no one typing a spec in this app could write the override in the
-first place, and the inert merge branch was never exercised. #5542 converged that type
-onto the shared app-shell declaration, which does declare the key — making the gap
-expressible, and therefore findable.
-
-The pin `#5542` left behind — `expect(row.maxLength).toBeUndefined()` in
-`FormPage.fieldSpec.test.ts`, which recorded the old answer explicitly rather than
-assuming it — is **inverted** to `toBe(40)` rather than deleted. It was the pre-registered
-evidence for this fix, and it is what made the gap findable in the first place, so it
-keeps its place and names the honoured answer.
diff --git a/.changeset/formpage-record-id-param-comment-4319.md b/.changeset/formpage-record-id-param-comment-4319.md
deleted file mode 100644
index d20c6fa82..000000000
--- a/.changeset/formpage-record-id-param-comment-4319.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
----
-
-Comment-only repair in `apps/console`'s `FormPage`: the `FORM_RECORD_ID_PARAM`
-docblock no longer borrows `createdRecordPath.ts` as a second witness for
-`@object-ui/app-shell`'s root-barrel unreachability. That sibling case was
-resolved when the host-app resolver was published from the package root, so a
-reader following the cross-reference found the opposite of what it promised.
-The surviving justification is unchanged and still true — the root barrel does
-not re-export `./urlParams` — and now states that fact directly. No published
-behaviour changes.
diff --git a/.changeset/gantt-viewmode-declared-both-branches-5074.md b/.changeset/gantt-viewmode-declared-both-branches-5074.md
deleted file mode 100644
index 650da3c57..000000000
--- a/.changeset/gantt-viewmode-declared-both-branches-5074.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-'@object-ui/types': minor
-'@object-ui/plugin-gantt': minor
----
-
-**`viewMode` is now declared authoring surface on `ObjectGanttSchema`, and both
-gantt renderer branches honour it** (objectui#5074, maintainer ruling
-2026-08-19: declare-and-wire; the spec half landed first upstream).
-
-- `ObjectGanttSchema` (TS interface and zod mirror) declares `viewMode`,
- DERIVED from the pinned `@objectstack/spec` `GanttConfigSchema.viewMode`
- enum by reference, so the member list cannot drift. Deliberately no
- default: an omitted `viewMode` keeps letting a persisted layout
- (`persistLayoutKey`) seed the timeline granularity before the renderer's
- `'day'` fallback.
-- The timeline branch (`GanttView`) now receives an authored `viewMode`.
- Previously only the resource-workload branch (`resourceView` +
- `assigneeField`) honoured it, so `viewMode: 'month'` on an ordinary gantt
- view was silently ignored.
-- The `(schema as any).viewMode` cast in `ObjectGantt` is retired; both
- branches read the declared `ganttConfig.viewMode`, which also honours the
- key when authored inside the spec's `gantt` config block.
-- Accept-set note: `viewMode` is now a DECLARED key, so an off-enum value
- (e.g. `viewMode: 'hour'`) becomes a zod validation error where it
- previously passed through unvalidated. Values on the published spec enum
- are unaffected.
diff --git a/.changeset/gate-listlocalinstalls-marketplace-5620.md b/.changeset/gate-listlocalinstalls-marketplace-5620.md
deleted file mode 100644
index cb6c4165c..000000000
--- a/.changeset/gate-listlocalinstalls-marketplace-5620.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`MarketplacePackagePage`'s third fetch effect — `listLocalInstalls` — is now gated on
-`marketplaceEnabled` and `isAdmin`, the same two predicates the page's other two fetch
-effects already check, in addition to (not instead of) its existing
-`features.installLocal` check (objectui#5620).
-
-`listLocalInstalls`'s only consumer is `localInstalls.find(...)` in the content branch,
-which is unreachable whenever the page has already returned `MarketplaceDisabled` (no
-marketplace on this runtime) or `MarketplaceAccessDenied` (a refused viewer) — both
-decided ahead of the content branch since objectui#5533 and objectui#5583. Before this
-fix, a runtime with `features.installLocal: true` still fired the request — and
-discarded its answer — on a marketplace-off runtime and for a non-admin, the same
-wasted-round-trip class objectui#5533 established the fix for on this page, on the flag
-that card was not about.
-
-`features.installLocal` remains its own axis: a runtime can mount a local kernel install
-path with no marketplace proxy at all, so the fix adds the two predicates as a
-conjunction rather than replacing the existing check. The request still fires exactly
-when it did before AND the viewer would actually see its answer.
diff --git a/.changeset/governed-surface-human-merge-5149.md b/.changeset/governed-surface-human-merge-5149.md
deleted file mode 100644
index 7d63234c4..000000000
--- a/.changeset/governed-surface-human-merge-5149.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
----
-
-Instruction files only — this publishes nothing, declared explicitly with an empty
-frontmatter rather than left undeclared. `AGENTS.md` records the governed surface
-(`AGENTS.md`, `CLAUDE.md`, `.claude/**`, `docs/adr/**`) and the rule that agent seats
-leave a PR touching it in draft for a human merge.
diff --git a/.changeset/grid-column-spelling-docs-5352.md b/.changeset/grid-column-spelling-docs-5352.md
deleted file mode 100644
index d80cdfb61..000000000
--- a/.changeset/grid-column-spelling-docs-5352.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
----
-
-Docs and skills only — this publishes nothing, declared explicitly with an empty
-frontmatter rather than left undeclared. No package `src/` is touched, so no
-`@object-ui/*` package changes behaviour and there is nothing here for a consumer
-to upgrade to.
-
-Corrects the two published corpora that taught `object-grid` columns in a `name`
-spelling `ObjectGrid` does not read. `ListColumnSchema` (`@objectstack/spec/ui`) is a
-strict object whose column-identity key is `field`; `{ "name": ... }` is refused by
-name (`unrecognized_keys: ["name"]`) and, at runtime, contributes no column.
-
-- `content/docs/api/schema-reference.md` — the `ObjectGridSchema` example authored a
- MIXED array (four bare strings followed by one column object). Two defects in one
- array: the object entry spelled `name`, and mixing forms is itself unsupported —
- `normalizeColumns` dispatches the whole array on `columns[0]`, so a column object
- standing behind a bare string is dropped whatever it spells. Renaming the key alone
- does not fix it; the example is now uniformly `ListColumn` objects. The `columns`
- row of the property table now names `field` and states the no-mixing rule.
-- `skills/objectui/guides/page-builder.md` — the grid example's three columns were
- all-object in the `name` spelling, so the grid rendered its row-number column and no
- data columns at all. Now spelled `field`. A note was added between the grid and form
- examples, which sit adjacent and mean the OPPOSITE thing by the same pair of words:
- `ListColumn.field` names the object field a column shows, while `FormField.name`
- names the field a form input writes. That adjacency is the documented cause of this
- defect family (`packages/core/src/utils/column-identity.ts`).
-
-The adjacent `object-form` example is unchanged and was never wrong — `FormField.name`
-is that layer's real key.
diff --git a/.changeset/grid-default-filters-lowering-4082.md b/.changeset/grid-default-filters-lowering-4082.md
deleted file mode 100644
index 1fa03310a..000000000
--- a/.changeset/grid-default-filters-lowering-4082.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-"@object-ui/plugin-grid": patch
----
-
-`ObjectGrid` lowers the deprecated `defaultFilters` through `toFilterNode` instead of
-byte-copying it onto `$filter` (objectui#4082).
-
-The query assembly already lowered the canonical `filter` key through `toFilterNode` —
-the repo's single "last hop before the wire" (objectui#4041) — while the legacy branch
-beside it assigned `params.$filter = schema.defaultFilters` verbatim. That made this the
-one leg on the chain reaching the wire unlowered: `plugin-list`'s `buildEffectiveFilter`
-and `plugin-view`'s non-grid fetch both already route the same value through
-`toFilterNode` / `mergeFilterNodes`.
-
-Byte-copying is refused on the wire for both shapes the slot carries. `defaultFilters` is
-declared `Record` (the MongoDB-style shape) and `isFilterAST` is false for a
-plain object; an array of `ViewFilterRule` objects fails the same predicate. Either one
-answers `400 INVALID_FILTER` — measured against a real backend in objectui#3431.
-
-`toFilterNode` handles both without new logic: objects route through
-`convertFiltersToAST`, rule arrays lower element-wise, and an AST already in the slot
-passes through untouched, so nothing is lowered twice. It also folds an absent or empty
-source to `undefined`, which is why the truthiness guard is gone — `defaultFilters: {}`
-used to send `$filter: {}`, asking the server a question with no content in a shape it
-refuses; now `$filter` is omitted, matching the canonical key's documented behaviour.
-
-**Grade — this is less dormant than the card assumed.** objectui#4082 was filed
-observation-class on "no measured producer", reasoning that `defaultFilters` is not in
-`object-grid`'s registered `inputs` so an author writing it only draws a save-gate
-warning. That reasoning covers authors, but not the framework: `plugin-view`'s
-`ObjectView` writes the slot itself, forwarding an active named view's `filter` as
-`defaultFilters: viewFilter || schema.table?.defaultFilters` in its `gridSchema` memo —
-and `plugin-view`'s own README documents `listViews..filter` as
-`[{ field, operator, value }, …]`, the exact shape objectui#3431 measured as
-`400 INVALID_FILTER`. The registered `object-view` / `view` renderer passes no
-`renderListView`, so that path falls through to `ObjectGrid` rather than to `ListView`,
-and `ListView`'s lowering does not cover it. So a schema-registration host — the
-documented authoring path — reached the raw assignment whenever an active named view
-carried a filter. Not asserted here: a failing request captured against a running
-deployment. `app-shell` is unaffected either way; it supplies `renderListView` and
-delegates to `ListView`, which lowers.
-
-Not in scope, and deliberately not done: retiring `defaultFilters`. This is
-consumer-side only — the key the schema admits is unchanged, and its precedence behind
-the canonical `filter` is unchanged.
-
-The sibling legacy `defaultSort` leg was graded and needs no change; see the PR for the
-measurements.
diff --git a/.changeset/grid-dom-attribute-whitelist-4787.md b/.changeset/grid-dom-attribute-whitelist-4787.md
deleted file mode 100644
index 1dc2d3506..000000000
--- a/.changeset/grid-dom-attribute-whitelist-4787.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-'@object-ui/components': patch
----
-
-The `ui:grid` renderer now forwards to the DOM by whitelist, so schema keys no longer
-land on the rendered `
` as invalid HTML attributes (objectui#4787).
-
-`grid.tsx` ended in a bare `{...gridProps}` spread that removed only `data-obj-*` and
-`style`, so everything else `SchemaRenderer` hands a registered component reached the
-element. Measured on a canary node, eight attributes leaked:
-`columns="4"`, `gap="4"`, `mdcolumns="2"`, `smcolumns="2"`, `name="grid_node"`,
-`props="[object Object]"`, `colorvariant="x"` (the flattened `props` container) and an
-unknown authored `zzcanary="leak"`. A responsive `columns` object rendered as
-`columns="[object Object]"`. Layout was unaffected, so every catalog grid example
-rendered with them — the reason this went unnoticed.
-
-The spread now goes through `toDomProps` from `@object-ui/core`, the same whitelist
-objectui#3291 established in `packages/fields` and objectui#4425 phase 2 promoted to the
-SDUI widget contract. Keys that are *declared* DOM-safe survive — `id`, `className`,
-`role`, `tabIndex`, plus the open `data-*` and `aria-*` families, which is how the
-designer's `data-obj-id` / `data-obj-type` still arrive — and `style` continues to be
-forwarded by name. Nothing an author can add to a grid node reaches the DOM implicitly
-any more, including keys `GridSchema` does not have yet; enumerating today's keys to
-strip would have re-rotted on the next schema addition.
-
-No authored input changes and no layout changes: the grid's own vocabulary was always
-read off `schema`, never off these props.
diff --git a/.changeset/grid-unresolved-column-diagnostic-5349.md b/.changeset/grid-unresolved-column-diagnostic-5349.md
deleted file mode 100644
index 43d1c5a7d..000000000
--- a/.changeset/grid-unresolved-column-diagnostic-5349.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-'@object-ui/plugin-grid': patch
----
-
-`ObjectGrid` says which column it dropped, instead of rendering a header-only grid in silence.
-
-objectui#5068 retired the undeclared `accessorKey` / `header` tolerance branch, so
-`ListColumnSchema`'s `field` / `label` is now the only column spelling the renderer
-reads. That was right — the spec refuses `accessorKey` and `header` by name, and the
-census found zero authored usages. But it relocated a failure mode instead of removing
-it: a column authored in a spelling the renderer does not read contributed nothing, and
-nothing said so. No error, no warning, no empty state — the author got a grid with its
-row-number column and no data columns, which is a success receipt for a disagreement
-between the renderer and the author.
-
-An authored column that can never resolve now emits one `console.warn` naming the
-address rather than the symptom: which block (`object-grid` or the `view:grid` alias),
-which object and label, which `columns[i]`, the keys that entry actually carries, and the
-rewrite that works — for a column authored `{ accessorKey: 'amount', header: 'Amount' }`
-the message spells out `{ field: 'amount', label: 'Amount' }`. It reuses the channel `ObjectGrid` already had for "you declared it, the renderer dropped
-it" (the export-format warning), rather than adding a second differently-shaped one.
-
-Rendering is unchanged in every case: this is additive. The diagnostic reads the
-`columns` input and nothing else — it never asks whether the grid found rows, because
-`object-grid` legitimately draws them from five different places (a bare `data` array,
-`data.provider: 'value'`, legacy `staticData`, `bind`, or a host that owns the fetch and
-passes the window down as a `data` React prop, which is what `plugin-list`'s `ListView`
-does). All five are pinned by test, in both directions. A `hidden: true` column is
-authored intent and is never reported, and so are the arms that legitimately produce no
-columns of their own: no `columns` key, an empty `columns` array, and the `string[]`
-spelling.
-
-A throw was rejected: a grid that renders nothing today would become a page that renders
-nothing.
diff --git a/.changeset/grid-user-actions-collision-5240.md b/.changeset/grid-user-actions-collision-5240.md
deleted file mode 100644
index 8f7c79a35..000000000
--- a/.changeset/grid-user-actions-collision-5240.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/plugin-grid': patch
----
-
-`object-grid` harvests row-action predicate fields from the OBJECT's `userActions` block only — a view's toolbar policy can no longer shadow it.
-
-`userActions` names two different blocks. On a **view** it is toolbar policy —
-the spec's `UserActionsConfigSchema` (`sort`, `search`, `filter`, `refresh`,
-`rowHeight`, `addRecordForm`, `editInline`, `buttons`), which rejects `edit` by
-name. On an **object** it is the CRUD-predicate block (`edit` / `delete` /
-`create` carrying `visibleWhen` / `disabledWhen`, objectui#2614) — and that is
-the only shape `listViewPredicates` can read, since its loop skips every
-non-object value.
-
-`ObjectGrid` read the key view-first when building the `$select` projection
-(`(schema as any).userActions ?? resolvedSchema.userActions`). A view carrying a
-perfectly legal toolbar block therefore shadowed the object's CRUD predicates,
-the harvest found none, and the predicate's operand left the projection. CEL then
-faults on the absent key, fails closed, and the row Edit/Delete button disappears
-for everyone with nothing pointing at the projection — objectui#3501's failure,
-reached with a success receipt at every step.
-
-The view-level block is not hypothetical: `SpecBridge.transformListView` copies
-it onto the `object-grid` node the renderer receives, and `app-shell`'s
-`ObjectView` builds one unconditionally.
-
-The harvest now reads the resolved object block only. Both `userActions` read
-sites carry a comment naming the collision, and
-`__tests__/gridNonAuthorKeys.test.tsx` pins each clause of it: the two shapes,
-the producer that writes the view one, the harvest's blindness to it, and the
-projection that must keep the object's operand with a toolbar block present.
-
-Toolbar policy itself is untouched — it was never read through this path.
diff --git a/.changeset/grid-widget-aria-invalid-3318.md b/.changeset/grid-widget-aria-invalid-3318.md
deleted file mode 100644
index e644f0adf..000000000
--- a/.changeset/grid-widget-aria-invalid-3318.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-'@object-ui/fields': patch
----
-
-Grid field widget: announce a form-level validation failure to assistive tech.
-
-A required `grid` submitted while still empty rendered its "is required" message
-but marked nothing — every row was a ghost row, and ghost rows were skipped by
-the widget's per-cell validity channel. A sighted user saw the red message; a
-screen-reader user was told nothing at all.
-
-The host failure now drives the per-cell channel the widget already owns: when
-the `error` slot is set on an empty grid, the ghost entry row's required cells
-flag, and the mark sits on each cell's own control rather than on the `td`
-wrapper (a `td` is not focusable, and assistive tech reads validity from the
-control). Populated grids are unaffected — they already marked their own empty
-required cells inline.
diff --git a/.changeset/hollow-view-overlay-hydration-pin-5773.md b/.changeset/hollow-view-overlay-hydration-pin-5773.md
deleted file mode 100644
index a389f4ca4..000000000
--- a/.changeset/hollow-view-overlay-hydration-pin-5773.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
----
-
-Tests only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. No package `src/` is touched; the only file added is
-`packages/app-shell/src/views/InterfaceListPage.hollowOverlayHydration.test.tsx`.
-
-objectui#5773 — pins the intended post-objectui#5233 behaviour at the seam the card
-named: `InterfaceListPage`'s hollow-view hydration effect merging a personalization
-overlay row it fetched through `listViewOverrides`/`getView` (the same undocked-narrowing
-read path `narrowPersonalizationOverlay`'s docblock in `packages/data-objectstack/src/index.ts`
-describes).
-
-Reachability was measured, not reasoned about — constructed through the REAL
-`ObjectStackAdapter` write (`updateViewConfig`/`buildPersistedViewBody`, the same seam
-`ObjectView.overlayPatchOnly.test.ts` uses) and the REAL `InterfaceListPage` render, not a
-hand-written override fixture. Two cases:
-
-- A hollow ADR-0017 expansion item (system view, no `columns`) plus the CURRENT
- (post-#5233) thin overlay row a toolbar toggle writes today: the page renders
- `defaultColumnsFromObject`'s defaults — reachable, and the intended behaviour per the
- card's disposition (an overlay was never a legitimate source of a view body).
-- The same hollow view with a PRE-#5233 fat overlay row (the shape an install that has
- not touched this view since before the fix is still carrying, per
- `ObjectView.overlayPatchOnly.test.ts`'s own "PRE-FIX" framing): the page renders the
- frozen stale columns instead — a control proving the pin above discriminates a real
- hydration-effect outcome rather than passing vacuously.
-
-Confirmed load-bearing by reverse verification: with the hydration effect's guard
-short-circuited (`if (true) return;`), the CONTROL case flips from the frozen `["status"]`
-to the un-hydrated defaults `["name","status"]` — the assertion depends on the fetch +
-merge actually running. No production code changed; `InterfaceListPage.tsx`'s hydration
-effect already behaves this way today, this was untested rather than unreachable.
diff --git a/.changeset/home-ai-studio-flag-5521.md b/.changeset/home-ai-studio-flag-5521.md
deleted file mode 100644
index 411aa0a99..000000000
--- a/.changeset/home-ai-studio-flag-5521.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-'@object-ui/app-shell': minor
----
-
-Console Home stops offering the metadata-authoring front door on a deployment
-whose own runtime config says authoring is not offered there (objectui#5521).
-
-The "Build an app" cover card is now withheld when
-`GET /api/v1/runtime/config` reports `features.aiStudio: false`. On the composed
-hosted-SaaS shape that card led a plain tenant into the full authoring flow
-behind a runtime whose `/api/v1/meta/*` answers `403` and whose ToolRegistry
-holds zero authoring handlers — the entry was offered and the refusal arrived at
-submit. The lockdown criterion for that shape is two-part, UI entry hidden AND
-API refused; only the backend half was green.
-
-- The card is **hidden, not dimmed**, because that is the flag's own declared
- meaning on both sides of the wire: `RuntimeFeatures.aiStudio` documents "when
- false, the SPA hides the AI authoring affordances", and the serving plugin
- documents "set false to force-hide the authoring UI".
-- `features.marketplace` keeps the different presentation objectui#5504 gave it
- — a dimmed card plus a visible localized reason. That flag means a route is
- reachable; this one means force-hide. "Start with a template" is untouched:
- installing a marketplace package is not AI metadata authoring and answers to
- its own flags.
-- No reason line is rendered in its place. `home.build.noCapability` says the
- *account* lacks "Manage Metadata"; on a runtime with no authoring at all the
- surface is absent for everyone, and pointing a viewer at a permission that
- would not help them is the misdirection objectui#5557 is about.
-- Unknown fails **OPEN** (`!== false`), the doctrine `isMarketplaceEnabled()`
- already encodes: a runtime predating the flag, or one whose config fetch
- failed, keeps the card exactly as visible as before.
-
-No new authorable config key, no new server surface, and no new copy — the flag
-was already being served and already reaches the browser.
diff --git a/.changeset/host-dispatch-action-keys-5611.md b/.changeset/host-dispatch-action-keys-5611.md
deleted file mode 100644
index 1f43f6e85..000000000
--- a/.changeset/host-dispatch-action-keys-5611.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-'@object-ui/core': patch
----
-
-The dev-mode unknown-key warning stops flagging `overrideNotice`, the console's
-privileged-override safety copy (objectui#5611).
-
-`ActionRunner.execute` classifies the object it was HANDED, and a console host
-hands it a DISPATCH, not a stored metadata row. `DeclaredActionsBar` composes
-`overrideNotice` on that dispatch and two param-collection handlers read it —
-yet the key inventory only mirrored AUTHORED surfaces, so the runner reported a
-key two files read as one "no reader recognizes", and prescribed promoting it to
-an explicit field on `ActionDef`. That prescription is the one shape the
-2026-08-22 maintainer ruling forbids for this key, so acting on the diagnostic
-walked an author into a rejected design. A false warning on the product's own
-privileged path — the branch that finalises an approval over approvers who have
-not acted — is how a dev console gets muted.
-
-Adds an exported `HOST_DISPATCH_ACTION_KEYS` (sole member `overrideNotice`) to
-`actions/actionKeys.ts` and unions it into `KNOWN_ACTION_KEYS`, which is the
-fourth input to that set and the first one that is not an authored-surface
-mirror. Measured before and after on the exact dispatch the bar composes: the
-warning went from one call naming `overrideNotice` to none, `KNOWN_ACTION_KEYS`
-grew by exactly one member, and an action carrying a real typo alongside it
-still warns — naming `targt` only.
-
-The authored surface does not move. `overrideNotice` is still NOT declared on
-`ActionDef` and still NOT in `ACTION_DEF_KEYS`; writing it in an action literal
-remains a compile error, and the AST-derived pin over the interface is unchanged.
-Membership in `KNOWN_ACTION_KEYS` widens what the WARNING tolerates, never what
-an author may write — `actionKeys.pin.test.ts` now pins both halves, including
-the new list's exact contents so a second member cannot arrive quietly.
diff --git a/.changeset/host-only-viewtypes-tree-icon-5321.md b/.changeset/host-only-viewtypes-tree-icon-5321.md
deleted file mode 100644
index 1c922a8be..000000000
--- a/.changeset/host-only-viewtypes-tree-icon-5321.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-'@object-ui/plugin-view': patch
----
-
-A host-composed `tree` view is now labelled with the tree icon in `ObjectView`'s
-view switcher instead of the grid one, and the `tree` / `chart` view types are
-recorded as host-composition-only surfaces (objectui#5321).
-
-`viewSwitcherSchema`'s `iconMap` carried an entry for every view type except
-`tree`, so a tree view fell through to the `|| 'table'` fallback and was drawn
-with the grid glyph. objectui#2916 fixed exactly this once, for `chart`, by
-adding a single key — nothing recorded that the map had to be COMPLETE, so the
-next missing member went unnoticed. The map is now typed
-`Record`, which is how `ViewSwitcher`'s own
-`DEFAULT_VIEW_ICONS` (the consumer of these strings) has always been declared:
-a future `ViewType` member fails `type-check` rather than silently rendering as
-a grid. The `tree` value is `'list-tree'`, the same `ListTree` glyph
-`DEFAULT_VIEW_ICONS` already names for this view type, and the runtime fallback
-stays for host props that carry an unrecognised type. Reached in practice by
-the console, whose `CreateViewDialog` offers `tree` among the view types a user
-can create.
-
-No authoring surface changes. `generateViewSchema` renders eight view types
-while `ObjectViewSchema.defaultViewType` and `NamedListView.type` admit six of
-them, so `tree` and `chart` are selectable only through the component's `views`
-prop. The maintainer ruled on 2026-08-20 that both stay recorded as
-host-composition-only rather than being added to those unions, following the
-objectui#5097 precedent; the record now lives beside that one, with the branch
-set derived from a source fence, the authored unions pinned at the type level,
-and host reachability measured.
diff --git a/.changeset/humanize-label-single-home-5444.md b/.changeset/humanize-label-single-home-5444.md
deleted file mode 100644
index 1e4cfdd0e..000000000
--- a/.changeset/humanize-label-single-home-5444.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-'@object-ui/core': patch
-'@object-ui/fields': patch
-'@object-ui/plugin-charts': patch
----
-
-The value-fallback label prettifier `humanizeLabel` has one implementation instead of two byte-identical copies.
-
-`humanizeLabel` turns a stored value into a display string when nothing else
-resolves it — an option with no declared label, an object name, a chart axis
-member. It existed twice, byte for byte: once in `@object-ui/fields` (read by
-`plugin-grid`, `plugin-gantt`, `plugin-detail` and by that package's own
-renderers) and once as a deliberate local copy in `plugin-charts`'
-`ObjectChart.tsx`, whose comment said it was there "to avoid a dependency on
-`@object-ui/fields`".
-
-Two copies of one convention is a live hazard rather than tidiness: one
-dashboard can hold a chart and a grid over the same stored value, so a change
-landing on one copy alone would put that value on screen under two spellings at
-once. The single implementation now lives in `@object-ui/core` — the shared
-ancestor both packages already depend on, so the dependency the copy existed to
-avoid is still avoided and no new edge is created, and core takes no React
-(objectui#4389: core-canonical logic, plugins consume). Both former sites
-re-export it, so `import { humanizeLabel } from '@object-ui/fields'` keeps
-working unchanged.
-
-**Nothing rendered changes.** The surviving implementation is byte-identical to
-both deleted copies, and each former call site is pinned by identity against the
-core function — not by a copied output table that someone would have to remember
-to edit in two places.
-
-The core module also writes down, for the first time, why this convention stays
-distinct from `humanizeFieldKey` (the KEY fallback, in `@object-ui/plugin-dashboard`),
-which additionally splits camelCase:
-
-```
-input humanizeFieldKey humanizeLabel
-needs_analysis Needs Analysis Needs Analysis
-NeedsAnalysis Needs Analysis NeedsAnalysis <- differ
-unitPrice Unit Price UnitPrice <- differ
-BestCase Best Case BestCase <- differ
-lost-to-competitor Lost-To-Competitor Lost To Competitor <- differ
-```
-
-A field KEY is authored in the codebase and carries a machine spelling, so
-splitting camelCase recovers words its author meant. A stored VALUE is arbitrary
-tenant data, where a mid-token capital is not reliably a word boundary and
-splitting it rewrites what the tenant wrote (`McDonald` to `Mc Donald`). The two
-conventions also do not nest — on the last row each leaves alone the separator
-the other rewrites. Whether they should ever converge is a separate decision
-that would move rendered output in four packages at once; it is deliberately not
-made here.
diff --git a/.changeset/i18n-guide-label-rule-5081.md b/.changeset/i18n-guide-label-rule-5081.md
deleted file mode 100644
index 16ec799ad..000000000
--- a/.changeset/i18n-guide-label-rule-5081.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
----
-
-Docs + gate ledger only — this publishes nothing, declared explicitly with an empty
-frontmatter rather than left undeclared.
-
-`skills/objectui/guides/i18n.md` attributed its label rule to `@objectstack/spec` v4 in two
-places (`:117`, `:162`) while every manifest here declares `^17.0.0` and `node_modules`
-carries `17.0.0` — thirteen majors, on the surface an AGENT reads before it writes a user's
-project. The version number was the reported defect; measurement against the installed
-package found the rule it was backing to be wrong as well, which is why neither arm of the
-original fork (renumber to v17, or drop the qualifier and keep the sentence) was writable:
-both would have laundered a v4-era false statement into a current one. Maintainer ruling
-2026-08-20, option A: restate the rule per the installed spec, with no version qualifier.
-
-Measured against `@objectstack/spec` 17.0.0's published dist, `I18nLabelSchema` is a union
-of a plain string and an inline locale map whose keys must match
-`/^(default|[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*)$/` — a BCP-47 tag, or `default`. The guide now
-states both forms, shows an inline-map example on the two keys this repo was measured to
-resolve (`card.title` and `button.label`, both through `pickLocalized`), and keeps its
-"don't use `{key, defaultValue}`" advice with the real reason: that key-reference vocabulary
-was retired in objectstack#5055, the spec rejects the object with its own message, and if one
-reaches a renderer anyway `pickLocalized` falls through to the first string value and paints
-the raw translation key on screen.
-
-The `KNOWN_CLAIMS` entry that inventoried the fossil as `stale` is deleted in the same commit
-— the downward ratchet in `scripts/__tests__/doc-version-claims.test.ts` ("no entry may
-outlive the claim it excuses") turns red otherwise — and that file's header prose, which
-restated the now-falsified two-branch fork, is corrected to record what the fork actually
-turned out to be.
diff --git a/.changeset/i18nlabel-comment-4611.md b/.changeset/i18nlabel-comment-4611.md
deleted file mode 100644
index 3a243e944..000000000
--- a/.changeset/i18nlabel-comment-4611.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-'@object-ui/types': patch
----
-
-`ActionParam`'s doc block no longer claims that spec 17 narrowed `I18nLabel` to a
-plain string (objectui#4611).
-
-The paragraph explaining why `label` / `options[].label` are inherited rather than
-locally overridden justified itself with a claim about `@objectstack/spec` that was
-never true: "in spec 17 `I18nLabelSchema` is `z.ZodString` — inline per-locale objects
-were dropped in favour of translation files". Measured against the installed GA pin
-`@objectstack/spec@17.0.0` (`dist/ui/index.d.ts:614`), `I18nLabelSchema` is a union of
-a string and a string-to-string record, and the schema's own doc block states two
-authorized forms with "Both are real; neither is deprecated by this schema". Executed
-against `dist/ui/index.mjs`: plain string accepted, inline locale map accepted,
-`{ key, defaultValue }` rejected. A reader who believed the comment would have taken a
-widening to `string | I18nLabel` for a no-op — which is what the finding recorded, one
-seat having nearly done exactly that.
-
-The replacement describes what `I18nLabel` admits and cites the spec's own doc block
-rather than restating a zod expression; where today's spelling is named it is scoped as
-a measurement against 17.0.0 with its file and line, so it ages as a reading rather than
-as a standing fact. The decision itself is unchanged and never depended on the false
-premise — `label` flows in by reference through the spec's schema, and a local
-`string | I18nLabel` collapses to `I18nLabel` whichever forms the union holds.
-
-Documentation only, and the release-visible surface is the declaration file: measured
-with the package's real `tsc` build (`removeComments: false`, per `tsconfig.base.json`),
-108 emitted files on both sides, `dist/ui-action.d.ts` 29,176 → 31,026 bytes, and every
-other file byte-identical — including `dist/ui-action.js` (3,480 bytes, unchanged sha),
-because the comment documents an `interface`, which is erased at emit along with its
-leading comment. No behaviour changes; hover text and the shipped `.d.ts` do.
diff --git a/.changeset/i18nlabel-inverted-pin-5612.md b/.changeset/i18nlabel-inverted-pin-5612.md
deleted file mode 100644
index ffa052d0b..000000000
--- a/.changeset/i18nlabel-inverted-pin-5612.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-'@object-ui/types': patch
----
-
-The `I18nLabel` "inverted pin" now watches the premise it claims to watch, and
-`ui-action.ts` no longer imports a symbol it never uses (objectui#5612, objectui#5613).
-
-Both are residue of the same removed local `label` / `options[].label` override.
-
-The `it(...)` case in `packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts`
-that called itself an inverted pin on the spec's `I18nLabel` rested on one assertion,
-`const label: SpecI18nLabel = 'Priority'`, under a comment claiming spec 17 had narrowed
-`I18nLabel` to a plain string and that a re-widening would stop it compiling. A plain
-string is assignable under the narrow shape *and* under the wide one, so that assignment
-could only ever fail if the plain-string form were removed — the opposite of the event it
-was written to catch. The widening had already landed: `@objectstack/spec@17.0.0`
-declares `I18nLabelSchema` as a union of a string and a string-to-string record
-(`dist/ui/index.d.ts:614`), and the pin stayed green through it. It reported protection
-it did not provide, and asserted a false premise in its own name.
-
-It is retargeted at what actually holds the decision up — not which single form the spec
-has, but that **both** authorized forms stay assignable, on the spec type and on the
-inherited `ActionParam['label']` and `options[].label`. It now fails when either form is
-withdrawn, and deliberately does not fail on a further widening, since inheriting by
-reference is exactly what stays correct as the authorized set moves. The comment is
-rewritten against the schema's own doc block (two authorized forms, "Both are real;
-neither is deprecated by this schema") instead of the false premise. Verified by
-construction: against a locally built narrow `type I18nLabel = string` the new assertions
-fail with `TS2344` and `TS2322`, where the old assignment compiles clean under both
-shapes.
-
-`ui-action.ts`'s `I18nLabel` type import is deleted — no type position had used it since
-the override was removed, and nothing re-exported it — and the doc paragraph that
-recorded the pin as `NOT guarded` is corrected, since the same change makes it a guard.
-
-No behaviour changes; the release-visible surface is the declaration file. Measured with
-the package's real `tsc` build, both legs building from a cleared `dist/` and cleared
-composite build info: 108 emitted files on both sides, exactly one differing —
-`dist/ui-action.d.ts`, 31,026 → 31,117 bytes, JSDoc prose only, no declaration changed.
-Every other file is byte-identical, including `dist/ui-action.js` (3,480 bytes, unchanged
-sha), because the comment documents an `interface`, which is erased at emit along with
-its leading comment. The deleted type import contributes no emitted delta at all, and the
-rewritten test file is not part of the build.
diff --git a/.changeset/inbox-actor-name-retired-5203.md b/.changeset/inbox-actor-name-retired-5203.md
deleted file mode 100644
index ebb7ec8a9..000000000
--- a/.changeset/inbox-actor-name-retired-5203.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
----
-
-Internal only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared.
-
-Retires `InboxNotification.actor_name` (`packages/app-shell/src/layout/inboxGrouping.ts`),
-which was dead at both ends. `mergeInboxRows`
-(`packages/app-shell/src/hooks/sharedUserFeeds.ts`) is the single producer of every row
-the bell and Home's action centre render and never mapped it; neither consumer read it;
-and `sys_inbox_message` declares no actor column for it to have been mapped FROM. It was
-the last declared-but-unfilled member of that interface after objectui#5190 removed the
-sibling `source_object` / `source_id` pair.
-
-**No published type surface changes.** `InboxNotification` is not reachable from
-`@object-ui/app-shell`'s public entry: neither `src/index.ts` nor `src/layout/index.ts`
-re-exports it, the built `dist/index.d.ts` does not name it, and the package `exports`
-map offers only `.` and `./styles.css` — no deep subpath an external consumer could
-import it through. The type is internal to the package, so removing an optional member
-of it is not an externally observable narrowing and nothing user-visible ships. Runtime
-behaviour is unchanged in both directions: no code path produced the field and no code
-path read it.
-
-Two pins keep it retired, in opposite directions — a TYPE PIN in
-`layout/__tests__/inboxGrouping.test.ts` that fails if the field is re-declared, and a
-runtime key-set pin in `hooks/__tests__/sharedInboxFeed.rowShape.test.tsx` that fails if
-the producer is ever changed to spread raw `sys_inbox_message` columns through instead
-of mapping them field by field.
diff --git a/.changeset/issue-5389-workspace-admin-positions.md b/.changeset/issue-5389-workspace-admin-positions.md
deleted file mode 100644
index 2c12bc2f6..000000000
--- a/.changeset/issue-5389-workspace-admin-positions.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-'@object-ui/auth': patch
----
-
-Restore platform-admin detection for permission-set-derived administrators.
-
-`useIsWorkspaceAdmin` decides Setup app + Studio visibility, App Marketplace
-gating and the "Build an app" CTAs. Its third source read `user.roles`, a key
-the protocol-17 session face no longer emits (framework ADR-0090 D3 renamed it
-to `positions`). An administrator whose adminship comes from the
-`admin_full_access` permission set — the single-tenant deployment shape, where
-there is no organization member row and the server deliberately no longer
-overwrites `user.role` — matched none of the three sources and read as **not an
-administrator**: Setup and Studio simply disappeared for them.
-
-The hook now reads `user.positions[]`, the one spelling the session publishes.
-Detection is restored for that path and unchanged everywhere else: an active
-member row with an admin role, a stored `user.role` admin scalar, and
-preview/no-auth mode all behave exactly as before, and nobody who was not an
-administrator becomes one — pinned by four negative cases alongside the
-positive one.
-
-Also corrects the now-stale documentation that described the removed spelling:
-the hook's own docblock, the `roles?: string[]` declaration on the client
-`AuthUser` (kept for one remaining compile-time reader; see objectui#5424), and
-two comments in `@object-ui/app-shell`'s Home page. No behaviour change from the
-comment corrections.
diff --git a/.changeset/issue-5474-static-table-narrow-types.md b/.changeset/issue-5474-static-table-narrow-types.md
deleted file mode 100644
index 11b8b4627..000000000
--- a/.changeset/issue-5474-static-table-narrow-types.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-'@object-ui/types': minor
-'@object-ui/components': minor
----
-
-Split the static `table` column type off the rich shared `TableColumn`
-(objectui#5474, maintainer ruling 2026-08-22: Option C), so declared =
-enforced holds per renderer.
-
-`TableColumn` is unchanged and remains the rich shape `data-table`,
-`CRUDSchema` and detail-view relations honour. The static `table` renderer's
-`TableSchema.columns` now declares the new narrow `StaticTableColumn`
-(`header`, `accessorKey`, `className`, `cellClassName`, `width` — exactly the
-keys that renderer reads). The eleven keys the static renderer never read are
-retired from its surface as ADR-0049 tombstones: `hoverable` / `striped` on
-`TableSchema`, and `minWidth` / `align` / `fixed` / `type` / `sortable` /
-`filterable` / `resizable` / `editable` / `cell` on its columns.
-
-Breaking for authored metadata that wrote those keys on a `type: 'table'`
-node: they were silently inert before and are now refused loudly — a tsc
-error on the interface (`?: never`) and a parse rejection naming the key in
-`@object-ui/types/zod`. That loud refusal is the ruled outcome. Migration:
-nodes that wanted the interactive behaviour move to `type: 'data-table'`
-(whose columns keep the rich `TableColumn`); right-aligned columns on the
-static table use `cellClassName: 'text-right'`; alternate-row styling uses
-Tailwind on `className`.
diff --git a/.changeset/issue-5619-workspace-admin-unresolved.md b/.changeset/issue-5619-workspace-admin-unresolved.md
deleted file mode 100644
index a6dd79f81..000000000
--- a/.changeset/issue-5619-workspace-admin-unresolved.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-'@object-ui/auth': minor
-'@object-ui/app-shell': minor
-'@object-ui/console': minor
----
-
-Stop showing an access-denied screen to a real administrator while their
-adminship is still resolving.
-
-`useIsWorkspaceAdmin()` returned a bare `boolean`, so "the inputs have not
-arrived yet" and "resolved: not an admin" were the same answer. One of its three
-sources — the active organization member row — is fetched some round trips after
-the session (`listOrganizations` → `getActiveOrganization` → `getActiveMember`),
-so an administrator whose adminship lives only in that row rendered at least
-once as a non-admin, and every gate downstream acted on it: the two marketplace
-surfaces painted `MarketplaceAccessDenied`, the console chrome dropped and
-re-added its admin nav entry, and `AppContent` fired a `` that the later flip could not undo.
-
-**Breaking (published API, hence `minor` per this repo's version policy):**
-`useIsWorkspaceAdmin(): boolean` is replaced by
-`useWorkspaceAdminStatus(): { isAdmin: boolean; isResolved: boolean }`. The old
-name is removed rather than kept alongside, so a call site that ignores the
-third state fails to compile instead of silently refusing an administrator.
-
- -const isAdmin = useIsWorkspaceAdmin();
- +const { isAdmin, isResolved } = useWorkspaceAdminStatus();
-
-`AuthProvider` gains `isMembershipResolved` on its context — the organization /
-member pipeline has reached a terminal state — because `organizations`,
-`activeOrganization` and `activeMember` read `[]` / `null` / `null` both before
-the pipeline starts and after it finds nothing.
-
-No extra wait for administrators: `isResolved` is true the instant `isAdmin` is,
-so an admin the session already identifies through `positions[]` never waits on
-the member row.
diff --git a/.changeset/issue-5709-unconsumed-widget-option-warning.md b/.changeset/issue-5709-unconsumed-widget-option-warning.md
deleted file mode 100644
index c4a548574..000000000
--- a/.changeset/issue-5709-unconsumed-widget-option-warning.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-'@object-ui/sdui-parser': minor
----
-
-`validateTree` now reports a dashboard widget `options` key that no renderer
-consumes as a `unconsumed-widget-option` **warning** naming the consumed set
-(objectui#5709 ruling). The census behind the accepted set — the spec's five
-declared query keys (`dateGranularity`, `sortBy`, `sortOrder`, `limit`,
-`stageOrder`) plus the `description` sub-caption convention key — is
-re-measured on every test run against `@objectstack/spec` and the
-`plugin-dashboard` renderer sources. The check fires only on dataset-bound
-shorthand widgets (the spec-legal form) hosted by `dashboard` /
-`dashboard-grid` nodes, and honours the spec's per-widget
-`suppressWarnings: ['unconsumed-widget-option']` escape hatch. Warning
-severity only: documents keep parsing, saving and rendering. Exported for
-other surfaces: `checkDashboardWidgetOptions`, `CONSUMED_WIDGET_OPTION_KEYS`,
-`DASHBOARD_WIDGET_HOST_TYPES`, `UNCONSUMED_WIDGET_OPTION`.
diff --git a/.changeset/layout-renderers-dom-props-5574.md b/.changeset/layout-renderers-dom-props-5574.md
deleted file mode 100644
index b018f67b4..000000000
--- a/.changeset/layout-renderers-dom-props-5574.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-'@object-ui/components': patch
----
-
-**Behaviour change:** the `flex`, `stack`, `container` and `text` renderers no
-longer forward their whole prop bag to the host element. They route it through
-`toDomProps` — the same whitelist `grid` was converged on — so an authored
-schema key becomes an HTML attribute only if the SDUI DOM contract declares it
-one (objectui#5574).
-
-What this stops reaching the DOM: the renderer's own declared props, which were
-consumed off `schema` to build the class list AND spread onto the element a
-second time as attributes HTML does not define. Measured across
-`examples/schema-catalog`, rendered through the real `SchemaRenderer`: 1194
-illegitimate attributes over 1141 nodes — `text[content]` 522, `flex[align]`
-198, `flex[gap]` 193, `stack[gap]` 153, `flex[justify]` 98, `container[padding]`
-14, `container[maxwidth]` 6, `flex[direction]` 5, `stack[align]` 4,
-`text[value]` 1. The same probe reads 0 after, with `grid`'s 26 nodes at 0 both
-times as the control.
-
-Nothing an author writes renders differently: every leaked key was already being
-read off `schema` and applied as a class, so the markup loses attributes that
-never had meaning and keeps the styling that did. `id`, `role`, `tabIndex`,
-`className`, `style`, event handlers and the open `data-*` / `aria-*` families
-are unchanged — including `data-obj-id` / `data-obj-type`, which now arrive
-through the `data-*` family rather than by hand.
-
-Anything that read one of the leaked attributes off the DOM — a CSS attribute
-selector such as `[gap="4"]`, or a test asserting `align` on a rendered `flex` —
-must read the schema or the class instead. No `@object-ui` code did; this is
-called out because the attributes were externally visible while they lasted.
diff --git a/.changeset/list-user-actions-collision-5398.md b/.changeset/list-user-actions-collision-5398.md
deleted file mode 100644
index 62a0d3c77..000000000
--- a/.changeset/list-user-actions-collision-5398.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-'@object-ui/plugin-list': patch
----
-
-`list-view` harvests row-action predicate fields from the OBJECT's `userActions` block only — a view's toolbar policy can no longer shadow it.
-
-`userActions` names two different blocks. On a **view** it is toolbar policy —
-the spec's `UserActionsConfigSchema` (`sort`, `search`, `filter`, `refresh`,
-`rowHeight`, `addRecordForm`, `editInline`, `buttons`), which rejects `edit` by
-name. On an **object** it is the CRUD-predicate block (`edit` / `delete` /
-`create` carrying `visibleWhen` / `disabledWhen`, objectui#2614) — and that is
-the only shape `listViewPredicates` can read, since its loop skips every
-non-object value.
-
-`ListView` read the key view-first when building the `$select` projection
-(`(schema as any).userActions ?? (objectDef as any)?.userActions`). A view
-carrying a perfectly legal toolbar block therefore shadowed the object's CRUD
-predicates, the harvest found none, and the predicate's operand left the
-projection. CEL then faults on the absent key, fails closed, and the row
-Edit/Delete button disappears for everyone with nothing pointing at the
-projection — objectui#3501's failure, reached with a success receipt at every
-step.
-
-This is the sibling of the `plugin-grid` read site fixed in objectui#5426, and
-it was the worse of the two: `app-shell`'s `ObjectView` builds the view-level
-`userActions` it hands down as an object literal of two spreads, so the left
-operand was `{}` at worst — never nullish. The `??` never fell through, and the
-object's CRUD predicates were never consumed at all on that path, whether or
-not an author wrote any toolbar policy.
-
-The harvest now reads the object block only. Both `userActions` read sites in
-`ListView.tsx` carry a comment naming the collision, and
-`__tests__/ListView.userActionsCollision.test.tsx` pins each clause of it: the
-two shapes, a producer that manufactures the view one, the harvest's blindness
-to it, and the projection that must keep the object's operand with a toolbar
-block — or an empty block — present on the view.
-
-Toolbar policy itself is untouched — it was never read through this path.
diff --git a/.changeset/list-view-nested-aria-label-i18n-5134.md b/.changeset/list-view-nested-aria-label-i18n-5134.md
deleted file mode 100644
index f330b77a6..000000000
--- a/.changeset/list-view-nested-aria-label-i18n-5134.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/plugin-list': patch
----
-
-`ListView` now resolves the nested `aria.ariaLabel` against the audience's locale
-instead of casting it to a string (objectui#5134).
-
-`@objectstack/spec`'s `AriaPropsSchema` types `ariaLabel` as `I18nLabel` — a plain
-string **or** an inline locale map (`{ en: 'Accounts', 'zh-CN': '客户' }`). The only
-read site in this repo spread it into the DOM as
-`{ 'aria-label': schema.aria.ariaLabel as string }`, and `as string` is a cast, not a
-conversion: a map-valued label reached the DOM as `aria-label="[object Object]"`, which
-a screen reader announces as the list view's accessible name — in every locale. The
-read now goes through the spec's own `resolveI18nLabel` (the resolver four other
-in-repo read sites already use) against `useDisplayLocale()`.
-
-Reachability, stated plainly: the path is **live but unexercised**. `I18nLabel` was a
-plain `string` through `@objectstack/spec` 17.0.0-rc.5, so no stored map-valued label
-predates rc.6, and no measured author writes one today — but map values are legitimate
-and arrive via API/import, so an imported list view carrying
-`aria: { ariaLabel: { en: …, 'zh-CN': … } }` is spec-valid metadata that renders a wrong
-accessible name. This is the map form working as declared, not a defect users are
-currently hitting.
-
-Behaviour on the string arm is byte-identical, including `''` (falsy before and after,
-so no attribute). One edge changes for the better: a map that matches no locale used to
-render `aria-label="[object Object]"` (`{}` is truthy) and now omits the attribute — an
-unnamed region beats a garbage-named one.
-
-The **flat** `schema.ariaLabel` is deliberately untouched: it carries a different
-vocabulary (objectui's keyed `{ key, defaultValue?, params? }` ref, resolved by
-`SchemaRenderer`'s `resolveKeyedI18nLabel`), and neither resolver accepts the other's
-shape.
diff --git a/.changeset/listview-comment-pair-4559.md b/.changeset/listview-comment-pair-4559.md
deleted file mode 100644
index 2f012823c..000000000
--- a/.changeset/listview-comment-pair-4559.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-'@object-ui/plugin-list': patch
----
-
-Two comment corrections in `ListView.tsx` (objectui#4559, objectui#4966). No runtime
-behaviour changes and the emitted bundle is byte-identical; the published `.d.ts` does
-change, which is why this is a `patch` rather than an empty frontmatter.
-
-**objectui#4559 — the sort rationale stopped prescribing a formula field.** The comment
-block above the `sortFields` memo still called a formula field "the supported
-alternative (… which sorts like any text column)". Since objectui#4294 the
-`list.sortRelationalHint` string in this same file says the opposite ("Not a formula
-field: it is virtual, so no column is stored for it and the server refuses to sort by
-one"), the memo underneath filters formula out via `UNMATERIALIZED_FIELD_TYPES`, and the
-server answers such a sort with `400 INVALID_SORT` (objectstack#6994). The parenthetical
-now names the remedy the hint, the server's refusal and the README already share — a
-stored field that denormalizes the name onto this object, written when the source
-changes. This was the last copy of the retired advice in the repo.
-
-**objectui#4966 — `formatActionLabel`'s docblock now sits above `formatActionLabel`.**
-It had drifted two declarations up, so the exported `parseSortConfig` carried two
-stacked leading comments and the helper carried none. This one was not cosmetic: because
-`parseSortConfig` is exported, `vite-plugin-dts` copied the misattributed block into
-`dist/ListView.d.ts`, so every consumer's editor hover and TypeDoc introduced the sort
-parser with a sentence about action labels. Moving the block removes it from the `.d.ts`;
-`formatActionLabel` is module-private, so its now-correct docblock does not appear there.
-It also matters to `scripts/check-spec-symbol-derivation.mjs`, whose rule 2 reads the
-comment block *attached* to a declaration — a misattributed docblock is the mechanism by
-which a claim gets scored against the wrong symbol. This block carries no spec-alignment
-phrase, so nothing fired today.
-
-No tests accompany this change and none could: both edits are comment-only, and there is
-no runtime behaviour to pin. The `.d.ts` delta was measured with the package's real
-`vite build` before and after, not asserted.
diff --git a/.changeset/lookup-dropdown-cell-renderer-5492.md b/.changeset/lookup-dropdown-cell-renderer-5492.md
deleted file mode 100644
index d43f0e19a..000000000
--- a/.changeset/lookup-dropdown-cell-renderer-5492.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-'@object-ui/fields': patch
----
-
-A lookup's inline dropdown renders its columns through the same cell renderer the browse-all picker uses, so one `lookup_columns` declaration cannot produce two answers.
-
-A form's lookup field offers two ways to pick a related record, and both read
-the same declaration: the inline dropdown under the field, and the
-"browse all records" picker behind it. The picker resolved every cell through
-the type-aware cell renderer. The dropdown did not — it printed
-`record[descriptionField]` verbatim into the option subtitle and concatenated
-`label: String(rawValue)` into the row's `title` attribute. Measured on the
-same declaration, on a real 17.1.0 deployment:
-
-```
-column inline dropdown (before) browse-all picker
-lookup T5MsMCuwP4t_yUHq (bare FK id) the related record's name
-date 2026-08-20T00:00:00.000Z (ISO) a formatted date
-select pending (enum code) the authored option label
-```
-
-Both surfaces now call one shared module — `widgets/lookupColumnDisplay.tsx`,
-which owns column normalisation, the field-descriptor enrichment from the
-referenced object's schema, and the render itself. The picker's own
-`renderCellContent` and `columnFieldDescriptors` are now thin calls into it, so
-there is a single renderer left to drift from. The dropdown's extra columns are
-rendered into the option row itself; the row's `title` keeps the full option
-label, which is what a truncated label needs, instead of a raw-value dump.
-
-No query changed and no contract widened. `lookupColumns` entries stay bare
-field names — no dot paths, no populate/expand semantics — because neither
-surface's request carries populate to begin with: the picker resolves a
-foreign-key id to a name client-side, in the lookup cell renderer, and the
-dropdown now inherits exactly that. An unresolved reference therefore renders
-what the picker renders for it, and keeps its column: a slot is dropped only
-when the record holds no value for the field, decided on the raw value and
-never on what the renderer makes of it, so an unresolved id can never degrade
-into a silently empty column.
diff --git a/.changeset/loud-lamps-shake.md b/.changeset/loud-lamps-shake.md
deleted file mode 100644
index eae0d1622..000000000
--- a/.changeset/loud-lamps-shake.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-"@object-ui/react": minor
-"@object-ui/plugin-detail": minor
-"@object-ui/plugin-grid": minor
-"@object-ui/plugin-form": minor
----
-
-`object-grid` / `object-form` / `detail-view` resolve their data source the same way, and a block that resolves none says so
-
-The three object-bound blocks disagreed about how the data-source adapter reached
-them. `object-grid` and `object-form` were registered through wrappers that read
-it from `SchemaRendererProvider` context; `detail-view` was registered as the raw
-component, which reads a React `dataSource` prop. `SchemaRenderer` itself reads
-only context, so the two wirings were mutually exclusive: measured with correct
-keys in every cell, provider wiring gave the grid `find` 1 and the detail view
-`findOne` 0, and prop wiring gave exactly the reverse. Neither reported anything.
-
-All three now resolve the adapter through one rule — an explicit `dataSource`
-prop first, the provider context second. This is additive: `detail-view` keeps
-its prop form (and direct `` callers are untouched),
-`object-form` gains a prop form it did not have, and `object-grid` no longer
-throws `useSchemaContext must be used within a SchemaRendererProvider` when a
-page has no provider.
-
-And the silence is over. A block in this family that resolves no adapter renders
-a **No data source resolved** panel naming the block, the object it was about to
-read, and the ancestor that injects the adapter — instead of a header-only grid,
-a field-less form card, or nothing at all. The check is opt-in per block, so a
-placement with inline rows, inline `customFields`, an inline record or an `api`
-endpoint is untouched.
-
-New from `@object-ui/react`: `useResolvedDataSource`, `NoDataSourcePanel`,
-`noDataSourceMessage`, and a `requiresDataSource` prop on `ElementDataSourceGate`.
diff --git a/.changeset/marketplace-catalog-runtime-before-admin-5557.md b/.changeset/marketplace-catalog-runtime-before-admin-5557.md
deleted file mode 100644
index 69a66da58..000000000
--- a/.changeset/marketplace-catalog-runtime-before-admin-5557.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-The marketplace **catalog** page now tells a non-admin that the runtime has no
-marketplace, instead of telling them they lack permission (objectui#5557).
-
-`MarketplacePage` ordered its two early returns admin-first, so on a runtime that
-mounts no marketplace at all (`features.marketplace: false` — an `OS_CLOUD_URL=off`
-deployment, the EE deploy template's factory default) an unprivileged member got
-"access denied" for a surface that exists for nobody. That answer sends them to
-ask an administrator for a grant that would not help them, and it left the
-informational disabled state built in objectui#5504 unreachable for every
-non-admin. The runtime check now answers first, because "this deployment has no
-marketplace" is true regardless of who is asking.
-
-This restores the sibling-page invariant for the one class of viewer it still
-failed for: `MarketplacePackagePage` was reordered the same way in objectui#5533,
-so on a marketplace-off runtime the catalog page and the package detail page now
-give a non-admin the same kind of answer.
-
-Scope, deliberately narrow:
-
-- **Admin-first ordering stays correct where a marketplace exists.** On a runtime
- with `features.marketplace: true`, a non-admin still gets `MarketplaceAccessDenied`
- — the catalog is an install surface, and a member who cannot install has nothing
- to do with it. That boundary is pinned by an explicit test, not left to prose:
- without it, a change that simply dropped the admin check would look correct.
-- Nothing an admin sees changes, on either kind of runtime.
-- No new i18n keys, and no change to `MarketplaceAccessDenied` or
- `MarketplaceDisabled` themselves — only which of the two the page reaches for,
- and in which order it decides.
-- The disabled state is still the server's own answer (`features.marketplace`),
- never inferred from a failed request, and it still fails open.
diff --git a/.changeset/marketplace-detail-admin-guard-order-5583.md b/.changeset/marketplace-detail-admin-guard-order-5583.md
deleted file mode 100644
index b50d52248..000000000
--- a/.changeset/marketplace-detail-admin-guard-order-5583.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-The marketplace package detail page decides "you are not an admin" before it fetches,
-instead of after the load has already failed.
-
-`MarketplacePackagePage` ordered its early returns with the `!isAdmin` guard *after*
-both the loading branch and the `error || !data` branch, and gated its two fetch
-effects on `features.marketplace` alone. On a runtime that mounts a marketplace, a
-non-admin who opened a package URL was therefore walked through the fetch and the
-skeleton, and — when the load failed — was handed the destructive "Failed to load
-package" card carrying the server's own error message. Whether that viewer was
-refused or handed a diagnosis about a surface they are not allowed to use came down
-to whether an unrelated request happened to succeed.
-
-The guard now sits ahead of both branches, and `getMarketplacePackage` and
-`getCloudInstallationInfo` are gated on `isAdmin` as well, so the page stops issuing
-requests on behalf of a viewer it has already decided to turn away. That is the
-discipline objectui#5533 established on this same page for `features.marketplace`,
-applied to the other predicate that decides the same thing. It is also the ordering
-`MarketplacePage` carries after objectui#5557, so the two sibling pages now answer one
-runtime the same way for every viewer. The server remains the authority on what a
-non-admin may fetch; this only stops the client doing work it would discard.
-
-Unchanged for an admin, deliberately and under test: a failing load still produces the
-destructive card with the server's message intact, and a successful one still renders
-the package. A "fix" that hoisted the refusal unconditionally, or that deleted the
-failure branch, would satisfy every non-admin assertion and fail those two.
-
-`loading` stays seeded from `marketplaceEnabled` alone rather than from
-`marketplaceEnabled && isAdmin`. `isAdmin` reads `activeMember`, which `AuthProvider`
-resolves asynchronously *after* the session settles, so an admin whose role comes from
-the org member row renders once as a non-admin before the flag flips. Seeding `false`
-there would leave that first admin render with `loading: false` and no data — the
-destructive card, painted for a frame before the effect could raise the flag again.
-`MarketplacePackagePage.guardOrder.test.tsx` pins the flip case for that reason, along
-with the ordering, the skipped requests, and the marketplace-off boundary the guard
-must not jump above.
diff --git a/.changeset/marketplace-detail-disabled-state-5533.md b/.changeset/marketplace-detail-disabled-state-5533.md
deleted file mode 100644
index 672d7f993..000000000
--- a/.changeset/marketplace-detail-disabled-state-5533.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-The marketplace **package detail** page now states that the marketplace is turned
-off, instead of red-erroring, on a runtime that has none (objectui#5533).
-
-`MarketplacePackagePage` takes the same `isMarketplaceEnabled()` early return its
-sibling catalog page took in objectui#5504, rendering the informational
-`MarketplaceDisabled` state when the server reports `features.marketplace: false`
-(an `OS_CLOUD_URL=off` deployment — the EE template's factory default). Until now
-the same runtime answered a bookmarked or pasted package URL with a destructive
-"Failed to load package / Not found." card, so the two sibling pages reached
-opposite conclusions about one runtime: the catalog called it configuration, the
-detail page called it a failure.
-
-Both requests the page fires for its own view are skipped in that state — the
-package fetch and the cloud-installation probe — rather than fired and discarded:
-a discarded request still reaches the server and can race the destructive card
-onto the screen before the disabled state settles.
-
-Scope, deliberately narrow:
-
-- **Not** a "swallow all errors" change. With `features.marketplace: true` the page
- behaves exactly as before, and a package that genuinely is not there still
- renders the destructive card carrying the server's own message. The flag is the
- runtime's own answer, never inferred from the shape of a failure, and it fails
- open — a runtime that answers nothing keeps its detail page.
-- The `installLocal` surfaces are untouched. That is a different capability flag,
- and an air-gapped `OS_CLOUD_URL=off` runtime still has a working install-local
- path.
-- No new i18n keys: `marketplace.disabled.*` and `marketplace.action.backHome`
- already ship in all ten locale packs.
diff --git a/.changeset/marketplace-disabled-state-5504.md b/.changeset/marketplace-disabled-state-5504.md
deleted file mode 100644
index 4462a0913..000000000
--- a/.changeset/marketplace-disabled-state-5504.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-'@object-ui/app-shell': minor
-'@object-ui/i18n': minor
----
-
-Marketplace-less runtimes now say so instead of erroring: `OS_CLOUD_URL=off` is a
-first-class disabled state, and the load-failure hint describes the control plane
-the runtime was actually pointed at (objectui#5504).
-
-`apps/objectos-ee/deploy/.env.example` ships `OS_CLOUD_URL=off` as its factory
-default, so a stock self-hosted stack has no marketplace at all. The Console still
-recommended one: Home led with "Start with a template" and "Browse App
-Marketplace", and the click landed on a red **Failed to load marketplace / Not
-found** card whose hint claimed this runtime "points at the public ObjectStack
-cloud by default" and advised setting `OS_CLOUD_URL`. Both claims were false for
-exactly the deployment reading them — the operator had not left the default, and
-the advice pointed back at the template that told them to set `off`. "Marketplace
-disabled by configuration" is a configuration conclusion, not a load failure.
-
-- `isMarketplaceEnabled()` (`runtime-config`) reads the server's own
- `features.marketplace`, which `RuntimeConfigPlugin` derives per request from the
- serving app's route table (objectstack#8356). It is never inferred from the shape
- of a failed request: a control plane that is merely DOWN leaves the flag `true`,
- so an outage still renders as an outage. Unknown fails OPEN.
-- The marketplace page renders an informational "App Marketplace is turned off"
- state — muted, not `destructive` — and issues no request it knows will 404.
-- Home's "Start with a template" cover greys out with a visible localized reason,
- and the "Browse App Marketplace" shortcut is withheld, exactly as they already
- are for the `manage_metadata` capability gate.
-- `marketplace.load.failedHint` is replaced by `failedHintConfigured` (naming the
- configured control plane) and `failedHintSameOrigin`. The "points at the public
- cloud by default" sentence is gone: it was rendered unconditionally, including on
- every runtime whose operator had overridden `OS_CLOUD_URL`.
-
-All ten locale packs carry the new keys.
diff --git a/.changeset/metadata-client-actor-retired-4834.md b/.changeset/metadata-client-actor-retired-4834.md
deleted file mode 100644
index 2aa8de93c..000000000
--- a/.changeset/metadata-client-actor-retired-4834.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-'@object-ui/data-objectstack': minor
----
-
-**Breaking (published surface):** remove `options.actor` from `MetadataClient`'s
-`save`, `reset`, `publish` and `rollback`, and stop emitting the `X-Actor`
-request header.
-
-The server stopped honouring that header. objectstack#7941 ruled that the
-recorded actor is the identity the request was authorized as, and removed the
-header limb from the `/meta` write resolver — attribution cannot drift from
-authorization. The option therefore typed cleanly, sent a header, and could not
-influence the audit or history row it appeared to address: a false affordance
-that promised attribution and silently failed to deliver it.
-
-Three declarations go: `MetadataClientSaveOptions.actor` (inherited by
-`MetadataDeleteOptions` via `extends`, so it served both `save` and `reset`),
-and the inline `{ actor?: string }` on each of `publish` and `rollback`.
-`MetadataAuditEntry.actor` is unaffected — that is the server's read-back of
-who acted, and it remains the way to see attribution.
-
-Marked `minor` rather than `major` per this repo's version-alignment policy
-(the fixed group's major tracks `@objectstack`, and `major` in a changeset
-would drag all 39 packages off that cadence).
-
-No caller in this repo passed `actor`; the census found the only in-repo
-occurrence was the client's own unit test. Callers outside this repo that still
-pass it are unaffected at runtime beyond losing a header the server already
-ignored — the property is dropped rather than forwarded, pinned by
-`metadata-actor-retired-4834.pin.test.ts`.
diff --git a/.changeset/metadata-lock-state-5024.md b/.changeset/metadata-lock-state-5024.md
deleted file mode 100644
index 785458cbf..000000000
--- a/.changeset/metadata-lock-state-5024.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-'@object-ui/data-objectstack': patch
-'@object-ui/app-shell': patch
----
-
-The metadata lock banner can no longer render an amber, padlocked box with no
-title, and the ADR-0010 §3.6 lock vocabulary is declared once instead of three
-times (objectui#5024).
-
-`MetadataLayered.lock` and `MetadataAuditEntry.lockState` each spelled the four
-states out by hand, 42 lines apart in one file, compared by no gate. They are now
-one exported `MetadataLockState` — derived from `GetMetaItemLayeredResponseSchema`'s
-`z.enum` in `@objectstack/spec`, which already owns this vocabulary, so the copies
-were restating a schema rather than filling a gap.
-
-The user-visible half is the banner. Its title was three independent `&&` branches
-with no fallback, while the switch that opens the banner is true for any non-`none`
-value — so a lock state outside the four opened the box and left the headline
-empty. That is reachable without a fifth state ever being added here:
-`MetadataClient.layered()` casts the wire value through unchecked, so a newer
-server reaches this banner as-is. Measured, not assumed — feeding `no-publish`
-through the page rendered the padlock, the border and an empty title. The title is
-now a keyed lookup with a loud fallback that names the unrecognised token, so a
-fifth state fails `type-check` here and, if one arrives from a server anyway, the
-operator reads a sentence instead of a blank box.
diff --git a/.changeset/nav-selection-root-keys-edge-5600.md b/.changeset/nav-selection-root-keys-edge-5600.md
deleted file mode 100644
index 5fd1bb1b3..000000000
--- a/.changeset/nav-selection-root-keys-edge-5600.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
----
-
-Internal layering fix, behaviour-unchanged: publishes nothing, declared explicitly
-with an empty frontmatter rather than left undeclared.
-
-Inverts the dependency edge objectui#5600 found: `nav-selection.ts` — otherwise pure
-URL-parsing plumbing (`parseSurfaceParam` / `formatSurfaceParam` / `parseNavSelParam`
-and friends) — statically imported `APP_NAV_ROOT_KEYS` from the 510-line
-`AppNavInspector.tsx`, so every consumer of the URL helpers (including two small
-utilities, `console/ai/artifactStudioPath.ts` and, transitively via
-`useSurfaceDeepLink.ts`, the studio-design surface) pulled a React inspector
-component into their static import graph for one array of root keys.
-
-`APP_NAV_ROOT_KEYS` (`['nav', 'navigation', 'tabs', 'items', 'menu']`) is a plain
-string-literal array — it describes nav SHAPE, not the inspector — so it now lives in
-`nav-selection.ts` instead. `AppNavInspector.tsx` never read the array itself, only
-re-exported it (`export const APP_NAV_ROOT_KEYS = ROOT_KEYS;` as its last line), so
-the fix is a straight move with no back-import needed on that side.
-
-Verified structurally rather than by reading the import line: a BFS over static
-(non-type, non-dynamic) relative imports from `nav-selection.ts` and the other
-non-test importers no longer reaches `AppNavInspector.tsx` (158 files visited, target
-not found), where the same walk over the pre-fix tree found it in 6 files via
-`nav-selection.ts -> AppNavInspector.tsx` directly.
diff --git a/.changeset/node-gate-data-predicate-report-5687.md b/.changeset/node-gate-data-predicate-report-5687.md
deleted file mode 100644
index 5465efc0e..000000000
--- a/.changeset/node-gate-data-predicate-report-5687.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-'@object-ui/react': patch
----
-
-`SchemaRenderer`'s node visibility gate now emits a dev-only warning when a
-predicate reads `data.*` and the data-source adapter cannot answer that read, so
-an author who wrote `properties: { visible: "data.status == 'draft'" }` sees the
-constant they authored instead of shipping it (objectui#5687).
-
-At the node tier `data` is the data-source **adapter** — the object `${data.total}`
-in a props bag resolves against — and it has never been the row. A predicate
-written with the deprecated `data.*` spelling therefore resolves `undefined ==
-'draft'`, which is a perfectly good `false`: the block is hidden on every row, and
-because it does not throw, the unresolvable-predicate reporter added for
-objectui#5454 never fired. Measured on this base, the same predicate written as a
-`{ dialect: 'cel' }` envelope *does* throw and *was* already reported — so whether
-an author heard about the identical mistake depended on which dialect they happened
-to write it in, which is the arbitrariness objectui#5454 existed to remove.
-
-**No verdict changes and no interpolation changes** (maintainer ruling,
-2026-08-22, option A). The node tier keeps its documented `data` = adapter
-semantics; objectui#5330's row binding does not extend here. The evaluator's answer
-is returned exactly as computed and `${data.*}` interpolation is untouched — only
-the silence moved.
-
-The report fires on a `data.*` read the bound adapter answers with `undefined`, not
-on the spelling. A genuine adapter read stays silent (`data.total > 0` against an
-adapter carrying `total`), a canonical `record.*` predicate stays silent, and a
-correctly-hiding gate stays silent. Dev builds only, `console.warn`, deduped per
-node type + key + predicate source — the same module, Set and lifecycle as the
-objectui#5454 reporter.
-
-This loudness is temporary by design: it dissolves when objectui#5330's deprecation
-window for the `data.*` row spelling closes.
diff --git a/.changeset/objectchart-fieldoptionlabel-ref-5587.md b/.changeset/objectchart-fieldoptionlabel-ref-5587.md
deleted file mode 100644
index 4b61e3f15..000000000
--- a/.changeset/objectchart-fieldoptionlabel-ref-5587.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-'@object-ui/plugin-charts': patch
----
-
-`ObjectChart` now depends on the `fieldOptionLabel` resolver directly instead of
-holding it behind a ref, so a chart re-resolves its groupBy option labels when
-the resolver genuinely changes (objectui#5587).
-
-The ref existed for a reason that no longer holds. `useSafeFieldLabel()` returned
-a fresh object on every render outside an i18next provider, so a direct
-dependency made `fetchData`'s `useCallback` identity fresh on every render, and
-the effect that depends on `fetchData` refetched on every render — an unbounded
-loop. `ObjectChart` worked around that locally with `fieldOptionLabelRef` plus a
-`useEffect` keeping it current. `useObjectLabel`'s memo now holds with or without
-an i18next instance bound (objectui#5564), so the resolver's identity is stable
-on both paths and the indirection buys nothing.
-
-It did cost something, and that is the user-visible half: a ref-hidden dependency
-meant `fetchData` did NOT re-run when the resolver changed. A chart mounted
-before its `I18nProvider`, or rendered across a language switch, kept serving
-groupBy labels resolved by the old resolver until some unrelated dependency
-(object name, filter, aggregate) happened to move. It now refetches once on that
-transition and shows labels in the active language.
-
-Pinned by `ObjectChart.fieldOptionLabelRefetch.test.tsx`, which counts fetches
-across forced re-renders both outside and inside a provider. Reverting
-`useObjectLabel.ts` to its pre-objectui#5564 state turns the no-provider case red
-(2 fetches instead of 1, alongside React's "Maximum update depth exceeded"), so
-the removal is pinned to the fix that unlocked it rather than to a comment.
diff --git a/.changeset/objectmap-schema-data-shorthand-5305.md b/.changeset/objectmap-schema-data-shorthand-5305.md
deleted file mode 100644
index c21345fce..000000000
--- a/.changeset/objectmap-schema-data-shorthand-5305.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-'@object-ui/plugin-map': patch
----
-
-`ObjectMap` reads `schema.data` in one place again, so an array-shorthand map stops
-making a metadata request it never uses.
-
-The fetch effect carried a second short-circuit beside the `props.data` one
-objectui#5003 fixed: it read `schema.data` directly and tested whether that value was
-itself an array. eslint reported it as `missing dependency: schema.data` — the last
-`react-hooks/exhaustive-deps` warning on that effect.
-
-The dependency was never actually missing. `getDataConfig(schema)` already returns
-`schema.data` verbatim, and the result is memoized on `JSON.stringify(rawDataConfig)`
-into `dataConfig`, which **is** one of the effect's declared dependencies. The authored
-rows therefore reached the effect before this change; the direct read was a duplicate of
-an already-threaded value, which is why neither adding a dependency nor deleting the
-branch was right.
-
-The array handling moved into `getDataConfig`, where `ObjectGrid`'s own `getDataConfig`
-already pins the same normalization (`"Check if data is an array (shorthand format)"`).
-Same rows render, and the effect now reads only `dataConfig`.
-
-One behavioural consequence, and it is the point: an array under `data` now yields
-`provider: 'value'`, so `hasInlineData` is true and the sibling effect no longer calls
-`dataSource.getObjectSchema()` for it. That request's only read site is
-`buildExpandFields()` inside the object-provider fetch branch, which an inline schema
-never reaches — so the call was pure waste, and the shorthand now behaves exactly like
-the declared `{ provider: 'value', items }` form it is shorthand for.
-
-Deleting the branch instead was measured, not assumed: with no producer-side handling,
-an array-shorthand map renders `Error: DataSource required for object/api providers`
-rather than its markers. The shorthand is a live convention in six sibling blocks
-(`ObjectGrid`, `ListView`, `ObjectTree`, `ObjectChart`, `ObjectDataTable`,
-`calendar-view-renderer`), so `object-map` would have become the one block in the family
-that answers it with an error box.
diff --git a/.changeset/objectpivottable-i18n-resolver-refs-5625.md b/.changeset/objectpivottable-i18n-resolver-refs-5625.md
deleted file mode 100644
index b5f4cc630..000000000
--- a/.changeset/objectpivottable-i18n-resolver-refs-5625.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-`ObjectPivotTable` now depends on the `fieldLabel` / `fieldOptionLabel`
-resolvers directly instead of holding them behind refs, so a pivot re-derives
-its header and option labels when the resolver genuinely changes
-(objectui#5625).
-
-The refs existed for a reason that no longer holds. `useSafeFieldLabel()`
-returned a fresh object on every render outside an i18next provider, so a direct
-dependency would have re-run the metadata-derivation effect on every render —
-and that effect ends in `setFieldLabelMaps` / `setFieldNameLabels` with freshly
-built objects, so each run scheduled the next one: an unbounded derive loop.
-`ObjectPivotTable` worked around that locally with `fieldLabelRef` /
-`fieldOptionLabelRef` plus a `useEffect` keeping them current.
-`useObjectLabel`'s memo now holds with or without an i18next instance bound
-(objectui#5564), so both resolvers have a stable identity on both paths and the
-indirection buys nothing.
-
-It did cost something, and that is the user-visible half: a ref-hidden
-dependency meant the derivation did NOT re-run when the resolver changed. A
-pivot mounted before its `I18nProvider`, or rendered across a language switch,
-kept showing its top-left header label and its select-option cell labels as
-resolved by the old resolver — until some unrelated dependency (the data source,
-the object name) happened to move. It now re-derives once on that transition and
-renders in the active language.
-
-This is the same removal objectui#5587 made in `ObjectChart`, one package over.
-
-Pinned by `ObjectPivotTable.i18nResolverDeps.test.tsx`, which counts derivations
-across forced re-renders both outside and inside a provider, checks that the two
-derived state maps settle, and asserts the language-switch re-derivation.
-Reverting `useObjectLabel.ts` to its pre-objectui#5585 state turns the
-no-provider case red (derivations in the dozens instead of 1), so the removal is
-pinned to the fix that unlocked it rather than to a comment.
diff --git a/.changeset/objectview-overlay-pair-completeness-5025.md b/.changeset/objectview-overlay-pair-completeness-5025.md
deleted file mode 100644
index 85273d19a..000000000
--- a/.changeset/objectview-overlay-pair-completeness-5025.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`sanitizeViewOverride` now strips a half-filled `between` from a stored view
-overlay, instead of handing it back to the merge (objectui#5025,
-objectstack#8815).
-
-The overlay recovery pass asked "is this filter row filled in?" with the
-shape-blind predicate objectstack#8815 retired — `value == null || value === ''
-|| (Array.isArray(value) && value.length === 0)`. That is correct for `scalar`
-and `list` and blind to `pair`: a `between` carrying one bound is
-`['2024-01-01', '']`, an array of length 2, so the pass read it as a real
-condition and kept it. The two write paths (`plugin-list`'s `ListView` and
-app-shell's `viewFilterFold`) were converted to the builder's arity-aware
-`isFilterValueComplete`; this read path was the third verbatim copy, and the one
-whose job was to clean up exactly the rows the other two used to write. Until
-now a stored half-range survived the pass, reached the query, and the server
-refused the whole view (`400 INVALID_FILTER`) for every user on every later
-read.
-
-The pass now delegates to the same `isFilterValueComplete` the write paths use,
-on both of the at-rest shapes it handles (the spec `ViewFilterRule` object and
-the legacy runtime triple). A complete range — bounds of `0` and `false`
-included — is untouched, and the `scalar` and `list` families read exactly as
-before, since the retired predicate was already right for them. One shape is
-newly stripped beyond the half-filled array: a `between` whose value is a bare
-scalar, which `ViewFilterRuleSchema` itself refuses.
-
-Which operators want no value at all is unchanged and still answered by
-app-shell's `VALUELESS_FILTER_OPERATORS` — that set is already derived from the
-builder's `VALUELESS_FILTER_BUILDER_OPERATORS` rather than being a private copy,
-and its parity is pinned.
diff --git a/.changeset/objectview-sort-sink-4869.md b/.changeset/objectview-sort-sink-4869.md
deleted file mode 100644
index 9408f9581..000000000
--- a/.changeset/objectview-sort-sink-4869.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-'@object-ui/plugin-view': patch
----
-
-**Bug fix:** a non-grid `object-view` (calendar / kanban / gallery / timeline)
-whose sort came from the deprecated `table.defaultSort` no longer comes back
-empty. `ObjectGridSchema.defaultSort` is declared a single `{ field, order }`
-object, and `ObjectView`'s own fetch handed it to `$orderby` verbatim — where
-the ObjectStack adapter reads it as an `$orderby` map and folds it with
-`Object.entries`, so the request went out sorting by two columns literally
-named `field` and `order`. The server answers `400 INVALID_SORT`, `ObjectView`
-swallows the error, and the view rendered with no records — while the *same*
-metadata sorted correctly as a grid, because `ObjectGrid` lowers that pair
-before using it (objectui#4869, maintainer ruling 2026-08-22).
-
-`ObjectView` now performs the same legacy-to-canonical lowering `ObjectGrid`
-already does (`sort ?? (defaultSort ? [defaultSort] : undefined)`) and routes
-the whole chain — named view sort, `views` prop sort, `table.sort`,
-`table.defaultSort` — through the shared `convertSortToQueryParams` sink. This
-was the last object-bound read site sending an authored sort to `$orderby`
-unlowered; every other block (gantt, map, calendar, timeline,
-`record:line_items`) already did, so an adapter that implements `find` itself
-now receives one normalized `Record` from all of them instead
-of two different dialects.
-
-Precedence is unchanged, and both spellings of the pair keep working. The
-shared sink was deliberately **not** widened to accept a bare `{ field, order }`:
-its input slot also legitimately carries `$orderby`'s own map, in which
-`{ field: 'desc' }` is a valid ordering by a column named `field`, so widening
-it would make one shared function guess.
-
-Two visible shape changes on the wire, both semantically identical to before: a
-string `table.sort` such as `'name desc'` now serializes as `-name` rather than
-riding through as `name desc`, and a `SortConfig[]` arrives as a map rather than
-as an array.
diff --git a/.changeset/olive-donkeys-shave.md b/.changeset/olive-donkeys-shave.md
deleted file mode 100644
index 6e765665f..000000000
--- a/.changeset/olive-donkeys-shave.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-'@object-ui/plugin-dashboard': minor
----
-
-Dashboard flat `table` widget: render the server's grand total as a `tfoot` row (objectui#5846).
-
-The flat branch now requests the `[]` marginal grouping — the same server-side
-totals machinery the cross-tab has used since framework#1753 — and renders the
-answer under the console's existing `dashboard.total` label. Totals are computed
-by the server with each measure's TRUE aggregate (a `sum` sums, an `avg`/`min`/
-`max` reports the whole-set aggregate); nothing is recombined client-side. The
-row lives in `tfoot`, so it is exempt from sorting, renders below the `—` null
-bucket, and never joins the drillable rows.
-
-No footer is rendered when the executor answers no `[]` grouping, and none is
-requested for a table truncated by `options.limit`, whose visible rows a
-whole-set total could not be reconciled against.
diff --git a/.changeset/olive-donkeys-smile.md b/.changeset/olive-donkeys-smile.md
deleted file mode 100644
index e643a315e..000000000
--- a/.changeset/olive-donkeys-smile.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-"@object-ui/plugin-charts": minor
----
-
-Dashboard chart widgets no longer render as a blank area when their height class
-resolves to `auto`.
-
-`ChartContainer`'s min-size fallback was applied only to the wrapper `div`.
-Recharts measures its own `width:100%;height:100%` size-detector element, and a
-percentage height never resolves against an ancestor's `min-height`, so the
-wrapper obediently grew to 280px while the measured element stayed at 0 — and
-Recharts renders no children at all for a non-positive box. The result was a
-widget card with its title over an empty chart area: no marks, no refusal, no
-empty state, and permanent, because a box that never changes fires no resize.
-The floor is now applied to the measured element as well, under the same
-condition, so an author's explicit height still wins.
diff --git a/.changeset/olive-pugs-sing.md b/.changeset/olive-pugs-sing.md
deleted file mode 100644
index 0ebe36886..000000000
--- a/.changeset/olive-pugs-sing.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-Dashboard record fields: percent columns now render through the one percent
-scaling decision instead of a second, drifted copy of it.
-
-`renderFieldValue`'s `%`-format branch normalised the value itself before
-calling `formatPercent` (`const normalized = value > 1 ? value / 100 : value`,
-then `normalized * 100`). `formatPercent` already applies `percentDisplayValue`,
-which `@object-ui/core` documents as the single source of truth for percent
-display scaling, so the branch was re-deciding what core owns — and its copy had
-drifted from it in three measured ways:
-
-- `(value / 100) * 100` is not value-preserving in binary floating point,
- re-introducing one call frame upstream the round trip that was removed from
- inside `formatPercent`. On the 0.001-step grid to 200, 19,978 of 199,000
- values change bit pattern and 1,108 rendered strings move, every one a
- last-digit off-by-one: a stored `1.605` rendered `1.60%` where half-up is
- `1.61%`.
-- A stored fraction below `0.01` was scaled twice — the local `* 100` put it
- back under 1, so core's fraction arm scaled it again. `0.005` (0.5%) rendered
- `50.00%`.
-- The local test was `value > 1` rather than core's symmetric `|value| < 1`, so
- a negative already in percentage points took the fraction arm: `-5` rendered
- `-500.00%`.
-
-The branch now hands the raw stored value to `formatPercent` — the identical
-call the list-view percent cell already makes for an ordinary percent column —
-so a percent reads the same as a record field, as a grid cell and as a dashboard
-measure. Output moves where it was wrong: values at or above 1 whose round trip
-lost a digit, fractions below `0.01`, negatives at or below `-1`, and exactly
-`1`, which is one percentage point by core's convention and now renders
-`1.00%` at two decimals, where the local `value > 1` test had made it
-`100.00%`.
diff --git a/.changeset/one-origin-dev-configs-5745.md b/.changeset/one-origin-dev-configs-5745.md
deleted file mode 100644
index a9ed98970..000000000
--- a/.changeset/one-origin-dev-configs-5745.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
----
-
-Dev/build configuration only — this publishes nothing, declared explicitly with an
-empty frontmatter rather than left undeclared. No package `src/` is touched. The
-files changed are `apps/console/.env.development`, `examples/console-starter/.env.*`
-and `examples/console-starter/vite.config.ts`; `.env.development` is not part of
-`@object-ui/console`'s published artifact (`files: ["dist"]`, built from
-`.env.production`, which is untouched), and the example is private and on the
-changeset `ignore` list.
-
-Converge the dev stacks on one origin: empty `VITE_SERVER_URL` and let the Vite
-proxy do the split-host hop.
-
-Both dev env files pointed `VITE_SERVER_URL` at `http://localhost:3000` while the
-page was served from `:5180` (console) and `:5173` (starter). Every client in these
-apps coalesces an unset value to `''` and then builds a relative `/api/...` URL, so
-an empty value routes same-origin through the dev proxy instead. `console-starter`
-had no `server` block at all, so it gets the same one-stanza `/api` proxy
-(`DEV_PROXY_TARGET` or `http://localhost:3000`) that `apps/console` already carries;
-no port is pinned, because its README documents the example on Vite's default 5173.
-
-This is the ruled prerequisite for the `sameOriginOnly` action-runtime default. Under
-that default a non-empty `VITE_SERVER_URL` makes every relative-target `type: 'api'`
-action resolve cross-origin and be fetched bare — no `Authorization`, no
-`X-Tenant-ID`, no `Accept-Language` — i.e. a 401 for the standard `pnpm dev` stack.
-
-`examples/console-starter/.env.production` is emptied too. Its committed
-`https://demo.objectstack.ai` was not a deliberate choice for the example: it is the
-same pre-convergence value the sibling console carried until 2026-05-24, when
-`c351c9604` ("default published SPA to same-origin") cleared it there for CORS-blank
--page reasons and touched only `apps/console/.env.production`. The starter was left
-behind by that commit, and as a fork-ready scaffold its committed value is the
-production origin every fork inherits. Split-origin deployments inject
-`VITE_SERVER_URL` at build time, the way the console's own `.env.production` already
-documents.
diff --git a/.changeset/owd-width-single-home-5477.md b/.changeset/owd-width-single-home-5477.md
deleted file mode 100644
index 10aa33c86..000000000
--- a/.changeset/owd-width-single-home-5477.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`ObjectSettingsPanel` now calls `isExternalWider` from `owd-sharing.ts` instead of
-re-declaring `OWD_WIDTH` and the ADR-0090 D11 width comparison inline (objectui#5477).
-
-`owd-sharing.ts` states its own purpose — it is "the single home" for the pieces the
-per-object Settings tab and the package-level OWD overview must agree on, the D11
-"external ≤ internal" comparison among them — and the Settings tab was the one surface
-not calling it. The two implementations were verified equivalent before the swap, over
-the full domain of both dials plus `undefined`, the rejected legacy aliases and
-prototype-chain keys: 144 input pairs, zero disagreements. The module's extra `!!`
-truthiness guards are redundant at this call site, which already normalizes both dials
-to `''`, and `'' in OWD_WIDTH` is false regardless. So no author-visible verdict
-changes — the same three pairs warn, and the same twenty-two stay calm.
-
-The substantive half is the pin that keeps it that way. `ObjectSettingsPanel.owdAgreement.test.tsx`
-drives BOTH surfaces over the full 5×5 cross-product of the values their dials offer and
-requires all three legs — the Settings tab's rendered warning, the overview's per-row
-error, and `isExternalWider` itself — to agree on every pair, with the violating pairs
-pinned by name so the sweep cannot pass vacuously. Re-inlining a drifted copy into either
-surface, swapping the `(internal, external)` argument order, or refining D11 in only one
-place now fails a test instead of silently leaving the authoring surface enforcing the
-old rule.
diff --git a/.changeset/page-source-tailwind-framing-5461.md b/.changeset/page-source-tailwind-framing-5461.md
deleted file mode 100644
index 476267511..000000000
--- a/.changeset/page-source-tailwind-framing-5461.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-'@object-ui/types': patch
----
-
-`PageSchema.kind`'s TSDoc names the real per-tier styling primitive for source-authored pages instead of the "HTML + Tailwind" framing ADR-0080's own amendment retracted.
-
-This is a published type surface: the TSDoc ships in `@object-ui/types`'s built
-`.d.ts` and is what an author reads on hover over `kind`. It said a `kind:'html'`
-page is "constrained JSX/HTML + Tailwind" — and it links
-`content/docs/guide/react-pages.md`, which objectui#5413 has already corrected to
-say the opposite. Shipped type documentation was contradicting the guide it points
-readers to.
-
-ADR-0080's header amendment (2026-06-30, under ADR-0065, Accepted) supersedes that
-framing on styling: a page's `source` is *runtime metadata*, the console's Tailwind
-is compiled at build time by scanning the console's own `src`, and there is no
-safelist — so an authored utility class produces CSS only by coincidence, when
-objectui already ships that exact class, and otherwise produces nothing with no
-error anywhere. That is the ADR-0065 failure mode verbatim ("works only by
-coincidence"), and it is how a modal's `bg-black/50` backdrop reached production
-fully transparent.
-
-The tiers themselves are unchanged, and every load-bearing claim in the TSDoc
-survives verbatim — parse-never-execute and untrusted-author safety for `html`,
-the deprecated `'jsx'` alias, EVALUATED-in-the-main-tree with no sandbox behind the
-`react-pages` host capability for `react`, the ADR-0080 citation and the guide
-link. Only the styling conclusion changes, to the primitive each tier actually has:
-
-| `kind` | Style with |
-|---|---|
-| `"html"` | The blocks' own structured props (`` `` ``, `` `` ``) plus a JSON `style` object. |
-| `"react"` | Inline `style` objects. |
-
-Colors on both tiers come from the theme as `hsl(var(--token))`, so a page follows
-light/dark and whatever theme the deployment installs. The TSDoc now also names the
-rule that reports a violation — `page-source-className-tailwind`, shipped in
-`@objectstack/lint@11.5.0` as `validatePageSourceStyling` and reported by
-`os validate` as a warning on both tiers.
-
-No behaviour change, and the accepted `kind` set is untouched.
-
-`packages/components/src/renderers/layout/react-page.tsx` carries the same
-correction on its two source comments (the injected-scope note and
-`buildComponentScope`), and gains the styling note the file was missing. Those are
-internal comments — they do not project into any `.d.ts` and change no export — so
-they get no entry of their own; there is nothing an `@object-ui/components`
-consumer could read in a CHANGELOG and act on.
diff --git a/.changeset/page-source-tailwind-prose-retraction-5469.md b/.changeset/page-source-tailwind-prose-retraction-5469.md
deleted file mode 100644
index f125f3894..000000000
--- a/.changeset/page-source-tailwind-prose-retraction-5469.md
+++ /dev/null
@@ -1,51 +0,0 @@
----
-'@object-ui/react-runtime': patch
-'@object-ui/sdui-parser': patch
-'@object-ui/components': patch
----
-
-Documentation no longer teaches the "JSX/HTML + Tailwind" framing for a page's
-`source`, which ADR-0080's own 2026-06-30 header amendment (under ADR-0065,
-Accepted) retracted. objectui#5461 corrected three sites; a multiline census
-found eight more, in three spellings a line-oriented grep could not see.
-
-A page's `source` is *runtime metadata*. The console's Tailwind is compiled at
-build time by scanning the console's own `src`, and there is no safelist, so it
-never sees your page: an authored utility class produces CSS only by coincidence
-(when objectui already ships that exact class) and otherwise produces nothing,
-with no error anywhere. That is the ADR-0065 "works only by coincidence" failure
-mode, and it is how a modal's `bg-black/50` backdrop reached production fully
-transparent. `os validate` reports it as `page-source-className-tailwind`, a
-warning on kinds `html`, `react` and `jsx`, shipped in `@objectstack/lint@11.5.0`.
-
-The tiers themselves are unchanged and every load-bearing claim survives —
-parse-never-execute, the untrusted-author safety argument for `html`, and the
-deprecated `'jsx'` alias. Only the styling primitive is corrected, to the wording
-`content/docs/guide/react-pages.md` §Styling already uses:
-
-| `kind` | Style with |
-|---|---|
-| `"html"` | The blocks' own structured props (`` `` ``, `` `` ``) plus a JSON `style` object. |
-| `"react"` | Inline `style` objects. |
-
-Colors on both tiers come from the theme as `hsl(var(--token))`.
-
-Why each package has an entry — each was measured against its built artefact, not
-assumed:
-
-- **`@object-ui/react-runtime`**: `README.md` is published to npm (npm includes
- `README.md` in the tarball regardless of `files`). Its "no sandbox" callout is
- the paragraph that routes untrusted-author work to the `html` tier, and it
- carried the retracted framing line-wrapped across `:17-18`. It also gains the
- §Styling section it was missing — the absence is why the framing survived here.
-- **`@object-ui/sdui-parser`**: the corrected header of `src/types.ts` projects
- verbatim into the published `dist/types.d.ts`.
-- **`@object-ui/components`**: the corrected header of
- `src/renderers/basic/html-elements.tsx` projects verbatim into the published
- `dist/renderers/basic/html-elements.d.ts`. The `kind === 'html'` dispatch-arm
- comment in `src/renderers/layout/page.tsx` does **not** project (it is inside a
- function body) and is included here only because the same package already owes
- an entry.
-
-No behaviour change: this is prose only. `CHANGELOG.md` occurrences are
-deliberately untouched — immutable release history.
diff --git a/.changeset/palette-canonical-discussion-5495.md b/.changeset/palette-canonical-discussion-5495.md
deleted file mode 100644
index 18563f664..000000000
--- a/.changeset/palette-canonical-discussion-5495.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-'@object-ui/app-shell': minor
----
-
-**The page-editor palette now offers the canonical `record:discussion` instead of the `record:chatter` alias.**
-
-The two spellings are one renderer under two names — `@object-ui/plugin-detail` registers both against `RecordChatterRenderer` — and the palette deliberately carries one entry per renderer. It carried the wrong one. `record:discussion` is the canonical spelling: `@object-ui/core`'s `public-blocks.ts` records `record:chatter` as that same block "under a Salesforce-familiar name, kept for schemas", app-shell's own page synthesiser (`buildDefaultPageSchema`) has emitted the canonical name all along, and the console's AI block vocabulary already leaves the alias uncurated for exactly this reason. The palette was the last surface still pointing at the alias, and it did so only because the `@objectstack/spec` build pinned when the entry was written had no canonical member to offer; the currently pinned build declares both. The author-facing label moves **"Chatter feed" → "Discussion"**. Maintainer ruling 2026-08-22 on objectui#5495.
-
-**The alias keeps rendering — this changes what Studio advertises, not what works.** `record:chatter` moves from `BLOCK_TYPE_META` into `PALETTE_EXCLUSIONS`, which is the palette's ledger of "renders, but is not offered". Every page schema in the wild that spells the block the Salesforce way renders exactly as before, and both halves are now pinned by a test (`palette-discussion-alias.test.tsx`) that asserts the registry resolves the alias to the *same renderer function* as the canonical name and that an alias schema still produces the discussion panel — so "no longer offered" cannot quietly drift into "no longer rendered".
-
-One knock-on worth knowing when you open an existing page: the block-editor canvas draws a node's icon and colour tone from the palette entry, so an existing `record:chatter` node now shows the generic unknown-block icon in the outline instead of the message icon. This is the pre-existing behaviour for any renderable-but-unoffered type — until this change it was the *canonical* `record:discussion` that read this way, including in every page the platform generates itself, so the affected spelling is now the rarer one.
diff --git a/.changeset/param-dialog-title-fallback-4282.md b/.changeset/param-dialog-title-fallback-4282.md
deleted file mode 100644
index 41c71b681..000000000
--- a/.changeset/param-dialog-title-fallback-4282.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-The console's param-collection dialog now titles itself from `action.label` alone —
-the unreachable `|| action?.title` fallback beside it is removed (objectui#4282).
-
-`title` is declared on no action surface in the ecosystem: it is absent from
-`@objectstack/spec`'s `ActionSchema` (44 keys walked at spec 17.0.0), from
-`@object-ui/core`'s `ActionDef` and its pinned `ACTION_DEF_KEYS` / `SPEC_ACTION_KEYS`
-inventories, and from `@object-ui/types`' renderer view (`ui-action.ts`) and `crud.ts`
-`ActionSchema` / `BaseSchema`. None of the four action renderers — `action:button`,
-`action:icon`, `action:group`, `action:menu` — forwards it either. So the right-hand
-side of that `||` could not be reached by authored metadata: a fallback that cannot
-fire, which is the "declared is not enforced" shape objectstack#4075 exists to reduce.
-Nothing a user hits changes; the line now reads exactly one key, matching the
-`description` line directly below it.
-
-`useConsoleActionRuntime.paramDialogTitle.test.tsx` pins the reader so the alias cannot
-be reinstated silently: an action carrying `title` and no `label` must open an untitled
-dialog rather than a dialog named by a key no producer sets.
diff --git a/.changeset/paramtofield-reference-rule-derives-from-core-5312.md b/.changeset/paramtofield-reference-rule-derives-from-core-5312.md
deleted file mode 100644
index a3aa770c3..000000000
--- a/.changeset/paramtofield-reference-rule-derives-from-core-5312.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`ActionParamDialog`'s "this param carries a reference target" rule now derives from the shared reference-field family instead of the last private copy of it.
-
-`packages/app-shell/src/utils/paramToField.ts` restated the rule inline as
-`LOOKUP_WIDGET_TYPES.has(type) || type === 'user'` — the fourth and last
-hand-maintained answer to one question ("does this widget resolve a foreign key,
-so hand it `reference_to` / `display_field` / the rest of the picker config").
-The other three converged on `@object-ui/core`'s `EXPANDABLE_FIELD_TYPES` in
-objectui#4770 / #4790 / #4815; this face is now the fourth.
-
-No reachable behaviour change. The shared set is one member wider (`tree`), and
-that member can never be a widget key on this surface: it is absent from
-`fields`' widget map and `mapFieldTypeToFormType` sends it to `field:lookup`, so
-every key the rule tests arrives as `lookup`. Both halves are pinned, so
-registering a real `tree` widget surfaces the change instead of shipping it
-silently.
-
-The module's second rule — which widget keys degrade to a text input for want of
-a declared `referenceTo` — is a different set over overlapping types (`user`
-defaults its target to `sys_user` and must never degrade) and was deliberately
-left un-merged, matching the same split the plugin-grid twin keeps.
-
-Also retires a comment that claimed the disjunction "moves in lockstep with
-plugin-grid's `bulkParamToField` twin — the two param faces are never split".
-Measured on the tip before this change, that was false in both senses: the twin
-had read core's Set since objectui#4815 while this line read a private literal,
-so the two shared nothing and no gate could report a split; and the two member
-sets already differed, by `tree`. Lockstep now holds mechanically — the pin is
-on object identity (a spy on core's `has`), so a member-identical private copy
-fails where a value check would pass.
diff --git a/.changeset/pending-drafts-bar-locale-parity-5705.md b/.changeset/pending-drafts-bar-locale-parity-5705.md
deleted file mode 100644
index 6bafb8435..000000000
--- a/.changeset/pending-drafts-bar-locale-parity-5705.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-'@object-ui/i18n': patch
----
-
-`console.ai.pendingDrafts` — the standing unpublished-changes bar's five strings —
-now exists in all ten locale packs. It previously existed only in `en` and `zh`, so
-`ar`, `ru`, `pt`, `es`, `fr`, `de`, `ko` and `ja` rendered the English defaults and
-`all-locales-key-parity` failed on `main` (objectui#5705).
-
-The feature landed `en`-only in objectui#5696; the follow-up in objectui#5697 was
-titled for the locale packs but reached only `zh`, so eight packs × five keys stayed
-missing and the parity assertion — which carries no allowlist — was red on `main` and
-on every PR whose diff touched source. Source-free diffs skip the shard that runs it,
-which is why the breakage survived several merges.
-
-Each pack keeps its own conventions rather than `en`'s: the eight all quote with `"`,
-`ru` puts the number last (`…: {{count}}`) as it already does for the sibling
-`home.pendingDrafts` counts, and `ja` uses the full-width `:` before `{{detail}}`
-because that value is a runtime message rather than a single token — both choices
-carry an in-pack note. Terminology is taken from each pack's existing publish-bar
-vocabulary (`home.pendingDrafts`, `console.ai.seedWarn`) so the two banners read
-alike.
-
-Both interpolations survive verbatim in every pack — `{{count}}` in `count` and
-`{{detail}}` in `publishedWithFindings` — asserted mechanically against the evaluated
-packs, not by eye. The unrelated `home.pendingDrafts` block (`message` / `cta`) is a
-different node and is untouched.
diff --git a/.changeset/pending-drafts-i18n-keys.md b/.changeset/pending-drafts-i18n-keys.md
deleted file mode 100644
index 84dab23b4..000000000
--- a/.changeset/pending-drafts-i18n-keys.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@object-ui/i18n': patch
-'@object-ui/app-shell': patch
----
-
-Follow-up to #5696: the pending-drafts bar's strings live at `console.ai.pendingDrafts.*` with en+zh locale entries — the i18n call-site key gate and ratchet flagged the original root-level keys that existed nowhere.
diff --git a/.changeset/per-user-active-org-and-session-user-purge-5664.md b/.changeset/per-user-active-org-and-session-user-purge-5664.md
deleted file mode 100644
index 45bdd142c..000000000
--- a/.changeset/per-user-active-org-and-session-user-purge-5664.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-'@object-ui/auth': patch
-'@object-ui/console': patch
----
-
-The active organization id is now stored per user, and a change of session user drops the
-previous user's client state wholesale (objectui#5664).
-
-`auth-active-organization-id` was a single un-namespaced `localStorage` key while its
-siblings were already user-scoped (`objectui-recent-items:u:`, `objectui-favorites:u:`,
-`flow-palette-recents:u:`). On a browser handed from one account to another — a shared
-machine, a kiosk, a handover, a support session — the arriving user's console read the
-PREVIOUS user's organization id. The header workspace chip rendered the previous user's
-workspace for a user whose `organization/list` was empty, and the consequence past the
-cosmetics is the one worth stating: the polluted org context suppressed
-`RequireOrganization`'s routing into the guided "Create your workspace" first-run flow, so
-a brand-new user on that browser silently never got the new-user flow at all.
-
-Nothing about row visibility rode on this. With the stale id the server answers
-`403 USER_IS_NOT_A_MEMBER` on `get-full-organization` and `set-active`, and lists zero
-environments; the damage was entirely in what the client believed about itself.
-
-Three changes, and the third is the one that closes the class rather than the instance:
-
-- The key is per-user (`auth-active-organization-id:u:$userId`), matching the convention
- its siblings already use.
-- It can no longer be written un-namespaced at all. Where no session user is known yet the
- value lives in memory for that page-load only — a namespacing that kept a bare-key
- fallback would re-open the defect the first time a write happened before the user id
- resolved.
-- **A change of session user drops the previous user's client state wholesale.** This is an
- allowlist sweep of both `localStorage` and `sessionStorage`, not a list of known keys, so
- the NEXT storage key someone adds without a `:u:` scope is covered before it is written.
- Only device-scoped entries survive: the arriving session's own bearer token, the pointer
- recording whose state the browser holds, and the UI theme.
-
-Both properties objectui#5703 established are preserved and still pinned: `get()` prefers a
-non-null `localStorage` read and falls back to the in-memory value, and the memory value is
-nulled BEFORE storage is touched — by `clear()` as before, and now by the user-change purge
-too, so the outgoing user's org id cannot outlive their persisted key on the sign-out-then-
-sign-in path that never reloads the page.
-
-Existing browsers are not migrated. A value sitting under the retired bare key is
-unattributable — nothing recorded whose org id it is — so migrating it is precisely the
-defect it would be migrating away from, and it is deleted instead. A signed-in user loses
-nothing durable: the active organization is a server-owned fact that
-`AuthProvider.refreshOrganizations` re-asks for whenever the list is non-empty and no
-active org is held, including the ADR-0081 single-membership repair. One boot re-supplies
-it; users with no organization land on the guided first-run flow, which is the outcome this
-card is about.
-
-`apps/console`'s pre-render auth preflight purges every spelling of the active-org key —
-the retired bare one and each `:u:` scope — when it finds a dead bearer token, and
-deliberately leaves the session-user pointer in place so the next sign-in can still tell
-that the browser changed hands.
diff --git a/.changeset/per-view-config-alias-fold-2890.md b/.changeset/per-view-config-alias-fold-2890.md
deleted file mode 100644
index 1636fc20c..000000000
--- a/.changeset/per-view-config-alias-fold-2890.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-'@object-ui/core': patch
----
-
-`normalizeListViewSchema` now folds the four per-view-type config aliases phase 3
-carried over — `kanban.groupField` → `groupByField`, `kanban.cardFields` →
-`columns`, `gallery.imageField` → `coverField`, `timeline.dateField` →
-`startDateField` (objectui#2890).
-
-These are the pre-#2231 objectui spellings, kept declared alongside the spec keys
-so stored view metadata would keep validating. They now fold at the same
-component boundary as the A1–A5 vocabulary folds, in the same one-directional
-shape: the canonical key wins when a config carries both, and the legacy key is
-removed from the result so a missed read-site fails loudly instead of quietly
-taking the legacy path.
-
-One rendering behaviour changes, and it is a correction of the same inverted
-precedence the `densityMode` fold fixed: `ListView`'s kanban adapter resolves the
-card field list as `cardFields || columns` — legacy over canonical — so a kanban
-config carrying **both** rendered the legacy `cardFields` value and silently
-ignored the spec-canonical `columns`. After the fold the authored `columns` is
-what reaches it. Configs carrying only one of the two are unaffected, and every
-other reader of these four keys was already canonical-first.
-
-`calendar.defaultView` is deliberately **not** folded: it aliases nothing and has
-no spec counterpart, so it wants promotion upstream rather than a rename.
diff --git a/.changeset/percent-figure-grid-citation-5608.md b/.changeset/percent-figure-grid-citation-5608.md
deleted file mode 100644
index 7c6075e49..000000000
--- a/.changeset/percent-figure-grid-citation-5608.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
----
-
-Comments only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared (objectui#5608).
-
-Two live sites still carried the bare `27,581 of 1,200,013` figure with neither the grid
-nor the measured surface named: the `DisplayNumberFormatOptions.style` doc comment and a
-comment in the percentage-points test. Both were accurate. Neither was *reconcilable* — a
-reader who had also seen `packages/fields`'s `27,577 of 1,200,003` met two counts that
-look like a contradiction, and a figure a reader cannot reconcile is what makes a correct
-comment get distrusted wholesale. That is the same defect class objectui#4596 was queued
-for, and the wording here is lifted from its PR rather than phrased a third way — a third
-phrasing for one fact is the defect one level up.
-
-Both counts stand unchanged. The disambiguator is the surface measured, so each site now
-names it: objectui#4576's tie-dense grid (0.005 steps to 2,000, precisions 0/1/2) on
-`formatDisplayNumber` / `formatMeasure`'s call shape, against objectui#4590's re-measure
-of the same route through `formatPercent`. No value, assertion or behaviour moves.
-
-Why the empty form is the right declaration, stated from what was measured rather than
-carried over from objectui#4596's PR — the two cases are mirror images. There, a function
-BODY comment was changed, so the emitted `.js` moved and the `.d.ts` did not. Here the
-changed source comment is the JSDoc on an exported interface member: the interface erases
-at runtime, so the new prose reaches `dist/utils/number-display.d.ts` and is absent from
-`dist/utils/number-display.js` (checked on a real build of the package). The second site
-is a test file, excluded from the build program by `src/**/__tests__/**` and never in
-`dist` at all.
-
-So a consumer-visible byte does move — the text shown on hover. That is documentation of
-an existing measurement, not a change to the measurement, the type, or any behaviour:
-nothing a consumer types against or calls is different, which is why this releases
-nothing. Bytes moving is not the criterion; released behaviour and consumer-visible
-surface are, and neither moves.
diff --git a/.changeset/pivot-table-stable-empty-rows-5562.md b/.changeset/pivot-table-stable-empty-rows-5562.md
deleted file mode 100644
index d7423a5b5..000000000
--- a/.changeset/pivot-table-stable-empty-rows-5562.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-'@object-ui/plugin-dashboard': patch
----
-
-`PivotTable` no longer re-runs its cross-tabulation memo on every render when it
-has no rows (objectui#5562).
-
-The component spelled the empty array twice — as the destructuring default for
-`schema.data` and as the `Array.isArray` fallback that keeps a provider-config
-object out of iteration — so a schema declaring no `data` key, or one whose
-`data` is a provider config rather than rows, produced a fresh array identity on
-every render. That value is the first entry of the memo's dependency list, so
-the memo rebuilt its two ordered key sets, its `bucket[row][col]` map, the
-aggregated matrix and the row/column/grand totals on every render, over nothing.
-Both spellings now resolve to one module-scope frozen empty, so "no rows" is a
-stable value and the memo holds.
-
-Wasted work only: the churn feeds a memo rather than a `setState`, and
-`PivotTable` holds no prop-to-state sync, so nothing rendered wrong and no
-render loop was possible. The identical fix landed for `data-table` in
-objectui#4618 and for `ObjectPivotTable` in objectui#4629; this closes the
-direct-use path those two did not cover, where `DashboardRenderer` and
-`DashboardGridLayout` construct pivot schemas without `ObjectPivotTable` in the
-chain.
diff --git a/.changeset/plugin-form-readme-classname-quantifier-5131.md b/.changeset/plugin-form-readme-classname-quantifier-5131.md
deleted file mode 100644
index 112411d63..000000000
--- a/.changeset/plugin-form-readme-classname-quantifier-5131.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@object-ui/plugin-form': patch
----
-
-`README.md`'s "Not a `FormField` key" table said a field-level `className` is
-"read on exactly one pseudo-field, `type: 'section-divider'`". That quantifier
-holds only for the renderer's *explicit* read — `className={fp.className}` on
-the `section-divider` branch of
-`packages/components/src/renderers/form/form.tsx`. The same renderer forwards
-every key it did not destructure, and `className` is not among the names taken
-off the field config, not among the ones `stripRendererOnlyProps` removes, and
-so rides `{...fieldProps}` into `renderFieldComponent`, whose built-in `input`
-branch spreads it onto ``. A field-level `className` therefore lands
-visibly on ordinary built-in controls, and a reader taking "exactly one"
-literally concludes the opposite of what the code does (objectui#5131).
-
-The cell now describes the contract rather than the reader count: an undeclared
-key still rides the props spread down to whichever component the field resolves
-to, nothing in the contract promises that, and a registered widget honours it
-only if it happens to spread its leftover props — the wording the docs site
-already ships, so the two sources agree again. The advice in the row is
-unchanged and was never wrong (`span` / `colSpan` for width,
-`FormSchema.fieldContainerClass` for the grid), and the explicit
-`section-divider` read is kept, now named as explicit.
-
-This is a documentation fix to a file `plugin-form` publishes to npm, which is
-why it carries a version: the npm landing page only picks up the correction on a
-release. No behaviour, export, type, or `dist` byte changes.
diff --git a/.changeset/properties-template-visibility-diagnostic-5756.md b/.changeset/properties-template-visibility-diagnostic-5756.md
deleted file mode 100644
index 5d050acf4..000000000
--- a/.changeset/properties-template-visibility-diagnostic-5756.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-'@object-ui/react': patch
----
-
-`SchemaRenderer`'s node visibility gate now also catches the `${…}`-templated
-spelling of the objectui#5454/#5687 diagnostics when it is written inside
-`properties`, e.g. `properties: { visible: "${data.status == 'draft'}" }`
-(objectui#5756).
-
-The `properties.*` evaluation loop runs, and interpolates every `${…}` template it
-finds, **before** the visibility gate ever sees the value — so by the time the
-existing diagnostics ran, a template-spelled predicate had already collapsed into a
-plain boolean and there was no predicate text left to inspect. The bare-string
-spelling of the exact same gate (`properties: { visible: "data.status == 'draft'"
-}`) was unaffected — nothing interpolates a string with no `${` in it — and was
-already reported; only the template spelling was structurally invisible.
-
-Reached by moving the diagnostic check to inside the `properties` evaluation loop,
-on the predicate's raw (pre-interpolation) text, gated on the key being one of the
-six visibility keys the render chain consults (`visibleWhen` / `visible` /
-`visibleOn` / `visibility` / `hidden` / `hiddenOn`) — a `properties.content`
-interpolation, or any other non-visibility key, is untouched and stays silent.
-
-Reports only the key that actually **decides** the node's visibility, mirroring
-objectui#5454's own leg semantics (its reporter is likewise only ever invoked on
-the leg the chain's early-return sequence actually reaches): a `properties.visible`
-template that a co-declared `visibleWhen` outranks is not reported for deciding
-nothing.
-
-**No verdict changes and no interpolation changes.** The diagnostic call's return
-value is discarded; the real verdict is still computed afterward, off the
-post-evaluation, post-hoist schema, by the same code path as before this change.
-Same two reporters as objectui#5454/#5687 (unresolvable-predicate / adapter-only-data
-predicate), same dedupe `Set`, same `console.warn` severity, same dev-only gate —
-only the silence moved, one render-step earlier.
diff --git a/.changeset/quick-reference-release-sync-5394.md b/.changeset/quick-reference-release-sync-5394.md
deleted file mode 100644
index d88e63d9e..000000000
--- a/.changeset/quick-reference-release-sync-5394.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
----
-
-Release tooling and repo docs only — this publishes nothing, declared explicitly with an
-empty frontmatter rather than left undeclared.
-
-`changeset:version` now runs `scripts/sync-quick-reference-release.mjs` after bumping the
-manifests, so `QUICK_REFERENCE.md`'s "Current Release" block moves in the same commit as
-the versions it quotes. Before this, the release path had no human in it and nothing
-updated the doc, so the block fossilised once per release and the anti-fossil gate
-`scripts/__tests__/quick-reference-current-release-4143.test.ts` reddened `main` on the
-push build every time (objectui#4642, objectui#4977, objectui#5394). The gate is
-unchanged — it is the judge; this is the thing that keeps the doc true.
-
-No package `src/` is touched, so no `@object-ui/*` package changes behaviour and there is
-nothing here for a consumer to upgrade to.
diff --git a/.changeset/react-pages-guide-source-contradictions-5413.md b/.changeset/react-pages-guide-source-contradictions-5413.md
deleted file mode 100644
index dbc67fae9..000000000
--- a/.changeset/react-pages-guide-source-contradictions-5413.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
----
-
-Docs only — this publishes nothing, declared explicitly with an empty frontmatter rather
-than left undeclared.
-
-`content/docs/guide/react-pages.md` contradicted the framework's sources on four points.
-The guide is live material (`@object-ui/react-runtime`'s README links it twice), so each
-one was reachable teaching. All four were re-measured against `objectstack@f094214b3` and
-resolved toward the source:
-
-- **Tailwind styling.** The guide taught Tailwind `className` as the react tier's styling
- primitive, which ADR-0080's 2026-06-30 amendment retracted under ADR-0065 (Accepted): a
- page's `source` is runtime metadata the console's build-time Tailwind never scans, so an
- authored utility class silently produces no CSS. Replaced with the per-tier primitive the
- shipped `page-source-className-tailwind` rule names — inline `style={{ … }}` with
- `hsl(var(--token))` on the react tier, structured props plus a JSON `style` object on the
- html tier — and added a Styling section giving the mechanism.
-- **`record:*` scope.** The family was offered as the illustrative in-scope example while
- `os validate` rejects it (`react-block-needs-record-context`, severity error, matched by
- type). The tag-derivation rule is kept — it is accurate — and the exclusion, the real
- error text, and the per-block alternatives are now stated beside it.
-- **`adapter.find` options.** The `Live data` sample passed `filters:`, which is not a
- `QueryParams` key; `convertQueryParams` reads only `$`-prefixed keys, so the sample
- returned the object's records unfiltered with no error. Corrected to `$filter`. The same
- sample also treated the result as an array — `find` resolves to a `QueryResult`, so
- `.map` on it throws; corrected to `res.data` alongside it.
-- **Block inventory.** The guide conflated the runtime scope (every public non-container
- block) with the authored contract (`REACT_BLOCKS`: `ObjectForm`, `ListView`,
- `ObjectChart`, `Block`). Both are now stated as two tiers, with the generated
- `react-blocks.md` named as the prop authority, and the flat-props example moved off two
- deprecated `ObjectGrid` spellings (`pageSize`, `fields`) onto the canonical
- `pagination` / `fields` on `ListView`.
diff --git a/.changeset/readonly-richtext-raw-markup-5498.md b/.changeset/readonly-richtext-raw-markup-5498.md
deleted file mode 100644
index c14d4ec2e..000000000
--- a/.changeset/readonly-richtext-raw-markup-5498.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-'@object-ui/fields': patch
----
-
-A readonly `markdown` / `html` / `richtext` form field now renders its content
-FORMATTED instead of showing the user its markup source (objectui#5498).
-
-`RichTextField`'s readonly early return rendered `{value}` as a React text child,
-so a readonly field of any of those three types displayed the stored markup as
-literal characters — a markdown field's asterisks and hashes, a richtext field's
-tags. The `prose` classes on that wrapper were the tell: they style rendered rich
-content, and there was none to style. Every other read surface — grid, kanban
-card, gallery, related list, dashboard record panel and the record detail page's
-read mode — dispatches through `getCellRenderer` and rendered the same stored
-bytes formatted, so one field disagreed with itself depending on which surface it
-was read on.
-
-The readonly branch now renders through the same components `getCellRenderer`
-resolves: `markdown` through the GFM renderer, `html` and `richtext` through the
-sanitizing HTML renderer. The two renderers moved out of the package barrel into
-`widgets/richTextDisplay.tsx` so the widget can reach them without importing the
-barrel back, and both sides now read one shared type-to-renderer table rather
-than two that can drift apart.
-
-The editor header's format label is fixed with it: it was computed as
-`field.format || 'markdown'`, and `format` is declared on `date` / `datetime` /
-`time` / `phone` / `auto_number` and on no rich-content type — so it read
-`undefined` for every real field and labelled an `html` field "Format: markdown".
-The label is now derived from the field type's display pipeline, so it names the
-syntax the value is actually stored in.
diff --git a/.changeset/reanchor-adr-0124-d1-citations-5701.md b/.changeset/reanchor-adr-0124-d1-citations-5701.md
deleted file mode 100644
index e176b1027..000000000
--- a/.changeset/reanchor-adr-0124-d1-citations-5701.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
----
-
-Traceability only — this publishes nothing, declared explicitly with an empty frontmatter
-rather than left undeclared. The change is comment text at eight live source sites; no
-executable line moves, and `git diff -U0` carries zero non-comment added or removed lines.
-
-`server enforces, client is courtesy` was cited at those eight sites as the framework's
-**`ADR-0057 D10`**. That decision reads *"Setup-nav surfacing follows the capability
-(ADR-0029 K2); the object stays open"* — nav-entry tiering, not enforcement location. The
-rule these sites actually invoke is decided by the framework's **`ADR-0124 D1`**, *"The
-server is the enforcement point; client-side gating is a usability courtesy"* (Accepted
-2026-08-18). #5699 fixed the repo-ambiguity half of this defect — whose ADR numbering the
-citation meant — and deliberately left the anchor half; this is that half.
-
-The new anchor is derived from an authority, not chosen: the framework's own ADR-0057
-carries a note aimed at precisely this citation — *"If a citation of `ADR-0057 D10`
-brought you here looking for that rule, ADR-0124 is where it is decided."* The substantive
-claim at every site is unchanged; only the anchor moves.
-
-The disambiguating parenthetical #5699 shipped — *"framework numbering; this repo's own
-ADR-0057 is an unrelated document"* — is retired along with the number it disambiguated.
-It warned about a collision specific to `0057`; this repository has no ADR-0124 at all
-(its own series stops at `0059`), and ADR-0124 records that a fresh, unambiguous number
-was chosen so that citations of it would not need such a warning. The `the framework's …`
-possessive stays at every site, so each one still says whose numbering it means.
-
-Left byte-untouched, deliberately: `packages/data-objectstack/src/appAccessProbe.test.ts`
-cites the same decision for *"an app gated by an absent optional service"* — the
-capability/service-gating family that decision genuinely does decide, so it is correct as
-it stands. `docs/adr/0036-field-conditional-rules.md`, the wording all eight derive from,
-carries the same misattribution and moves in its own PR: `docs/adr/**` is a governed
-surface that stops at draft for human merge.
diff --git a/.changeset/record-activity-scheduled-event-5840.md b/.changeset/record-activity-scheduled-event-5840.md
deleted file mode 100644
index 011e1447b..000000000
--- a/.changeset/record-activity-scheduled-event-5840.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-"@object-ui/plugin-detail": patch
----
-
-`record:activity` no longer discards scheduled activities. `sys_activity` rows with
-`type: "scheduled"` now map to the `event` feed kind instead of being dropped before any
-filter runs, so a not-yet-held meeting reaches the timeline and `types: ['event']` stops
-being a permanently empty tab. Held meetings are unchanged: `completed` still maps to
-`task` and still hides unless `showCompleted` is set, which is what keeps an upcoming
-meeting visible by default while a finished one is not.
-
-The unknown-type default is unchanged and deliberately so — a row whose type nothing maps
-is still dropped rather than bucketed into `system`, because rendering an unmeasured kind
-as something it is not would be new wrong data rather than recovered data. What changes is
-that the drop is no longer silent: an unmapped type now logs one `console.warn` naming it,
-once per type. Types the map knows and deliberately excludes (`commented`, `mentioned`,
-`login`, `logout`) stay silent, since those are decisions rather than gaps.
diff --git a/.changeset/record-alert-cta-label-i18n-4998.md b/.changeset/record-alert-cta-label-i18n-4998.md
deleted file mode 100644
index c42fa2e98..000000000
--- a/.changeset/record-alert-cta-label-i18n-4998.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-'@object-ui/plugin-detail': patch
----
-
-`record:alert`'s renderer-local `RecordAlertProps` CTA slot (`action.label`) is
-widened to `string | I18nLabel` in both copies (`properties.*` and the flat
-compat mirror) in `packages/plugin-detail/src/renderers/record-alert.tsx`.
-
-The renderer already resolves `action.label` through the same inline-locale-map
-`pickLocalized` call as `title` / `body` (`const ctaLabel =
-pickLocalized(props.action?.label, language)`), so a bare `string` declaration
-was narrower than the renderer's own runtime behavior — the same
-declaration-narrower-than-the-renderer contradiction objectui#4970 fixed for
-`title` / `body` one level up in the same interface (objectui#4998).
-
-Type-only: the block's published authoring surface still declares `action` as
-a bare `object` with the member shape in prose only
-(`plugin-detail/src/index.tsx`), so there is no manifest arm to align yet —
-that half stays parked on the `ComponentInput` member-shape question (PR
-#3795) and is out of scope here.
diff --git a/.changeset/record-alert-loading-frame-diagnostic-5776.md b/.changeset/record-alert-loading-frame-diagnostic-5776.md
deleted file mode 100644
index e6b72b977..000000000
--- a/.changeset/record-alert-loading-frame-diagnostic-5776.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-'@object-ui/plugin-detail': patch
----
-
-`record:alert`'s `visible` predicate no longer logs a spurious `record is not
-defined` evaluation-failure warning during the record-detail loading frame.
-
-The predicate is evaluated on every render (Rules of Hooks), including the
-frame before `useRecordContext().data` has loaded. In that frame `record` is
-unset, `usePredicateRecordContext` binds an empty context bag by design (no
-`record` key at all), and a bare/`${…}` predicate referencing `record.*`
-faulted with `record is not defined` — logged via `console.warn` on every
-page load, even the correct, working ones, because the SAME predicate
-resolved fine one frame later once `record` populated. The banner's own
-visibility was never wrong (it already hides unconditionally while unloaded);
-only the diagnostic was permanently misleading on the happy path.
-
-The predicate is now skipped while `record` hasn't loaded — the same
-condition the banner's own "hide while unloaded" early return already used —
-since its verdict in that frame was never consulted anyway. A predicate that
-is genuinely broken (bad field, bad syntax) still faults, and still logs, on
-every frame once `record` is populated (objectui#5776).
diff --git a/.changeset/record-alert-row-binding-4807.md b/.changeset/record-alert-row-binding-4807.md
deleted file mode 100644
index ecca389da..000000000
--- a/.changeset/record-alert-row-binding-4807.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-'@object-ui/plugin-detail': minor
----
-
-`record:alert` binds the row through `usePredicateRecordContext`, so an
-author-declared `properties.visible` is actually consulted.
-
-`renderers/record-alert.tsx` was the last predicate face in the repo still
-handing `useCondition` a root-only `{ record }` bag. Every other row-scoped
-predicate — the four generic action renderers (objectui#4075) and app-shell's
-`DeclaredActionsBar` (objectui#4077) — binds the row through the shared
-`usePredicateRecordContext(record)` helper, which resolves the three spellings
-objectui#5330 ruled on: canonical `record.status`, the deprecated row-action
-shorthand `status`, and deprecated legacy `data.status`.
-
-Under the root-only bag only the canonical spelling worked, and the two others
-failed in **opposite** directions — both of them silently, because this call
-site is fail-soft:
-
-- **row-action shorthand** (`status == 'x'`) resolved nothing, so the evaluator
- threw. The legacy `${…}` path answers a throw with its own source text, a
- non-empty and therefore truthy string, so the verdict was **SHOWN on every
- row**. A banner the author had gated was permanently on screen.
-- **legacy `data.*`** (`data.status == 'x'`) did not throw at all. App-shell's
- ambient predicate scope (`providers/ExpressionProvider.tsx`) carries
- `data: {}`, so the predicate read that object instead of the row, compared
- `undefined`, and the verdict was a constant false — **never shown**.
-
-**Behaviour change, stated plainly:** a shipped `record:alert` whose `visible`
-was written in either deprecated spelling was inert and is now live. A banner
-that was permanently visible may begin to hide, and one that never appeared may
-begin to show — that is the point of the fix, but it is a verdict change rather
-than a no-op. Canonical `record.*` predicates are unaffected in verdict: they
-resolved before and resolve now, pinned on both polarities. An in-tree census
-found no `record:alert` `visible` predicate outside this package's own tests.
-
-A node-level `visibleWhen` is a separate gate one tier up in `SchemaRenderer`,
-with its own deliberate bindings (`data` is the data-source adapter there, not
-the row). This change does not touch it; the two still compose as AND.
-
-The renderer's header comment described the shared-scope behaviour it did not
-have. It now describes what the file does, including the fail-soft policy and
-the two-gate composition.
diff --git a/.changeset/record-detail-param-dialog-title-5610.md b/.changeset/record-detail-param-dialog-title-5610.md
deleted file mode 100644
index f8c16e205..000000000
--- a/.changeset/record-detail-param-dialog-title-5610.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-'@object-ui/app-shell': patch
----
-
-`RecordDetailView`'s param-collection dialog now titles itself from `action.label`
-alone — the unreachable `|| action?.title` fallback beside it is removed
-(objectui#5610).
-
-This was the second copy of the limb objectui#4282 removed from
-`useConsoleActionRuntime`. `RecordDetailView` builds its own action runtime rather
-than routing through that hook, so the two near-identical `paramCollectionHandler`s
-have drifted as a pair and the first fix could not reach this one.
-
-`title` is declared on no action surface in the ecosystem: it is absent from
-`@objectstack/spec`'s `ActionSchema` (44 keys walked at spec 17.0.0), from
-`@object-ui/core`'s `ActionDef` and its pinned `ACTION_DEF_KEYS` / `SPEC_ACTION_KEYS`
-inventories, and from `@object-ui/types`' renderer view (`ui-action.ts`) and `crud.ts`
-`ActionSchema` / `BaseSchema`. None of the four action renderers — `action:button`,
-`action:icon`, `action:group`, `action:menu` — forwards it either. So the right-hand
-side of that `||` could not be reached by authored metadata: a fallback that cannot
-fire, which is the "declared is not enforced" shape objectstack#4075 exists to reduce.
-Nothing a user hits changes; the line now reads exactly one key, matching the
-`description` line directly below it.
-
-`RecordDetailView.paramDialogTitle.test.tsx` pins the reader so the alias cannot be
-reinstated silently: an action carrying `title` and no `label` must open an untitled
-dialog rather than a dialog named by a key no producer sets. A pin per reader is the
-only shape that covers both handlers, since the hook's own pin cannot see this site.
diff --git a/.changeset/record-picker-empty-text-i18n-5590.md b/.changeset/record-picker-empty-text-i18n-5590.md
deleted file mode 100644
index 8c53ac4a9..000000000
--- a/.changeset/record-picker-empty-text-i18n-5590.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-'@object-ui/components': patch
----
-
-`element:record_picker`'s `emptyText` now resolves the inline per-locale map its
-contract has admitted since rc.6, and its published declaration says so
-(objectui#5590).
-
-`@objectstack/spec` widened this key to the `I18nLabel` union
-(`string | Record< string, string >`) at 17.0.0-rc.6, and the installed 17.0.0 GA
-still carries it — measured, not assumed:
-`ElementRecordPickerPropsSchema.safeParse({ object: 'account', emptyText: { en, 'zh-CN' } })`
-succeeds. The renderer honoured only the string arm, handing the map straight to a
-text node. React refuses a plain object in a child position rather than stringifying
-it, so an author writing the map form the contract accepts did not get a mis-rendered
-empty state — the whole picker subtree threw
-`Objects are not valid as a React child (found: object with keys {en, zh-CN})`.
-
-The read site now resolves through `pickLocalized`, the objectui-side helper the
-sibling text-node sites already read through (`element:text.content`,
-`element:button.label`, `page:card.title`), which spells a miss as `''` rather than
-the spec resolver's `undefined`. The default is applied before resolution, so
-`emptyText` absent still means "No records" and an authored empty string still
-renders empty.
-
-The `ComponentMeta` entry, which held a single `'string'` arm precisely because the
-renderer dropped the other one, now declares `['string', 'object']`. That narrowing
-was correct for exactly as long as it was true: with the map arm reaching the screen
-resolved, withholding it would be the false declaration in the other direction — the
-manifest gate reporting `type-mismatch` on a legal write the same input's own
-`description` teaches the author to make. The `apps/console` specimen that pinned the
-narrow arm named this release condition in its own words ("keeps its single `'string'`
-arm until the render site catches up") and is flipped here, keeping its controls.
-
-Three comments in the renderer deferred this gap to objectui#4163, which closed as
-completed on 2026-08-15 while the gap was still open; the file now carries no
-reference to it. The `ComponentInput.type` doc in `@object-ui/types` cited this very
-key as its worked example of an arm deliberately withheld, and is corrected in the
-same change so the example stays true.
diff --git a/.changeset/record-picker-label-association-5771.md b/.changeset/record-picker-label-association-5771.md
deleted file mode 100644
index df693b93c..000000000
--- a/.changeset/record-picker-label-association-5771.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-'@object-ui/components': patch
----
-
-`element:record_picker`'s `label` is now programmatically associated with its
-`SelectTrigger` combobox — fixing a case where the caption the block's own
-registration prose advertised (`'Caption rendered above the picker, in a
-