@@ -7,6 +7,7 @@ import { Locale } from "@/util/locale"
77import { useProject } from "@tui/context/project"
88import { useTheme } from "../context/theme"
99import { useSDK } from "../context/sdk"
10+ import { useLocal } from "../context/local"
1011import { Flag } from "@opencode-ai/core/flag/flag"
1112import { DialogSessionRename } from "./dialog-session-rename"
1213import { createDebouncedSignal } from "../util/signal"
@@ -25,6 +26,7 @@ export function DialogSessionList() {
2526 const project = useProject ( )
2627 const { theme } = useTheme ( )
2728 const sdk = useSDK ( )
29+ const local = useLocal ( )
2830 const toast = useToast ( )
2931 const [ toDelete , setToDelete ] = createSignal < string > ( )
3032 const [ search , setSearch ] = createDebouncedSignal ( "" , 150 )
@@ -128,7 +130,10 @@ export function DialogSessionList() {
128130
129131 const [ browseOrder ] = createSignal < string [ ] > ( orderByRecency ( sync . data . session ) )
130132
133+ const RECENT_LIMIT = 5
134+
131135 const options = createMemo ( ( ) => {
136+ const enabled = Flag . OPENCODE_EXPERIMENTAL_SESSION_SWITCHING
132137 const today = new Date ( ) . toDateString ( )
133138 const sessionMap = new Map (
134139 sessions ( )
@@ -139,46 +144,74 @@ export function DialogSessionList() {
139144 const searchResult = searchResults ( )
140145 const displayOrder = searchResult ? orderByRecency ( searchResult ) : browseOrder ( )
141146
142- return displayOrder
143- . map ( ( id ) => sessionMap . get ( id ) )
144- . filter ( ( x ) => x !== undefined )
145- . map ( ( x ) => {
146- const workspace = x . workspaceID ? project . workspace . get ( x . workspaceID ) : undefined
147+ const dismissed = enabled ? new Set ( local . session . dismissedRecent ( ) ) : new Set < string > ( )
148+ const pinned = enabled ? local . session . pinned ( ) . filter ( ( id ) => sessionMap . has ( id ) ) : [ ]
149+ const pinnedSet = new Set ( pinned )
150+ const slotByID = enabled
151+ ? new Map < string , number > ( local . session . slots ( ) . map ( ( id , i ) => [ id , i + 1 ] ) )
152+ : new Map < string , number > ( )
147153
148- let footer : JSX . Element | string = ""
149- if ( Flag . OPENCODE_EXPERIMENTAL_WORKSPACES ) {
150- if ( x . workspaceID ) {
151- footer = workspace ? (
152- < WorkspaceLabel
153- type = { workspace . type }
154- name = { workspace . name }
155- status = { project . workspace . status ( x . workspaceID ) ?? "error" }
156- />
157- ) : (
158- < WorkspaceLabel type = "unknown" name = { x . workspaceID } status = "error" />
159- )
160- }
161- } else {
162- footer = Locale . time ( x . time . updated )
163- }
154+ const recent = enabled
155+ ? displayOrder . filter ( ( id ) => ! pinnedSet . has ( id ) && ! dismissed . has ( id ) ) . slice ( 0 , RECENT_LIMIT )
156+ : [ ]
157+ const recentSet = new Set ( recent )
164158
165- const date = new Date ( x . time . updated )
166- let category = date . toDateString ( )
167- if ( category === today ) {
168- category = "Today"
169- }
170- const isDeleting = toDelete ( ) === x . id
171- const status = sync . data . session_status ?. [ x . id ]
172- const isWorking = status ?. type === "busy" || status ?. type === "retry"
173- return {
174- title : isDeleting ? `Press ${ deleteHint ( ) } again to confirm` : x . title ,
175- bg : isDeleting ? theme . error : undefined ,
176- value : x . id ,
177- category,
178- footer,
179- gutter : isWorking ? ( ) => < Spinner /> : undefined ,
159+ function buildOption ( id : string , category : string ) {
160+ const x = sessionMap . get ( id )
161+ if ( ! x ) return undefined
162+ const workspace = x . workspaceID ? project . workspace . get ( x . workspaceID ) : undefined
163+
164+ let footer : JSX . Element | string = ""
165+ if ( Flag . OPENCODE_EXPERIMENTAL_WORKSPACES ) {
166+ if ( x . workspaceID ) {
167+ footer = workspace ? (
168+ < WorkspaceLabel
169+ type = { workspace . type }
170+ name = { workspace . name }
171+ status = { project . workspace . status ( x . workspaceID ) ?? "error" }
172+ />
173+ ) : (
174+ < WorkspaceLabel type = "unknown" name = { x . workspaceID } status = "error" />
175+ )
180176 }
177+ } else {
178+ footer = Locale . time ( x . time . updated )
179+ }
180+
181+ const isDeleting = toDelete ( ) === x . id
182+ const status = sync . data . session_status ?. [ x . id ]
183+ const isWorking = status ?. type === "busy" || status ?. type === "retry"
184+ const slot = slotByID . get ( x . id )
185+ const gutter = isWorking
186+ ? ( ) => < Spinner />
187+ : slot !== undefined
188+ ? ( ) => < text fg = { theme . accent } > { slot } </ text >
189+ : undefined
190+ return {
191+ title : isDeleting ? `Press ${ deleteHint ( ) } again to confirm` : x . title ,
192+ bg : isDeleting ? theme . error : undefined ,
193+ value : x . id ,
194+ category,
195+ footer,
196+ gutter,
197+ }
198+ }
199+
200+ const remaining = displayOrder
201+ . filter ( ( id ) => ! pinnedSet . has ( id ) && ! recentSet . has ( id ) )
202+ . map ( ( id ) => {
203+ const x = sessionMap . get ( id )
204+ if ( ! x ) return undefined
205+ const label = new Date ( x . time . updated ) . toDateString ( )
206+ return buildOption ( id , label === today ? "Today" : label )
181207 } )
208+ . filter ( ( x ) => x !== undefined )
209+
210+ return [
211+ ...pinned . map ( ( id ) => buildOption ( id , "Pinned" ) ) . filter ( ( x ) => x !== undefined ) ,
212+ ...recent . map ( ( id ) => buildOption ( id , "Recent" ) ) . filter ( ( x ) => x !== undefined ) ,
213+ ...remaining ,
214+ ]
182215 } )
183216
184217 onMount ( ( ) => {
@@ -203,6 +236,32 @@ export function DialogSessionList() {
203236 dialog . clear ( )
204237 } }
205238 actions = { [
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+ : [ ] ) ,
206265 {
207266 command : "session.delete" ,
208267 title : "delete" ,
0 commit comments