Repository navigation
Commit a89074a
fix(app-shell,plugin-chatbot): AI build page preview header reads the build card's label and live state; progress items show labels (#26538)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9H8pxvmafNv5DS7U2grhcqZ)_
Fixes #26469
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:AI 构建页右侧预览的标题改用构建结果卡片同一份状态:显示应用名称(不再是机器名),卡片说已上线时去掉「未发布 ·
仅自己可见」,发布后随之更新;进度列表有 label 时显示 label;卡片底部说明改为「在对话里点「发布」即可上线」。
- 为什么改:同一页面上结果卡片说「已搭建并上线」,预览标题却说「未发布」,让客户怀疑应用是否真的能用。
- 风险与回滚:只改 ui 显示逻辑,新增一个可选回调 `onBuildResult`;物化且 `appHidden: false`
的构建现在在卡片上也算已上线。回滚即 revert 本 PR。
-
先看:`ui/packages/app-shell/src/console/ai/AiChatPage.tsx`(handleBuildResult)、`ui/packages/plugin-chatbot/src/ChatbotEnhanced.tsx`(BuildProgressPanel
的上报 effect 与
buildIsLive)、`ui/packages/app-shell/src/console/ai/LiveCanvas.tsx`(标题三分支)。
- 两分钟验证:`cd ui && pnpm exec vitest run
packages/app-shell/src/console/ai/LiveCanvas.header-26469.test.tsx
packages/app-shell/src/console/ai/__tests__/AiChatPage.canvasHeader-26469.test.tsx`,预期
8 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
- **Preview header reads the build card (`LiveCanvas.tsx`,
`AiChatPage.tsx`).** `LiveCanvas` takes `appLabel` and `live`. The
header shows the label (falling back to the machine name), and when
`live` is true it reads 「运行预览 · LABEL」 / "Live app — LABEL" (new key
`console.ai.liveCanvasLive`), with no unlisted note whatever
`materialized` says. A materialized build that is not live keeps 「(未发布 ·
仅自己可见)」. `AiChatPage` keeps the card's answer per app name
(`buildResults`), fed only by `onBuildResult`, and hands it to both
canvas mounts (desktop split and mobile overlay). It is stored apart
from `canvasApp` because the card's effect runs before its host's: on a
reopened conversation the report arrives before `onBuildMaterialized`
opens the canvas. Cleared with the canvas on a conversation change.
- **The card hands its answer to the host (`ChatbotEnhanced.tsx`).** New
optional prop `onBuildResult({ appName, label?, live })`, passed to
`BuildProgressPanel`. The panel reports, from an effect, the very values
its title renders: `builtApp.name`, `checklist.app.label ?? appLabel`,
and `isDone && live`. It fires again whenever one of them changes, which
is how the header follows a publish. No second state derivation: the
header shows what the card shows.
- **A listed materialized build is live (`ChatbotEnhanced.tsx`
`buildIsLive`, `mapMessages.ts`).** `DraftReview` lifts the envelope's
`appHidden` when it is a boolean. `buildIsLive` also holds when the
draft review says `materialized: true` and `appHidden: false` (ADR-0045:
publish = live AND visible). `appHidden: true` adds nothing. What else
reads `buildIsLive`: only the finished card's `live` prop (its 「已搭建并上线」
vs 「已搭建」 title) and, through the panel, the new `onBuildResult` report.
Nothing else in the file reads it.
- **Progress items show labels (`mapMessages.ts`,
`ChatbotEnhanced.tsx`).** `extractBuildProgress` keeps a non-empty
string `label` on an item; `ChatBuildProgress.items` gains the optional
`label`; the panel renders `label` and falls back to the
`_sample`-stripped name. Until objectstack-ai/cloud#3310 adds `label` to
the frames, the panel looks as before.
- **Footnote (`chatbot.build.draftPosture`, every locale).** Names
Publish in the chat instead of Studio: zh 「之后的修改先存草稿,在对话里点「发布」即可上线。」, en
"Later changes are saved as drafts; click Publish here in the chat to
make them live." Each locale quotes its own `console.ai.publishDrafts`
label.
- Root changeset `.changeset/26469-ai-build-canvas-header-labels.md`:
`@object-ui/app-shell`, `@object-ui/plugin-chatbot`, `@object-ui/i18n`,
patch.
The draft tool card's 「发布 (N)」 block (shared with #26471) is untouched.
## Tests
New:
-
`plugin-chatbot/src/__tests__/ChatbotEnhanced.buildResultHeader-26469.test.tsx`
(9): a labelled item shows its label and an unlabelled one its name; the
stream reader keeps `label`; a published build reports `{label, live:
true}`; a drafted build reports `live: false` and reports `live: true`
after a rerender into published; the checklist label wins as on the
title; materialized + `appHidden: false` reads 「Built and live」 and
reports live; materialized + `appHidden: true` is not live;
`detectDraftResult` lifts `appHidden` only when present; the zh
footnote.
- `app-shell/src/console/ai/LiveCanvas.header-26469.test.tsx` (4,
shipped zh pack): published → label, no 未发布; materialized not live →
badge; no label → machine name; draft preview by label.
-
`app-shell/src/console/ai/__tests__/AiChatPage.canvasHeader-26469.test.tsx`
(4): materialized + card not live → canvas props `materialized`, not
live, label; card turns live → canvas turns live; a report before the
canvas opens still reaches it; another app's report leaves the canvas
alone.
Forced test edits:
-
`plugin-chatbot/src/__tests__/ChatbotEnhanced.buildChat-12724.test.tsx`
pinned the old zh posture sentence (「之后的修改先存草稿,由你在 Studio 发布。」); it now
pins the new one.
- `i18n/src/__tests__/de-quote-pairing-3876.test.ts` counts the de
pack's correctly paired 「„…“」 spans; the new de `draftPosture` adds one
(the chat's „Veröffentlichen“ button), so its two pins move 96 → 97 with
a line in each running tally, as every earlier addition did.
## Acceptance notes
- The header follows the card's `live`, which is the existing
result-card signal (published envelope, the package's pending-draft
count reaching 0, this chat's publish signal) plus the new
listed-materialized arm. When the card says live but the build was not
materialized (an auto-publish environment), the iframe still opens the
draft overlay (`?preview=draft`) as before; only the header text
changed. With no pending drafts the overlay shows the published app.
- The cloud half (frames carrying `label`, the cloud posture strings) is
objectstack-ai/cloud#3310. Nothing here fakes it.
## Verification
Everything at head `af7a3dae`, branched off `origin/main` `00a4593a`; no
file of this PR moved on `main` since (checked against `origin/main`
before opening).
<details><summary>Targeted vitest (7 files, 90 tests)</summary>
`cd ui && pnpm exec vitest run --maxWorkers=2` over the three new files
plus `ChatbotEnhanced.buildChat-12724.test.tsx`, `mapMessages.test.ts`,
`LiveCanvas.test.tsx`, `AiChatPage.previewDraft-12968.test.tsx`: `Test
Files 7 passed (7)`, `Tests 90 passed (90)`, lock VERDICT command-exit
0.
</details>
<details><summary>Package suites</summary>
- `pnpm --filter @object-ui/plugin-chatbot test --maxWorkers=2`: 85
files, 814 tests passed.
- `pnpm --filter @object-ui/i18n test --maxWorkers=2`: 87 files passed;
1353 tests passed, 13 skipped.
- `cd ui && pnpm exec vitest run --maxWorkers=2
packages/app-shell/src/console/ai/`: 45 files, 245 tests passed. The
whole app-shell suite (about 1,700 files) is left to CI `Test Core` /
`Test UI`; this is a declared narrowing to the directory the change
lives in.
</details>
<details><summary>Ablations (each restored, blob == HEAD, `git diff
HEAD` empty)</summary>
All through `scripts/ablation-replace.mjs` (anchor 1 → 0 on disk); ui
vitest resolves `@object-ui/*` to `src/`, so no build sits between
mutation and run.
1. `AiChatPage.tsx` `handleBuildResult` stores `live: false`:
`AiChatPage.canvasHeader-26469` 2 failed / 2 passed (the publish-update
and report-before-open cases).
2. `LiveCanvas.tsx` `displayName = appName` (label ignored):
`LiveCanvas.header-26469` 3 failed / 1 passed.
3. `LiveCanvas.tsx` live branch forced off: `LiveCanvas.header-26469` 1
failed / 3 passed (the published case).
4. `ChatbotEnhanced.tsx` `buildIsLive` listed-materialized arm never
matches: `ChatbotEnhanced.buildResultHeader-26469` 1 failed / 8 passed
(`appHidden: false` reads live).
</details>
<details><summary>Derived gates (`dispatch-gates --commands`,
35)</summary>
All 35 at `af7a3dae`, each `exit 0` (codes captured before any pipe);
`dispatch-gates --ran`: 35 derived, 35 run, 0 NOT-MEASURED (a derived
zero). `pnpm --dir ui exec vitest run scripts/`: 178 files passed, 2
skipped; 5097 tests passed.
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
Also run (outside the derived list): `ui` `check:i18n-keys`,
`check:i18n-drift`, `check:i18n-dead-keys`,
`check:i18n-designer-parity`, each exit 0.
</details>
<details><summary>Type check</summary>
`pnpm --filter @object-ui/plugin-chatbot --filter @object-ui/i18n
--filter @object-ui/app-shell run type-check` (both `tsc --noEmit` and
`tsc -p tsconfig.test.json` per package) after `turbo run build
--filter='@object-ui/app-shell^...'`: lock VERDICT command-exit 0.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmQw9ePD2KCzPfJGa6p)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7600209 commit a89074a
20 files changed
Lines changed: 624 additions & 30 deletions
File tree
- .changeset
- ui/packages
- app-shell/src/console/ai
- __tests__
- i18n/src
- __tests__
- locales
- plugin-chatbot/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1831 | 1831 | | |
1832 | 1832 | | |
1833 | 1833 | | |
| 1834 | + | |
| 1835 | + | |
| 1836 | + | |
| 1837 | + | |
| 1838 | + | |
| 1839 | + | |
| 1840 | + | |
| 1841 | + | |
| 1842 | + | |
| 1843 | + | |
| 1844 | + | |
| 1845 | + | |
| 1846 | + | |
| 1847 | + | |
| 1848 | + | |
| 1849 | + | |
1834 | 1850 | | |
1835 | 1851 | | |
1836 | 1852 | | |
| 1853 | + | |
1837 | 1854 | | |
1838 | 1855 | | |
1839 | 1856 | | |
| |||
2896 | 2913 | | |
2897 | 2914 | | |
2898 | 2915 | | |
| 2916 | + | |
2899 | 2917 | | |
2900 | 2918 | | |
2901 | 2919 | | |
| |||
2932 | 2950 | | |
2933 | 2951 | | |
2934 | 2952 | | |
| 2953 | + | |
| 2954 | + | |
2935 | 2955 | | |
2936 | 2956 | | |
2937 | 2957 | | |
| |||
2957 | 2977 | | |
2958 | 2978 | | |
2959 | 2979 | | |
| 2980 | + | |
| 2981 | + | |
2960 | 2982 | | |
2961 | 2983 | | |
2962 | 2984 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | | - | |
| 29 | + | |
30 | 30 | | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
31 | 42 | | |
32 | 43 | | |
33 | 44 | | |
| |||
73 | 84 | | |
74 | 85 | | |
75 | 86 | | |
76 | | - | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
77 | 97 | | |
78 | 98 | | |
79 | 99 | | |
80 | 100 | | |
81 | 101 | | |
| 102 | + | |
82 | 103 | | |
83 | 104 | | |
84 | 105 | | |
| |||
108 | 129 | | |
109 | 130 | | |
110 | 131 | | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
115 | 136 | | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
120 | 146 | | |
121 | 147 | | |
122 | 148 | | |
| |||
149 | 175 | | |
150 | 176 | | |
151 | 177 | | |
152 | | - | |
| 178 | + | |
153 | 179 | | |
154 | 180 | | |
155 | 181 | | |
| |||
0 commit comments