fix(tui): show session timestamps in listings

This commit is contained in:
cyq
2026-06-02 09:58:22 +08:00
committed by Hunter B
parent b1cc344d21
commit baba81cfb9
4 changed files with 73 additions and 3 deletions
+6 -1
View File
@@ -388,7 +388,7 @@ codewhale doctor --json # machine-readable diagnostics
codewhale setup --status # read-only setup status
codewhale setup --tools --plugins # scaffold tool/plugin dirs
codewhale models # list live API models
codewhale sessions # list saved sessions
codewhale sessions # list saved sessions with timestamps
codewhale resume --last # resume the most recent session in this workspace
codewhale resume <SESSION_ID> # resume a specific session by UUID
codewhale fork <SESSION_ID> # fork a saved session into a sibling path
@@ -414,6 +414,11 @@ id in metadata, and opens that fork so you can explore an alternate direction
without polluting the original path. The session picker and `codewhale sessions`
mark forked sessions with their parent id.
`codewhale sessions` lists saved sessions across workspaces and includes the
last-updated timestamp. `codewhale resume --last` and `codewhale --continue`
choose the latest session for the current workspace; pass an explicit session id
when resuming work from another directory.
Inside the TUI, Esc-Esc backtrack can rewind the active transcript to a prior
user prompt and put that prompt back in the composer for editing. `/restore`
and `revert_turn` are separate workspace rollback tools: they restore files
+27 -1
View File
@@ -957,6 +957,7 @@ fn truncate_title(s: &str, max_len: usize) -> String {
/// Format a session for display in a picker
pub fn format_session_line(meta: &SessionMetadata) -> String {
let age = format_age(&meta.updated_at);
let updated = format_session_updated_at(&meta.updated_at, &age);
let truncated_title = truncate_title(extract_title(&meta.title), 40);
let fork_label = meta
.parent_session_id
@@ -970,10 +971,14 @@ pub fn format_session_line(meta: &SessionMetadata) -> String {
truncated_title,
meta.message_count,
fork_label,
age
updated
)
}
pub(crate) fn format_session_updated_at(dt: &DateTime<Utc>, age: &str) -> String {
format!("{} ({age})", dt.format("%Y-%m-%d %H:%M UTC"))
}
/// Format a datetime as relative age
fn format_age(dt: &DateTime<Utc>) -> String {
let now = Utc::now();
@@ -1480,6 +1485,27 @@ mod tests {
assert_eq!(format_age(&day_ago), "3d ago");
}
#[test]
fn format_session_line_includes_absolute_updated_timestamp() {
let mut session = create_saved_session(
&[make_test_message("user", "Find Friday work")],
"test-model",
Path::new("/tmp/project"),
100,
None,
);
session.metadata.updated_at = DateTime::parse_from_rfc3339("2026-06-01T12:34:00Z")
.expect("timestamp")
.with_timezone(&Utc);
let line = format_session_line(&session.metadata);
assert!(
line.contains("2026-06-01 12:34 UTC"),
"session list should include an absolute timestamp, got {line:?}"
);
}
#[test]
fn test_update_session() {
let tmp = tempdir().expect("tempdir");
+35 -1
View File
@@ -692,7 +692,8 @@ fn build_list_lines(
}
fn format_session_line(session: &SessionMetadata) -> String {
let updated = format_relative_time(&session.updated_at);
let age = format_relative_time(&session.updated_at);
let updated = crate::session_manager::format_session_updated_at(&session.updated_at, &age);
let raw_title = extract_title(&session.title);
let title = if raw_title == "Session" {
truncate(crate::session_manager::truncate_id(&session.id), 32)
@@ -1111,6 +1112,39 @@ mod tests {
assert!(span.style.add_modifier.contains(Modifier::BOLD));
}
#[test]
fn build_list_lines_includes_absolute_updated_timestamp() {
let mut session = test_session(1, "last friday thread");
session.updated_at = DateTime::parse_from_rfc3339("2026-06-01T12:34:00Z")
.expect("timestamp")
.with_timezone(&Utc);
let lines = build_list_lines(
&[session],
0,
120,
0,
5,
false,
"",
"recent",
false,
false,
"",
None,
);
let rendered = lines
.iter()
.flat_map(|line| line.spans.iter())
.map(|span| span.content.as_ref())
.collect::<Vec<_>>()
.join("\n");
assert!(
rendered.contains("2026-06-01 12:34 UTC"),
"session picker should include an absolute timestamp, got {rendered:?}"
);
}
#[test]
fn build_list_lines_marks_fork_lineage() {
let mut forked = test_session(1, "forked path");
+5
View File
@@ -508,6 +508,11 @@ CodeWhale saves sessions. Use the session picker or resume/continue CLI paths
documented in the README and modes guide. For a risky experiment, fork the
session before changing direction.
The `/sessions` picker starts scoped to the current workspace so resumes stay
attached to the project you opened. Press `a` in the picker to show sessions
from every workspace, or run `codewhale sessions` to list all saved sessions
with last-updated timestamps before resuming a specific id.
### What should I do when the model gets confused?
Stop and restate the goal, constraints, and current evidence. If the transcript