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