From f2e08444cefacd883ad367e88de51355e0c046e1 Mon Sep 17 00:00:00 2001 From: andresdjasso Date: Thu, 4 Jun 2026 14:27:03 -0700 Subject: [PATCH 01/34] =?UTF-8?q?feat(mothership):=20home=20chat=20UX=20?= =?UTF-8?q?=E2=80=94=20grey=20tray,=20inline=20chat=20open,=20title=20bar,?= =?UTF-8?q?=20stationary=20panel=20toggle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restructure the home input into a stacked card (grey tray behind the input) that hosts the All Chats launcher; the chat list expands inline within the tray - Open existing chats inline with no route remount via adoptResolvedChatId (now exposed from useChat), with hover-prefetch and the slide-in morph - Add a Codex-style chat title bar (title + action menu) to the open chat view - Make the right resource-panel collapse/expand a single stationary toggle outside the animating panel, so it no longer moves on collapse - Auto-hide the chat scrollbar; reveal it only while actively scrolling - Bump the home input corner radius to 17px Co-Authored-By: Claude Opus 4.8 --- .../components/chat-history/chat-history.tsx | 244 ++++++++++++++++++ .../home/components/chat-history/index.ts | 1 + .../[workspaceId]/home/components/index.ts | 1 + .../chat-title-bar/chat-title-bar.tsx | 72 ++++++ .../components/chat-title-bar/index.ts | 1 + .../mothership-chat/mothership-chat.tsx | 24 +- .../resource-tabs/resource-panel-toggle.tsx | 63 +++++ .../resource-tabs/resource-tabs.tsx | 66 ++--- .../mothership-view/mothership-view.tsx | 3 - .../home/components/user-input/user-input.tsx | 2 +- .../app/workspace/[workspaceId]/home/home.tsx | 86 +++--- .../[workspaceId]/home/hooks/use-chat.ts | 5 + apps/sim/hooks/queries/mothership-chats.ts | 20 ++ apps/sim/hooks/use-autohide-scrollbar.ts | 38 +++ apps/sim/lib/posthog/events.ts | 6 + 15 files changed, 557 insertions(+), 75 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/chat-history/chat-history.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/chat-history/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/components/chat-title-bar/chat-title-bar.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/components/chat-title-bar/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-panel-toggle.tsx create mode 100644 apps/sim/hooks/use-autohide-scrollbar.ts diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/chat-history/chat-history.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/chat-history/chat-history.tsx new file mode 100644 index 00000000000..d7b4a1552ab --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/home/components/chat-history/chat-history.tsx @@ -0,0 +1,244 @@ +'use client' + +import { useEffect, useMemo, useRef, useState } from 'react' +import { differenceInCalendarDays, isToday, isYesterday } from 'date-fns' +import { useParams, useRouter } from 'next/navigation' +import { Expandable, ExpandableContent, Skeleton } from '@/components/emcn' +import { Clock, Search } from '@/components/emcn/icons' +import { cn } from '@/lib/core/utils/cn' +import { type TaskMetadata, usePrefetchChatHistory, useTasks } from '@/hooks/queries/tasks' + +const CONFIG = { + LIST_MAX_HEIGHT: 320, + SKELETON_ROWS: 5, +} as const + +/** A recency bucket of chats rendered as one section in the history list. */ +interface ChatBucket { + key: string + label: string + tasks: TaskMetadata[] +} + +/** + * Buckets chats into Codex-style recency sections. Pinned chats are lifted out + * of their date bucket into a dedicated section at the top; everything else is + * grouped by how recently it was last updated. The server already returns the + * list ordered (pinned first, then desc by `updatedAt`), so per-bucket order is + * preserved by simply appending as we iterate. + */ +function bucketChats(tasks: readonly TaskMetadata[]): ChatBucket[] { + const now = new Date() + const pinned: TaskMetadata[] = [] + const today: TaskMetadata[] = [] + const yesterday: TaskMetadata[] = [] + const last7: TaskMetadata[] = [] + const last30: TaskMetadata[] = [] + const older: TaskMetadata[] = [] + + for (const task of tasks) { + if (task.isPinned) { + pinned.push(task) + continue + } + const date = task.updatedAt + if (isToday(date)) { + today.push(task) + } else if (isYesterday(date)) { + yesterday.push(task) + } else { + const days = differenceInCalendarDays(now, date) + if (days <= 7) last7.push(task) + else if (days <= 30) last30.push(task) + else older.push(task) + } + } + + return ( + [ + { key: 'pinned', label: 'Pinned', tasks: pinned }, + { key: 'today', label: 'Today', tasks: today }, + { key: 'yesterday', label: 'Yesterday', tasks: yesterday }, + { key: 'last7', label: 'Previous 7 Days', tasks: last7 }, + { key: 'last30', label: 'Previous 30 Days', tasks: last30 }, + { key: 'older', label: 'Older', tasks: older }, + ] as const + ).filter((bucket) => bucket.tasks.length > 0) +} + +/** + * A small status dot mirroring the sidebar's semantics: yellow while a chat is + * actively streaming, brand accent when it has unread activity. Rendered only + * when one of those states applies. + */ +function StatusDot({ task }: { task: TaskMetadata }) { + if (!task.isActive && !task.isUnread) return null + return ( +