From c0bddc289e2a0bf3e75817b05cb26f5fcd795af3 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Thu, 28 May 2026 14:17:02 -0400 Subject: [PATCH] feat(l1): L1WalkPage tree variant with Resolve/Escalate modals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the T20 stub. WalkPage dispatches by session_kind: - 'flow' / 'proposal' → L1WalkTreeVariant (this commit) - 'adhoc' → placeholder until T23 L1WalkTreeVariant: sticky header with back link + AI-built badge + persistent Escalate/Resolve buttons; two-pane body (current step yes/no card on left, walked-path transcript on right). ResolveModal and EscalateModal extracted to shared WalkModals.tsx (T23 reuses). Phase 1 caveat: this surface isn't reached by user-driven intake (which creates adhoc sessions only). It's exercised via direct URL or integration tests until Phase 2 wires match_or_build. Co-Authored-By: Claude Opus 4.7 --- .../src/components/l1/L1WalkTreeVariant.tsx | 173 ++++++++++++++++++ frontend/src/components/l1/WalkModals.tsx | 121 ++++++++++++ frontend/src/pages/l1/L1WalkPage.tsx | 69 ++++++- 3 files changed, 356 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/l1/L1WalkTreeVariant.tsx create mode 100644 frontend/src/components/l1/WalkModals.tsx diff --git a/frontend/src/components/l1/L1WalkTreeVariant.tsx b/frontend/src/components/l1/L1WalkTreeVariant.tsx new file mode 100644 index 00000000..eaebcf3a --- /dev/null +++ b/frontend/src/components/l1/L1WalkTreeVariant.tsx @@ -0,0 +1,173 @@ +import { useState } from 'react' +import { ChevronLeft } from 'lucide-react' +import { Link } from 'react-router-dom' +import { l1Api } from '@/api/l1' +import type { WalkSession } from '@/types/l1' +import { EscalateModal, ResolveModal } from '@/components/l1/WalkModals' + +interface Props { + session: WalkSession + onSessionUpdate: (s: WalkSession) => void + onDone: () => void +} + +export function L1WalkTreeVariant({ session, onSessionUpdate, onDone }: Props) { + const [showResolve, setShowResolve] = useState(false) + const [showEscalate, setShowEscalate] = useState(false) + const [note, setNote] = useState('') + + // Phase 1: we don't have the live flow-tree fetch wired up here yet + // (the tree-navigation pages have their own loader). The walker shows the + // walked-path side panel, advance buttons stubbed for now — Phase 2 wires + // the actual flow tree fetching + node advancement against tree data. + // The "Yes/No" buttons record a synthetic step so the walked_path JSONB + // grows; this gives us a functional roundtrip until Phase 2 wires the tree. + + const handleAnswer = async (answer: 'yes' | 'no') => { + const nodeId = session.current_node_id || `step-${session.walked_path.length + 1}` + try { + const updated = await l1Api.step(session.id, { + node_id: nodeId, + question: `Step ${session.walked_path.length + 1}`, + answer, + note: note || null, + }) + onSessionUpdate(updated) + setNote('') + } catch (err) { + // Keep silent for v1 — Phase 2 wires real error UI + console.error('step failed', err) + } + } + + const lastError = (err: unknown): string => { + if (typeof err === 'object' && err && 'response' in err) { + const detail = (err as any).response?.data?.detail + if (typeof detail === 'string') return detail + } + return 'Unexpected error' + } + + return ( +
+ {/* Header */} +
+ + + #{session.id.slice(0, 8)} + {session.session_kind === 'proposal' && ( + AI-built + )} + +
+ + +
+
+ + {/* Two-pane body */} +
+
+

+ Step {session.walked_path.length + 1} +

+ {session.status !== 'active' ? ( +
+

+ This session is {session.status}. +

+ +
+ ) : ( +
+

Continue the walk:

+
+ + +
+