feat: resizable sidebar, branch hover preview, Fork Point label fix

- Sidebar: click-and-drag resize handle (200-500px range), accent
  highlight on drag, cursor changes to col-resize
- BranchNode: hover/active expands to show context_summary (tried,
  result), status_reason, and step count with smooth animation
- ForkCard: "Fork Point" label changed from text-muted to
  text-accent-text for visibility against bg-card

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-24 22:55:11 +00:00
parent 01836d6a2d
commit 55d24118e0
3 changed files with 122 additions and 27 deletions

View File

@@ -1,3 +1,4 @@
import { useState } from 'react'
import { CircleDot, CheckCircle2, XCircle, Circle, RotateCcw } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { BranchResponse } from '@/types/branching'
@@ -52,40 +53,86 @@ interface BranchNodeProps {
}
export function BranchNode({ branch, depth, isActive, onClick }: BranchNodeProps) {
const [isHovered, setIsHovered] = useState(false)
const config = STATUS_CONFIG[branch.status]
const Icon = config.icon
const showDetail = isActive || isHovered
return (
<button
type="button"
onClick={() => onClick(branch.id)}
style={{ paddingLeft: `${8 + depth * 16}px` }}
className={cn(
'w-full flex items-center gap-2 py-2 pr-3 text-left rounded-md transition-colors',
'hover:bg-elevated',
isActive && 'bg-accent-dim border-l-2 border-accent'
)}
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Icon
size={14}
className={cn('shrink-0', config.textClass)}
/>
<span
<button
type="button"
onClick={() => onClick(branch.id)}
style={{ paddingLeft: `${8 + depth * 16}px` }}
className={cn(
'flex-1 text-sm truncate',
isActive ? 'text-heading font-medium' : 'text-primary'
'w-full flex items-center gap-2 py-2 pr-3 text-left rounded-md transition-all duration-150',
'hover:bg-elevated',
isActive && 'bg-accent-dim border-l-2 border-accent'
)}
>
{branch.label}
</span>
<span
<Icon
size={14}
className={cn('shrink-0', config.textClass)}
/>
<span
className={cn(
'flex-1 text-sm truncate',
isActive ? 'text-heading font-medium' : 'text-primary'
)}
>
{branch.label}
</span>
<span
className={cn(
'text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded-full shrink-0',
config.badgeClass
)}
>
{config.label}
</span>
</button>
{/* Expandable detail panel */}
<div
className={cn(
'text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded-full shrink-0',
config.badgeClass
'overflow-hidden transition-all duration-200 ease-out',
showDetail ? 'max-h-[120px] opacity-100' : 'max-h-0 opacity-0'
)}
style={{ paddingLeft: `${24 + depth * 16}px` }}
>
{config.label}
</span>
</button>
<div className="py-1.5 pr-3 space-y-1">
{branch.context_summary ? (
<>
{branch.context_summary.tried.length > 0 && (
<p className="text-[11px] text-muted-foreground leading-snug">
<span className="text-muted">Tried:</span>{' '}
{branch.context_summary.tried.join(', ')}
</p>
)}
{branch.context_summary.concluded && (
<p className="text-[11px] text-muted-foreground leading-snug">
<span className="text-muted">Result:</span>{' '}
{branch.context_summary.concluded}
</p>
)}
</>
) : (
<p className="text-[11px] text-muted italic">No activity yet</p>
)}
{branch.status_reason && (
<p className="text-[11px] text-muted-foreground leading-snug">
<span className="text-muted">Reason:</span>{' '}
{branch.status_reason}
</p>
)}
<div className="flex items-center gap-3 text-[10px] text-muted">
<span>{branch.step_count} step{branch.step_count !== 1 ? 's' : ''}</span>
</div>
</div>
</div>
</div>
)
}

View File

@@ -14,7 +14,7 @@ export function ForkCard({ fork, selectedBranchId, onSelectOption }: ForkCardPro
{/* Header */}
<div className="flex items-center gap-2">
<GitFork size={16} className="text-accent shrink-0" />
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted">
<span className="text-[10px] font-semibold uppercase tracking-wider text-accent-text">
Fork Point
</span>
</div>