fix(web): hide ask-user card immediately after submit - #190
Conversation
Optimistically resolve the pending ask_user tool once /api/ask succeeds so the docked card does not stay stuck on "Submitting…" until tool_result.
|
Warning Review incompletejcode 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: Plan: 2 of 2 files indexed · 2 eligible · 172 changed lines This status comment is updated in place. The native review is a separate, non-blocking COMMENT review. |
📝 WalkthroughWalkthroughThe 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. ChangesAsk-user resolution
Estimated code review effort: 3 (Moderate) | ~20 minutes Mergeability Score: 🔵 Low · up to 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
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
web/src/app/store.askUser.test.tsweb/src/app/store.ts
| /** 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 }) | ||
| } |
There was a problem hiding this comment.
🗄️ 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
doneRepository: 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 internalRepository: 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.tsRepository: 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 500Repository: 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)
PYRepository: 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)
PYRepository: 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.
There was a problem hiding this comment.
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.
Summary
/api/asksucceeds, optimistically mark the matchingask_usertool as done and clearaskUserId, so the docked answer card hides immediately instead of lingering in “Submitting…”.tool_resultarrives, avoiding races with the optimistic path.Test plan
cd web && pnpm exec vitest run src/app/store.askUser.test.tscd packages/jcode-ui && pnpm test -- AskUserCard Thread.askUserask_user, submit an option, confirm the docked card disappears right away and the transcript shows the answer receiptSummary by CodeRabbit