Skip to content

Commit 6a4ce54

Browse files
committed
feat(tui): gate session switching behind OPENCODE_EXPERIMENTAL_SESSION_SWITCHING
the new pinned/recent picker layout, quick-switch hotkeys, cycle-recent hotkeys, in-picker pin/toggle-recent actions, route MRU tracking, and discoverability tips are all gated on Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING. defaults off; opt in with OPENCODE_EXPERIMENTAL_SESSION_SWITCHING=true or the global OPENCODE_EXPERIMENTAL=true. pinned state is still loaded from session.json so toggling the flag is non-destructive.
1 parent 6cc810a commit 6a4ce54

5 files changed

Lines changed: 104 additions & 76 deletions

File tree

packages/core/src/flag/flag.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export const Flag = {
9696
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"],
9797
OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"),
9898
OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
99+
OPENCODE_EXPERIMENTAL_SESSION_SWITCHING:
100+
OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SESSION_SWITCHING"),
99101

100102
// Evaluated at access time (not module load) because tests, the CLI, and
101103
// external tooling set these env vars at runtime.

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -473,33 +473,37 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
473473
dialog.clear()
474474
},
475475
},
476-
{
477-
name: "session.cycle_recent",
478-
title: "Cycle to previous recent session",
479-
category: "Session",
480-
hidden: true,
481-
run: () => {
482-
local.session.cycleRecent(1)
483-
},
484-
},
485-
{
486-
name: "session.cycle_recent_reverse",
487-
title: "Cycle to next recent session",
488-
category: "Session",
489-
hidden: true,
490-
run: () => {
491-
local.session.cycleRecent(-1)
492-
},
493-
},
494-
...Array.from({ length: 9 }, (_, i) => ({
495-
name: `session.quick_switch.${i + 1}`,
496-
title: `Switch to session in quick slot ${i + 1}`,
497-
category: "Session",
498-
hidden: true,
499-
run: () => {
500-
local.session.quickSwitch(i + 1)
501-
},
502-
})),
476+
...(Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING
477+
? [
478+
{
479+
name: "session.cycle_recent",
480+
title: "Cycle to previous recent session",
481+
category: "Session",
482+
hidden: true,
483+
run: () => {
484+
local.session.cycleRecent(1)
485+
},
486+
},
487+
{
488+
name: "session.cycle_recent_reverse",
489+
title: "Cycle to next recent session",
490+
category: "Session",
491+
hidden: true,
492+
run: () => {
493+
local.session.cycleRecent(-1)
494+
},
495+
},
496+
...Array.from({ length: 9 }, (_, i) => ({
497+
name: `session.quick_switch.${i + 1}`,
498+
title: `Switch to session in quick slot ${i + 1}`,
499+
category: "Session",
500+
hidden: true,
501+
run: () => {
502+
local.session.quickSwitch(i + 1)
503+
},
504+
})),
505+
]
506+
: []),
503507
{
504508
name: "model.list",
505509
title: "Switch model",
@@ -814,7 +818,14 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
814818

815819
useBindings(() => ({
816820
enabled: command.matcher,
817-
bindings: tuiConfig.keybinds.gather("app", appBindingCommands),
821+
bindings: tuiConfig.keybinds.gather(
822+
"app",
823+
Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING
824+
? appBindingCommands
825+
: appBindingCommands.filter(
826+
(c) => !c.startsWith("session.cycle_recent") && !c.startsWith("session.quick_switch"),
827+
),
828+
),
818829
}))
819830

820831
useBindings(() => ({

packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export function DialogSessionList() {
133133
const RECENT_LIMIT = 5
134134

135135
const options = createMemo(() => {
136+
const enabled = Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING
136137
const today = new Date().toDateString()
137138
const sessionMap = new Map(
138139
sessions()
@@ -143,14 +144,16 @@ export function DialogSessionList() {
143144
const searchResult = searchResults()
144145
const displayOrder = searchResult ? orderByRecency(searchResult) : browseOrder()
145146

146-
const dismissed = new Set(local.session.dismissedRecent())
147-
const pinned = local.session.pinned().filter((id) => sessionMap.has(id))
147+
const dismissed = enabled ? new Set(local.session.dismissedRecent()) : new Set<string>()
148+
const pinned = enabled ? local.session.pinned().filter((id) => sessionMap.has(id)) : []
148149
const pinnedSet = new Set(pinned)
149-
const slotByID = new Map(local.session.slots().map((id, i) => [id, i + 1]))
150+
const slotByID = enabled
151+
? new Map<string, number>(local.session.slots().map((id, i) => [id, i + 1]))
152+
: new Map<string, number>()
150153

151-
const recent = displayOrder
152-
.filter((id) => !pinnedSet.has(id) && !dismissed.has(id))
153-
.slice(0, RECENT_LIMIT)
154+
const recent = enabled
155+
? displayOrder.filter((id) => !pinnedSet.has(id) && !dismissed.has(id)).slice(0, RECENT_LIMIT)
156+
: []
154157
const recentSet = new Set(recent)
155158

156159
function buildOption(id: string, category: string) {
@@ -233,28 +236,32 @@ export function DialogSessionList() {
233236
dialog.clear()
234237
}}
235238
actions={[
236-
{
237-
command: "session.pin.toggle",
238-
title: "pin/unpin",
239-
onTrigger: (option) => {
240-
local.session.togglePin(option.value)
241-
},
242-
},
243-
{
244-
command: "session.toggle.recent",
245-
title: "toggle recent",
246-
onTrigger: (option) => {
247-
if (local.session.isPinned(option.value)) {
248-
toast.show({
249-
variant: "info",
250-
message: "Unpin the session first to toggle it in Recent",
251-
duration: 3000,
252-
})
253-
return
254-
}
255-
local.session.toggleRecent(option.value)
256-
},
257-
},
239+
...(Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING
240+
? [
241+
{
242+
command: "session.pin.toggle",
243+
title: "pin/unpin",
244+
onTrigger: (option: { value: string }) => {
245+
local.session.togglePin(option.value)
246+
},
247+
},
248+
{
249+
command: "session.toggle.recent",
250+
title: "toggle recent",
251+
onTrigger: (option: { value: string }) => {
252+
if (local.session.isPinned(option.value)) {
253+
toast.show({
254+
variant: "info",
255+
message: "Unpin the session first to toggle it in Recent",
256+
duration: 3000,
257+
})
258+
return
259+
}
260+
local.session.toggleRecent(option.value)
261+
},
262+
},
263+
]
264+
: []),
258265
{
259266
command: "session.delete",
260267
title: "delete",

packages/opencode/src/cli/cmd/tui/context/local.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useEvent } from "@tui/context/event"
88
import { uniqueBy } from "remeda"
99
import path from "path"
1010
import { Global } from "@opencode-ai/core/global"
11+
import { Flag } from "@opencode-ai/core/flag/flag"
1112
import { iife } from "@/util/iife"
1213
import { useToast } from "../ui/toast"
1314
import { useArgs } from "./args"
@@ -470,22 +471,24 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
470471
prune(evt.properties.info.id)
471472
})
472473

473-
createEffect(
474-
on(
475-
() => (sessionStore.ready && route.data.type === "session" ? route.data.sessionID : undefined),
476-
(sessionID) => {
477-
if (!sessionID) return
478-
if (cycling) {
479-
cycling = false
480-
return
481-
}
482-
const filtered = sessionStore.recentOrder.filter((x) => x !== sessionID)
483-
const next = [sessionID, ...filtered].slice(0, 20)
484-
setSessionStore("recentOrder", next)
485-
save()
486-
},
487-
),
488-
)
474+
if (Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING) {
475+
createEffect(
476+
on(
477+
() => (sessionStore.ready && route.data.type === "session" ? route.data.sessionID : undefined),
478+
(sessionID) => {
479+
if (!sessionID) return
480+
if (cycling) {
481+
cycling = false
482+
return
483+
}
484+
const filtered = sessionStore.recentOrder.filter((x) => x !== sessionID)
485+
const next = [sessionID, ...filtered].slice(0, 20)
486+
setSessionStore("recentOrder", next)
487+
save()
488+
},
489+
),
490+
)
491+
}
489492

490493
return {
491494
get ready() {

packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createMemo, For } from "solid-js"
22
import { DEFAULT_THEMES, useTheme } from "@tui/context/theme"
3+
import { Flag } from "@opencode-ai/core/flag/flag"
34

45
const themeCount = Object.keys(DEFAULT_THEMES).length
56
const themeTip = `Use {highlight}/themes{/highlight} or {highlight}Ctrl+X T{/highlight} to switch between ${themeCount} built-in themes`
@@ -66,10 +67,14 @@ const TIPS = [
6667
themeTip,
6768
"Press {highlight}Ctrl+X N{/highlight} or {highlight}/new{/highlight} to start a fresh conversation session",
6869
"Use {highlight}/sessions{/highlight} or {highlight}Ctrl+X L{/highlight} to list and continue previous conversations",
69-
"Press {highlight}Ctrl+F{/highlight} in the session list to pin a session so it stays at the top",
70-
"Pinned and recent sessions are bound to {highlight}Ctrl+X 1{/highlight} through {highlight}Ctrl+X 9{/highlight} for one-press switching",
71-
"Press {highlight}Ctrl+X ]{/highlight} / {highlight}Ctrl+X [{/highlight} to cycle through recently visited sessions",
72-
"Press {highlight}Ctrl+H{/highlight} in the session list to show or hide a session in the Recent group",
70+
...(Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING
71+
? [
72+
"Press {highlight}Ctrl+F{/highlight} in the session list to pin a session so it stays at the top",
73+
"Pinned and recent sessions are bound to {highlight}Ctrl+X 1{/highlight} through {highlight}Ctrl+X 9{/highlight} for one-press switching",
74+
"Press {highlight}Ctrl+X ]{/highlight} / {highlight}Ctrl+X [{/highlight} to cycle through recently visited sessions",
75+
"Press {highlight}Ctrl+H{/highlight} in the session list to show or hide a session in the Recent group",
76+
]
77+
: []),
7378
"Run {highlight}/compact{/highlight} to summarize long sessions near context limits",
7479
"Press {highlight}Ctrl+X X{/highlight} or {highlight}/export{/highlight} to save the conversation as Markdown",
7580
"Press {highlight}Ctrl+X Y{/highlight} to copy the assistant's last message to clipboard",

0 commit comments

Comments
 (0)