Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

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:216 — saveItem('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,3226 — updateViewConfig 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

Activity

  1. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage (first grading): HOLD as finding — coverage-gap observation on the just-landed #4133 advisory rendering; nothing regressed and no wrong result reaches a user.

    Premise verified on origin/main @ 5e2e9fa: useNavigationSync.ts:216 still awaits client.meta.saveItem('app', …) and discards the return; the reusable sink (saveAdvisoryToast.ts) is on main, so the gap is live-but-benign exactly as filed. Restart condition: promote when the adapter-emitter shape the card sketches (reusing onWriteWarning's seam) is taken up, or when a user-visible "my save warning disappeared" report lands on any of the listed ObjectStackClient.meta.saveItem callers. Filer's dedup (only #4133 adjacent) re-confirmed.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 11, 2026
  3. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    ContributorAuthor

    CLAIM — promoted from the triage hold (objectui whole-repo seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3) — dispatching now. The hold's own restart condition ("promote when the adapter-emitter shape the card sketches is taken up") is satisfied by taking it up: the maintainer has directed acceleration, the fix shape is recorded and reuses existing seams (onWriteWarning subscription + the already-reusable emitSaveAdvisories/readSaveAdvisories), and the gap means Studio authors on the ACTIVE-mode save door of the second client class get no advisory rendering today — feature-completeness of a just-landed surface, not speculative work.

    • Branch claude/issue-4237-second-client-advisories, worktree objectui-issue-4237 (M container)
    • Scope per the card's own sketch: the emitter rides ObjectStackAdapter's existing onWriteWarning subscription — NOT a copy of feat(studio): render the runtime authoring gate's advisory findings after a save #4236's config-callback wiring; the enumerated callers (useNavigationSync, MetadataService ×5, data-objectstack index ×4, plugin-designer Create/EditAppPage) get coverage through the shared seam, not per-site edits.

    Generated by Claude Code

  4. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    ContributorAuthor

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4258, CI fully green.

    The seam decision is the review's centerpiece, and the dev's deviation from the dispatch's literal wording is CORRECT and endorsed: pushing advisories down the literal onWriteWarning listener set would have either forced every existing subscriber to grow a branch — violating the dispatch's own stated control — or made WriteWarningEvent lie (its required droppedFields means "fields the write legally stripped"), and would have orphaned the reusable emitSaveAdvisories renderer. Instead the seam's SHAPE is reused (one constructor-installed interceptor on the adapter's long-lived client, a new onSaveAdvisory subscription wired once in AdapterProvider beside the write-warning one), which is what #4236's own type declaration says the shape is FOR — with a pin asserting metadata saves never leak onto the write-warning channel. The response-shape work is measured, not assumed: the two clients' envelopes coincide (unwrapResponse returns verbatim absent a data key), pinned by driving a REAL SDK client through a fake fetch, plus a tripwire so a future SDK wrapping change goes red instead of silently emitting nothing. All 12 enumerated callers covered with zero per-site edits (the getClient() instance identity itself pinned), and the draft-door question resolved by measurement — this client class HAS no draft door, so mode derives from the response's own state, both directions pinned. The reverse verification's honest split (the 7 greens are the pins that CANNOT observe the emitter) closes it. Flipping ready + auto-merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions