11import { AlertCircle , X } from "lucide-react" ;
22import { useCallback , useEffect , useMemo } from "react" ;
33import { QueueList } from "@/components/QueueList" ;
4- import { TodoSidebar } from "@/components/TodoSidebar" ;
54import { UpdateDialog } from "@/components/UpdateDialog" ;
65import { Button } from "@/components/ui/button" ;
76import {
8- RightSidebarProvider ,
97 SidebarInset ,
108 SidebarProvider ,
9+ useSidebar ,
1110} from "@/components/ui/sidebar" ;
1211import { Spinner } from "@/components/ui/spinner" ;
1312import {
@@ -20,7 +19,6 @@ import {
2019 useSessionState ,
2120} from "@/hooks/use-opencode" ;
2221import { useUpdateCheck } from "@/hooks/use-update-check" ;
23- import { useSessionTodos } from "@/lib/todos" ;
2422import { computeTokenTotal } from "@/lib/utils" ;
2523import { AppSidebar } from "./components/AppSidebar" ;
2624import { MessageList } from "./components/MessageList" ;
@@ -29,6 +27,7 @@ import { TitleBar } from "./components/TitleBar";
2927import "./index.css" ;
3028
3129function AppContent ( ) {
30+ const leftSidebar = useSidebar ( ) ;
3231 const {
3332 sendPrompt,
3433 abortSession,
@@ -185,128 +184,119 @@ function AppContent() {
185184 ) ;
186185 } , [ activeSessionId , messages , providers , selectedModel , providerDefaults ] ) ;
187186
188- // Extract the latest todo snapshot from the current session's messages
189- const sessionTodos = useSessionTodos ( messages ) ;
190-
191187 // Check for app updates on startup
192188 const updateCheck = useUpdateCheck ( ) ;
193189
194190 return (
195- < SidebarProvider >
191+ < >
196192 < AppSidebar />
197- < SidebarInset >
198- < RightSidebarProvider className = "flex-col" >
193+ < SidebarInset className = "overflow-hidden" >
194+ < div className = "flex flex -col h-full " >
199195 { /* Title bar spans full width */ }
200- < TitleBar todos = { sessionTodos } />
196+ < TitleBar onToggleLeftSidebar = { leftSidebar . toggleSidebar } />
201197
202- { /* Below title bar: content column + right sidebar in a row */ }
203- < div className = "flex flex-1 min-h-0 min-w-0" >
204- < div className = "flex-1 flex flex-col min-w-0 select-none" >
205- { /* Startup banner */ }
206- { isBooting && (
207- < div className = "flex items-center gap-2 px-4 py-2 border-b border-border text-sm text-muted-foreground bg-muted/30" >
208- < Spinner className = "size-4 shrink-0" />
209- < span >
210- { bootState === "checking-server"
211- ? "Checking local OpenCode server..."
212- : "Starting local OpenCode server..." }
213- </ span >
214- </ div >
215- ) }
198+ < div className = "flex-1 flex flex-col min-w-0 min-h-0 select-none" >
199+ { /* Startup banner */ }
200+ { isBooting && (
201+ < div className = "flex items-center gap-2 px-4 py-2 border-b border-border text-sm text-muted-foreground bg-muted/30" >
202+ < Spinner className = "size-4 shrink-0" />
203+ < span >
204+ { bootState === "checking-server"
205+ ? "Checking local OpenCode server..."
206+ : "Starting local OpenCode server..." }
207+ </ span >
208+ </ div >
209+ ) }
216210
217- { /* Error banner */ }
218- { ! isBooting && ( bootState === "error" || lastError ) && (
219- < div className = "flex items-center gap-2 px-4 py-2 bg-destructive/10 border-b border-destructive/20 text-sm text-destructive" >
220- < AlertCircle className = "size-4 shrink-0" />
221- < span className = "flex-1 truncate" >
222- { bootState === "error" ? bootError : lastError }
223- </ span >
224- < Button variant = "ghost" size = "icon-xs" onClick = { clearError } >
225- < X className = "size-3" />
226- </ Button >
227- </ div >
228- ) }
211+ { /* Error banner */ }
212+ { ! isBooting && ( bootState === "error" || lastError ) && (
213+ < div className = "flex items-center gap-2 px-4 py-2 bg-destructive/10 border-b border-destructive/20 text-sm text-destructive" >
214+ < AlertCircle className = "size-4 shrink-0" />
215+ < span className = "flex-1 truncate" >
216+ { bootState === "error" ? bootError : lastError }
217+ </ span >
218+ < Button variant = "ghost" size = "icon-xs" onClick = { clearError } >
219+ < X className = "size-3" />
220+ </ Button >
221+ </ div >
222+ ) }
229223
230- { /* Chat area */ }
231- < MessageList />
224+ { /* Chat area */ }
225+ < MessageList />
232226
233- { /* Queue list + Prompt input */ }
234- < div className = "shrink-0" >
235- < div className = "max-w-2xl mx-auto" >
236- { queuedPrompts . length > 0 && (
237- < div className = "mb-1.5" >
238- < QueueList
239- items = { queuedPrompts }
240- onRemove = { ( id ) => {
241- if ( ! activeSessionId ) return ;
242- removeFromQueue ( activeSessionId , id ) ;
243- } }
244- onMoveUp = { ( index ) => {
245- if ( ! activeSessionId ) return ;
246- reorderQueue ( activeSessionId , index , index - 1 ) ;
247- } }
248- onMoveDown = { ( index ) => {
249- if ( ! activeSessionId ) return ;
250- reorderQueue ( activeSessionId , index , index + 1 ) ;
251- } }
252- onMoveToTop = { ( index ) => {
253- if ( ! activeSessionId ) return ;
254- reorderQueue ( activeSessionId , index , 0 ) ;
255- } }
256- onMoveToBottom = { ( index ) => {
257- if ( ! activeSessionId ) return ;
258- reorderQueue (
259- activeSessionId ,
260- index ,
261- queuedPrompts . length - 1 ,
262- ) ;
263- } }
264- onEdit = { ( id , newText ) => {
265- if ( ! activeSessionId ) return ;
266- updateQueuedPrompt ( activeSessionId , id , newText ) ;
267- } }
268- onSendNow = { ( id ) => {
269- if ( ! activeSessionId ) return ;
270- void sendQueuedNow ( activeSessionId , id ) ;
271- } }
272- />
273- </ div >
274- ) }
275- < PromptBox
276- autoFocus
277- disabled = {
278- isBooting ||
279- ! isConnected ||
280- isLoadingMessages ||
281- ( ! activeSessionId && ! draftSessionDirectory )
282- }
283- isLoading = { isBusy }
284- contextPercent = { contextPercent }
285- onSubmit = { ( message , images ) => {
286- sendPrompt ( message , images ) ;
287- } }
288- onStop = { ( ) => abortSession ( ) }
289- />
290- </ div >
227+ { /* Queue list + Prompt input */ }
228+ < div className = "shrink-0" >
229+ < div className = "max-w-2xl mx-auto" >
230+ { queuedPrompts . length > 0 && (
231+ < div className = "mb-1.5" >
232+ < QueueList
233+ items = { queuedPrompts }
234+ onRemove = { ( id ) => {
235+ if ( ! activeSessionId ) return ;
236+ removeFromQueue ( activeSessionId , id ) ;
237+ } }
238+ onMoveUp = { ( index ) => {
239+ if ( ! activeSessionId ) return ;
240+ reorderQueue ( activeSessionId , index , index - 1 ) ;
241+ } }
242+ onMoveDown = { ( index ) => {
243+ if ( ! activeSessionId ) return ;
244+ reorderQueue ( activeSessionId , index , index + 1 ) ;
245+ } }
246+ onMoveToTop = { ( index ) => {
247+ if ( ! activeSessionId ) return ;
248+ reorderQueue ( activeSessionId , index , 0 ) ;
249+ } }
250+ onMoveToBottom = { ( index ) => {
251+ if ( ! activeSessionId ) return ;
252+ reorderQueue (
253+ activeSessionId ,
254+ index ,
255+ queuedPrompts . length - 1 ,
256+ ) ;
257+ } }
258+ onEdit = { ( id , newText ) => {
259+ if ( ! activeSessionId ) return ;
260+ updateQueuedPrompt ( activeSessionId , id , newText ) ;
261+ } }
262+ onSendNow = { ( id ) => {
263+ if ( ! activeSessionId ) return ;
264+ void sendQueuedNow ( activeSessionId , id ) ;
265+ } }
266+ />
267+ </ div >
268+ ) }
269+ < PromptBox
270+ autoFocus
271+ disabled = {
272+ isBooting ||
273+ ! isConnected ||
274+ isLoadingMessages ||
275+ ( ! activeSessionId && ! draftSessionDirectory )
276+ }
277+ isLoading = { isBusy }
278+ contextPercent = { contextPercent }
279+ onSubmit = { ( message , images ) => {
280+ sendPrompt ( message , images ) ;
281+ } }
282+ onStop = { ( ) => abortSession ( ) }
283+ />
291284 </ div >
292285 </ div >
293-
294- { /* Right sidebar: task list */ }
295- < TodoSidebar todos = { sessionTodos } />
296286 </ div >
297- </ RightSidebarProvider >
287+ </ div >
298288 </ SidebarInset >
299-
300- { /* Update-available popup */ }
301289 < UpdateDialog update = { updateCheck } />
302- </ SidebarProvider >
290+ </ >
303291 ) ;
304292}
305293
306294export function App ( ) {
307295 return (
308296 < OpenCodeProvider >
309- < AppContent />
297+ < SidebarProvider className = "!h-dvh" >
298+ < AppContent />
299+ </ SidebarProvider >
310300 </ OpenCodeProvider >
311301 ) ;
312302}
0 commit comments