Skip to content

Commit 22a59ea

Browse files
committed
fix(plugin-chatbot): keep @objectstack/spec symbols out of the published build-progress typings (objectui#11988)
Naming BuildProgressPhase in ChatbotEnhanced's exported interface put an @objectstack/spec/ai reference into the published .d.ts that the package's ^17.0.0 floor cannot carry (check:spec-floors: floor-too-low, needs ^17.5.0). The exported phase union is spelled out instead, and a compile-time test holds it equal to the spec's BuildProgressPhase in both directions; the type-only spec import stays internal to mapMessages and is erased from the typings. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 04c64b0 commit 22a59ea

4 files changed

Lines changed: 39 additions & 16 deletions

File tree

‎.changeset/11988-build-verify-receiver.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ After `apply_blueprint` finishes, cloud's agent loop reports its verification ho
99

1010
- **The tree and the verification part are read apart, by part id.** The `build-verify` part never displaces the tree, whichever order the two parts arrive in. A message that carries only the tree maps and renders exactly as before.
1111
- **The build panel shows a verification line under the tree.** It reads "Checking the change… step N" while a hop runs, with the hop's tool name as its tooltip, and "Checked the change" once the `done` frame arrives. A `build-verify` part on a message that has no build tree is not drawn as a tree.
12-
- **Phases are read against the spec's vocabulary.** The receiver reads `BUILD_PROGRESS_PHASES` and `BUILD_PROGRESS_FRAME_TYPE` from `@objectstack/spec/ai` instead of restating them. A phase outside that vocabulary, or a frame without one, is now `unknown` and shows as a warning line ("Unknown build phase") on the tree header or on the verification line. It is no longer coerced to `structure`.
13-
- **Type change.** `ChatBuildProgress.phase`, reached through `ChatMessage['buildProgress']`, widens from `'structure' | 'data' | 'done'` to the spec's `BuildProgressPhase` plus `'unknown'`, and `ChatBuildProgress` gains an optional `verify` member: `{ phase, hop?, tool? }`.
12+
- **Phases are read against the spec's vocabulary.** The receiver's phase table and frame type are typed by `@objectstack/spec/ai`'s own `BuildProgressPhase` and `BUILD_PROGRESS_FRAME_TYPE`, so a phase the spec adds or drops fails the type check here instead of drifting. The spec's runtime module is not imported, because it would put its whole AI schema module on the console's first load. A phase outside the vocabulary, or a frame without one, is now `unknown` and shows as a warning line ("Unknown build phase") on the tree header or on the verification line. It is no longer coerced to `structure`.
13+
- **Type change.** `ChatBuildProgress.phase`, reached through `ChatMessage['buildProgress']`, widens from `'structure' | 'data' | 'done'` to `'structure' | 'data' | 'verify' | 'done' | 'unknown'`: the spec's phase vocabulary plus `'unknown'`, held equal to the spec's union by a compile-time test. `ChatBuildProgress` also gains an optional `verify` member, `{ phase, hop?, tool? }`. The published typings name no new `@objectstack/spec` symbol, so the package's spec range is unchanged.
1414
- **New language-pack keys, in all ten packs:** `chatbot.build.verifying`, `chatbot.build.verifyStep`, `chatbot.build.verified` and `chatbot.build.unknownPhase`.

‎packages/plugin-chatbot/src/ChatbotEnhanced.tsx‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import { SchemaRenderer } from '@object-ui/react';
2323
import { useObjectTranslation, useSafeTranslate } from '@object-ui/i18n';
2424
import { AlertCircle, ArrowRight, Copy, Check, RefreshCw, CornerDownLeft, Bot, Eye, GitCompareArrows, Rocket, Clock3, CheckCircle2, XCircle, Loader2, ShieldCheck, TriangleAlert, ClipboardList, HelpCircle, Table2, WifiOff, Sparkles, Hourglass } from 'lucide-react';
2525
import type { ChatStatus } from 'ai';
26-
import type { BuildProgressPhase } from '@objectstack/spec/ai';
2726
import {
2827
humanizeToolName,
2928
isRateLimitError,
@@ -158,15 +157,20 @@ export interface ChatChart {
158157
series: Array<{ dataKey: string; label?: string }>;
159158
}
160159

160+
/**
161+
* A build-progress frame's phase: `@objectstack/spec/ai`'s closed
162+
* `BuildProgressPhase` vocabulary, plus `unknown` for a value outside it, which
163+
* is surfaced as a warning — never as "Building" (objectui#11988). Spelled out
164+
* rather than imported so the published typings need no newer spec floor;
165+
* `buildVerify-11988.test.tsx` asserts at compile time that it equals the
166+
* spec's union, both ways.
167+
*/
168+
type ChatBuildPhase = 'structure' | 'data' | 'verify' | 'done' | 'unknown';
169+
161170
/** A reconciled snapshot of an in-flight app build (apply_blueprint). */
162171
export interface ChatBuildProgress {
163-
/**
164-
* Coarse phase, from `@objectstack/spec/ai`'s closed vocabulary: drafting
165-
* structure, generating sample data, or finished. `unknown` is a value
166-
* outside that vocabulary, surfaced as a warning — never as "Building"
167-
* (objectui#11988).
168-
*/
169-
phase: BuildProgressPhase | 'unknown';
172+
/** Coarse phase: drafting structure, generating sample data, or finished. */
173+
phase: ChatBuildPhase;
170174
/** Human label for the app being built (for the panel header). */
171175
appLabel?: string;
172176
/** Artifacts drafted so far, cumulative. */
@@ -187,7 +191,7 @@ export interface ChatBuildProgress {
187191
* beside the tree (objectui#11988): `verify` while a hop runs, `done` once
188192
* the loop exits. `hop` and `tool` are the spec frame's own fields.
189193
*/
190-
verify?: { phase: BuildProgressPhase | 'unknown'; hop?: number; tool?: string };
194+
verify?: { phase: ChatBuildPhase; hop?: number; tool?: string };
191195
}
192196

193197
/**

‎packages/plugin-chatbot/src/__tests__/buildVerify-11988.test.tsx‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@
2121
import '@testing-library/jest-dom/vitest';
2222
import { describe, it, expect, vi, afterEach } from 'vitest';
2323
import { render, screen, cleanup } from '@testing-library/react';
24-
import { BUILD_PROGRESS_PHASES } from '@objectstack/spec/ai';
24+
import { BUILD_PROGRESS_PHASES, type BuildProgressPhase } from '@objectstack/spec/ai';
2525
import { I18nProvider } from '@object-ui/i18n';
26-
import { ChatbotEnhanced } from '../ChatbotEnhanced';
26+
import { ChatbotEnhanced, type ChatBuildProgress } from '../ChatbotEnhanced';
2727
import { uiMessageToChatMessage } from '../mapMessages';
2828

29+
type Assert<T extends true> = T;
30+
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
31+
? true
32+
: false;
33+
2934
const ITEMS = [
3035
{ type: 'object', name: 'customer' },
3136
{ type: 'view', name: 'customer.list' },
@@ -57,6 +62,18 @@ function renderIn(language: string, parts: Array<Record<string, unknown>>) {
5762

5863
afterEach(cleanup);
5964

65+
describe('the published phase union IS the spec vocabulary plus `unknown` (objectui#11988)', () => {
66+
it('is pinned at compile time, in both directions', () => {
67+
// A `tsc` error, not a runtime failure: compiled by this package's
68+
// `tsconfig.test.json`. A phase the spec adds or drops turns it red.
69+
type _TreePhaseIsSpec = Assert<Equal<Exclude<ChatBuildProgress['phase'], 'unknown'>, BuildProgressPhase>>;
70+
type _VerifyPhaseIsSpec = Assert<
71+
Equal<Exclude<NonNullable<ChatBuildProgress['verify']>['phase'], 'unknown'>, BuildProgressPhase>
72+
>;
73+
expect(true).toBe(true);
74+
});
75+
});
76+
6077
describe('the receiver keeps the tree and the build-verify part apart (objectui#11988)', () => {
6178
it('a verify hop never displaces the finished tree', () => {
6279
const out = map([TREE, verifyPart({ phase: 'verify', hop: 1, tool: 'verify_build' })]);

‎packages/plugin-chatbot/src/mapMessages.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -826,14 +826,16 @@ function extractSources(parts: AnyPart[]): ChatSource[] | undefined {
826826

827827
/**
828828
* The build-progress frame vocabulary of `@objectstack/spec/ai`, read through
829-
* its TYPES (objectui#11988). Both tables are typed by the spec's own
829+
* its TYPES (objectui#11988). Both constants are typed by the spec's own
830830
* declarations, so the compiler refuses a value the spec does not declare and,
831831
* for the phases, requires a row for every phase it does: neither can drift
832832
* from the vocabulary. The spec's runtime `BUILD_PROGRESS_PHASES` is not
833833
* imported: its module does not tree-shake, and a value import of it moved the
834834
* console's eager closure about 27 KB gzipped over budget (a one-time reading
835-
* of `pnpm check:eager-closure` against `fff07fbba`, objectui#11988). The test
836-
* beside the panel reads the runtime array and pins every member.
835+
* of `pnpm check:eager-closure` against `fff07fbba`, objectui#11988). The
836+
* type-only import is erased from the published typings, so it asks nothing of
837+
* the package's spec floor. `buildVerify-11988.test.tsx` reads the runtime
838+
* array and pins every member.
837839
*/
838840
const BUILD_FRAME_TYPE: typeof BUILD_PROGRESS_FRAME_TYPE = 'data-build-progress';
839841
const BUILD_PHASES: Record<BuildProgressPhase, true> = { structure: true, data: true, verify: true, done: true };

0 commit comments

Comments
 (0)