UI: mobile responsive layout, streamline agent budget display, and xs avatar size
Make agents list force list view on mobile with condensed trailing info. Add mobile bottom bar for config save/cancel and live run indicator on agent detail. Make MetricCard, PageTabBar, Dashboard tasks, and ActivityRow responsive for small screens. Add xs avatar size for inline text flow. Remove redundant budget displays from agent overview, properties panel, costs tab, and config form. Add attachment activity verb labels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ const ACTION_VERBS: Record<string, string> = {
|
||||
"issue.checked_out": "checked out",
|
||||
"issue.released": "released",
|
||||
"issue.comment_added": "commented on",
|
||||
"issue.attachment_added": "attached file to",
|
||||
"issue.attachment_removed": "removed attachment from",
|
||||
"issue.commented": "commented on",
|
||||
"issue.deleted": "deleted",
|
||||
"agent.created": "created",
|
||||
@@ -105,23 +107,24 @@ export function ActivityRow({ event, agentMap, entityNameMap, className }: Activ
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"px-4 py-2 flex flex-wrap items-center justify-between gap-x-2 gap-y-0.5 text-sm",
|
||||
"px-4 py-2 text-sm",
|
||||
link && "cursor-pointer hover:bg-accent/50 transition-colors",
|
||||
className,
|
||||
)}
|
||||
onClick={link ? () => navigate(link) : undefined}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<Identity
|
||||
name={actor?.name ?? (event.actorType === "system" ? "System" : event.actorId || "You")}
|
||||
size="sm"
|
||||
/>
|
||||
<span className="text-muted-foreground shrink-0">{verb}</span>
|
||||
{name && <span className="truncate">{name}</span>}
|
||||
<div className="flex gap-3">
|
||||
<p className="flex-1 min-w-0">
|
||||
<Identity
|
||||
name={actor?.name ?? (event.actorType === "system" ? "System" : event.actorId || "You")}
|
||||
size="xs"
|
||||
className="align-baseline"
|
||||
/>
|
||||
<span className="text-muted-foreground ml-1">{verb} </span>
|
||||
{name && <span className="font-medium">{name}</span>}
|
||||
</p>
|
||||
<span className="text-xs text-muted-foreground shrink-0 pt-0.5">{timeAgo(event.createdAt)}</span>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
{timeAgo(event.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -716,26 +716,6 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ---- Runtime (edit only) ---- */}
|
||||
{!isCreate && (
|
||||
<div className="border-b border-border">
|
||||
<div className="px-4 py-2 text-xs font-medium text-muted-foreground">Runtime</div>
|
||||
<div className="px-4 pb-3 space-y-3">
|
||||
<Field label="Monthly budget (cents)" hint={help.budgetMonthlyCents}>
|
||||
<DraftNumberInput
|
||||
value={eff(
|
||||
"runtime",
|
||||
"budgetMonthlyCents",
|
||||
props.agent.budgetMonthlyCents,
|
||||
)}
|
||||
onCommit={(v) => mark("runtime", "budgetMonthlyCents", v)}
|
||||
immediate
|
||||
className={inputClass}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useCompany } from "../context/CompanyContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { StatusBadge } from "./StatusBadge";
|
||||
import { Identity } from "./Identity";
|
||||
import { formatCents, formatDate } from "../lib/utils";
|
||||
import { formatDate } from "../lib/utils";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
interface AgentPropertiesProps {
|
||||
@@ -62,24 +62,6 @@ export function AgentProperties({ agent, runtimeState }: AgentPropertiesProps) {
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-1">
|
||||
<PropertyRow label="Budget">
|
||||
<span className="text-sm">
|
||||
{formatCents(agent.spentMonthlyCents)} / {formatCents(agent.budgetMonthlyCents)}
|
||||
</span>
|
||||
</PropertyRow>
|
||||
<PropertyRow label="Utilization">
|
||||
<span className="text-sm">
|
||||
{agent.budgetMonthlyCents > 0
|
||||
? Math.round((agent.spentMonthlyCents / agent.budgetMonthlyCents) * 100)
|
||||
: 0}
|
||||
%
|
||||
</span>
|
||||
</PropertyRow>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-1">
|
||||
{(runtimeState?.sessionDisplayId ?? runtimeState?.sessionId) && (
|
||||
<PropertyRow label="Session">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
|
||||
type IdentitySize = "sm" | "default" | "lg";
|
||||
type IdentitySize = "xs" | "sm" | "default" | "lg";
|
||||
|
||||
export interface IdentityProps {
|
||||
name: string;
|
||||
@@ -18,6 +18,7 @@ function deriveInitials(name: string): string {
|
||||
}
|
||||
|
||||
const textSize: Record<IdentitySize, string> = {
|
||||
xs: "text-sm",
|
||||
sm: "text-xs",
|
||||
default: "text-sm",
|
||||
lg: "text-sm",
|
||||
@@ -27,8 +28,8 @@ export function Identity({ name, avatarUrl, initials, size = "default", classNam
|
||||
const displayInitials = initials ?? deriveInitials(name);
|
||||
|
||||
return (
|
||||
<span className={cn("inline-flex items-center gap-1.5", size === "lg" && "gap-2", className)}>
|
||||
<Avatar size={size}>
|
||||
<span className={cn("inline-flex gap-1.5", size === "xs" ? "items-baseline gap-1" : "items-center", size === "lg" && "gap-2", className)}>
|
||||
<Avatar size={size} className={size === "xs" ? "relative top-[2px]" : undefined}>
|
||||
{avatarUrl && <AvatarImage src={avatarUrl} alt={name} />}
|
||||
<AvatarFallback>{displayInitials}</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
@@ -13,27 +13,27 @@ interface MetricCardProps {
|
||||
export function MetricCard({ icon: Icon, value, label, description, onClick }: MetricCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex gap-3">
|
||||
<CardContent className="p-3 sm:p-4">
|
||||
<div className="flex gap-2 sm:gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p
|
||||
className={`text-2xl font-bold${onClick ? " cursor-pointer" : ""}`}
|
||||
className={`text-lg sm:text-2xl font-bold${onClick ? " cursor-pointer" : ""}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{value}
|
||||
</p>
|
||||
<p
|
||||
className={`text-sm text-muted-foreground${onClick ? " cursor-pointer" : ""}`}
|
||||
className={`text-xs sm:text-sm text-muted-foreground${onClick ? " cursor-pointer" : ""}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
{description && (
|
||||
<div className="text-xs text-muted-foreground mt-1">{description}</div>
|
||||
<div className="text-[11px] sm:text-xs text-muted-foreground mt-1 hidden sm:block">{description}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-muted p-2 rounded-md h-fit shrink-0">
|
||||
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="bg-muted p-1.5 sm:p-2 rounded-md h-fit shrink-0">
|
||||
<Icon className="h-3.5 w-3.5 sm:h-4 sm:w-4 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -21,7 +21,7 @@ export function PageTabBar({ items, value, onValueChange }: PageTabBarProps) {
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onValueChange(e.target.value)}
|
||||
className="h-8 rounded-md border border-border bg-background px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
className="h-9 rounded-md border border-border bg-background px-2 py-1 text-base focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
>
|
||||
{items.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
|
||||
@@ -8,14 +8,14 @@ function Avatar({
|
||||
size = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
||||
size?: "default" | "sm" | "lg"
|
||||
size?: "default" | "xs" | "sm" | "lg"
|
||||
}) {
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
|
||||
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6 data-[size=xs]:size-5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -44,7 +44,7 @@ function AvatarFallback({
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
|
||||
"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs group-data-[size=xs]/avatar:text-[10px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user