Files
resolutionflow/frontend/src/components/flowpilot/SimilarSessions.tsx
Michael Chihlas cef853d7ea refactor: normalize FlowPilot/Assistant/ScriptBuilder to design system tokens
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>
2026-04-06 20:20:07 -04:00

97 lines
3.1 KiB
TypeScript

import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { Loader2 } from 'lucide-react'
import { aiSessionsApi } from '@/api'
import type { SimilarSession } from '@/types/ai-session'
import { cn } from '@/lib/utils'
interface SimilarSessionsProps {
sessionId: string
}
export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
const [sessions, setSessions] = useState<SimilarSession[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: syncs derived state from props
setLoading(true)
aiSessionsApi
.getSimilar(sessionId, 5)
.then((data) => {
if (!cancelled) setSessions(data)
})
.catch(() => {
// Silently ignore errors — don't clutter the UI
})
.finally(() => {
if (!cancelled) setLoading(false)
})
return () => {
cancelled = true
}
}, [sessionId])
if (loading) {
return (
<div className="flex items-center gap-1.5 py-1">
<Loader2 size={10} className="animate-spin text-muted-foreground" />
<span className="text-[0.625rem] text-muted-foreground font-sans text-xs">Loading similar sessions</span>
</div>
)
}
if (sessions.length === 0) {
return null
}
return (
<div className="space-y-2">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Similar Past Sessions
</h4>
{sessions.map((session) => (
<Link
key={session.id}
to={`/pilot/${session.id}`}
className="card-interactive p-3 block hover:border-[rgba(255,255,255,0.12)] transition-all"
>
<div className="flex items-start justify-between gap-2">
<p className="text-xs text-foreground line-clamp-2">
{session.problem_summary || 'Untitled session'}
</p>
<span className="text-[0.625rem] font-sans text-xs text-primary shrink-0">
{Math.round(session.similarity * 100)}%
</span>
</div>
{session.resolution_summary && (
<p className="text-[0.625rem] text-muted-foreground mt-1 line-clamp-1">
{session.resolution_summary}
</p>
)}
<div className="flex items-center gap-2 mt-1.5">
{session.problem_domain && (
<span className="text-[0.5rem] font-sans text-xs uppercase tracking-wider text-muted-foreground/70">
{session.problem_domain}
</span>
)}
<span
className={cn(
'text-[0.5rem] font-sans text-xs uppercase',
session.status === 'resolved'
? 'text-success'
: session.status === 'escalated'
? 'text-warning'
: 'text-muted-foreground'
)}
>
{session.status}
</span>
</div>
</Link>
))}
</div>
)
}