From 28770c7f1e4e4c9b05ded130090a3689b3238861 Mon Sep 17 00:00:00 2001
From: klopez4212
Date: Fri, 12 Jun 2026 15:05:32 +0100
Subject: [PATCH 1/3] Add shared skeleton loader primitives
---
desktop/src/shared/styles/globals.css | 125 ++++++++++++++++++++++++++
desktop/src/shared/ui/skeleton.tsx | 94 +++++++++++++++++--
desktop/src/shared/ui/spinner.tsx | 32 +++++--
3 files changed, 237 insertions(+), 14 deletions(-)
diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css
index bb3fdb2492..3cb2da5230 100644
--- a/desktop/src/shared/styles/globals.css
+++ b/desktop/src/shared/styles/globals.css
@@ -2,6 +2,16 @@
@import "tw-animate-css";
@config "../../../tailwind.config.js";
+.sprout-arc-spinner {
+ animation: sprout-arc-spinner-spin 500ms linear infinite;
+}
+
+@keyframes sprout-arc-spinner-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
.buzz-emoji-mart {
align-items: stretch;
display: flex;
@@ -837,6 +847,121 @@
}
}
+:root {
+ --skel-pulse-dur: 1000ms;
+ --skel-pulse-count: infinite;
+ --skel-pulse-min: 0.5;
+ --skel-reveal-dur: 400ms;
+ --skel-reveal-blur: 2px;
+ --skel-reveal-ease: ease-in-out;
+}
+
+.t-skel {
+ position: relative;
+}
+
+.t-skel[data-layout="flow"] {
+ display: grid;
+}
+
+.t-skel-skeleton,
+.t-skel-content {
+ inset: 0;
+ position: absolute;
+}
+
+.t-skel[data-layout="flow"] > .t-skel-skeleton,
+.t-skel[data-layout="flow"] > .t-skel-content {
+ grid-area: 1 / 1;
+ width: 100%;
+}
+
+.t-skel[data-layout="flow"] > .t-skel-skeleton {
+ inset: auto;
+ position: relative;
+}
+
+.t-skel[data-layout="flow"] > .t-skel-content,
+.t-skel[data-layout="flow"].is-revealed > .t-skel-skeleton {
+ inset: 0;
+ position: absolute;
+}
+
+.t-skel[data-layout="flow"].is-revealed > .t-skel-content {
+ inset: auto;
+ position: relative;
+}
+
+.t-skel-skeleton {
+ filter: blur(0);
+ opacity: 1;
+ transition:
+ opacity var(--skel-reveal-dur) var(--skel-reveal-ease),
+ filter var(--skel-reveal-dur) var(--skel-reveal-ease);
+ z-index: 1;
+}
+
+.t-skel-content {
+ filter: blur(var(--skel-reveal-blur));
+ opacity: 0;
+ pointer-events: none;
+ transition:
+ opacity var(--skel-reveal-dur) var(--skel-reveal-ease),
+ filter var(--skel-reveal-dur) var(--skel-reveal-ease);
+ z-index: 2;
+}
+
+.t-skel.is-revealed .t-skel-skeleton {
+ filter: blur(var(--skel-reveal-blur));
+ opacity: 0;
+ pointer-events: none;
+}
+
+.t-skel.is-revealed .t-skel-content {
+ filter: blur(0);
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.t-skel.is-resetting .t-skel-skeleton,
+.t-skel.is-resetting .t-skel-content {
+ transition: none;
+}
+
+.t-skel-bar.is-pulsing,
+.t-skel-skeleton.is-pulsing > :not(:has(.t-skel-bar)) {
+ animation: t-skel-pulse var(--skel-pulse-dur) ease-in-out
+ var(--skel-pulse-count);
+}
+
+.t-skel.is-revealed .t-skel-bar.is-pulsing,
+.t-skel.is-revealed .t-skel-skeleton.is-pulsing > :not(:has(.t-skel-bar)) {
+ animation: none;
+}
+
+@keyframes t-skel-pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: var(--skel-pulse-min);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .t-skel-skeleton,
+ .t-skel-content {
+ transition: none;
+ }
+
+ .t-skel-bar.is-pulsing,
+ .t-skel-skeleton.is-pulsing > :not(:has(.t-skel-bar)) {
+ animation: none;
+ }
+}
+
@layer components {
.buzz-startup-shell {
min-height: 100dvh;
diff --git a/desktop/src/shared/ui/skeleton.tsx b/desktop/src/shared/ui/skeleton.tsx
index 563cc4c9ef..b324eb5da4 100644
--- a/desktop/src/shared/ui/skeleton.tsx
+++ b/desktop/src/shared/ui/skeleton.tsx
@@ -1,15 +1,99 @@
+import * as React from "react";
+
import { cn } from "@/shared/lib/cn";
-function Skeleton({
+type SkeletonProps = React.HTMLAttributes & {
+ pulsing?: boolean;
+};
+
+function Skeleton({ className, pulsing = true, ...props }: SkeletonProps) {
+ return (
+
+ );
+}
+
+type SkeletonRevealProps = React.HTMLAttributes & {
+ contentClassName?: string;
+ layout?: "absolute" | "flow";
+ loading: boolean;
+ skeleton: React.ReactNode;
+ skeletonClassName?: string;
+};
+
+function SkeletonReveal({
+ children,
className,
+ contentClassName,
+ layout = "flow",
+ loading,
+ skeleton,
+ skeletonClassName,
...props
-}: React.HTMLAttributes) {
+}: SkeletonRevealProps) {
+ const rootRef = React.useRef(null);
+ const previousLoadingRef = React.useRef(loading);
+ const [isResetting, setIsResetting] = React.useState(false);
+
+ React.useLayoutEffect(() => {
+ const wasLoading = previousLoadingRef.current;
+ previousLoadingRef.current = loading;
+
+ if (!loading || wasLoading) return;
+
+ setIsResetting(true);
+ rootRef.current?.getBoundingClientRect();
+
+ const reset = () => setIsResetting(false);
+ const frameId = globalThis.requestAnimationFrame
+ ? globalThis.requestAnimationFrame(reset)
+ : globalThis.setTimeout(reset, 0);
+
+ return () => {
+ if (typeof frameId === "number") {
+ if (globalThis.cancelAnimationFrame) {
+ globalThis.cancelAnimationFrame(frameId);
+ } else {
+ globalThis.clearTimeout(frameId);
+ }
+ }
+ };
+ }, [loading]);
+
return (
+ >
+
+ {skeleton}
+
+
+ {children}
+
+
);
}
-export { Skeleton };
+export { Skeleton, SkeletonReveal };
diff --git a/desktop/src/shared/ui/spinner.tsx b/desktop/src/shared/ui/spinner.tsx
index e3f37181ce..764e390837 100644
--- a/desktop/src/shared/ui/spinner.tsx
+++ b/desktop/src/shared/ui/spinner.tsx
@@ -1,26 +1,40 @@
-import { Loader2 } from "lucide-react";
+import type * as React from "react";
import { cn } from "@/shared/lib/cn";
-type SpinnerProps = React.ComponentPropsWithoutRef<"svg"> & {
+type SpinnerProps = React.ComponentPropsWithoutRef<"span"> & {
className?: string;
- size?: number;
+ size?: number | string;
};
export function Spinner({
+ children,
className,
size,
role = "status",
"aria-label": ariaLabel = "Loading",
+ "aria-hidden": ariaHidden,
+ style,
...rest
}: SpinnerProps) {
+ const isDecorative = ariaHidden === true || ariaHidden === "true";
+
return (
-
+ >
+ {children}
+ {isDecorative ? null : {ariaLabel} }
+
);
}
From bdd5d5c5fe5862a5dc9fda9f3bba72a5946d3e0b Mon Sep 17 00:00:00 2001
From: klopez4212
Date: Fri, 12 Jun 2026 15:06:54 +0100
Subject: [PATCH 2/3] Normalize desktop icon sizing
---
desktop/src/app/AppTopChrome.tsx | 39 ++++++++++++++-----
.../agent-memory/ui/MemorySection.tsx | 8 ++--
.../agents/ui/AgentSessionToolItem.tsx | 4 +-
.../agents/ui/AgentSessionTranscriptList.tsx | 10 ++---
.../features/agents/ui/BatchImportDialog.tsx | 6 +--
desktop/src/features/agents/ui/CopyButton.tsx | 2 +-
.../agents/ui/ManagedAgentSessionPanel.tsx | 15 ++++---
.../src/features/agents/ui/ModelPicker.tsx | 4 +-
.../ui/PersonaCatalogSelectionBadge.tsx | 2 +-
.../src/features/agents/ui/PersonaDialog.tsx | 4 +-
.../features/agents/ui/PersonaIdentity.tsx | 2 +-
.../src/features/agents/ui/RespondToField.tsx | 2 +-
desktop/src/features/agents/ui/TeamDialog.tsx | 4 +-
.../features/agents/ui/TeamImportDialog.tsx | 2 +-
.../src/features/agents/ui/TeamsSection.tsx | 6 +--
.../channels/ui/AddChannelBotDialog.tsx | 2 +-
.../ui/AddChannelBotPersonasSection.tsx | 4 +-
.../channels/ui/AgentSessionThreadPanel.tsx | 2 +-
.../features/channels/ui/BotActivityBar.tsx | 4 +-
.../channels/ui/ChannelManagementSheet.tsx | 2 +-
.../channels/ui/ChannelMemberInviteCard.tsx | 2 +-
.../channels/ui/ChannelScreenHeader.tsx | 2 +-
.../src/features/channels/ui/QuickBotBar.tsx | 2 +-
desktop/src/features/chat/ui/ChatHeader.tsx | 8 ++--
.../features/forum/ui/DeleteActionMenu.tsx | 8 +---
.../forum/ui/ForumComposerCompactLayout.tsx | 7 ++--
.../src/features/forum/ui/ForumPostCard.tsx | 2 +-
.../src/features/home/ui/InboxDetailPane.tsx | 10 +----
.../src/features/home/ui/InboxListPane.tsx | 4 +-
.../features/home/ui/RecentNotesSection.tsx | 2 +-
.../huddle/components/HeadphonesNotice.tsx | 2 +-
.../features/huddle/components/HuddleBar.tsx | 2 +-
.../huddle/components/MicControls.tsx | 8 ++--
.../ui/MeshComputeSettingsCard.tsx | 2 +-
.../mesh-compute/ui/RelayMeshAgentSection.tsx | 2 +-
.../messages/ui/ComposerAttachments.tsx | 6 +--
.../src/features/messages/ui/DiffMessage.tsx | 2 +-
.../messages/ui/DiffMessageExpanded.tsx | 4 +-
.../messages/ui/FormattingToolbar.tsx | 4 +-
.../messages/ui/MentionAutocomplete.tsx | 2 +-
.../messages/ui/TypingIndicatorRow.tsx | 2 +-
.../src/features/onboarding/ui/AvatarStep.tsx | 2 +-
.../onboarding/ui/MembershipDenied.tsx | 11 ++++--
.../onboarding/ui/NostrKeyImportForm.tsx | 2 +-
.../features/onboarding/ui/ProfileStep.tsx | 3 +-
.../src/features/onboarding/ui/SetupStep.tsx | 2 +-
.../src/features/profile/ui/AvatarUpload.tsx | 12 ++++--
.../profile/ui/ProfileAvatarEditor.tsx | 15 +++++--
.../profile/ui/UserProfilePanelSections.tsx | 4 +-
.../profile/ui/UserProfilePopover.tsx | 2 +-
.../projects/ui/ProjectDetailScreen.tsx | 18 ++++-----
.../src/features/projects/ui/ProjectsView.tsx | 6 +--
.../features/pulse/ui/AgentActivityCard.tsx | 6 +--
desktop/src/features/pulse/ui/NoteCard.tsx | 2 +-
desktop/src/features/pulse/ui/PulseView.tsx | 2 +-
.../relay-members/ui/RelayMembersCard.tsx | 4 +-
.../ui/RelayMembersSettingsCard.tsx | 7 +++-
.../src/features/settings/UpdateIndicator.tsx | 25 +++++++-----
.../ui/ChannelTemplatesSettingsCard.tsx | 12 +++---
.../settings/ui/DoctorSettingsPanel.tsx | 6 +--
.../settings/ui/MobilePairingCard.tsx | 4 +-
.../settings/ui/NotificationSettingsCard.tsx | 2 +-
.../settings/ui/ProfileSettingsCard.tsx | 6 +--
.../features/settings/ui/SettingsPanels.tsx | 5 ++-
.../src/features/settings/ui/SoundPicker.tsx | 4 +-
.../sidebar/ui/CustomChannelSection.tsx | 10 ++---
.../features/sidebar/ui/MoreUnreadButton.tsx | 2 +-
.../features/sidebar/ui/SidebarSection.tsx | 2 +-
.../features/workflows/ui/ChannelCombobox.tsx | 10 +++--
.../workflows/ui/WorkflowApprovalCard.tsx | 4 +-
.../features/workflows/ui/WorkflowCard.tsx | 2 +-
.../workflows/ui/WorkflowDetailPanel.tsx | 4 +-
.../workflows/ui/WorkflowFormBuilder.tsx | 4 +-
.../workflows/ui/WorkflowStepCard.tsx | 2 +-
.../ui/WorkflowWebhookHeadersEditor.tsx | 4 +-
.../workflows/ui/workflowFormPrimitives.tsx | 2 +-
.../workspaces/ui/WorkspaceSwitcher.tsx | 14 +++----
desktop/src/shared/ui/VideoPlayer.tsx | 4 +-
desktop/src/shared/ui/import-status-icon.tsx | 4 +-
desktop/src/shared/ui/markdown.tsx | 4 +-
web/src/features/repos/ui/RepoBlobViewer.tsx | 2 +-
web/src/features/repos/ui/RepoRefsSection.tsx | 8 ++--
web/src/features/repos/ui/ReposPage.tsx | 4 +-
83 files changed, 251 insertions(+), 204 deletions(-)
diff --git a/desktop/src/app/AppTopChrome.tsx b/desktop/src/app/AppTopChrome.tsx
index bfe27655ff..739c03ecc7 100644
--- a/desktop/src/app/AppTopChrome.tsx
+++ b/desktop/src/app/AppTopChrome.tsx
@@ -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 (
-
+ {searchLoading ? (
+
+
+
+ ) : (
+
+ )}
);
}
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
/>
-
+
)}
>
diff --git a/desktop/src/features/agent-memory/ui/MemorySection.tsx b/desktop/src/features/agent-memory/ui/MemorySection.tsx
index 0b2d4f4a45..46ff006e50 100644
--- a/desktop/src/features/agent-memory/ui/MemorySection.tsx
+++ b/desktop/src/features/agent-memory/ui/MemorySection.tsx
@@ -85,7 +85,7 @@ export function MemoryRefreshButton({
>
@@ -169,7 +169,7 @@ function MemoryErrorState({
role="alert"
>
-
+
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"
>
-
+
Refresh failed.
{hasDanglingRefs ? (
-
+
) : null}
diff --git a/desktop/src/features/agents/ui/AgentSessionToolItem.tsx b/desktop/src/features/agents/ui/AgentSessionToolItem.tsx
index 09411f2e66..78dd98304f 100644
--- a/desktop/src/features/agents/ui/AgentSessionToolItem.tsx
+++ b/desktop/src/features/agents/ui/AgentSessionToolItem.tsx
@@ -81,7 +81,7 @@ export function ToolItem({
) : null}
-
+
{action.label}
{action.value}
-
+
);
}
diff --git a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
index ef11f463fd..2f1f47e1f8 100644
--- a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
+++ b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
@@ -27,7 +27,7 @@ export function AgentSessionTranscriptList({
if (items.length === 0) {
return (
-
+
No ACP activity yet
{emptyDescription}
@@ -126,7 +126,7 @@ function MessageItem({
{isAssistant ? (
-
+
{agentName}
@@ -163,7 +163,7 @@ function ThoughtItem({
{item.title}
-
+
@@ -186,7 +186,7 @@ function MetadataItem({
{item.sections.length} section{item.sections.length === 1 ? "" : "s"}
-
+
{item.sections.map((section) => (
@@ -196,7 +196,7 @@ function MetadataItem({
>
{section.title}
-
+
{section.body.trim() || "No metadata."}
diff --git a/desktop/src/features/agents/ui/BatchImportDialog.tsx b/desktop/src/features/agents/ui/BatchImportDialog.tsx
index 7d09089cb5..5e0ba0f6c1 100644
--- a/desktop/src/features/agents/ui/BatchImportDialog.tsx
+++ b/desktop/src/features/agents/ui/BatchImportDialog.tsx
@@ -213,12 +213,12 @@ export function BatchImportDialog({
onClick={() => setSkippedExpanded((prev) => !prev)}
type="button"
>
-
+
{skipped.length} file{skipped.length !== 1 ? "s" : ""} skipped
{skippedExpanded ? (
-
+
) : (
-
+
)}
{skippedExpanded ? (
diff --git a/desktop/src/features/agents/ui/CopyButton.tsx b/desktop/src/features/agents/ui/CopyButton.tsx
index fa81831423..a8d0b621f0 100644
--- a/desktop/src/features/agents/ui/CopyButton.tsx
+++ b/desktop/src/features/agents/ui/CopyButton.tsx
@@ -20,7 +20,7 @@ export function CopyButton({
type="button"
variant="outline"
>
-
+
{label ?? "Copy"}
);
diff --git a/desktop/src/features/agents/ui/ManagedAgentSessionPanel.tsx b/desktop/src/features/agents/ui/ManagedAgentSessionPanel.tsx
index 4ff97a5bb7..3defb7d2bd 100644
--- a/desktop/src/features/agents/ui/ManagedAgentSessionPanel.tsx
+++ b/desktop/src/features/agents/ui/ManagedAgentSessionPanel.tsx
@@ -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 (
-
+ {StatusIcon ? (
+
+ ) : (
+
+ )}
{display.label}
);
@@ -253,7 +256,7 @@ function ObserverStatusBadge({ state }: { state: ConnectionState }) {
function EmptyObserverState() {
return (
-
+
Observer not attached
The live feed is available for local agents started after this update.
diff --git a/desktop/src/features/agents/ui/ModelPicker.tsx b/desktop/src/features/agents/ui/ModelPicker.tsx
index 783b8db546..3e16814f63 100644
--- a/desktop/src/features/agents/ui/ModelPicker.tsx
+++ b/desktop/src/features/agents/ui/ModelPicker.tsx
@@ -93,7 +93,7 @@ export function ModelPicker({
variant="ghost"
>
{displayLabel}
-
+
{loading ? (
-
+
Loading models...
) : error ? (
diff --git a/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx b/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx
index fb00584246..0dcd586699 100644
--- a/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx
+++ b/desktop/src/features/agents/ui/PersonaCatalogSelectionBadge.tsx
@@ -20,7 +20,7 @@ export function PersonaCatalogSelectionBadge({
: "border border-border/70 bg-background/85 text-muted-foreground",
)}
>
- {isActive ? : null}
+ {isActive ? : null}
{isActive
? personaCatalogCopy.selectedState
: personaCatalogCopy.availableState}
diff --git a/desktop/src/features/agents/ui/PersonaDialog.tsx b/desktop/src/features/agents/ui/PersonaDialog.tsx
index 4393187f58..e2e42a14a5 100644
--- a/desktop/src/features/agents/ui/PersonaDialog.tsx
+++ b/desktop/src/features/agents/ui/PersonaDialog.tsx
@@ -497,12 +497,12 @@ export function PersonaDialog({
: undefined
}
>
-
+
{importButtonLabel}
{isImportingUpdate ? (
-
+
) : null}
>
diff --git a/desktop/src/features/agents/ui/PersonaIdentity.tsx b/desktop/src/features/agents/ui/PersonaIdentity.tsx
index 3b129cc42e..eb2c5c0253 100644
--- a/desktop/src/features/agents/ui/PersonaIdentity.tsx
+++ b/desktop/src/features/agents/ui/PersonaIdentity.tsx
@@ -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"
>
-
+
diff --git a/desktop/src/features/agents/ui/RespondToField.tsx b/desktop/src/features/agents/ui/RespondToField.tsx
index 2e903bfebb..055022a8d2 100644
--- a/desktop/src/features/agents/ui/RespondToField.tsx
+++ b/desktop/src/features/agents/ui/RespondToField.tsx
@@ -258,7 +258,7 @@ function AllowlistPicker({
onClick={() => onRemove(pubkey)}
type="button"
>
-
+
))}
diff --git a/desktop/src/features/agents/ui/TeamDialog.tsx b/desktop/src/features/agents/ui/TeamDialog.tsx
index 06421bc517..2e2eb2856b 100644
--- a/desktop/src/features/agents/ui/TeamDialog.tsx
+++ b/desktop/src/features/agents/ui/TeamDialog.tsx
@@ -475,12 +475,12 @@ export function TeamDialog({
: undefined
}
>
-
+
{importButtonLabel}
{isImportingUpdate ? (
-
+
) : null}
>
diff --git a/desktop/src/features/agents/ui/TeamImportDialog.tsx b/desktop/src/features/agents/ui/TeamImportDialog.tsx
index e29d8e486b..021548ea57 100644
--- a/desktop/src/features/agents/ui/TeamImportDialog.tsx
+++ b/desktop/src/features/agents/ui/TeamImportDialog.tsx
@@ -130,7 +130,7 @@ export function TeamImportDialog({
{preview ? (
-
+
{preview.name}
diff --git a/desktop/src/features/agents/ui/TeamsSection.tsx b/desktop/src/features/agents/ui/TeamsSection.tsx
index bcbcd7e104..145fc22f4d 100644
--- a/desktop/src/features/agents/ui/TeamsSection.tsx
+++ b/desktop/src/features/agents/ui/TeamsSection.tsx
@@ -108,7 +108,7 @@ export function TeamsSection({
onClick={onInstallFromDirectory}
type="button"
>
-
+
Install from directory
-
+
@@ -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"
>
-
+
diff --git a/desktop/src/features/channels/ui/AddChannelBotDialog.tsx b/desktop/src/features/channels/ui/AddChannelBotDialog.tsx
index 139931e632..403fa84c1b 100644
--- a/desktop/src/features/channels/ui/AddChannelBotDialog.tsx
+++ b/desktop/src/features/channels/ui/AddChannelBotDialog.tsx
@@ -530,7 +530,7 @@ export function AddChannelBotDialog({
variant="ghost"
>
{runtimeTriggerLabel}
-
+
) : null}
@@ -193,7 +193,7 @@ export function AddChannelBotPersonasSection({
{persona.displayName}
diff --git a/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx b/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx
index c35be56572..ae633bbf30 100644
--- a/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx
+++ b/desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx
@@ -102,7 +102,7 @@ export function AgentSessionThreadPanel({
type="button"
variant="outline"
>
-
+
Stop
diff --git a/desktop/src/features/channels/ui/BotActivityBar.tsx b/desktop/src/features/channels/ui/BotActivityBar.tsx
index d4ee6ff1e5..a289200ad7 100644
--- a/desktop/src/features/channels/ui/BotActivityBar.tsx
+++ b/desktop/src/features/channels/ui/BotActivityBar.tsx
@@ -201,7 +201,7 @@ export function BotActivityComposerAction({
{isInline ? {visibleStatusLabel} : "working"}
{isInline ? null : (
-
+
)}
@@ -244,7 +244,7 @@ export function BotActivityComposerAction({
displayName={agent.name}
/>
{agent.name}
-
+
);
})}
diff --git a/desktop/src/features/channels/ui/ChannelManagementSheet.tsx b/desktop/src/features/channels/ui/ChannelManagementSheet.tsx
index cda00f0cf6..e708dfb9b6 100644
--- a/desktop/src/features/channels/ui/ChannelManagementSheet.tsx
+++ b/desktop/src/features/channels/ui/ChannelManagementSheet.tsx
@@ -79,7 +79,7 @@ function MetadataPill({
}) {
return (
-
+
{label}
);
diff --git a/desktop/src/features/channels/ui/ChannelMemberInviteCard.tsx b/desktop/src/features/channels/ui/ChannelMemberInviteCard.tsx
index 7f093294d7..4a06ff83ec 100644
--- a/desktop/src/features/channels/ui/ChannelMemberInviteCard.tsx
+++ b/desktop/src/features/channels/ui/ChannelMemberInviteCard.tsx
@@ -234,7 +234,7 @@ export function ChannelMemberInviteCard({
}}
type="button"
>
-
+
))}
diff --git a/desktop/src/features/channels/ui/ChannelScreenHeader.tsx b/desktop/src/features/channels/ui/ChannelScreenHeader.tsx
index a873069f71..a8dbeda72a 100644
--- a/desktop/src/features/channels/ui/ChannelScreenHeader.tsx
+++ b/desktop/src/features/channels/ui/ChannelScreenHeader.tsx
@@ -60,7 +60,7 @@ export function ChannelScreenHeader({
size="sm"
variant="default"
>
-
+
{isJoining ? "Joining…" : "Join"}
) : (
diff --git a/desktop/src/features/channels/ui/QuickBotBar.tsx b/desktop/src/features/channels/ui/QuickBotBar.tsx
index b4eec62c0f..3d4d243857 100644
--- a/desktop/src/features/channels/ui/QuickBotBar.tsx
+++ b/desktop/src/features/channels/ui/QuickBotBar.tsx
@@ -81,7 +81,7 @@ export function QuickBotBar({ personas, pending, onAdd }: QuickBotBarProps) {
)}
{isThisPending ? (
-
+
) : null}
diff --git a/desktop/src/features/chat/ui/ChatHeader.tsx b/desktop/src/features/chat/ui/ChatHeader.tsx
index ba027edaae..90b6c4e7db 100644
--- a/desktop/src/features/chat/ui/ChatHeader.tsx
+++ b/desktop/src/features/chat/ui/ChatHeader.tsx
@@ -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 = (
diff --git a/desktop/src/features/forum/ui/ForumComposerCompactLayout.tsx b/desktop/src/features/forum/ui/ForumComposerCompactLayout.tsx
index e482e01421..848dd7964e 100644
--- a/desktop/src/features/forum/ui/ForumComposerCompactLayout.tsx
+++ b/desktop/src/features/forum/ui/ForumComposerCompactLayout.tsx
@@ -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 ? (
-
) : (
-
+
)}
diff --git a/desktop/src/features/forum/ui/ForumPostCard.tsx b/desktop/src/features/forum/ui/ForumPostCard.tsx
index 92633a0d19..2ce878aee7 100644
--- a/desktop/src/features/forum/ui/ForumPostCard.tsx
+++ b/desktop/src/features/forum/ui/ForumPostCard.tsx
@@ -125,7 +125,7 @@ export function ForumPostCard({
{summary && summary.replyCount > 0 ? (
-
+
{summary.replyCount}{" "}
{summary.replyCount === 1 ? "reply" : "replies"}
diff --git a/desktop/src/features/home/ui/InboxDetailPane.tsx b/desktop/src/features/home/ui/InboxDetailPane.tsx
index 2104811b1c..8b504744fc 100644
--- a/desktop/src/features/home/ui/InboxDetailPane.tsx
+++ b/desktop/src/features/home/ui/InboxDetailPane.tsx
@@ -264,10 +264,7 @@ export function InboxDetailPane({
type="button"
>
{hasChannelContext ? (
-
+
) : null}
{contextLabel}
@@ -279,10 +276,7 @@ export function InboxDetailPane({
title={item.fullTimestampLabel}
>
{hasChannelContext ? (
-
+
) : null}
{contextLabel}
diff --git a/desktop/src/features/home/ui/InboxListPane.tsx b/desktop/src/features/home/ui/InboxListPane.tsx
index f3f2af6621..ac3e59b1b1 100644
--- a/desktop/src/features/home/ui/InboxListPane.tsx
+++ b/desktop/src/features/home/ui/InboxListPane.tsx
@@ -59,7 +59,7 @@ export function InboxListPane({
-
+
Inbox
@@ -73,7 +73,7 @@ export function InboxListPane({
variant="outline"
>
{activeFilter?.label ?? "All"}
-
+
diff --git a/desktop/src/features/home/ui/RecentNotesSection.tsx b/desktop/src/features/home/ui/RecentNotesSection.tsx
index d786af3790..2bacbc8382 100644
--- a/desktop/src/features/home/ui/RecentNotesSection.tsx
+++ b/desktop/src/features/home/ui/RecentNotesSection.tsx
@@ -67,7 +67,7 @@ export function RecentNotesSection({
size="sm"
/>
{isAgent ? (
-
+
) : null}
diff --git a/desktop/src/features/huddle/components/HeadphonesNotice.tsx b/desktop/src/features/huddle/components/HeadphonesNotice.tsx
index de588bed7b..17a8f343cc 100644
--- a/desktop/src/features/huddle/components/HeadphonesNotice.tsx
+++ b/desktop/src/features/huddle/components/HeadphonesNotice.tsx
@@ -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 recommended — echo cancellation lands in the next release.
diff --git a/desktop/src/features/huddle/components/HuddleBar.tsx b/desktop/src/features/huddle/components/HuddleBar.tsx
index fb32276b72..d288951852 100644
--- a/desktop/src/features/huddle/components/HuddleBar.tsx
+++ b/desktop/src/features/huddle/components/HuddleBar.tsx
@@ -304,7 +304,7 @@ export function HuddleBar({ className }: HuddleBarProps) {
size="icon"
variant="ghost"
>
-
+
diff --git a/desktop/src/features/huddle/components/MicControls.tsx b/desktop/src/features/huddle/components/MicControls.tsx
index 5730144f59..7cd5d648d8 100644
--- a/desktop/src/features/huddle/components/MicControls.tsx
+++ b/desktop/src/features/huddle/components/MicControls.tsx
@@ -67,7 +67,7 @@ export function MicControls({
size="icon"
variant="secondary"
>
-
+
@@ -151,7 +151,7 @@ export function SpeakerControls({
size="icon"
variant="secondary"
>
-
+
@@ -192,7 +192,7 @@ export function DeviceList({
type="button"
>
System default
@@ -207,7 +207,7 @@ export function DeviceList({
type="button"
>
{d.label}
diff --git a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx
index 3af7622349..edb08e1313 100644
--- a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx
+++ b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx
@@ -244,7 +244,7 @@ export function MeshComputeSettingsCard() {
diff --git a/desktop/src/features/mesh-compute/ui/RelayMeshAgentSection.tsx b/desktop/src/features/mesh-compute/ui/RelayMeshAgentSection.tsx
index 3a9fdb0b4f..c0ac1235d2 100644
--- a/desktop/src/features/mesh-compute/ui/RelayMeshAgentSection.tsx
+++ b/desktop/src/features/mesh-compute/ui/RelayMeshAgentSection.tsx
@@ -166,7 +166,7 @@ export function RelayMeshAgentSection({
) : null}
{overrides.length > 0 ? (
-
+
Using Relay mesh overrides this agent's {overrides.join(", ")}.
diff --git a/desktop/src/features/messages/ui/ComposerAttachments.tsx b/desktop/src/features/messages/ui/ComposerAttachments.tsx
index 2444895a22..8030c173c6 100644
--- a/desktop/src/features/messages/ui/ComposerAttachments.tsx
+++ b/desktop/src/features/messages/ui/ComposerAttachments.tsx
@@ -135,7 +135,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({
className="group relative"
>
-
+
{label}
@@ -186,7 +186,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({
)}
) : (
@@ -231,7 +231,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({
/>
)}
-
+
Close
diff --git a/desktop/src/features/messages/ui/DiffMessage.tsx b/desktop/src/features/messages/ui/DiffMessage.tsx
index ca5336dfc4..9189e6309c 100644
--- a/desktop/src/features/messages/ui/DiffMessage.tsx
+++ b/desktop/src/features/messages/ui/DiffMessage.tsx
@@ -86,7 +86,7 @@ export default function DiffMessage({
type="button"
variant="ghost"
>
-
+
Expand diff
diff --git a/desktop/src/features/messages/ui/DiffMessageExpanded.tsx b/desktop/src/features/messages/ui/DiffMessageExpanded.tsx
index fa7ec24baf..15ce1703e4 100644
--- a/desktop/src/features/messages/ui/DiffMessageExpanded.tsx
+++ b/desktop/src/features/messages/ui/DiffMessageExpanded.tsx
@@ -46,7 +46,7 @@ export default function DiffMessageExpanded({
type="button"
variant={viewType === "unified" ? "secondary" : "ghost"}
>
-
+
Unified
-
+
Split
diff --git a/desktop/src/features/messages/ui/FormattingToolbar.tsx b/desktop/src/features/messages/ui/FormattingToolbar.tsx
index fab4a1bfd9..368ef5ea44 100644
--- a/desktop/src/features/messages/ui/FormattingToolbar.tsx
+++ b/desktop/src/features/messages/ui/FormattingToolbar.tsx
@@ -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",
)}
>
-
+
diff --git a/desktop/src/features/messages/ui/MentionAutocomplete.tsx b/desktop/src/features/messages/ui/MentionAutocomplete.tsx
index c9a5528cd2..94a9fcb2de 100644
--- a/desktop/src/features/messages/ui/MentionAutocomplete.tsx
+++ b/desktop/src/features/messages/ui/MentionAutocomplete.tsx
@@ -98,7 +98,7 @@ export const MentionAutocomplete = React.memo(function MentionAutocomplete({
>
{agentLabel}
diff --git a/desktop/src/features/messages/ui/TypingIndicatorRow.tsx b/desktop/src/features/messages/ui/TypingIndicatorRow.tsx
index bd68bb042a..890407153b 100644
--- a/desktop/src/features/messages/ui/TypingIndicatorRow.tsx
+++ b/desktop/src/features/messages/ui/TypingIndicatorRow.tsx
@@ -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"
}
/>
diff --git a/desktop/src/features/onboarding/ui/AvatarStep.tsx b/desktop/src/features/onboarding/ui/AvatarStep.tsx
index a99c69013e..d339c1d62e 100644
--- a/desktop/src/features/onboarding/ui/AvatarStep.tsx
+++ b/desktop/src/features/onboarding/ui/AvatarStep.tsx
@@ -167,7 +167,7 @@ function AvatarStepActions({
{isSaving || isUploadingAvatar ? (
) : (
"Next"
diff --git a/desktop/src/features/onboarding/ui/MembershipDenied.tsx b/desktop/src/features/onboarding/ui/MembershipDenied.tsx
index 4f0618f94e..a3bfc5c879 100644
--- a/desktop/src/features/onboarding/ui/MembershipDenied.tsx
+++ b/desktop/src/features/onboarding/ui/MembershipDenied.tsx
@@ -89,7 +89,7 @@ export function MembershipDenied({
Membership required
-
+
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"
>
-
+
This will use this Nostr identity:
@@ -197,7 +197,10 @@ export function MembershipDenied({
type="submit"
>
{isImportingKey ? (
-
+
) : (
"Import key"
)}
@@ -236,7 +239,7 @@ export function MembershipDenied({
}}
type="button"
>
-
+
Use a different key
) : null}
diff --git a/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx b/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx
index b251fb9e3b..a31e455920 100644
--- a/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx
+++ b/desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx
@@ -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"
>
-
+
);
}
diff --git a/desktop/src/features/profile/ui/AvatarUpload.tsx b/desktop/src/features/profile/ui/AvatarUpload.tsx
index 81a5c83cf6..dd371a398e 100644
--- a/desktop/src/features/profile/ui/AvatarUpload.tsx
+++ b/desktop/src/features/profile/ui/AvatarUpload.tsx
@@ -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"
>
-
+
) : (
@@ -127,9 +128,12 @@ export function AvatarUpload({
type="button"
>
{isUploading ? (
-
+
) : (
-
+
)}
{isUploading ? (
diff --git a/desktop/src/features/profile/ui/ProfileAvatarEditor.tsx b/desktop/src/features/profile/ui/ProfileAvatarEditor.tsx
index 5ae567faff..d2de419732 100644
--- a/desktop/src/features/profile/ui/ProfileAvatarEditor.tsx
+++ b/desktop/src/features/profile/ui/ProfileAvatarEditor.tsx
@@ -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 ? (
-
+
) : (
@@ -960,7 +966,10 @@ export function ProfileAvatarEditor({
type="button"
>
{donePending ? (
-
+
) : (
"Done"
)}
diff --git a/desktop/src/features/profile/ui/UserProfilePanelSections.tsx b/desktop/src/features/profile/ui/UserProfilePanelSections.tsx
index 05a97e82f5..d003ed94e8 100644
--- a/desktop/src/features/profile/ui/UserProfilePanelSections.tsx
+++ b/desktop/src/features/profile/ui/UserProfilePanelSections.tsx
@@ -345,7 +345,7 @@ function ProfileHeroDescription({ about }: { about: string }) {
type="button"
>
more
-
+
) : null}
{expanded ? (
@@ -356,7 +356,7 @@ function ProfileHeroDescription({ about }: { about: string }) {
type="button"
>
less
-
+
) : null}
diff --git a/desktop/src/features/profile/ui/UserProfilePopover.tsx b/desktop/src/features/profile/ui/UserProfilePopover.tsx
index d6e3d277c2..dcceb0a278 100644
--- a/desktop/src/features/profile/ui/UserProfilePopover.tsx
+++ b/desktop/src/features/profile/ui/UserProfilePopover.tsx
@@ -259,7 +259,7 @@ export function UserProfilePopover({
}}
type="button"
>
-
+
View activity log
) : null}
diff --git a/desktop/src/features/projects/ui/ProjectDetailScreen.tsx b/desktop/src/features/projects/ui/ProjectDetailScreen.tsx
index 5f48472f92..f01f383d6d 100644
--- a/desktop/src/features/projects/ui/ProjectDetailScreen.tsx
+++ b/desktop/src/features/projects/ui/ProjectDetailScreen.tsx
@@ -30,7 +30,7 @@ function CloneUrlRow({ url }: { url: string }) {
return (
-
+
{url}
{copied ? (
-
+
) : (
-
+
)}
@@ -89,7 +89,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
size="sm"
variant="ghost"
>
-
+
Back to Projects
@@ -111,7 +111,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
size="sm"
variant="outline"
>
-
+
Back to Projects
@@ -136,7 +136,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
size="sm"
variant="ghost"
>
-
+
Back to Projects
@@ -144,7 +144,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
-
+
{project.name}
{project.description ? (
@@ -178,7 +178,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
rel="noopener noreferrer"
target="_blank"
>
-
+
{project.webUrl}
@@ -188,7 +188,7 @@ export function ProjectDetailScreen({ projectId }: ProjectDetailScreenProps) {
-
+
Contributors ({project.contributors.length})
diff --git a/desktop/src/features/projects/ui/ProjectsView.tsx b/desktop/src/features/projects/ui/ProjectsView.tsx
index 098eb72389..715fe4c18a 100644
--- a/desktop/src/features/projects/ui/ProjectsView.tsx
+++ b/desktop/src/features/projects/ui/ProjectsView.tsx
@@ -86,19 +86,19 @@ export function ProjectsView() {
{project.cloneUrls.length > 0 ? (
-
+
{project.cloneUrls[0]}
) : null}
{project.contributors.length > 0 ? (
-
+
{project.contributors.length}
) : null}
{project.webUrl ? (
-
+
Web
) : null}
diff --git a/desktop/src/features/pulse/ui/AgentActivityCard.tsx b/desktop/src/features/pulse/ui/AgentActivityCard.tsx
index 7fdc109775..3c2e8a7a3b 100644
--- a/desktop/src/features/pulse/ui/AgentActivityCard.tsx
+++ b/desktop/src/features/pulse/ui/AgentActivityCard.tsx
@@ -66,7 +66,7 @@ export function AgentActivityCard({
type="button"
>
-
+
@@ -87,9 +87,9 @@ export function AgentActivityCard({
type="button"
>
{expanded ? (
-
+
) : (
-
+
)}
{group.notes.length} updates
diff --git a/desktop/src/features/pulse/ui/NoteCard.tsx b/desktop/src/features/pulse/ui/NoteCard.tsx
index a3e71f89fe..7942376674 100644
--- a/desktop/src/features/pulse/ui/NoteCard.tsx
+++ b/desktop/src/features/pulse/ui/NoteCard.tsx
@@ -171,7 +171,7 @@ export function NoteCard({
displayName={displayName}
/>
{isAgent ? (
-
+
) : null}
diff --git a/desktop/src/features/pulse/ui/PulseView.tsx b/desktop/src/features/pulse/ui/PulseView.tsx
index 9ea771b38f..82aefc14de 100644
--- a/desktop/src/features/pulse/ui/PulseView.tsx
+++ b/desktop/src/features/pulse/ui/PulseView.tsx
@@ -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"
>
-
+
diff --git a/desktop/src/features/relay-members/ui/RelayMembersCard.tsx b/desktop/src/features/relay-members/ui/RelayMembersCard.tsx
index 38faefa20f..ac64b108a3 100644
--- a/desktop/src/features/relay-members/ui/RelayMembersCard.tsx
+++ b/desktop/src/features/relay-members/ui/RelayMembersCard.tsx
@@ -121,7 +121,7 @@ function MemberRow({
size="sm"
variant="ghost"
>
-
+
Actions
@@ -231,7 +231,7 @@ export function RelayMembersCard({
onClick={() => setAddDialogOpen(true)}
size="sm"
>
-
+
Add Member
) : null}
diff --git a/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx b/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx
index bc1c60c368..c522e209b1 100644
--- a/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx
+++ b/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx
@@ -165,10 +165,10 @@ function RelayMemberRow({
{member.role === "owner" ? (
-
+
) : null}
{member.role === "admin" ? (
-
+
) : null}
{displayName}
{isSelf ? (
@@ -461,10 +461,13 @@ export function RelayMembersSettingsCard({
setSearch(event.target.value)}
placeholder="Search members by name, npub, or role…"
+ spellCheck={false}
type="text"
value={search}
/>
diff --git a/desktop/src/features/settings/UpdateIndicator.tsx b/desktop/src/features/settings/UpdateIndicator.tsx
index 97d8bb6e35..dd004286ee 100644
--- a/desktop/src/features/settings/UpdateIndicator.tsx
+++ b/desktop/src/features/settings/UpdateIndicator.tsx
@@ -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"
>
-
+
diff --git a/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx b/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx
index 0181e19288..b933c61a2e 100644
--- a/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx
+++ b/desktop/src/features/settings/ui/ChannelTemplatesSettingsCard.tsx
@@ -120,7 +120,7 @@ export function ChannelTemplatesSettingsCard() {
type="button"
variant="outline"
>
-
+
Create
@@ -229,13 +229,13 @@ function TemplateRow({
{agentCount > 0 ? (
-
+
{agentCount} {agentCount === 1 ? "agent" : "agents"}
) : null}
{template.canvasTemplate ? (
-
+
canvas
) : null}
@@ -255,11 +255,11 @@ function TemplateRow({
-
+
Edit
-
+
Duplicate
{!template.isBuiltin ? (
@@ -267,7 +267,7 @@ function TemplateRow({
className="text-destructive focus:text-destructive"
onClick={onDelete}
>
-
+
Delete
) : null}
diff --git a/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx b/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx
index a6d3044cdc..4b7022baff 100644
--- a/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx
+++ b/desktop/src/features/settings/ui/DoctorSettingsPanel.tsx
@@ -56,9 +56,9 @@ function InstallActions({
variant="outline"
>
{isInstalling ? (
-
+
) : (
-
+
)}
{isInstalling ? "Installing..." : "Install"}
@@ -68,7 +68,7 @@ function InstallActions({
onClick={() => void openUrl(runtime.installInstructionsUrl)}
type="button"
>
-
+
View instructions
diff --git a/desktop/src/features/settings/ui/MobilePairingCard.tsx b/desktop/src/features/settings/ui/MobilePairingCard.tsx
index 2e6c77c49a..6ae0850e33 100644
--- a/desktop/src/features/settings/ui/MobilePairingCard.tsx
+++ b/desktop/src/features/settings/ui/MobilePairingCard.tsx
@@ -225,7 +225,7 @@ function PairingDialog({
type="button"
>
{qrUri}
-
+
@@ -332,7 +332,7 @@ export function MobilePairingCard({
-
+
Pair Mobile Device
diff --git a/desktop/src/features/settings/ui/NotificationSettingsCard.tsx b/desktop/src/features/settings/ui/NotificationSettingsCard.tsx
index 55a0c9ab77..05a47e03c9 100644
--- a/desktop/src/features/settings/ui/NotificationSettingsCard.tsx
+++ b/desktop/src/features/settings/ui/NotificationSettingsCard.tsx
@@ -227,7 +227,7 @@ export function NotificationSettingsCard({
>
{showComingSoon ? (
<>
-
+
Show less
>
) : (
diff --git a/desktop/src/features/settings/ui/ProfileSettingsCard.tsx b/desktop/src/features/settings/ui/ProfileSettingsCard.tsx
index cfe3c9bb20..66951685c5 100644
--- a/desktop/src/features/settings/ui/ProfileSettingsCard.tsx
+++ b/desktop/src/features/settings/ui/ProfileSettingsCard.tsx
@@ -57,7 +57,7 @@ function IdentityRow({
title={`Copy ${label}`}
type="button"
>
-
+
Copy
) : null}
@@ -97,7 +97,7 @@ function EditProfileMetadataButton({
title={accessibleLabel}
type="button"
>
-
+
{actionLabel}
);
@@ -577,7 +577,7 @@ export function ProfileSettingsCard({
this device.
-
+
setSearch(e.target.value)}
placeholder="Search themes..."
+ spellCheck={false}
type="text"
value={search}
/>
@@ -269,7 +272,7 @@ function ThemeSettingsCard() {
type="button"
>
{accentColor === color.value && (
-
+
)}
);
diff --git a/desktop/src/features/settings/ui/SoundPicker.tsx b/desktop/src/features/settings/ui/SoundPicker.tsx
index 5de4f450c6..716290f335 100644
--- a/desktop/src/features/settings/ui/SoundPicker.tsx
+++ b/desktop/src/features/settings/ui/SoundPicker.tsx
@@ -135,9 +135,9 @@ export function SoundPicker({
variant="ghost"
>
{isPlaying ? (
-
+
) : (
-
+
)}
diff --git a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx
index 61986d70d6..28bad42934 100644
--- a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx
+++ b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx
@@ -255,7 +255,7 @@ function SectionHeaderActions({
title="Mark all as read"
type="button"
>
-
+
) : null}
{onBrowse ? (
@@ -548,7 +548,7 @@ export function CustomChannelSection({
>
-
+
) : null}
-
+
-
+
diff --git a/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx b/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx
index 5ebca7e47e..22f9119f02 100644
--- a/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx
+++ b/desktop/src/features/sidebar/ui/MoreUnreadButton.tsx
@@ -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",
diff --git a/desktop/src/features/sidebar/ui/SidebarSection.tsx b/desktop/src/features/sidebar/ui/SidebarSection.tsx
index 6d9210622d..7f1998f021 100644
--- a/desktop/src/features/sidebar/ui/SidebarSection.tsx
+++ b/desktop/src/features/sidebar/ui/SidebarSection.tsx
@@ -202,7 +202,7 @@ export function ChannelMenuButton({
{isMuted ? (
{selected ? formatChannelLabel(selected) : "Select a channel..."}
-
+
-
+
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}
/>
@@ -142,7 +144,7 @@ export function ChannelCombobox({
>
diff --git a/desktop/src/features/workflows/ui/WorkflowApprovalCard.tsx b/desktop/src/features/workflows/ui/WorkflowApprovalCard.tsx
index e48f09c463..8c39b01f59 100644
--- a/desktop/src/features/workflows/ui/WorkflowApprovalCard.tsx
+++ b/desktop/src/features/workflows/ui/WorkflowApprovalCard.tsx
@@ -54,7 +54,7 @@ export function WorkflowApprovalCard({ approval }: WorkflowApprovalCardProps) {
}
size="sm"
>
-
+
Approve
-
+
Deny
diff --git a/desktop/src/features/workflows/ui/WorkflowCard.tsx b/desktop/src/features/workflows/ui/WorkflowCard.tsx
index 0a5526a4da..577bdbabdb 100644
--- a/desktop/src/features/workflows/ui/WorkflowCard.tsx
+++ b/desktop/src/features/workflows/ui/WorkflowCard.tsx
@@ -89,7 +89,7 @@ export function WorkflowCard({
{channelName ?
{channelName} : null}
{triggerSummary ?
{triggerSummary} : null}
-
+
{new Date(workflow.updatedAt * 1000).toLocaleDateString()}
diff --git a/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx b/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx
index faf22857f8..f610caee12 100644
--- a/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx
+++ b/desktop/src/features/workflows/ui/WorkflowDetailPanel.tsx
@@ -93,7 +93,7 @@ export function WorkflowDetailPanel({
size="sm"
variant="outline"
>
-
+
Edit
) : null}
@@ -103,7 +103,7 @@ export function WorkflowDetailPanel({
size="sm"
variant="outline"
>
-
+
{triggerMutation.isPending ? "Triggering..." : "Trigger"}
-
+
{mode === "form" ? "Edit as YAML" : "Back to form"}
@@ -335,7 +335,7 @@ export function WorkflowFormBuilder({
type="button"
variant="outline"
>
-
+
Add step
diff --git a/desktop/src/features/workflows/ui/WorkflowStepCard.tsx b/desktop/src/features/workflows/ui/WorkflowStepCard.tsx
index ac7018ff56..ffe6b3546f 100644
--- a/desktop/src/features/workflows/ui/WorkflowStepCard.tsx
+++ b/desktop/src/features/workflows/ui/WorkflowStepCard.tsx
@@ -326,7 +326,7 @@ export function WorkflowStepCard({
type="button"
variant="ghost"
>
-
+
diff --git a/desktop/src/features/workflows/ui/WorkflowWebhookHeadersEditor.tsx b/desktop/src/features/workflows/ui/WorkflowWebhookHeadersEditor.tsx
index 6645ac1a25..14eb33e3db 100644
--- a/desktop/src/features/workflows/ui/WorkflowWebhookHeadersEditor.tsx
+++ b/desktop/src/features/workflows/ui/WorkflowWebhookHeadersEditor.tsx
@@ -48,7 +48,7 @@ export function WorkflowWebhookHeadersEditor({
type="button"
variant="outline"
>
-
+
Add header
@@ -109,7 +109,7 @@ export function WorkflowWebhookHeadersEditor({
type="button"
variant="ghost"
>
-
+
))}
diff --git a/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx b/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx
index e6173ee23d..ca0f8a8724 100644
--- a/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx
+++ b/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx
@@ -25,7 +25,7 @@ export function FormSelect({
>
{children}
-
+
);
}
diff --git a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx
index 2e7e14d045..89b17a93ca 100644
--- a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx
+++ b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx
@@ -121,7 +121,7 @@ export function WorkspaceSwitcher({
data-testid="relay-connection-warning"
role="img"
>
-
+
@@ -154,8 +154,8 @@ export function WorkspaceSwitcher({
)}
@@ -209,7 +209,7 @@ export function WorkspaceSwitcher({
>
{activeWorkspace?.id === workspace.id ? (
-
+
) : null}
@@ -226,7 +226,7 @@ export function WorkspaceSwitcher({
}}
type="button"
>
-
+
))}
@@ -296,7 +296,7 @@ export function WorkspaceSwitcher({
>
{activeWorkspace?.id === workspace.id ? (
-
+
) : null}
{workspace.name}
@@ -311,7 +311,7 @@ export function WorkspaceSwitcher({
}}
type="button"
>
-
+
))}
diff --git a/desktop/src/shared/ui/VideoPlayer.tsx b/desktop/src/shared/ui/VideoPlayer.tsx
index b967b00e95..5dd42bff98 100644
--- a/desktop/src/shared/ui/VideoPlayer.tsx
+++ b/desktop/src/shared/ui/VideoPlayer.tsx
@@ -1627,7 +1627,7 @@ function VideoReviewDialog({
type="button"
onClick={() => setIsEmojiPickerOpen((open) => !open)}
>
-
+
More reactions
@@ -1900,7 +1900,7 @@ function VideoReviewCommentBody({
{reactions.some((reaction) => reaction.reactedByCurrentUser) ? (
) : null}
diff --git a/desktop/src/shared/ui/import-status-icon.tsx b/desktop/src/shared/ui/import-status-icon.tsx
index 9617e44eba..aadb5b9bc6 100644
--- a/desktop/src/shared/ui/import-status-icon.tsx
+++ b/desktop/src/shared/ui/import-status-icon.tsx
@@ -16,7 +16,9 @@ export function ImportStatusIcon({
}) {
switch (status) {
case "importing":
- return ;
+ return (
+
+ );
case "done":
return ;
case "error":
diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx
index b21b45e732..2f4a84663b 100644
--- a/desktop/src/shared/ui/markdown.tsx
+++ b/desktop/src/shared/ui/markdown.tsx
@@ -520,7 +520,7 @@ function MarkdownCodeBlock({
type="button"
variant="ghost"
>
-
+
Copy code block
@@ -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"
>
-
+
diff --git a/web/src/features/repos/ui/RepoBlobViewer.tsx b/web/src/features/repos/ui/RepoBlobViewer.tsx
index 0a53696d3b..5adfdd2f0e 100644
--- a/web/src/features/repos/ui/RepoBlobViewer.tsx
+++ b/web/src/features/repos/ui/RepoBlobViewer.tsx
@@ -258,7 +258,7 @@ export function RepoBlobPage() {
-
+
{filepath}
{view &&
diff --git a/web/src/features/repos/ui/RepoRefsSection.tsx b/web/src/features/repos/ui/RepoRefsSection.tsx
index 11b38acd75..07fdafa823 100644
--- a/web/src/features/repos/ui/RepoRefsSection.tsx
+++ b/web/src/features/repos/ui/RepoRefsSection.tsx
@@ -22,12 +22,12 @@ export function RepoRefsSection({
<>
-
+
{refs.head.ref}
{refs.head.sha && (
-
+
{refs.head.sha.slice(0, 7)}
)}
@@ -36,13 +36,13 @@ export function RepoRefsSection({
>
)}
-
+
{refs.branches.length}{" "}
{refs.branches.length === 1 ? "branch" : "branches"}
·
-
+
{refs.tags.length} {refs.tags.length === 1 ? "tag" : "tags"}
diff --git a/web/src/features/repos/ui/ReposPage.tsx b/web/src/features/repos/ui/ReposPage.tsx
index 72a972a4a9..c738f769aa 100644
--- a/web/src/features/repos/ui/ReposPage.tsx
+++ b/web/src/features/repos/ui/ReposPage.tsx
@@ -104,7 +104,7 @@ export function ReposPage() {
- Repositories
+ Repositories
{["a", "b", "c", "d", "e"].map((key) => (
@@ -131,7 +131,7 @@ export function ReposPage() {
- Repositories
+ Repositories
{/* Search + Sort bar */}
From 9f121b0e2bff3adce5bfe84e585fbd5727622fa7 Mon Sep 17 00:00:00 2001
From: klopez4212
Date: Fri, 12 Jun 2026 15:26:04 +0100
Subject: [PATCH 3/3] Update narrow header menu spacing expectation
---
desktop/tests/e2e/messaging.spec.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/desktop/tests/e2e/messaging.spec.ts b/desktop/tests/e2e/messaging.spec.ts
index 610f174e00..825b53b0b5 100644
--- a/desktop/tests/e2e/messaging.spec.ts
+++ b/desktop/tests/e2e/messaging.spec.ts
@@ -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();