Skip to content

feat(studio): the right slot counts the copilot's items awaiting confirmation - #27789

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-pf560g
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-pf560g

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Requested by Bill · project thread

维护者速读

  • 改了什么:Studio 右栏的「AI 副驾」分段上加一个数字徽标,看属性时底部折叠条读「AI 副驾 · N 项待确认 · 回到对话 →」;N 是副驾最新一张「修改方案 · N 项」卡的条数(搭建方案算 1 项)。点了「存为草稿 / 确认」或改动落地,徽标和文字立刻消失。
  • 为什么改:Studio copilot: one right slot that shows either the properties or the copilot — properties follow the selection and mark the copilot's drafts; the copilot folds to a strip while properties show; change card v2; the 变更 badge in sync; no 源码 tab; pillar labels kept #25943 Target 第 1、2 项要「N 项待确认」计数;main 上只有一个「方案待确认」小圆点,而且只认搭建方案,副驾给已上线应用提的「修改方案」卡根本不会点亮它。
  • 风险与回滚:控制台的对话启动器(右下角 FAB、ChatDock 边条)现在在「修改方案」卡待确认时也会亮提示点,这是有意的行为扩展;plugin-chatbot 的回调多一个参数,只读第一个参数的宿主不受影响。回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-chatbot/src/ChatbotEnhanced.tsx(newestProposal 一段)、ui/packages/app-shell/src/assistant/assistantBus.ts(按会话存条数)、ui/packages/app-shell/src/views/studio-design/StudioRightSlot.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.planApprovalCount-25943.test.tsx packages/app-shell/src/views/studio-design/__tests__/studioRightSlot-12725.test.tsx,预期全部通过,其中 zh 用例断言折叠条读「AI 副驾 · 3 项待确认」。
  • 需要你做的:无,席位落地。

Part of #25943

Clause-②: yes (widening)
Metadata-protocol: none

What changes

The card's Target 1–6 were measured on main @ 481ade61 first: items 1–6 already hold (the right slot, select-to-properties, the copilot strip and canvas hint, no 源码 tab, the status line and the item ⋯ menu, change card v2, the 变更 badge and 「草稿 · N 项未发布」, pillar labels from lg), with one gap: the copilot segment and the folded strip carried a boolean 「方案待确认」 dot, not a count, and only for a build plan. This PR closes that gap.

  • @object-ui/plugin-chatbot, ChatbotEnhanced. The newest-proposal reading behind onPlanApprovalPendingChange now also counts a change plan (proposedChanges, the 「修改方案 · N 项」 card). A change plan is pending while its card still offers its confirm / 存为草稿 button: no replay verdict, no later same-tool commit, no click in flight (confirmPendingIds), no quota refusal (quotaBlockedChanges). The callback gains a second argument, count: the rows of a pending change plan, 1 for a pending build plan, 0 when nothing is pending. It fires when either value changes, and still keys on the reading alone, not the callback's identity.
  • @object-ui/app-shell, assistant bus. publishPlanApprovalPending takes an optional count (defaults to 1 while pending) and keeps it per conversation beside the owner. usePlanApprovalPending() is unchanged in meaning (any pending reading of the signed-in user). New usePlanApprovalPendingCount() sums the user's conversations. ChatPane publishes the chat's count with its boolean.
  • Studio right slot. StudioCopilotStatus carries planPendingCount in place of planPending. The copilot tab's dot becomes a count badge, and the strip reads AI 副驾 · {count} 项待确认 (en AI copilot · {count} awaiting your approval) through the new studio key engine.studio.slot.pendingCount; the tab's accessible description is the same sentence.
  • Docs. ui/packages/plugin-chatbot/README.md describes the two-argument callback and the change-plan reading. content/docs/frontend/ has no page that documents this prop, so none changed.

Tests

  • New ChatbotEnhanced.planApprovalCount-25943.test.tsx (5 cases, real wire envelopes through the real mapper, each asserting the reading and the card it mirrors): a change plan awaiting 存为草稿 reads [true, 2]; the click reads [false, 0] at once (applying); a drafted replay reads [false, 0]; a build plan reads [true, 1] and a newer change plan supersedes it with its rows (an unchanged reading is not re-announced); nothing proposed reads [false, 0].
  • New planApprovalPendingCount-25943.test.tsx (4 cases) for the bus: the per-conversation sum, a count change, a reading without a count counts 1, another user's readings are not counted.
  • AiChatPage.planApprovalReachesLaunchers-11666.test.tsx gains one case: the chat's count reaches the bus and its end clears it.
  • StudioDesignSurface.rightSlot-12725.test.tsx: the marker case now counts (badge 2, description and strip text), plus a case with nothing pending. studioRightSlot-12725.test.tsx gains the zh strip case 「AI 副驾 · 3 项待确认」.
  • Five existing slot test doubles move from planPending to planPendingCount.

Readings, all on the final head 413cff81 (the work merged with origin/main) under the verification lock unless a line says otherwise:

Package suites: plugin-chatbot 92 files / 854 tests, i18n 90 files / 1380 passed + 13 skipped
pnpm --filter @objectstack/spec build :: exit 0   (spec moved in the merge)
pnpm --filter @object-ui/plugin-chatbot test :: exit 0 · Test Files 92 passed (92) · Tests 854 passed (854)
pnpm --filter @object-ui/i18n test :: exit 0 · Test Files 90 passed (90) · Tests 1380 passed | 13 skipped (1393)
app-shell and console: the 287 test files that name a touched module, 2130 / 2130 passed

Every packages/app-shell/src/** and apps/console/src/** test file that names StudioDesignSurface, StudioRightSlot, StudioAiCopilot, assistantBus, AiChatPage, ChatDock, ConsoleChatbotFab or planApproval (git grep -l), --reporter=verbose: Test Files 287 passed (287) · Tests 2130 passed (2130), lock VERDICT command-exit 0. The whole @object-ui/app-shell suite (1,911 files) was started and stopped after 16 minutes with no output (non-TTY reporter); it is left to CI Test UI, which shards it.

Type-check and lint
pnpm --filter @object-ui/plugin-chatbot run type-check :: exit 0   (tsc --noEmit && tsc -p tsconfig.test.json)
pnpm --filter @object-ui/i18n run type-check :: exit 0
pnpm --filter @object-ui/app-shell run type-check :: exit 0
eslint --no-inline-config on the 11 changed source and test files :: exit 0 (0 errors; warnings are pre-existing)
pnpm --dir ui run check:new-line-citations :: exit 0 (0 new citations)
node scripts/check-platform-checklist.mjs --changed-from origin/main :: exit 0
Ablations (four, each through scripts/ablation-replace.mjs, restored with blob == HEAD and git diff HEAD empty)
  • ChatbotEnhanced.tsx, the newest-proposal scan without tool.proposedChanges → planApprovalCount-25943: 2 failed / 5 (the change plan reads, and the newer change plan's rows).
  • ChatbotEnhanced.tsx, the count fixed to 1 → planApprovalCount-25943: 2 failed / 5.
  • StudioRightSlot.tsx, the strip text back to engine.studio.slot.planPending → studioRightSlot-12725 zh case and StudioDesignSurface.rightSlot-12725 count case: 2 failed / 23.
  • assistantBus.ts, the sum counting each reading as 1 → planApprovalPendingCount-25943 and the ChatPane count case: 2 failed / 9.
  • app-shell and plugin-chatbot tests import src, so no dist build sits between the mutation and the run.
Derived gates (dispatch-gates --commands on this diff): 35 run, 35 exit 0, reconciled
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

pnpm --dir ui exec vitest run scripts/ first exited 1 with four 15 s timeouts (check-hand-rolled-comment-mask, check-vi-mock-inherit) while it ran beside the locked test run; re-run alone under the lock: Test Files 169 passed | 2 skipped (171) · Tests 4902 passed | 2 skipped. dispatch-gates --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN.

Acceptance notes

  • Card Acceptance. 1: the slot's own cases were already on main (StudioDesignSurface.rightSlot-12725, pillarsClaimSlot-12725, leafColumnSelection-12725, kanbanCardSelection-12725, copilotDraftBadge-12725, itemDraftLine-12725); this PR adds the count. 2: screenshots are NOT MEASURED — a local backend serves no AI agent catalog, so the copilot half never mounts and no proposal can be produced; the DOM is pinned by the jsdom cases above. 3: see the readings above; the whole @object-ui/app-shell suite (1,911 files) is left to CI Test UI.
  • What stays on the card (B, not ui work). The live 草稿 chip and 「由 AI 副驾在 N 分钟前作为草稿加入」 do not show on staging because the copilot's drafts are stamped by: "api": the producer (cloud service-ai-studio, stageDraft / applyDraft) does not pass surface: 'copilot', and this repo's HTTP doors refuse copilot from a client by design. The ui code for the marks is on main. Carrier: cloud.
  • Measured, not a proven ui defect (C). On staging the Interfaces inspector opened hcpl_book.default (404 on GET /meta/view/...) while the copilot drafted hcpl_book.list. Reading of main: listViewTargetOf falls back to OBJECT.default when no merged list view is flagged isDefault, and the runtime's ObjectView promotes the object's embedded list under that same identity (defaultListViewId) as its default tab, so for an object that declares list the inspector and the runtime agree, and the view the copilot edited is a secondary one. They would disagree only for an object with neither list nor a flagged view, where the runtime shows the first list view; whether hcpl_book is that case was not measurable from here. Not changed.
  • Behaviour widened on purpose. The console launchers' waiting marker (objectui#11666) now also lights for a pending change plan, since the reading counts one.
  • The count is the user's, not the package's. Like the boolean before it, the bus sums every conversation of the signed-in user, so a plan awaiting approval in another app's conversation also counts in this Studio's strip.
  • engine.studio.slot.planPending is no longer read but stays in the studio table, so the i18n change only widens; retiring it is a narrowing for a later sweep.
  • The harness's after-PR assignee / review request was skipped on the maintainer's rule (the seat reviews first).
  • An empty change plan. ChatbotEnhanced would report (true, 0) for a pending proposedChanges with no rows; the assistant bus clamps a pending reading to at least 1. No such card reaches the chat today: the mapper lifts proposedChanges only when the envelope carries at least one parseable change row (objectui#8343), so there is no visible defect in this repository. (Added by the seat from contract review 6510852862.)

Verification

See the readings under Tests.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…irmation

The copilot tab badge and the folded strip read 「AI 副驾 · N 项待确认」:
plugin-chatbot's onPlanApprovalPendingChange now also reports the count
(a change plan's rows, 1 for a build plan) and treats a pending change
plan as a proposal; the assistant bus keeps the count per conversation.

Claude-Session: https://claude.ai/code/session_01Utp6CZkqrAJBqDcZ2PKjos
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Contract-review-claim: session_01Utp6CaJoinvGQSbHeDaWBe · head 413cff8 · 2026-10-29T23:20Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 413cff81c047722b6777d4e65b542b41441afcd6
Local-runs: none

Face: declaration only (Clause-②: yes (widening) on the amended claim, the PR body and the changeset). The committed tree touches no packages/spec/src/**, no governed path (check-governed-merges --pr 27789: NOT governed) and no narrowing line. Reviewed 2026-10-29T23:21Z from the diff, the card and the amended claim.

① Derived judgments

  • @object-ui/plugin-chatbot (public; exports . → dist/index.d.ts, ChatbotEnhancedProps re-exported from src/index.tsx): onPlanApprovalPendingChange goes from (pending: boolean) => void to (pending: boolean, count: number) => void. A host callback that declares one parameter stays assignable (TypeScript parameter bivariance/arity), and the extra argument is ignored at runtime, so every existing host compiles and behaves as before on the type. Correct and additive.
  • The reading widens: pending is now also true for a change plan (proposedChanges) whose card still offers its confirm button (no replay verdict, no later same-tool commit, no click in flight via confirmPendingIds, not quotaBlockedChanges). This is a behaviour widening of an existing signal, not a new accept set, and the changeset and README name it, including the side effect that the console's FAB and ChatDock launchers now also mark a pending change plan. That is the card's ask (the slot did not light for a change plan at all). Correct.
  • count = proposedChanges.changes.length for a change plan, 1 for a build plan, 0 when not pending. Edge noted, not blocking: a change plan with an empty changes array would report (true, 0), against the README's "0 when nothing is pending". The app-shell bus clamps it to max(1, count), so no host-visible defect in this repo; a third-party host would read 0 with pending: true. Recorded for the Acceptance notes.
  • @object-ui/i18n (public): adds engine.studio.slot.pendingCount (en, zh), interpolating {count}. engine.studio.slot.planPending stays. No key is renamed or removed.
  • @object-ui/app-shell is private: true, so PlanApprovalReading, publishPlanApprovalPending({count?}), usePlanApprovalPendingCount and StudioCopilotStatus.planPendingCount (renamed from planPending) are not a published surface. usePlanApprovalPendingCount sums over the user's conversations, the same scope usePlanApprovalPending already had.
  • No accept set narrows.

② Semver level

patch for all three packages, matching Metadata-protocol: none (AGENTS.md Post-Task Checklist step 3: the level follows the metadata-protocol kind, and none ships patch). Consistent with the claim, the PR body and the changeset.

③ Boundary flags

  • Scope is A only (the count via the plugin-chatbot prop). C was measured: the inspector matches the runtime default view when a list is declared, so no ui defect is shown; this is recorded in the Acceptance notes. B belongs to the cloud producer and is out of scope. The PR reads Part of #25943, which matches the amended claim.
  • The screenshot is NOT MEASURED (no local AI agent catalog). The badge and strip are pinned by unit tests (studioRightSlot-12725, planApprovalPendingCount-25943, ChatbotEnhanced.planApprovalCount-25943). Accepted.
  • The launcher side effect is named in the PR and changeset. Accepted.
  • PR author is objectstack-fleet[bot] (relay write).

Implemented-by: claude/project-thread-pf560g
Reviewed-by: session_01Utp6CaJoinvGQSbHeDaWBe

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head 413cff81 · 2026-10-29T23:39Z

  • Shape: draft → main, Part of #25943 on its own line, matching the amended claim (scope A only). Clause-②: yes (widening) and Metadata-protocol: none match the claim, the body and the changeset (patch for @object-ui/plugin-chatbot, @object-ui/i18n, @object-ui/app-shell).
  • Contract review: the seat's TIER_DEFAULT declaration-face review on this same head is PASS (6510852862, claim 6510847120). Its one non-blocking note (an empty change plan would report (true, 0); the bus clamps to 1; no such card reaches the chat) is now in the PR's Acceptance notes.
  • Premise, measured: onPlanApprovalPendingChange(pending, count). A pending change plan now counts as a proposal (its card still offers confirm, nothing in flight, no quota refusal). The count is the change plan's rows, or 1 for a build plan. The bus stores {owner, count} per conversation, and usePlanApprovalPendingCount sums it for the signed-in user. Studio's right slot shows a numeric badge on the copilot segment and 「AI 副驾 · N 项待确认」 on the folded strip. Diff read on 413cff81.
  • Gates (check-runs on the head): 46 success, 13 skipped, 0 failure. check-governed-merges --pr 27789: NOT governed, 539 changed lines.
  • Acceptance notes, accepted:
    • The console FAB and ChatDock launchers now also mark a pending change plan; this widening is intended and named.
    • The count is the user's across conversations, the same scope the boolean had.
    • Screenshots are NOT MEASURED (no local agent catalog); the DOM is pinned in jsdom.
    • C was measured: the inspector and the runtime agree when list is declared, so no ui defect is shown.
    • B is the cloud producer's (stageDraft/applyDraft do not pass surface: 'copilot').
  • Landing: non-governed, all checks green, mergeable_state clean ⇒ the seat lands it (pr_ready + automerge_enable). At merge, the card's B remainder goes to triage, because it is another lane's work.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant