diff --git a/packages/app/src/components/session-task-indicator.tsx b/packages/app/src/components/session-task-indicator.tsx
new file mode 100644
index 00000000000..f4f11ee92bc
--- /dev/null
+++ b/packages/app/src/components/session-task-indicator.tsx
@@ -0,0 +1,49 @@
+import { createMemo, Show } from "solid-js"
+import { Button } from "@opencode-ai/ui/button"
+import { useSync } from "@/context/sync"
+import { useLayout } from "@/context/layout"
+import { useParams } from "@solidjs/router"
+
+export function SessionTaskIndicator() {
+ const sync = useSync()
+ const layout = useLayout()
+ const params = useParams()
+
+ const taskStats = createMemo(() => {
+ const sessionID = params.id
+ if (!sessionID) return { completed: 0, total: 0, hasInProgress: false, hasIncomplete: false }
+ const todos = sync.data.todo[sessionID] ?? []
+ const completed = todos.filter((t) => t.status === "completed").length
+ const hasInProgress = todos.some((t) => t.status === "in_progress")
+ const hasIncomplete = todos.some((t) => t.status !== "completed")
+ return { completed, total: todos.length, hasInProgress, hasIncomplete }
+ })
+
+ const handleClick = () => {
+ const sessionKey = `${params.dir}${params.id ? "/" + params.id : ""}`
+ const tabs = layout.tabs(sessionKey)
+ if (tabs.active() === "tasks") {
+ layout.review.close()
+ return
+ }
+ if (!layout.review.opened()) layout.review.open()
+ tabs.setActive("tasks")
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/app/src/components/session-task-panel.tsx b/packages/app/src/components/session-task-panel.tsx
new file mode 100644
index 00000000000..47158a955d5
--- /dev/null
+++ b/packages/app/src/components/session-task-panel.tsx
@@ -0,0 +1,90 @@
+import { createMemo, For, Show } from "solid-js"
+import { useSync } from "@/context/sync"
+import { useParams } from "@solidjs/router"
+import { Icon } from "@opencode-ai/ui/icon"
+import type { Todo } from "@opencode-ai/sdk/v2/client"
+
+function TaskItem(props: { todo: Todo }) {
+ return (
+
+
+ }
+ >
+
+
+ }
+ >
+
+
+ }
+ >
+
+
+
+
+
+ {props.todo.content}
+
+
+
+ )
+}
+
+export function SessionTaskPanel() {
+ const sync = useSync()
+ const params = useParams()
+
+ const todos = createMemo(() => {
+ const sessionID = params.id
+ if (!sessionID) return []
+ return sync.data.todo[sessionID] ?? []
+ })
+
+ return (
+
+
+
+
+
+ No tasks yet
+
+
+
0}>
+
+ {(todo) => }
+
+
+
+
+ )
+}
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index 4958ad2c353..ee8c23a0d38 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -19,6 +19,7 @@ import { TextField } from "@opencode-ai/ui/text-field"
import { DialogSelectServer } from "@/components/dialog-select-server"
import { SessionLspIndicator } from "@/components/session-lsp-indicator"
import { SessionMcpIndicator } from "@/components/session-mcp-indicator"
+import { SessionTaskIndicator } from "@/components/session-task-indicator"
import type { Session } from "@opencode-ai/sdk/v2/client"
import { same } from "@/utils/same"
@@ -44,6 +45,15 @@ export function SessionHeader() {
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same })
+ const hasIncompleteTasks = createMemo(() => {
+ const sessionID = params.id
+ if (!sessionID) return false
+ const todos = sync.data.todo[sessionID] ?? []
+ return todos.some((t) => t.status !== "completed")
+ })
+
+ const showSidebarToggle = createMemo(() => currentSession()?.summary?.files || hasIncompleteTasks())
+
function navigateToProject(directory: string) {
navigate(`/${base64Encode(directory)}`)
}
@@ -163,12 +173,13 @@ export function SessionHeader() {
+
-
+
+
+
+
+
+
Tasks
+ {(() => {
+ const todos = sync.data.todo[params.id ?? ""] ?? []
+ const completed = todos.filter((t) => t.status === "completed").length
+ return (
+
+ {completed}/{todos.length}
+
+ )
+ })()}
+
+
+
{(tab) => }
@@ -1061,6 +1089,11 @@ export default function Page() {
+
+
+
+
+
{(tab) => {
let scroll: HTMLDivElement | undefined