Skip to content

fix(plugin-chatbot,app-shell): an incremental edit (kind: 'edit') is never read as a whole-app build (objectui#10109) - #10441

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10109-edit-envelope-not-built-app
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10109-edit-envelope-not-built-app

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10109

Clause-②: yes — DraftReview is exported from @object-ui/plugin-chatbot's entry, and this PR adds an optional kind field to it. The widening is small, but it is on the published surface.

What changed

An apply_edit result says kind: 'edit' on its envelope, and its drafted[] can list the app artifact the edit re-staged: an add_object op re-stages it for the nav merge. An apply_blueprint build carries no kind. Every reader that asks "did this finish a whole-app build?" now uses the producer's kind to tell the two apart, instead of guessing a build from the staged artifact types. An envelope without kind behaves exactly as before. autoPublishable is not read as a second whole-app signal.

Round 1 (packages/plugin-chatbot/src/mapMessages.ts):

  • detectBuiltAppPackage returns undefined for an envelope that says kind: 'edit', before it looks at drafted[]. This covers both the drafted posture and the auto-publish (status: 'published') posture.

Round 2 (the seat's answer A on the card):

  • DraftReview gains an optional kind?: 'edit'. detectDraftResult fills it from the envelope. It declares only the value its readers act on, so an envelope that says anything else leaves it unset. The edit still gets its draft card, its items and its packageId, so one-click publish still works.
  • buildProgressFromDraftReview builds no finished "Built X" panel for a draft review whose kind is 'edit'. A reloaded edit now shows what it showed live.
  • packages/app-shell/src/console/ai/AiChatPage.tsx: the draft-review fallback in the builtPackageId memo no longer moves an edit to Studio. It reads kind straight off tool.draftReview, which round 3 types as DraftReview.
  • packages/app-shell/src/hooks/useChatConversation.ts: CachedDraftReview and draftReviewToCachedResult keep kind, so the envelope rebuilt for a cache-fallback reload still says kind: 'edit', and the round-1 guard still applies.

Round 3 (after the contract review of fb2a20e):

  • ChatToolInvocation.draftReview in packages/plugin-chatbot/src/ChatbotEnhanced.tsx, which is published as ChatbotEnhancedToolInvocation['draftReview'], is now typed as DraftReview instead of an inline copy of its fields. The two copies were field-identical until round 2 added kind, which left the published tool-invocation type under-declaring what the runtime delivers. The copy's field docs move onto DraftReview, so the published type keeps them. The DraftReview upcast in AiChatPage.tsx became redundant and was dropped. The DraftReview.kind doc now names the readers that actually check it. All of this is type and comment only: the emitted JavaScript of AiChatPage.tsx, ChatbotEnhanced.tsx and mapMessages.ts is byte-identical between fb2a20e and f7eaadc (TypeScript transpileModule with comments removed, compared by SHA-256).

Tests: packages/plugin-chatbot/src/__tests__/mapMessages.builtAppEditEnvelope-10109.test.ts (extended), packages/app-shell/src/hooks/__tests__/useChatConversation.editKindRoundTrip-10109.test.ts (new), and packages/app-shell/src/console/ai/__tests__/AiChatPage.builtTransition.test.tsx (three new cases; its existing apply_blueprint reopen, auto-publish and live-build pins are the controls). Changesets: .changeset/10109-edit-envelope-not-built-app.md (@object-ui/plugin-chatbot: minor, for the added type field) and .changeset/10109-app-shell-edit-not-built-moment.md (@object-ui/app-shell: patch).

Red, then green

The pins were committed on their own first (aa52689) and run against the source as it was before round 2: Tests 5 failed | 13 passed (18). The five red cases:

  • the draft review carries kind: 'edit';
  • a reloaded edit builds no done panel;
  • the cache round trip keeps kind;
  • reopening a drafted-posture edit stays out of Studio;
  • a live edit completion stays out of Studio.

Every apply_blueprint control was green. The published-posture reopen was also green, because the round-1 guard already covered it. With the fix (fb2a20e) the result is Tests 18 passed (18).

Then each fix site was removed on its own at fb2a20e, using ablation-replace.mjs --delete. Every mutation was proven on disk (anchor x1 to x0, blob changed), and every restore was proven (blob equals HEAD, git diff HEAD empty):

site removed result red cases
detectDraftResult lifting kind 4 failed, 14 passed draft-review kind, reload panel, cache round trip, drafted reopen
buildProgressFromDraftReview guard 2 failed, 16 passed reload panel, cache round trip (its reload-panel assertion)
AiChatPage fallback kind check 2 failed, 16 passed drafted reopen, live completion
cache keeps kind 1 failed, 17 passed cache round trip
round-1 guard in detectBuiltAppPackage (re-run after round 2) 5 failed, 13 passed both unit postures, both reopen postures, cache round trip

In every run the only red cases were edit cases, and no apply_blueprint control went red.

Reverse check on the rebuilt .d.ts: app-shell's test project resolves @object-ui/plugin-chatbot through dist. In a typed probe there, DraftReview with kind: 'edit' compiled (exit 0), and kind: 'build' failed with TS2322: Type '"build"' is not assignable to type '"edit"'. Both were restored.

Local verification (HEAD fb2a20e, with origin/main 0d39dc6 merged in; merged, not rebased)

  • turbo run build --filter="@object-ui/app-shell^..." --concurrency=2: 28 successful, 28 total.
  • pnpm --filter @object-ui/plugin-chatbot type-check and pnpm --filter @object-ui/app-shell type-check (each is tsc --noEmit && tsc -p tsconfig.test.json): exit 0 each. The test projects' --listFilesOnly includes the new and extended pins.
  • pnpm exec vitest run packages/plugin-chatbot/ plus the 34 test files outside plugin-chatbot that name AiChatPage or useChatConversation (app-shell, apps/console, components, i18n): Test Files 83 passed (83), Tests 856 passed (856). This is a narrowed run: test files that reach these modules only indirectly, without naming them, are not in it. The full app-shell suite is left to CI.
  • check-changeset-presence: "6 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s)".
  • check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed, check-control-bytes, check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-test-path-roots, check-shell-escape-residue, check:esm-specifiers: exit 0 each.
  • check:new-line-citations: "VERDICT new-cross-file-line-citations: 0 new citation(s)".
  • check-changeset-claims: exit 0 (report-only). It flags .changeset/6169-chatbot-authoring-face-type.md (names AiChatPage.tsx, about the autoResponse fields) and .changeset/8214-chatbot-anypart-state-widen.md (names mapMessages.ts, about AnyPart.state). I read both, and both still hold.
  • check-readme-exports: NOT MEASURED. It needs every package built, and the run said "the population COLLAPSED -- this run proves nothing". Left to CI.
  • Lint on the six changed TS files: 0 errors. The 35 warnings are all in AiChatPage.tsx and useChatConversation.ts, and the same two files at 0d39dc6 give the same 35. None is on a changed line. Linting is not type-aware (empty parserOptions), so this diff cannot change the verdict on any untouched file. The full pnpm lint is left to CI.

Round 3 verification (HEAD f7eaadc, with origin/main ff14e29 merged in; merged, not rebased)

  • Typed probe through the published type: in app-shell's test project, which resolves @object-ui/plugin-chatbot through dist, a function that takes a ChatbotEnhancedToolInvocation and returns tool.draftReview?.kind failed at fb2a20e with TS2339: Property 'kind' does not exist on type '{ items: ... and compiled at f7eaadc (exit 0). The same probe bytes were used both times, and it was removed after each run.
  • turbo run build of the app-shell closure (28 successful) at the old head, then of plugin-chatbot (9 successful) at the new head. The rebuilt dist/ChatbotEnhanced.d.ts declares draftReview?: DraftReview.
  • pnpm --filter @object-ui/plugin-chatbot type-check and pnpm --filter @object-ui/app-shell type-check: exit 0 each.
  • The 18-case pin set: Tests 18 passed (18). The AiChatPage fallback site was ablated again because the declaration it reads through changed: Tests 2 failed | 16 passed (18) (drafted reopen, live completion), then restored. The other sites' lines are unchanged, so their ablations were not re-run.
  • Gates, exit 0 each: check-changeset-presence ("7 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s)"), check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed, check-package-self-import, check-control-bytes, check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-test-path-roots, check-shell-escape-residue, check:esm-specifiers, check:new-line-citations (0 new). check-changeset-claims (report-only) now also flags .changeset/6687-chatbot-surface-authorable.md, which names ChatbotEnhanced.tsx about ChatbotSurface; that paragraph still holds.
  • Lint on the three changed source files: 0 errors, with the same warning counts as at fb2a20e (ChatbotEnhanced.tsx 10, AiChatPage.tsx 31, mapMessages.ts 0).

Acceptance notes

  • Standing gap. The cloud envelope shape is NOT MEASURED (cloud unreachable). Every kind read here (apply_edit answering kind: 'edit', apply_blueprint carrying none) rests on the producer seat's filing on the card. Whether the producer change is live in the cloud repository, and which .objectui-sha it pins, is also NOT MEASURED. Nothing in objectui or in the installed @objectstack/* 17.4.0 declares kind on the chat tool-result envelope.

Generated by Claude Code

…' envelope as a whole-app build

apply_edit now lists every artifact it staged in drafted[], including the
app artifact an add_object op re-stages for the nav merge. The whole-app
test "drafted[] contains an app entry" was exact only while apply_blueprint
was its sole producer. The producer declares the difference on the
envelope (kind: 'edit' on apply_edit, absent on apply_blueprint), so
detectBuiltAppPackage returns undefined for an edit envelope before the
drafted[] scan. detectDraftResult is untouched: the edit keeps its draft
card and its packageId.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 3 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6169-chatbot-authoring-face-type.md

  • names packages/app-shell/src/console/ai/AiChatPage.tsx → packages/app-shell/src/console/ai/AiChatPage.tsx — edited by this change

    • showTimestamp, userAvatarUrl, userAvatarFallback, assistantAvatarUrl, assistantAvatarFallback, maxHeight — display fields. - autoResponse, autoResponseText, autoResponseDelay — the local auto-response (demo/playground) fields, already live via a real consumer (packages/app-shell/src/console/ai/AiChatPage.tsx). - onSend — the send-callback, now typed on the published authoring face rather than against the plugin's internal runtime message type. Its messages element is the authoring ChatMessage widened by the three runtime-only approval states a chat runtime hands back (objectui#10018).

.changeset/6687-chatbot-surface-authorable.md

  • names ChatbotEnhanced.tsx → packages/plugin-chatbot/src/ChatbotEnhanced.tsx — edited by this change

    content/docs/plugins/plugin-chatbot.mdx's Properties table listed surface ('card' | 'plain', "bordered panel or a frameless full-page workspace"), but the key had zero read points: none of the three ComponentRegistry.register('chatbot*', ...) sites in renderer.tsx forwarded it, and ChatbotSchema did not declare it. surface was real only as a prop of the React component — ChatbotEnhanced.tsx defines ChatbotSurface, defaults it to 'card', and branches six layout decisions off isPlainSurface — so it was reachable by a hand-written React host and by nobody writing metadata. An author who wrote surface: 'plain' got the 'card' default, with no error and no signal.

.changeset/8214-chatbot-anypart-state-widen.md

  • names mapMessages.ts → packages/plugin-chatbot/src/mapMessages.ts — edited by this change

    mapMessages.ts's local AnyPart is written to absorb whatever a producer hands the mapper — every other member is string / unknown — but state was typed against the OUTPUT contract (ChatToolInvocation['state'], the tool-invocation lifecycle). The AI SDK's own text and reasoning parts carry state: 'streaming' | 'done', which is not in that union, so the deliberately-permissive input interface was on that one property STRICTER than the union it exists to absorb and the whole UIMessage[] assignment was refused (TS2345). An app that followed the README and drove useChat() itself had to add an as never / as any of its own, permanently disabling checking on that seam.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 94436f42a (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1387 pending declaration(s) that publish a body (1964 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3044.0 KB 3104.5 KB
Main entry chunk (gzip) 148.4 KB 350 KB
Entry file index-Bb-G_Kb_.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 543.20KB 129.69KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.84KB 65.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 72.54KB 20.26KB
plugin-chatbot (index.js) 198.29KB 47.19KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.94KB 67.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.47KB 41.53KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.36KB 28.24KB
plugin-map (index.js) 22.05KB 7.14KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.72KB 8.96KB
plugin-tree (index.js) 10.74KB 3.76KB
plugin-view (index.js) 85.86KB 21.38KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…d as a whole-app build (objectui#10109)

Pins added ahead of the change: the draft review carries the producer's kind, a reloaded edit builds no done panel, the cache round trip keeps kind, and neither a reopened nor a live apply_edit moves the conversation to Studio. The apply_blueprint cases are the controls.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
…eview so an apply_edit is never a whole-app build (objectui#10109)

DraftReview gains an optional kind 'edit' that detectDraftResult lifts from the envelope. Three readers that answered whole-app from the staged set now respect it: buildProgressFromDraftReview builds no done panel for an edit, the builtPackageId draft-review fallback in AiChatPage does not move an edit to Studio, and the chat cache keeps kind when it re-mints the draft envelope, so the detectBuiltAppPackage guard survives a cache-fallback reload. An envelope without kind behaves as before; autoPublishable is not read as a second signal.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3044.4 KB 3104.5 KB
Main entry chunk (gzip) 148.4 KB 350 KB
Entry file index-KOEBnTHv.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 543.20KB 129.69KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.84KB 65.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 74.76KB 20.82KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.94KB 67.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.47KB 41.53KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.17KB 28.21KB
plugin-map (index.js) 22.05KB 7.14KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.74KB 3.76KB
plugin-view (index.js) 85.86KB 21.38KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fb2a20e514c0f28fd1b8bd32dcf66fa6a6d32c4c

① Derived judgments

Diff read. git diff $(git merge-base refs/review/main refs/review/pr-10441) refs/review/pr-10441 --stat: 8 files, +394/−5; merge-base 0d39dc6; 5 commits on the branch (be57267, a99e6e3, acf3b89 merge of main, aa52689 pins-alone, fb2a20e fix). Source changes are confined to packages/plugin-chatbot/src/mapMessages.ts (+28/−2), packages/app-shell/src/console/ai/AiChatPage.tsx (+9/−2), packages/app-shell/src/hooks/useChatConversation.ts (+8/−0), plus 3 test files and 2 changesets.

DraftReview.kind?: 'edit' on the published surface. Reachable: packages/plugin-chatbot/package.json exports["."].types = ./dist/index.d.ts; src/index.tsx lines 369-370 export type { DraftReview, … } from './mapMessages'. Measured against npm: npm pack @object-ui/plugin-chatbot@latest gives 17.6.0; its dist/index.d.ts:99 re-exports DraftReview and dist/mapMessages.d.ts:59-93 declares 9 fields (items, summary, packageId, autoPublishable, failedCount, materialized, verification, issues, nextSteps) with no kind, and dist/index.js has 0 occurrences of a kind/edit read. The PR adds exactly one optional field plus a doc block; no other field of the interface is touched (the interface hunk is +11 lines, all comment except kind?: 'edit';). Additive, optional: no existing importer can break. Declaring only 'edit' is right by the card's premise (edit says kind: 'edit', build carries no kind); detectDraftResult lifts it only when obj.kind === 'edit', so an unknown future value is dropped rather than misread. parseResultEnvelope (mapMessages.ts 134-168) returns the whole parsed object, so kind survives the Vercel text-wrapper peel.

No accidental narrowing of the no-kind (apply_blueprint) path. All five sites are strict comparisons against the one literal: detectBuiltAppPackage if (obj.kind === 'edit') return undefined; placed before the status check (so drafted and published postures alike); detectDraftResult spreads kind only when === 'edit'; buildProgressFromDraftReview if (draft.kind === 'edit') return undefined; after the items check; AiChatPage fallback dr.kind !== 'edit' (undefined passes through); cache ...(dr.kind === 'edit' ? { kind: 'edit' } : {}). An envelope without kind takes exactly the pre-PR branches. The apply_blueprint controls are pinned in all three test files (unit drafted+published, page reopen drafted+published, cache round trip).

Readers honour kind without a tolerant second signal. git grep -n autoPublishable at head outside tests: only the pre-existing detectDraftResult lift and the ChatbotEnhanced auto-publish effect; none of the five new sites reads it. The cache path is sound end to end: the only two draftReview assignment sites in non-test code (mapMessages.ts:752 live, AiChatPage.tsx:217 hydration) both call detectDraftResult, so the value sanitizeChatMessagesForCache hands to draftReviewToCachedResult carries kind; the rebuilt envelope re-parses through detectDraftResult and detectBuiltAppPackage(tool.result) on reload. The live mapper puts the raw output on tool.result (extractToolInvocations: const result = p.output ?? p.result; … result,), so the round-1 guard applies live as well as hydrated. Mixed threads keep #5799's kill criterion: the builtPackageId memo walks newest-first and an edit after a blueprint build is skipped, so the earlier build still transitions.

Whole-app readers left that ignore kind. git grep -n -E "draftReview|DraftReview|detectBuiltAppPackage|buildProgressFromDraftReview" over packages/** and apps/** (non-test, non-dist): hits only in AiChatPage.tsx, useChatConversation.ts, ChatbotEnhanced.tsx, index.tsx, mapMessages.ts; apps/ has 0 (the tree-wide grep for the two functions lists 8 files, all under packages/ or .changeset/). The remaining type === 'app' inference sites that still do not consult kind: ChatbotEnhanced.tsx onDraftArtifacts effect (appSegment from dr.items?.some(type === 'app')) feeding AiChatPage.handleDraftArtifacts (artifacts.find(a.type === 'app') then setCanvasApp(prev ⇒ prev ?? …)); the BuildProgressPanel appSegment lookup (only rendered when buildProgress exists, which a reloaded edit no longer has); the draft-card Preview button; the materialized effect (gated by materialized). These are preview/routing affordances (ADR-0037 "surface every draft artifact", ADR-0048 route on package id), not readers of "did a whole-app build finish"; after cloud#1960 an edit that re-stages the app will open or route the live canvas to that app, which is the producer-accepted cost, not the card's built-moment symptom. Observation only: the new DraftReview.kind doc comment says "every reader that asks 'is this a whole-app build?' checks this", which is true of the three built-moment readers and overstated for nothing else; no defect.

Declared residual: ChatToolInvocation.draftReview in ChatbotEnhanced.tsx (lines 272-307) repeats DraftReview's 9 fields inline without kind; the published dist/ChatbotEnhanced.d.ts:182-… is the same. const dr: DraftReview | undefined = tool.draftReview; compiles because the two object types are mutually assignable (a missing optional property is allowed in either direction), so "upcast" is loose wording, not an unsafe assertion. It is type-sound in the failure direction: a value that lacks or misspells kind reads !== 'edit' and takes the pre-PR branch, so type-blindness can never suppress a real build. Where it can mislead: this is the first time the two declarations diverge (they were field-identical), so on the published surface ChatbotEnhancedToolInvocation['draftReview'] now under-declares what the runtime delivers; a consumer reading tool.draftReview.kind through that type gets a property-does-not-exist error, and a fresh literal carrying kind gets an excess-property error (the PR's own page test avoids this only because its harness types toolInvocations?: unknown[]). The app-shell mirror CachedDraftReview was kept in sync, so the inline declaration is the only stale copy. Judgment: acceptable to land without it, since it is additive, breaks no existing compile, is runtime-consistent, fails toward old behaviour, and was scoped out by the seat's decision A and declared in the PR's Acceptance notes; but it is a genuine published-type inconsistency and needs the one-line type-only follow-up (kind?: 'edit' on the inline declaration, or better draftReview?: DraftReview so the duplicate cannot drift again). Recorded as a boundary flag below, not a stop.

② Semver level

  • .changeset/config.json at head: one fixed group of 40 packages containing both @object-ui/plugin-chatbot and @object-ui/app-shell; the highest declared level bumps the whole group. Pending changesets on refs/review/main already declare minor for plugin-chatbot (.changeset/10018-authoring-state-sheds-approval.md) and for app-shell (10042, 10059, 10129), so the next release is a group minor regardless of this PR; the PR's level choice affects only which CHANGELOG section its entry lands in.
  • AGENTS.md 版本号策略 (lines 257-263) forbids major only and is silent on minor-vs-patch for an additive exported field. Precedent: plugin-chatbot CHANGELOG 17.5.0 "Minor Changes" carries the exported-type changes 3256b14 (ChatMessage unification) and eec2e4f (ObjectChatMessage). @object-ui/plugin-chatbot: minor for a new field on an exported type is the conventional reading and matches that precedent. Changeset Bump Policy and Changeset Fixed Group Check checks: success.
  • @object-ui/app-shell: patch is right: CachedDraftReview is a non-exported interface, the cached envelope gains one optional key (older caches without it parse as before), and the builtPackageId change is behaviour only.
  • Prose truth, checked line by line against the diff: the plugin-chatbot changeset's three bullets (guard before the status check hence both postures; kind lifted by detectDraftResult; no done panel for kind: 'edit') and its closing sentences (no-kind envelope unchanged; only 'edit' declared) are all true. The app-shell changeset (fallback skips kind: 'edit'; cache keeps kind; apply_blueprint moves to Studio as before) is true. The pending .changeset/built-transition-published-5799.md, which names detectBuiltAppPackage, remains true (the guard adds a condition, removes none); Changeset Claim Re-read: success. check-changeset-presence count "6 source files of 2 released packages, 2 changesets" matches the file list (3 src + 3 tests under src/).

③ Boundary flags

  • Unmeasured premise (cloud 403). What I could measure instead: npm pack @objectstack/spec@latest (17.4.0) names apply_edit exactly once (a tool-name string at dist/system/index.mjs:28611) and declares no result envelope and no kind; @objectstack/client@17.4.0 has 0 hits for apply_edit and 0 for kind: 'edit'; the objectui checkout has no node_modules/@objectstack to read. If the premise were false: (a) the producer emits no kind: the PR is inert, the card's symptom persists, no regression; (b) the producer also emits kind: 'edit' on apply_blueprint: the Studio 工作台增量一:建成时刻转场 — 蓝图确认后路由进 /studio 工作台(冷启动全屏面保留)(cloud#1609) #5799 Studio transition and the Built panel are suppressed for real builds, a regression that only the no-kind controls would miss; (c) any other spelling: inert. No objectui envelope reader used kind before this PR (git grep obj.kind non-test: only the new sites; ReplayOutcome.kind and detectAuthoringVerdict are objectui's own return shapes), so there is no collision inside this repo.
  • Dispatched PR posture. The card carries Claim: 5824640321, so AGENTS.md's rule ② keeps it at draft until the dispatch seat flips it; draft: true on the PR is the correct state. Governed Surface Queue Guard: success, so rule ① is not triggered.
  • Checks on the head, read at 2026-09-25T01:53Z. 43 check runs: 39 success, 3 skipped (two coverage shards, dependabot), 1 still running at that poll: Spec Main Shape Gate. Lint, Type Check, Test (shard 1/8) through (8/8), Test, Test (dist pins), Build & E2E, README Export Check, Changeset Bump Policy, Changeset Claim Re-read: all success. This closes the dev's declared local narrowing (34 named app-shell test files; check-readme-exports NOT MEASURED locally). The Spec Main Shape Gate conclusion was not re-polled after 01:53Z; it is reported as in progress, not as passed.
  • mergeable_state: behind. main advanced 4 commits since the merge-base (0d39dc6 → 3632060); git diff --stat 0d39dc6 refs/review/main -- over the 6 touched source/test files and .changeset/config.json is empty, so there is no textual overlap; the merge queue rebuilds on current main.
  • Residual to close after landing (not a stop): the inline ChatToolInvocation.draftReview declaration in packages/plugin-chatbot/src/ChatbotEnhanced.tsx lacks kind, leaving the published ChatbotEnhancedToolInvocation type inconsistent with DraftReview (details in ①). One-line, type-only; the dev's out_of_scope_findings names this review as the carrier for authorizing it.
  • Nits: the PR title is still round 1's (fix(plugin-chatbot): detectBuiltAppPackage …) while the body and scope are round 2's two-package change, so the squash title will under-describe the app-shell half; the DraftReview.kind doc comment's "every reader" is broader than the code.
  • Delivery note. The earlier run's SubagentHandback calls were refused by the permission system (session not in auto mode); no finding was left unfinished, and no GitHub write of any kind was made in either run.

Implemented-by: claude/issue-10109-edit-envelope-not-built-app
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

@objectstack-fleet objectstack-fleet Bot changed the title fix(plugin-chatbot): detectBuiltAppPackage does not read a kind:'edit' envelope as a whole-app build (objectui#10109) fix(plugin-chatbot,app-shell): an incremental edit (kind: 'edit') is never read as a whole-app build (objectui#10109) Sep 25, 2026
…he published type declares kind (objectui#10109)

The inline copy of DraftReview's fields on ChatToolInvocation.draftReview stopped matching when DraftReview gained the producer's optional kind, so ChatbotEnhancedToolInvocation under-declared what the runtime delivers. It now references DraftReview; the copy's field docs move onto DraftReview so the published type keeps them. AiChatPage's DraftReview upcast is redundant and is dropped (type-only, same emitted code). The DraftReview.kind doc names the readers that actually check it.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3045.2 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-B85BNZMz.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 543.20KB 129.69KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.84KB 65.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.22KB 14.56KB
plugin-charts (index.js) 74.76KB 20.82KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 261.04KB 67.97KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.25KB 41.46KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.17KB 28.21KB
plugin-map (index.js) 21.83KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.90KB 21.97KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f7eaadc9a2c8e4aaeb95a2a22a31fbc7cbf63343

① Derived judgments

Refs and delta. git rev-parse refs/review/pr-10441 = f7eaadc9a2c8… (matches; the branch has not moved). Merge-base with refs/review/main (0348bc9) = ff14e29. Full diff: 9 files, +415/−41; REST file list: the same 9. Delta since the earlier PASS head: 0811a11 (merge; parents fb2a20e + ff14e29) and f7eaadc (4 files: the plugin-chatbot changeset +1 bullet; AiChatPage.tsx drops one type import, two comment lines and one annotation; ChatbotEnhanced.tsx 7+/37−; mapMessages.ts 25+/3−). Evil-merge check: git diff ff14e29 0811a11 --stat lists exactly the PR's 8 pre-round-3 files (394+/5−), so the merge of main touched no chat file and brought nothing else onto the branch.

DraftReview.kind?: 'edit'. Reachable from the published entry: package.json exports["."].types = ./dist/index.d.ts; src/index.tsx:370 export type { DraftReview … } from './mapMessages'. Published @object-ui/plugin-chatbot@17.6.0 (npm pack, read-only): dist/index.d.ts:99 already exports DraftReview; its dist/mapMessages.d.ts has the 9 pre-PR members and no kind. Declaring only 'edit' matches the seat's decision A (comment 5825082826) and the card's filing (kind: 'edit' on apply_edit, absent on apply_blueprint); detectDraftResult lifts it only when obj.kind === 'edit', so any other spelling is dropped rather than mis-typed. Nothing else on the type changed: DraftReview at merge-base vs head, member by member, is the same 9 members with the same optionality plus kind?: 'edit'; the remaining round-3 delta on the interface is doc comments only.

ChatToolInvocation.draftReview retyped as DraftReview. The inline copy removed by the @@ -268,43 +268,13 @@ hunk had items, summary?, packageId?, autoPublishable?, failedCount?, materialized?, verification?, issues?, nextSteps?, with member types literally identical to merge-base DraftReview (items element { type: string; name: string }, verification { errors: number; warnings: number }, issues element { severity: 'error' | 'warning'; code: string; message: string; fix?: string }, nextSteps string[]). The retyping is therefore exactly "old shape plus optional kind": a widening on read, no narrowing of any member a consumer could observe. DraftReview and ChatbotEnhancedToolInvocation are exported from the same entry (index.tsx:304 and :370; published index.d.ts:63 and :99), so a consumer's declaration emit resolves it. dts emit is vite-plugin-dts with createDtsFailOnTypeErrors; the inline type DraftReview import is the right form for it. The AiChatPage.tsx upcast (const dr: DraftReview | undefined = tool.draftReview) is dropped in f7eaadc and dr.kind still compiles (CI Type Check success); hydratedMessagesToChatMessages builds ChatbotEnhancedToolInvocation[] and assigns detectDraftResult's DraftReview straight onto draftReview, which is now an exact match instead of an extra-property pass.

Emitted-JS identity (PR-body claim reproduced, not taken on trust). git show fb2a20e:… and f7eaadc:… of the three source files into scratch, typescript@5.9.3 transpileModule (ES2020, ESNext modules, react-jsx, removeComments), sha256 prefix: mapMessages.ts cc77a539e0e5a761 on both; ChatbotEnhanced.tsx 8a02d255fb2c7b8c on both; AiChatPage.tsx 1e5c11c922422574 on both. All IDENTICAL, and the hashes equal the ones the PR body cites. Round 3 is type and comment only.

No-kind path (apply_blueprint) unchanged. Every new branch is gated on the literal 'edit': detectBuiltAppPackage if (obj.kind === 'edit') return undefined; placed before the status check (so both drafted and published postures); detectDraftResult ...(obj.kind === 'edit' ? { kind: 'edit' as const } : {}); buildProgressFromDraftReview if (draft.kind === 'edit') return undefined;; AiChatPage dr.kind !== 'edit' &&; cache ...(dr.kind === 'edit' ? { kind: 'edit' } : {}). An envelope with no kind, or any other value, takes the pre-PR path at every site. No accidental narrowing of whole-app detection found.

Reader sites, each honouring kind with no tolerant second signal. (1) detectBuiltAppPackage: the kind guard only; autoPublishable not read. (2) detectDraftResult: lifts kind. (3) buildProgressFromDraftReview: kind guard; it feeds both uiMessageToChatMessage (mapMessages.ts:967) and app-shell hydration (AiChatPage.tsx:275), so live and reloaded paths agree. (4) AiChatPage builtPackageId memo: raw-envelope detector first, then the draft-review fallback with the kind check. (5) useChatConversation.ts: CachedDraftReview.kind?: 'edit' and draftReviewToCachedResult re-mints kind; sanitizeChatMessagesForCache hands tool.draftReview to it whole, so nothing else needs to pick the field. Grep at head (git grep -E "draftReview|DraftReview|detectBuiltAppPackage|buildProgressFromDraftReview" refs/review/pr-10441 -- packages/ apps/, non-test) leaves no whole-app reader ignoring kind: the other type === 'app' sites are not "did a whole-app build finish" tests. ChatbotEnhanced.tsx:1668 is the auto-publish moment keyed on autoPublishable (backend intent, unchanged); :1707 and :3147 derive the preview appSegment (a routing package id, honest for an edit that re-staged the app); :1732 is gated on materialized; :2341 is the draft-card Preview button, which needs an app artifact to preview; :4090 lives inside BuildProgressPanel, which only renders when a buildProgress exists, now withheld for an edit on reload. AiChatPage.tsx:1655 handleDraftArtifacts opens the ADR-0037 canvas when any drafted artifact is an app: a preview, not the built moment, and the intended surface for a draft that touches the app. apps/ has no reader (apps/console/src/components/FormPage.tsx kind: 'edit' is the form target, unrelated). Before this PR no plugin-chatbot code read kind off an envelope (git grep "\.kind\b" non-test: replayOutcome.kind, lastOutcome.kind, prompt-input item.kind only), so there is no in-repo collision.

Tests. Pin set at head: mapMessages.builtAppEditEnvelope-10109.test.ts 4 it + 2 it.each of 2 = 8; useChatConversation.editKindRoundTrip-10109.test.ts 2; AiChatPage.builtTransition.test.tsx 5 pre-existing + 1 it.each of 2 + 1 = 8. Total 18, as the body says; "three new cases" = 8 − 5. The round-trip pin drives the production path (live map, sanitizeChatMessagesForCache, JSON, re-map) and asserts detectBuiltAppPackage(output) undefined and reloaded.buildProgress undefined for the edit, with a no-kind blueprint control asserting the opposite. The live-completion pin asserts the settled turn rendered, so it is not vacuous.

Premise (cloud 403, unmeasured). pnpm-lock.yaml pins @objectstack/spec@17.4.0; the checkout has no node_modules; objectui declares no kind: 'edit' on any tool-result envelope. If the premise were false: (a) apply_edit emits no kind: every new branch is dead, the card's symptom persists, no regression; (b) apply_blueprint also emits kind: 'edit': real builds lose the Studio transition and the reloaded Built panel, which the no-kind controls could not catch (auto-publish is untouched; it keys on autoPublishable); (c) any other spelling: inert.

② Semver level

.changeset/10109-edit-envelope-not-built-app.md: '@object-ui/plugin-chatbot': minor. .changeset/10109-app-shell-edit-not-built-moment.md: '@object-ui/app-shell': patch. objectui AGENTS.md 版本号策略: no changeset declares major (objectui's own breaking changes are also minor), enforced by check-changeset-no-major; .changeset/config.json fixed group holds both packages. An added optional member on an exported type is additive, so minor is correct and the highest the policy allows. app-shell patch is correct: CachedDraftReview is not exported (export interface Cached…: 0 hits), app-shell's entry re-exports only toUIMessages, aiMessageRowsToServerMessages, HydratedUIMessage, RawAiMessageRow from the hook, and the AiChatPage change is behaviour only; the widening reaches app-shell consumers through plugin-chatbot's type, not app-shell's. Changeset prose at head, sentence by sentence: the guard in both postures (true: the kind check precedes the status check); DraftReview gains kind?: 'edit', filled by detectDraftResult, the edit keeps card, items and packageId (true); ChatbotEnhancedToolInvocation['draftReview'] retyped and "field-identical before this change" (true, verified member by member); buildProgressFromDraftReview withholds the panel (true); no-kind unchanged (true). app-shell prose: the transition also checks the draft review and skips kind: 'edit' (true), the cache keeps kind (true), apply_blueprint moves to Studio as before (true; control pins). The f7eaadc change is described where it must be: bullet 3 of the plugin-chatbot changeset, added in f7eaadc; its app-shell half is a dropped type annotation with identical emit and needs no prose. Head gates: Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read: success (the re-read flags 6169, 8214, 6687 as report-only; 6687's ChatbotSurface claim still holds, 2 hits in ChatbotEnhanced.tsx at head).

③ Boundary flags

  • Declared deviations. Cloud unreachable: stated in the body's Acceptance notes and in every pin's header; the envelope shape is the card's filing. check-readme-exports NOT MEASURED locally: CI README Export Check success. Narrowed local test run (plugin-chatbot plus 34 named files; full suites not re-run in round 3 on the emitted-JS identity argument, which I reproduced): CI Test (shard 1/8 to 8/8), Test, Test (dist pins), Build & E2E: success, which supersedes the narrowing.
  • Check-runs on f7eaadc (/commits/f7eaadc…/check-runs?per_page=100, read after 2026-09-25T02:45Z): 43 total, 40 success, 3 skipped (Test (coverage shard ${{ matrix.shard }}/4), Test (coverage), dependabot), 0 in progress. Lint, Type Check, Spec Main Shape Gate, Governed Surface Queue Guard, Line Citation Gate: success. Nothing is still running.
  • PR body truth at this head. Every checkable sentence holds: lineage (acf3b89 merges 0d39dc6; 0811a11 merges ff14e29; merged, not rebased); "7 source file(s) of 2 released package(s)" = 3 plugin-chatbot + 4 app-shell; the 18-case set and the three new transition cases; "field-identical" and "byte-identical" both reproduced here; Fixes #10109 is the first line (round 1's Part of is gone, and the drafted-posture symptom the round-1 report left open is now pinned closed); the title names both packages. One loose sentence, not a stop: "the copy's field docs move onto DraftReview" is true for four members (autoPublishable, failedCount, materialized, verification); issues and nextSteps kept DraftReview's own pre-existing docs and the copy's shorter ones were dropped.
  • Posture. draft: true, mergeable_state: clean, labels plugin, tests, package: app-shell. The card carries a Claim: and three os-dev-report comments and no maintainer ruling; round 2 implements the seat's own decision, which the seat justified as within the card's direction (AGENTS.md §5 #0.1). Draft is the expected state under the dispatch flow until the seat flips it; nothing in the diff should stop landing.
  • Residual from the earlier record, closed. The one residual it named (the inline draftReview copy lacking kind) is what f7eaadc removes; its "every reader" doc nit is also fixed (the DraftReview.kind doc now names buildProgressFromDraftReview and the app-shell transition, and says detectBuiltAppPackage reads the envelope's own kind).

Implemented-by: claude/issue-10109-edit-envelope-not-built-app
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 03:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit e6203d7 Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10109-edit-envelope-not-built-app branch September 25, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment