diff --git a/packages/plugin/src/tui/context.ts b/packages/plugin/src/tui/context.ts
index 616b266a28d..e1ba963131d 100644
--- a/packages/plugin/src/tui/context.ts
+++ b/packages/plugin/src/tui/context.ts
@@ -372,8 +372,6 @@ export interface KeymapCommand {
readonly aliases?: string[]
/** Keeps the slash command in the prompt and passes its raw input to run. */
readonly arguments?: true
- /** Hides the command from slash completion until its exact name is typed. */
- readonly secret?: true
}
/** Promotes the command in discovery UI. */
readonly suggested?: boolean | (() => boolean)
diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx
index dbf2a6299b8..377ebb598be 100644
--- a/packages/tui/src/app.tsx
+++ b/packages/tui/src/app.tsx
@@ -30,7 +30,7 @@ import {
batch,
Show,
} from "solid-js"
-import { createStore, unwrap } from "solid-js/store"
+import { createStore } from "solid-js/store"
import {
TuiLifecycleProvider,
TuiAppProvider,
@@ -62,7 +62,6 @@ import { useConnected } from "./component/use-connected"
import { DialogMcp } from "./component/dialog-mcp"
import { DialogStatus } from "./component/dialog-status"
import { DialogConfig } from "./component/dialog-config"
-import { DialogExperiments } from "./component/dialog-experiments"
import { DialogDebug } from "./component/dialog-debug"
import { DialogPair, type DialogPairCredentials } from "./component/dialog-pair"
import { DialogThemeList } from "./component/dialog-theme-list"
@@ -658,22 +657,8 @@ function App(props: { pair?: DialogPairCredentials }) {
category: "Session",
slash: { name: "new", aliases: ["clear"] },
run: () => {
- // With per-tab drafts, a new session is an explicit "this belongs
- // elsewhere" gesture: move the in-progress draft instead of leaving
- // a copy behind on the tab it came from.
- const carried = (() => {
- if (config.data.experimental?.tab_drafts !== true) return undefined
- const current = promptRef.current
- if (!current?.current.text) return undefined
- // Copy before reset: reset() merges an empty prompt into the same
- // underlying store object that unwrap exposes.
- const prompt = { ...unwrap(current.current) }
- current.reset()
- return prompt
- })()
route.navigate({
type: "home",
- prompt: carried,
location:
route.data.type === "session"
? (data.session.get(route.data.sessionID)?.location ?? location.ref)
@@ -885,19 +870,6 @@ function App(props: { pair?: DialogPairCredentials }) {
},
category: "System",
},
- {
- // Deliberately absent from the command palette; reachable only by the
- // secret /baldbeard incantation.
- name: "opencode.experiments",
- title: "Experiments",
- description: "look is my devrel meme face",
- palette: undefined,
- slash: { name: "baldbeard", secret: true as const },
- run: () => {
- dialog.replace(() => )
- },
- category: "System",
- },
{
name: "opencode.status",
title: "View status",
diff --git a/packages/tui/src/component/devtools-bar.tsx b/packages/tui/src/component/devtools-bar.tsx
index f6c387971e5..3b1fbe76ab5 100644
--- a/packages/tui/src/component/devtools-bar.tsx
+++ b/packages/tui/src/component/devtools-bar.tsx
@@ -13,6 +13,8 @@ import { useRoute } from "../context/route"
import { Keymap } from "../context/keymap"
import { useTheme, useThemes } from "../context/theme"
import { DevTools } from "../devtools"
+import { useDialog } from "../ui/dialog"
+import { DialogExperiments } from "./dialog-experiments"
import { usePlugin } from "../plugin/context"
import { errorMessage } from "../util/error"
@@ -27,6 +29,7 @@ export type RuntimeStatus = "normal" | "medium" | "high"
export function DevToolsBar() {
const client = useClient()
const config = useConfig()
+ const dialog = useDialog()
const data = useData()
const location = useLocation()
const route = useRoute()
@@ -405,6 +408,15 @@ export function DevToolsBar() {
+ {
+ close()
+ dialog.replace(() => )
+ }}
+ >
+ Experiments
+
diff --git a/packages/tui/src/component/dialog-experiments.tsx b/packages/tui/src/component/dialog-experiments.tsx
index dda144515f9..9db2a623618 100644
--- a/packages/tui/src/component/dialog-experiments.tsx
+++ b/packages/tui/src/component/dialog-experiments.tsx
@@ -16,13 +16,14 @@ export const experiments: Experiment[] = [
{
id: "tab_drafts",
title: "Per-tab prompt drafts",
- description: "Keep unsent prompt drafts on the tab where they were written. New session moves the current draft.",
+ description: "Keep unsent prompt drafts on the tab where they were written. New sessions start blank.",
},
]
export function DialogExperiments() {
const config = useConfig()
const toast = useToast()
+ const [selected, setSelected] = createSignal(0)
const [saving, setSaving] = createSignal(false)
const enabled = (experiment: Experiment) => config.data.experimental?.[experiment.id] === true
@@ -30,14 +31,15 @@ export function DialogExperiments() {
const options = createMemo(() =>
experiments.map((experiment, index) => ({
title: experiment.title,
- description: experiment.description,
category: "Experiments",
+ searchText: experiment.description,
footer: enabled(experiment) ? "on" : "off",
value: index,
})),
)
- async function toggle(index: number) {
+ // All experiments are booleans, so either direction toggles.
+ async function change(index = selected()) {
if (saving()) return
const experiment = experiments[index]
if (!experiment) return
@@ -56,8 +58,23 @@ export function DialogExperiments() {
void toggle(option.value)}
- footerHints={[{ title: "enter", label: "toggle" }]}
+ onMove={(option) => setSelected(option.value)}
+ onSelect={(option) => void change(option.value)}
+ footerHints={[{ title: "←/→", label: "change" }]}
+ bindings={[
+ {
+ bind: "left",
+ title: "Previous value",
+ group: "Experiments",
+ run: () => void change(),
+ },
+ {
+ bind: "right",
+ title: "Next value",
+ group: "Experiments",
+ run: () => void change(),
+ },
+ ]}
/>
)
}
diff --git a/packages/tui/src/component/prompt/autocomplete.tsx b/packages/tui/src/component/prompt/autocomplete.tsx
index e018fec3bcb..4600491f90c 100644
--- a/packages/tui/src/component/prompt/autocomplete.tsx
+++ b/packages/tui/src/component/prompt/autocomplete.tsx
@@ -512,9 +512,6 @@ export function Autocomplete(props: {
const results: AutocompleteOption[] = keymapCommands().flatMap((command) => {
const slash = command.slash
if (!slash) return []
- // Secret commands are incantations: absent from the "/" listing and from
- // fuzzy matching until the exact name is typed.
- if (slash.secret && search().toLowerCase() !== slash.name) return []
return {
display: `/${slash.name}`,
description: command.description ?? command.title,
diff --git a/packages/tui/src/context/keymap.tsx b/packages/tui/src/context/keymap.tsx
index 0cfa8737dcd..a7a28efa127 100644
--- a/packages/tui/src/context/keymap.tsx
+++ b/packages/tui/src/context/keymap.tsx
@@ -24,7 +24,6 @@ declare module "@opentui/keymap" {
name: string
aliases?: string[]
arguments?: true
- secret?: true
}
}
}