Skip to content

refactor(chat): unify ask-user flow on <question-form>, delete AskUserQuestion mechanism#4114

Merged
lefarcen merged 4 commits into
mainfrom
feat/new-work-0610-4
Jun 10, 2026
Merged

refactor(chat): unify ask-user flow on <question-form>, delete AskUserQuestion mechanism#4114
lefarcen merged 4 commits into
mainfrom
feat/new-work-0610-4

Conversation

@elihahah666

Copy link
Copy Markdown
Contributor

Why

While building on Open Design I hit a broken UI: a chat message showed a
"Question" card whose text status badge ("Awaiting your answer") was stuffed
into the 24px .op-status icon badge and overflowed onto the title. Digging in,
the real problem was bigger than CSS — the repo carries two parallel
mechanisms
for the same job of asking the user a clarifying question:

  • A — the AskUserQuestion SDK tool: an inline interactive card in the chat
    stream (AskUserQuestionCard / StreamingAskUserQuestionCard), whose answer
    is fed back into the still-open stream-json child via
    POST /api/runs/:id/tool-result + submitChatRunToolResult.
  • B — the <question-form> artifact: a QuestionsBanner entry point in
    chat, the form rendered in the right-hand Questions tab
    (QuestionsPanel / QuestionFormView), answers returned as the next user
    message.

Two visual languages and two interaction models for one concept, with A's card
visually broken, and an artificial split where A was pinned to mid-conversation
clarification and B to turn-1 discovery. This is the technical-debt / UX-fix that
the PR addresses: delete A, keep only B, and widen <question-form> to any
turn
so mid-conversation clarification flows through the same
banner → Questions tab → next-user-message loop.

What users will see

  • The broken inline "Question" card is gone. Every clarifying question — whether
    the first-turn discovery brief or a mid-conversation "you marked a region but
    didn't say what to change" — now appears as the "Answer a few questions…"
    banner
    in chat, opening the form in the right-hand Questions tab. One
    consistent surface instead of two.
  • No behavior is gated behind a setting; this is the default ask-user flow.

Surface area

  • API / contract — removes the POST /api/runs/:id/tool-result endpoint
    and the submitChatRunToolResult web caller (return-path deletion).
  • i18n keys — removes the tool.askQuestion* keys from types.ts and
    all locales.
  • Default behavior change — clarifying questions render via the Questions
    tab banner instead of an inline chat card.

(No new UI page/dialog is added — the surviving surface, QuestionsBanner +
Questions tab, already existed. The system-prompt change that widens
<question-form> to any turn is prompt-layer only; the front-end already
supported a question-form in any assistant message.)

Screenshots

Before: the broken inline card (status text overflowing the icon badge) — see
the originating report. After: clarifying questions surface as the existing
QuestionsBanner → Questions tab, validated live on an isolated worktree
runtime. No new UI chrome is introduced, so there is no new entry point to show
beyond the pre-existing banner.

Bug fix verification

This is a refactor that also removes a visual bug whose fix is structural
(deleting the broken card entirely), so the acceptance is the migrated test
suite plus human validation rather than a single red-spec:

  • apps/daemon/tests/chat-run-artifact-quiet-period.test.ts — the obsolete
    pendingHostAnswers-pending assertion was removed and replaced with two specs
    pinning the simplified stdin-close decision (closes on a non-tool_use clean
    terminal turn; stays open on tool_use).
  • apps/daemon/tests/run-artifacts.test.tsrunAskedUserQuestion now asserts
    detection of a <question-form> marker (incl. one split across text_delta
    chunks), replacing the AUQ-tool-use detection.
  • Human-validated in the browser on an isolated worktree runtime: triggered a
    mid-conversation clarification and confirmed the banner → Questions tab → reply
    loop, and that the broken card no longer appears.

Validation

  • pnpm guard (54 pass), pnpm typecheck (all packages green).
  • pnpm --filter @open-design/web test, pnpm --filter @open-design/contracts test.
  • pnpm --filter @open-design/daemon test4351 pass / 4 skip / 4 todo,
    346 files, exit 0.
  • mocks replay: pulled the Claude recording subset and replayed 04097377
    (17-tool single turn) and 21747360 ([form answers — discovery]) through the
    mock CLI — 0 parse failures, well-formed stream-json terminating on end_turn
    (the clean-terminal path the simplified bookkeeping closes stdin on).

Adjacent / out of scope

  • docs/plans/2026-06-01-sandbox-orchestration-hardening-architecture.md item I6
    proposes hardening POST /api/runs/:id/tool-result. That endpoint is deleted
    by this PR, so I6 is now moot — left untouched here as a dated archival
    plan; flagging it so a future contributor doesn't harden a removed endpoint.

…rQuestion mechanism

There were two parallel "ask the user a clarifying question" mechanisms:

  A. The AskUserQuestion SDK tool — an inline interactive card in the chat
     stream (AskUserQuestionCard / StreamingAskUserQuestionCard), whose answer
     was fed back into the still-open stream-json child via
     POST /api/runs/:id/tool-result + submitChatRunToolResult.
  B. The <question-form> markdown artifact — a QuestionsBanner entry point in
     chat, the form rendered in the right-hand Questions tab
     (QuestionsPanel / QuestionFormView), answers returned as the next user
     message.

Two visual languages for the same job, and mechanism A's card was visually
broken (the text status "Awaiting your answer" was stuffed into the 24px
.op-status icon badge and overflowed onto the title). Mechanism A was pinned to
mid-conversation clarification while B was pinned to turn-1 discovery — an
artificial split.

This deletes mechanism A entirely and keeps only B, and widens <question-form>
from "turn-1 discovery only" to ANY turn so mid-conversation clarification flows
through the same banner → Questions tab → next user message loop.

Web:
- Delete apps/web/src/runtime/ask-user-question.ts (parser trio).
- ToolCard.tsx: drop the AUQ card/streaming-card/dispatch/imports and the
  onAnswerToolUse / onSubmitForm props.
- AssistantMessage.tsx: drop the live AUQ render path,
  suppressAskUserQuestionFallbackText, the AUQ name from SNAPSHOT_TOOL_NAMES,
  and the onSubmitForm bridge. Keep suppressDuplicateQuestionForms.
- Delete the whole onSubmitForm / onAssistantFormSubmitStart return chain
  across AssistantMessage / ChatPane / ProjectView / SideChatTab, and
  submitChatRunToolResult in providers/daemon.ts.

Daemon (conservative — keep the generic stream-json input skeleton):
- applyClaudeStreamJsonRunBookkeeping: drop the AUQ detection branch and
  run.pendingHostAnswers; stdin now closes on any non-tool_use clean terminal
  turn.
- Delete run-tool-results.ts, submitToolResultToRun, and the
  POST /api/runs/:id/tool-result endpoint.
- run-artifacts.ts:runAskedUserQuestion now scans streamed text for a
  <question-form> marker (reassembled across text_delta chunks) instead of an
  AUQ tool_use, preserving the run_finished.asked_user_question analytics
  signal.
- Keep --input-format stream-json / promptInputFormat: 'stream-json' as generic
  mid-turn input infra; refresh the comments that referenced AUQ.

System prompt / contracts:
- Replace the Claude-only AskUserQuestion clarification section with generic
  "emit <question-form> for mid-conversation clarification" guidance; relax the
  API-mode override wording; drop "Do not call AskUserQuestion" from the
  skip-discovery override.

i18n / CSS:
- Remove the tool.askQuestion* keys from types.ts + all locales.
- Remove the op-ask-question* block and .op-status-awaiting from tools.css;
  keep .op-status (the icon badge) and the qf-* classes that mechanism B uses.

Docs / tests:
- AGENTS.md: rewrite the runtime-conventions + chat-UI sections; add an
  "Asking the user questions" section documenting the single mechanism.
- Delete the AUQ-only tests; rewrite the quiet-period bookkeeping, run-artifacts,
  prompt, and plugin-folder tests to the new behavior.

Validation: pnpm guard (54), full typecheck, web tests, daemon suite
(4351 pass), contracts tests; replayed two Claude stream-json traces through the
mock CLI (incl. the [form answers — discovery] trace) — 0 parse failures, clean
end_turn termination.
@lefarcen lefarcen requested a review from Siri-Ray June 10, 2026 17:35
@lefarcen lefarcen added size/XXL PR changes 1500+ lines risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps labels Jun 10, 2026
@lefarcen lefarcen requested a review from nettee June 10, 2026 17:37
@lefarcen lefarcen added the type/refactor Code refactor (no behavior change) label Jun 10, 2026
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Visual regression review

Head: 0f1585a · Base: 4c8914d

11 changed · 24 unchanged · 0 missing baseline · 0 failed

Changed cases

Case Main PR Diff
visual-home-plugin-use-staged main pr diff
visual-home-plugin-use-with-query main pr diff
visual-new-project-modal main pr diff
visual-project-avatar-model-dropdown main pr diff
visual-project-workspace main pr diff
visual-settings-byok main pr diff
visual-settings-byok-model-dropdown main pr diff
visual-settings-byok-openai main pr diff
visual-settings-execution main pr diff
visual-settings-local-cli main pr diff
visual-workspace-staged-contexts main pr diff
Unchanged cases
Case Main PR Diff
visual-avatar-local-agent-list main pr diff
visual-avatar-menu main pr diff
visual-design-system-detail main pr diff
visual-design-systems main pr diff
visual-home main pr diff
visual-home-catalog main pr diff
visual-home-context-picker main pr diff
visual-home-plugin-filter main pr diff
visual-home-staged-attachment main pr diff
visual-integrations main pr diff
visual-integrations-mcp main pr diff
visual-integrations-use-everywhere main pr diff
visual-onboarding-runtime main pr diff
visual-plugin-details main pr diff
visual-plugin-share-menu main pr diff
visual-plugins main pr diff
visual-projects main pr diff
visual-projects-kanban main pr diff
visual-settings-local-cli-model-dropdown main pr diff
visual-tasks main pr diff

Visual diff is advisory only and does not block merging.

@Siri-Ray Siri-Ray left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihahah666 thanks for the thoughtful cleanup here. I reviewed the daemon stdin lifecycle changes, the question-form rendering path, the endpoint/caller deletion, and the prompt/analytics updates. I found one non-blocking consistency issue where the newly documented API/BYOK mirroring does not appear to be implemented yet; details inline.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread AGENTS.md

@nettee nettee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found two blocking regressions in the unified ask-user flow: the contracts/API prompt path is still turn-1-only, and the AUQ removal drops the compatibility path for already-persisted AUQ runs. I also left one smaller inline note on the new analytics detector.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread packages/contracts/src/prompts/system.ts
Comment thread apps/daemon/src/server.ts
Comment thread apps/daemon/src/run-artifacts.ts Outdated
…lytics alias

Address review feedback on the ask-user unification:

- contracts composer (used by API/BYOK 'plain' mode) now mirrors the
  daemon-side '## Clarifying questions mid-conversation' section and widens
  the API_MODE_OVERRIDE allowed-output list from 'discovery on turn 1' to
  'discovery (turn 1) and mid-conversation clarification', so BYOK/API runs
  route follow-up choices through the unified Questions tab instead of
  drifting back to plain markdown option lists. Makes the AGENTS.md
  'mirrored through packages/contracts/src/prompts/system.ts' claim true.
- runAskedUserQuestion now matches the '<ask-question>' alias (already
  whitelisted by the UI parser and daemon open-tag matcher), so a model that
  drifts to the alias still records run_finished.asked_user_question instead
  of being misclassified in the artifact funnel.
- Tests: contracts api-mode asserts mid-conversation forms are permitted in
  both daemon and plain mode; run-artifacts asserts the alias is detected.
@lefarcen lefarcen requested review from Siri-Ray and nettee June 10, 2026 18:04

@Siri-Ray Siri-Ray left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihahah666 thanks for continuing to tighten this ask-user unification. I re-reviewed the current head across the daemon stdin lifecycle, question-form prompt updates, web rendering path, and the follow-up alias analytics fix. I found one non-blocking consistency issue in the newly added alias handling; details inline.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread apps/daemon/src/run-artifacts.ts Outdated

@nettee nettee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihahah666 I re-checked the current f8dd697 head after the follow-up fixes. The prompt mirroring and alias analytics updates look better, but I still found two merge-safe implementation follow-ups in the legacy-history and alias replay paths; details inline.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread apps/web/src/components/ToolCard.tsx
Comment thread apps/daemon/src/run-artifacts.ts Outdated
…gacy AUQ renderer

Follow-ups from re-review of the ask-user unification:

- The <ask-question> alias is already accepted by the UI parser and the
  daemon open-tag matcher, but two consumers still only recognized the
  canonical <question-form>: db.listProjectsAwaitingInput's awaiting-input
  filter and the next-turn transcript sanitizer. Both now match either tag,
  so an alias-form turn (a) marks the project awaiting input and (b) gets
  scrubbed from the prior assistant turn instead of replaying verbatim and
  re-triggering the discovery-form loop.
- Re-add a read-only legacy AskUserQuestion renderer in ToolCard. The
  interactive mechanism is gone, but persisted AUQ tool_use events survive
  in upgraded chat history; without this they fell through to GenericCard and
  surfaced the raw {"questions":[...]} JSON. The card now renders an inert
  question summary (model-authored text, no new i18n keys, no submit path).

Tests: alias awaiting-input case (project-status), alias-form sanitizer
regression (sse), and legacy AUQ render + unparseable fallback (tool-renderers).
@lefarcen lefarcen requested review from Siri-Ray and nettee June 10, 2026 18:22

@nettee nettee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihahah666 I re-reviewed the current da000d3 head across the unified question-form flow, the alias follow-ups, and the legacy AUQ compatibility path. The main refactor looks much tighter now, but I still found two merge-safe implementation issues in the new analytics matcher and the historical AUQ fallback renderer; details are inline.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread apps/daemon/src/run-artifacts.ts Outdated
Comment thread apps/web/src/components/ToolCard.tsx Outdated

@Siri-Ray Siri-Ray left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihahah666 thanks for continuing to tighten this cleanup. I re-reviewed the current head across the daemon run lifecycle, question-form prompt path, legacy AUQ rendering, alias handling, and the updated tests. I found one merge-safe analytics regression in the new question-form detector; details inline.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread apps/daemon/src/run-artifacts.ts Outdated
…endering

Re-review follow-ups:

- runAskedUserQuestion read `data.text` for text_delta events, but the
  persisted SSE shape carries the chunk on `delta` (packages/contracts/src/
  sse/chat.ts), so production <question-form> runs appended nothing and
  run_finished.asked_user_question was permanently false (the old tests passed
  only because they built the wrong `text` field). Now reads `delta` (still
  accepts `text` defensively).
- It also matched a bare open-tag, so a run that merely shows the literal
  markup in a generated doc / code sample / HTML artifact could be
  misclassified as a clarification turn and excluded from the artifact funnel.
  Now requires a renderable closed block via a shared detector.
- Extracted the renderable-form detector + helpers out of server.ts into
  apps/daemon/src/question-form-detect.ts so the missing-artifacts guard,
  awaiting-input status, and run analytics share ONE check (the codebase
  comment already anticipated promoting this once it had a third consumer).
  The `<ask-question>` alias is covered by that shared matcher.
- LegacyAskUserQuestionCard now parses the persisted answer from
  result.content and surfaces it ("question -> answer") so two historical AUQ
  runs with different answers no longer render identically.

Tests: run-artifacts now uses the real `delta` shape + renderable bodies, adds
a literal-markup negative case; tool-renderers asserts the persisted answer is
surfaced.
@lefarcen lefarcen requested review from Siri-Ray and nettee June 10, 2026 19:13

@nettee nettee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihahah666 I re-reviewed the current 0f1585a head across the shared renderable question-form detector, the run_finished.asked_user_question follow-up, and the legacy AskUserQuestion history rendering. The latest fixes line up with the earlier review threads, and I did not find any further actionable issues in the changed ranges on this head.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@Siri-Ray Siri-Ray left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihahah666 I re-reviewed the current head across the removed AskUserQuestion/tool-result path, the unified <question-form> prompt flow, daemon analytics and awaiting-input detection, the legacy AUQ history renderer, and the web transcript sanitization updates. The prior concerns around API/BYOK prompt mirroring, alias handling, persisted text_delta.delta, and legacy answer visibility look addressed in this revision. I did attempt focused local tests, but this reviewer worktree has no node_modules, so vitest was unavailable; the static pass did not turn up any actionable changed-range issues. Thanks for continuing to tighten this cleanup and for keeping the legacy history path legible.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen lefarcen added this pull request to the merge queue Jun 10, 2026
@lefarcen

Copy link
Copy Markdown
Contributor

All gates green — @nettee and @Siri-Ray both approved the latest head and CI is passing. Great work iterating through those review rounds carefully, @elihahah666 🎉

💡 To drive this PR to merge hands-free, paste this to your AI coding agent (Claude Code / Codex / opencode / Cursor …):
Take over nexu-io/open-design#4114 until it merges — read https://raw.githubusercontent.com/nexu-io/looper/main/skills/pr-takeover/SKILL.md and follow it.

Merged via the queue into main with commit ca22620 Jun 10, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps size/XXL PR changes 1500+ lines type/refactor Code refactor (no behavior change)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants