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 */}
+
+
+ {/* Drawer */}
+
+ {/* Header */}
+
+
+
+ {id}
+
+
+ {title}
+
+
+
+
+
+ {/* Thread Content */}
+
+
+
+
+ {/* Compose */}
+
+
+ setComment(e.target.value)}
+ placeholder="Add a comment..."
+ className="flex-1 px-3 py-2 rounded-md text-sm"
+ style={{
+ backgroundColor: 'var(--color-bg-input)',
+ color: 'var(--color-text-primary)',
+ border: '1px solid rgba(255, 255, 255, 0.1)',
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' && comment.trim()) {
+ // TODO: Post comment
+ setComment('');
+ }
+ }}
+ />
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/shared/unified-shell.tsx b/src/components/shared/unified-shell.tsx
index 26f2b26..eb62f58 100644
--- a/src/components/shared/unified-shell.tsx
+++ b/src/components/shared/unified-shell.tsx
@@ -7,12 +7,11 @@ import { TopBar } from './top-bar';
import { LeftPanel } from './left-panel';
import { RightPanel } from './right-panel';
import { MobileNav } from './mobile-nav';
+import { ThreadDrawer } from './thread-drawer';
import { useUrlState } from '../../hooks/use-url-state';
import { GraphView } from '../graph/graph-view';
import { SocialPage } from '../social/social-page';
-import { SocialDetail } from '../social/social-detail';
import { SwarmPage } from '../swarm/swarm-page';
-import { SwarmDetail } from '../swarm/swarm-detail';
import { buildSocialCards } from '../../lib/social-cards';
import { buildSwarmCards } from '../../lib/swarm-cards';
@@ -27,7 +26,7 @@ export interface UnifiedShellProps {
export function UnifiedShell({
issues,
}: UnifiedShellProps) {
- const { view, taskId, setTaskId, swarmId, setSwarmId, graphTab, setGraphTab, panel } = useUrlState();
+ const { view, taskId, setTaskId, swarmId, setSwarmId, graphTab, setGraphTab, panel, drawer, setDrawer } = useUrlState();
const socialCards = useMemo(() => buildSocialCards(issues), [issues]);
const swarmCards = useMemo(() => buildSwarmCards(issues), [issues]);
@@ -39,14 +38,33 @@ export function UnifiedShell({
setTaskId(id);
};
+ const handleCardSelect = (id: string) => {
+ if (view === 'social') {
+ setTaskId(id);
+ } else if (view === 'swarm') {
+ setSwarmId(id);
+ }
+ setDrawer('open');
+ };
+
+ const handleCloseDrawer = () => {
+ setDrawer('closed');
+ };
+
+ // Thread drawer - shows when card selected
+ const isDrawerOpen = drawer === 'open' && (!!taskId || !!swarmId);
+ const drawerTitle = selectedSocialCard?.title || selectedSwarmCard?.title || '';
+ const drawerId = taskId || swarmId || '';
+
const renderRightPanel = () => {
- if (view === 'social' && taskId && selectedSocialCard) {
- return ;
- }
- if (view === 'swarm' && swarmId && selectedSwarmCard) {
- return ;
- }
- return null;
+ // TODO: Wire up ActivityPanel (bb-ui2.29) - for now show placeholder
+ return (
+
+ Activity Panel coming
+
+ (bb-ui2.29)
+
+ );
};
const renderMiddleContent = () => {
@@ -105,12 +123,20 @@ export function UnifiedShell({
{renderMiddleContent()}
- {/* 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,