Symptom
Authoring a view through the runtime metadata door with the documented ViewSchema container shape succeeds at every step and produces nothing a consumer can see.
PUT /api/v1/meta/view/<name>?mode=draft&package=<pkg> with {object, list:{…}, form:{…}} → 200state:'draft'POST /api/v1/meta/view/<name>/publish → 200GET /api/v1/meta/view?object=<obj> → {"items":[]}GET /api/v1/meta/view/<name> → 200 with the full authored body
So the item is stored — it is just never served. The end-user list keeps rendering synthesized default columns. A deliberate full server restart does not help: the path is inert, not restart-required. Reproduced on two objects, and with the container stored under both an arbitrary name and under the object name.
Control that isolates it. Publishing the same list and form bodies as pre-expanded ViewItems ({name, object, viewKind, config}) makes the list columns and the authored form section render live, after a client reload only. Draft isolation is clean in both shapes (pre-publish, both consumer doors report absent; only ?preview=draft exposes it), and the edit loop is repeatable with zero restarts on the shape that works.
Root cause
Container expansion exists, but it is wired to exactly one path — the artifact/package boot load — and the runtime write door is not on it.
packages/metadata/src/plugin.ts iterates ARTIFACT_FIELD_TO_TYPE over the builtobjectstack.json and, for a view item that isAggregatedViewContainer() accepts, registers the container under the bare object key and registers every expandViewContainer(viewObject, item) result as its own ViewItem (origin/main lines 634-668).- The runtime write door stores the container verbatim.
packages/metadata-protocol/src/protocol.ts imports isAggregatedViewContainer (line 60) and uses it only to skip containers in a list response (line 4405) and to leave container bodies untouched during identity normalization (normalizeViewMetadata, ~line 774). It never calls expandViewContainer — that symbol has zero references anywhere in packages/metadata-protocol. MetadataManager.getViewsByObject() (packages/metadata/src/metadata-manager.ts:1432-1444) filters v.viewKind && v.object === object. A container carries neither, so it is filtered out by design: its own docblock says it "Returns only expanded ViewItems (those carrying a viewKind) — never the legacy aggregated container".
The three facts compose into the measured behaviour: the write door accepts and persists the container, the expansion that would give it viewKind never runs off the boot path, and the by-object consumer drops anything without viewKind.
Stale-premise check: re-verified on objectstackorigin/main (00e9196). All three sites are present and unchanged in substance; only line numbers moved from what the run recorded.
Neighbour:#7163 (closed) is the same missing-expansion class on a different seam — the nested-plugin loader. That one was fixed at its own seam; the runtime write door described here was not covered by it, so the shared question — where expansion belongs so every producer gets it — is worth answering once rather than a third time.
Reproduction
- Boot the showcase with writable runtime packages.
PUT /api/v1/meta/view/<name>?mode=draft&package=<pkg> with a ViewSchema container: {object: '<obj>', list: {…}, form: {…}} → 200 state:'draft'.POST /api/v1/meta/view/<name>/publish → 200.GET /api/v1/meta/view?object=<obj> → {"items":[]}, while GET /api/v1/meta/view/<name> returns the full body.- Restart the server and repeat step 4 — still
{"items":[]}. - Control: publish the same bodies as
{name, object, viewKind, config} ViewItems; after a client reload the authored list columns and form section render.
Source
Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).
Symptom
Authoring a view through the runtime metadata door with the documented ViewSchema container shape succeeds at every step and produces nothing a consumer can see.
PUT /api/v1/meta/view/<name>?mode=draft&package=<pkg>with{object, list:{…}, form:{…}}→ 200state:'draft'POST /api/v1/meta/view/<name>/publish→ 200GET /api/v1/meta/view?object=<obj>→{"items":[]}GET /api/v1/meta/view/<name>→ 200 with the full authored bodySo the item is stored — it is just never served. The end-user list keeps rendering synthesized default columns. A deliberate full server restart does not help: the path is inert, not restart-required. Reproduced on two objects, and with the container stored under both an arbitrary name and under the object name.
Control that isolates it. Publishing the same list and form bodies as pre-expanded ViewItems (
{name, object, viewKind, config}) makes the list columns and the authored form section render live, after a client reload only. Draft isolation is clean in both shapes (pre-publish, both consumer doors report absent; only?preview=draftexposes it), and the edit loop is repeatable with zero restarts on the shape that works.Root cause
Container expansion exists, but it is wired to exactly one path — the artifact/package boot load — and the runtime write door is not on it.
packages/metadata/src/plugin.tsiteratesARTIFACT_FIELD_TO_TYPEover the builtobjectstack.jsonand, for aviewitem thatisAggregatedViewContainer()accepts, registers the container under the bare object key and registers everyexpandViewContainer(viewObject, item)result as its own ViewItem (origin/mainlines 634-668).packages/metadata-protocol/src/protocol.tsimportsisAggregatedViewContainer(line 60) and uses it only to skip containers in a list response (line 4405) and to leave container bodies untouched during identity normalization (normalizeViewMetadata, ~line 774). It never callsexpandViewContainer— that symbol has zero references anywhere inpackages/metadata-protocol.MetadataManager.getViewsByObject()(packages/metadata/src/metadata-manager.ts:1432-1444) filtersv.viewKind && v.object === object. A container carries neither, so it is filtered out by design: its own docblock says it "Returns only expanded ViewItems (those carrying aviewKind) — never the legacy aggregated container".The three facts compose into the measured behaviour: the write door accepts and persists the container, the expansion that would give it
viewKindnever runs off the boot path, and the by-object consumer drops anything withoutviewKind.Stale-premise check: re-verified on
objectstackorigin/main(00e9196). All three sites are present and unchanged in substance; only line numbers moved from what the run recorded.Neighbour:#7163 (closed) is the same missing-expansion class on a different seam — the nested-plugin loader. That one was fixed at its own seam; the runtime write door described here was not covered by it, so the shared question — where expansion belongs so every producer gets it — is worth answering once rather than a third time.
Reproduction
PUT /api/v1/meta/view/<name>?mode=draft&package=<pkg>with a ViewSchema container:{object: '<obj>', list: {…}, form: {…}}→ 200state:'draft'.POST /api/v1/meta/view/<name>/publish→ 200.GET /api/v1/meta/view?object=<obj>→{"items":[]}, whileGET /api/v1/meta/view/<name>returns the full body.{"items":[]}.{name, object, viewKind, config}ViewItems; after a client reload the authored list columns and form section render.Source
Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).