From a7787733b974fde79ecef87c22f01859e15c4c87 Mon Sep 17 00:00:00 2001 From: zenchantlive Date: Mon, 16 Feb 2026 10:16:33 -0800 Subject: [PATCH] fix(bb-ui2): separate ThreadDrawer from RightPanel - Created ThreadDrawer component (24rem) that slides from right edge of middle - RightPanel now reserved for Activity Feed + Agent roster (bb-ui2.29) - Updated URL state: added drawer and agentId params - Thread shows in drawer when card selected Architecture now matches PRD: - Right Panel (17rem): Activity Feed + Agent roster - Thread Drawer (24rem): Opens from middle when card clicked Beads: bb-ui2.31 thread drawer created, bb-ui2.13 closed --- .beads/bd.sock.startlock | 2 +- src/components/shared/thread-drawer.tsx | 133 ++++++++++++++++++++++++ src/components/shared/unified-shell.tsx | 48 +++++++-- src/components/social/social-detail.tsx | 30 +----- src/components/swarm/swarm-detail.tsx | 30 +----- src/hooks/use-url-state.ts | 35 ++++++- 6 files changed, 208 insertions(+), 70 deletions(-) create mode 100644 src/components/shared/thread-drawer.tsx diff --git a/.beads/bd.sock.startlock b/.beads/bd.sock.startlock index 71bb469..b18724b 100644 --- a/.beads/bd.sock.startlock +++ b/.beads/bd.sock.startlock @@ -1 +1 @@ -62948 +55588 diff --git a/src/components/shared/thread-drawer.tsx b/src/components/shared/thread-drawer.tsx new file mode 100644 index 0000000..0eebe5f --- /dev/null +++ b/src/components/shared/thread-drawer.tsx @@ -0,0 +1,133 @@ +'use client'; + +import { X, Send } from 'lucide-react'; +import { ThreadView, type ThreadItem } from './thread-view'; +import { useState } from 'react'; + +interface ThreadDrawerProps { + isOpen: boolean; + onClose: () => void; + title: string; + id: string; + items?: ThreadItem[]; +} + +// Sample data for demo +const SAMPLE_ITEMS: ThreadItem[] = [ + { + id: '1', + type: 'status_change', + from: 'backlog', + to: 'in_progress', + timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000), + }, + { + id: '2', + type: 'comment', + author: 'zenchantlive', + content: 'Started working on this task.', + timestamp: new Date(Date.now() - 1 * 60 * 60 * 1000), + }, + { + id: '3', + type: 'protocol_event', + event: 'HANDOFF', + content: 'Handed off to agent', + timestamp: new Date(Date.now() - 30 * 60 * 1000), + }, +]; + +export function ThreadDrawer({ isOpen, onClose, title, id, items = SAMPLE_ITEMS }: ThreadDrawerProps) { + const [comment, setComment] = useState(''); + + if (!isOpen) return null; + + return ( + <> + {/* Backdrop */} + - {/* RIGHT PANEL: 17rem detail strip */} + {/* RIGHT PANEL: 17rem - Always shows Activity (bb-ui2.29) */} {renderRightPanel()} + {/* THREAD DRAWER: 24rem - Slides from right edge of middle when card selected */} + + {/* MOBILE NAV: Bottom tab bar */} diff --git a/src/components/social/social-detail.tsx b/src/components/social/social-detail.tsx index 1362be3..587fdf1 100644 --- a/src/components/social/social-detail.tsx +++ b/src/components/social/social-detail.tsx @@ -3,34 +3,8 @@ import type { SocialCard as SocialCardData, AgentStatus } from '../../lib/social-cards'; import { StatusBadge } from '../shared/status-badge'; import { AgentAvatar } from '../shared/agent-avatar'; -import { ThreadView, type ThreadItem } from '../shared/thread-view'; import { Plus } from 'lucide-react'; -// Sample data for demo - remove when real data connected -const SAMPLE_THREAD_ITEMS: ThreadItem[] = [ - { - id: '1', - type: 'status_change', - from: 'backlog', - to: 'in_progress', - timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000), - }, - { - id: '2', - type: 'comment', - author: 'zenchantlive', - content: 'Started working on this task. Will need input from the API team.', - timestamp: new Date(Date.now() - 1 * 60 * 60 * 1000), - }, - { - id: '3', - type: 'protocol_event', - event: 'HANDOFF', - content: 'Handed off to bb-agent-1 for implementation', - timestamp: new Date(Date.now() - 30 * 60 * 1000), - }, -]; - interface SocialDetailProps { data: SocialCardData; } @@ -67,7 +41,9 @@ export function SocialDetail({ data }: SocialDetailProps) {

Thread

- +

+ Thread drawer coming (bb-ui2.31) +

{data.blocks.length > 0 && ( diff --git a/src/components/swarm/swarm-detail.tsx b/src/components/swarm/swarm-detail.tsx index be69a85..bde3141 100644 --- a/src/components/swarm/swarm-detail.tsx +++ b/src/components/swarm/swarm-detail.tsx @@ -3,7 +3,6 @@ import type { SwarmCard as SwarmCardType } from '../../lib/swarm-cards'; import { Badge } from '../../../components/ui/badge'; import { AgentAvatar } from '../shared/agent-avatar'; -import { ThreadView, type ThreadItem } from '../shared/thread-view'; import { cn } from '../../lib/utils'; import { AlertTriangle, Clock, Users } from 'lucide-react'; @@ -149,38 +148,15 @@ function LastActivitySection({ date }: { date: Date }) { ); } -// Sample data for demo - remove when real data connected -const SAMPLE_SWARM_THREAD: ThreadItem[] = [ - { - id: '1', - type: 'status_change', - from: 'planning', - to: 'in_progress', - timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000), - }, - { - id: '2', - type: 'comment', - author: 'bb-agent-1', - content: 'Starting work on the first batch of tasks.', - timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000), - }, - { - id: '3', - type: 'protocol_event', - event: 'CLOSED', - content: 'Task bb-buff.1 completed', - timestamp: new Date(Date.now() - 30 * 60 * 1000), - }, -]; - function ThreadSection() { return (
Thread - +

+ Thread drawer coming (bb-ui2.31) +

); } diff --git a/src/hooks/use-url-state.ts b/src/hooks/use-url-state.ts index 1618432..3e9a74c 100644 --- a/src/hooks/use-url-state.ts +++ b/src/hooks/use-url-state.ts @@ -3,8 +3,9 @@ import { useCallback, useMemo } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; -export type ViewType = 'social' | 'graph' | 'swarm'; +export type ViewType = 'social' | 'graph' | 'swarm' | 'activity'; export type PanelState = 'open' | 'closed'; +export type DrawerState = 'open' | 'closed'; export type GraphTabType = 'flow' | 'overview'; export interface UrlState { @@ -14,8 +15,12 @@ export interface UrlState { setTaskId: (id: string | null) => void; swarmId: string | null; setSwarmId: (id: string | null) => void; + agentId: string | null; + setAgentId: (id: string | null) => void; panel: PanelState; togglePanel: () => void; + drawer: DrawerState; + setDrawer: (state: DrawerState) => void; graphTab: GraphTabType; setGraphTab: (tab: GraphTabType) => void; clearSelection: () => void; @@ -23,17 +28,21 @@ export interface UrlState { const DEFAULT_VIEW: ViewType = 'social'; const DEFAULT_PANEL: PanelState = 'closed'; +const DEFAULT_DRAWER: DrawerState = 'closed'; const DEFAULT_GRAPH_TAB: GraphTabType = 'flow'; -const VALID_VIEWS: ViewType[] = ['social', 'graph', 'swarm']; +const VALID_VIEWS: ViewType[] = ['social', 'graph', 'swarm', 'activity']; const VALID_PANELS: PanelState[] = ['open', 'closed']; +const VALID_DRAWERS: DrawerState[] = ['open', 'closed']; const VALID_GRAPH_TABS: GraphTabType[] = ['flow', 'overview']; export function parseUrlState(searchParams: URLSearchParams): { view: ViewType; taskId: string | null; swarmId: string | null; + agentId: string | null; panel: PanelState; + drawer: DrawerState; graphTab: GraphTabType; } { const viewParam = searchParams.get('view'); @@ -43,18 +52,24 @@ export function parseUrlState(searchParams: URLSearchParams): { const taskId = searchParams.get('task'); const swarmId = searchParams.get('swarm'); + const agentId = searchParams.get('agent'); const panelParam = searchParams.get('panel'); const panel: PanelState = panelParam && VALID_PANELS.includes(panelParam as PanelState) ? (panelParam as PanelState) : DEFAULT_PANEL; + const drawerParam = searchParams.get('drawer'); + const drawer: DrawerState = drawerParam && VALID_DRAWERS.includes(drawerParam as DrawerState) + ? (drawerParam as DrawerState) + : DEFAULT_DRAWER; + const graphTabParam = searchParams.get('graphTab'); const graphTab: GraphTabType = graphTabParam && VALID_GRAPH_TABS.includes(graphTabParam as GraphTabType) ? (graphTabParam as GraphTabType) : DEFAULT_GRAPH_TAB; - return { view, taskId, swarmId, panel, graphTab }; + return { view, taskId, swarmId, agentId, panel, drawer, graphTab }; } export function buildUrlParams( @@ -98,17 +113,25 @@ export function useUrlState(): UrlState { updateUrl({ swarm: id, panel: id ? 'open' : null }); }, [updateUrl]); + const setAgentId = useCallback((id: string | null) => { + updateUrl({ agent: id, panel: id ? 'open' : null }); + }, [updateUrl]); + const togglePanel = useCallback(() => { const newPanel = state.panel === 'open' ? 'closed' : 'open'; updateUrl({ panel: newPanel }); }, [state.panel, updateUrl]); + const setDrawer = useCallback((state: DrawerState) => { + updateUrl({ drawer: state }); + }, [updateUrl]); + const setGraphTab = useCallback((tab: GraphTabType) => { updateUrl({ graphTab: tab }); }, [updateUrl]); const clearSelection = useCallback(() => { - updateUrl({ task: null, swarm: null, panel: 'closed' }); + updateUrl({ task: null, swarm: null, panel: 'closed', drawer: 'closed' }); }, [updateUrl]); return { @@ -118,8 +141,12 @@ export function useUrlState(): UrlState { setTaskId, swarmId: state.swarmId, setSwarmId, + agentId: state.agentId, + setAgentId, panel: state.panel, togglePanel, + drawer: state.drawer, + setDrawer, graphTab: state.graphTab, setGraphTab, clearSelection,