feat(tui-go): interactive clarification question view

Render stage clarification questions as an AskUserQuestion-style form:
header chip, prompt, selectable option chips, and a free-text custom slot.
Arrow/hjkl nav, space to select, e for custom, enter to submit. Submit
guards on all-answered and sends ClarificationResponse; the parked stage
re-runs with the answers in context.
This commit is contained in:
2026-06-14 13:00:59 +04:00
parent 6242b590e4
commit 218a98034e
8 changed files with 584 additions and 3 deletions
@@ -149,6 +149,27 @@ func TestDecodeGoldenServerFrames(t *testing.T) {
}
},
},
{
name: "clarification.required carries structured questions",
json: `{"type":"clarification.required","sessionId":"s1","requestId":"req-1","stageId":"analyst",` +
`"questions":[{"id":"stack","prompt":"Which stack?","options":["React","Vue"],"multiSelect":false,"header":"Stack"}],` +
`"sequence":9,"sessionSequence":4}`,
wantType: TypeClarification,
eventBear: true,
check: func(t *testing.T, m ServerMessage) {
if m.RequestID != "req-1" || m.StageID != "analyst" {
t.Fatalf("clarification ids: %+v", m)
}
if len(m.Questions) != 1 {
t.Fatalf("clarification questions: %+v", m.Questions)
}
q := m.Questions[0]
if q.Prompt != "Which stack?" || q.Header != "Stack" ||
len(q.Options) != 2 || q.Options[0] != "React" {
t.Fatalf("clarification question fields: %+v", q)
}
},
},
}
for _, c := range cases {
@@ -193,6 +214,25 @@ func TestEncodeGoldenClientFrames(t *testing.T) {
}
},
},
{
name: "ClarificationResponse carries ids + answers",
frame: ClarificationResponse("s1", "analyst", "req-1",
[]ClarificationAnswerDto{{QuestionID: "stack", Value: "React"}}),
wantType: clientPrefix + "ClarificationResponse",
check: func(t *testing.T, raw map[string]any) {
if raw["sessionId"] != "s1" || raw["stageId"] != "analyst" || raw["requestId"] != "req-1" {
t.Fatalf("ClarificationResponse ids: %+v", raw)
}
answers, ok := raw["answers"].([]any)
if !ok || len(answers) != 1 {
t.Fatalf("ClarificationResponse answers: %+v", raw["answers"])
}
a := answers[0].(map[string]any)
if a["questionId"] != "stack" || a["value"] != "React" {
t.Fatalf("ClarificationResponse answer: %+v", a)
}
},
},
}
for _, c := range cases {