detached 0cc0036

core: sort sessions by last-modified instead of filename

ListSessions sorted by filename descending. Filenames embed the
creation timestamp, so /sessions, --continue, and the resume
picker were effectively ordered by when each session was first
created, not when the user last worked in it.

Sort by filesystem ModTime descending instead, with filename desc
as a stable tie-break. The session you most recently touched now
sits at the top of every picker and is what --continue resumes,
even when newer-but-idle sessions exist.
commit
0cc003639c1c133ba38c669538041381720fa1ba
author
patriceckhart · · 2026-04-28 17:13 UTC
parent
9f5f195
signature
unsigned
refs
tag: v0.1.40
1 file · +32 -6
M internal/core/session.go +32 -6
@@ -443,21 +443,47 @@ func sessionHasNoMessages(path string) bool {
443443 return true
444444 }
445445
446-// ListSessions returns session file paths for cwd, newest first.
446+// ListSessions returns session file paths for cwd, most-recently-
447+// modified first. Sorting on filesystem ModTime instead of the
448+// timestamp embedded in the filename means a long-running session
449+// the user actually returned to recently floats to the top of
450+// /sessions, /continue, and the resume picker, even when it was
451+// originally created days earlier than newer but idle sessions.
452+// Files with identical ModTime fall back to filename desc so the
453+// order stays stable across calls.
447454 func ListSessions(root, cwd string) []string {
448455 dir := SessionsDir(root, cwd)
449456 entries, err := os.ReadDir(dir)
450457 if err != nil {
451458 return nil
452459 }
453- var files []string
460+ type rec struct {
461+ path string
462+ mod time.Time
463+ }
464+ var files []rec
454465 for _, e := range entries {
455- if !e.IsDir() && strings.HasSuffix(e.Name(), ".jsonl") {
456- files = append(files, filepath.Join(dir, e.Name()))
466+ if e.IsDir() || !strings.HasSuffix(e.Name(), ".jsonl") {
467+ continue
457468 }
469+ p := filepath.Join(dir, e.Name())
470+ info, err := e.Info()
471+ if err != nil {
472+ continue
473+ }
474+ files = append(files, rec{path: p, mod: info.ModTime()})
475+ }
476+ sort.Slice(files, func(i, j int) bool {
477+ if !files[i].mod.Equal(files[j].mod) {
478+ return files[i].mod.After(files[j].mod)
479+ }
480+ return files[i].path > files[j].path
481+ })
482+ out := make([]string, 0, len(files))
483+ for _, r := range files {
484+ out = append(out, r.path)
458485 }
459- sort.Sort(sort.Reverse(sort.StringSlice(files)))
460- return files
486+ return out
461487 }
462488
463489 // AppendMessage writes a message to the session.
@@ -443,21 +443,47 @@ func sessionHasNoMessages(path string) bool {
443 return true443 return true
444 }444 }
445 445
446-// ListSessions returns session file paths for cwd, newest first.446+// ListSessions returns session file paths for cwd, most-recently-
447+// modified first. Sorting on filesystem ModTime instead of the
448+// timestamp embedded in the filename means a long-running session
449+// the user actually returned to recently floats to the top of
450+// /sessions, /continue, and the resume picker, even when it was
451+// originally created days earlier than newer but idle sessions.
452+// Files with identical ModTime fall back to filename desc so the
453+// order stays stable across calls.
447 func ListSessions(root, cwd string) []string {454 func ListSessions(root, cwd string) []string {
448 dir := SessionsDir(root, cwd)455 dir := SessionsDir(root, cwd)
449 entries, err := os.ReadDir(dir)456 entries, err := os.ReadDir(dir)
450 if err != nil {457 if err != nil {
451 return nil458 return nil
452 }459 }
453- var files []string460+ type rec struct {
461+ path string
462+ mod time.Time
463+ }
464+ var files []rec
454 for _, e := range entries {465 for _, e := range entries {
455- if !e.IsDir() && strings.HasSuffix(e.Name(), ".jsonl") {466+ if e.IsDir() || !strings.HasSuffix(e.Name(), ".jsonl") {
456- files = append(files, filepath.Join(dir, e.Name()))467+ continue
457 }468 }
469+ p := filepath.Join(dir, e.Name())
470+ info, err := e.Info()
471+ if err != nil {
472+ continue
473+ }
474+ files = append(files, rec{path: p, mod: info.ModTime()})
475+ }
476+ sort.Slice(files, func(i, j int) bool {
477+ if !files[i].mod.Equal(files[j].mod) {
478+ return files[i].mod.After(files[j].mod)
479+ }
480+ return files[i].path > files[j].path
481+ })
482+ out := make([]string, 0, len(files))
483+ for _, r := range files {
484+ out = append(out, r.path)
458 }485 }
459- sort.Sort(sort.Reverse(sort.StringSlice(files)))486+ return out
460- return files
461 }487 }
462 488
463 // AppendMessage writes a message to the session.489 // AppendMessage writes a message to the session.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close