diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/activity-bar/index.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/activity-bar/index.tsx
new file mode 100644
index 00000000..8ad68a8e
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/activity-bar/index.tsx
@@ -0,0 +1,84 @@
+import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@mimo-ai/plugin/tui"
+import { For, Show, createMemo } from "solid-js"
+import { PANELS, getPanelComponent } from "./panels"
+
+const id = "activity-bar-panels"
+
+function ActivityBarIcons(props: {
+ api: TuiPluginApi
+ session_id: string
+ active_panels: string[]
+ on_toggle: (panel_id: string) => void
+}) {
+ const theme = () => props.api.theme.current
+ const isActive = (id: string) => props.active_panels.includes(id)
+
+ return (
+
+ {(panel) => (
+ props.on_toggle(panel.id)}
+ >
+
+ {panel.icon}
+
+
+ )}
+
+ )
+}
+
+function SidebarPanelContent(props: {
+ api: TuiPluginApi
+ session_id: string
+ panel_id: string
+}) {
+ const panelDef = getPanelComponent(props.panel_id)
+ if (!panelDef) return null
+
+ return (
+
+ {panelDef.component(props.api, props.session_id)}
+
+ )
+}
+
+const tui: TuiPlugin = async (api) => {
+ api.slots.register({
+ order: 100,
+ slots: {
+ activity_bar(_ctx, props) {
+ return (
+
+ )
+ },
+ sidebar_panel(_ctx, props) {
+ return (
+
+ )
+ },
+ },
+ })
+}
+
+const plugin: TuiPluginModule & { id: string } = {
+ id,
+ tui,
+}
+
+export default plugin
diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/activity-bar/panels.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/activity-bar/panels.tsx
new file mode 100644
index 00000000..512b653e
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/activity-bar/panels.tsx
@@ -0,0 +1,128 @@
+import type { TuiPluginApi } from "@mimo-ai/plugin/tui"
+import { createMemo, For, Show } from "solid-js"
+
+interface PanelDef {
+ id: string
+ icon: string
+ label: string
+ component: (api: TuiPluginApi, session_id: string) => any
+}
+
+function FileExplorerPanel(api: TuiPluginApi, session_id: string) {
+ const theme = () => api.theme.current
+ const files = createMemo(() => api.state.session.diff(session_id))
+
+ return (
+
+ File Explorer
+ 0}>
+
+ {(file) => (
+
+ {file.file}
+
+ +{file.additions}
+
+
+ -{file.deletions}
+
+
+ )}
+
+
+
+ )
+}
+
+function TasksPanel(api: TuiPluginApi, session_id: string) {
+ const theme = () => api.theme.current
+ const tasks = createMemo(() => api.state.session.task(session_id))
+
+ return (
+
+ Tasks
+ 0}>
+
+ {(task) => (
+
+ {task.summary || "Untitled"}
+ {task.status} {task.id}
+
+ )}
+
+
+
+ )
+}
+
+function TodoPanel(api: TuiPluginApi, session_id: string) {
+ const theme = () => api.theme.current
+ const todos = createMemo(() => api.state.session.todo(session_id))
+
+ return (
+
+ Todo
+ 0}>
+
+ {(todo) => (
+
+ {todo.status === "completed" ? "✓" : "○"} {todo.content}
+
+ )}
+
+
+
+ )
+}
+
+function McpPanel(api: TuiPluginApi, session_id: string) {
+ const theme = () => api.theme.current
+ const mcp = createMemo(() => api.state.mcp())
+
+ return (
+
+ MCP Servers
+ 0}>
+
+ {(server) => (
+
+ {server.status === "connected" ? "●" : "○"} {server.name}
+
+ )}
+
+
+
+ )
+}
+
+function LspPanel(api: TuiPluginApi, session_id: string) {
+ const theme = () => api.theme.current
+ const lsp = createMemo(() => api.state.lsp())
+
+ return (
+
+ LSP Servers
+ 0}>
+
+ {(server) => (
+
+ {server.status === "connected" ? "●" : "○"} {server.id}
+
+ )}
+
+
+
+ )
+}
+
+export const PANELS: PanelDef[] = [
+ { id: "files", icon: " ", label: "File Explorer", component: FileExplorerPanel },
+ { id: "tasks", icon: " ", label: "Tasks", component: TasksPanel },
+ { id: "todo", icon: "✓", label: "Todo", component: TodoPanel },
+ { id: "mcp", icon: " ", label: "MCP Servers", component: McpPanel },
+ { id: "lsp", icon: " ", label: "LSP Servers", component: LspPanel },
+]
+
+export function getPanelComponent(id: string): PanelDef | undefined {
+ return PANELS.find(p => p.id === id)
+}
diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/panels.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/panels.tsx
new file mode 100644
index 00000000..05e7789c
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/panels.tsx
@@ -0,0 +1,132 @@
+import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@mimo-ai/plugin/tui"
+import { createMemo, For, Show, createSignal } from "solid-js"
+
+const id = "mimocode-sidebar-panels"
+
+interface PanelDef {
+ id: string
+ icon: string
+ label: string
+}
+
+const PANELS: PanelDef[] = [
+ { id: "files", icon: " ", label: "File Explorer" },
+ { id: "skills", icon: "⚡", label: "Skills" },
+ { id: "plugins", icon: " ", label: "Plugins" },
+ { id: "sessions", icon: " ", label: "Sessions" },
+ { id: "model", icon: " ", label: "Model" },
+]
+
+function PanelSection(props: {
+ api: TuiPluginApi
+ panel: PanelDef
+ session_id: string
+}) {
+ const [open, setOpen] = createSignal(true)
+ const theme = () => props.api.theme.current
+
+ const content = createMemo(() => {
+ switch (props.panel.id) {
+ case "files":
+ return props.api.state.session.diff(props.session_id)
+ case "skills":
+ return props.api.state.session.skill?.() ?? []
+ case "plugins":
+ return props.api.state.session.plugin?.() ?? []
+ case "sessions":
+ return Object.values(props.api.state.session ?? {})
+ case "model":
+ return props.api.state.session.model?.() ?? []
+ default:
+ return []
+ }
+ })
+
+ const hasContent = createMemo(() => {
+ const c = content()
+ return Array.isArray(c) ? c.length > 0 : !!c
+ })
+
+ return (
+
+
+ setOpen((x) => !x)}
+ >
+ {open() ? "▼" : "▶"}
+
+ {props.panel.icon} {props.panel.label}
+
+
+
+
+ {props.panel.id === "files" && (
+ }>
+ {(item) => (
+
+ {item.file}
+
+ +{item.additions}
+
+
+ -{item.deletions}
+
+
+ )}
+
+ )}
+ {props.panel.id === "sessions" && (
+ )}>
+ {(session) => (
+
+ {session.title || "Untitled"}
+ {session.id.slice(0, 8)}
+
+ )}
+
+ )}
+ {props.panel.id === "model" && (
+
+ {(content() as { name?: string })?.name || "No model"}
+
+ )}
+
+
+
+
+ )
+}
+
+function View(props: { api: TuiPluginApi; session_id: string }) {
+ const theme = () => props.api.theme.current
+
+ return (
+
+
+ {(panel) => (
+
+ )}
+
+
+ )
+}
+
+const tui: TuiPlugin = async (api) => {
+ api.slots.register({
+ order: 100,
+ slots: {
+ sidebar_content(_ctx, props) {
+ return
+ },
+ },
+ })
+}
+
+const plugin: TuiPluginModule & { id: string } = {
+ id,
+ tui,
+}
+
+export default plugin
diff --git a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts
index 1b6cd996..eb485d91 100644
--- a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts
+++ b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts
@@ -10,6 +10,8 @@ import SidebarTask from "../feature-plugins/sidebar/task"
import SidebarTodo from "../feature-plugins/sidebar/todo"
import SidebarFiles from "../feature-plugins/sidebar/files"
import SidebarFooter from "../feature-plugins/sidebar/footer"
+import SidebarPanels from "../feature-plugins/sidebar/panels"
+import ActivityBarPanels from "../feature-plugins/activity-bar"
import PluginManager from "../feature-plugins/system/plugins"
import type { TuiPlugin, TuiPluginModule } from "@mimo-ai/plugin/tui"
@@ -31,5 +33,7 @@ export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [
SidebarTodo,
SidebarFiles,
SidebarFooter,
+ SidebarPanels,
+ ActivityBarPanels,
PluginManager,
]
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/activity-bar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/activity-bar.tsx
new file mode 100644
index 00000000..b890f432
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/activity-bar.tsx
@@ -0,0 +1,81 @@
+import { For, Show, createMemo } from "solid-js"
+import { useTheme } from "../../context/theme"
+import { TuiPluginRuntime } from "../../plugin"
+
+export type PanelID = string
+
+interface PanelDef {
+ id: PanelID
+ icon: string
+ label: string
+}
+
+const BUILTIN_PANELS: PanelDef[] = [
+ { id: "files", icon: " ", label: "File Explorer" },
+ { id: "tasks", icon: " ", label: "Tasks" },
+ { id: "todo", icon: "✓", label: "Todo" },
+ { id: "mcp", icon: " ", label: "MCP Servers" },
+ { id: "lsp", icon: " ", label: "LSP Servers" },
+]
+
+export function ActivityBar(props: {
+ sessionID: string
+ activePanels: PanelID[]
+ onTogglePanel: (panel: PanelID) => void
+ visible: boolean
+ onToggle: () => void
+}) {
+ const { theme } = useTheme()
+
+ const isActive = (id: PanelID) => props.activePanels.includes(id)
+
+ return (
+
+
+
+
+
+ {(panel) => (
+ props.onTogglePanel(panel.id)}
+ >
+
+ {panel.icon}
+
+
+ )}
+
+
+
+
+
+
+ {props.visible ? "◁" : "▷"}
+
+
+
+
+ )
+}
+
+export { BUILTIN_PANELS }
+export type { PanelDef }
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index c2a6f525..ec9394d7 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -64,6 +64,7 @@ import { DialogTimeline } from "./dialog-timeline"
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
import { DialogSessionRename } from "../../component/dialog-session-rename"
import { Sidebar } from "./sidebar"
+import { ActivityBar, type PanelID } from "./activity-bar"
import { SubagentFooter } from "./subagent-footer.tsx"
import { DialogSubagent } from "./dialog-subagent.tsx"
import { Flag } from "@/flag/flag"
@@ -181,15 +182,22 @@ export function Session() {
const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false)
const wide = createMemo(() => dimensions().width > 120)
+ const [activePanels, setActivePanels] = createSignal([])
+ const togglePanel = (panel: PanelID) => {
+ setActivePanels((prev) =>
+ prev.includes(panel) ? prev.filter((p) => p !== panel) : [...prev, panel]
+ )
+ }
const sidebarVisible = createMemo(() => {
if (session()?.parentID) return false
if (currentAgentID() !== "main") return false
if (sidebarOpen()) return true
+ if (activePanels().length > 0) return true
if (sidebar() === "auto" && wide()) return true
return false
})
const showTimestamps = createMemo(() => timestamps() === "show")
- const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
+ const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 45 : 0) - 4)
const providers = createMemo(() => Model.index(sync.data.provider))
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
@@ -1216,9 +1224,23 @@ export function Session() {
+ {
+ batch(() => {
+ const isVisible = sidebarVisible()
+ setSidebar(() => (isVisible ? "hide" : "auto"))
+ setSidebarOpen(!isVisible)
+ if (isVisible) setActivePanels([])
+ })
+ }}
+ />
-
+
-
+
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/file-explorer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/file-explorer.tsx
new file mode 100644
index 00000000..04451b92
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/file-explorer.tsx
@@ -0,0 +1,73 @@
+import { createMemo, createSignal, For, Show } from "solid-js"
+import { useTheme } from "../../../context/theme"
+import { useDirectory } from "@tui/context/directory"
+import fs from "fs/promises"
+import path from "path"
+
+interface FileEntry {
+ name: string
+ isDirectory: boolean
+ children?: FileEntry[]
+ expanded?: boolean
+}
+
+export function FileExplorerPanel() {
+ const { theme } = useTheme()
+ const directory = useDirectory()
+ const [files, setFiles] = createSignal([])
+ const [loading, setLoading] = createSignal(true)
+
+ const loadDir = async (dirPath: string): Promise => {
+ try {
+ const entries = await fs.readdir(dirPath, { withFileTypes: true })
+ const result: FileEntry[] = []
+ for (const entry of entries) {
+ if (entry.name.startsWith(".")) continue
+ if (entry.name === "node_modules") continue
+ result.push({
+ name: entry.name,
+ isDirectory: entry.isDirectory(),
+ })
+ }
+ return result.sort((a, b) => {
+ if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1
+ return a.name.localeCompare(b.name)
+ })
+ } catch {
+ return []
+ }
+ }
+
+ createMemo(async () => {
+ const dir = directory().split(":")[0].replace("~", process.env.HOME || "")
+ const entries = await loadDir(dir)
+ setFiles(entries)
+ setLoading(false)
+ })
+
+ return (
+
+
+ File Explorer
+
+ {directory()}
+
+
+ {(entry) => (
+
+
+ {entry.isDirectory ? " " : " "}
+
+
+ {entry.name}
+
+
+ )}
+
+
+
+ Loading...
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/index.ts b/packages/opencode/src/cli/cmd/tui/routes/session/panels/index.ts
new file mode 100644
index 00000000..b311c337
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/index.ts
@@ -0,0 +1,5 @@
+export { FileExplorerPanel } from "./file-explorer"
+export { TasksPanel } from "./tasks"
+export { TodoPanel } from "./todo"
+export { McpPanel } from "./mcp"
+export { LspPanel } from "./lsp"
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/lsp.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/lsp.tsx
new file mode 100644
index 00000000..4c376f8b
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/lsp.tsx
@@ -0,0 +1,39 @@
+import { createMemo, For, Show } from "solid-js"
+import { useTheme } from "../../../context/theme"
+import { useSync } from "@tui/context/sync"
+
+interface LspServer {
+ id: string
+ root: string
+ status: string
+}
+
+export function LspPanel() {
+ const { theme } = useTheme()
+ const sync = useSync()
+
+ const servers = createMemo(() => {
+ return sync.data.lsp.map((item) => ({
+ id: item.id,
+ root: item.root,
+ status: item.status,
+ }))
+ })
+
+ return (
+
+
+ LSP Servers
+
+ 0}>
+
+ {(server) => (
+
+ {server.status === "connected" ? "●" : "○"} {server.id}
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/mcp.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/mcp.tsx
new file mode 100644
index 00000000..2b002f8b
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/mcp.tsx
@@ -0,0 +1,41 @@
+import { createMemo, For, Show } from "solid-js"
+import { useTheme } from "../../../context/theme"
+import { useSync } from "@tui/context/sync"
+
+interface McpServer {
+ name: string
+ status: string
+ error?: string
+}
+
+export function McpPanel() {
+ const { theme } = useTheme()
+ const sync = useSync()
+
+ const servers = createMemo(() => {
+ return Object.entries(sync.data.mcp)
+ .sort(([a], [b]) => a.localeCompare(b))
+ .map(([name, item]) => ({
+ name,
+ status: item.status,
+ error: item.status === "failed" ? item.error : undefined,
+ }))
+ })
+
+ return (
+
+
+ MCP Servers
+
+ 0}>
+
+ {(server) => (
+
+ {server.status === "connected" ? "●" : "○"} {server.name}
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/model.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/model.tsx
new file mode 100644
index 00000000..f9601099
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/model.tsx
@@ -0,0 +1,27 @@
+import { createMemo, For, Show } from "solid-js"
+import { useTheme } from "@tui/context/theme"
+import { useSync } from "@tui/context/sync"
+
+export function ModelPanel() {
+ const { theme } = useTheme()
+ const sync = useSync()
+
+ const model = createMemo(() => sync.data.model)
+
+ return (
+
+
+ Model
+
+
+
+ {model()!.name || "Unknown"}
+ {model()!.provider || ""}
+
+
+
+ No model selected
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/plugins.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/plugins.tsx
new file mode 100644
index 00000000..68f990fa
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/plugins.tsx
@@ -0,0 +1,42 @@
+import { createMemo, For } from "solid-js"
+import { useTheme } from "@tui/context/theme"
+
+interface Plugin {
+ name: string
+ version: string
+ enabled: boolean
+}
+
+export function PluginsPanel() {
+ const { theme } = useTheme()
+
+ const plugins = createMemo(() => {
+ return [
+ { name: "opencode-puter-auth", version: "1.0.0", enabled: true },
+ { name: "@zenobius/opencode-skillful", version: "1.2.0", enabled: true },
+ { name: "@opencode-manager/memory", version: "0.9.0", enabled: true },
+ { name: "work-checkpoints", version: "1.0.0", enabled: false },
+ ]
+ })
+
+ return (
+
+
+ Plugins
+
+
+ {(plugin) => (
+
+
+ {plugin.name}
+
+ {plugin.enabled ? "●" : "○"}
+
+
+ v{plugin.version}
+
+ )}
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/sessions.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/sessions.tsx
new file mode 100644
index 00000000..65f236c8
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/sessions.tsx
@@ -0,0 +1,33 @@
+import { createMemo, For, Show } from "solid-js"
+import { useTheme } from "@tui/context/theme"
+import { useSync } from "@tui/context/sync"
+
+export function SessionsPanel() {
+ const { theme } = useTheme()
+ const sync = useSync()
+
+ const sessions = createMemo(() => {
+ return Object.values(sync.data.session ?? {})
+ })
+
+ return (
+
+
+ Sessions
+
+ 0}>
+
+ {(session) => (
+
+ {session.title || "Untitled"}
+ {session.id.slice(0, 8)}
+
+ )}
+
+
+
+ No sessions
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/skills.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/skills.tsx
new file mode 100644
index 00000000..01cd9d89
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/skills.tsx
@@ -0,0 +1,39 @@
+import { createMemo, For, Show } from "solid-js"
+import { useTheme } from "@tui/context/theme"
+
+interface Skill {
+ name: string
+ description: string
+ enabled: boolean
+}
+
+export function SkillsPanel() {
+ const { theme } = useTheme()
+
+ const skills = createMemo(() => {
+ return [
+ { name: "brainstorming", description: "Creative work planning", enabled: true },
+ { name: "systematic-debugging", description: "Bug investigation", enabled: true },
+ { name: "test-driven-development", description: "TDD workflow", enabled: false },
+ { name: "writing-plans", description: "Implementation planning", enabled: true },
+ ]
+ })
+
+ return (
+
+
+ Skills
+
+
+ {(skill) => (
+
+ {skill.name}
+
+ {skill.enabled ? "●" : "○"}
+
+
+ )}
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/tasks.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/tasks.tsx
new file mode 100644
index 00000000..e4aebf29
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/tasks.tsx
@@ -0,0 +1,42 @@
+import { createMemo, For, Show } from "solid-js"
+import { useTheme } from "../../../context/theme"
+import { useSync } from "@tui/context/sync"
+
+interface Task {
+ id: string
+ status: string
+ summary: string
+ owner?: string
+ ended_at?: string
+}
+
+export function TasksPanel() {
+ const { theme } = useTheme()
+ const sync = useSync()
+
+ const tasks = createMemo(() => {
+ const sessionData = sync.data.session
+ if (!sessionData) return []
+ return Object.values(sessionData).flatMap((session: any) =>
+ sync.data.task[session.id] ?? []
+ )
+ })
+
+ return (
+
+
+ Tasks
+
+ 0}>
+
+ {(task) => (
+
+ {task.summary || "Untitled"}
+ {task.status} {task.id}
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/panels/todo.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/panels/todo.tsx
new file mode 100644
index 00000000..e0aa1dc9
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/panels/todo.tsx
@@ -0,0 +1,38 @@
+import { createMemo, For, Show } from "solid-js"
+import { useTheme } from "../../../context/theme"
+import { useSync } from "@tui/context/sync"
+
+interface TodoItem {
+ content: string
+ status: string
+}
+
+export function TodoPanel() {
+ const { theme } = useTheme()
+ const sync = useSync()
+
+ const todos = createMemo(() => {
+ const sessionData = sync.data.session
+ if (!sessionData) return []
+ return Object.values(sessionData).flatMap((session: any) =>
+ sync.data.todo[session.id] ?? []
+ )
+ })
+
+ return (
+
+
+ Todo
+
+ 0}>
+
+ {(todo) => (
+
+ {todo.status === "completed" ? "✓" : "○"} {todo.content}
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
index 6d92752e..9525f4d5 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
@@ -1,14 +1,33 @@
import { useProject } from "@tui/context/project"
import { useSync } from "@tui/context/sync"
-import { createMemo, Show } from "solid-js"
+import { createMemo, For, Show } from "solid-js"
import { useTheme } from "../../context/theme"
import { useTuiConfig } from "../../context/tui-config"
import { InstallationChannel, InstallationVersion } from "@/installation/version"
import { TuiPluginRuntime } from "../../plugin"
-
import { getScrollAcceleration } from "../../util/scroll"
+import type { PanelID } from "./activity-bar"
+import {
+ FileExplorerPanel,
+ TasksPanel,
+ TodoPanel,
+ McpPanel,
+ LspPanel,
+} from "./panels"
+
+const PANEL_COMPONENTS: Record any> = {
+ files: FileExplorerPanel,
+ tasks: TasksPanel,
+ todo: TodoPanel,
+ mcp: McpPanel,
+ lsp: LspPanel,
+}
-export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
+export function Sidebar(props: {
+ sessionID: string
+ overlay?: boolean
+ activePanels?: PanelID[]
+}) {
const project = useProject()
const sync = useSync()
const { theme } = useTheme()
@@ -27,6 +46,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
return `${info.type}: ${info.name}`
}
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
+ const hasActivePanels = () => (props.activePanels?.length ?? 0) > 0
return (
@@ -51,37 +71,77 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
}}
>
-
-
-
- {session()!.title}
-
-
- {props.sessionID}
-
-
-
- ●{" "}
- {workspaceLabel()}
+
+
+ {(panelId) => {
+ const Component = PANEL_COMPONENTS[panelId]
+ if (Component) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+ )
+ }}
+
+
+
+
+
+
+
+ {session()!.title}
-
-
- {session()!.share!.url}
-
-
-
-
+
+ {props.sessionID}
+
+
+
+
+ ●
+ {" "}
+ {workspaceLabel()}
+
+
+
+ {session()!.share!.url}
+
+
+
+
+
-
+
• Open
diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts
index a7c4b551..a327c1e2 100644
--- a/packages/plugin/src/tui.ts
+++ b/packages/plugin/src/tui.ts
@@ -376,6 +376,15 @@ export type TuiHostSlotMap = {
sidebar_footer: {
session_id: string
}
+ activity_bar: {
+ session_id: string
+ active_panels: string[]
+ on_toggle: (panel_id: string) => void
+ }
+ sidebar_panel: {
+ session_id: string
+ panel_id: string
+ }
}
export type TuiSlotMap = {}> = TuiHostSlotMap & Slots