diff --git a/frontend/src/components/session/BranchRevivalCard.tsx b/frontend/src/components/session/BranchRevivalCard.tsx
new file mode 100644
index 00000000..4c3175d7
--- /dev/null
+++ b/frontend/src/components/session/BranchRevivalCard.tsx
@@ -0,0 +1,26 @@
+import { RotateCcw } from 'lucide-react'
+import type { BranchResponse } from '@/types/branching'
+
+interface BranchRevivalCardProps {
+ branch: BranchResponse
+ evidenceSource: BranchResponse | null
+}
+
+export function BranchRevivalCard({ branch, evidenceSource }: BranchRevivalCardProps) {
+ if (branch.status !== 'revived') return null
+
+ return (
+
+
+
+ Branch Revived
+
+ {branch.evidence_description && (
+
{branch.evidence_description}
+ )}
+ {evidenceSource && (
+
Evidence from: {evidenceSource.label}
+ )}
+
+ )
+}
diff --git a/frontend/src/components/session/BranchTransitionBar.tsx b/frontend/src/components/session/BranchTransitionBar.tsx
new file mode 100644
index 00000000..8ddb33c3
--- /dev/null
+++ b/frontend/src/components/session/BranchTransitionBar.tsx
@@ -0,0 +1,22 @@
+import { ArrowRight } from 'lucide-react'
+import type { BranchResponse } from '@/types/branching'
+
+interface BranchTransitionBarProps {
+ fromBranch: BranchResponse | null
+ toBranch: BranchResponse
+}
+
+export function BranchTransitionBar({ fromBranch, toBranch }: BranchTransitionBarProps) {
+ return (
+
+
Switched to
+
{toBranch.label}
+ {fromBranch && (
+ <>
+
+
from {fromBranch.label}
+ >
+ )}
+
+ )
+}
diff --git a/frontend/src/hooks/useFlowPilotSession.ts b/frontend/src/hooks/useFlowPilotSession.ts
index 3875109b..8b0f661c 100644
--- a/frontend/src/hooks/useFlowPilotSession.ts
+++ b/frontend/src/hooks/useFlowPilotSession.ts
@@ -92,6 +92,8 @@ export function useFlowPilotSession(): UseFlowPilotSession {
ticket_data: null,
steps: [firstStep],
conversation_messages: [],
+ is_branching: false,
+ active_branch_id: null,
})
setAllSteps([firstStep])
setCurrentStep(firstStep)
diff --git a/frontend/src/hooks/useHandoff.ts b/frontend/src/hooks/useHandoff.ts
new file mode 100644
index 00000000..38c8cc77
--- /dev/null
+++ b/frontend/src/hooks/useHandoff.ts
@@ -0,0 +1,49 @@
+import { useState, useCallback } from 'react'
+import { handoffsApi } from '@/api'
+import type { HandoffCreateRequest, HandoffResponse, QueueItemResponse } from '@/types/branching'
+import { toast } from '@/lib/toast'
+
+export function useHandoff() {
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [queue, setQueue] = useState([])
+ const [isLoadingQueue, setIsLoadingQueue] = useState(false)
+
+ const createHandoff = useCallback(async (sessionId: string, data: HandoffCreateRequest): Promise => {
+ setIsSubmitting(true)
+ try {
+ const result = await handoffsApi.createHandoff(sessionId, data)
+ toast.success(data.intent === 'park' ? 'Session parked' : 'Session escalated')
+ return result
+ } catch {
+ toast.error('Failed to hand off session')
+ return null
+ } finally {
+ setIsSubmitting(false)
+ }
+ }, [])
+
+ const claimHandoff = useCallback(async (sessionId: string, handoffId: string): Promise => {
+ try {
+ const result = await handoffsApi.claimHandoff(sessionId, handoffId)
+ toast.success('Session claimed')
+ return result
+ } catch {
+ toast.error('Failed to claim session')
+ return null
+ }
+ }, [])
+
+ const loadQueue = useCallback(async () => {
+ setIsLoadingQueue(true)
+ try {
+ const items = await handoffsApi.getQueue()
+ setQueue(items)
+ } catch {
+ toast.error('Failed to load queue')
+ } finally {
+ setIsLoadingQueue(false)
+ }
+ }, [])
+
+ return { isSubmitting, queue, isLoadingQueue, createHandoff, claimHandoff, loadQueue }
+}
diff --git a/frontend/src/hooks/useResolutionOutputs.ts b/frontend/src/hooks/useResolutionOutputs.ts
new file mode 100644
index 00000000..70cd1430
--- /dev/null
+++ b/frontend/src/hooks/useResolutionOutputs.ts
@@ -0,0 +1,43 @@
+import { useState, useCallback } from 'react'
+import { resolutionsApi } from '@/api'
+import type { ResolutionOutputResponse, PushDestination } from '@/types/branching'
+import { toast } from '@/lib/toast'
+
+export function useResolutionOutputs(sessionId: string) {
+ const [outputs, setOutputs] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+
+ const loadOutputs = useCallback(async () => {
+ setIsLoading(true)
+ try {
+ const data = await resolutionsApi.getOutputs(sessionId)
+ setOutputs(data.outputs)
+ } catch {
+ toast.error('Failed to load resolution outputs')
+ } finally {
+ setIsLoading(false)
+ }
+ }, [sessionId])
+
+ const editOutput = useCallback(async (outputId: string, content: string) => {
+ try {
+ const updated = await resolutionsApi.editOutput(sessionId, outputId, { edited_content: content })
+ setOutputs(prev => prev.map(o => o.id === updated.id ? updated : o))
+ toast.success('Changes saved')
+ } catch {
+ toast.error('Failed to save changes')
+ }
+ }, [sessionId])
+
+ const pushOutput = useCallback(async (outputId: string, destination: PushDestination) => {
+ try {
+ await resolutionsApi.pushOutput(sessionId, outputId, { destination })
+ toast.success(`Pushed to ${destination}`)
+ await loadOutputs()
+ } catch {
+ toast.error('Failed to push output')
+ }
+ }, [sessionId, loadOutputs])
+
+ return { outputs, isLoading, loadOutputs, editOutput, pushOutput }
+}
diff --git a/frontend/src/pages/SessionQueuePage.tsx b/frontend/src/pages/SessionQueuePage.tsx
new file mode 100644
index 00000000..6a4973d6
--- /dev/null
+++ b/frontend/src/pages/SessionQueuePage.tsx
@@ -0,0 +1,54 @@
+import { useEffect } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Inbox, ArrowUpRight, Pause, Clock } from 'lucide-react'
+import { useHandoff } from '@/hooks/useHandoff'
+
+export default function SessionQueuePage() {
+ const navigate = useNavigate()
+ const { queue, isLoadingQueue, loadQueue, claimHandoff } = useHandoff()
+
+ useEffect(() => { loadQueue() }, [loadQueue])
+
+ const handleClaim = async (item: typeof queue[0]) => {
+ const result = await claimHandoff(item.session_id, item.handoff_id)
+ if (result) navigate(`/pilot?sessionId=${item.session_id}`)
+ }
+
+ return (
+
+
+
+
Session Queue
+
+ {isLoadingQueue ? (
+
Loading queue...
+ ) : queue.length === 0 ? (
+
+
+
No sessions waiting
+
+ ) : (
+
+ {queue.map(item => (
+
+
+
+ {item.intent === 'escalate' ?
:
}
+
{item.problem_summary || 'Untitled session'}
+ {item.priority === 'elevated' &&
Elevated}
+
+ {item.engineer_notes &&
{item.engineer_notes}
}
+
+
+ {new Date(item.created_at).toLocaleString()}
+ {item.problem_domain && ยท {item.problem_domain}}
+
+
+
+
+ ))}
+
+ )}
+
+ )
+}