diff --git a/desktop/src/app/AppTopChrome.tsx b/desktop/src/app/AppTopChrome.tsx index bfe27655f..739c03ecc 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/agents/ui/AgentSessionToolItem.tsx b/desktop/src/features/agents/ui/AgentSessionToolItem.tsx index 09411f2e6..78dd98304 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 ef11f463f..2f1f47e1f 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 7d09089cb..5e0ba0f6c 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 fa8183142..a8d0b621f 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 4ff97a5bb..3defb7d2b 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 783b8db54..3e16814f6 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 fb0058424..0dcd58669 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 4393187f5..e2e42a14a 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 3b129cc42..eb2c5c025 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 2e903bfeb..055022a8d 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 06421bc51..2e2eb2856 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 e29d8e486..021548ea5 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 bcbcd7e10..145fc22f4 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 139931e63..403fa84c1 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 c35be5657..ae633bbf3 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 d4ee6ff1e..a289200ad 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/ChannelBrowserDialog.tsx b/desktop/src/features/channels/ui/ChannelBrowserDialog.tsx index 2fc794bb2..32616259f 100644 --- a/desktop/src/features/channels/ui/ChannelBrowserDialog.tsx +++ b/desktop/src/features/channels/ui/ChannelBrowserDialog.tsx @@ -1,52 +1,20 @@ import * as React from "react"; -import { - Compass, - FileText, - Hash, - LogIn, - Search, - Users, - type LucideIcon, -} from "lucide-react"; +import { Compass, Search, X, type LucideIcon } from "lucide-react"; import type { Channel } from "@/shared/api/types"; import { Dialog, + DialogClose, DialogContent, - DialogDescription, DialogHeader, DialogTitle, } from "@/shared/ui/dialog"; import { Badge } from "@/shared/ui/badge"; -import { Input } from "@/shared/ui/input"; import { Button } from "@/shared/ui/button"; +import { Tabs, TabsList, TabsTrigger } from "@/shared/ui/tabs"; const BROWSE_CHANNELS_SHORTCUT_HINT = "\u21E7\u2318O"; - -function formatRelativeTime(isoString: string | null) { - if (!isoString) { - return "No activity"; - } - - const diff = Math.floor((Date.now() - new Date(isoString).getTime()) / 1_000); - - if (diff < 60) { - return "just now"; - } - - if (diff < 60 * 60) { - return `${Math.floor(diff / 60)}m ago`; - } - - if (diff < 60 * 60 * 24) { - return `${Math.floor(diff / (60 * 60))}h ago`; - } - - return new Intl.DateTimeFormat("en-US", { - month: "short", - day: "numeric", - }).format(new Date(isoString)); -} +type BrowserTab = "all" | "joined" | "archived"; function BrowseState({ icon: Icon, @@ -60,7 +28,7 @@ function BrowseState({ return (
- +

{title}

@@ -88,24 +56,34 @@ export function ChannelBrowserDialog({ onSelectChannel, }: ChannelBrowserDialogProps) { const [query, setQuery] = React.useState(""); - const [selectedIndex, setSelectedIndex] = React.useState(0); + const [activeTab, setActiveTab] = React.useState("all"); + const [selectedIndex, setSelectedIndex] = React.useState(null); const [joiningChannelId, setJoiningChannelId] = React.useState( null, ); const inputRef = React.useRef(null); + const tabListRef = React.useRef(null); + const tabTriggerRefs = React.useRef< + Record + >({ + all: null, + joined: null, + archived: null, + }); + const [tabIndicator, setTabIndicator] = React.useState({ + left: 0, + width: 0, + }); const deferredQuery = React.useDeferredValue(query.trim().toLowerCase()); const isForumMode = channelTypeFilter === "forum"; const browseTitle = isForumMode ? "Browse Forums" : "Browse Channels"; - const browseDescription = isForumMode - ? "Discover and join open forums." - : "Discover and join open channels."; const searchPlaceholder = isForumMode ? "Search forums by name or description" : "Search channels by name or description"; const entityLabel = isForumMode ? "forum" : "channel"; - const browsableChannels = React.useMemo(() => { + const matchingChannels = React.useMemo(() => { const filtered = channels.filter( (channel) => channel.channelType !== "dm" && @@ -126,53 +104,114 @@ export function ChannelBrowserDialog({ ); }, [channels, channelTypeFilter, deferredQuery]); - const notJoined = React.useMemo( - () => browsableChannels.filter((channel) => !channel.isMember), - [browsableChannels], + const currentChannels = React.useMemo( + () => matchingChannels.filter((channel) => channel.archivedAt === null), + [matchingChannels], ); - const joined = React.useMemo( - () => browsableChannels.filter((channel) => channel.isMember), - [browsableChannels], + const joinedChannels = React.useMemo( + () => currentChannels.filter((channel) => channel.isMember), + [currentChannels], ); - const hasArchivedJoinedChannels = React.useMemo( - () => joined.some((channel) => channel.archivedAt !== null), - [joined], + + const archivedChannels = React.useMemo( + () => matchingChannels.filter((channel) => channel.archivedAt !== null), + [matchingChannels], ); - // Flat list for keyboard navigation: not-joined first, then joined - const allItems = React.useMemo( - () => [...notJoined, ...joined], - [notJoined, joined], + const visibleChannels = + activeTab === "archived" + ? archivedChannels + : activeTab === "joined" + ? joinedChannels + : matchingChannels; + + const orderedVisibleChannels = React.useMemo( + () => [ + ...visibleChannels.filter((channel) => !channel.isMember), + ...visibleChannels.filter((channel) => channel.isMember), + ], + [visibleChannels], ); - React.useEffect(() => { + const allTabLabel = isForumMode ? "All forums" : "All channels"; + + const updateTabIndicator = React.useCallback(() => { + const list = tabListRef.current; + const trigger = tabTriggerRefs.current[activeTab]; + + if (!open || !list || !trigger) { + return; + } + + const nextIndicator = { + left: trigger.offsetLeft, + width: trigger.offsetWidth, + }; + + setTabIndicator((current) => + Math.abs(current.left - nextIndicator.left) < 0.5 && + Math.abs(current.width - nextIndicator.width) < 0.5 + ? current + : nextIndicator, + ); + }, [activeTab, open]); + + React.useLayoutEffect(() => { + updateTabIndicator(); + if (!open) { - setQuery(""); - setSelectedIndex(0); - setJoiningChannelId(null); return; } - const timeout = window.setTimeout(() => { - inputRef.current?.focus(); - inputRef.current?.select(); - }, 0); + let isCancelled = false; + const updateIfActive = () => { + if (!isCancelled) { + updateTabIndicator(); + } + }; + const frameId = window.requestAnimationFrame(updateIfActive); + const observer = new ResizeObserver(updateTabIndicator); + const list = tabListRef.current; + + void document.fonts.ready.then(updateIfActive); + + if (list) { + observer.observe(list); + } + + for (const trigger of Object.values(tabTriggerRefs.current)) { + if (trigger) { + observer.observe(trigger); + } + } return () => { - window.clearTimeout(timeout); + isCancelled = true; + window.cancelAnimationFrame(frameId); + observer.disconnect(); }; + }, [open, updateTabIndicator]); + + React.useEffect(() => { + if (!open) { + setQuery(""); + setActiveTab("all"); + setSelectedIndex(null); + setJoiningChannelId(null); + return; + } }, [open]); React.useEffect(() => { setSelectedIndex((current) => { - if (allItems.length === 0) { - return 0; + if (current === null || orderedVisibleChannels.length === 0) { + return null; } - return Math.min(current, allItems.length - 1); + return Math.min(current, orderedVisibleChannels.length - 1); }); - }, [allItems]); + }, [orderedVisibleChannels]); async function handleJoin(channelId: string) { setJoiningChannelId(channelId); @@ -191,144 +230,199 @@ export function ChannelBrowserDialog({ onSelectChannel(channel.id); } - const selectedItem = allItems[selectedIndex]; + const selectedItem = + selectedIndex !== null ? orderedVisibleChannels[selectedIndex] : undefined; + const emptyTitle = + deferredQuery.length > 0 + ? `No ${entityLabel}s match your search` + : activeTab === "archived" + ? `No archived ${entityLabel}s` + : activeTab === "joined" + ? `No joined ${entityLabel}s` + : `No ${entityLabel}s to browse`; + const emptyDescription = + deferredQuery.length > 0 + ? "Try a different name or keyword." + : activeTab === "archived" + ? `Archived ${entityLabel}s you have joined will appear here.` + : activeTab === "joined" + ? `${entityLabel[0].toUpperCase()}${entityLabel.slice(1)}s you join will appear here.` + : `All open ${entityLabel}s are available in the sidebar. Create a new ${entityLabel} to get started.`; return (

{ + event.preventDefault(); + inputRef.current?.focus({ preventScroll: true }); + }} + showCloseButton={false} > - - - - - - {browseTitle} - - {browseDescription} -
- - +
+ {browseTitle} + + + Close + +
+
+
-
- {browsableChannels.length === 0 ? ( - deferredQuery.length > 0 ? ( - - ) : ( - - ) - ) : ( -
- {notJoined.length > 0 ? ( - <> -
- - {notJoined.length} {entityLabel} - {notJoined.length !== 1 ? "s" : ""} to join - - Enter to view -
-
- {notJoined.map((channel) => { - const flatIndex = allItems.indexOf(channel); - return ( - { - void handleJoin(channel.id); - }} - onMouseEnter={() => setSelectedIndex(flatIndex)} - onSelect={() => handleSelect(channel)} - /> - ); - })} -
- - ) : null} - - {joined.length > 0 ? ( - <> -
- Joined -
-
- {joined.map((channel) => { - const flatIndex = allItems.indexOf(channel); - return ( - setSelectedIndex(flatIndex)} - onSelect={() => handleSelect(channel)} - /> - ); - })} -
- - ) : null} +
+
+ { + setActiveTab(value as BrowserTab); + setSelectedIndex(null); + }} + value={activeTab} + > + + + + +
+ {orderedVisibleChannels.length === 0 ? ( + 0 ? Search : Compass} + title={emptyTitle} + /> + ) : ( +
+ {orderedVisibleChannels.map((channel, index) => ( + { + void handleJoin(channel.id); + } + : undefined + } + onSelect={() => handleSelect(channel)} + /> + ))} +
+ )}
- )} -
- -
- {hasArchivedJoinedChannels - ? `Showing open ${entityLabel}s and your archived ${entityLabel}s. Private ${entityLabel}s require an invite.` - : `Showing open ${entityLabel}s. Private ${entityLabel}s require an invite.`} +
@@ -340,80 +434,80 @@ function ChannelCard({ isJoining, isSelected, onJoin, - onMouseEnter, onSelect, }: { channel: Channel; isJoining: boolean; isSelected: boolean; onJoin?: () => void; - onMouseEnter: () => void; onSelect: () => void; }) { + const memberLabel = `${channel.memberCount} ${ + channel.memberCount === 1 ? "member" : "members" + }`; + return ( - - ) : null} -
- + + + {!channel.isMember && onJoin ? ( + + ) : null} +
); } diff --git a/desktop/src/features/channels/ui/ChannelManagementSheet.tsx b/desktop/src/features/channels/ui/ChannelManagementSheet.tsx index cda00f0cf..e708dfb9b 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 7f093294d..4a06ff83e 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 a873069f7..a8dbeda72 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 b4eec62c0..3d4d24385 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 ba027edaa..90b6c4e7d 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 e482e0142..848dd7964 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 92633a0d1..2ce878aee 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 2104811b1..8b504744f 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 f3f2af662..ac3e59b1b 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 d786af379..2bacbc838 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 de588bed7..17a8f343c 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 fb32276b7..d28895185 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 5730144f5..7cd5d648d 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 3af762234..edb08e131 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 3a9fdb0b4..c0ac1235d 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/lib/useRichTextEditor.ts b/desktop/src/features/messages/lib/useRichTextEditor.ts index e293aa9ab..0a4885cd5 100644 --- a/desktop/src/features/messages/lib/useRichTextEditor.ts +++ b/desktop/src/features/messages/lib/useRichTextEditor.ts @@ -321,9 +321,12 @@ export function useRichTextEditor({ ], editorProps: { attributes: { + autocapitalize: "none", + autocorrect: "off", class: "min-h-0 resize-none overflow-y-hidden border-0 bg-transparent px-0 py-0 text-sm leading-6 md:leading-6 shadow-none focus-visible:ring-0 caret-foreground outline-hidden prose-sm max-w-none", "data-testid": "message-input", + spellcheck: "false", }, // ArrowUp in an empty composer → edit your last message (Slack // parity). Handled here in ProseMirror's own DOM `keydown` hook — diff --git a/desktop/src/features/messages/ui/ComposerAttachments.tsx b/desktop/src/features/messages/ui/ComposerAttachments.tsx index 2444895a2..8030c173c 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 ca5336dfc..9189e6309 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 fa7ec24ba..15ce1703e 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
diff --git a/desktop/src/features/messages/ui/FormattingToolbar.tsx b/desktop/src/features/messages/ui/FormattingToolbar.tsx index fab4a1bfd..368ef5ea4 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 c9a5528cd..94a9fcb2d 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({ >
diff --git a/desktop/src/features/onboarding/ui/AvatarStep.tsx b/desktop/src/features/onboarding/ui/AvatarStep.tsx index a99c69013..d339c1d62 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 4f0618f94..a3bfc5c87 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 b251fb9e3..a31e45592 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" > - +

This will use this Nostr identity: diff --git a/desktop/src/features/onboarding/ui/ProfileStep.tsx b/desktop/src/features/onboarding/ui/ProfileStep.tsx index 9236f9e8b..024bd1e3d 100644 --- a/desktop/src/features/onboarding/ui/ProfileStep.tsx +++ b/desktop/src/features/onboarding/ui/ProfileStep.tsx @@ -92,6 +92,7 @@ export function ProfileStep({ ) : null} {isSaving ? ( - + ) : ( "Next" )} diff --git a/desktop/src/features/onboarding/ui/SetupStep.tsx b/desktop/src/features/onboarding/ui/SetupStep.tsx index 2bc343958..2a3588bbc 100644 --- a/desktop/src/features/onboarding/ui/SetupStep.tsx +++ b/desktop/src/features/onboarding/ui/SetupStep.tsx @@ -106,7 +106,7 @@ function RuntimeStatus({ className="flex h-8 shrink-0 items-center justify-center" role="status" > - +

); } diff --git a/desktop/src/features/profile/ui/AvatarUpload.tsx b/desktop/src/features/profile/ui/AvatarUpload.tsx index 81a5c83cf..dd371a398 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 5ae567faf..d2de41973 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 05a97e82f..d003ed94e 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 d6e3d277c..dcceb0a27 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 5f48472f9..f01f383d6 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}
@@ -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 098eb7238..715fe4c18 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 7fdc10977..3c2e8a7a3 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 a3e71f89f..794237667 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 9ea771b38..82aefc14d 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 38faefa20..ac64b108a 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 bc1c60c36..c522e209b 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/search/ui/ChannelFindBar.tsx b/desktop/src/features/search/ui/ChannelFindBar.tsx index 0e306a13e..3babd14cb 100644 --- a/desktop/src/features/search/ui/ChannelFindBar.tsx +++ b/desktop/src/features/search/ui/ChannelFindBar.tsx @@ -62,6 +62,8 @@ export function ChannelFindBar({
onQueryChange(event.target.value)} onKeyDown={handleKeyDown} placeholder="Find in channel" + spellCheck={false} type="text" value={query} /> diff --git a/desktop/src/features/settings/UpdateIndicator.tsx b/desktop/src/features/settings/UpdateIndicator.tsx index 97d8bb6e3..dd004286e 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 0181e1928..b933c61a2 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 a6d3044cd..4b7022baf 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 2e6c77c49..6ae0850e3 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 55a0c9ab7..05a47e03c 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 cfe3c9bb2..66951685c 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 5de4f450c..716290f33 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/CreateChannelDialog.tsx b/desktop/src/features/sidebar/ui/CreateChannelDialog.tsx index 9dcf5f411..95fe3a5bc 100644 --- a/desktop/src/features/sidebar/ui/CreateChannelDialog.tsx +++ b/desktop/src/features/sidebar/ui/CreateChannelDialog.tsx @@ -1,17 +1,22 @@ -import { Lock, Zap } from "lucide-react"; +import { ClockFading, Hash } from "lucide-react"; import * as React from "react"; import { useChannelTemplatesQuery } from "@/features/channel-templates/hooks"; import type { ChannelTemplate, ChannelVisibility } from "@/shared/api/types"; +import { cn } from "@/shared/lib/cn"; import { Button } from "@/shared/ui/button"; import { ChooserDialogContent } from "@/shared/ui/chooser-dialog-content"; import { Dialog } from "@/shared/ui/dialog"; import { Input } from "@/shared/ui/input"; -import { Switch } from "@/shared/ui/switch"; +import { Tabs, TabsList, TabsTrigger } from "@/shared/ui/tabs"; import { Textarea } from "@/shared/ui/textarea"; /** Default TTL for ephemeral channels: 1 day of inactivity. */ const EPHEMERAL_TTL_SECONDS = 86400; +const CREATE_FIELD_SHELL_CLASS = + "rounded-xl border border-input bg-background shadow-xs transition-colors duration-150 ease-out hover:border-muted-foreground/40 hover:bg-muted/70 focus-within:border-muted-foreground/50 focus-within:bg-muted/70"; +const CREATE_FIELD_CONTROL_CLASS = + "border-0 bg-transparent shadow-none outline-none ring-0 placeholder:text-muted-foreground/45 focus:bg-transparent focus:outline-hidden focus-visible:ring-0"; type ChannelKind = "stream" | "forum"; @@ -136,7 +141,10 @@ export function CreateChannelDialog({ >
{ void handleSubmit(event); @@ -179,24 +187,34 @@ export function CreateChannelDialog({ > Name - { - setName(event.target.value); - setErrorMessage(null); - }} - placeholder={ - channelKind === "forum" ? "design-discussions" : "release-notes" - } - ref={nameInputRef} - spellCheck={false} - value={name} - /> +
+ { + setName(event.target.value); + setErrorMessage(null); + }} + placeholder={ + channelKind === "forum" + ? "design-discussions" + : "release-notes" + } + ref={nameInputRef} + spellCheck={false} + value={name} + /> +
{/* Description */} @@ -210,91 +228,137 @@ export function CreateChannelDialog({ (optional) -