Repository navigation
feat(plugin-chatbot,i18n): the copilot's change card draws the draft posture, and the applied card offers review and undo (objectui#12894) - #12951
Conversation
…posture, and the applied card offers review and undo (objectui#12894) The mappers keep the keys cloud#2924's envelopes declare, and only those: detectProposedChanges keeps `posture` and `primaryAction`; detectReplayOutcome keeps `draftState`, `existingRows` and `appliedCard` on a drafted replay. A value without its declared required members is dropped whole, and every absent key leaves the shape an older cloud produced. The change card draws v2 (the StudioRailCopilot artboard) when the envelope carries the posture: 「修改方案 · N 项」, one row per change (symbol, kind, object · field, type and details in small type), the summary as an amber note for a change saved as a draft, and 存为草稿 as the primary label when `primaryAction` is `save_draft` (the message it sends is unchanged). The applied card draws `appliedCard.title` and its lines, 查看变更并发布 through a new `onReviewDrafts` host door, and 撤销 through a new `onDiscardDrafts` door. Because discard-drafts drops every pending draft of the package, 撤销 says so and asks first whenever other drafts are pending, by `draftState.others` or the host's live count. Without the new keys or doors, the card draws as before. Claude-Session: https://claude.ai/code/session_01Cbdek8SKSHxddGqTPZXdqX Co-authored-by: Claude <noreply@anthropic.com>
…s two doors; changeset (objectui#12894) Claude-Session: https://claude.ai/code/session_01Cbdek8SKSHxddGqTPZXdqX Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cbdek8SKSHxddGqTPZXdqX Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: Face (reviewed 2026-10-18T09:52Z): the Inputs read:
Nothing was built or run locally. ① Derived judgments
② Semver levelThe changeset bumps ③ Boundary flags
Implemented-by: VERDICT: PASS |
席位核对 · ACCEPT ·
|
Part of #12894
Part of #12725
Clause-②: yes
The copilot's change card and applied card draw the draft posture objectstack-ai/cloud now sends (ruling A on cloud#2924; cloud PR #2974 as
f9e45145, and cloud PR #3004 as1e3e04f8forapply_blueprint's extend mode). This is also objectui#12725's Target item 4 (change card v2), as the StudioRailCopilot artboard draws it, without the composer half (see "Not drawn"). objectui#12725 stays open for its other items.Premise, re-read on
main(03f1d652)changesConfirmLabelwas a host prop ofChatbotEnhanced(default'Confirm'); nothing on the envelope could change it.detectProposedChangeskeptsummaryand the rows.detectReplayOutcomekeptkind,packageIdand the publish failure.posture,primaryAction,draftState,existingRowsandappliedCardwere dropped before render.The premise holds.
The producer (read at cloud
1e3e04f8,packages/service-ai-studio)changesProposedEnvelope(metadata-tools.ts) addsposture(frompostureEnvelope:mode,liveRecords, optionalplatformPackageandbuildTurnRows) andprimaryAction(save_draftorconfirm). It adds both only when the environment publishes AI changes in the turn.summarycarriesconfirmChangeAsDraftin the user's language. The rows come fromdescribeEditOps/describeChange(confirm-gate.ts); a row'sdetailsischangeParams(label, default, required).describeLandedChangestampsdraftState(readDraftStateinchange-posture.ts:pending,items,others) on the applied envelope. On a change saved as a draft it also stampsexistingRowsandappliedCard(savedAsDraft,existingRowsEmpty,checkedStructure).build-cards.schema.jsoncovers the build cards only). So the producer's source is the declaration. The fixtures copy it, and the producer's own test edit (RENEWAL_EDITincopilot-change-posture.test.ts).What changed
Mappers (
mapMessages.ts):detectProposedChangeskeepspostureandprimaryAction, and nothing else new. A posture without bothmodeandliveRecords, or an action outsidesave_draft/confirm, is dropped whole.detectReplayOutcomekeepsdraftState,existingRowsandappliedCard, on adraftedreplay only. A draft state without both counts, a card without a title, and malformed rows are dropped.Change card (
ChangePlanBodyinChatbotEnhanced.tsx), drawn when the envelope carriespostureorprimaryAction:object · field, and the type anddetailsin mono small type;summaryas an amber note whenposture.modeisdraft, else as plain text;primaryActionissave_draft, and the host'schangesConfirmLabelotherwise. 调整 is unchanged.Applied card (
AppliedDraftCard), drawn on a drafted replay that carriesappliedCard:appliedCard.titlebeside a tick, and itslines;onReviewDrafts(packageId);onDiscardDrafts(packageId).discard-draftsdrops EVERY pending draft of the package (framework runtimedomains/packages.ts, protocoldiscardPackageDrafts, read at the pinbe076dad28a0). So whendraftState.othersis non-zero, 撤销 says so (「撤销(连同另外 2 项草稿)」), and a confirm names the counts before anything runs (「撤销会丢弃这个应用包全部 3 项未发布的草稿,其中 2 项不属于这次修改。」, then 全部撤销 / 取消).i18n: new
chatbot.changes.*keys in all ten packs: the title, the actions and the confirm (count families with every CLDR category each language selects), and the row kinds.Measured: neither door reaches the console without
packages/app-shellThis is the dispatch's stop rule, and it turned out to apply to both buttons, not only to 查看变更并发布.
onPublishDrafts, whichChatPane(packages/app-shell/src/console/ai/AiChatPage.tsx) wires throughmetadataClient.publishPackageDrafts. So 撤销 is a host door here too,onDiscardDrafts. The console half isChatPanepassing it.MetadataClienthas nodiscardPackageDraftstoday;PackagesPage'sdiscardDraftsposts todiscard-draftswith a bare fetch.onDesignBuiltAppinChatPane) goes to Studio's Interfaces pillar. Studio's 变更 drawer ischangesOpen, component state inStudioDesignSurface, with no URL parameter or bus that opens it. So the door isonReviewDrafts(packageId). The console half isChatPanepassing it, plus a way intoStudioDesignSurface'schangesOpen.onPublishDrafts) where 查看变更并发布 would be. It draws no 撤销. Both doors are drawn and tested here behind their props. The console wiring waits for objectui#12725's phase 5, which holdsapp-shell.Decisions for review
postureorprimaryActionis present; the producer sends the two together. An envelope without them (an older cloud, or an environment that does not publish AI changes in the turn) draws the old card, with the host's title and verb labels.delete_field) draws - in red rather than ✎.draftState.othersis above 0, or when there is nodraftState. It also asks when the host's livefetchPendingDraftCountreads more drafts than this change staged: a later change in the same conversation stages more drafts into the same package, and the snapshot on an older card cannot know that. The console already wiresfetchPendingDraftCount.changesConfirmMessage. 「存为草稿」 alone does not match cloud'sPROPOSAL_APPROVAL_RE, so changing the message would leave the button inert.Not drawn
app-shell;existingRowsfor it;Tests
At
a1601be5(after mergingmainat0707b4ae), every run through the shared verify lock:pnpm exec vitest run --maxWorkers=2 packages/plugin-chatbot/: 68 files passed (68), 711 tests passed (711).git ls-filescounts 68 tracked test files in the package.pnpm exec vitest run --maxWorkers=2 packages/i18n/: 83 files passed (83), 1325 passed and 13 skipped (1338). 83 tracked test files.mapMessages.changePosture-12894.test.tscovers each kept key, each absent key degrading, malformed values dropped whole, no other envelope key lifted, and the live mapper carrying both lifts.ChatbotEnhanced.changeCardV2-12894.test.tsxcovers 存为草稿 onsave_draftand the host label onconfirm; the rows' symbol, kind, target and small type; the amber note; the old card for an older cloud; the applied card's title, lines and both doors; 撤销 at once with nothing else pending; 撤销 asking with the counts whenothersis above 0, or the live count is higher, or there is no draft state; cancel; a refused discard; and the Publish fallback without a review door. Both run through the real mapper from raw tool parts, on the fixtures infixtures/changePosture.fixtures.ts.Ablation (one, on a mapper): through objectstack's
scripts/ablation-replace.mjs,primaryActionwas dropped fromdetectProposedChanges's return (anchor 1 to 0, blob1b39ebbftobd565dbe). The two new suites went red: 6 failed and 26 passed of 32. Those were four mapper cases and the zh and en render cases, where the button read 确认修改 / Confirm. Restore: blob back to1b39ebbf, equal toHEAD, andgit diff HEADempty. The suites importmapMessagesandChatbotEnhancedrelatively, so no dist was involved.Gates (local, at
a1601be5)type-checkfor@object-ui/plugin-chatbotand@object-ui/i18n: exit 0 each.tsc -p tsconfig.test.json --listFileslists 68 test files in the chatbot program, the two new ones among them, plus the fixture.lintfor both packages: exit 0, 0 errors. None of the 120 warnings falls on an added line.check:i18n-keys,check:i18n-drift(0 en values changed, 25 keys added),check:i18n-dead-keys,check:i18n-designer-parity,check:new-line-citations(0 new),check:control-bytes,check:test-path-roots,check:unreferenced-sources,check:phantom-deps,check:unused-deps,check:self-import,check:published-tsconfig-exclude,check:changeset-claims,check:pending-changeset-literals, the threecheck:vi-mock-*,check:shell-escape-residue,check:comment-mask-corpus,check:lint-rule-coverage,check:spec-symbols,check:esm-specifiers,check:handler-key-reads,check:registry-bare-names,check:doc-fences,check:doc-json,check:doc-types,check:icon-record-names,check:action-ref-convention, and the scriptscheck-changeset-presence,check-changeset-no-major,check-changeset-fixed,check-changeset-overwrite,check-doc-links,check-doc-expression-carriage,check-type-check-coverageandcheck-lint-coverage.@object-ui/app-shell's type-check and suite. It is the one consumer of the widened types, and its dependency closure (19 unbuilt packages) was not built in this shared container. The widening only adds optional members.check:readme-exportsandcheck:eager-locale-cataloguesneed dists this run did not build; this diff touches no README.Screenshots
On the evidence branch
claude/issue-12894-evidence(25b88b08, never merged; no image is in this diff), rendered at 1280 px, light, through the real mapper andChatbotEnhancedin a 360 px right slot:The branch's
docs/design/findings/12894/README.mdsays how they were rendered and what is faked: the envelopes, and the three host doors as no-ops.Acceptance notes
useChatConversation(replayOutcomeToCachedResult) keeps only the minimal replay envelope. Widening it is anapp-shellchange outside this claim. Carrier: none; noted, not filed.Published faces
@object-ui/plugin-chatbot:ProposedChangesgains optionalpostureandprimaryAction;ReplayOutcomegains optionaldraftState,existingRowsandappliedCard;ChatbotEnhancedToolInvocation'sproposedChangesandreplayOutcomenow reference those two types instead of repeating them inline, so they widen the same way;ChatbotEnhancedPropsgains optionalonReviewDraftsandonDiscardDrafts.ChangePosture,ChangeDraftState,PendingDraftItem,ExistingRowsLine,AppliedCardinmapMessages.ts) are not re-exported from the package entry.@object-ui/i18n: newchatbot.changes.*keys in every pack.The claim declares
Clause-②: yesfor this, and the seat's contract review is on this PR.Session:
https://claude.ai/code/session_01Cbdek8SKSHxddGqTPZXdqX