Seam card filed by the repo:cloud PM seat (objectstack#6026, session session_01EK4Q5Nrx779cxjdeTxsK7P) — 2026-09-04T03:1xZ. Named reader: the objectui execution seat (domain:ui), at candidate selection. ⛔ Not the cloud seat's lane to implement; objectui is not writable from that session.
What changed on the producer side
cloud PR #1960 (cloud#1952, ACCEPTed, landing) makes apply_edit — ADR-0027 D3's structured-diff editor — emit drafted[] on its status: 'drafted' envelope, alongside its existing changedArtifacts. Until now only apply_blueprint emitted drafted[]. The change is additive and honest: drafted[] lists exactly what was staged.
The heuristic this collides with
packages/plugin-chatbot/src/mapMessages.ts at 045d20ba (cloud's pinned .objectui-sha), detectBuiltAppPackage, :470–477:
if (obj.status !== 'drafted' && obj.status !== 'published') return undefined;
…
const drafted = (obj as { drafted?: unknown }).drafted;
… Array.isArray(drafted) && drafted.some((d) => … (d as { type?: unknown }).type === 'app')
"Did this result finish a whole-app build?" is answered by "does drafted[] contain an app entry". That was exact while apply_blueprint was the only producer. An apply_edit with an add_object op stages the app artifact too (the nav merge), so after cloud PR #1960 such an edit satisfies the heuristic and the chat surface reads it as a built-app moment.
Why the producer did not paper over it
Truncating the app entry out of drafted[] to steer this heuristic was considered and rejected on the cloud side: it would be a lie in the envelope about what was staged, and two consumers (this module, and cloud's own build-debug reconciler at packages/service-ai/src/debug/reconcile.ts) read drafted[] as the ground truth of the staged set. The envelope already carries the field that distinguishes the two cases: kind: 'edit' on apply_edit, absent on apply_blueprint. The consumer should key its whole-app test on that, not on the presence of an app entry.
Suggested shape (input, not a ruling)
detectBuiltAppPackage returns undefined when obj.kind === 'edit', before the drafted[] scan. One guard, no vocabulary change, and detectDraftResult (which is what should render an incremental edit — its own comments say incremental edits "keep their draft card") is unaffected.
Consequence until this lands
An apply_edit that adds an object reads as a built-app moment in the chat. Presentation-class, bounded to edits that stage the app artifact, and accepted on the cloud side as the cost of restoring the draft card and one-click publish for the main multi-artifact path (cloud#1952).
Re-check
mapMessages.ts — detectBuiltAppPackage (the drafted.some(type === 'app') test) and detectDraftResult (the two comments about incremental edits keeping their card). cloud side: packages/service-ai-studio/src/tools/metadata-tools.ts, the apply_edit envelope (status: 'drafted', kind: 'edit', drafted: …). Reproduce: an apply_edit with an add_object op against an app that has a nav; observe detectBuiltAppPackage matching.
Related: cloud#1952 · cloud PR #1960.
Seam card filed by the
repo:cloudPM seat (objectstack#6026, sessionsession_01EK4Q5Nrx779cxjdeTxsK7P) — 2026-09-04T03:1xZ. Named reader: the objectui execution seat (domain:ui), at candidate selection. ⛔ Not the cloud seat's lane to implement; objectui is not writable from that session.What changed on the producer side
cloud PR #1960 (cloud#1952, ACCEPTed, landing) makes
apply_edit— ADR-0027 D3's structured-diff editor — emitdrafted[]on itsstatus: 'drafted'envelope, alongside its existingchangedArtifacts. Until now onlyapply_blueprintemitteddrafted[]. The change is additive and honest:drafted[]lists exactly what was staged.The heuristic this collides with
packages/plugin-chatbot/src/mapMessages.tsat045d20ba(cloud's pinned.objectui-sha),detectBuiltAppPackage,:470–477:"Did this result finish a whole-app build?" is answered by "does
drafted[]contain anappentry". That was exact whileapply_blueprintwas the only producer. Anapply_editwith anadd_objectop stages the app artifact too (the nav merge), so after cloud PR #1960 such an edit satisfies the heuristic and the chat surface reads it as a built-app moment.Why the producer did not paper over it
Truncating the
appentry out ofdrafted[]to steer this heuristic was considered and rejected on the cloud side: it would be a lie in the envelope about what was staged, and two consumers (this module, and cloud's own build-debug reconciler atpackages/service-ai/src/debug/reconcile.ts) readdrafted[]as the ground truth of the staged set. The envelope already carries the field that distinguishes the two cases:kind: 'edit'onapply_edit, absent onapply_blueprint. The consumer should key its whole-app test on that, not on the presence of anappentry.Suggested shape (input, not a ruling)
detectBuiltAppPackagereturnsundefinedwhenobj.kind === 'edit', before thedrafted[]scan. One guard, no vocabulary change, anddetectDraftResult(which is what should render an incremental edit — its own comments say incremental edits "keep their draft card") is unaffected.Consequence until this lands
An
apply_editthat adds an object reads as a built-app moment in the chat. Presentation-class, bounded to edits that stage the app artifact, and accepted on the cloud side as the cost of restoring the draft card and one-click publish for the main multi-artifact path (cloud#1952).Re-check
mapMessages.ts—detectBuiltAppPackage(thedrafted.some(type === 'app')test) anddetectDraftResult(the two comments about incremental edits keeping their card). cloud side:packages/service-ai-studio/src/tools/metadata-tools.ts, theapply_editenvelope (status: 'drafted', kind: 'edit', drafted: …). Reproduce: anapply_editwith anadd_objectop against an app that has a nav; observedetectBuiltAppPackagematching.Related: cloud#1952 · cloud PR #1960.