diff --git a/.opencode/plugins/tui-smoke.tsx b/.opencode/plugins/tui-smoke.tsx
index 4552bed5aa..e41cf62366 100644
--- a/.opencode/plugins/tui-smoke.tsx
+++ b/.opencode/plugins/tui-smoke.tsx
@@ -636,28 +636,12 @@ const home = (input: Cfg): TuiSlotPlugin => ({
)
},
- home_tips(ctx, value) {
- if (!value.show_tips) return null
+ home_bottom(ctx) {
const skin = look(ctx.theme.current)
+ const text = "extra content in the unified home bottom slot"
return (
-
-
- {input.label} replaces the built-in home tips slot
-
-
- )
- },
- home_below_tips(ctx, value) {
- const skin = look(ctx.theme.current)
- const text = value.first_time_user
- ? "first-time user state"
- : value.tips_hidden
- ? "tips are hidden"
- : "extra content below tips"
-
- return (
-
+
+
+
+
+
+ )
+}
+
+const tui: TuiPlugin = async (api) => {
+ const hidden = createMemo(() => api.kv.get("tips_hidden", false))
+ const first = createMemo(() => api.state.session.count() === 0)
+ const show = createMemo(() => !first() && !hidden())
+
+ api.command.register(() => [
+ {
+ title: hidden() ? "Show tips" : "Hide tips",
+ value: "tips.toggle",
+ keybind: "tips_toggle",
+ category: "System",
+ hidden: api.route.current.name !== "home",
+ onSelect() {
+ api.kv.set("tips_hidden", !hidden())
+ api.ui.dialog.clear()
+ },
+ },
+ ])
+
+ api.slots.register({
+ order: 100,
+ slots: {
+ home_bottom() {
+ return
+ },
+ },
+ })
+}
+
+export default {
+ tui,
+}
diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx
index d8afb8badb..82076ba219 100644
--- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx
+++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx
@@ -131,6 +131,9 @@ function stateApi(sync: ReturnType): TuiApi["state"] {
return sync.data.provider
},
session: {
+ count() {
+ return sync.data.session.length
+ },
diff(sessionID) {
return sync.data.session_diff[sessionID] ?? []
},
diff --git a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts
index cf40e405f8..b09ee48c55 100644
--- a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts
+++ b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts
@@ -1,3 +1,4 @@
+import * as HomeTips from "../feature-plugins/home/tips"
import * as SidebarTitle from "../feature-plugins/sidebar/title"
import * as SidebarContext from "../feature-plugins/sidebar/context"
import * as SidebarMcp from "../feature-plugins/sidebar/mcp"
@@ -13,6 +14,10 @@ export type InternalTuiPlugin = {
}
export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [
+ {
+ name: "home-tips",
+ module: HomeTips,
+ },
{
name: "sidebar-title",
module: SidebarTitle,
diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
index 67a2acc92e..07549c6c29 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
@@ -1,9 +1,7 @@
import { Prompt, type PromptRef } from "@tui/component/prompt"
import { createEffect, createMemo, Match, on, onMount, Show, Switch } from "solid-js"
import { useTheme } from "@tui/context/theme"
-import { useKeybind } from "@tui/context/keybind"
import { Logo } from "../component/logo"
-import { Tips } from "../component/tips"
import { Locale } from "@/util/locale"
import { useSync } from "../context/sync"
import { Toast } from "../ui/toast"
@@ -12,8 +10,6 @@ import { useDirectory } from "../context/directory"
import { useRouteData } from "@tui/context/route"
import { usePromptRef } from "../context/prompt"
import { Installation } from "@/installation"
-import { useKV } from "../context/kv"
-import { useCommandDialog } from "../component/dialog-command"
import { useLocal } from "../context/local"
import { TuiPluginRuntime } from "../plugin"
@@ -22,11 +18,9 @@ let once = false
export function Home() {
const sync = useSync()
- const kv = useKV()
const { theme } = useTheme()
const route = useRouteData("home")
const promptRef = usePromptRef()
- const command = useCommandDialog()
const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0)
const mcpError = createMemo(() => {
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
@@ -36,27 +30,6 @@ export function Home() {
return Object.values(sync.data.mcp).filter((x) => x.status === "connected").length
})
- const isFirstTimeUser = createMemo(() => sync.data.session.length === 0)
- const tipsHidden = createMemo(() => kv.get("tips_hidden", false))
- const showTips = createMemo(() => {
- // Don't show tips for first-time users
- if (isFirstTimeUser()) return false
- return !tipsHidden()
- })
-
- command.register(() => [
- {
- title: tipsHidden() ? "Show tips" : "Hide tips",
- value: "tips.toggle",
- keybind: "tips_toggle",
- category: "System",
- onSelect: (dialog) => {
- kv.set("tips_hidden", !tipsHidden())
- dialog.clear()
- },
- },
- ])
-
const Hint = (
0}>
@@ -104,8 +77,6 @@ export function Home() {
)
const directory = useDirectory()
- const keybind = useKeybind()
-
return (
<>
@@ -127,25 +98,7 @@ export function Home() {
workspaceID={route.workspaceID}
/>
-
-
-
-
-
-
-
-
+
diff --git a/packages/opencode/test/cli/tui/plugin-lifecycle.test.ts b/packages/opencode/test/cli/tui/plugin-lifecycle.test.ts
index d80ed94cc9..2220416c99 100644
--- a/packages/opencode/test/cli/tui/plugin-lifecycle.test.ts
+++ b/packages/opencode/test/cli/tui/plugin-lifecycle.test.ts
@@ -81,20 +81,20 @@ test("disposes tracked event, route, and command hooks", async () => {
expect(count.event_drop).toBe(0)
expect(count.route_add).toBe(1)
expect(count.route_drop).toBe(0)
- expect(count.command_add).toBe(1)
+ expect(count.command_add).toBe(2)
expect(count.command_drop).toBe(1)
await TuiPluginRuntime.dispose()
expect(count.event_drop).toBe(1)
expect(count.route_drop).toBe(1)
- expect(count.command_drop).toBe(1)
+ expect(count.command_drop).toBe(2)
await TuiPluginRuntime.dispose()
expect(count.event_drop).toBe(1)
expect(count.route_drop).toBe(1)
- expect(count.command_drop).toBe(1)
+ expect(count.command_drop).toBe(2)
const marker = await fs.readFile(tmp.extra.marker, "utf8")
expect(marker).toContain("custom")
@@ -231,7 +231,7 @@ export default {
mark("two")
},
slots: {
- home_tips() {
+ home_bottom() {
return null
},
},
diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts
index f884ea7179..b0bb66ba30 100644
--- a/packages/opencode/test/fixture/tui-plugin.ts
+++ b/packages/opencode/test/fixture/tui-plugin.ts
@@ -244,6 +244,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
return opts.state?.provider ?? []
},
session: {
+ count: opts.state?.session?.count ?? (() => 0),
diff: opts.state?.session?.diff ?? (() => []),
todo: opts.state?.session?.todo ?? (() => []),
messages: opts.state?.session?.messages ?? (() => []),
diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts
index b92ece0542..07334098f3 100644
--- a/packages/plugin/src/tui.ts
+++ b/packages/plugin/src/tui.ts
@@ -212,6 +212,7 @@ export type TuiState = {
readonly config: SdkConfig
readonly provider: ReadonlyArray
session: {
+ count: () => number
diff: (sessionID: string) => ReadonlyArray
todo: (sessionID: string) => ReadonlyArray
messages: (sessionID: string) => ReadonlyArray
@@ -285,16 +286,7 @@ export type TuiSidebarFileItem = {
export type TuiSlotMap = {
app: {}
home_logo: {}
- home_tips: {
- show_tips: boolean
- tips_hidden: boolean
- first_time_user: boolean
- }
- home_below_tips: {
- show_tips: boolean
- tips_hidden: boolean
- first_time_user: boolean
- }
+ home_bottom: {}
sidebar_title: {
session_id: string
title: string