feat: add /dev/branching test page for branching components
Standalone page with mock data showing BranchMap, ForkCard, BranchTransitionBar, BranchRevivalCard, HandoffModal, and ResolutionOutputPanel — no API calls needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
577
frontend/src/pages/DevBranchingPage.tsx
Normal file
577
frontend/src/pages/DevBranchingPage.tsx
Normal file
@@ -0,0 +1,577 @@
|
||||
/**
|
||||
* Dev test page for Conversational Branching components.
|
||||
* Route: /dev/branching
|
||||
*
|
||||
* Renders all branching components with mock data — no API calls.
|
||||
* Use this to validate visual design before wiring into FlowPilotSessionPage.
|
||||
*/
|
||||
import { useState } from 'react'
|
||||
import { BranchMap } from '@/components/session/BranchMap'
|
||||
import { ForkCard } from '@/components/session/ForkCard'
|
||||
import { BranchTransitionBar } from '@/components/session/BranchTransitionBar'
|
||||
import { BranchRevivalCard } from '@/components/session/BranchRevivalCard'
|
||||
import { HandoffModal } from '@/components/session/HandoffModal'
|
||||
import type {
|
||||
BranchResponse,
|
||||
ForkPointResponse,
|
||||
ResolutionOutputResponse,
|
||||
ResolutionOutputType,
|
||||
} from '@/types/branching'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { toast } from '@/lib/toast'
|
||||
import {
|
||||
FileText,
|
||||
BookOpen,
|
||||
MessageSquare,
|
||||
Pencil,
|
||||
Copy,
|
||||
Send,
|
||||
Check,
|
||||
} from 'lucide-react'
|
||||
|
||||
// ── Mock Data ──
|
||||
|
||||
const SESSION_ID = 'mock-session-001'
|
||||
const ROOT_ID = 'branch-root'
|
||||
const BRANCH_NET = 'branch-network'
|
||||
const BRANCH_DNS = 'branch-dns'
|
||||
const BRANCH_DHCP = 'branch-dhcp'
|
||||
const BRANCH_REVIVED = 'branch-revived-firewall'
|
||||
|
||||
const MOCK_BRANCHES: BranchResponse[] = [
|
||||
{
|
||||
id: ROOT_ID,
|
||||
session_id: SESSION_ID,
|
||||
parent_branch_id: null,
|
||||
fork_point_step_id: null,
|
||||
branch_order: 1,
|
||||
label: 'Root — Initial Investigation',
|
||||
status: 'active',
|
||||
status_reason: null,
|
||||
status_changed_at: null,
|
||||
context_summary: {
|
||||
tried: ['Checked event logs', 'Ran ipconfig'],
|
||||
concluded: 'Multiple possible causes identified',
|
||||
artifacts: [],
|
||||
},
|
||||
evidence_from_branch_id: null,
|
||||
evidence_description: null,
|
||||
step_count: 4,
|
||||
created_at: '2026-03-24T10:00:00Z',
|
||||
updated_at: '2026-03-24T10:05:00Z',
|
||||
},
|
||||
{
|
||||
id: BRANCH_NET,
|
||||
session_id: SESSION_ID,
|
||||
parent_branch_id: ROOT_ID,
|
||||
fork_point_step_id: null,
|
||||
branch_order: 1,
|
||||
label: 'Network Connectivity',
|
||||
status: 'dead_end',
|
||||
status_reason: 'Ping to gateway successful, NIC is healthy',
|
||||
status_changed_at: '2026-03-24T10:12:00Z',
|
||||
context_summary: {
|
||||
tried: ['Ping gateway', 'Check NIC status', 'Traceroute'],
|
||||
concluded: 'Network layer is not the issue',
|
||||
artifacts: ['traceroute.txt'],
|
||||
},
|
||||
evidence_from_branch_id: null,
|
||||
evidence_description: null,
|
||||
step_count: 3,
|
||||
created_at: '2026-03-24T10:05:00Z',
|
||||
updated_at: '2026-03-24T10:12:00Z',
|
||||
},
|
||||
{
|
||||
id: BRANCH_DNS,
|
||||
session_id: SESSION_ID,
|
||||
parent_branch_id: ROOT_ID,
|
||||
fork_point_step_id: null,
|
||||
branch_order: 2,
|
||||
label: 'DNS Resolution',
|
||||
status: 'solved',
|
||||
status_reason: 'DNS cache was poisoned — flushed and resolved',
|
||||
status_changed_at: '2026-03-24T10:20:00Z',
|
||||
context_summary: {
|
||||
tried: ['nslookup', 'ipconfig /flushdns', 'Checked DNS server config'],
|
||||
concluded: 'Root cause: stale DNS cache entry for internal domain',
|
||||
artifacts: ['nslookup-output.txt'],
|
||||
},
|
||||
evidence_from_branch_id: null,
|
||||
evidence_description: null,
|
||||
step_count: 5,
|
||||
created_at: '2026-03-24T10:05:00Z',
|
||||
updated_at: '2026-03-24T10:20:00Z',
|
||||
},
|
||||
{
|
||||
id: BRANCH_DHCP,
|
||||
session_id: SESSION_ID,
|
||||
parent_branch_id: ROOT_ID,
|
||||
fork_point_step_id: null,
|
||||
branch_order: 3,
|
||||
label: 'DHCP Lease Issue',
|
||||
status: 'untried',
|
||||
status_reason: null,
|
||||
status_changed_at: null,
|
||||
context_summary: null,
|
||||
evidence_from_branch_id: null,
|
||||
evidence_description: null,
|
||||
step_count: 0,
|
||||
created_at: '2026-03-24T10:05:00Z',
|
||||
updated_at: '2026-03-24T10:05:00Z',
|
||||
},
|
||||
{
|
||||
id: BRANCH_REVIVED,
|
||||
session_id: SESSION_ID,
|
||||
parent_branch_id: BRANCH_NET,
|
||||
fork_point_step_id: null,
|
||||
branch_order: 1,
|
||||
label: 'Firewall Rules',
|
||||
status: 'revived',
|
||||
status_reason: null,
|
||||
status_changed_at: '2026-03-24T10:25:00Z',
|
||||
context_summary: {
|
||||
tried: ['Checked Windows Firewall'],
|
||||
concluded: 'Revisiting after DNS fix revealed blocked outbound on port 443',
|
||||
artifacts: [],
|
||||
},
|
||||
evidence_from_branch_id: BRANCH_DNS,
|
||||
evidence_description: 'DNS fix revealed that port 443 is being blocked by a new firewall rule pushed via GPO',
|
||||
step_count: 2,
|
||||
created_at: '2026-03-24T10:15:00Z',
|
||||
updated_at: '2026-03-24T10:25:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
const MOCK_FORK: ForkPointResponse = {
|
||||
id: 'fork-001',
|
||||
session_id: SESSION_ID,
|
||||
parent_branch_id: ROOT_ID,
|
||||
trigger_step_id: null,
|
||||
fork_reason:
|
||||
"Based on the symptoms (intermittent connectivity loss affecting only this workstation), I see three possible causes. Let's investigate each:",
|
||||
options: [
|
||||
{
|
||||
label: 'Network Connectivity',
|
||||
description: 'NIC driver issue, cable fault, or switch port problem',
|
||||
branch_id: BRANCH_NET,
|
||||
status: 'dead_end',
|
||||
},
|
||||
{
|
||||
label: 'DNS Resolution',
|
||||
description: 'Stale cache, wrong DNS server, or poisoned entry',
|
||||
branch_id: BRANCH_DNS,
|
||||
status: 'solved',
|
||||
},
|
||||
{
|
||||
label: 'DHCP Lease Issue',
|
||||
description: 'Lease expiry, scope exhaustion, or relay agent failure',
|
||||
branch_id: BRANCH_DHCP,
|
||||
status: 'untried',
|
||||
},
|
||||
],
|
||||
created_at: '2026-03-24T10:05:00Z',
|
||||
}
|
||||
|
||||
const MOCK_OUTPUTS: ResolutionOutputResponse[] = [
|
||||
{
|
||||
id: 'output-psa',
|
||||
session_id: SESSION_ID,
|
||||
output_type: 'psa_ticket_notes',
|
||||
generated_content: `## Problem
|
||||
User reported intermittent connectivity loss on workstation WS-1042 in the Accounting department.
|
||||
|
||||
## Diagnostic Steps
|
||||
1. **Network Connectivity** [Dead End] — Ping to gateway successful (avg 2ms), NIC healthy, traceroute clean. Network layer ruled out.
|
||||
2. **DNS Resolution** [Solved] — nslookup for internal domain \`erp.contoso.local\` returned stale IP (10.0.1.50 instead of 10.0.2.50). DNS cache poisoned by a cached entry from before the ERP server migration last Tuesday.
|
||||
3. **Firewall Rules** [Revived] — DNS fix revealed that a new GPO-pushed firewall rule was blocking outbound port 443. Rule was added by the security team on 3/22 without change advisory.
|
||||
|
||||
## Resolution
|
||||
- Flushed DNS cache: \`ipconfig /flushdns\`
|
||||
- Cleared stale DNS record on domain controller
|
||||
- Firewall rule exception added for ERP traffic
|
||||
- Verified connectivity stable for 15 minutes post-fix
|
||||
|
||||
## Recommendations
|
||||
- Add DNS record validation to the ERP migration runbook
|
||||
- Request change advisory process for GPO firewall rule changes`,
|
||||
structured_data: null,
|
||||
edited_content: null,
|
||||
status: 'draft',
|
||||
pushed_to: null,
|
||||
pushed_at: null,
|
||||
pushed_reference: null,
|
||||
generated_by_model: 'claude-sonnet-4-6',
|
||||
created_at: '2026-03-24T10:22:00Z',
|
||||
updated_at: '2026-03-24T10:22:00Z',
|
||||
},
|
||||
{
|
||||
id: 'output-kb',
|
||||
session_id: SESSION_ID,
|
||||
output_type: 'knowledge_base',
|
||||
generated_content: `# Intermittent Connectivity After Server Migration
|
||||
|
||||
## Symptoms
|
||||
- Workstation loses connectivity to internal applications intermittently
|
||||
- External sites work normally
|
||||
- Affects single workstation or small group
|
||||
|
||||
## Root Cause
|
||||
Stale DNS cache entries pointing to pre-migration IP addresses. Often occurs 1-7 days after internal server migrations when DNS TTLs haven't expired.
|
||||
|
||||
## Things to Rule Out First
|
||||
- **Network layer** — If ping to gateway succeeds and traceroute is clean, skip to DNS
|
||||
- **DHCP** — Only investigate if ipconfig shows APIPA address (169.254.x.x)
|
||||
|
||||
## Resolution Steps
|
||||
1. Run \`nslookup <affected-hostname>\` — compare returned IP with current server IP
|
||||
2. If mismatched: \`ipconfig /flushdns\` on the workstation
|
||||
3. Check AD DNS for stale records: \`dnscmd /enumrecords contoso.local <hostname>\`
|
||||
4. Delete stale record if found
|
||||
5. Verify: \`nslookup <hostname>\` returns correct IP
|
||||
|
||||
## Tags
|
||||
DNS, migration, connectivity, cache, stale-record`,
|
||||
structured_data: {
|
||||
symptoms: ['Intermittent connectivity to internal apps', 'External sites work'],
|
||||
root_cause: 'Stale DNS cache from server migration',
|
||||
steps: ['nslookup', 'flushdns', 'Check AD DNS', 'Delete stale record'],
|
||||
tags: ['DNS', 'migration', 'connectivity'],
|
||||
},
|
||||
edited_content: null,
|
||||
status: 'draft',
|
||||
pushed_to: null,
|
||||
pushed_at: null,
|
||||
pushed_reference: null,
|
||||
generated_by_model: 'claude-sonnet-4-6',
|
||||
created_at: '2026-03-24T10:22:00Z',
|
||||
updated_at: '2026-03-24T10:22:00Z',
|
||||
},
|
||||
{
|
||||
id: 'output-client',
|
||||
session_id: SESSION_ID,
|
||||
output_type: 'client_summary',
|
||||
generated_content: `Hi,
|
||||
|
||||
We've resolved the connectivity issue on your workstation. Here's what happened:
|
||||
|
||||
After your company's ERP server was moved to a new address last week, your computer was still trying to reach it at the old address. This caused the intermittent connection drops you were experiencing when accessing internal applications.
|
||||
|
||||
We've updated the address records and cleared the old cached information on your machine. We also found and fixed a secondary issue where a recently applied security policy was blocking some web traffic.
|
||||
|
||||
Your connectivity should now be stable. If you experience any further issues, please don't hesitate to reach out.`,
|
||||
structured_data: null,
|
||||
edited_content: null,
|
||||
status: 'draft',
|
||||
pushed_to: null,
|
||||
pushed_at: null,
|
||||
pushed_reference: null,
|
||||
generated_by_model: 'claude-sonnet-4-6',
|
||||
created_at: '2026-03-24T10:22:00Z',
|
||||
updated_at: '2026-03-24T10:22:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
// ── Mock Resolution Panel (self-contained, no API calls) ──
|
||||
|
||||
const TABS: Array<{ type: ResolutionOutputType; label: string; icon: React.ElementType }> = [
|
||||
{ type: 'psa_ticket_notes', label: 'PSA Notes', icon: FileText },
|
||||
{ type: 'knowledge_base', label: 'KB Article', icon: BookOpen },
|
||||
{ type: 'client_summary', label: 'Client Summary', icon: MessageSquare },
|
||||
]
|
||||
|
||||
function MockResolutionPanel({ outputs }: { outputs: ResolutionOutputResponse[] }) {
|
||||
const [activeType, setActiveType] = useState<ResolutionOutputType>('psa_ticket_notes')
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editValue, setEditValue] = useState('')
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const activeOutput = outputs.find(o => o.output_type === activeType) ?? null
|
||||
const displayContent = activeOutput?.edited_content ?? activeOutput?.generated_content ?? ''
|
||||
|
||||
function handleEditToggle() {
|
||||
if (!isEditing) {
|
||||
setEditValue(displayContent)
|
||||
setIsEditing(true)
|
||||
} else {
|
||||
setIsEditing(false)
|
||||
setEditValue('')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCopy() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(displayContent)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
} catch {
|
||||
toast.error('Failed to copy')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-card border border-default rounded-lg overflow-hidden">
|
||||
{/* Tab bar */}
|
||||
<div className="flex border-b border-default shrink-0">
|
||||
{TABS.map(tab => {
|
||||
const Icon = tab.icon
|
||||
const isActive = tab.type === activeType
|
||||
return (
|
||||
<button
|
||||
key={tab.type}
|
||||
type="button"
|
||||
onClick={() => { setActiveType(tab.type); setIsEditing(false) }}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors border-b-2 -mb-px',
|
||||
isActive
|
||||
? 'border-accent text-accent-text'
|
||||
: 'border-transparent text-secondary hover:text-primary hover:border-hover'
|
||||
)}
|
||||
>
|
||||
<Icon size={13} />
|
||||
<span>{tab.label}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-auto p-3 min-h-0 max-h-[400px]">
|
||||
{!activeOutput ? (
|
||||
<div className="flex items-center justify-center h-24">
|
||||
<span className="text-xs text-muted">No output generated yet.</span>
|
||||
</div>
|
||||
) : isEditing ? (
|
||||
<textarea
|
||||
value={editValue}
|
||||
onChange={e => setEditValue(e.target.value)}
|
||||
className={cn(
|
||||
'w-full h-full min-h-[240px] resize-none rounded-[5px] border border-default bg-input',
|
||||
'px-3 py-2 text-sm text-primary font-mono leading-relaxed',
|
||||
'focus:outline-none focus:border-accent focus:shadow-[0_0_0_2px_var(--color-accent-dim)]',
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<pre className="text-sm text-primary font-mono whitespace-pre-wrap leading-relaxed">
|
||||
{displayContent}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action bar */}
|
||||
<div className="flex items-center gap-2 px-3 py-2.5 border-t border-default shrink-0">
|
||||
{isEditing ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { toast.success('Saved (mock)'); setIsEditing(false) }}
|
||||
className="rounded-[5px] bg-accent px-3 py-1.5 text-xs font-medium text-white hover:bg-[#ea580c] transition-colors"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEditToggle}
|
||||
className="rounded-[5px] border border-default px-3 py-1.5 text-xs text-secondary hover:bg-elevated hover:text-primary transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEditToggle}
|
||||
disabled={!activeOutput}
|
||||
className="flex items-center gap-1.5 rounded-[5px] border border-default px-3 py-1.5 text-xs text-secondary hover:bg-elevated hover:text-primary transition-colors disabled:opacity-40"
|
||||
>
|
||||
<Pencil size={12} /> Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
disabled={!activeOutput}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-[5px] border border-default px-3 py-1.5 text-xs transition-colors disabled:opacity-40',
|
||||
copied ? 'border-[#34d399] text-[#34d399]' : 'text-secondary hover:bg-elevated hover:text-primary'
|
||||
)}
|
||||
>
|
||||
{copied ? <Check size={12} /> : <Copy size={12} />}
|
||||
{copied ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toast.success('Pushed to PSA (mock)')}
|
||||
disabled={!activeOutput}
|
||||
className="flex items-center gap-1.5 rounded-[5px] border border-default px-3 py-1.5 text-xs text-secondary hover:bg-elevated hover:text-primary transition-colors disabled:opacity-40"
|
||||
>
|
||||
<Send size={12} /> Push to PSA
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Page ──
|
||||
|
||||
export default function DevBranchingPage() {
|
||||
const [activeBranchId, setActiveBranchId] = useState<string>(BRANCH_DNS)
|
||||
const [selectedForkBranch, setSelectedForkBranch] = useState<string | null>(BRANCH_DNS)
|
||||
const [showHandoff, setShowHandoff] = useState(false)
|
||||
const [previousBranchId, setPreviousBranchId] = useState<string | null>(null)
|
||||
const [showTransition, setShowTransition] = useState(false)
|
||||
|
||||
const activeBranch = MOCK_BRANCHES.find(b => b.id === activeBranchId)!
|
||||
const previousBranch = previousBranchId ? MOCK_BRANCHES.find(b => b.id === previousBranchId) ?? null : null
|
||||
const revivedBranch = MOCK_BRANCHES.find(b => b.id === BRANCH_REVIVED)!
|
||||
const evidenceSource = MOCK_BRANCHES.find(b => b.id === revivedBranch.evidence_from_branch_id) ?? null
|
||||
|
||||
function handleSwitchBranch(branchId: string) {
|
||||
if (branchId !== activeBranchId) {
|
||||
setPreviousBranchId(activeBranchId)
|
||||
setActiveBranchId(branchId)
|
||||
setShowTransition(true)
|
||||
setTimeout(() => setShowTransition(false), 3000)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
{/* Branch Map Sidebar */}
|
||||
<aside className="w-[260px] shrink-0 border-r border-default bg-sidebar p-3 overflow-y-auto">
|
||||
<BranchMap
|
||||
branches={MOCK_BRANCHES}
|
||||
activeBranchId={activeBranchId}
|
||||
onSelectBranch={handleSwitchBranch}
|
||||
/>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 overflow-y-auto p-6 flex flex-col gap-6">
|
||||
<div className="max-w-3xl mx-auto w-full flex flex-col gap-6">
|
||||
{/* Page header */}
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-heading">
|
||||
Conversational Branching — Component Test
|
||||
</h1>
|
||||
<p className="text-sm text-secondary mt-1">
|
||||
Mock scenario: Intermittent connectivity on WS-1042. 5 branches, 1 fork, 1 revival.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Branch Transition Bar */}
|
||||
<section>
|
||||
<SectionLabel>Branch Transition Bar</SectionLabel>
|
||||
{showTransition ? (
|
||||
<BranchTransitionBar
|
||||
fromBranch={previousBranch}
|
||||
toBranch={activeBranch}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-xs text-muted">Click a branch in the sidebar to see the transition bar.</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Fork Card */}
|
||||
<section>
|
||||
<SectionLabel>Fork Card</SectionLabel>
|
||||
<ForkCard
|
||||
fork={MOCK_FORK}
|
||||
selectedBranchId={selectedForkBranch}
|
||||
onSelectOption={(branchId) => {
|
||||
setSelectedForkBranch(branchId)
|
||||
handleSwitchBranch(branchId)
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Revival Card */}
|
||||
<section>
|
||||
<SectionLabel>Branch Revival Card</SectionLabel>
|
||||
<BranchRevivalCard
|
||||
branch={revivedBranch}
|
||||
evidenceSource={evidenceSource}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Handoff Modal Trigger */}
|
||||
<section>
|
||||
<SectionLabel>Handoff Modal</SectionLabel>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowHandoff(true)}
|
||||
className="rounded-[5px] bg-accent px-4 py-2 text-sm font-medium text-white hover:bg-[#ea580c] transition-colors"
|
||||
>
|
||||
Open Handoff Modal
|
||||
</button>
|
||||
</section>
|
||||
|
||||
{/* Resolution Output Panel */}
|
||||
<section>
|
||||
<SectionLabel>Resolution Output Panel</SectionLabel>
|
||||
<MockResolutionPanel outputs={MOCK_OUTPUTS} />
|
||||
</section>
|
||||
|
||||
{/* Current branch info */}
|
||||
<section>
|
||||
<SectionLabel>Active Branch Details</SectionLabel>
|
||||
<div className="bg-card border border-default rounded-lg p-4 text-sm">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<span className="text-muted">Label:</span>{' '}
|
||||
<span className="text-primary">{activeBranch.label}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted">Status:</span>{' '}
|
||||
<span className="text-primary">{activeBranch.status}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted">Steps:</span>{' '}
|
||||
<span className="text-primary">{activeBranch.step_count}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted">Order:</span>{' '}
|
||||
<span className="text-primary">{activeBranch.branch_order}</span>
|
||||
</div>
|
||||
</div>
|
||||
{activeBranch.status_reason && (
|
||||
<div className="mt-2 pt-2 border-t border-default">
|
||||
<span className="text-muted">Reason:</span>{' '}
|
||||
<span className="text-secondary">{activeBranch.status_reason}</span>
|
||||
</div>
|
||||
)}
|
||||
{activeBranch.context_summary && (
|
||||
<div className="mt-2 pt-2 border-t border-default">
|
||||
<span className="text-muted">Tried:</span>{' '}
|
||||
<span className="text-secondary">{activeBranch.context_summary.tried.join(', ')}</span>
|
||||
<br />
|
||||
<span className="text-muted">Concluded:</span>{' '}
|
||||
<span className="text-secondary">{activeBranch.context_summary.concluded}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Handoff Modal */}
|
||||
{showHandoff && (
|
||||
<HandoffModal
|
||||
onClose={() => setShowHandoff(false)}
|
||||
onSubmit={async (data) => {
|
||||
toast.success(`Handoff submitted: ${data.intent} (${data.priority ?? 'normal'})`)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SectionLabel({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted mb-2 block">
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -56,6 +56,7 @@ const FlowPilotAnalyticsPage = lazyWithRetry(() => import('@/pages/FlowPilotAnal
|
||||
const ScriptBuilderPage = lazyWithRetry(() => import('@/pages/ScriptBuilderPage'))
|
||||
const KBAcceleratorPage = lazyWithRetry(() => import('@/pages/KBAcceleratorPage'))
|
||||
const SessionQueuePage = lazyWithRetry(() => import('@/pages/SessionQueuePage'))
|
||||
const DevBranchingPage = lazyWithRetry(() => import('@/pages/DevBranchingPage'))
|
||||
const GuidesHubPage = lazyWithRetry(() => import('@/pages/GuidesHubPage'))
|
||||
const GuideDetailPage = lazyWithRetry(() => import('@/pages/GuideDetailPage'))
|
||||
const AccountSettingsPage = lazyWithRetry(() => import('@/pages/AccountSettingsPage'))
|
||||
@@ -204,6 +205,7 @@ export const router = sentryCreateBrowserRouter([
|
||||
{ path: 'queue', element: page(SessionQueuePage) },
|
||||
{ path: 'review-queue', element: page(ReviewQueuePage) },
|
||||
{ path: 'analytics/flowpilot', element: page(FlowPilotAnalyticsPage) },
|
||||
{ path: 'dev/branching', element: page(DevBranchingPage) },
|
||||
{ path: 'guides', element: page(GuidesHubPage) },
|
||||
{ path: 'guides/:slug', element: page(GuideDetailPage) },
|
||||
// Admin routes
|
||||
|
||||
Reference in New Issue
Block a user