fix(tui): show session timestamps in listings
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user