From 602889e340b1bceafd6f3f4e6f32966badabdc5d Mon Sep 17 00:00:00 2001 From: Gagan Date: Thu, 9 Jul 2026 18:11:51 +0530 Subject: [PATCH 1/7] style(x): unify tab headers and clean up Brain/Home UI - Shared header treatment across Apps, Background tasks, Workspace, Meetings, Brain: 24px/650 heading, no leading icons, no divider line, centered 1120px column with fixed 34px/30px padding - Apps-style page background on BG tasks, Workspace, Meetings - Brain files view: monochrome then icon-free rows, inline note-count meta line instead of pill chips, semibold item names, uniform row heights, breadcrumb-only navigation, ghost quick actions - Bases toolbar flush with table edge - Meetings: full-width Coming up section - Home: drop Connections/Ask-anything icon tiles, real Slack logo - Sidebar: Apps above Background agents --- .../src/components/apps/apps-view.tsx | 4 +- .../renderer/src/components/bases-view.tsx | 2 +- .../renderer/src/components/bg-tasks-view.tsx | 15 +- .../renderer/src/components/home-view.tsx | 6 +- .../src/components/knowledge-view.tsx | 131 ++++++------------ .../renderer/src/components/meetings-view.tsx | 19 ++- .../src/components/sidebar-content.tsx | 20 +-- .../src/components/tool-connections-card.tsx | 5 +- .../src/components/workspace-view.tsx | 16 +-- 9 files changed, 86 insertions(+), 132 deletions(-) diff --git a/apps/x/apps/renderer/src/components/apps/apps-view.tsx b/apps/x/apps/renderer/src/components/apps/apps-view.tsx index 9fdb89de..04a01bce 100644 --- a/apps/x/apps/renderer/src/components/apps/apps-view.tsx +++ b/apps/x/apps/renderer/src/components/apps/apps-view.tsx @@ -58,8 +58,8 @@ const CARD_CSS = ` --ma-pat-opacity:0.05; --ma-glow-opacity:0.10; --ma-glow-hover-opacity:0.16; --ma-badge-mix:15%; --ma-pill-mix:13%; --ma-tint:20%; --ma-tint-hover:26%; } -.ma-inner { max-width:1120px; margin:0 auto; padding:clamp(20px,3.5cqw,34px) clamp(16px,3cqw,30px) 48px; } -.ma-h1 { font-size:clamp(19px,2.6cqw,24px); font-weight:650; letter-spacing:-0.02em; color:var(--ma-h1); margin:0 0 4px; } +.ma-inner { max-width:1120px; margin:0 auto; padding:34px 30px 48px; } +.ma-h1 { font-size:24px; font-weight:650; letter-spacing:-0.02em; color:var(--ma-h1); margin:0 0 4px; } .ma-sub { font-size:clamp(13px,1.5cqw,14px); color:var(--ma-sub); margin:0 0 clamp(14px,2cqw,20px); } .ma-tabs { display:flex; gap:6px; margin-bottom:clamp(14px,2cqw,22px); } .ma-tab { border:1px solid var(--ma-border); background:transparent; color:var(--ma-sub); border-radius:999px; padding:5px 14px; font-size:13px; font-weight:600; cursor:pointer; } diff --git a/apps/x/apps/renderer/src/components/bases-view.tsx b/apps/x/apps/renderer/src/components/bases-view.tsx index b1f5413c..f41a7a82 100644 --- a/apps/x/apps/renderer/src/components/bases-view.tsx +++ b/apps/x/apps/renderer/src/components/bases-view.tsx @@ -466,7 +466,7 @@ export function BasesView({ tree, onSelectNote, config, onConfigChange, isDefaul return (
{/* Toolbar */} -
+
-

+

Persistent agents that fire on a schedule or in response to events. Toggle a task inactive to pause it.

-
+
+
{loading ? (
@@ -1885,6 +1883,7 @@ export function BgTasksView({ onCreateWithCopilot, onEditWithCopilot, initialSlu
)} +
- + Slack Latest messages @@ -548,9 +549,6 @@ export function HomeView({ onClick={onOpenChat} className="flex items-center gap-3.5 rounded-xl border border-border bg-card p-4 text-left transition-colors hover:bg-accent" > -
- -
Ask anything — create presentations, do research, collaborate on docs. diff --git a/apps/x/apps/renderer/src/components/knowledge-view.tsx b/apps/x/apps/renderer/src/components/knowledge-view.tsx index 1d5b1397..254dc0e6 100644 --- a/apps/x/apps/renderer/src/components/knowledge-view.tsx +++ b/apps/x/apps/renderer/src/components/knowledge-view.tsx @@ -1,6 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react' import { - ArrowLeft, ChevronRight, Copy, ExternalLink, @@ -70,20 +69,6 @@ const HIDDEN_PATHS = new Set(['knowledge/Meetings', 'knowledge/Workspace']) // Theme-aware accent palette for folder avatars — colored letter on a faint // tint of the same hue. Mirrors the design's six-colour rotation. -const AVATAR_PALETTE = [ - 'bg-indigo-500/10 text-indigo-600 dark:text-indigo-400', - 'bg-violet-500/10 text-violet-600 dark:text-violet-400', - 'bg-amber-500/10 text-amber-600 dark:text-amber-400', - 'bg-rose-500/10 text-rose-600 dark:text-rose-400', - 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', - 'bg-sky-500/10 text-sky-600 dark:text-sky-400', -] as const - -function avatarClass(name: string): string { - let hash = 0 - for (let i = 0; i < name.length; i++) hash = (hash * 31 + name.charCodeAt(i)) >>> 0 - return AVATAR_PALETTE[hash % AVATAR_PALETTE.length] -} function isMarkdown(node: TreeNode): boolean { return node.kind === 'file' && node.name.toLowerCase().endsWith('.md') @@ -204,10 +189,10 @@ export function KnowledgeView({ return (
-
+
-

Brain

-

+

Brain

+

{totalNotes} {totalNotes === 1 ? 'note' : 'notes'} across {folders.length}{' '} {folders.length === 1 ? 'folder' : 'folders'}

@@ -242,12 +227,12 @@ export function KnowledgeView({ {graphContent}
) : mode === 'basis' ? ( -
+
{basisContent}
) : (
-
+
{currentFolder ? ( ) : ( <> - + {folders.length === 0 ? ( ) : ( @@ -398,9 +383,9 @@ function QuickAction({ ) @@ -425,20 +410,6 @@ function EmptyState({ text }: { text: string }) { ) } -function FolderAvatar({ name, className }: { name: string; className?: string }) { - return ( -
- {name.charAt(0).toUpperCase() || '?'} -
- ) -} - function FolderCard({ node, actions, @@ -472,9 +443,8 @@ function FolderCard({ onOpenFolder(node.path) } }} - className="group flex w-full cursor-pointer items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50" + className="group flex w-full cursor-pointer items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50" > -
{renameActive ? ( )} -
- {count} {count === 1 ? 'note' : 'notes'} +
+ + {count} {count === 1 ? 'note' : 'notes'} + + {peek.length > 0 && ( + + {peek.map((n) => ( + + {' · '} + + + ))} + + )}
- {peek.length > 0 && ( -
- {peek.map((n) => ( - - ))} -
- )}
-
+
{modified} @@ -565,17 +539,6 @@ function FolderDetail({ return ( <>
-
-

+

Upcoming events and meeting notes.

+
-
+
{loading ? (
@@ -1329,6 +1327,7 @@ export function MeetingsView({ onOpenNote, onTakeMeetingNotes, meetingState, mee
)}
+
) diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index b6b42c9e..f192df1c 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -890,6 +890,16 @@ export function SidebarContentPanel({
+ + + + Apps + + - - - - Apps - -
-
- -
Bring context from and take action in the apps you already use. diff --git a/apps/x/apps/renderer/src/components/workspace-view.tsx b/apps/x/apps/renderer/src/components/workspace-view.tsx index d5738e9b..9d475e13 100644 --- a/apps/x/apps/renderer/src/components/workspace-view.tsx +++ b/apps/x/apps/renderer/src/components/workspace-view.tsx @@ -8,7 +8,6 @@ import { Folder as FolderIcon, FolderOpen, FolderPlus, - Home, Loader2, MessageSquare, Pencil, @@ -365,19 +364,18 @@ export function WorkspaceView({ tree, initialPath, actions, onNavigate, onOpenNo }, [newName, onCreateWorkspace, resetAddDialog]) return ( -
-
+
+
{breadcrumbs.map((crumb, idx) => { const isLast = idx === breadcrumbs.length - 1 @@ -482,12 +480,13 @@ export function WorkspaceView({ tree, initialPath, actions, onNavigate, onOpenNo
+
{items.length === 0 ? (
@@ -594,6 +593,7 @@ export function WorkspaceView({ tree, initialPath, actions, onNavigate, onOpenNo })}
)} +
{dropEnabled && isDraggingOver && (
From 6def1bf7d3e131cadcb68561175a1c3563ef5750 Mon Sep 17 00:00:00 2001 From: Gagan Date: Thu, 9 Jul 2026 18:14:21 +0530 Subject: [PATCH 2/7] style(x): icon-free up-next hero on home tab --- apps/x/apps/renderer/src/components/home-view.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/x/apps/renderer/src/components/home-view.tsx b/apps/x/apps/renderer/src/components/home-view.tsx index d5e1e3a4..cc834004 100644 --- a/apps/x/apps/renderer/src/components/home-view.tsx +++ b/apps/x/apps/renderer/src/components/home-view.tsx @@ -332,15 +332,12 @@ export function HomeView({ {/* Up-next hero */} {nextEvent && ( -
-
- -
+
-
+
Up next · {nextEvent.isAllDay ? 'today' : relativeFromNow(nextEvent.start)}
-
{nextEvent.summary}
+
{nextEvent.summary}
{nextEvent.isAllDay ? 'All day' : `${timeOfDay(nextEvent.start)}${nextEvent.end ? ` – ${timeOfDay(nextEvent.end)}` : ''}`} {nextEvent.location ? ` · ${nextEvent.location}` : ''} From 5dd8c85ce4d2935708b1ebe074f73310d64ca43f Mon Sep 17 00:00:00 2001 From: Gagan Date: Thu, 9 Jul 2026 18:42:47 +0530 Subject: [PATCH 3/7] style(x): chat empty state, message bubbles, sidebar hover gap - Chat empty state: icon-free heading, single suggestion list with leading icons and hover arrows, aligned to the composer column - User message bubbles: rounded-2xl with flattened top-right corner, hover-only copy button under the bubble's bottom-right - Sidebar: hairline gap between menu items so adjacent active/hover highlights don't merge --- apps/x/apps/renderer/src/App.tsx | 63 ++++++++++--------- .../src/components/ai-elements/message.tsx | 35 ++++++++++- .../src/components/chat-empty-state.tsx | 56 +++++++---------- .../renderer/src/components/ui/sidebar.tsx | 2 +- 4 files changed, 94 insertions(+), 62 deletions(-) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 2952b7d0..f401fb0f 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -51,6 +51,7 @@ import { import { Message, MessageContent, + MessageCopyButton, MessageResponse, } from '@/components/ai-elements/message'; import { @@ -5888,14 +5889,17 @@ function App() { {item.content && ( - - - {item.content} - - +
+ + + {item.content} + + + +
)} ) @@ -5903,26 +5907,29 @@ function App() { const { message, files } = parseAttachedFiles(item.content) return ( - - {files.length > 0 && ( -
- {files.map((filePath, index) => ( - - @{wikiLabel(filePath)} - - ))} -
- )} - - {message} - -
+
+ + {files.length > 0 && ( +
+ {files.map((filePath, index) => ( + + @{wikiLabel(filePath)} + + ))} +
+ )} + + {message} + +
+ +
) } diff --git a/apps/x/apps/renderer/src/components/ai-elements/message.tsx b/apps/x/apps/renderer/src/components/ai-elements/message.tsx index ec3acfc1..fcb5250c 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/message.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/message.tsx @@ -14,8 +14,10 @@ import { import { cn } from "@/lib/utils"; import type { FileUIPart, UIMessage } from "ai"; import { + CheckIcon, ChevronLeftIcon, ChevronRightIcon, + CopyIcon, PaperclipIcon, XIcon, } from "lucide-react"; @@ -38,6 +40,37 @@ export const Message = ({ className, from, ...props }: MessageProps) => ( /> ); +/** + * Minimal copy-to-clipboard affordance for a message bubble. Invisible until + * the surrounding Message (`.group`) is hovered. + */ +export const MessageCopyButton = ({ + text, + className, +}: { + text: string; + className?: string; +}) => { + const [copied, setCopied] = useState(false); + return ( + + ); +}; + export type MessageContentProps = HTMLAttributes; export const MessageContent = ({ @@ -49,7 +82,7 @@ export const MessageContent = ({ data-slot="message-content" className={cn( "is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm", - "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground", + "group-[.is-user]:ml-auto group-[.is-user]:rounded-2xl group-[.is-user]:rounded-tr-md group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-2.5 group-[.is-user]:text-foreground", "group-[.is-assistant]:w-full group-[.is-assistant]:text-foreground", className )} diff --git a/apps/x/apps/renderer/src/components/chat-empty-state.tsx b/apps/x/apps/renderer/src/components/chat-empty-state.tsx index fca21600..b38e41a5 100644 --- a/apps/x/apps/renderer/src/components/chat-empty-state.tsx +++ b/apps/x/apps/renderer/src/components/chat-empty-state.tsx @@ -1,4 +1,4 @@ -import { Bot, Mail, Sparkles, Telescope } from 'lucide-react' +import { ArrowRight, BookOpen, Mail, Zap } from 'lucide-react' import { cn } from '@/lib/utils' import { ToolConnectionsCard } from '@/components/tool-connections-card' @@ -12,8 +12,8 @@ interface ChatEmptyStateProps { const SUGGESTED_ACTIONS: { icon: typeof Mail; title: string; sub: string; prompt: string }[] = [ { icon: Mail, title: 'Draft a reply', sub: 'to an email', prompt: "Let's draft a reply to [name]'s email" }, - { icon: Bot, title: 'Set up a background agent', sub: 'that automates tasks', prompt: 'Set up a background agent that automates [task]' }, - { icon: Telescope, title: 'Research a topic', sub: 'create a local wiki for me', prompt: 'Research [topic] and create a local wiki for me' }, + { icon: Zap, title: 'Set up a background agent', sub: 'that automates tasks', prompt: 'Set up a background agent that automates [task]' }, + { icon: BookOpen, title: 'Research a topic', sub: 'create a local wiki for me', prompt: 'Research [topic] and create a local wiki for me' }, ] /** @@ -25,37 +25,29 @@ export function ChatEmptyState({ wide = false, }: ChatEmptyStateProps) { return ( -
-
-
- -
-
-
What are we working on?
-
Ask anything, or start with a suggestion.
-
+
+
+
What are we working on?
+
Ask anything, or start with a suggestion.
-
-
- Get started -
-
- {SUGGESTED_ACTIONS.map((action) => ( - - ))} -
+
+ {SUGGESTED_ACTIONS.map((action, i) => ( + + ))}
diff --git a/apps/x/apps/renderer/src/components/ui/sidebar.tsx b/apps/x/apps/renderer/src/components/ui/sidebar.tsx index ffe40957..f3550f72 100644 --- a/apps/x/apps/renderer/src/components/ui/sidebar.tsx +++ b/apps/x/apps/renderer/src/components/ui/sidebar.tsx @@ -462,7 +462,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
    ) From 834b8b0390060bae0eb6c306fb7e0723cceb0b2d Mon Sep 17 00:00:00 2001 From: Gagan Date: Thu, 9 Jul 2026 23:52:38 +0530 Subject: [PATCH 4/7] style(x): sidebar alignment and quick-action hierarchy - Top-align icons with titles on two-line nav rows; consistent ink color for Apps/Background agents/Workspaces (sublabels stay muted) - Quick actions: labeled New chat button, secondary actions as compact ghost icons --- .../src/components/sidebar-content.tsx | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index f192df1c..84a53410 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -737,9 +737,16 @@ export function SidebarContentPanel({ {/* Top spacer to clear the traffic lights + fixed toggle row */}
    {/* Quick actions */} -
    +
    {onNewChat && ( - + )} knowledgeActions.createNote()} /> @@ -764,9 +771,9 @@ export function SidebarContentPanel({ data-tour-id="nav-email" isActive={activeNav === 'email'} onClick={() => onOpenEmail?.()} - className={previewEmail ? 'h-auto py-1.5' : undefined} + className={previewEmail ? 'h-auto items-start py-1.5' : undefined} > - +
    Email {previewEmail && ( @@ -787,9 +794,9 @@ export function SidebarContentPanel({ data-tour-id="nav-meetings" isActive={activeNav === 'meetings'} onClick={onOpenMeetings} - className={meetingSublabel ? 'h-auto py-1.5' : undefined} + className={meetingSublabel ? 'h-auto items-start py-1.5' : undefined} > - +
    Meetings {meetingSublabel && ( @@ -874,9 +881,9 @@ export function SidebarContentPanel({ data-tour-id="nav-knowledge" isActive={activeNav === 'knowledge'} onClick={() => knowledgeActions.openKnowledgeView()} - className={knowledgeUpdatedLabel ? 'h-auto py-1.5' : undefined} + className={knowledgeUpdatedLabel ? 'h-auto items-start py-1.5' : undefined} > - +
    Brain {knowledgeUpdatedLabel && ( @@ -896,8 +903,8 @@ export function SidebarContentPanel({ isActive={activeNav === 'apps'} onClick={onOpenApps} > - - Apps + + Apps @@ -905,11 +912,11 @@ export function SidebarContentPanel({ data-tour-id="nav-agents" isActive={activeNav === 'agents'} onClick={onOpenBgTasks} - className={bgAgentsLabel ? 'h-auto py-1.5' : undefined} + className={bgAgentsLabel ? 'h-auto items-start py-1.5' : undefined} > - +
    - Background agents + Background agents {bgAgentsLabel && ( knowledgeActions.openWorkspaceAt()} - className="h-auto py-1.5" + className="h-auto items-start py-1.5" > - +
    - Workspaces + Workspaces {workspaceCount === 0 ? 'No workspaces' : `${workspaceCount} workspace${workspaceCount === 1 ? '' : 's'}`} @@ -1398,7 +1405,7 @@ path: ${currentRelativePath} if (!hasDeepgramKey) return null - const actionClass = "flex h-9 flex-1 items-center justify-center rounded-md border border-sidebar-border text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors" + const actionClass = "flex size-8 shrink-0 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors" const iconClass = "text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors" return ( @@ -1432,7 +1439,7 @@ function ActionButton({ icon: Icon, label, onClick }: { icon: typeof Mic; label: type="button" onClick={onClick} aria-label={label} - className="flex h-9 flex-1 items-center justify-center rounded-md border border-sidebar-border text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors" + className="flex size-8 shrink-0 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors" > From 565a5032eceb88228297bd3b5ac120a5ceabaae4 Mon Sep 17 00:00:00 2001 From: Gagan Date: Fri, 10 Jul 2026 00:11:40 +0530 Subject: [PATCH 5/7] style(x): side-pane chat sizing, bg-tasks stop chip, Brain contrast - Chat empty state: smaller type scale in the side-pane copilot, compact prop on connections card - Background tasks: stop button sized to match the Updating pill, wraps under it in narrow panes, aligned with the state toggle - Sidebar: Code above Meetings - Brain: page + graph background matches other tabs, darker light-mode borders for Base rows and Files containers, bolder section labels --- apps/x/apps/renderer/src/App.css | 6 +++- .../renderer/src/components/bases-view.tsx | 2 +- .../renderer/src/components/bg-tasks-view.tsx | 5 ++-- .../src/components/chat-empty-state.tsx | 28 +++++++++++++------ .../src/components/knowledge-view.tsx | 10 +++---- .../src/components/sidebar-content.tsx | 16 +++++------ .../src/components/tool-connections-card.tsx | 9 ++++-- 7 files changed, 47 insertions(+), 29 deletions(-) diff --git a/apps/x/apps/renderer/src/App.css b/apps/x/apps/renderer/src/App.css index b83e7dd7..252a145f 100644 --- a/apps/x/apps/renderer/src/App.css +++ b/apps/x/apps/renderer/src/App.css @@ -1240,10 +1240,14 @@ } .graph-view { - background-color: var(--background); + background-color: #f8f8f9; user-select: none; } +.dark .graph-view { + background-color: #0b0b0d; +} + .graph-view::before { content: ''; position: absolute; diff --git a/apps/x/apps/renderer/src/components/bases-view.tsx b/apps/x/apps/renderer/src/components/bases-view.tsx index f41a7a82..3fe191e6 100644 --- a/apps/x/apps/renderer/src/components/bases-view.tsx +++ b/apps/x/apps/renderer/src/components/bases-view.tsx @@ -884,7 +884,7 @@ function NoteRow({ const row = ( onSelectNote(note.path)} > {visibleColumns.map((col) => ( diff --git a/apps/x/apps/renderer/src/components/bg-tasks-view.tsx b/apps/x/apps/renderer/src/components/bg-tasks-view.tsx index 1020dd0c..7b591731 100644 --- a/apps/x/apps/renderer/src/components/bg-tasks-view.tsx +++ b/apps/x/apps/renderer/src/components/bg-tasks-view.tsx @@ -1843,16 +1843,17 @@ export function BgTasksView({ onCreateWithCopilot, onEditWithCopilot, initialSlu {isRunning ? ( -
    +
    - Updating… + Updating ))}
    - +
    ) } diff --git a/apps/x/apps/renderer/src/components/knowledge-view.tsx b/apps/x/apps/renderer/src/components/knowledge-view.tsx index 254dc0e6..862bf416 100644 --- a/apps/x/apps/renderer/src/components/knowledge-view.tsx +++ b/apps/x/apps/renderer/src/components/knowledge-view.tsx @@ -188,7 +188,7 @@ export function KnowledgeView({ const currentFolder = folderPath ? findNode(tree, folderPath) : null return ( -
    +

    Brain

    @@ -250,7 +250,7 @@ export function KnowledgeView({ {folders.length === 0 ? ( ) : ( -
    +
    {folders.map((node, i) => (
    0 && 'border-t border-border/60')}> 0 && (
    -
    +
    {looseNotes.map((node, i) => (
    0 && 'border-t border-border/60')}> - + {label} {aside && {aside}} @@ -568,7 +568,7 @@ function FolderDetail({ {items.length === 0 ? ( ) : ( -
    +
    {items.map((node, i) => (
    0 && 'border-t border-border/60')}> + {codeModeEnabled && ( + + + + Code + + + )} ) : null} - {codeModeEnabled && ( - - - - Code - - - )} (cachedToolkitPreviews ?? []) const [toolkitLogosLoaded, setToolkitLogosLoaded] = useState(cachedToolkitLogosLoaded) const [connectionsSettingsOpen, setConnectionsSettingsOpen] = useState(false) @@ -102,7 +102,7 @@ export function ToolConnectionsCard({ className }: { className?: string }) {
    -
    +
    Bring context from and take action in the apps you already use.
    @@ -116,7 +116,10 @@ export function ToolConnectionsCard({ className }: { className?: string }) { - ))} +
    {/* Main content */}
    {/* Header */} -
    -

    {activeTabConfig.label}

    -

    +

    +

    {activeTabConfig.label}

    +

    {activeTab === "models" && rowboatConnected ? "Select your default models" : activeTabConfig.description} @@ -2358,7 +2378,7 @@ export function SettingsDialog({ children, defaultTab = "account", open: control

    {/* Content */} -
    +
    {activeTab === "account" ? ( ) : activeTab === "connections" ? ( diff --git a/apps/x/apps/renderer/src/components/settings/connected-accounts-settings.tsx b/apps/x/apps/renderer/src/components/settings/connected-accounts-settings.tsx index 4b9eb802..08db114d 100644 --- a/apps/x/apps/renderer/src/components/settings/connected-accounts-settings.tsx +++ b/apps/x/apps/renderer/src/components/settings/connected-accounts-settings.tsx @@ -1,9 +1,9 @@ "use client" import * as React from "react" -import { Loader2, Mic, Mail, Calendar, MessageSquare } from "lucide-react" +import { Loader2, Calendar } from "lucide-react" +import { FirefliesIcon, GoogleIcon, SlackIcon } from "@/components/onboarding/provider-icons" import { Button } from "@/components/ui/button" -import { Separator } from "@/components/ui/separator" import { Switch } from "@/components/ui/switch" import { Textarea } from "@/components/ui/textarea" import { GoogleClientIdModal } from "@/components/google-client-id-modal" @@ -47,9 +47,7 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti className="flex items-center justify-between gap-2 rounded-md px-3 py-2 hover:bg-accent/50 transition-colors" >
    -
    - {icon} -
    + {icon}
    {displayName} {state.isLoading ? ( @@ -145,9 +143,7 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti {c.useComposioForGoogle ? (
    -
    - -
    +
    Gmail {c.gmailLoading ? ( @@ -189,14 +185,12 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti
    ) : ( - c.providers.includes('google') && renderOAuthProvider('google', 'Google', , 'Sync emails and calendar') + c.providers.includes('google') && renderOAuthProvider('google', 'Google', , 'Sync emails and calendar') )} {c.useComposioForGoogleCalendar && (
    -
    - -
    +
    Google Calendar {c.googleCalendarLoading ? ( @@ -238,7 +232,6 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti
    )} - )} @@ -252,14 +245,13 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti
    {/* Fireflies */} - {renderOAuthProvider('fireflies-ai', 'Fireflies', , 'AI meeting transcripts')} + {renderOAuthProvider('fireflies-ai', 'Fireflies', , 'AI meeting transcripts')} )} {/* Team Communication Section */} <> - -
    +
    Team Communication @@ -267,9 +259,7 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti
    -
    - -
    +
    Slack {c.slackLoading ? ( @@ -408,8 +398,7 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti {/* Knowledge Sources Section */} {c.slackEnabled && ( <> - -
    +
    Knowledge Sources @@ -417,9 +406,7 @@ export function ConnectedAccountsSettings({ dialogOpen }: ConnectedAccountsSetti
    -
    - -
    +
    Slack to knowledge diff --git a/apps/x/apps/renderer/src/components/settings/mobile-channels-settings.tsx b/apps/x/apps/renderer/src/components/settings/mobile-channels-settings.tsx index 9974ee94..0e34c08e 100644 --- a/apps/x/apps/renderer/src/components/settings/mobile-channels-settings.tsx +++ b/apps/x/apps/renderer/src/components/settings/mobile-channels-settings.tsx @@ -2,10 +2,10 @@ import { useCallback, useEffect, useState } from "react" import type { z } from "zod" -import { Coffee, Loader2, MessageCircle, Send, Smartphone } from "lucide-react" +import { Coffee, Loader2, Smartphone } from "lucide-react" +import { TelegramIcon, WhatsAppIcon } from "@/components/onboarding/provider-icons" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Separator } from "@/components/ui/separator" import { Switch } from "@/components/ui/switch" import { toast } from "sonner" import type { ChannelsConfig, ChannelsStatus } from "@x/shared/src/channels.js" @@ -104,9 +104,7 @@ export function MobileChannelsSettings({ dialogOpen }: { dialogOpen: boolean }) {/* Caffeinate */}
    -
    - -
    +
    Caffeinate @@ -131,15 +129,12 @@ export function MobileChannelsSettings({ dialogOpen }: { dialogOpen: boolean }) />
    - {/* WhatsApp */}
    -
    - -
    +
    WhatsApp @@ -228,15 +223,12 @@ export function MobileChannelsSettings({ dialogOpen }: { dialogOpen: boolean }) )}
    - {/* Telegram */}
    -
    - -
    +
    Telegram diff --git a/apps/x/apps/renderer/src/components/ui/dialog.tsx b/apps/x/apps/renderer/src/components/ui/dialog.tsx index 60cc10e1..7ba2cb4b 100644 --- a/apps/x/apps/renderer/src/components/ui/dialog.tsx +++ b/apps/x/apps/renderer/src/components/ui/dialog.tsx @@ -36,7 +36,7 @@ function DialogOverlay({ Date: Fri, 10 Jul 2026 01:10:22 +0530 Subject: [PATCH 7/7] feat(x): rename, pin, and delete chats from the sidebar - Hover kebab menu on sidebar chat rows with Pin / Rename / Delete - Rename: inline edit persisted via the existing sessions:setTitle; custom titles survive since auto-titling only fills empty titles - Pin: up to 3 chats float to the top of the list (localStorage) - Delete: confirm dialog, sessions:delete, closes any open tab --- apps/x/apps/renderer/src/App.tsx | 14 ++ .../src/components/sidebar-content.tsx | 161 +++++++++++++++++- 2 files changed, 166 insertions(+), 9 deletions(-) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index f401fb0f..14b2ba2f 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -6163,6 +6163,20 @@ function App() { onOpenApps={openAppsView} recentRuns={runs} onOpenRun={(rid) => void navigateToView({ type: 'chat', runId: rid })} + onRenameRun={(rid, title) => { + void window.ipc.invoke('sessions:setTitle', { sessionId: rid, title }) + .then(() => setRuns((prev) => prev.map((r) => (r.id === rid ? { ...r, title } : r)))) + .catch((err) => console.error('Failed to rename chat:', err)) + }} + onDeleteRun={(rid) => { + void window.ipc.invoke('sessions:delete', { sessionId: rid }) + .then(() => { + setRuns((prev) => prev.filter((r) => r.id !== rid)) + const openTab = chatTabs.find((t) => t.runId === rid) + if (openTab) closeChatTab(openTab.id) + }) + .catch((err) => console.error('Failed to delete chat:', err)) + }} onOpenChatHistory={() => void navigateToView({ type: 'chat-history' })} onOpenEmail={(threadId) => openEmailView(threadId)} onOpenHome={() => void navigateToView({ type: 'home' })} diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index 569f9de6..2d17d957 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -15,7 +15,11 @@ import { Home, LayoutGrid, Mic, + MoreVertical, + Pencil, + Pin, SquarePen, + Trash2, Plug, LoaderIcon, Mail, @@ -61,6 +65,12 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" import { cn } from "@/lib/utils" import { isOutOfCredits, CREDIT_EXHAUSTED_EVENT, CREDIT_REPLENISHED_EVENT } from "@/lib/credit-status" import { SettingsDialog } from "@/components/settings-dialog" @@ -127,6 +137,8 @@ type ServiceEventType = z.infer const MAX_SYNC_EVENTS = 1000 const RUN_STALE_MS = 2 * 60 * 60 * 1000 +const PINNED_CHATS_STORAGE_KEY = 'x:pinned-chats' +const MAX_PINNED_CHATS = 3 const SERVICE_LABELS: Record = { gmail: "Syncing Gmail", @@ -171,6 +183,10 @@ type SidebarContentPanelProps = { onOpenAgent?: (slug: string) => void recentRuns?: { id: string; title?: string; createdAt: string; modifiedAt?: string }[] onOpenRun?: (runId: string) => void + /** Persist a custom chat title (sessions:setTitle) and refresh the runs list. */ + onRenameRun?: (runId: string, title: string) => void + /** Delete the chat's session (sessions:delete) and refresh the runs list. */ + onDeleteRun?: (runId: string) => void onOpenChatHistory?: () => void onOpenEmail?: (threadId?: string) => void onOpenHome?: () => void @@ -422,6 +438,8 @@ export function SidebarContentPanel({ onOpenApps, recentRuns = [], onOpenRun, + onRenameRun, + onDeleteRun, onOpenChatHistory, onOpenEmail, onOpenHome, @@ -548,16 +566,54 @@ export function SidebarContentPanel({ .slice(0, 10) }, [tree]) - // Chats: the 5 most recently modified chats, newest first. + // Pinned chats: a per-machine UI preference, persisted in localStorage. + const [pinnedChatIds, setPinnedChatIds] = useState(() => { + try { + const raw = window.localStorage.getItem(PINNED_CHATS_STORAGE_KEY) + const parsed: unknown = raw ? JSON.parse(raw) : [] + return Array.isArray(parsed) ? parsed.filter((x): x is string => typeof x === 'string') : [] + } catch { + return [] + } + }) + const toggleChatPin = useCallback((chatId: string) => { + const isPinned = pinnedChatIds.includes(chatId) + if (!isPinned && pinnedChatIds.length >= MAX_PINNED_CHATS) { + toast(`You can pin up to ${MAX_PINNED_CHATS} chats`, 'error') + return + } + const next = isPinned ? pinnedChatIds.filter((id) => id !== chatId) : [...pinnedChatIds, chatId] + try { + window.localStorage.setItem(PINNED_CHATS_STORAGE_KEY, JSON.stringify(next)) + } catch { /* ignore */ } + setPinnedChatIds(next) + }, [pinnedChatIds]) + + // Chats: pinned first, then the most recently modified, 10 rows total. const recentChats = React.useMemo(() => { const chatRecency = (r: { createdAt: string; modifiedAt?: string }) => { const ms = new Date(r.modifiedAt ?? r.createdAt).getTime() return Number.isFinite(ms) ? ms : 0 } - return [...recentRuns] - .sort((a, b) => chatRecency(b) - chatRecency(a)) - .slice(0, 10) - }, [recentRuns]) + const sorted = [...recentRuns].sort((a, b) => chatRecency(b) - chatRecency(a)) + const pinned = sorted.filter((r) => pinnedChatIds.includes(r.id)) + const rest = sorted.filter((r) => !pinnedChatIds.includes(r.id)) + return [...pinned, ...rest.slice(0, Math.max(0, 10 - pinned.length))] + }, [recentRuns, pinnedChatIds]) + + // Chat pending delete confirmation, if any. + const [deleteChatTarget, setDeleteChatTarget] = useState<{ id: string; title: string } | null>(null) + + // Inline chat rename: which row is editing and its draft text. + const [renamingChatId, setRenamingChatId] = useState(null) + const [renameDraft, setRenameDraft] = useState('') + const commitChatRename = useCallback((chatId: string) => { + const title = renameDraft.trim() + const current = recentChats.find((c) => c.id === chatId) + setRenamingChatId(null) + if (!title || title === (current?.title ?? '')) return + onRenameRun?.(chatId, title) + }, [renameDraft, recentChats, onRenameRun]) // Workspace count for the Workspaces sublabel — top-level dir children of // knowledge/Workspace (matches WorkspaceView's root listing). @@ -971,10 +1027,75 @@ export function SidebarContentPanel({ {recentChats.map((chat) => ( - onOpenRun?.(chat.id)}> - - {chat.title || '(Untitled chat)'} - + {renamingChatId === chat.id ? ( +
    + + setRenameDraft(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault() + commitChatRename(chat.id) + } else if (e.key === 'Escape') { + e.preventDefault() + setRenamingChatId(null) + } + }} + onBlur={() => commitChatRename(chat.id)} + className="h-6 min-w-0 flex-1 rounded-sm border border-border bg-background px-1.5 text-sm outline-none focus:ring-1 focus:ring-ring" + /> +
    + ) : ( + <> + onOpenRun?.(chat.id)} className={onRenameRun ? 'pr-7' : undefined}> + + {chat.title || '(Untitled chat)'} + {pinnedChatIds.includes(chat.id) && ( + + )} + + {onRenameRun && ( + + + + + + toggleChatPin(chat.id)}> + + {pinnedChatIds.includes(chat.id) ? 'Unpin' : 'Pin'} + + { + setRenameDraft(chat.title || '') + setRenamingChatId(chat.id) + }} + > + + Rename + + {onDeleteRun && ( + setDeleteChatTarget({ id: chat.id, title: chat.title || '(Untitled chat)' })} + > + + Delete + + )} + + + )} + + )}
    ))} {onOpenChatHistory && ( @@ -993,6 +1114,28 @@ export function SidebarContentPanel({ )} + { if (!open) setDeleteChatTarget(null) }}> + + + Delete chat? + + “{deleteChatTarget?.title}” and its full history will be permanently deleted. + + + + Cancel + { + if (deleteChatTarget) onDeleteRun?.(deleteChatTarget.id) + setDeleteChatTarget(null) + }} + > + Delete + + + + {/* Billing / upgrade CTA or Log in CTA */} {isRowboatConnected && billing ? (() => {