mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
Normalize desktop icon sizing (#999)
This commit is contained in:
@@ -2,8 +2,10 @@ import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
|
||||
import { TopbarSearch } from "@/features/search/ui/TopbarSearch";
|
||||
import type { Channel, SearchHit } from "@/shared/api/types";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { SidebarTrigger, useSidebar } from "@/shared/ui/sidebar";
|
||||
import { Skeleton } from "@/shared/ui/skeleton";
|
||||
|
||||
type AppTopChromeProps = {
|
||||
canGoBack: boolean;
|
||||
@@ -16,6 +18,7 @@ type AppTopChromeProps = {
|
||||
onOpenResult: (hit: SearchHit) => void;
|
||||
searchHidden?: boolean;
|
||||
searchFocusRequest: number;
|
||||
searchLoading?: boolean;
|
||||
};
|
||||
|
||||
function GlobalTopDivider() {
|
||||
@@ -39,6 +42,7 @@ function CenterColumnTopbarSearch({
|
||||
onOpenChannel,
|
||||
onOpenResult,
|
||||
searchFocusRequest,
|
||||
searchLoading = false,
|
||||
}: Pick<
|
||||
AppTopChromeProps,
|
||||
| "channels"
|
||||
@@ -46,8 +50,11 @@ function CenterColumnTopbarSearch({
|
||||
| "onOpenChannel"
|
||||
| "onOpenResult"
|
||||
| "searchFocusRequest"
|
||||
| "searchLoading"
|
||||
>) {
|
||||
const { isResizing, state } = useSidebar();
|
||||
const searchClassName =
|
||||
"pointer-events-auto w-[220px] max-w-full md:w-[300px] lg:w-[360px] xl:w-[420px] 2xl:w-[480px]";
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -59,20 +66,30 @@ function CenterColumnTopbarSearch({
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
<TopbarSearch
|
||||
channels={channels}
|
||||
className="pointer-events-auto w-[220px] max-w-full md:w-[300px] lg:w-[360px] xl:w-[420px] 2xl:w-[480px]"
|
||||
currentPubkey={currentPubkey}
|
||||
focusRequest={searchFocusRequest}
|
||||
onOpenChannel={onOpenChannel}
|
||||
onOpenResult={onOpenResult}
|
||||
/>
|
||||
{searchLoading ? (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={cn("h-7", searchClassName)}
|
||||
data-testid="topbar-search-loading"
|
||||
>
|
||||
<Skeleton className="h-full w-full rounded-lg" />
|
||||
</div>
|
||||
) : (
|
||||
<TopbarSearch
|
||||
channels={channels}
|
||||
className={searchClassName}
|
||||
currentPubkey={currentPubkey}
|
||||
focusRequest={searchFocusRequest}
|
||||
onOpenChannel={onOpenChannel}
|
||||
onOpenResult={onOpenResult}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const TOP_CHROME_ICON_BUTTON_CLASS =
|
||||
"rounded-[4px] text-muted-foreground/70 hover:bg-border/45 hover:text-foreground";
|
||||
"h-7 w-7 rounded-[4px] text-muted-foreground/70 hover:bg-border/45 hover:text-foreground [&_svg]:size-4";
|
||||
|
||||
export function AppTopChrome({
|
||||
canGoBack,
|
||||
@@ -85,6 +102,7 @@ export function AppTopChrome({
|
||||
onOpenResult,
|
||||
searchHidden = false,
|
||||
searchFocusRequest,
|
||||
searchLoading = false,
|
||||
}: AppTopChromeProps) {
|
||||
return (
|
||||
<>
|
||||
@@ -94,7 +112,7 @@ export function AppTopChrome({
|
||||
data-tauri-drag-region
|
||||
/>
|
||||
<GlobalTopDivider />
|
||||
<div className="fixed left-[80px] top-[9px] z-45 flex items-center gap-0.5">
|
||||
<div className="fixed left-[80px] top-[6px] z-45 flex items-center gap-0.5">
|
||||
<SidebarTrigger className={TOP_CHROME_ICON_BUTTON_CLASS} />
|
||||
<Button
|
||||
aria-label="Go back"
|
||||
@@ -126,6 +144,7 @@ export function AppTopChrome({
|
||||
onOpenChannel={onOpenChannel}
|
||||
onOpenResult={onOpenResult}
|
||||
searchFocusRequest={searchFocusRequest}
|
||||
searchLoading={searchLoading}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -85,7 +85,7 @@ export function MemoryRefreshButton({
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn(
|
||||
iconClassName ?? "h-3.5 w-3.5",
|
||||
iconClassName ?? "h-4 w-4",
|
||||
query.isFetching && "animate-spin",
|
||||
)}
|
||||
/>
|
||||
@@ -169,7 +169,7 @@ function MemoryErrorState({
|
||||
role="alert"
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-destructive" />
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-destructive" />
|
||||
<div className="space-y-1">
|
||||
<div className="font-medium text-destructive">
|
||||
Couldn't load memory
|
||||
@@ -196,7 +196,7 @@ function MemoryStaleErrorBanner({ onRetry }: { onRetry: () => void }) {
|
||||
className="mb-2 flex items-center gap-2 rounded-md border border-warning/30 bg-warning/5 px-2 py-1.5 text-xs"
|
||||
data-testid="agent-memory-stale-error"
|
||||
>
|
||||
<AlertTriangle className="h-3 w-3 shrink-0 text-warning" />
|
||||
<AlertTriangle className="h-4 w-4 shrink-0 text-warning" />
|
||||
<span className="flex-1 text-muted-foreground">Refresh failed.</span>
|
||||
<button
|
||||
className="font-medium text-warning hover:underline"
|
||||
@@ -511,7 +511,7 @@ function MemoryEntryAccordion({
|
||||
ref={titleRef}
|
||||
>
|
||||
{hasDanglingRefs ? (
|
||||
<AlertTriangle className="mr-1 inline-block h-3.5 w-3.5 align-[-2px] text-warning" />
|
||||
<AlertTriangle className="mr-1 inline-block h-4 w-4 align-[-2px] text-warning" />
|
||||
) : null}
|
||||
<MemorySlugTitle slug={entry.slug} />
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,7 @@ export function ToolItem({
|
||||
</span>
|
||||
) : null}
|
||||
<ToolTimestamp item={item} />
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
|
||||
</summary>
|
||||
|
||||
<ToolDetailBlocks
|
||||
@@ -272,7 +272,7 @@ function BuzzToolInlineAction({
|
||||
{action.avatar}
|
||||
<span className="shrink-0">{action.label}</span>
|
||||
<span className="truncate">{action.value}</span>
|
||||
<ArrowUpRight className="h-3 w-3 shrink-0" />
|
||||
<ArrowUpRight className="h-4 w-4 shrink-0" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function AgentSessionTranscriptList({
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className="flex min-h-56 flex-col items-center justify-center px-6 py-10 text-center">
|
||||
<Radio className="mx-auto h-5 w-5 text-muted-foreground" />
|
||||
<Radio className="mx-auto h-4 w-4 text-muted-foreground" />
|
||||
<p className="mt-3 text-sm font-medium">No ACP activity yet</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{emptyDescription}</p>
|
||||
</div>
|
||||
@@ -126,7 +126,7 @@ function MessageItem({
|
||||
{isAssistant ? (
|
||||
<div className="mb-0.5 flex items-center gap-1 text-xs">
|
||||
<span className="flex h-5 w-5 items-center justify-center">
|
||||
<Bot className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Bot className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
<span className="font-normal text-foreground">{agentName}</span>
|
||||
<TranscriptTimestamp timestamp={item.timestamp} />
|
||||
@@ -163,7 +163,7 @@ function ThoughtItem({
|
||||
<Brain className="h-4 w-4" />
|
||||
<span className="truncate text-sm font-medium">{item.title}</span>
|
||||
<TranscriptTimestamp timestamp={item.timestamp} />
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 transition-transform group-open:rotate-180" />
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform group-open:rotate-180" />
|
||||
</summary>
|
||||
<div className="py-2 pl-5 text-sm leading-6 text-muted-foreground">
|
||||
<Markdown compact content={item.text.trim() || " "} />
|
||||
@@ -186,7 +186,7 @@ function MetadataItem({
|
||||
{item.sections.length} section{item.sections.length === 1 ? "" : "s"}
|
||||
</span>
|
||||
<TranscriptTimestamp timestamp={item.timestamp} />
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 transition-transform group-open:rotate-180" />
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform group-open:rotate-180" />
|
||||
</summary>
|
||||
<div className="space-y-3 py-2 pl-5">
|
||||
{item.sections.map((section) => (
|
||||
@@ -196,7 +196,7 @@ function MetadataItem({
|
||||
>
|
||||
<summary className="inline-flex max-w-full cursor-pointer list-none items-center gap-1.5 text-xs font-medium text-foreground/80">
|
||||
<span className="truncate">{section.title}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform group-open/section:rotate-180" />
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform group-open/section:rotate-180" />
|
||||
</summary>
|
||||
<pre className="mt-2 max-h-56 overflow-auto whitespace-pre-wrap break-words rounded-md bg-muted/50 px-3 py-2 font-mono text-[11px] leading-5 text-muted-foreground">
|
||||
{section.body.trim() || "No metadata."}
|
||||
|
||||
@@ -213,12 +213,12 @@ export function BatchImportDialog({
|
||||
onClick={() => setSkippedExpanded((prev) => !prev)}
|
||||
type="button"
|
||||
>
|
||||
<AlertTriangle className="h-3.5 w-3.5" />
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
{skipped.length} file{skipped.length !== 1 ? "s" : ""} skipped
|
||||
{skippedExpanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
{skippedExpanded ? (
|
||||
|
||||
@@ -20,7 +20,7 @@ export function CopyButton({
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
<Copy className="h-4 w-4" />
|
||||
<span>{label ?? "Copy"}</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
CircleAlert,
|
||||
CircleDot,
|
||||
Clock3,
|
||||
Loader2,
|
||||
TerminalSquare,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
@@ -14,6 +13,7 @@ import type { ManagedAgent } from "@/shared/api/types";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Badge } from "@/shared/ui/badge";
|
||||
import { Skeleton } from "@/shared/ui/skeleton";
|
||||
import { Spinner } from "@/shared/ui/spinner";
|
||||
import { AgentSessionTranscriptList } from "./AgentSessionTranscriptList";
|
||||
import { RawEventRail } from "./RawEventRail";
|
||||
import type {
|
||||
@@ -229,7 +229,7 @@ function ObserverStatusBadge({ state }: { state: ConnectionState }) {
|
||||
state === "open"
|
||||
? { label: "Live", Icon: CircleDot, variant: "default" as const }
|
||||
: state === "connecting"
|
||||
? { label: "Connecting", Icon: Loader2, variant: "secondary" as const }
|
||||
? { label: "Connecting", variant: "secondary" as const }
|
||||
: state === "error"
|
||||
? {
|
||||
label: "Unavailable",
|
||||
@@ -239,12 +239,15 @@ function ObserverStatusBadge({ state }: { state: ConnectionState }) {
|
||||
: state === "closed"
|
||||
? { label: "Closed", Icon: Clock3, variant: "secondary" as const }
|
||||
: { label: "Idle", Icon: Clock3, variant: "secondary" as const };
|
||||
const StatusIcon = display.Icon;
|
||||
|
||||
return (
|
||||
<Badge className="gap-1.5" variant={display.variant}>
|
||||
<display.Icon
|
||||
className={cn("h-3.5 w-3.5", state === "connecting" && "animate-spin")}
|
||||
/>
|
||||
{StatusIcon ? (
|
||||
<StatusIcon aria-hidden className="h-4 w-4" />
|
||||
) : (
|
||||
<Spinner aria-hidden className="h-4 w-4 border-2" />
|
||||
)}
|
||||
{display.label}
|
||||
</Badge>
|
||||
);
|
||||
@@ -253,7 +256,7 @@ function ObserverStatusBadge({ state }: { state: ConnectionState }) {
|
||||
function EmptyObserverState() {
|
||||
return (
|
||||
<div className="mt-4 flex min-h-48 flex-col items-center justify-center px-6 py-8 text-center">
|
||||
<TerminalSquare className="mx-auto h-5 w-5 text-muted-foreground" />
|
||||
<TerminalSquare className="mx-auto h-4 w-4 text-muted-foreground" />
|
||||
<p className="mt-3 text-sm font-medium">Observer not attached</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
The live feed is available for local agents started after this update.
|
||||
|
||||
@@ -93,7 +93,7 @@ export function ModelPicker({
|
||||
variant="ghost"
|
||||
>
|
||||
<span className="truncate">{displayLabel}</span>
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
@@ -103,7 +103,7 @@ export function ModelPicker({
|
||||
>
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground">
|
||||
<Spinner className="h-3.5 w-3.5" />
|
||||
<Spinner className="h-4 w-4 border-2" />
|
||||
Loading models...
|
||||
</div>
|
||||
) : error ? (
|
||||
|
||||
@@ -20,7 +20,7 @@ export function PersonaCatalogSelectionBadge({
|
||||
: "border border-border/70 bg-background/85 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isActive ? <Check className="h-3 w-3" /> : null}
|
||||
{isActive ? <Check className="h-4 w-4" /> : null}
|
||||
{isActive
|
||||
? personaCatalogCopy.selectedState
|
||||
: personaCatalogCopy.availableState}
|
||||
|
||||
@@ -497,12 +497,12 @@ export function PersonaDialog({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Upload className="h-3.5 w-3.5" />
|
||||
<Upload className="h-4 w-4" />
|
||||
<span className="max-w-[16rem] truncate">
|
||||
{importButtonLabel}
|
||||
</span>
|
||||
{isImportingUpdate ? (
|
||||
<RefreshCw className="h-3.5 w-3.5 animate-spin" />
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
) : null}
|
||||
</button>
|
||||
</>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function PersonaIdentity({
|
||||
className="flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground"
|
||||
type="button"
|
||||
>
|
||||
<Info className="h-3.5 w-3.5" />
|
||||
<Info className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-xs">
|
||||
|
||||
@@ -258,7 +258,7 @@ function AllowlistPicker({
|
||||
onClick={() => onRemove(pubkey)}
|
||||
type="button"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -475,12 +475,12 @@ export function TeamDialog({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Upload className="h-3.5 w-3.5" />
|
||||
<Upload className="h-4 w-4" />
|
||||
<span className="max-w-[16rem] truncate">
|
||||
{importButtonLabel}
|
||||
</span>
|
||||
{isImportingUpdate ? (
|
||||
<RefreshCw className="h-3.5 w-3.5 animate-spin" />
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
) : null}
|
||||
</button>
|
||||
</>
|
||||
|
||||
@@ -130,7 +130,7 @@ export function TeamImportDialog({
|
||||
{preview ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 rounded-lg border border-border/60 bg-card/80 px-4 py-3">
|
||||
<Users className="h-5 w-5 shrink-0 text-muted-foreground" />
|
||||
<Users className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold tracking-tight">
|
||||
{preview.name}
|
||||
|
||||
@@ -108,7 +108,7 @@ export function TeamsSection({
|
||||
onClick={onInstallFromDirectory}
|
||||
type="button"
|
||||
>
|
||||
<FolderOpen className="h-3.5 w-3.5" />
|
||||
<FolderOpen className="h-4 w-4" />
|
||||
Install from directory
|
||||
</button>
|
||||
<CreateNewButton
|
||||
@@ -161,7 +161,7 @@ export function TeamsSection({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground">
|
||||
<Link className="h-3.5 w-3.5" />
|
||||
<Link className="h-4 w-4" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-xs">
|
||||
@@ -184,7 +184,7 @@ export function TeamsSection({
|
||||
className="flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground"
|
||||
type="button"
|
||||
>
|
||||
<Info className="h-3.5 w-3.5" />
|
||||
<Info className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-xs">
|
||||
|
||||
@@ -530,7 +530,7 @@ export function AddChannelBotDialog({
|
||||
variant="ghost"
|
||||
>
|
||||
<span className="truncate">{runtimeTriggerLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
|
||||
@@ -54,7 +54,7 @@ function SelectionChipButton({
|
||||
? "bg-primary/20 text-primary ring-1 ring-primary/20"
|
||||
: "bg-background/80 text-muted-foreground ring-1 ring-border/70",
|
||||
)}
|
||||
iconClassName="h-3.5 w-3.5"
|
||||
iconClassName="h-4 w-4"
|
||||
label={label}
|
||||
/>
|
||||
) : null}
|
||||
@@ -193,7 +193,7 @@ export function AddChannelBotPersonasSection({
|
||||
<ProfileAvatar
|
||||
avatarUrl={persona.avatarUrl}
|
||||
className="h-7 w-7 text-[10px] bg-primary-foreground/20 text-primary-foreground"
|
||||
iconClassName="h-3.5 w-3.5"
|
||||
iconClassName="h-4 w-4"
|
||||
label={persona.displayName}
|
||||
/>
|
||||
<p className="font-medium">{persona.displayName}</p>
|
||||
|
||||
@@ -102,7 +102,7 @@ export function AgentSessionThreadPanel({
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<Octagon className="h-3 w-3" />
|
||||
<Octagon className="h-4 w-4" />
|
||||
Stop
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -201,7 +201,7 @@ export function BotActivityComposerAction({
|
||||
{isInline ? <Shimmer>{visibleStatusLabel}</Shimmer> : "working"}
|
||||
</span>
|
||||
{isInline ? null : (
|
||||
<Loader2 className="h-3.5 w-3.5 shrink-0 animate-spin opacity-70" />
|
||||
<Loader2 className="h-4 w-4 shrink-0 animate-spin opacity-70" />
|
||||
)}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
@@ -244,7 +244,7 @@ export function BotActivityComposerAction({
|
||||
displayName={agent.name}
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate">{agent.name}</span>
|
||||
<Loader2 className="h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground/70" />
|
||||
<Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground/70" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -79,7 +79,7 @@ function MetadataPill({
|
||||
}) {
|
||||
return (
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-border/80 bg-muted/40 px-3 py-1 text-xs font-medium text-muted-foreground">
|
||||
<Icon className="h-3.5 w-3.5" />
|
||||
<Icon className="h-4 w-4" />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -234,7 +234,7 @@ export function ChannelMemberInviteCard({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -60,7 +60,7 @@ export function ChannelScreenHeader({
|
||||
size="sm"
|
||||
variant="default"
|
||||
>
|
||||
<LogIn className="mr-1.5 h-3.5 w-3.5" />
|
||||
<LogIn className="mr-1.5 h-4 w-4" />
|
||||
{isJoining ? "Joining…" : "Join"}
|
||||
</Button>
|
||||
) : (
|
||||
|
||||
@@ -81,7 +81,7 @@ export function QuickBotBar({ personas, pending, onAdd }: QuickBotBarProps) {
|
||||
)}
|
||||
{isThisPending ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-background/70">
|
||||
<Spinner className="h-3.5 w-3.5 text-primary" />
|
||||
<Spinner className="h-4 w-4 border-2 text-primary" />
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
|
||||
@@ -32,8 +32,8 @@ type ChatHeaderProps = {
|
||||
statusBadge?: React.ReactNode;
|
||||
};
|
||||
|
||||
const HEADER_ICON_CLASS = "h-[14px] w-[14px] text-muted-foreground";
|
||||
const CHANNEL_HASH_ICON_CLASS = "h-[14px] w-[14px] translate-y-px";
|
||||
const HEADER_ICON_CLASS = "h-4 w-4 text-muted-foreground";
|
||||
const CHANNEL_HASH_ICON_CLASS = "h-4 w-4 translate-y-px";
|
||||
|
||||
function ChannelIcon({
|
||||
channelType,
|
||||
@@ -98,12 +98,12 @@ export function ChatHeader({
|
||||
const header = (
|
||||
<header
|
||||
className={cn(
|
||||
"pointer-events-auto relative z-30 flex min-w-0 shrink-0 cursor-default select-none items-center gap-2.5 bg-transparent pl-4 pr-2 transition-[margin,padding] duration-200 ease-linear sm:pr-3",
|
||||
"pointer-events-auto relative z-30 flex min-w-0 shrink-0 cursor-default select-none items-center gap-2.5 bg-transparent px-4 transition-[margin,padding] duration-200 ease-linear sm:px-6",
|
||||
density === "compact"
|
||||
? belowSystemChrome
|
||||
? "min-h-8 py-1.5"
|
||||
: "min-h-8 py-0"
|
||||
: "min-h-11 py-1.5 sm:pl-6",
|
||||
: "min-h-11 py-1.5",
|
||||
overlaysContent && !belowSystemChrome && "-mb-11",
|
||||
)}
|
||||
data-testid="chat-header"
|
||||
|
||||
@@ -16,13 +16,9 @@ type DeleteActionMenuProps = {
|
||||
iconSize?: "sm" | "md";
|
||||
};
|
||||
|
||||
export function DeleteActionMenu({
|
||||
label,
|
||||
onConfirm,
|
||||
iconSize = "md",
|
||||
}: DeleteActionMenuProps) {
|
||||
export function DeleteActionMenu({ label, onConfirm }: DeleteActionMenuProps) {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const iconClass = iconSize === "sm" ? "h-3.5 w-3.5" : "h-4 w-4";
|
||||
const iconClass = "h-4 w-4";
|
||||
|
||||
return (
|
||||
<div className="ml-auto opacity-0 transition-opacity group-hover:opacity-100">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Plus } from "lucide-react";
|
||||
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Spinner } from "@/shared/ui/spinner";
|
||||
|
||||
type ForumComposerCompactLayoutProps = {
|
||||
editor: Editor | null;
|
||||
@@ -45,12 +46,12 @@ export function ForumComposerCompactLayout({
|
||||
variant="ghost"
|
||||
>
|
||||
{isSending ? (
|
||||
<span
|
||||
<Spinner
|
||||
aria-hidden
|
||||
className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"
|
||||
className="h-4 w-4 border-2 text-primary-foreground"
|
||||
/>
|
||||
) : (
|
||||
<Plus aria-hidden className="h-3.5 w-3.5" />
|
||||
<Plus aria-hidden className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -125,7 +125,7 @@ export function ForumPostCard({
|
||||
|
||||
{summary && summary.replyCount > 0 ? (
|
||||
<div className="mt-3 flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<MessageSquare className="h-3.5 w-3.5" />
|
||||
<MessageSquare className="h-4 w-4" />
|
||||
<span>
|
||||
{summary.replyCount}{" "}
|
||||
{summary.replyCount === 1 ? "reply" : "replies"}
|
||||
|
||||
@@ -264,10 +264,7 @@ export function InboxDetailPane({
|
||||
type="button"
|
||||
>
|
||||
{hasChannelContext ? (
|
||||
<Hash
|
||||
className="h-[14px] w-[14px] shrink-0"
|
||||
color="gray"
|
||||
/>
|
||||
<Hash className="h-4 w-4 shrink-0" color="gray" />
|
||||
) : null}
|
||||
<span className="min-w-0 translate-y-px truncate">
|
||||
{contextLabel}
|
||||
@@ -279,10 +276,7 @@ export function InboxDetailPane({
|
||||
title={item.fullTimestampLabel}
|
||||
>
|
||||
{hasChannelContext ? (
|
||||
<Hash
|
||||
className="h-[14px] w-[14px] shrink-0"
|
||||
color="gray"
|
||||
/>
|
||||
<Hash className="h-4 w-4 shrink-0" color="gray" />
|
||||
) : null}
|
||||
<span className="min-w-0 translate-y-px truncate">
|
||||
{contextLabel}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function InboxListPane({
|
||||
<div className="px-5 py-1">
|
||||
<div className="flex min-w-0 items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-[6px]">
|
||||
<Inbox className="h-[14px] w-[14px] shrink-0 text-muted-foreground" />
|
||||
<Inbox className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<h2 className="translate-y-px truncate text-sm font-semibold leading-5 tracking-tight">
|
||||
Inbox
|
||||
</h2>
|
||||
@@ -73,7 +73,7 @@ export function InboxListPane({
|
||||
variant="outline"
|
||||
>
|
||||
<span>{activeFilter?.label ?? "All"}</span>
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="min-w-40">
|
||||
|
||||
@@ -67,7 +67,7 @@ export function RecentNotesSection({
|
||||
size="sm"
|
||||
/>
|
||||
{isAgent ? (
|
||||
<Bot className="absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full bg-background p-0.5 text-muted-foreground" />
|
||||
<Bot className="absolute -bottom-0.5 -right-0.5 h-4 w-4 rounded-full bg-background p-0.5 text-muted-foreground" />
|
||||
) : null}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
|
||||
@@ -24,7 +24,7 @@ export function HeadphonesNotice({ onDismiss }: { onDismiss: () => void }) {
|
||||
data-testid="huddle-headphones-notice"
|
||||
className="flex items-center gap-1.5 rounded bg-amber-500/10 px-2 py-1 text-xs text-amber-700 dark:text-amber-300"
|
||||
>
|
||||
<Headphones className="h-3 w-3" />
|
||||
<Headphones className="h-4 w-4" />
|
||||
<span className="max-w-[260px] truncate">
|
||||
Headphones recommended — echo cancellation lands in the next release.
|
||||
</span>
|
||||
|
||||
@@ -304,7 +304,7 @@ export function HuddleBar({ className }: HuddleBarProps) {
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export function MicControls({
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
>
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@ export function SpeakerControls({
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
>
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</div>
|
||||
@@ -192,7 +192,7 @@ export function DeviceList({
|
||||
type="button"
|
||||
>
|
||||
<Check
|
||||
className={cn("h-3 w-3 shrink-0", selectedId && "invisible")}
|
||||
className={cn("h-4 w-4 shrink-0", selectedId && "invisible")}
|
||||
/>
|
||||
System default
|
||||
</button>
|
||||
@@ -207,7 +207,7 @@ export function DeviceList({
|
||||
type="button"
|
||||
>
|
||||
<Check
|
||||
className={cn("h-3 w-3 shrink-0", !isSelected && "invisible")}
|
||||
className={cn("h-4 w-4 shrink-0", !isSelected && "invisible")}
|
||||
/>
|
||||
<span className="truncate">{d.label}</span>
|
||||
</button>
|
||||
|
||||
@@ -244,7 +244,7 @@ export function MeshComputeSettingsCard() {
|
||||
<summary className="flex cursor-pointer items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-3.5 w-3.5 text-muted-foreground transition-transform",
|
||||
"h-4 w-4 text-muted-foreground transition-transform",
|
||||
advancedOpen ? "rotate-0" : "-rotate-90",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -166,7 +166,7 @@ export function RelayMeshAgentSection({
|
||||
) : null}
|
||||
{overrides.length > 0 ? (
|
||||
<p className="flex items-start gap-1.5 rounded bg-warning-bg/60 px-2 py-1.5 text-xs text-warning">
|
||||
<AlertCircle className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<span>
|
||||
Using Relay mesh overrides this agent's {overrides.join(", ")}.
|
||||
</span>
|
||||
|
||||
@@ -135,7 +135,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({
|
||||
className="group relative"
|
||||
>
|
||||
<div className="flex h-5 max-w-[10rem] items-center gap-1 rounded border border-border/70 bg-muted px-1.5">
|
||||
<FileText className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
<FileText className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate text-[10px] text-muted-foreground">
|
||||
{label}
|
||||
</span>
|
||||
@@ -186,7 +186,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({
|
||||
)}
|
||||
<div className="absolute inset-0 bg-black/15" />
|
||||
<div className="absolute flex h-5 w-5 items-center justify-center rounded-full bg-black/55 backdrop-blur-sm">
|
||||
<Play className="h-3 w-3 fill-white text-white" />
|
||||
<Play className="h-4 w-4 fill-white text-white" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -231,7 +231,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({
|
||||
/>
|
||||
)}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-full bg-black/50 p-2 text-white/80 transition-colors hover:bg-black/70 hover:text-white focus:outline-hidden focus:ring-2 focus:ring-white/30">
|
||||
<X className="h-5 w-5" />
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function DiffMessage({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Maximize2 className="h-3.5 w-3.5" />
|
||||
<Maximize2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Expand diff</TooltipContent>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function DiffMessageExpanded({
|
||||
type="button"
|
||||
variant={viewType === "unified" ? "secondary" : "ghost"}
|
||||
>
|
||||
<Rows3 className="h-3.5 w-3.5" />
|
||||
<Rows3 className="h-4 w-4" />
|
||||
Unified
|
||||
</Button>
|
||||
<Button
|
||||
@@ -58,7 +58,7 @@ export default function DiffMessageExpanded({
|
||||
type="button"
|
||||
variant={viewType === "split" ? "secondary" : "ghost"}
|
||||
>
|
||||
<SplitSquareVertical className="h-3.5 w-3.5" />
|
||||
<SplitSquareVertical className="h-4 w-4" />
|
||||
Split
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -212,13 +212,13 @@ export const FormattingToolbar = React.memo(function FormattingToolbar({
|
||||
"hover:bg-muted hover:text-foreground",
|
||||
"focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring",
|
||||
"disabled:pointer-events-none disabled:opacity-50",
|
||||
"[&_svg]:pointer-events-none [&_svg]:size-3.5 [&_svg]:shrink-0",
|
||||
"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
item.active
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-transparent text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-3.5 w-3.5" />
|
||||
<item.icon className="h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const MentionAutocomplete = React.memo(function MentionAutocomplete({
|
||||
>
|
||||
<Bot
|
||||
aria-hidden="true"
|
||||
className="h-3 w-3"
|
||||
className="h-4 w-4"
|
||||
data-testid="mention-agent-icon"
|
||||
/>
|
||||
{agentLabel}
|
||||
|
||||
@@ -117,7 +117,7 @@ export function TypingIndicatorRow({
|
||||
: "h-5 w-5 text-[8px]",
|
||||
)}
|
||||
iconClassName={
|
||||
isActivityVariant ? "h-2.5 w-2.5" : "h-3 w-3"
|
||||
isActivityVariant ? "h-2.5 w-2.5" : "h-4 w-4"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,7 @@ function AvatarStepActions({
|
||||
{isSaving || isUploadingAvatar ? (
|
||||
<Spinner
|
||||
aria-label={isSaving ? "Saving profile" : "Uploading avatar"}
|
||||
className="h-4 w-4"
|
||||
className="h-4 w-4 border-2"
|
||||
/>
|
||||
) : (
|
||||
"Next"
|
||||
|
||||
@@ -89,7 +89,7 @@ export function MembershipDenied({
|
||||
<Badge variant="warning">Membership required</Badge>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-destructive/10">
|
||||
<ShieldX className="h-5 w-5 text-destructive" />
|
||||
<ShieldX className="h-4 w-4 text-destructive" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight text-foreground">
|
||||
Not a member yet
|
||||
@@ -172,7 +172,7 @@ export function MembershipDenied({
|
||||
className="flex items-start gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-xs"
|
||||
data-testid="membership-denied-npub-preview"
|
||||
>
|
||||
<Check className="mt-0.5 h-3.5 w-3.5 shrink-0 text-primary" />
|
||||
<Check className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
||||
<div className="min-w-0 space-y-0.5">
|
||||
<p className="font-medium text-foreground">
|
||||
This will use this Nostr identity:
|
||||
@@ -197,7 +197,10 @@ export function MembershipDenied({
|
||||
type="submit"
|
||||
>
|
||||
{isImportingKey ? (
|
||||
<Spinner aria-label="Importing key" className="h-4 w-4" />
|
||||
<Spinner
|
||||
aria-label="Importing key"
|
||||
className="h-4 w-4 border-2"
|
||||
/>
|
||||
) : (
|
||||
"Import key"
|
||||
)}
|
||||
@@ -236,7 +239,7 @@ export function MembershipDenied({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<KeyRound className="h-3 w-3" />
|
||||
<KeyRound className="h-4 w-4" />
|
||||
Use a different key
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
@@ -222,7 +222,7 @@ export function NostrKeyImportForm({
|
||||
className="flex items-start gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-xs"
|
||||
data-testid="nostr-import-npub-preview"
|
||||
>
|
||||
<Check className="mt-0.5 h-3.5 w-3.5 shrink-0 text-primary" />
|
||||
<Check className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
||||
<div className="min-w-0 space-y-0.5">
|
||||
<p className="font-medium text-foreground">
|
||||
This will use this Nostr identity:
|
||||
|
||||
@@ -92,6 +92,7 @@ export function ProfileStep({
|
||||
) : null}
|
||||
<input
|
||||
aria-label="Name"
|
||||
autoCapitalize="none"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className={cn(
|
||||
@@ -128,7 +129,7 @@ export function ProfileStep({
|
||||
type="button"
|
||||
>
|
||||
{isSaving ? (
|
||||
<Spinner aria-label="Saving profile" className="h-4 w-4" />
|
||||
<Spinner aria-label="Saving profile" className="h-4 w-4 border-2" />
|
||||
) : (
|
||||
"Next"
|
||||
)}
|
||||
|
||||
@@ -106,7 +106,7 @@ function RuntimeStatus({
|
||||
className="flex h-8 shrink-0 items-center justify-center"
|
||||
role="status"
|
||||
>
|
||||
<Spinner className="h-4 w-4 text-foreground" />
|
||||
<Spinner className="h-4 w-4 border-2 text-foreground" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { Camera, Link2, Loader2, Upload, X } from "lucide-react";
|
||||
import { Camera, Link2, Upload, X } from "lucide-react";
|
||||
|
||||
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
||||
import { useAvatarUpload } from "@/features/profile/useAvatarUpload";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import { Spinner } from "@/shared/ui/spinner";
|
||||
|
||||
type AvatarUploadProps = {
|
||||
avatarUrl: string;
|
||||
@@ -90,7 +91,7 @@ export function AvatarUpload({
|
||||
title="Remove photo"
|
||||
type="button"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
) : (
|
||||
<div className="absolute -bottom-1 -right-1 flex h-8 w-8 items-center justify-center rounded-full border border-background bg-primary text-primary-foreground shadow-xs">
|
||||
@@ -127,9 +128,12 @@ export function AvatarUpload({
|
||||
type="button"
|
||||
>
|
||||
{isUploading ? (
|
||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
<Spinner
|
||||
aria-hidden
|
||||
className="h-4 w-4 border-2 text-muted-foreground"
|
||||
/>
|
||||
) : (
|
||||
<Upload className="h-5 w-5 text-muted-foreground" />
|
||||
<Upload className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isUploading ? (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import emojiData from "@emoji-mart/data";
|
||||
import Picker from "@emoji-mart/react";
|
||||
import { Link2, Loader2, UploadCloud } from "lucide-react";
|
||||
import { Link2, UploadCloud } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import * as React from "react";
|
||||
|
||||
@@ -579,7 +579,10 @@ export function ProfileAvatarEditor({
|
||||
data-testid={`${testIdPrefix}-drop-mask`}
|
||||
/>
|
||||
{isUploading ? (
|
||||
<Loader2 className="relative h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<Spinner
|
||||
aria-hidden
|
||||
className="relative h-8 w-8 text-muted-foreground"
|
||||
/>
|
||||
) : (
|
||||
<UploadCloud
|
||||
className={cn(
|
||||
@@ -612,6 +615,8 @@ export function ProfileAvatarEditor({
|
||||
<div className="flex h-16 items-center gap-3 rounded-xl bg-muted px-5 transition-colors duration-[250ms] ease-out focus-within:bg-muted/80">
|
||||
<Link2 className="h-4 w-4 text-muted-foreground" />
|
||||
<input
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
className="min-w-0 flex-1 bg-transparent text-sm font-medium text-foreground outline-none placeholder:text-muted-foreground"
|
||||
data-testid={`${testIdPrefix}-url`}
|
||||
disabled={isInputDisabled}
|
||||
@@ -636,6 +641,7 @@ export function ProfileAvatarEditor({
|
||||
}
|
||||
}}
|
||||
placeholder="Paste a URL (Slack profile, etc.)"
|
||||
spellCheck={false}
|
||||
type="url"
|
||||
value={urlDraft}
|
||||
/>
|
||||
@@ -960,7 +966,10 @@ export function ProfileAvatarEditor({
|
||||
type="button"
|
||||
>
|
||||
{donePending ? (
|
||||
<Spinner aria-label="Saving avatar" className="h-4 w-4" />
|
||||
<Spinner
|
||||
aria-label="Saving avatar"
|
||||
className="h-4 w-4 border-2"
|
||||
/>
|
||||
) : (
|
||||
"Done"
|
||||
)}
|
||||
|
||||
@@ -393,7 +393,7 @@ function ProfileHeroDescription({ about }: { about: string }) {
|
||||
type="button"
|
||||
>
|
||||
more
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
{expanded ? (
|
||||
@@ -404,7 +404,7 @@ function ProfileHeroDescription({ about }: { about: string }) {
|
||||
type="button"
|
||||
>
|
||||
less
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -284,7 +284,7 @@ export function UserProfilePopover({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Activity className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Activity className="h-4 w-4 text-muted-foreground" />
|
||||
View activity log
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
@@ -30,7 +30,7 @@ function CloneUrlRow({ url }: { url: string }) {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border/60 bg-muted/30 px-3 py-2">
|
||||
<GitFork className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<GitFork className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<code className="min-w-0 flex-1 truncate text-xs">{url}</code>
|
||||
<Button
|
||||
className="h-6 w-6 shrink-0"
|
||||
@@ -39,9 +39,9 @@ function CloneUrlRow({ url }: { url: string }) {
|
||||
variant="ghost"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-3 w-3 text-green-500" />
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-3 w-3" />
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -89,7 +89,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
<ArrowLeft className="mr-1.5 h-3.5 w-3.5" />
|
||||
<ArrowLeft className="mr-1.5 h-4 w-4" />
|
||||
Back to Projects
|
||||
</Button>
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
<ArrowLeft className="mr-1.5 h-3.5 w-3.5" />
|
||||
<ArrowLeft className="mr-1.5 h-4 w-4" />
|
||||
Back to Projects
|
||||
</Button>
|
||||
</div>
|
||||
@@ -136,7 +136,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
<ArrowLeft className="h-3.5 w-3.5" />
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to Projects
|
||||
</Button>
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
|
||||
<div className="mx-auto w-full max-w-2xl space-y-6">
|
||||
<section className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderGit2 className="h-5 w-5 text-muted-foreground" />
|
||||
<FolderGit2 className="h-4 w-4 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold">{project.name}</h2>
|
||||
</div>
|
||||
{project.description ? (
|
||||
@@ -178,7 +178,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
{project.webUrl}
|
||||
</a>
|
||||
</section>
|
||||
@@ -188,7 +188,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
|
||||
<section className="space-y-2">
|
||||
<h3 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Users className="h-3.5 w-3.5" />
|
||||
<Users className="h-4 w-4" />
|
||||
Contributors ({project.contributors.length})
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
@@ -86,19 +86,19 @@ export function ProjectsView() {
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-muted-foreground/70">
|
||||
{project.cloneUrls.length > 0 ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<GitFork className="h-3 w-3" />
|
||||
<GitFork className="h-4 w-4" />
|
||||
{project.cloneUrls[0]}
|
||||
</span>
|
||||
) : null}
|
||||
{project.contributors.length > 0 ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<Users className="h-3 w-3" />
|
||||
<Users className="h-4 w-4" />
|
||||
{project.contributors.length}
|
||||
</span>
|
||||
) : null}
|
||||
{project.webUrl ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
Web
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -66,7 +66,7 @@ export function AgentActivityCard({
|
||||
type="button"
|
||||
>
|
||||
<UserAvatar avatarUrl={avatarUrl} displayName={displayName} />
|
||||
<Bot className="absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded-full bg-background p-0.5 text-muted-foreground" />
|
||||
<Bot className="absolute -bottom-0.5 -right-0.5 h-4 w-4 rounded-full bg-background p-0.5 text-muted-foreground" />
|
||||
</button>
|
||||
</UserProfilePopover>
|
||||
<div className="min-w-0 flex-1">
|
||||
@@ -87,9 +87,9 @@ export function AgentActivityCard({
|
||||
type="button"
|
||||
>
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
{group.notes.length} updates
|
||||
</button>
|
||||
|
||||
@@ -171,7 +171,7 @@ export function NoteCard({
|
||||
displayName={displayName}
|
||||
/>
|
||||
{isAgent ? (
|
||||
<Bot className="absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded-full bg-background p-0.5 text-muted-foreground" />
|
||||
<Bot className="absolute -bottom-0.5 -right-0.5 h-4 w-4 rounded-full bg-background p-0.5 text-muted-foreground" />
|
||||
) : null}
|
||||
</button>
|
||||
</UserProfilePopover>
|
||||
|
||||
@@ -349,7 +349,7 @@ export function PulseView({ currentPubkey }: PulseViewProps) {
|
||||
className="absolute right-1.5 top-1/2 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-foreground/10 text-foreground transition-colors hover:bg-foreground/15 dark:bg-white/85 dark:text-black dark:hover:bg-white"
|
||||
type="button"
|
||||
>
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
<Search className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -121,7 +121,7 @@ function MemberRow({
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Actions</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -231,7 +231,7 @@ export function RelayMembersCard({
|
||||
onClick={() => setAddDialogOpen(true)}
|
||||
size="sm"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Member
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
@@ -165,10 +165,10 @@ function RelayMemberRow({
|
||||
<div className="min-w-0 flex-1 space-y-0.5">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{member.role === "owner" ? (
|
||||
<Crown className="h-3.5 w-3.5 text-amber-500" />
|
||||
<Crown className="h-4 w-4 text-amber-500" />
|
||||
) : null}
|
||||
{member.role === "admin" ? (
|
||||
<Shield className="h-3.5 w-3.5 text-blue-500" />
|
||||
<Shield className="h-4 w-4 text-blue-500" />
|
||||
) : null}
|
||||
<span className="truncate text-sm font-medium">{displayName}</span>
|
||||
{isSelf ? (
|
||||
@@ -461,10 +461,13 @@ export function RelayMembersSettingsCard({
|
||||
<div className="relative">
|
||||
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
className="w-full rounded-lg border border-border/70 bg-background/70 py-2 pl-9 pr-3 text-sm placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
|
||||
data-testid="relay-members-search"
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder="Search members by name, npub, or role…"
|
||||
spellCheck={false}
|
||||
type="text"
|
||||
value={search}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Loader2, RefreshCcw, RotateCw } from "lucide-react";
|
||||
import type { ComponentType } from "react";
|
||||
import { RefreshCcw, RotateCw } from "lucide-react";
|
||||
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Spinner } from "@/shared/ui/spinner";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
|
||||
import { useUpdaterContext } from "./hooks/UpdaterProvider";
|
||||
@@ -9,13 +11,18 @@ import type { UpdateStatus } from "./hooks/use-updater";
|
||||
const indicatorButtonClass =
|
||||
"relative text-muted-foreground/80 hover:bg-muted/60 hover:text-foreground";
|
||||
|
||||
type IndicatorIcon = ComponentType<{
|
||||
"aria-hidden"?: boolean;
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const variants: Record<
|
||||
"available" | "downloading" | "installing" | "ready",
|
||||
{
|
||||
Icon: typeof RefreshCcw;
|
||||
Icon: IndicatorIcon;
|
||||
iconClassName?: string;
|
||||
label: string;
|
||||
badgeColor: string;
|
||||
spin?: boolean;
|
||||
}
|
||||
> = {
|
||||
available: {
|
||||
@@ -24,16 +31,16 @@ const variants: Record<
|
||||
badgeColor: "bg-primary",
|
||||
},
|
||||
downloading: {
|
||||
Icon: Loader2,
|
||||
Icon: Spinner,
|
||||
iconClassName: "h-4 w-4 border-2",
|
||||
label: "Downloading update\u2026",
|
||||
badgeColor: "bg-primary",
|
||||
spin: true,
|
||||
},
|
||||
installing: {
|
||||
Icon: Loader2,
|
||||
Icon: Spinner,
|
||||
iconClassName: "h-4 w-4 border-2",
|
||||
label: "Installing update\u2026",
|
||||
badgeColor: "bg-primary",
|
||||
spin: true,
|
||||
},
|
||||
ready: {
|
||||
Icon: RotateCw,
|
||||
@@ -62,7 +69,7 @@ export function UpdateIndicator({ className }: { className?: string }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { Icon, label, badgeColor, spin } = variant;
|
||||
const { Icon, iconClassName = "h-4 w-4", label, badgeColor } = variant;
|
||||
const isActionable = status.state === "available" || status.state === "ready";
|
||||
const handleClick =
|
||||
status.state === "ready"
|
||||
@@ -87,7 +94,7 @@ export function UpdateIndicator({ className }: { className?: string }) {
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icon className={spin ? "animate-spin" : undefined} />
|
||||
<Icon aria-hidden className={iconClassName} />
|
||||
<span
|
||||
className={`absolute right-1 top-1 h-1.5 w-1.5 rounded-full ${badgeColor} animate-pulse`}
|
||||
/>
|
||||
|
||||
@@ -120,7 +120,7 @@ export function ChannelTemplatesSettingsCard() {
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" />
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
@@ -229,13 +229,13 @@ function TemplateRow({
|
||||
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
|
||||
{agentCount > 0 ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<Users className="h-3 w-3" />
|
||||
<Users className="h-4 w-4" />
|
||||
{agentCount} {agentCount === 1 ? "agent" : "agents"}
|
||||
</span>
|
||||
) : null}
|
||||
{template.canvasTemplate ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<MessageSquare className="h-3 w-3" />
|
||||
<MessageSquare className="h-4 w-4" />
|
||||
canvas
|
||||
</span>
|
||||
) : null}
|
||||
@@ -255,11 +255,11 @@ function TemplateRow({
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={onEdit}>
|
||||
<Pencil className="mr-2 h-3.5 w-3.5" />
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onDuplicate}>
|
||||
<Copy className="mr-2 h-3.5 w-3.5" />
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Duplicate
|
||||
</DropdownMenuItem>
|
||||
{!template.isBuiltin ? (
|
||||
@@ -267,7 +267,7 @@ function TemplateRow({
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={onDelete}
|
||||
>
|
||||
<Trash2 className="mr-2 h-3.5 w-3.5" />
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
|
||||
@@ -56,9 +56,9 @@ function InstallActions({
|
||||
variant="outline"
|
||||
>
|
||||
{isInstalling ? (
|
||||
<RefreshCw className="h-3.5 w-3.5 animate-spin" />
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-3.5 w-3.5" />
|
||||
<Download className="h-4 w-4" />
|
||||
)}
|
||||
{isInstalling ? "Installing..." : "Install"}
|
||||
</Button>
|
||||
@@ -68,7 +68,7 @@ function InstallActions({
|
||||
onClick={() => void openUrl(runtime.installInstructionsUrl)}
|
||||
type="button"
|
||||
>
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
View instructions
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -225,7 +225,7 @@ function PairingDialog({
|
||||
type="button"
|
||||
>
|
||||
<code className="min-w-0 flex-1 break-all">{qrUri}</code>
|
||||
<Copy className="h-3.5 w-3.5 shrink-0" />
|
||||
<Copy className="h-4 w-4 shrink-0" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -332,7 +332,7 @@ export function MobilePairingCard({
|
||||
|
||||
<SettingsOptionGroup>
|
||||
<SettingsOptionRow className="gap-3">
|
||||
<Smartphone className="h-5 w-5 shrink-0 text-muted-foreground" />
|
||||
<Smartphone className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium">Pair Mobile Device</p>
|
||||
<p className="text-sm font-normal text-muted-foreground">
|
||||
|
||||
@@ -227,7 +227,7 @@ export function NotificationSettingsCard({
|
||||
>
|
||||
{showComingSoon ? (
|
||||
<>
|
||||
<ChevronUp className="h-3.5 w-3.5" />
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
Show less
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -57,7 +57,7 @@ function IdentityRow({
|
||||
title={`Copy ${label}`}
|
||||
type="button"
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5 shrink-0" />
|
||||
<Copy className="h-4 w-4 shrink-0" />
|
||||
Copy
|
||||
</button>
|
||||
) : null}
|
||||
@@ -97,7 +97,7 @@ function EditProfileMetadataButton({
|
||||
title={accessibleLabel}
|
||||
type="button"
|
||||
>
|
||||
<Icon className="h-3.5 w-3.5 shrink-0" />
|
||||
<Icon className="h-4 w-4 shrink-0" />
|
||||
{actionLabel}
|
||||
</button>
|
||||
);
|
||||
@@ -577,7 +577,7 @@ export function ProfileSettingsCard({
|
||||
this device.
|
||||
</p>
|
||||
</div>
|
||||
<ChevronDown className="h-5 w-5 shrink-0 text-muted-foreground transition-[color,transform] duration-150 ease-out group-open:rotate-180 group-hover/identity:text-foreground group-focus-visible/identity:text-foreground" />
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-[color,transform] duration-150 ease-out group-open:rotate-180 group-hover/identity:text-foreground group-focus-visible/identity:text-foreground" />
|
||||
</summary>
|
||||
<div
|
||||
className="border-t border-border/55 divide-y divide-border/55"
|
||||
|
||||
@@ -193,9 +193,12 @@ function ThemeSettingsCard() {
|
||||
<div className="relative mb-3">
|
||||
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
className="w-full rounded-lg border border-border/70 bg-background/70 py-2 pl-9 pr-3 text-sm placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Search themes..."
|
||||
spellCheck={false}
|
||||
type="text"
|
||||
value={search}
|
||||
/>
|
||||
@@ -269,7 +272,7 @@ function ThemeSettingsCard() {
|
||||
type="button"
|
||||
>
|
||||
{accentColor === color.value && (
|
||||
<Check className={cn("h-3.5 w-3.5", checkClassName)} />
|
||||
<Check className={cn("h-4 w-4", checkClassName)} />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -135,9 +135,9 @@ export function SoundPicker({
|
||||
variant="ghost"
|
||||
>
|
||||
{isPlaying ? (
|
||||
<Pause className="h-3 w-3" />
|
||||
<Pause className="h-4 w-4" />
|
||||
) : (
|
||||
<Play className="h-3 w-3" />
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
|
||||
@@ -255,7 +255,7 @@ function SectionHeaderActions({
|
||||
title="Mark all as read"
|
||||
type="button"
|
||||
>
|
||||
<CheckCheck className="h-3.5 w-3.5" />
|
||||
<CheckCheck className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
{onBrowse ? (
|
||||
@@ -548,7 +548,7 @@ export function CustomChannelSection({
|
||||
>
|
||||
<GripVertical
|
||||
className={cn(
|
||||
"h-3 w-3 shrink-0 text-sidebar-foreground/30",
|
||||
"h-4 w-4 shrink-0 text-sidebar-foreground/30",
|
||||
SECTION_ACTION_VISIBILITY_CLASS,
|
||||
)}
|
||||
aria-hidden="true"
|
||||
@@ -580,7 +580,7 @@ export function CustomChannelSection({
|
||||
title="Mark all as read"
|
||||
type="button"
|
||||
>
|
||||
<CheckCheck className="h-3.5 w-3.5" />
|
||||
<CheckCheck className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
@@ -592,7 +592,7 @@ export function CustomChannelSection({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
aria-label="Delete section"
|
||||
@@ -603,7 +603,7 @@ export function CustomChannelSection({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type * as React from "react";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
|
||||
const MORE_UNREAD_BUTTON_CLASS =
|
||||
"h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-[11px] font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-3.5";
|
||||
"h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-[11px] font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-4";
|
||||
|
||||
export function MoreUnreadButton({
|
||||
bottomClassName = "bottom-0",
|
||||
|
||||
@@ -202,7 +202,7 @@ export function ChannelMenuButton({
|
||||
{isMuted ? (
|
||||
<BellOff
|
||||
className={cn(
|
||||
"ml-auto h-3 w-3 shrink-0",
|
||||
"ml-auto h-4 w-4 shrink-0",
|
||||
isActive
|
||||
? "text-sidebar-active-foreground/60"
|
||||
: "text-sidebar-foreground/40",
|
||||
|
||||
@@ -99,7 +99,7 @@ export function ChannelCombobox({
|
||||
<span className="truncate">
|
||||
{selected ? formatChannelLabel(selected) : "Select a channel..."}
|
||||
</span>
|
||||
<ChevronsUpDown className="ml-2 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
@@ -107,10 +107,11 @@ export function ChannelCombobox({
|
||||
className="w-(--radix-popover-trigger-width) p-0"
|
||||
>
|
||||
<div className="flex items-center gap-2 border-b border-border px-3 py-2">
|
||||
<Search className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<Search className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<input
|
||||
autoCapitalize="off"
|
||||
autoCapitalize="none"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
ref={(el) => el?.focus()}
|
||||
className="flex-1 bg-transparent text-sm outline-hidden placeholder:text-muted-foreground"
|
||||
onChange={(e) => {
|
||||
@@ -119,6 +120,7 @@ export function ChannelCombobox({
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Search channels..."
|
||||
spellCheck={false}
|
||||
value={query}
|
||||
/>
|
||||
</div>
|
||||
@@ -142,7 +144,7 @@ export function ChannelCombobox({
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"h-3.5 w-3.5 shrink-0",
|
||||
"h-4 w-4 shrink-0",
|
||||
channel.id === value ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -54,7 +54,7 @@ export function WorkflowApprovalCard({ approval }: WorkflowApprovalCardProps) {
|
||||
}
|
||||
size="sm"
|
||||
>
|
||||
<Check className="mr-1 h-3 w-3" />
|
||||
<Check className="mr-1 h-4 w-4" />
|
||||
Approve
|
||||
</Button>
|
||||
<Button
|
||||
@@ -70,7 +70,7 @@ export function WorkflowApprovalCard({ approval }: WorkflowApprovalCardProps) {
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
>
|
||||
<X className="mr-1 h-3 w-3" />
|
||||
<X className="mr-1 h-4 w-4" />
|
||||
Deny
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@ export function WorkflowCard({
|
||||
{channelName ? <span>{channelName}</span> : null}
|
||||
{triggerSummary ? <span>{triggerSummary}</span> : null}
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
<Clock className="h-4 w-4" />
|
||||
{new Date(workflow.updatedAt * 1000).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ export function WorkflowDetailPanel({
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
<Pencil className="mr-1 h-3 w-3" />
|
||||
<Pencil className="mr-1 h-4 w-4" />
|
||||
Edit
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -103,7 +103,7 @@ export function WorkflowDetailPanel({
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
<Play className="mr-1 h-3 w-3" />
|
||||
<Play className="mr-1 h-4 w-4" />
|
||||
{triggerMutation.isPending ? "Triggering..." : "Trigger"}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -219,7 +219,7 @@ export function WorkflowFormBuilder({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Code className="h-3.5 w-3.5" />
|
||||
<Code className="h-4 w-4" />
|
||||
{mode === "form" ? "Edit as YAML" : "Back to form"}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -335,7 +335,7 @@ export function WorkflowFormBuilder({
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
<Plus className="h-4 w-4" />
|
||||
Add step
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -326,7 +326,7 @@ export function WorkflowStepCard({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Trash2 className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export function WorkflowWebhookHeadersEditor({
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
<Plus className="h-4 w-4" />
|
||||
Add header
|
||||
</Button>
|
||||
</div>
|
||||
@@ -109,7 +109,7 @@ export function WorkflowWebhookHeadersEditor({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Trash2 className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function FormSelect({
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
||||
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ export function WorkspaceSwitcher({
|
||||
data-testid="relay-connection-warning"
|
||||
role="img"
|
||||
>
|
||||
<WifiOff className={isProfileVariant ? "h-3 w-3" : "h-4 w-4"} />
|
||||
<WifiOff className={isProfileVariant ? "h-4 w-4" : "h-4 w-4"} />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side={isProfileVariant ? "top" : "bottom"}>
|
||||
@@ -154,8 +154,8 @@ export function WorkspaceSwitcher({
|
||||
<ChevronDown
|
||||
className={
|
||||
isProfileVariant
|
||||
? "h-3 w-3 shrink-0 text-sidebar-foreground/45"
|
||||
: "h-3.5 w-3.5 shrink-0 text-sidebar-foreground/50"
|
||||
? "h-4 w-4 shrink-0 text-sidebar-foreground/45"
|
||||
: "h-4 w-4 shrink-0 text-sidebar-foreground/50"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -209,7 +209,7 @@ export function WorkspaceSwitcher({
|
||||
>
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
{activeWorkspace?.id === workspace.id ? (
|
||||
<Check className="h-3.5 w-3.5 text-primary" />
|
||||
<Check className="h-4 w-4 text-primary" />
|
||||
) : null}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
@@ -226,7 +226,7 @@ export function WorkspaceSwitcher({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -296,7 +296,7 @@ export function WorkspaceSwitcher({
|
||||
>
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
{activeWorkspace?.id === workspace.id ? (
|
||||
<Check className="h-3.5 w-3.5 text-primary" />
|
||||
<Check className="h-4 w-4 text-primary" />
|
||||
) : null}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{workspace.name}</span>
|
||||
@@ -311,7 +311,7 @@ export function WorkspaceSwitcher({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
|
||||
@@ -1627,7 +1627,7 @@ function VideoReviewDialog({
|
||||
type="button"
|
||||
onClick={() => setIsEmojiPickerOpen((open) => !open)}
|
||||
>
|
||||
<SmilePlus className="pointer-events-none h-5 w-5" />
|
||||
<SmilePlus className="pointer-events-none h-4 w-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>More reactions</TooltipContent>
|
||||
@@ -1900,7 +1900,7 @@ function VideoReviewCommentBody({
|
||||
</p>
|
||||
{reactions.some((reaction) => reaction.reactedByCurrentUser) ? (
|
||||
<Check
|
||||
className="ml-auto h-3.5 w-3.5 shrink-0 text-primary"
|
||||
className="ml-auto h-4 w-4 shrink-0 text-primary"
|
||||
aria-hidden
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -16,7 +16,9 @@ export function ImportStatusIcon({
|
||||
}) {
|
||||
switch (status) {
|
||||
case "importing":
|
||||
return <Spinner className="h-4 w-4 shrink-0 text-muted-foreground" />;
|
||||
return (
|
||||
<Spinner className="h-4 w-4 shrink-0 border-2 text-muted-foreground" />
|
||||
);
|
||||
case "done":
|
||||
return <Check className="h-4 w-4 shrink-0 text-green-500" />;
|
||||
case "error":
|
||||
|
||||
@@ -520,7 +520,7 @@ function MarkdownCodeBlock({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
<Copy className="h-4 w-4" />
|
||||
<span className="sr-only">Copy code block</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
@@ -579,7 +579,7 @@ function FileCard({
|
||||
className="my-1 inline-flex max-w-sm items-center gap-3 rounded-xl border border-border/70 bg-muted/40 px-3 py-2 text-left no-underline transition-colors hover:bg-muted/70"
|
||||
>
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-background text-muted-foreground">
|
||||
<FileText className="h-5 w-5" />
|
||||
<FileText className="h-4 w-4" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-medium text-foreground">
|
||||
|
||||
@@ -751,8 +751,8 @@ test("narrow thread view collapses channel header actions into a menu", async ({
|
||||
throw new Error("Expected header action menu and thread panel bounds");
|
||||
}
|
||||
const menuGapPx = threadPanelBox.x - (menuBox.x + menuBox.width);
|
||||
expect(menuGapPx).toBeGreaterThanOrEqual(10);
|
||||
expect(menuGapPx).toBeLessThanOrEqual(14);
|
||||
expect(menuGapPx).toBeGreaterThanOrEqual(22);
|
||||
expect(menuGapPx).toBeLessThanOrEqual(26);
|
||||
|
||||
await menuTrigger.click();
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ export function RepoBlobPage() {
|
||||
<BackLink repoId={repoId} />
|
||||
|
||||
<div className="mt-4 flex flex-wrap items-center gap-3">
|
||||
<FileText className="h-5 w-5 text-muted-foreground" />
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
<h1 className="min-w-0 truncate font-mono text-sm">{filepath}</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{view &&
|
||||
|
||||
@@ -22,12 +22,12 @@ export function RepoRefsSection({
|
||||
<>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Badge variant="secondary">
|
||||
<GitBranch className="mr-1 h-3 w-3" />
|
||||
<GitBranch className="mr-1 h-4 w-4" />
|
||||
{refs.head.ref}
|
||||
</Badge>
|
||||
{refs.head.sha && (
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
<Hash className="mr-0.5 h-3 w-3" />
|
||||
<Hash className="mr-0.5 h-4 w-4" />
|
||||
{refs.head.sha.slice(0, 7)}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -36,13 +36,13 @@ export function RepoRefsSection({
|
||||
</>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
<GitBranch className="h-3.5 w-3.5" />
|
||||
<GitBranch className="h-4 w-4" />
|
||||
{refs.branches.length}{" "}
|
||||
{refs.branches.length === 1 ? "branch" : "branches"}
|
||||
</span>
|
||||
<span className="text-muted-foreground/60">·</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Tag className="h-3.5 w-3.5" />
|
||||
<Tag className="h-4 w-4" />
|
||||
{refs.tags.length} {refs.tags.length === 1 ? "tag" : "tags"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -104,7 +104,7 @@ export function ReposPage() {
|
||||
<div className="flex w-full gap-8 px-4 py-8">
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="mb-4 flex items-center gap-2 text-lg font-semibold">
|
||||
<BookMarked className="h-5 w-5" /> Repositories
|
||||
<BookMarked className="h-4 w-4" /> Repositories
|
||||
</h2>
|
||||
<div className="divide-y">
|
||||
{["a", "b", "c", "d", "e"].map((key) => (
|
||||
@@ -131,7 +131,7 @@ export function ReposPage() {
|
||||
</div>
|
||||
|
||||
<h2 className="mb-4 flex items-center gap-2 text-lg font-semibold">
|
||||
<BookMarked className="h-5 w-5" /> Repositories
|
||||
<BookMarked className="h-4 w-4" /> Repositories
|
||||
</h2>
|
||||
|
||||
{/* Search + Sort bar */}
|
||||
|
||||
Reference in New Issue
Block a user