From 8ce6bc80fa1fe1a456d9b4b8dded6c78d97cb5c0 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Sat, 30 May 2026 20:48:30 -0400 Subject: [PATCH] feat(l1): proposal L1-source block + engineer L1-escalations section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - flow-proposal.ts: source_session_id nullable + add l1_session_id (matches backend FlowProposalSummary). - ProposalDetail.tsx: render an 'AI L1 walk (outcome-validated)' note when l1_session_id is set instead of the /pilot/{source_session_id} link; fall back to the link for ai_session-sourced proposals. - New L1EscalationsSection.tsx (GET /l1/escalations) — expandable rows with walked-path summary; renders nothing if empty. Mounted below the FlowPilot queue on EscalationQueuePage. tsc -b + eslint clean. Co-Authored-By: Claude Opus 4.7 --- .../components/l1/L1EscalationsSection.tsx | 77 +++++++++++++++++++ frontend/src/types/flow-proposal.ts | 5 +- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/l1/L1EscalationsSection.tsx diff --git a/frontend/src/components/l1/L1EscalationsSection.tsx b/frontend/src/components/l1/L1EscalationsSection.tsx new file mode 100644 index 00000000..5640a7e8 --- /dev/null +++ b/frontend/src/components/l1/L1EscalationsSection.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from 'react' +import { l1Api } from '@/api/l1' +import type { WalkSession } from '@/types/l1' + +/** + * Engineer-visible list of escalated L1 sessions (the Phase 2A handoff queue). + * Backed by GET /l1/escalations (engineer-or-above). Pollable, dependency-free — + * each row expands to show the walked path summary. Renders nothing if empty. + */ +export function L1EscalationsSection() { + const [rows, setRows] = useState([]) + const [loaded, setLoaded] = useState(false) + const [expanded, setExpanded] = useState(null) + + useEffect(() => { + l1Api + .escalations() + .then(setRows) + .catch(() => setRows([])) + .finally(() => setLoaded(true)) + }, []) + + if (!loaded || rows.length === 0) return null + + return ( +
+
+

L1 escalations

+

+ Tickets an L1 tech escalated mid-walk — pick one up to continue. +

+
+
+ {rows.map((s) => { + const isOpen = expanded === s.id + return ( +
+ + {isOpen && ( +
+ {s.walked_path.length === 0 ? ( +

No steps recorded.

+ ) : ( +
    + {s.walked_path.map((step, i) => ( +
  1. + {step.question} + {step.answer && ( + → {step.answer} + )} +
  2. + ))} +
+ )} +
+ )} +
+ ) + })} +
+
+ ) +} diff --git a/frontend/src/types/flow-proposal.ts b/frontend/src/types/flow-proposal.ts index 6005e1ff..e0e83aa3 100644 --- a/frontend/src/types/flow-proposal.ts +++ b/frontend/src/types/flow-proposal.ts @@ -10,7 +10,10 @@ export interface FlowProposalSummary { supporting_session_count: number status: 'pending' | 'approved' | 'modified' | 'rejected' | 'dismissed' | 'auto_reinforced' target_flow_id: string | null - source_session_id: string + // Exactly one source is set: source_session_id (FlowPilot ai_session) XOR + // l1_session_id (L1 ai_build walk). Both nullable on the backend (Phase 2A). + source_session_id: string | null + l1_session_id: string | null created_at: string }