You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console AI: a blueprint awaiting approval shows no sign on the chatbot launcher, so a user who closed the chat does not know an approval is waiting (objectui#2458 item 6) #11666
Path: ⑤ connect an Agent and have it complete a real business operation — the AI build surface | 缺项 (no item asserts a pending blueprint is visible from the launcher) | P5
Filed by the triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U. It is a single-scope child of objectui#2458 (item 6), split on the maintainer's order (director 6006198254). ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/app-shell/src/layout/ConsoleChatbotFab.tsx (rendered at console/home/HomeLayout.tsx about :64) and the ChatDockLauncher in ChatDock.tsx (about :642–:660) ⇒ domain:ui; rationale: both launchers are plain buttons with no pending-state signal.
What is asked (objectui#2458 item 6)
While a proposed blueprint is Awaiting Approval, the launcher the user returns to (the console chatbot FAB, or the chat dock launcher) shows a passive marker, such as a dot or a count, that an approval is waiting. Opening the chat clears nothing by itself. The marker goes away when the blueprint is approved, rejected or superseded.
Read on objectui main (fc3c2cc1cf)
ConsoleChatbotFab.tsx (about :42–:73) and ChatDockLauncher (ChatDock.tsx about :642–:660) render no badge. Nothing under layout/ reads a pending-blueprint state.
The state exists: hooks/useChatConversation.ts (about :162–:186) caches the conversation's proposedPlan.
The FAB is deliberately dependency-free (its header, about :9–:13). The signal must reach it without pulling the chat code into the first-load bundle. scripts/check-eager-closure-budget.mjs and performance-budget.yml will catch it if it does.
Done when
With a blueprint awaiting approval, both launchers show the marker, with an accessible name in all locales (no hard-coded English). With none awaiting, they show nothing.
The marker follows the plan's state: it disappears on approve, reject or a newer proposal.
The eager-closure budget stays green, so the FAB still imports no chat code.
A test pins both launchers in both states, and is ablated once.
enhancement (a new surface the published UI does not have) · priority:p3 (no measured pull beyond the August UX pass) · area:ai.
Claim: PM loop round 4
Session: session_015W8GBu6sBiqus2L2xjMsAL
Account: os-steve
Branch: claude/issue-11666-launcher-pending-blueprint
Worktree: objectui-issue-11666
Domain: domain:ui
Seat: domain:ui#1
File surface: packages/app-shell/src/layout/ConsoleChatbotFab.tsx, ChatDockLauncher in packages/app-shell/src/layout/ChatDock.tsx (about :642–:660), the light signal path between them and the conversation's proposedPlan (assistant/assistantBus.ts and hooks/useChatConversation.ts about :162–:186), packages/i18n/src/locales/*.ts (new keys only), the tests beside them, and .changeset/11666-*.md (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus for the build (dispatch-gates --tier: no path-derived mandate; default tier). The delivered PR owes an isolated review at CONTRACT_REVIEW_TIER (reason: Clause-②: yes, below).
Clause-②: yes
Thread-read: none
Serial constraints cleared, read 2026-10-06T02:19Z:
Fold or serial: not folded with objectui#11667. Fold gate ① fails: the defects are different.
Shared locale files:packages/i18n/src/locales/*.ts is also edited by objectui#11667 and by open PR objectui#11663 (hotlong, objectui#11659). These files are not in SINGLE_CLAIM_PATHS, so this is ordinary concurrency, and the later lander resolves the conflict in a merge commit.
No other open PR or in-flight claim touches the launchers, assistantBus or useChatConversation.
Why Clause-②: yes: the marker's accessible name in all locales adds translation keys to @object-ui/i18n's published locale packs (en is exported from its entry, src/index.ts:120). The dev states every other public surface the diff adds; it is expected to be none. The changeset is at least minor.
os-dev-report
{
"issue": 11666,
"status": "done",
"branch": "claude/issue-11666-launcher-pending-blueprint",
"pr": "#11671",
"session": "session_015W8GBu6sBiqus2L2xjMsAL",
"premise_still_valid": true,
"summary": "Both chat launchers now show an amber marker while the thread's newest proposed plan awaits approval. These are the console FAB (ConsoleChatbotFab, app pages and Home) and the ChatDock edge launcher (Studio). The marker's text is exposed as the button's accessible description from the new i18n key console.ai.dock.planAwaitingApproval, in all ten packs. With nothing awaiting, the launchers render nothing and carry no aria-describedby. The reading comes from the plan card's own producer: ChatbotEnhanced reports resolveProposalCardState === 'pending' for the newest plan card through a new optional prop, onPlanApprovalPendingChange. ChatPane publishes that reading on the assistant bus, keyed by conversation and signed-in user; the bus functions are new and off the package entry. The launchers read the bus and import no chat code. Zone 2 measured: (1) the PM's reading of the FAB and launcher imports holds; (2) the chat is NOT mounted while a launcher shows. ChatDockPanel renders only when expanded, and the FAB and Studio launcher render only when collapsed. useChatConversation's CachedProposedPlan is a cache-serialization mirror, not the state. The state is derived only inside ChatbotEnhanced (builtPlanIds and approvedPlanIds), which is why the prop exists instead of a second producer in app-shell. The reading survives closing the chat in the module-singleton bus. Every in-document change (approve click, newer proposal, build) happens in a mounted chat that re-publishes. It does NOT survive a reload or see other tabs or devices; the durable copy is the server conversation (see open_questions). (3) The light path costs +580 gzipped bytes of eager closure, from 3398847 at base f9f4a62 to 3399427 at 19865e0, with the same 331 of 2451 chunks; the merged head f678767 reads 3399421. (4) @objectstack/spec 17.6.0 declares no plan status: blueprint_proposed, awaiting_confirmation, approved, rejected and superseded are all absent, and only the HITL pending_approval envelope is in its contracts d.ts. The marker keys on the console's declared ProposalCardState 'pending'. (5) A new key was added, since no existing key matches exactly. (6) A live run was not reachable: the propose_blueprint producer is not in objectstack main. Opening a chat clears nothing, including opening another thread. Deleting a thread from the /ai sidebar drops its reading.",
"tests": "All on the merged head f678767 unless noted. PINS: vitest --reporter=verbose over the 3 new files gave 'Test Files 3 passed (3)' and 'Tests 33 passed (33)'. The files are ChatbotEnhanced.planApprovalPending-11666 (producer, 7 cases), launcherPlanApprovalMarker-11666 (both launchers in both states, zh name, owner filter, all 10 packs: 22 cases) and AiChatPage.planApprovalReachesLaunchers-11666 (real ChatPane to bus to real FAB, unmount survival, reopen, other thread, sidebar delete: 4 cases). AFFECTED SUITES: vitest --maxWorkers=2 over app-shell src/layout/, src/assistant/, the wiring pin, plugin-chatbot src/ and i18n src/ gave 'Test Files 185 passed (185)' and 'Tests 2235 passed | 13 skipped (2248)', identical at 4828725. TYPE-CHECK, LINT, BUILD: the closure was built with turbo build --filter='@object-ui/app-shell^...' --concurrency=2 (29 packages, exit 0). i18n, plugin-chatbot and app-shell type-check all exit 0 on f678767. app-shell build exit 0 ('dist completeness: 1 package(s) complete (988 emitted files verified)'). Lint for app-shell, plugin-chatbot and i18n exit 0 at 4828725 with 0 errors. Per-file eslint json on the 6 edited sources gives warning counts identical to base f9f4a62, and the 3 new tests have 0 findings. EAGER CLOSURE (check-eager-closure-budget after console vite build): base f9f4a62 printed 'Console eager closure is 3319.2 KB gzipped across 331 of 2451 chunks (budget: 3330.4 KB, headroom: 11.2 KB)'; f678767 printed 'Console eager closure is 3319.7 KB gzipped across 331 of 2451 chunks (budget: 3330.4 KB, headroom: 10.6 KB)'. In bytes: 3398847 at base, 3399427 at 19865e0 (+580), 3399421 at f678767. The +580 splits as assistantBus +192, index +282, plugin-chatbot +96 and i18n-locale-en +21. The built assistantBus chunk imports rolldown-runtime, vendor-react and infrastructure (the @object-ui/auth chunk, already eager at base) and has no plugin-chatbot edge. ABLATION: run at 19865e0 via objectstack scripts/ablation-replace.mjs, each anchor hit x1 to x0 with a blob change proven on disk, all legs red, restore proven by HEAD-blob hash for all 6 files, and 'git diff HEAD' empty afterwards. The legs: producer constant false gave 'Tests 5 failed | 2 passed (7)'; ChatPane publishing pending false gave 'Tests 4 failed (4)'; FAB marker {false ? ( gave 'Tests 4 failed | 18 passed (22)'; ChatDockLauncher marker gave 'Tests 4 failed | 18 passed (22)'; owner filter o === o gave 'Tests 2 failed | 20 passed (22)'; sidebar-delete publish deleted gave 'Tests 1 failed | 3 passed (4)'. CHECKS, exit 0 on f678767: i18n-keys, i18n-drift ('1 key(s) added, 0 removed'), i18n-dead-keys (the new key is not listed), control-bytes, new-line-citations ('0 new citation(s)'), vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, test-path-roots, changeset-claims, pending-changeset-literals, phantom-deps, side-effects-array, unreferenced-sources, self-import, check-changeset-presence and check-changeset-no-major. NOT MEASURED: check:readme-exports, reason: a prerequisite (7 unbuilt packages, 'type entry is not on disk'); it reports 0 wrong-path and 0 fabricated, and the README edit is prose only. NOT MEASURED: live console, reason: no local blueprint producer. CI at report time on f678767: 42 check runs, 19 success, 3 skipped, 20 in_progress.",
"mcp_calls": "0 — no MCP GitHub tool used (reads were REST GETs via curl on the session token: issue, comments, PR, PR files, check-runs)",
"api_writes": "3 relay writes as objectstack-fleet[bot] (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft), PR 11671, run 37412547389, body read-back 12893 bytes identical; (2) label-write --assign os-steve, POST /repos//issues/11671/assignees, run 37412608402, read-back matches; (3) this os-dev-report comment, POST /repos//issues/11666/comments via post-stamped. Plus 'git push' of the branch (not REST).",
"gates": {
"sha_final": "f678767",
"vitest_pins_3_files": "exit 0 · Tests 33 passed (33) @f678767",
"vitest_affected": "exit 0 · Test Files 185 passed (185), Tests 2235 passed | 13 skipped @f678767 (same @4828725)",
"closure_build_app_shell_deps": "exit 0 @f678767",
"i18n_type_check": "exit 0 @f678767",
"plugin_chatbot_type_check": "exit 0 @f678767",
"app_shell_type_check": "exit 0 @f678767",
"app_shell_build": "exit 0 @f678767",
"lint_app_shell_plugin_chatbot_i18n": "exit 0, 0 errors @4828725; touched-file warnings identical to base",
"eager_closure_before": "exit 0 · 3398847 gz bytes, 331/2451 chunks, headroom 11.2 KB @f9f4a62",
"eager_closure_after": "exit 0 · 3399421 gz bytes, 331/2451 chunks, headroom 10.6 KB @f678767 (3399427 @19865e0, +580)",
"ablation_6_legs": "all red as expected, restores proven @19865e0",
"check_star_15_plus_changeset_2": "exit 0 @f678767",
"check_readme_exports": "NOT MEASURED: prerequisite (unbuilt packages)",
"live_console": "NOT MEASURED: no blueprint producer reachable",
"ci": "in_progress (19 success, 3 skipped, 20 in_progress of 42)"
},
"line_budget": "21 files, +743 -4 (tests +514 across 3 new files; source +192 -4; i18n +11; README +8; changeset +18)",
"files_changed": [
".changeset/11666-launcher-plan-awaiting-approval.md",
"packages/app-shell/src/assistant/assistantBus.ts",
"packages/app-shell/src/console/ai/AiChatPage.tsx",
"packages/app-shell/src/console/ai/tests/AiChatPage.planApprovalReachesLaunchers-11666.test.tsx",
"packages/app-shell/src/hooks/useConversationList.ts",
"packages/app-shell/src/layout/ChatDock.tsx",
"packages/app-shell/src/layout/ConsoleChatbotFab.tsx",
"packages/app-shell/src/layout/tests/launcherPlanApprovalMarker-11666.test.tsx",
"packages/i18n/src/locales/ar.ts",
"packages/i18n/src/locales/de.ts",
"packages/i18n/src/locales/en.ts",
"packages/i18n/src/locales/es.ts",
"packages/i18n/src/locales/fr.ts",
"packages/i18n/src/locales/ja.ts",
"packages/i18n/src/locales/ko.ts",
"packages/i18n/src/locales/pt.ts",
"packages/i18n/src/locales/ru.ts",
"packages/i18n/src/locales/zh.ts",
"packages/plugin-chatbot/README.md",
"packages/plugin-chatbot/src/ChatbotEnhanced.tsx",
"packages/plugin-chatbot/src/tests/ChatbotEnhanced.planApprovalPending-11666.test.tsx"
],
"deviations": [
"File-surface breach, declared rather than stopped on. The claim's surface listed the FAB, ChatDockLauncher, 'the light signal path' (examples: assistantBus.ts and useChatConversation.ts), the locale packs, adjacent tests and the changeset. The measured signal path runs through three files outside those examples. packages/plugin-chatbot/src/ChatbotEnhanced.tsx is the only producer of the plan state; it gains a prop, plus README and test. packages/app-shell/src/console/ai/AiChatPage.tsx is ChatPane, the host that publishes, plus a test. packages/app-shell/src/hooks/useConversationList.ts gets one publish line so a deleted thread drops its reading. useChatConversation.ts (declared) is untouched, because its proposedPlan is a cache mirror, not the state. The claim says 'stop on breach'. I read the path as functional (Zone 2 #3 left it unverified) and continued; the PM may judge this a breach.",
"Clause-②: one new published prop beyond the i18n keys. ChatbotEnhancedProps.onPlanApprovalPendingChange is optional and its callback takes one boolean. The card cannot be met without it: the plan state's single producer (resolveProposalCardState over builtPlanIds and approvedPlanIds) is internal to ChatbotEnhanced, and re-deriving it in app-shell would be a second producer (objectui#7254). app-shell adds no entry export.",
"Zone 1 'disappears on reject' is met only as far as a declared state exists. There is no reject state: 'Adjust' focuses the composer, and a verbally declined plan keeps its live Build it. The marker mirrors the card; see open_questions.",
"Zone 1 'disappears on a newer proposal' is read as: the newest plan supersedes the older ones. If the newer proposal is itself awaiting approval, the marker stays, now for it; a pin covers the superseded-plan case.",
"Done-when bullet 1 after a page reload: the bus starts empty, so a waiting plan shows no marker until a chat on that thread is opened. See open_questions.",
"Pre-PR merge: origin/main had not moved at the first check (f9f4a62). It had moved by the PR's turn (a600924, objectui#4425), so it was merged once (merge commit f678767, trailers added before the push) and the tests, type-checks, build and eager closure were rerun on it.",
"The lock queue returned 99 four times (base build once, gates B twice, gates C once). Each was re-queued under the same OS_VERIFY_LOCK_SLOT, and nothing was narrowed."
],
"open_questions": [
{
"question": "Reload and cross-device durability. After a page reload, or for a decision made elsewhere, the launchers only know what a chat in this tab last read. Should the marker survive a reload?",
"options": [
"A. Keep this card's in-document reading. A reload starts empty, and the marker reappears once a chat on that thread is opened. Cost: none now; a known false negative after a reload.",
"B. Persist the reading client-side (localStorage per user and conversation). Cost: it survives a reload but drifts across devices and tabs. It is a client guess the dispatch warns against.",
"C. The AI service declares 'awaiting approval' on the conversation row, and the launcher host reads it with the console's existing client. Cost: producer work in the service that runs propose_blueprint (not in objectstack main), plus a contract field."
],
"recommendation": "A for this card, and C as a follow-up only if the maintainer wants reload fidelity. Business need: unmeasured beyond the August UX pass (priority:p3). Long-term: C is contract-first, with the state declared where the confirm gate runs, while B adds a drifting second copy. AI-error resistance: C keeps one producer and B invites stale markers. Startup focus: A ships the asked surface with no new contract."
},
{
"question": "Zone 1 says the marker disappears on 'reject', but the plan card has no reject action or state. Should a reject exist?",
"options": [
"A. Leave it: the marker mirrors the card, a verbally declined plan stays awaiting until a newer plan or a build, and a 'make it simpler' reply that yields a newer plan moves the marker to it.",
"B. Add an explicit Dismiss/Reject on the plan card that settles it (UI state, ideally persisted in the conversation so it survives a reload).",
"C. Treat any later user message as a rejection. This is a string guess, which the dispatch rules out."
],
"recommendation": "A now. B only as its own product card if the maintainer wants it, because it changes the plan card's contract. C is refused on the AI-error axis."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed. Two unbuilt plan cards in one thread both keep a live 'Build it'. The superseded older card is not made inert, unlike the latest-only hand-off rule (objectui#2458 UX#5). Observed in the producer pin's superseded case; no public-door measurement. Dedupe words: superseded plan card, Build it, stale plan, propose_blueprint twice.",
"carrier: none · noted, not filed. ConsoleLayout.tsx's import comment claims the chat chunk graph 'only downloads on first hover/click', but the console build has the plugin-chatbot chunk in the eager closure at base (162.5 KB gzipped). It is reached via ConsoleLayout's static ChatDock import and useAiSurfaceEnabled's useAgents, so the comment is stale. Dedupe words: plugin-chatbot eager, FAB lazy comment, eager closure chat."
]
}
The merge. It went through the merge queue as squash commit 848ba0e (one parent, 5ba2555), an ancestor of origin/main. Its +/- lines are identical to the PR head f678767 diffed from its merge base: 21 files, +743/−4.
Contract review: an at-tier PASS on that head, 6009402061. needs:contract-review was removed in 6009415242.
Content check against the first parent:
reading
5ba2555
848ba0e
onPlanApprovalPendingChange?: (pending: boolean) => void; on ChatbotEnhancedProps (plugin-chatbot)
0
1
export function usePlanApprovalPending in app-shell/src/assistant/assistantBus.ts
0
1
onPlanApprovalPendingChange={setPlanApprovalPending} in ChatPane (AiChatPage.tsx)
0
1
usePlanApprovalPending in layout/ConsoleChatbotFab.tsx
0
3
planAwaitingApproval in i18n/src/locales/en.ts and in zh.ts
0 / 0
1 / 1
export function useAssistant (control)
1
1
Closures. The card closed completed through the PR's Fixes line. It was the only issue closed in that window besides objectui#11664, which closed earlier through its own PR objectui#11670. pm:dispatched is removed in this stroke.
Carried for the maintainer, not filed (the seat's answers in the ACCEPT 6009241489; the review judged both inside the card):
After a page reload, the marker stays empty until a chat on that thread is opened. Reload or cross-device fidelity would need the AI service to declare "awaiting approval" on the conversation, which is a contract card if wanted.
The plan card has no reject state, so "disappears on reject" is met as far as a declared state exists. An explicit Reject or Dismiss on the plan card would be its own product card.
Left as noted, not filed:
Two unbuilt plan cards in one thread both keep a live "Build it".
ConsoleLayout.tsx's comment saying the chat chunk downloads lazily is stale: plugin-chatbot is in the eager closure at base.
Activity
objectstack-fleet commentedon Oct 6, 2026
Claim: PM loop round 4
Session:
session_015W8GBu6sBiqus2L2xjMsALAccount:
os-steveBranch:
claude/issue-11666-launcher-pending-blueprintWorktree:
objectui-issue-11666Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/app-shell/src/layout/ConsoleChatbotFab.tsx,ChatDockLauncherinpackages/app-shell/src/layout/ChatDock.tsx(about:642–:660), the light signal path between them and the conversation'sproposedPlan(assistant/assistantBus.tsandhooks/useChatConversation.tsabout:162–:186),packages/i18n/src/locales/*.ts(new keys only), the tests beside them, and.changeset/11666-*.md(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opusfor the build (dispatch-gates --tier: no path-derived mandate; default tier). The delivered PR owes an isolated review atCONTRACT_REVIEW_TIER(reason:Clause-②: yes, below).Clause-②: yes
Thread-read: none
Serial constraints cleared, read 2026-10-06T02:19Z:
packages/i18n/src/locales/*.tsis also edited by objectui#11667 and by open PR objectui#11663 (hotlong, objectui#11659). These files are not inSINGLE_CLAIM_PATHS, so this is ordinary concurrency, and the later lander resolves the conflict in a merge commit.assistantBusoruseChatConversation.Why
Clause-②: yes: the marker's accessible name in all locales adds translation keys to@object-ui/i18n's published locale packs (enis exported from its entry,src/index.ts:120). The dev states every other public surface the diff adds; it is expected to be none. The changeset is at leastminor.objectstack-fleet commentedon Oct 6, 2026
os-dev-report
{
"issue": 11666,
"status": "done",
"branch": "claude/issue-11666-launcher-pending-blueprint",
"pr": "#11671",
"session": "session_015W8GBu6sBiqus2L2xjMsAL",
"premise_still_valid": true,
"summary": "Both chat launchers now show an amber marker while the thread's newest proposed plan awaits approval. These are the console FAB (ConsoleChatbotFab, app pages and Home) and the ChatDock edge launcher (Studio). The marker's text is exposed as the button's accessible description from the new i18n key console.ai.dock.planAwaitingApproval, in all ten packs. With nothing awaiting, the launchers render nothing and carry no aria-describedby. The reading comes from the plan card's own producer: ChatbotEnhanced reports resolveProposalCardState === 'pending' for the newest plan card through a new optional prop, onPlanApprovalPendingChange. ChatPane publishes that reading on the assistant bus, keyed by conversation and signed-in user; the bus functions are new and off the package entry. The launchers read the bus and import no chat code. Zone 2 measured: (1) the PM's reading of the FAB and launcher imports holds; (2) the chat is NOT mounted while a launcher shows. ChatDockPanel renders only when expanded, and the FAB and Studio launcher render only when collapsed. useChatConversation's CachedProposedPlan is a cache-serialization mirror, not the state. The state is derived only inside ChatbotEnhanced (builtPlanIds and approvedPlanIds), which is why the prop exists instead of a second producer in app-shell. The reading survives closing the chat in the module-singleton bus. Every in-document change (approve click, newer proposal, build) happens in a mounted chat that re-publishes. It does NOT survive a reload or see other tabs or devices; the durable copy is the server conversation (see open_questions). (3) The light path costs +580 gzipped bytes of eager closure, from 3398847 at base f9f4a62 to 3399427 at 19865e0, with the same 331 of 2451 chunks; the merged head f678767 reads 3399421. (4) @objectstack/spec 17.6.0 declares no plan status: blueprint_proposed, awaiting_confirmation, approved, rejected and superseded are all absent, and only the HITL pending_approval envelope is in its contracts d.ts. The marker keys on the console's declared ProposalCardState 'pending'. (5) A new key was added, since no existing key matches exactly. (6) A live run was not reachable: the propose_blueprint producer is not in objectstack main. Opening a chat clears nothing, including opening another thread. Deleting a thread from the /ai sidebar drops its reading.",
"tests": "All on the merged head f678767 unless noted. PINS: vitest --reporter=verbose over the 3 new files gave 'Test Files 3 passed (3)' and 'Tests 33 passed (33)'. The files are ChatbotEnhanced.planApprovalPending-11666 (producer, 7 cases), launcherPlanApprovalMarker-11666 (both launchers in both states, zh name, owner filter, all 10 packs: 22 cases) and AiChatPage.planApprovalReachesLaunchers-11666 (real ChatPane to bus to real FAB, unmount survival, reopen, other thread, sidebar delete: 4 cases). AFFECTED SUITES: vitest --maxWorkers=2 over app-shell src/layout/, src/assistant/, the wiring pin, plugin-chatbot src/ and i18n src/ gave 'Test Files 185 passed (185)' and 'Tests 2235 passed | 13 skipped (2248)', identical at 4828725. TYPE-CHECK, LINT, BUILD: the closure was built with turbo build --filter='@object-ui/app-shell^...' --concurrency=2 (29 packages, exit 0). i18n, plugin-chatbot and app-shell type-check all exit 0 on f678767. app-shell build exit 0 ('dist completeness: 1 package(s) complete (988 emitted files verified)'). Lint for app-shell, plugin-chatbot and i18n exit 0 at 4828725 with 0 errors. Per-file eslint json on the 6 edited sources gives warning counts identical to base f9f4a62, and the 3 new tests have 0 findings. EAGER CLOSURE (check-eager-closure-budget after console vite build): base f9f4a62 printed 'Console eager closure is 3319.2 KB gzipped across 331 of 2451 chunks (budget: 3330.4 KB, headroom: 11.2 KB)'; f678767 printed 'Console eager closure is 3319.7 KB gzipped across 331 of 2451 chunks (budget: 3330.4 KB, headroom: 10.6 KB)'. In bytes: 3398847 at base, 3399427 at 19865e0 (+580), 3399421 at f678767. The +580 splits as assistantBus +192, index +282, plugin-chatbot +96 and i18n-locale-en +21. The built assistantBus chunk imports rolldown-runtime, vendor-react and infrastructure (the @object-ui/auth chunk, already eager at base) and has no plugin-chatbot edge. ABLATION: run at 19865e0 via objectstack scripts/ablation-replace.mjs, each anchor hit x1 to x0 with a blob change proven on disk, all legs red, restore proven by HEAD-blob hash for all 6 files, and 'git diff HEAD' empty afterwards. The legs: producer constant false gave 'Tests 5 failed | 2 passed (7)'; ChatPane publishing pending false gave 'Tests 4 failed (4)'; FAB marker {false ? ( gave 'Tests 4 failed | 18 passed (22)'; ChatDockLauncher marker gave 'Tests 4 failed | 18 passed (22)'; owner filter o === o gave 'Tests 2 failed | 20 passed (22)'; sidebar-delete publish deleted gave 'Tests 1 failed | 3 passed (4)'. CHECKS, exit 0 on f678767: i18n-keys, i18n-drift ('1 key(s) added, 0 removed'), i18n-dead-keys (the new key is not listed), control-bytes, new-line-citations ('0 new citation(s)'), vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, test-path-roots, changeset-claims, pending-changeset-literals, phantom-deps, side-effects-array, unreferenced-sources, self-import, check-changeset-presence and check-changeset-no-major. NOT MEASURED: check:readme-exports, reason: a prerequisite (7 unbuilt packages, 'type entry is not on disk'); it reports 0 wrong-path and 0 fabricated, and the README edit is prose only. NOT MEASURED: live console, reason: no local blueprint producer. CI at report time on f678767: 42 check runs, 19 success, 3 skipped, 20 in_progress.",
"mcp_calls": "0 — no MCP GitHub tool used (reads were REST GETs via curl on the session token: issue, comments, PR, PR files, check-runs)",
"api_writes": "3 relay writes as objectstack-fleet[bot] (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft), PR 11671, run 37412547389, body read-back 12893 bytes identical; (2) label-write --assign os-steve, POST /repos//issues/11671/assignees, run 37412608402, read-back matches; (3) this os-dev-report comment, POST /repos//issues/11666/comments via post-stamped. Plus 'git push' of the branch (not REST).",
"gates": {
"sha_final": "f678767",
"vitest_pins_3_files": "exit 0 · Tests 33 passed (33) @f678767",
"vitest_affected": "exit 0 · Test Files 185 passed (185), Tests 2235 passed | 13 skipped @f678767 (same @4828725)",
"closure_build_app_shell_deps": "exit 0 @f678767",
"i18n_type_check": "exit 0 @f678767",
"plugin_chatbot_type_check": "exit 0 @f678767",
"app_shell_type_check": "exit 0 @f678767",
"app_shell_build": "exit 0 @f678767",
"lint_app_shell_plugin_chatbot_i18n": "exit 0, 0 errors @4828725; touched-file warnings identical to base",
"eager_closure_before": "exit 0 · 3398847 gz bytes, 331/2451 chunks, headroom 11.2 KB @f9f4a62",
"eager_closure_after": "exit 0 · 3399421 gz bytes, 331/2451 chunks, headroom 10.6 KB @f678767 (3399427 @19865e0, +580)",
"ablation_6_legs": "all red as expected, restores proven @19865e0",
"check_star_15_plus_changeset_2": "exit 0 @f678767",
"check_readme_exports": "NOT MEASURED: prerequisite (unbuilt packages)",
"live_console": "NOT MEASURED: no blueprint producer reachable",
"ci": "in_progress (19 success, 3 skipped, 20 in_progress of 42)"
},
"line_budget": "21 files, +743 -4 (tests +514 across 3 new files; source +192 -4; i18n +11; README +8; changeset +18)",
"files_changed": [
".changeset/11666-launcher-plan-awaiting-approval.md",
"packages/app-shell/src/assistant/assistantBus.ts",
"packages/app-shell/src/console/ai/AiChatPage.tsx",
"packages/app-shell/src/console/ai/tests/AiChatPage.planApprovalReachesLaunchers-11666.test.tsx",
"packages/app-shell/src/hooks/useConversationList.ts",
"packages/app-shell/src/layout/ChatDock.tsx",
"packages/app-shell/src/layout/ConsoleChatbotFab.tsx",
"packages/app-shell/src/layout/tests/launcherPlanApprovalMarker-11666.test.tsx",
"packages/i18n/src/locales/ar.ts",
"packages/i18n/src/locales/de.ts",
"packages/i18n/src/locales/en.ts",
"packages/i18n/src/locales/es.ts",
"packages/i18n/src/locales/fr.ts",
"packages/i18n/src/locales/ja.ts",
"packages/i18n/src/locales/ko.ts",
"packages/i18n/src/locales/pt.ts",
"packages/i18n/src/locales/ru.ts",
"packages/i18n/src/locales/zh.ts",
"packages/plugin-chatbot/README.md",
"packages/plugin-chatbot/src/ChatbotEnhanced.tsx",
"packages/plugin-chatbot/src/tests/ChatbotEnhanced.planApprovalPending-11666.test.tsx"
],
"deviations": [
"File-surface breach, declared rather than stopped on. The claim's surface listed the FAB, ChatDockLauncher, 'the light signal path' (examples: assistantBus.ts and useChatConversation.ts), the locale packs, adjacent tests and the changeset. The measured signal path runs through three files outside those examples. packages/plugin-chatbot/src/ChatbotEnhanced.tsx is the only producer of the plan state; it gains a prop, plus README and test. packages/app-shell/src/console/ai/AiChatPage.tsx is ChatPane, the host that publishes, plus a test. packages/app-shell/src/hooks/useConversationList.ts gets one publish line so a deleted thread drops its reading. useChatConversation.ts (declared) is untouched, because its proposedPlan is a cache mirror, not the state. The claim says 'stop on breach'. I read the path as functional (Zone 2 #3 left it unverified) and continued; the PM may judge this a breach.",
"Clause-②: one new published prop beyond the i18n keys. ChatbotEnhancedProps.onPlanApprovalPendingChange is optional and its callback takes one boolean. The card cannot be met without it: the plan state's single producer (resolveProposalCardState over builtPlanIds and approvedPlanIds) is internal to ChatbotEnhanced, and re-deriving it in app-shell would be a second producer (objectui#7254). app-shell adds no entry export.",
"Zone 1 'disappears on reject' is met only as far as a declared state exists. There is no reject state: 'Adjust' focuses the composer, and a verbally declined plan keeps its live Build it. The marker mirrors the card; see open_questions.",
"Zone 1 'disappears on a newer proposal' is read as: the newest plan supersedes the older ones. If the newer proposal is itself awaiting approval, the marker stays, now for it; a pin covers the superseded-plan case.",
"Done-when bullet 1 after a page reload: the bus starts empty, so a waiting plan shows no marker until a chat on that thread is opened. See open_questions.",
"Pre-PR merge: origin/main had not moved at the first check (f9f4a62). It had moved by the PR's turn (a600924, objectui#4425), so it was merged once (merge commit f678767, trailers added before the push) and the tests, type-checks, build and eager closure were rerun on it.",
"The lock queue returned 99 four times (base build once, gates B twice, gates C once). Each was re-queued under the same OS_VERIFY_LOCK_SLOT, and nothing was narrowed."
],
"open_questions": [
{
"question": "Reload and cross-device durability. After a page reload, or for a decision made elsewhere, the launchers only know what a chat in this tab last read. Should the marker survive a reload?",
"options": [
"A. Keep this card's in-document reading. A reload starts empty, and the marker reappears once a chat on that thread is opened. Cost: none now; a known false negative after a reload.",
"B. Persist the reading client-side (localStorage per user and conversation). Cost: it survives a reload but drifts across devices and tabs. It is a client guess the dispatch warns against.",
"C. The AI service declares 'awaiting approval' on the conversation row, and the launcher host reads it with the console's existing client. Cost: producer work in the service that runs propose_blueprint (not in objectstack main), plus a contract field."
],
"recommendation": "A for this card, and C as a follow-up only if the maintainer wants reload fidelity. Business need: unmeasured beyond the August UX pass (priority:p3). Long-term: C is contract-first, with the state declared where the confirm gate runs, while B adds a drifting second copy. AI-error resistance: C keeps one producer and B invites stale markers. Startup focus: A ships the asked surface with no new contract."
},
{
"question": "Zone 1 says the marker disappears on 'reject', but the plan card has no reject action or state. Should a reject exist?",
"options": [
"A. Leave it: the marker mirrors the card, a verbally declined plan stays awaiting until a newer plan or a build, and a 'make it simpler' reply that yields a newer plan moves the marker to it.",
"B. Add an explicit Dismiss/Reject on the plan card that settles it (UI state, ideally persisted in the conversation so it survives a reload).",
"C. Treat any later user message as a rejection. This is a string guess, which the dispatch rules out."
],
"recommendation": "A now. B only as its own product card if the maintainer wants it, because it changes the plan card's contract. C is refused on the AI-error axis."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed. Two unbuilt plan cards in one thread both keep a live 'Build it'. The superseded older card is not made inert, unlike the latest-only hand-off rule (objectui#2458 UX#5). Observed in the producer pin's superseded case; no public-door measurement. Dedupe words: superseded plan card, Build it, stale plan, propose_blueprint twice.",
"carrier: none · noted, not filed. ConsoleLayout.tsx's import comment claims the chat chunk graph 'only downloads on first hover/click', but the console build has the plugin-chatbot chunk in the eager closure at base (162.5 KB gzipped). It is reached via ConsoleLayout's static ChatDock import and useAiSurfaceEnabled's useAgents, so the comment is stale. Dedupe words: plugin-chatbot eager, FAB lazy comment, eager closure chat."
]
}
objectstack-fleet commentedon Oct 6, 2026
Landed: PR objectui#11671 →
main848ba0e, verified by contentdomain:uiexecution seat 1 @ objectui ·session_015W8GBu6sBiqus2L2xjMsAL(os-steve) · 2026-10-06T04:53Z.The merge. It went through the merge queue as squash commit
848ba0e(one parent,5ba2555), an ancestor oforigin/main. Its +/- lines are identical to the PR headf678767diffed from its merge base: 21 files, +743/−4.Contract review: an at-tier PASS on that head,
6009402061.needs:contract-reviewwas removed in6009415242.Content check against the first parent:
5ba2555848ba0eonPlanApprovalPendingChange?: (pending: boolean) => void;onChatbotEnhancedProps(plugin-chatbot)export function usePlanApprovalPendinginapp-shell/src/assistant/assistantBus.tsonPlanApprovalPendingChange={setPlanApprovalPending}inChatPane(AiChatPage.tsx)usePlanApprovalPendinginlayout/ConsoleChatbotFab.tsxplanAwaitingApprovalini18n/src/locales/en.tsand inzh.tsexport function useAssistant(control)Closures. The card closed
completedthrough the PR'sFixesline. It was the only issue closed in that window besides objectui#11664, which closed earlier through its own PR objectui#11670.pm:dispatchedis removed in this stroke.Carried for the maintainer, not filed (the seat's answers in the ACCEPT
6009241489; the review judged both inside the card):Left as noted, not filed:
ConsoleLayout.tsx's comment saying the chat chunk downloads lazily is stale:plugin-chatbotis in the eager closure at base.Generated by Claude Code
feat(app-shell,plugin-chatbot,i18n): launchers mark a proposed plan a…