From 27500a6fe772054b476dbefaf11bff07be56886a Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Mon, 24 Aug 2026 08:56:43 +0800 Subject: [PATCH] fix(app-shell): built-moment transition fires on auto-publish envelopes too (#5799) Staging verification caught it: the transition keyed on draftReview, which only lifts status:'drafted' -- but an auto-publish environment (staging/cloud posture) rewrites the whole-app build envelope to status:'published' (keeping drafted[] + packageId), so reopening a built conversation stayed on the full page. detectBuiltAppPackage reads the raw envelope, posture-independent; new pin drives the published shape through the real page. Co-Authored-By: Claude Fable 5 --- .changeset/built-transition-published-5799.md | 6 +++++ .../app-shell/src/console/ai/AiChatPage.tsx | 7 ++++++ .../AiChatPage.builtTransition.test.tsx | 10 ++++++++ packages/plugin-chatbot/src/index.tsx | 1 + packages/plugin-chatbot/src/mapMessages.ts | 23 +++++++++++++++++++ 5 files changed, 47 insertions(+) create mode 100644 .changeset/built-transition-published-5799.md diff --git a/.changeset/built-transition-published-5799.md b/.changeset/built-transition-published-5799.md new file mode 100644 index 0000000000..69bd9c0cfa --- /dev/null +++ b/.changeset/built-transition-published-5799.md @@ -0,0 +1,6 @@ +--- +'@object-ui/plugin-chatbot': patch +'@object-ui/app-shell': patch +--- + +The built-moment transition (#5799) now fires on auto-publish environments too: `detectBuiltAppPackage` reads the raw build envelope (`status:'drafted'` OR `'published'`, packageId + an `app` item), because an auto-publish posture rewrites apply_blueprint's envelope to `published` and the drafted-only `draftReview` lift never fired there — measured live on staging, where reopening a built conversation stayed on the full page. diff --git a/packages/app-shell/src/console/ai/AiChatPage.tsx b/packages/app-shell/src/console/ai/AiChatPage.tsx index b80899eba9..7b23e5e038 100644 --- a/packages/app-shell/src/console/ai/AiChatPage.tsx +++ b/packages/app-shell/src/console/ai/AiChatPage.tsx @@ -75,6 +75,7 @@ import { detectBuilderHandoff, detectProposedChanges, detectReplayOutcome, + detectBuiltAppPackage, buildProgressFromDraftReview, // The authoring/honest -> runtime message seam (objectui#4399 / PR #4416), // consumed here one hop up from the plugin's own renderers (objectui#4437). @@ -1886,6 +1887,12 @@ export function ChatPane({ const builtPackageId = useMemo(() => { for (let i = messages.length - 1; i >= 0; i--) { for (const tool of messages[i].toolInvocations ?? []) { + // Posture-independent: read the raw envelope, not draftReview — an + // auto-publish env rewrites the build envelope to status:'published' + // and the drafted-only lift never fires there (measured live on + // staging: reopening a built conversation stayed on the full page). + const pkg = detectBuiltAppPackage(tool.result); + if (pkg) return pkg; const dr = tool.draftReview; if (dr?.packageId && dr.items?.some((it) => it.type === 'app')) return dr.packageId; } diff --git a/packages/app-shell/src/console/ai/__tests__/AiChatPage.builtTransition.test.tsx b/packages/app-shell/src/console/ai/__tests__/AiChatPage.builtTransition.test.tsx index 7779197c39..076f9cc2f7 100644 --- a/packages/app-shell/src/console/ai/__tests__/AiChatPage.builtTransition.test.tsx +++ b/packages/app-shell/src/console/ai/__tests__/AiChatPage.builtTransition.test.tsx @@ -207,6 +207,16 @@ describe('AiChatPage — built-moment transition (objectui#5799)', () => { }); }); + it("an AUTO-PUBLISH environment's published envelope transitions too (the staging posture)", async () => { + serverTurns = JSON.parse( + JSON.stringify(BUILT_TURNS).replace('"status":"drafted"', '"status":"published"'), + ); + renderPage(); + await waitFor(() => expect(screen.getByTestId('studio-page')).toBeInTheDocument(), { + timeout: 4000, + }); + }); + it('a conversation with no whole-app build stays on the full page', async () => { serverTurns = UNBUILT_TURNS; renderPage(); diff --git a/packages/plugin-chatbot/src/index.tsx b/packages/plugin-chatbot/src/index.tsx index 09ae0738a3..ffabac137f 100644 --- a/packages/plugin-chatbot/src/index.tsx +++ b/packages/plugin-chatbot/src/index.tsx @@ -361,6 +361,7 @@ export { detectProposedChanges, detectReplayOutcome, detectAuthoringVerdict, + detectBuiltAppPackage, buildProgressFromDraftReview, } from './mapMessages'; export type { diff --git a/packages/plugin-chatbot/src/mapMessages.ts b/packages/plugin-chatbot/src/mapMessages.ts index ab26913fb2..50667e8999 100644 --- a/packages/plugin-chatbot/src/mapMessages.ts +++ b/packages/plugin-chatbot/src/mapMessages.ts @@ -420,6 +420,29 @@ export function detectAuthoringVerdict( return { kind: 'drafted', ...packageId }; } +/** + * objectui#5799 — did this tool result finish a WHOLE-APP build, and for which + * package? Posture-independent on purpose: an auto-publish environment + * rewrites the apply_blueprint envelope to `status:'published'` (keeping + * `drafted[]` + `packageId`), so keying the built-moment transition on + * `draftReview` (drafted-only) missed every staging/cloud build — measured + * live: reopening a built conversation stayed on the full page. + */ +export function detectBuiltAppPackage(result: unknown): string | undefined { + const obj = parseResultEnvelope(result); + if (!obj) return undefined; + if (obj.status !== 'drafted' && obj.status !== 'published') return undefined; + const pkg = (obj as { packageId?: unknown }).packageId; + if (typeof pkg !== 'string' || !pkg) return undefined; + const drafted = (obj as { drafted?: unknown }).drafted; + const hasApp = + Array.isArray(drafted) && + drafted.some( + (d) => d && typeof d === 'object' && (d as { type?: unknown }).type === 'app', + ); + return hasApp ? pkg : undefined; +} + export function detectDraftResult(result: unknown): DraftReview | undefined { const obj = parseResultEnvelope(result); if (!obj || obj.status !== 'drafted') return undefined;