Skip to content

fix(web): hide ask-user card immediately after submit - #190

Merged
cnjack merged 1 commit into
mainfrom
fix/ask-user-hide-after-submit
Aug 13, 2026
Merged

fix(web): hide ask-user card immediately after submit#190
cnjack merged 1 commit into
mainfrom
fix/ask-user-hide-after-submit

Conversation

@cnjack

@cnjack cnjack commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • After /api/ask succeeds, optimistically mark the matching ask_user tool as done and clear askUserId, so the docked answer card hides immediately instead of lingering in “Submitting…”.
  • Clear pending ask-user markers when a real tool_result arrives, avoiding races with the optimistic path.
  • Add store coverage for format/output, successful submit, and failed submit.

Test plan

  • cd web && pnpm exec vitest run src/app/store.askUser.test.ts
  • cd packages/jcode-ui && pnpm test -- AskUserCard Thread.askUser
  • Manually: trigger ask_user, submit an option, confirm the docked card disappears right away and the transcript shows the answer receipt

Summary by CodeRabbit

  • Bug Fixes
    • Completed ask-user interactions now resolve immediately after successful submission.
    • Prevented completed ask-user cards from reappearing.
    • Improved answer display for empty, single-answer, and multiple-answer responses.
    • Pending interactions are preserved when answer submission fails.

Optimistically resolve the pending ask_user tool once /api/ask succeeds so
the docked card does not stay stuck on "Submitting…" until tool_result.
@jcode-cloud-app

jcode-cloud-app Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review incomplete

jcode did not reach a clean conclusion; a partial native review was published separately.

Pull request: #190 · fix(web): hide ask-user card immediately after submit

Revision: ebb787b9dd27

Plan: 2 of 2 files indexed · 2 eligible · 172 changed lines

View run


This status comment is updated in place. The native review is a separate, non-blocking COMMENT review.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The store now formats ask-user answers, clears pending interaction markers, and resolves ask-user tools optimistically after successful submission. Tests cover empty, single, and multiple answers, success, failure, and state cleanup.

Changes

Ask-user resolution

Layer / File(s) Summary
Answer formatting and resolution state
web/src/app/store.ts
The store adds formatAskUserOutput and resolveAskUserItem. Ask-user resolution now stores formatted answers and clears pending markers.
Optimistic submission and validation
web/src/app/store.ts, web/src/app/store.askUser.test.ts
submitAskUser uses the imported AskUserAnswer type and resolves the tool after a successful API submission. Tests cover output formatting, state updates, API success, API failure, and cleanup.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: 🔵 Low · up to ebb78

The PR hides the ask-user card immediately after submission, but its optimistic receipt can differ from the backend receipt for some multiple-answer values, affecting transcript consistency and replay. The change is otherwise mergeable with explicit owner follow-up to align serialization and add coverage.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant submitAskUser
  participant AskUserAPI
  participant AskUserStore
  User->>submitAskUser: submit answers
  submitAskUser->>AskUserAPI: send answers
  AskUserAPI-->>submitAskUser: successful response
  submitAskUser->>AskUserStore: dispatch optimistic resolution
  AskUserStore->>AskUserStore: format answers and clear pending state
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: hiding the ask-user card immediately after submission.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ask-user-hide-after-submit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/app/store.ts`:
- Around line 927-938: Update formatAskUserOutput to serialize multiple answers
using the backend’s JSON behavior: omit empty selected fields and apply
Go-compatible HTML escaping for <, >, and &. Reuse the existing answer
serialization conventions if available, and add tests covering empty selected
arrays and HTML-sensitive answer text while preserving the single-answer and
empty-answer outputs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 889c98b3-0a28-4912-8532-7a55021475dd

📥 Commits

Reviewing files that changed from the base of the PR and between 0a78625 and ebb787b.

📒 Files selected for processing (2)
  • web/src/app/store.askUser.test.ts
  • web/src/app/store.ts

Comment thread web/src/app/store.ts
Comment on lines +927 to +938
/** Mirror the ask_user tool's formatBatchResponse so optimistic receipts match
* the eventual tool_result / session replay text. */
export function formatAskUserOutput(answers: AskUserAnswer[]): string {
if (answers.length === 0) return 'The user did not provide any answers.'
if (answers.length === 1) {
const ans = answers[0]
const text = ans.answer || (ans.selected?.length ? ans.selected.join(', ') : '')
if (!text) return 'The user did not provide an answer.'
return `User's answer: ${text}`
}
return JSON.stringify({ answers })
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the backend formatter before inspecting its implementation.
fd -t f -e go -e ts -e tsx . | while IFS= read -r file; do
  if rg -q '\bformatBatchResponse\b' "$file"; then
    ast-grep outline "$file" --items all
    rg -n -C 12 'formatBatchResponse|ask_user|The user did not provide' "$file"
  fi
done

Repository: cnjack/jcode

Length of output: 23858


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- backend formatter ---'
sed -n '285,330p' internal/tools/ask_user.go

printf '%s\n' '--- frontend answer type and ask API flow ---'
rg -n -C 12 'interface AskUserAnswer|type AskUserAnswer|answers: AskUserAnswer|resolveAskUser|api\.ask|/api/ask|tool_result|session replay' web/src

printf '%s\n' '--- all selected/answer serialization sites ---'
rg -n -C 8 'question_header|selected|JSON\.stringify\(\{ answers|formatAskUserOutput' web/src internal

Repository: cnjack/jcode

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- backend formatter ---'
sed -n '295,322p' internal/tools/ask_user.go

printf '%s\n' '--- frontend answer type ---'
sed -n '690,714p' web/src/lib/types.ts

printf '%s\n' '--- ask resolution call sites ---'
rg -n -C 10 --glob '*.ts' --glob '*.tsx' \
  'formatAskUserOutput|resolveAskUserItem|api\.ask|askPending|answers' \
  web/src/app web/src/lib web/src/components/AskUser*

printf '%s\n' '--- replay and tool-result handling ---'
rg -n -C 10 --glob '*.ts' --glob '*.tsx' \
  'tool_result|session replay|replayTimeline|displayOutput|output' \
  web/src/app/store.ts web/src/lib/types.ts

Repository: cnjack/jcode

Length of output: 19742


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ask-user UI answer construction ---'
rg -l --glob '*.ts' --glob '*.tsx' 'submitAskUser|selected:|question_header' web/src | sort
rg -n -C 14 --glob '*.ts' --glob '*.tsx' \
  'submitAskUser|selected:|question_header' \
  web/src | rg -v 'Artifacts|SidebarFilter|selectedIdx|selectedFile' | head -n 500

printf '%s\n' '--- relevant backend API decoding and result emission ---'
rg -n -C 12 \
  'AskUserBatchResponse|formatBatchResponse|/api/ask|BatchRequestFn|tool_result|ToolMessage' \
  --glob '*.go' internal cmd . | head -n 500

Repository: cnjack/jcode

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ask API handlers ---'
rg -n -C 18 \
  'HandleFunc.*ask|/api/ask|askRequest|AskUserAnswer|BatchResponseCh|BatchRequestFn' \
  --glob '*.go' internal cmd | head -n 700

printf '%s\n' '--- standalone formatter comparison ---'
python3 - <<'PY'
import json

def frontend(answers):
    if not answers:
        return 'The user did not provide any answers.'
    if len(answers) == 1:
        ans = answers[0]
        text = ans.get('answer') or (', '.join(ans['selected']) if ans.get('selected') else '')
        return "The user did not provide an answer." if not text else f"User's answer: {text}"
    return json.dumps({'answers': answers}, ensure_ascii=False, separators=(',', ':'))

def go_json(answers):
    # Model encoding/json for AskUserBatchResponse and AskUserAnswer:
    # selected has `omitempty`; Go escapes HTML-sensitive characters.
    out = []
    for ans in answers:
        item = {
            'question_header': ans.get('question_header', ''),
            'answer': ans.get('answer', ''),
        }
        if ans.get('selected'):
            item['selected'] = ans['selected']
        out.append(item)
    return json.dumps({'answers': out}, ensure_ascii=False, separators=(',', ':')) \
        .replace('&', '\\u0026').replace('<', '\\u003c').replace('>', '\\u003e')

cases = [
    [],
    [{'question_header': 'H', 'answer': ''}],
    [{'question_header': 'H', 'answer': '', 'selected': ['A', 'B']}],
    [
        {'question_header': 'H1', 'answer': 'A', 'selected': []},
        {'question_header': 'H2', 'answer': 'B'},
    ],
    [
        {'question_header': 'H1', 'answer': '<A&'},
        {'question_header': 'H2', 'answer': 'B'},
    ],
]
for case in cases:
    f = frontend(case)
    g = 'The user did not provide any answers.' if not case else (
        "The user did not provide an answer." if len(case) == 1 and not (case[0].get('answer') or case[0].get('selected')) else
        f"User's answer: {case[0].get('answer') or ', '.join(case[0].get('selected', []))}" if len(case) == 1 else
        go_json(case)
    )
    print('MATCH' if f == g else 'DIFF')
    print(' frontend:', f)
    print(' backend :', g)
PY

Repository: cnjack/jcode

Length of output: 45391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ask API handlers ---'
rg -n -C 18 \
  'HandleFunc.*ask|/api/ask|askRequest|AskUserAnswer|BatchResponseCh|BatchRequestFn' \
  --glob '*.go' internal cmd | head -n 700

printf '%s\n' '--- standalone formatter comparison ---'
python3 - <<'PY'
import json

def frontend(answers):
    if not answers:
        return 'The user did not provide any answers.'
    if len(answers) == 1:
        ans = answers[0]
        text = ans.get('answer') or (', '.join(ans['selected']) if ans.get('selected') else '')
        return 'The user did not provide an answer.' if not text else f"User's answer: {text}"
    return json.dumps({'answers': answers}, ensure_ascii=False, separators=(',', ':'))

def go_json(answers):
    out = []
    for ans in answers:
        item = {
            'question_header': ans.get('question_header', ''),
            'answer': ans.get('answer', ''),
        }
        if ans.get('selected'):
            item['selected'] = ans['selected']
        out.append(item)
    return json.dumps({'answers': out}, ensure_ascii=False, separators=(',', ':')) \
        .replace('&', '\\u0026').replace('<', '\\u003c').replace('>', '\\u003e')

cases = [
    [],
    [{'question_header': 'H', 'answer': ''}],
    [{'question_header': 'H', 'answer': '', 'selected': ['A', 'B']}],
    [
        {'question_header': 'H1', 'answer': 'A', 'selected': []},
        {'question_header': 'H2', 'answer': 'B'},
    ],
    [
        {'question_header': 'H1', 'answer': '<A&'},
        {'question_header': 'H2', 'answer': 'B'},
    ],
]
for case in cases:
    f = frontend(case)
    if not case:
        g = 'The user did not provide any answers.'
    elif len(case) == 1:
        text = case[0].get('answer') or ', '.join(case[0].get('selected', []))
        g = "The user did not provide an answer." if not text else f"User's answer: {text}"
    else:
        g = go_json(case)
    print('MATCH' if f == g else 'DIFF')
    print(' frontend:', f)
    print(' backend :', g)
PY

Repository: cnjack/jcode

Length of output: 45391


Match Go json.Marshal for multiple-answer receipts.

JSON.stringify({ answers }) retains selected: [] and leaves <, >, and & unescaped. The backend uses selected,omitempty and Go HTML escaping, so optimistic output can differ from the later tool_result and session replay. Normalize the JSON serialization and add tests for these cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/app/store.ts` around lines 927 - 938, Update formatAskUserOutput to
serialize multiple answers using the backend’s JSON behavior: omit empty
selected fields and apply Go-compatible HTML escaping for <, >, and &. Reuse the
existing answer serialization conventions if available, and add tests covering
empty selected arrays and HTML-sensitive answer text while preserving the
single-answer and empty-answer outputs.

@jcode-cloud-app jcode-cloud-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note

No high-confidence findings

No findings met the configured confidence threshold.

Summary

Clean. The PR adds an optimistic resolve for the ask_user docked card (resolveAskUserItem reducer + formatAskUserOutput helper, dispatched on successful /api/ask, with defensive marker cleanup in applyResolvedToolFields). formatAskUserOutput faithfully mirrors the backend formatBatchResponse for empty/skip, single-answer (free-text and selected-only), and multi-question JSON branches (matching field order and selected omission), so the optimistic receipt matches the later real tool_result. resolveAskUserItem only fires after api.askUser succeeds, so the card is never hidden before the backend is unblocked, and the late tool_result path overwrites the optimistic output cleanly. The new store.askUser.test.ts passes 4/4 and the broader web store suite shows no regressions (2 unrelated failures are environmental: the jcode-ui package is not built locally). No verified defects.

🔍 Checks performed · 10
  • Read full diff at .git/jcode-review.diff
  • Read store.ts: applyResolvedToolFields, resolveAskUserItem, submitAskUser, attachAskUser, tool_result reducer
  • Read internal/tools/ask_user.go formatBatchResponse/runSingle/runBatch to verify output-format parity
  • Read internal/web/approval.go handleAskUser to verify answer round-trip and AskUserBatchResponse construction
  • Read packages/jcode-ui-core AskUserBlock.tsx and jcode-ui AskUserCard.tsx to verify answers.length == questions.length in submit/skip flows
  • Confirmed formatAskUserOutput mirrors backend for empty, single (free-text/selected), and multi-question JSON (field order + selected omission)
  • Verified resolveAskUserItem only dispatched after await api.askUser succeeds; failure path preserves pending card
  • Ran vitest src/app/store.askUser.test.ts after stubbing gitignored themes.generated.ts: 4/4 pass
  • Ran full web vitest suite: 137 pass; 2 failures are environmental (unbuilt jcode-ui package, unrelated to PR)
  • Ran tsc -p tsconfig.app.json: no new errors in store.ts or the new test beyond environmental missing jcode-ui-core

jcode posts a non-blocking COMMENT review. Merge decisions remain with your team.

@cnjack
cnjack merged commit 8fb0a54 into main Aug 13, 2026
4 checks passed
@cnjack
cnjack deleted the fix/ask-user-hide-after-submit branch August 13, 2026 02:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant