okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
10e0f66
main
1// The session list in every state a person sees, for the shot gallery. A 2// plain test run draws each state and writes nothing. 3package tui 4 5import ( 6"time" 7 8"albedo/cli/internal/daemon" 9 10 tea"charm.land/bubbletea/v2" 11) 12 13func init () { 14enter := tea.KeyPressMsg {Code :tea .KeyEnter } 15ctrl := func (r rune ) tea.Msg {return tea.KeyPressMsg {Code :r ,Mod :tea .ModCtrl } } 16registerShots ("sessions" , 17shotState {"list" ,sessionsShot (nil )}, 18shotState {"filtering" ,sessionsShot (nil ,typedKeys ("warm" )... )}, 19shotState {"no-match" ,sessionsShot (nil ,typedKeys ("zzz" )... )}, 20shotState {"action-row" ,sessionsShot (func (m * SessionViewer ) {m .focus ("new" ) })}, 21shotState {"preview-loading" ,sessionsShot (func (m * SessionViewer ) {m .focus ("week" ) })}, 22shotState {"renaming" ,sessionsShot (nil ,ctrl ('r' ))}, 23shotState {"archive" ,sessionsShot (func (m * SessionViewer ) {m .OpenArchive () })}, 24shotState {"archive-confirm" ,sessionsShot (func (m * SessionViewer ) {m .OpenArchive () },ctrl ('d' ))}, 25shotState {"archive-empty" ,sessionsShot (func (m * SessionViewer ) { 26m .SetSessions ([]daemon.Session {{ID :"pin" ,Title :"Only live session" }},nil ) 27m .OpenArchive () 28 })}, 29shotState {"notice" ,sessionsShot (func (m * SessionViewer ) { 30m .notice = "could not rename: the daemon refused the name" 31 })}, 32shotState {"loading" ,func (width ,height int )string { 33m := NewSessionViewer ("/Users/dawn/proj/albedo" ) 34m .SetSize (width ,height ) 35return m .View () 36 }}, 37shotState {"empty" ,func (width ,height int )string { 38m := sessionsModel (width ,height ,nil ) 39m .SetSessions (nil ,nil ) 40return m .View () 41 }}, 42shotState {"confirm-enter" ,sessionsShot (func (m * SessionViewer ) {m .OpenArchive () },ctrl ('d' ),enter )}, 43 ) 44} 45 46// typedKeys is the keystrokes that type text into a picker. 47func typedKeys (text string ) []tea.Msg { 48var keys []tea.Msg 49for _ ,r := range text { 50keys = append (keys , tea.KeyPressMsg {Code :r ,Text :string (r )}) 51 } 52return keys 53} 54 55// sessionsModel is the list at a terminal size, with the fixtures every 56// state shares, and setup applied before any keys. 57func sessionsModel (width ,height int ,setup func (* SessionViewer ))SessionViewer { 58now := time .Date (2026 ,9 ,23 ,12 ,0 ,0 ,0 ,time .Local ) 59recent ,older := now .Add (- 5 * time .Minute ).Unix (),now .Add (- 3 * 24 * time .Hour ).Unix () 60m := viewerAt (now ) 61m .SetSize (width ,height ) 62m .Fetch = func (string ) tea.Cmd {return nil } 63m .SetSessions ([]daemon.Session { 64 {ID :"pin" ,Title :"Refactor the session viewer" ,Model :"claude-sonnet-4" ,Workspace :"/Users/dawn/proj/albedo" ,LastAssistantAt :& recent ,Pinned :true ,Cursor :& daemon.Cursor {},TranscriptCount :42 }, 65 {ID :"live" ,Title :"Fix flaky warm test" ,Model :"gpt-5" ,Workspace :"/Users/dawn/proj/albedo" ,LastAssistantAt :& recent ,Cursor :& daemon.Cursor {},TranscriptCount :9 }, 66 {ID :"week" ,Title :"Read the folder picker spec" ,Model :"claude-opus-4" ,Workspace :"/Users/dawn/proj/other" ,LastAssistantAt :& older ,TranscriptCount :3 }, 67 {ID :"old" ,Title :"Design notes for the archive" ,Model :"gpt-5" ,Workspace :"/Users/dawn/proj/albedo" ,Archived :true }, 68 },nil ) 69m .focus ("live" ) 70m .previews ["live" ]= & cachedPreview {stamp :recent ,SessionPreview : daemon.SessionPreview {Items : []daemon.PreviewItem { 71 {Type :"user" ,Preview :"why does the warm test flake under load" }, 72 {Type :"tool" ,Preview :"read" }, {Type :"tool" ,Preview :"read" }, {Type :"tool" ,Preview :"bash" }, 73 {Type :"assistant" ,Preview :"the cache ttl was racing the stamp" }, 74 }}} 75if setup != nil { 76setup (& m ) 77 } 78return m 79} 80 81// sessionsShot draws the list after setup and keys. 82func sessionsShot (setup func (* SessionViewer ),keys ... tea.Msg )func (width ,height int )string { 83return func (width ,height int )string { 84m := sessionsModel (width ,height ,setup ) 85for _ ,key := range keys { 86m ,_ = m .Update (key ) 87 } 88return m .View () 89 } 90}