feat: Polish pass — display font on headings, empty states, card interactivity, panel dividers
- Apply font-display (Plus Jakarta Sans) to all page h1/h2 headings - Wire interactive Card prop on initiative cards and agent cards - Redesign empty states with icons: agents (Users), inbox (Inbox/MessageSquare), right panels (Terminal) - Unify initiative detail tabs to border-b-2 indicator style matching settings - Strengthen two-panel dividers on agents and inbox pages (lg:border-r) - Clean up filter pill badges — replace nested Badge with simpler span - Increase PhaseAccordion spacing for better readability - Fix error state styling in settings to use status tokens - Increase settings/projects section spacing
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import { Inbox, RefreshCw } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MessageCard } from "@/components/MessageCard";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Agent {
|
||||
id: string;
|
||||
@@ -119,11 +118,11 @@ export function InboxList({
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 lg:border-r lg:border-border/50 lg:pr-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-lg font-semibold">Agent Inbox</h2>
|
||||
<h2 className="text-lg font-semibold font-display tracking-tight">Agent Inbox</h2>
|
||||
<Badge variant="secondary">{joined.length}</Badge>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh}>
|
||||
@@ -139,7 +138,7 @@ export function InboxList({
|
||||
{filterOptions.map((opt) => (
|
||||
<Button
|
||||
key={opt.value}
|
||||
variant={filter === opt.value ? "default" : "outline"}
|
||||
variant={filter === opt.value ? "default" : "ghost"}
|
||||
size="sm"
|
||||
className="h-7 px-2 text-xs"
|
||||
onClick={() => setFilter(opt.value)}
|
||||
@@ -153,9 +152,9 @@ export function InboxList({
|
||||
{sortOptions.map((opt) => (
|
||||
<Button
|
||||
key={opt.value}
|
||||
variant={sort === opt.value ? "default" : "outline"}
|
||||
variant={sort === opt.value ? "default" : "ghost"}
|
||||
size="sm"
|
||||
className={cn("h-7 px-2 text-xs")}
|
||||
className="h-7 px-2 text-xs"
|
||||
onClick={() => setSort(opt.value)}
|
||||
>
|
||||
{opt.label}
|
||||
@@ -166,13 +165,12 @@ export function InboxList({
|
||||
|
||||
{/* Message list or empty state */}
|
||||
{sorted.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-16">
|
||||
<p className="text-lg font-medium text-muted-foreground">
|
||||
No pending messages
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Agents will appear here when they have questions or status updates
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed p-12 text-center">
|
||||
<Inbox className="h-8 w-8 text-muted-foreground/40" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground">No pending messages</p>
|
||||
<p className="text-xs text-muted-foreground/70">Agents will appear here when they have questions or status updates</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -84,7 +84,8 @@ export function InitiativeCard({ initiative, onClick }: InitiativeCardProps) {
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="cursor-pointer p-4 transition-colors hover:bg-accent/50"
|
||||
interactive
|
||||
className="p-4"
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* Row 1: Name + overflow menu */}
|
||||
|
||||
@@ -49,7 +49,7 @@ export function PhaseAccordion({
|
||||
<div className="border-b border-border">
|
||||
{/* Phase header — clickable to toggle */}
|
||||
<button
|
||||
className="flex w-full items-center gap-3 px-4 py-3 text-left hover:bg-accent/50"
|
||||
className="flex w-full items-center gap-3 px-4 py-3.5 text-left hover:bg-accent/50"
|
||||
onClick={() => setExpanded((prev) => !prev)}
|
||||
>
|
||||
{/* Expand / collapse chevron */}
|
||||
@@ -60,7 +60,7 @@ export function PhaseAccordion({
|
||||
)}
|
||||
|
||||
{/* Phase name */}
|
||||
<span className="min-w-0 flex-1 truncate font-medium">
|
||||
<span className="min-w-0 flex-1 truncate font-display font-medium">
|
||||
{phase.name}
|
||||
</span>
|
||||
|
||||
@@ -84,8 +84,9 @@ export function PhaseAccordion({
|
||||
|
||||
{/* Expanded content editor + task list */}
|
||||
{expanded && (
|
||||
<div className="pb-3 pl-10 pr-4">
|
||||
<div className="pb-4 pl-10 pr-4">
|
||||
<PhaseContentEditor phaseId={phase.id} initiativeId={phase.initiativeId} />
|
||||
<div className="mt-1 space-y-1">
|
||||
{tasks.map((entry, idx) => (
|
||||
<TaskRow
|
||||
key={entry.task.id}
|
||||
@@ -97,6 +98,7 @@ export function PhaseAccordion({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router";
|
||||
import { motion } from "motion/react";
|
||||
import { AlertCircle, RefreshCw } from "lucide-react";
|
||||
import { AlertCircle, RefreshCw, Terminal, Users } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -11,7 +11,6 @@ import { trpc } from "@/lib/trpc";
|
||||
import { AgentOutputViewer } from "@/components/AgentOutputViewer";
|
||||
import { AgentActions } from "@/components/AgentActions";
|
||||
import { formatRelativeTime } from "@/lib/utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { modeLabel } from "@/lib/labels";
|
||||
import { StatusDot } from "@/components/StatusDot";
|
||||
import { useLiveUpdates } from "@/hooks";
|
||||
@@ -193,7 +192,7 @@ function AgentsPage() {
|
||||
<div className="shrink-0 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-lg font-semibold">Agents</h1>
|
||||
<h1 className="font-display text-lg font-semibold tracking-tight">Agents</h1>
|
||||
<Badge variant="secondary">{agents.length}</Badge>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={handleRefresh}>
|
||||
@@ -216,12 +215,9 @@ function AgentsPage() {
|
||||
onClick={() => navigate({ to: "/agents", search: { filter: opt.value } })}
|
||||
>
|
||||
{opt.label}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="ml-1.5 h-4 min-w-4 px-1 text-[10px]"
|
||||
>
|
||||
<span className="ml-1.5 text-[10px] tabular-nums opacity-70">
|
||||
{counts[opt.value]}
|
||||
</Badge>
|
||||
</span>
|
||||
</Button>
|
||||
))}
|
||||
</motion.div>
|
||||
@@ -235,12 +231,14 @@ function AgentsPage() {
|
||||
className="grid grid-cols-1 gap-4 lg:grid-cols-[320px_1fr] min-h-0 flex-1"
|
||||
>
|
||||
{/* Left: Agent List */}
|
||||
<div className="overflow-y-auto min-h-0 space-y-2">
|
||||
<div className="overflow-y-auto min-h-0 space-y-2 lg:border-r lg:border-border/50 lg:pr-4">
|
||||
{filtered.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed p-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No agents match this filter
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed p-12 text-center">
|
||||
<Users className="h-8 w-8 text-muted-foreground/40" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground">No agents match this filter</p>
|
||||
<p className="text-xs text-muted-foreground/70">Try selecting a different filter above</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
filtered.map((agent, i) => (
|
||||
@@ -255,10 +253,9 @@ function AgentsPage() {
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
className={cn(
|
||||
"cursor-pointer p-3 transition-colors hover:bg-muted/50",
|
||||
selectedAgentId === agent.id && "bg-muted"
|
||||
)}
|
||||
interactive
|
||||
selected={selectedAgentId === agent.id}
|
||||
className="p-3"
|
||||
onClick={() => setSelectedAgentId(agent.id)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -321,10 +318,12 @@ function AgentsPage() {
|
||||
onStop={handleStop}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center rounded-lg border border-dashed">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Select an agent to view output
|
||||
</p>
|
||||
<div className="flex h-full flex-col items-center justify-center gap-3 rounded-lg border border-dashed">
|
||||
<Terminal className="h-10 w-10 text-muted-foreground/30" />
|
||||
<div className="space-y-1 text-center">
|
||||
<p className="text-sm font-medium text-muted-foreground">No agent selected</p>
|
||||
<p className="text-xs text-muted-foreground/70">Select an agent from the list to view output</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { motion } from "motion/react";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { AlertCircle, MessageSquare } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/Skeleton";
|
||||
@@ -255,10 +255,12 @@ function InboxPage() {
|
||||
|
||||
{/* Empty detail panel placeholder */}
|
||||
{!selectedAgent && (
|
||||
<div className="hidden items-center justify-center rounded-lg border border-dashed border-border p-8 lg:flex">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Select an agent to view details
|
||||
</p>
|
||||
<div className="hidden flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border p-8 lg:flex">
|
||||
<MessageSquare className="h-10 w-10 text-muted-foreground/30" />
|
||||
<div className="space-y-1 text-center">
|
||||
<p className="text-sm font-medium text-muted-foreground">No message selected</p>
|
||||
<p className="text-xs text-muted-foreground/70">Select an agent from the inbox to view details</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AlertCircle } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/Skeleton";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { InitiativeHeader } from "@/components/InitiativeHeader";
|
||||
import { ContentTab } from "@/components/editor/ContentTab";
|
||||
import { ExecutionTab } from "@/components/ExecutionTab";
|
||||
@@ -114,7 +115,7 @@ function InitiativeDetailPage() {
|
||||
/>
|
||||
|
||||
{/* Tab bar */}
|
||||
<motion.div
|
||||
<motion.nav
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, delay: 0.05, ease: [0, 0, 0.2, 1] }}
|
||||
@@ -129,16 +130,17 @@ function InitiativeDetailPage() {
|
||||
replace: true,
|
||||
})
|
||||
}
|
||||
className={`px-4 py-2 text-sm font-medium capitalize border-b-2 transition-colors ${
|
||||
className={cn(
|
||||
"px-4 py-2 text-sm font-medium border-b-2 transition-colors",
|
||||
activeTab === tab
|
||||
? "border-primary text-foreground"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
)}
|
||||
>
|
||||
{tab}
|
||||
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
</motion.nav>
|
||||
|
||||
{/* Tab content */}
|
||||
<motion.div
|
||||
|
||||
@@ -40,7 +40,7 @@ function DashboardPage() {
|
||||
>
|
||||
{/* Page header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Initiatives</h1>
|
||||
<h1 className="font-display text-2xl font-bold tracking-tight">Initiatives</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<select
|
||||
value={statusFilter}
|
||||
|
||||
Reference in New Issue
Block a user