@@ -7,10 +7,13 @@ import { useApi, useApp, useHost } from "../../store/context";
77import { Button } from "../../ui/Button" ;
88import { Confirm } from "../../ui/Confirm" ;
99import { Menu } from "../../ui/Menu" ;
10+ import { type Tab , Tabs , tabId } from "../../ui/Tabs" ;
1011import { attempt } from "../../ui/toast" ;
11- import { BotAvatar } from "../bots/BotAvatar" ;
1212import { BotDialog } from "../bots/BotDialog" ;
13- import { BotStateBadge } from "../bots/BotStateBadge" ;
13+ import { Composer } from "../messages/Composer" ;
14+ import { Timeline } from "../messages/Timeline" ;
15+ import { TaskList } from "../tasks/TaskList" ;
16+ import { CrewBots } from "./CrewBots" ;
1417import { CrewDialog } from "./CrewDialog" ;
1518
1619/** The crew's `shared` folder, next to its bots' workspaces (spec 5). */
@@ -20,14 +23,21 @@ export function sharedFolder(bots: Bot[]): string | null {
2023}
2124
2225type Open = "bot" | "rename" | "archive" | null ;
26+ type Pane = "bots" | "timeline" | "tasks" ;
27+
28+ const TABS : Tab < Pane > [ ] = [
29+ { id : "bots" , label : "Bots" } ,
30+ { id : "timeline" , label : "Timeline" } ,
31+ { id : "tasks" , label : "Tasks" } ,
32+ ] ;
2333
2434export function CrewView ( { crew } : { crew : Crew } ) {
2535 const api = useApi ( ) ;
2636 const host = useHost ( ) ;
2737 const putCrew = useApp ( ( state ) => state . putCrew ) ;
28- const selectBot = useApp ( ( state ) => state . selectBot ) ;
2938 const bots = useApp ( useShallow ( ( state ) => botsOf ( state , crew . id ) ) ) ;
3039 const [ open , setOpen ] = useState < Open > ( null ) ;
40+ const [ pane , setPane ] = useState < Pane > ( "bots" ) ;
3141 const shared = sharedFolder ( bots ) ;
3242 const close = ( ) => setOpen ( null ) ;
3343
@@ -38,7 +48,7 @@ export function CrewView({ crew }: { crew: Crew }) {
3848
3949 return (
4050 < section aria-label = { crew . name } className = "flex min-h-0 flex-1 flex-col" >
41- < header className = "flex items-center gap-3 border-line border-b px-6 py-4 " >
51+ < header className = "flex items-center gap-3 border-line border-b px-5 py-3.5 " >
4252 < div className = "min-w-0 flex-1" >
4353 < h1 className = "truncate font-semibold text-xl tracking-tight" > { crew . name } </ h1 >
4454 < p className = "mt-0.5 text-muted text-sm" >
@@ -80,43 +90,38 @@ export function CrewView({ crew }: { crew: Crew }) {
8090 />
8191 </ header >
8292
83- < div className = "min-h-0 flex-1 overflow-y-auto p-6" >
84- { bots . length === 0 ? (
85- < div className = "border border-line border-dashed px-6 py-10 text-center" >
86- < p className = "font-medium" > No bots in { crew . name } yet.</ p >
87- < p className = "mt-1 text-muted text-sm" >
88- A bot is a Claude Code session that keeps running, with its own folder and role.
89- </ p >
90- < Button className = "mt-4" variant = "primary" icon = { Plus } onClick = { ( ) => setOpen ( "bot" ) } >
91- New bot
92- </ Button >
93- </ div >
94- ) : (
95- < ul className = "grid grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-3" >
96- { bots . map ( ( bot ) => (
97- < li key = { bot . id } >
98- < button
99- type = "button"
100- onClick = { ( ) => selectBot ( bot . id ) }
101- className = "flex h-full w-full flex-col gap-2 border border-line bg-panel p-3 text-left hover:border-line-strong"
102- >
103- < div className = "flex w-full items-center gap-2.5" >
104- < BotAvatar color = { bot . color } size = { 32 } />
105- < div className = "min-w-0 flex-1" >
106- < p className = "truncate font-semibold" > { bot . name } </ p >
107- < p className = "truncate font-mono text-muted text-xs" > @{ bot . handle } </ p >
108- </ div >
109- </ div >
110- < p className = "line-clamp-2 min-h-10 text-ink-soft text-sm" >
111- { bot . role || "No role yet." }
112- </ p >
113- < BotStateBadge bot = { bot } crewPaused = { crew . paused } />
114- </ button >
115- </ li >
116- ) ) }
117- </ ul >
118- ) }
119- </ div >
93+ < Tabs < Pane > label = "Crew views" tabs = { TABS } value = { pane } onChange = { setPane } />
94+ { pane === "bots" && (
95+ < div
96+ role = "tabpanel"
97+ aria-labelledby = { tabId ( "bots" ) }
98+ className = "min-h-0 flex-1 overflow-y-auto p-5"
99+ >
100+ < CrewBots crew = { crew } bots = { bots } onNewBot = { ( ) => setOpen ( "bot" ) } />
101+ </ div >
102+ ) }
103+ { pane === "timeline" && (
104+ < div
105+ role = "tabpanel"
106+ aria-labelledby = { tabId ( "timeline" ) }
107+ className = "flex min-h-0 flex-1 flex-col"
108+ >
109+ < Timeline
110+ filter = { { crewId : crew . id } }
111+ empty = "No messages yet. Write to a bot below; what the bots send each other shows up here too."
112+ composer = { ( onSent ) => < Composer crewId = { crew . id } onSent = { onSent } /> }
113+ />
114+ </ div >
115+ ) }
116+ { pane === "tasks" && (
117+ < div
118+ role = "tabpanel"
119+ aria-labelledby = { tabId ( "tasks" ) }
120+ className = "flex min-h-0 flex-1 flex-col"
121+ >
122+ < TaskList crewId = { crew . id } />
123+ </ div >
124+ ) }
120125
121126 { open === "bot" && < BotDialog crewId = { crew . id } onClose = { close } /> }
122127 { open === "rename" && < CrewDialog crew = { crew } onClose = { close } /> }
0 commit comments