feat: cockpit UX polish — conversation log, interactive steps, onboarding

- Redesign conversation log: proper role labels, left-border accents,
  larger text, CSS variable background, min-height guarantee
- Interactive steps panel: click-to-complete, click-to-select, progress
  bar with counter, hover-reveal descriptions, smooth transitions
- Replace noop overflow button with real dropdown menu (Pause, Copy Link,
  Close Case) with keyboard/click-outside dismiss
- Evidence cycling: right-click to reverse-cycle status, tooltips on icons
- First-run onboarding overlay labeling the three cockpit zones, auto-
  dismisses on first message or manual dismiss, persisted via localStorage
- Drag handle: taller, visible hover state, title tooltip
- Simplify input placeholder (remove redundant paste-log hint)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-02 00:13:46 +00:00
parent 63023d486d
commit 9462da8b80
4 changed files with 257 additions and 51 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useRef, useEffect } from 'react'
import { Pencil, X, Check, ExternalLink } from 'lucide-react'
import { Pencil, X, Check, ExternalLink, Pause, XCircle, Link2 } from 'lucide-react'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import type { TriageMeta } from '@/types/ai-session'
interface IncidentHeaderProps {
@@ -9,7 +10,8 @@ interface IncidentHeaderProps {
sessionId: string
onFieldSave: (field: keyof TriageMeta, value: string) => void
onResolve: () => void
onOverflow: () => void
onPause?: () => void
onClose?: () => void
}
interface EditPopoverProps {
@@ -95,12 +97,81 @@ function HeaderField({ label, value, placeholder, onSave, isHypothesis }: Header
)
}
function OverflowMenu({ onPause, onClose, sessionId }: { onPause?: () => void; onClose?: () => void; sessionId: string }) {
const [open, setOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!open) return
const handleClickOutside = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) setOpen(false)
}
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') setOpen(false)
}
document.addEventListener('mousedown', handleClickOutside)
document.addEventListener('keydown', handleEsc)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
document.removeEventListener('keydown', handleEsc)
}
}, [open])
const handleCopyLink = () => {
navigator.clipboard.writeText(`${window.location.origin}/assistant/${sessionId}`)
toast.success('Session link copied')
setOpen(false)
}
return (
<div ref={menuRef} className="relative">
<button
onClick={() => setOpen(!open)}
className="bg-elevated border border-default rounded px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
</button>
{open && (
<div className="absolute right-0 top-full mt-1 z-50 w-44 bg-elevated border border-hover rounded-md shadow-lg py-1">
{onPause && (
<button
onClick={() => { onPause(); setOpen(false) }}
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-card transition-colors text-left"
>
<Pause size={12} />
Pause Case
</button>
)}
<button
onClick={handleCopyLink}
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-card transition-colors text-left"
>
<Link2 size={12} />
Copy Link
</button>
{onClose && (
<button
onClick={() => { onClose(); setOpen(false) }}
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-danger hover:text-danger hover:bg-danger-dim transition-colors text-left"
>
<XCircle size={12} />
Close Case
</button>
)}
</div>
)}
</div>
)
}
export function IncidentHeader({
triageMeta,
psaTicketId,
sessionId,
onFieldSave,
onResolve,
onOverflow,
onPause,
onClose,
}: IncidentHeaderProps) {
return (
<div className="bg-card border-b border-default px-4 py-2 flex items-center gap-4 flex-wrap">
@@ -146,12 +217,7 @@ export function IncidentHeader({
>
Resolve
</button>
<button
onClick={onOverflow}
className="bg-elevated border border-default rounded px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
</button>
<OverflowMenu onPause={onPause} onClose={onClose} sessionId={sessionId} />
</div>
</div>
)