Skip to content

Proposed-plan card actions are clickable while the proposing turn is still streaming, which overlaps turns #10925

Description

@hotlong

Source

objectui#10899 item 5b, the replay ordering. The dev root-caused it in PR #10924 on 2026-09-28. Epic objectstack-ai/cloud#2440.

What happens

The proposed-plan card's build / adjust buttons are clickable while the proposing turn is still streaming. In the E2E, 「确认,开始搭建」 was sent while the previous turn was still persisting its tail. That overlapped two turns, and the stored history interleaved.

Direction

The epic PM takes the objectui half of the dev's option C: keep the plan card's actions disabled until the proposing turn has finished streaming. The producer-side fix (ordering by turn in the cloud conversation store) is its own cloud card.

Acceptance

  1. The plan card actions are disabled while their turn streams.
  2. They are enabled after the turn finishes.
  3. A test pins both states.

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 28, 2026
  2. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Serial note, ⛔ not a claim and no state change. From domain:ui seat 4, session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T09:11Z.

    This card is held behind PR #10924 (epic objectui#10899, open and non-draft at this read). That PR edits packages/plugin-chatbot/src/ChatbotEnhanced.tsx, which renders the proposed-plan card whose actions this card disables, and it adds ChatbotEnhanced.hydratedStepTurns-10899.test.tsx beside it. Same file, same region, so it runs serial across rounds.

    Known before dispatch, so the next taker does not re-derive it:

    • The plan card surfaces on objectui main 1dae95a: packages/plugin-chatbot/src/ChatbotEnhanced.tsx and chatMessageAdapter.ts, plus packages/app-shell/src/console/ai/AiChatPage.tsx and hooks/useChatConversation.ts (a git grep for the plan-card copy and for ProposedPlan).
    • The card's own source is PR objectui#10924's root-cause analysis (option C), so read that PR's diff after it lands before designing the disabled state. Its hydration change may already expose whether a turn is still streaming.
    • This seat takes this card once PR objectui#10924 merges, unless another seat claims it first.

    Generated by Claude Code

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 2
    Session: session_015AUunPkX7UTkCH9e7AdZo1
    Account: os-zhuang (the seat's linked user as GET /user answers it; the maintainer authorised this seat to run on it, verbatim 「允许你用当前账户继续开发」, seat-post marker 5864093687)
    Branch: claude/issue-10925-plan-card-wait-stream
    Worktree: objectui-issue-10925
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: packages/plugin-chatbot/src/ChatbotEnhanced.tsx (the proposed-plan card's action buttons: planApprove and the adjust action) and a test beside it under packages/plugin-chatbot/src/__tests__/, plus one .changeset/10925-*.md. packages/app-shell/src/console/ai/AiChatPage.tsx only if the turn's streaming state does not reach the card, and then only the prop that carries it. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (default judgement tier; dispatch-gates --tier --repo objectstack-ai/objectui on these paths: no path-derived mandate)
    Clause-②: no
    Thread-read: 5866915376
    Serial constraints cleared: the serial condition in 5866915376 is met: PR objectui#10924 merged as ac15833eb1. At 2026-09-28T10:01Z no open PR (7) touches ChatbotEnhanced.tsx, and no claim on the lane's 15 pm:dispatched cards holds it (this seat's objectui#10926 names the file only to say it does not touch it). objectui#10900 (seat 1) holds the Build Doctor region of AiChatPage.tsx, and objectui#10926 (this seat) holds its build-thread scope region. If this card must touch that file, it is the plan-card prop only; merge main before opening the PR.

    Take order: p2 bug. The maintainer placed the cloud-epic cards on this seat, verbatim 「全部接 (推荐)」 (this session's chat, 2026-09-28).

    Re-pricing after the unblock (was: behind PR objectui#10924): no effect. PR objectui#10924's ChatbotEnhanced.tsx hunks change how hydrated step turns render (!message.streaming && !showsActivity …, hasVisibleProse). The plan card (proposedPlan on a tool invocation, ChatbotEnhanced.tsx near L286 and L1063–1170) is untouched. The per-message streaming flag the fix needs is already read in the same component.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
    "issue": 10925,
    "status": "done",
    "branch": "claude/issue-10925-plan-card-wait-stream",
    "pr": "#10952",
    "session": "session_015AUunPkX7UTkCH9e7AdZo1",
    "premise_still_valid": true,
    "summary": "The proposed-plan card actions (Build it, Adjust, the one-click answer chips, on the structured and the fallback plan card) now carry the native disabled attribute while isLoading is true, in packages/plugin-chatbot/src/ChatbotEnhanced.tsx; AiChatPage.tsx untouched (it already passes isLoading from useObjectChat). Zone 2 measured true: message.streaming = isLoading on the trailing assistant message (uiMessagesToChatMessages); ai@7.0.65 makeRequest sets status ready only after consumeStream ends; cloud origin/main 2611cdd AIService.streamChatWithTools awaits the store of each tool result and of the final reply before yield finishPart, and ObjectQLConversationService.addMessage awaits engine.insert, so neither signal drops before the tail persists. Zone 3 route changed: the gate reads isLoading, not message.streaming, because the per-message flag is a subset that misses the submitted phase and a newer turn streaming below an older card, both of which overlap turns the same way (pinned by case 5). One patch changeset; Clause-2 stays no (no schema, export or accept set moved; only the isLoading JSDoc text changed).",
    "tests": "All on HEAD 847cb69, clean tree. New pin ChatbotEnhanced.planActionsWaitForTurn-10925.test.tsx (5 cases through the real mapper): Test Files 1 passed (1), Tests 5 passed (5). Ablation via objectstack scripts/ablation-replace.mjs, anchor const planActionsLocked = isLoading; replaced by = false; anchor x1 to x0, blob 1aa9b4bd0f9e to ae9939a163a9, run: Tests 5 failed (5), each toBeDisabled Received element is not disabled; restore blob == HEAD 1aa9b4bd0f9e, git diff HEAD empty; test imports the component from source by relative path, so no build or dist leg. Package suite pnpm exec vitest run --maxWorkers=2 packages/plugin-chatbot/: Test Files 52 passed (52), Tests 553 passed (553); git ls-files lists 52 test files. Closure build pnpm --workspace-concurrency=2 --filter @object-ui/plugin-chatbot^... build: 9 projects in scope, VERDICT command-exit 0. pnpm --filter @object-ui/plugin-chatbot type-check: exit 0; tsc -p tsconfig.test.json --listFiles lists the new test. Lint narrowed: eslint --no-inline-config --format json on the 2 touched ts/tsx files, JSON lists 2 files; ChatbotEnhanced.tsx 0 errors 10 warnings at HEAD and 0/10 at base 5c94589 (same 3 rules), new test 0/0; population from eslint.config.js files **/*.{ts,tsx}; invariance: no type-aware linting (no project, projectService or type-checked preset). Gates exit 0: check-changeset-presence, check-changeset-no-major, check-changeset-overwrite, check-changeset-claims (report-only; names .changeset/6687-chatbot-surface-authorable.md, read, its surface paragraph stays true), check-changeset-fixed, check-pending-changeset-literals, check-new-cross-file-line-citations (0 new), check-control-bytes, check-test-path-roots, check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-lint-coverage, check-i18n-call-site-keys, check-phantom-dependencies, check-package-self-import, check-unreferenced-sources, check-handler-key-read-sites, check-type-check-coverage; check-governed-queue-guard --test: NOT GOVERNED. Every build/test ran through os-verify-lock (slot issue-10925-dev). CI at report time: 42 check runs, 20 success, 3 skipped, 19 in_progress, 0 failed. NOT MEASURED: in-browser or live E2E, reason: no rig in this dispatch; consumer app-shell suites, reason: exported types unchanged apart from JSDoc and the one plan-card consumer test renders isLoading false.",
    "mcp_calls": "0",
    "api_writes": "3 — all through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches each): pr_create to POST /repos/objectstack-ai/objectui/pulls (PR 10952, draft forced); label-write assign to POST /repos//issues/10952/assignees (read back: os-zhuang); os-dev-report comment to POST /repos//issues/10925/comments. Plus 2 git push (branch probe, commit 847cb69), not REST.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed — same family as this card (a confirm action on a proposal card that sends the next turn while the proposing turn streams): the confirm-change card proposed-changes-confirm sends changesConfirmMessage and stays enabled while isLoading is true. One-off probe, not kept: update_metadata changes_proposed on the streaming tail, isLoading true, confirm toBeEnabled and a click sent the message (1 passed). No public-door measurement, so no reach; the same planActionsLocked guard would close it. In PR Acceptance notes. Dedupe words: proposed-changes confirm streaming, changesConfirmMessage isLoading, confirm card overlaps turns"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR #10952 @ 847cb69

    domain:ui seat 4, session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T10:49Z. Checked against GitHub and origin/main, not against the report.

    • Form: draft, base main, body first line Fixes #10925, and no other closing keyword. 3 files, +283/−6: packages/plugin-chatbot/src/ChatbotEnhanced.tsx, one pin, and .changeset/10925-plan-card-wait-stream.md. All are inside the claim's file surface. AiChatPage.tsx is untouched, and the file does not overlap the held PR feat(app-shell): the console dock binds its build thread to the current authorable app (objectui#10926) #10950. Nothing under content/docs/releases/.
    • CI on the head 847cb69341: 43 check-runs, 40 success and 3 skipped, none failed. Governed-surface guard (scripts/check-governed-queue-guard.mjs --test, 3 paths): NOT GOVERNED.
    • Contract review: PASS at CONTRACT_REVIEW_TIER on this head, 5868396957. @object-ui/plugin-chatbot patch matches a fix with no export, prop or type change (JSDoc only). Clause-②: no holds. Every changeset sentence is true against the plugin source and cloud's streamChatWithTools store-before-finish ordering.
    • Route deviation accepted: the gate reads the component-wide isLoading, not the suggested per-message streaming. The per-message flag is derived from isLoading, and it misses two windows that overlap turns the same way: submitted with no reply yet, and a newer turn below an older card. Case 5 pins both. The cost is that an older plan card also waits while a newer turn is in flight. That matches the composer, which the same signal already holds, so no capability is lost. Adjust only focuses the composer, which is disabled in that window.
    • Acceptance:
      • 1: the structured approve and adjust, the fallback approve and adjust, and the one-click chips carry native disabled while a turn is in flight.
      • 2: they are enabled at ready.
      • 3: five cases through the real mapper pin both states. The report's ablation (planActionsLocked = false) turns all five red.
    • Out-of-scope finding (same defect class, routed to the maintainer, not filed): the proposed-changes confirm card (proposed-changes-confirm, handleChangesConfirm) sends the next turn and stays enabled while isLoading is true. The dev's probe measured a click sending the message. The contract review confirms it is the same class and leaves the carrier open: this PR, a new card under epic objectstack-ai/cloud#2440, or nowhere. The seat does not widen this PR: the confirm card has its own state machine, and it is outside the claim. The question is in the seat's round report to the maintainer.

    Next: ready → auto-merge (merge queue), followed to MERGED by this seat.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions