Replace hardcoded Tailwind color utilities with semantic CSS variable tokens across 31 files in the FlowPilot, Assistant Chat, and Script Builder feature communities — the areas graphify identified as design-system-free. - text-blue-400 → text-accent, bg-blue-500/10 → bg-accent-dim, border-blue-500/20 → border-accent/20 - text-amber-400 → text-warning, bg-amber-400/10 → bg-warning-dim, border-l-amber-500 → border-l-warning - text-rose-400/500 → text-danger, bg-rose-500/10 → bg-danger-dim - text-emerald-400 → text-success, bg-emerald-500/10 → bg-success-dim, border-l-emerald-500 → border-l-success - bg-white/[0.08] → bg-elevated (opacity hack → semantic surface token) - bg-gradient-to-r from-blue-500 to-blue-400 → bg-accent (no gradient surfaces) - bg-[#60a5fa] → bg-accent (hard-coded hex removed) Also adds graphify-out/ to .gitignore. Theme resilience: accent color has changed twice in 5 weeks. Semantic tokens mean the next change is a 1-line edit in index.css, not 110 grep-and-replace. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
174 lines
6.6 KiB
TypeScript
174 lines
6.6 KiB
TypeScript
import { useState } from 'react'
|
|
import { ArrowRight, ChevronDown, ChevronRight, MessageSquare } from 'lucide-react'
|
|
|
|
interface EscalationPackage {
|
|
original_user_id?: string
|
|
problem_summary?: string
|
|
escalation_reason?: string
|
|
steps_tried?: Array<{
|
|
step_type?: string
|
|
description?: string
|
|
response?: string
|
|
}>
|
|
steps_ruled_out?: string[]
|
|
remaining_hypotheses?: string[]
|
|
suggested_next_steps?: string[]
|
|
confidence_at_escalation?: number
|
|
}
|
|
|
|
interface SessionBriefingProps {
|
|
escalationPackage: EscalationPackage
|
|
originalEngineerName?: string
|
|
onContinue: () => void
|
|
onFresh: (context: string) => void
|
|
isProcessing: boolean
|
|
}
|
|
|
|
export function SessionBriefing({
|
|
escalationPackage,
|
|
originalEngineerName,
|
|
onContinue,
|
|
onFresh,
|
|
isProcessing,
|
|
}: SessionBriefingProps) {
|
|
const [showSteps, setShowSteps] = useState(false)
|
|
const [freshMode, setFreshMode] = useState(false)
|
|
const [freshContext, setFreshContext] = useState('')
|
|
|
|
const pkg = escalationPackage
|
|
|
|
return (
|
|
<div className="card-flat border-l-2 border-l-warning p-5 space-y-4">
|
|
<div>
|
|
<h3 className="font-heading text-base font-semibold text-foreground">
|
|
Escalation from {originalEngineerName || 'another engineer'}
|
|
</h3>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
Review the briefing below, then choose how to proceed.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Problem */}
|
|
{pkg.problem_summary && (
|
|
<div>
|
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Problem</h4>
|
|
<p className="text-sm text-foreground">{pkg.problem_summary}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Escalation reason */}
|
|
{pkg.escalation_reason && (
|
|
<div>
|
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Why escalated</h4>
|
|
<p className="text-sm text-warning">{pkg.escalation_reason}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Steps taken (collapsible) */}
|
|
{pkg.steps_tried && pkg.steps_tried.length > 0 && (
|
|
<div>
|
|
<button
|
|
onClick={() => setShowSteps(!showSteps)}
|
|
className="flex items-center gap-1.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted hover:text-foreground transition-colors"
|
|
>
|
|
{showSteps ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
|
Steps taken ({pkg.steps_tried.length})
|
|
</button>
|
|
{showSteps && (
|
|
<div className="mt-2 space-y-1.5">
|
|
{pkg.steps_tried.map((step, i) => (
|
|
<div key={i} className="rounded-lg bg-card/50 px-3 py-2 text-xs">
|
|
<p className="text-foreground">{i + 1}. {step.description}</p>
|
|
{step.response && (
|
|
<p className="mt-0.5 text-primary">→ {step.response}</p>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Remaining hypotheses */}
|
|
{pkg.remaining_hypotheses && pkg.remaining_hypotheses.length > 0 && (
|
|
<div>
|
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Remaining hypotheses</h4>
|
|
<ul className="space-y-1">
|
|
{pkg.remaining_hypotheses.map((h, i) => (
|
|
<li key={i} className="text-sm text-foreground flex items-start gap-2">
|
|
<span className="text-primary mt-0.5">•</span>
|
|
{h}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{/* Suggested next steps */}
|
|
{pkg.suggested_next_steps && pkg.suggested_next_steps.length > 0 && (
|
|
<div>
|
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Suggested next steps</h4>
|
|
<ul className="space-y-1">
|
|
{pkg.suggested_next_steps.map((s, i) => (
|
|
<li key={i} className="text-sm text-foreground flex items-start gap-2">
|
|
<span className="text-success mt-0.5">→</span>
|
|
{s}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{/* Action buttons */}
|
|
{!freshMode ? (
|
|
<div className="flex gap-2 pt-2">
|
|
<button
|
|
onClick={onContinue}
|
|
disabled={isProcessing}
|
|
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
|
>
|
|
<ArrowRight size={14} />
|
|
Continue Where They Left Off
|
|
</button>
|
|
<button
|
|
onClick={() => setFreshMode(true)}
|
|
disabled={isProcessing}
|
|
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2.5 text-sm font-medium text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
|
>
|
|
<MessageSquare size={14} />
|
|
Start Fresh With Context
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3 pt-2">
|
|
<textarea
|
|
value={freshContext}
|
|
onChange={(e) => setFreshContext(e.target.value)}
|
|
placeholder="What additional information do you have, or what would you like to investigate first?"
|
|
className="w-full rounded-lg border border-border bg-card px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(96,165,250,0.3)] focus:outline-none resize-none"
|
|
rows={3}
|
|
autoFocus
|
|
/>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={() => setFreshMode(false)}
|
|
disabled={isProcessing}
|
|
className="rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm font-medium text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
|
>
|
|
Back
|
|
</button>
|
|
<button
|
|
onClick={() => freshContext.trim() && onFresh(freshContext.trim())}
|
|
disabled={!freshContext.trim() || isProcessing}
|
|
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
|
>
|
|
<ArrowRight size={14} />
|
|
Start Diagnosis
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|