fix(l1): answer buttons must match the question — yes_label/no_label end-to-end
Live walk defect: the builder generated alternatives questions ("Is Jane's
account a Microsoft account or a local account?") while the UI could only
offer Yes/No. Root cause: SYSTEM_PROMPT mandated a label-less
'<yes/no question>' shape with no way to express the two answers.
- SYSTEM_PROMPT: question nodes must carry yes_label/no_label — the literal
button texts; alternatives questions must use the alternatives as labels.
- validate_node: labels hard-floor-scanned, must be distinct non-empty strings.
- _ensure_labels: server defaults missing labels to Yes/No.
- advance_ai_build: records answer_label (and both labels) in walked_path,
derived from the server-held pending_node — never client-supplied.
- _build_context: LLM context shows the chosen label, not a bare yes/no
(a raw "-> yes" on an alternatives question degrades the next generation).
- normalize_walked_path: captured flywheel trees keep question labels.
- Frontend: buttons render yes_label/no_label; walk transcript and
L1EscalationsSection render answer_label.
Phase 2A backend set: 137 passed / 0 failed / 8 deselected. tsc, eslint,
vite build clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,7 @@ export function L1EscalationsSection() {
|
||||
<li key={i} className="flex flex-col">
|
||||
<span className="text-text-muted text-xs">{step.question ?? step.text}</span>
|
||||
{step.answer && (
|
||||
<span className="font-medium text-text-primary">→ {step.answer}</span>
|
||||
<span className="font-medium text-text-primary">→ {step.answer_label ?? step.answer}</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -167,13 +167,13 @@ export function L1WalkTreeVariant({ session, onSessionUpdate, onDone }: Props) {
|
||||
onClick={() => advanceNode({ answer: 'yes' })}
|
||||
className="flex-1 rounded-md bg-accent text-white py-3 text-base font-medium hover:bg-accent/90 min-h-[44px] transition-colors"
|
||||
>
|
||||
Yes
|
||||
{node.yes_label ?? 'Yes'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => advanceNode({ answer: 'no' })}
|
||||
className="flex-1 rounded-md border border-default py-3 text-base font-medium hover:bg-elevated min-h-[44px] transition-colors"
|
||||
>
|
||||
No
|
||||
{node.no_label ?? 'No'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@@ -252,7 +252,7 @@ export function L1WalkTreeVariant({ session, onSessionUpdate, onDone }: Props) {
|
||||
{session.walked_path.map((step, i) => (
|
||||
<li key={i} className="flex flex-col">
|
||||
<span className="text-muted-foreground text-xs">{step.question ?? step.text}</span>
|
||||
{step.answer && <span className="font-medium">→ {step.answer}</span>}
|
||||
{step.answer && <span className="font-medium">→ {step.answer_label ?? step.answer}</span>}
|
||||
{step.l1_note && <span className="text-muted-foreground text-xs italic mt-0.5">{step.l1_note}</span>}
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface WalkStep {
|
||||
question?: string
|
||||
text?: string
|
||||
answer: string | null
|
||||
/** Button text the tech clicked (ai_build); falls back to `answer`. */
|
||||
answer_label?: string
|
||||
l1_note: string | null
|
||||
}
|
||||
|
||||
@@ -75,9 +77,12 @@ export interface IntakeResult {
|
||||
category?: string // for 'out_of_scope'
|
||||
}
|
||||
|
||||
/** A single node of an AI-built decision tree, returned by /next-node. */
|
||||
/** A single node of an AI-built decision tree, returned by /next-node.
|
||||
* Question nodes carry the literal button texts (yes_label/no_label) so the
|
||||
* choices always match the question ("Microsoft account" / "Local account",
|
||||
* not a mismatched Yes/No). The backend defaults them to Yes/No. */
|
||||
export type TreeNode =
|
||||
| { node_type: 'question'; id: string; text: string }
|
||||
| { node_type: 'question'; id: string; text: string; yes_label?: string; no_label?: string }
|
||||
| { node_type: 'instruction'; id: string; text: string }
|
||||
| { node_type: 'resolved'; id: string; text: string }
|
||||
| { node_type: 'escalate'; id: string; reason_category?: string; text: string }
|
||||
|
||||
Reference in New Issue
Block a user