Skip to content

A second client class also traverses the metadata save door and still discards its advisory findings #4237

Description

@yinlianghui

Filed while implementing #4133 (PR #4236). Unassigned — observation-class, recorded rather than fixed, and deliberately not folded into that PR's scope.

What was measured

#4133 renders the runtime authoring gate's advisory findings after a successful save. The wiring lifts once at useMetadataClient, which is the single point every app-shell write path takes its MetadataClient from — so ResourceEditPage, StudioDesignSurface, EmbeddedItemEditor, DatasourceResourcePage and ObjectHooksPanel are all covered by one sink.

There is a second, unrelated client class that writes through the same REST door and is not covered by it: ObjectStackClient.meta.saveItem (the SDK client hanging off ObjectStackAdapter), which is a different class from MetadataClient despite hitting the same PUT /api/v1/meta/:type/:name. Its callers, all writing in active mode (so the gate does run — this is not the D1 draft case):

  • packages/app-shell/src/hooks/useNavigationSync.ts:216saveItem('app', appName, schema)
  • packages/app-shell/src/services/MetadataService.ts:139,149,164,178,210 — object saves
  • packages/data-objectstack/src/index.ts:2878,3048,3139,3226updateViewConfig and the dashboard save paths
  • packages/plugin-designer/src/pages/CreateAppPage.tsx:56, EditAppPage.tsx:68

Each awaits the call and discards the response value, so an advisories[] the server attaches is parsed off the wire and dropped — the same client-side discard #4133 closed for the other client.

Why this is observation-class, not a defect

Nothing regressed and no user hits a wrong result: these paths behaved this way before #4133 and still do. It is a coverage gap in a feature that just landed, not a break. Severity is deliberately left for triage rather than judged here.

Worth noting for whoever picks it up: the two clients have genuinely different shapes — MetadataClient is constructed per-component through one factory (which is what made a config-level sink work), whereas ObjectStackAdapter is a single long-lived instance that already carries an event-emitter seam (onWriteWarning, #3431/#3455). So the natural fix here is probably the emitter shape rather than the config-callback shape, reusing the AdapterProvider subscription that already exists — not a copy of #4133's wiring.

The renderer itself is already reusable: emitSaveAdvisories in packages/app-shell/src/providers/saveAdvisoryToast.ts is a pure builder taking a caller-owned sink, and readSaveAdvisories is exported from @object-ui/data-objectstack. Neither would need changing.

Dedup

Searched open objectui issues for the saveItem / advisories / second-client framing: only #4133 itself and two closed unrelated items. No twin.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions