Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 25 additions & 25 deletions frontend/src/app/dashboard/projects/[id]/ProjectClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,28 +154,28 @@ export default function ProjectClient() {
<span>&larr;</span> Back to Dashboard
</button>

<h1 className="text-2xl font-bold mb-2">{project.name}</h1>
<h1 className="text-xl sm:text-2xl font-bold mb-2 break-words">{project.name}</h1>
<p className="text-sm text-muted-foreground mb-4">{project.description}</p>

{/* Stats row */}
<div className="flex items-center gap-6 flex-wrap">
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-6">
<div>
<span className="text-3xl font-bold">${project.stats.totalCostUsd.toFixed(2)}</span>
<span className="text-sm text-muted-foreground ml-2">total cost</span>
</div>
<div className="h-8 w-px bg-border/50" />
<div className="hidden sm:block h-8 w-px bg-border/50" />
<div className="text-sm text-muted-foreground">
<div className="font-medium text-foreground">{project.stats.sessionCount} sessions</div>
</div>
<div className="h-8 w-px bg-border/50" />
<div className="hidden sm:block h-8 w-px bg-border/50" />
<div className="text-sm text-muted-foreground">
<div className="font-medium text-foreground">{project.stats.totalMessages} messages</div>
</div>
<div className="h-8 w-px bg-border/50" />
<div className="hidden sm:block h-8 w-px bg-border/50" />
<div className="text-sm text-muted-foreground">
<div className="font-medium text-foreground">{formatTokens(project.stats.totalTokens)} tokens</div>
</div>
<div className="h-8 w-px bg-border/50" />
<div className="hidden sm:block h-8 w-px bg-border/50" />
<div className="text-xs text-muted-foreground">
<div>{formatDate(project.stats.dateRange.from)} &mdash; {formatDate(project.stats.dateRange.to)}</div>
</div>
Expand Down Expand Up @@ -230,23 +230,23 @@ export default function ProjectClient() {
<div
key={session.id}
onClick={() => router.push(`/dashboard/sessions/${session.id}`)}
className="rounded-xl border border-border/50 bg-card p-4 hover:border-border transition-colors cursor-pointer group"
className="rounded-xl border border-border/50 bg-card p-3 sm:p-4 hover:border-border transition-colors cursor-pointer group"
>
<div className="flex items-start gap-4">
<div className="flex items-start gap-3 sm:gap-4">
<span className={`size-2.5 rounded-full mt-1.5 shrink-0 ${sc.dot}`} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium truncate max-w-[500px] group-hover:text-emerald-400 transition-colors">
<span className="font-medium truncate group-hover:text-emerald-400 transition-colors">
{session.title.length > 80 ? session.title.slice(0, 80) + "..." : session.title}
</span>
Comment on lines +239 to 241
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions removing the fixed max width on session titles to rely on “natural truncation”, but this card still hard-caps titles via session.title.slice(0, 80) + "...". With the max-width removed, this manual slicing becomes the main limiter (even on wide screens) and can conflict with truncate. Consider removing the manual slice and relying on CSS truncation (or making the slice conditional on viewport) so behavior matches the PR intent.

Copilot uses AI. Check for mistakes.
</div>
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className={`text-[10px] border ${colors.badge}`}>
{session.agentId}
</Badge>
<span className="text-[11px] font-mono text-muted-foreground">{session.model}</span>
<span className="text-[11px] font-mono text-muted-foreground truncate max-w-[120px] sm:max-w-none">{session.model}</span>
<span className="text-[11px] text-muted-foreground">{session.messageCount} msgs</span>
<span className="text-[11px] text-muted-foreground">{formatRelativeTime(session.lastActivityAt)}</span>
<span className="text-[11px] text-muted-foreground hidden sm:inline">{formatRelativeTime(session.lastActivityAt)}</span>
</div>
</div>
<div className="flex items-center gap-4 shrink-0 text-sm">
Expand Down Expand Up @@ -318,22 +318,22 @@ function TimelineMessageBubble({

if (msg.role === "user") {
return (
<div className={`rounded-lg border-l-4 ${colors.border} border border-blue-500/20 bg-blue-500/10 p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center gap-2 mb-2">
<div className={`rounded-lg border-l-4 ${colors.border} border border-blue-500/20 bg-blue-500/10 p-3 sm:p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center gap-2 mb-2 flex-wrap">
<Badge variant="outline" className={`text-[10px] border ${colors.badge}`}>{msg.agentId}</Badge>
<span className="text-xs font-medium text-blue-400">User</span>
<span className="text-[11px] text-muted-foreground">{formatRelativeTime(msg.timestamp)}</span>
</div>
<p className="text-sm whitespace-pre-wrap">{msg.content}</p>
<p className="text-sm whitespace-pre-wrap break-words">{msg.content}</p>
</div>
);
}

if (msg.role === "assistant") {
return (
<div className={`rounded-lg border-l-4 ${colors.border} border border-border/50 bg-zinc-800/50 p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className={`rounded-lg border-l-4 ${colors.border} border border-border/50 bg-zinc-800/50 p-3 sm:p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-1 sm:gap-2 mb-2">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className={`text-[10px] border ${colors.badge}`}>{msg.agentId}</Badge>
<span className="text-xs font-medium text-zinc-400">Assistant</span>
{msg.model && <span className="text-[10px] font-mono text-muted-foreground">{msg.model}</span>}
Expand All @@ -345,16 +345,16 @@ function TimelineMessageBubble({
</Badge>
)}
</div>
<p className="text-sm whitespace-pre-wrap">{msg.content}</p>
<p className="text-sm whitespace-pre-wrap break-words">{msg.content}</p>
</div>
);
}

if (msg.role === "tool") {
return (
<div className={`rounded-lg border-l-4 ${colors.border} border border-border/50 bg-zinc-900 p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className={`rounded-lg border-l-4 ${colors.border} border border-border/50 bg-zinc-900 p-3 sm:p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center justify-between mb-2 gap-2">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className={`text-[10px] border ${colors.badge}`}>{msg.agentId}</Badge>
<span className="text-xs text-amber-400">Tool</span>
{msg.toolName && (
Expand All @@ -364,7 +364,7 @@ function TimelineMessageBubble({
)}
<span className="text-[11px] text-muted-foreground">{formatRelativeTime(msg.timestamp)}</span>
</div>
<button onClick={onToggle} className="text-[10px] text-muted-foreground hover:text-foreground transition-colors">
<button onClick={onToggle} className="text-[10px] text-muted-foreground hover:text-foreground transition-colors shrink-0 min-h-[28px] px-2">
{collapsed ? "Show" : "Hide"}
</button>
</div>
Expand All @@ -377,13 +377,13 @@ function TimelineMessageBubble({

// system
return (
<div className={`rounded-lg border-l-4 ${colors.border} px-4 py-2`}>
<div className="flex items-center gap-2">
<div className={`rounded-lg border-l-4 ${colors.border} px-3 sm:px-4 py-2`}>
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className={`text-[10px] border ${colors.badge}`}>{msg.agentId}</Badge>
<span className="text-[11px] text-muted-foreground">System</span>
<span className="text-[11px] text-muted-foreground">{formatRelativeTime(msg.timestamp)}</span>
</div>
<p className="text-xs text-muted-foreground mt-1">{msg.content}</p>
<p className="text-xs text-muted-foreground mt-1 break-words">{msg.content}</p>
</div>
);
}
53 changes: 27 additions & 26 deletions frontend/src/app/dashboard/sessions/[id]/SessionClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export default function SessionClient() {
</button>

<div className="space-y-3">
<h1 className="text-2xl font-bold">{session.title}</h1>
<h1 className="text-xl sm:text-2xl font-bold break-words">{session.title}</h1>
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className={`border ${agentColors[session.agentId] || "text-zinc-400"}`}>
{session.agentId}
Expand All @@ -203,7 +203,7 @@ export default function SessionClient() {
{sc.label}
</Badge>
</div>
<div className="flex items-center gap-6 text-sm">
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-6 text-sm">
<div>
<span className="text-3xl font-bold">${session.costUsd.toFixed(2)}</span>
</div>
Expand All @@ -212,7 +212,8 @@ export default function SessionClient() {
<div>{session.messageCount} messages</div>
</div>
<div className="text-muted-foreground text-xs">
<div>Started: {formatRelativeTime(session.startedAt)} ({formatAbsoluteTime(session.startedAt)})</div>
<div>Started: {formatRelativeTime(session.startedAt)}</div>
<div className="hidden sm:block text-[10px]">({formatAbsoluteTime(session.startedAt)})</div>
<div>Last activity: {formatRelativeTime(session.lastActivityAt)}</div>
</div>
</div>
Expand Down Expand Up @@ -309,26 +310,26 @@ function MessageBubble({

if (msg.role === "user") {
return (
<div className="relative pl-12 flex justify-end">
<div className="absolute left-3.5 top-3 size-3 rounded-full bg-blue-500 ring-4 ring-background z-10" />
<div className={`rounded-lg border border-blue-500/20 bg-blue-500/10 p-4 max-w-[80%] ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center gap-2 mb-2">
<div className="relative pl-8 sm:pl-12 flex justify-end">
<div className="absolute left-1.5 sm:left-3.5 top-3 size-3 rounded-full bg-blue-500 ring-4 ring-background z-10" />
<div className={`rounded-lg border border-blue-500/20 bg-blue-500/10 p-3 sm:p-4 max-w-full sm:max-w-[80%] ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
Comment on lines +313 to +315
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timeline dot was moved to left-1.5 on mobile, but the vertical timeline line is still positioned at left-5 (see the "Vertical timeline line" div). This will misalign dots vs the line on small screens (desktop still aligns because sm:left-3.5 centers on left-5). Consider making the line responsive (e.g. left-3 sm:left-5) or adjusting the mobile dot position/padding so the dot center stays on the line.

Copilot uses AI. Check for mistakes.
<div className="flex items-center gap-2 mb-2 flex-wrap">
<span className="text-xs font-medium text-blue-400">User</span>
<span className="text-[11px] text-muted-foreground">{formatAbsoluteTime(msg.timestamp)}</span>
</div>
<ExpandableText text={msg.content} className="text-sm whitespace-pre-wrap" />
<ExpandableText text={msg.content} className="text-sm whitespace-pre-wrap break-words" />
</div>
</div>
);
}

if (msg.role === "assistant") {
return (
<div className="relative pl-12">
<div className="absolute left-3.5 top-3 size-3 rounded-full bg-zinc-500 ring-4 ring-background z-10" />
<div className={`rounded-lg border border-border/50 bg-zinc-800/50 p-4 max-w-[80%] ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className="relative pl-8 sm:pl-12">
<div className="absolute left-1.5 sm:left-3.5 top-3 size-3 rounded-full bg-zinc-500 ring-4 ring-background z-10" />
<div className={`rounded-lg border border-border/50 bg-zinc-800/50 p-3 sm:p-4 max-w-full sm:max-w-[80%] ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-1 sm:gap-2 mb-2">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-xs font-medium text-zinc-400">Assistant</span>
{msg.model && <span className="text-[10px] font-mono text-muted-foreground">{msg.model}</span>}
<span className="text-[11px] text-muted-foreground">{formatAbsoluteTime(msg.timestamp)}</span>
Expand All @@ -344,19 +345,19 @@ function MessageBubble({
</div>
)}
</div>
<ExpandableText text={msg.content} className="text-sm whitespace-pre-wrap" />
<ExpandableText text={msg.content} className="text-sm whitespace-pre-wrap break-words" />
</div>
</div>
);
}

if (msg.role === "tool") {
return (
<div className="relative pl-12">
<div className="absolute left-3.5 top-3 size-3 rounded-full bg-amber-500 ring-4 ring-background z-10" />
<div className={`rounded-lg border border-border/50 bg-zinc-900 p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className="relative pl-8 sm:pl-12">
<div className="absolute left-1.5 sm:left-3.5 top-3 size-3 rounded-full bg-amber-500 ring-4 ring-background z-10" />
<div className={`rounded-lg border border-border/50 bg-zinc-900 p-3 sm:p-4 ${isError ? "border-red-500/30 bg-red-500/10" : ""}`}>
<div className="flex items-center justify-between mb-2 gap-2">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-xs text-amber-400">Tool</span>
{msg.toolName && (
<Badge variant="outline" className="text-[10px] font-mono bg-amber-500/10 text-amber-400 border-amber-500/20">
Expand All @@ -367,14 +368,14 @@ function MessageBubble({
</div>
<button
onClick={onToggle}
className="text-[10px] text-muted-foreground hover:text-foreground transition-colors"
className="text-[10px] text-muted-foreground hover:text-foreground transition-colors shrink-0 min-h-[28px] px-2"
>
{collapsed ? "Show" : "Hide"}
</button>
</div>
{msg.toolInput && (
<div className="bg-black/30 rounded px-2 py-1 mb-2 overflow-x-auto">
<ExpandableText text={msg.toolInput} className="text-[11px] font-mono text-muted-foreground" preformatted />
<ExpandableText text={msg.toolInput} className="text-[11px] font-mono text-muted-foreground break-all" preformatted />
</div>
)}
{!collapsed && (
Expand All @@ -387,14 +388,14 @@ function MessageBubble({

// system
return (
<div className="relative pl-12">
<div className="absolute left-3.5 top-3 size-3 rounded-full bg-zinc-700 ring-4 ring-background z-10" />
<div className="rounded-lg px-4 py-2">
<div className="flex items-center gap-2">
<div className="relative pl-8 sm:pl-12">
<div className="absolute left-1.5 sm:left-3.5 top-3 size-3 rounded-full bg-zinc-700 ring-4 ring-background z-10" />
<div className="rounded-lg px-3 sm:px-4 py-2">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-[11px] text-muted-foreground">System</span>
<span className="text-[11px] text-muted-foreground">{formatAbsoluteTime(msg.timestamp)}</span>
</div>
<ExpandableText text={msg.content} className="text-xs text-muted-foreground mt-1" />
<ExpandableText text={msg.content} className="text-xs text-muted-foreground mt-1 break-words" />
</div>
</div>
);
Expand Down
Loading