mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Fix resume picker timestamp labels and stability (#16822)
Problem: The resume picker used awkward "Created at" and "Updated at" headers, and its relative timestamps changed while navigating because they were recomputed on each redraw. Solution: Rename the headers to "Created" and "Updated", and anchor relative timestamp formatting to the picker load time so the displayed ages stay stable while browsing.
This commit is contained in:
committed by
GitHub
Unverified
parent
fb41a79f37
commit
4294031a93
@@ -395,11 +395,14 @@ fn spawn_app_server_page_loader(
|
||||
/// Returns the human-readable column header for the given sort key.
|
||||
fn sort_key_label(sort_key: ThreadSortKey) -> &'static str {
|
||||
match sort_key {
|
||||
ThreadSortKey::CreatedAt => "Created at",
|
||||
ThreadSortKey::UpdatedAt => "Updated at",
|
||||
ThreadSortKey::CreatedAt => "Created",
|
||||
ThreadSortKey::UpdatedAt => "Updated",
|
||||
}
|
||||
}
|
||||
|
||||
const CREATED_COLUMN_LABEL: &str = "Created";
|
||||
const UPDATED_COLUMN_LABEL: &str = "Updated";
|
||||
|
||||
/// RAII guard that ensures we leave the alt-screen on scope exit.
|
||||
struct AltScreenGuard<'a> {
|
||||
tui: &'a mut Tui,
|
||||
@@ -421,6 +424,7 @@ impl Drop for AltScreenGuard<'_> {
|
||||
struct PickerState {
|
||||
codex_home: PathBuf,
|
||||
requester: FrameRequester,
|
||||
relative_time_reference: Option<DateTime<Utc>>,
|
||||
pagination: PaginationState,
|
||||
all_rows: Vec<Row>,
|
||||
filtered_rows: Vec<Row>,
|
||||
@@ -579,6 +583,7 @@ impl PickerState {
|
||||
Self {
|
||||
codex_home,
|
||||
requester,
|
||||
relative_time_reference: None,
|
||||
pagination: PaginationState {
|
||||
next_cursor: None,
|
||||
num_scanned_files: 0,
|
||||
@@ -711,6 +716,7 @@ impl PickerState {
|
||||
}
|
||||
|
||||
fn start_initial_load(&mut self) {
|
||||
self.relative_time_reference = Some(Utc::now());
|
||||
self.reset_pagination();
|
||||
self.all_rows.clear();
|
||||
self.filtered_rows.clear();
|
||||
@@ -1186,7 +1192,11 @@ fn draw_picker(tui: &mut Tui, state: &PickerState) -> std::io::Result<()> {
|
||||
// Search line
|
||||
frame.render_widget_ref(search_line(state), search);
|
||||
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all);
|
||||
let metrics = calculate_column_metrics(
|
||||
&state.filtered_rows,
|
||||
state.show_all,
|
||||
state.relative_time_reference.unwrap_or_else(Utc::now),
|
||||
);
|
||||
|
||||
// Column headers and list
|
||||
render_column_headers(frame, columns, &metrics, state.sort_key);
|
||||
@@ -1387,9 +1397,8 @@ fn render_empty_state_line(state: &PickerState) -> Line<'static> {
|
||||
vec!["No sessions yet".italic().dim()].into()
|
||||
}
|
||||
|
||||
fn human_time_ago(ts: DateTime<Utc>) -> String {
|
||||
let now = Utc::now();
|
||||
let delta = now - ts;
|
||||
fn human_time_ago(ts: DateTime<Utc>, reference_now: DateTime<Utc>) -> String {
|
||||
let delta = reference_now - ts;
|
||||
let secs = delta.num_seconds();
|
||||
if secs < 60 {
|
||||
let n = secs.max(0);
|
||||
@@ -1422,17 +1431,17 @@ fn human_time_ago(ts: DateTime<Utc>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_updated_label(row: &Row) -> String {
|
||||
fn format_updated_label_at(row: &Row, reference_now: DateTime<Utc>) -> String {
|
||||
match (row.updated_at, row.created_at) {
|
||||
(Some(updated), _) => human_time_ago(updated),
|
||||
(None, Some(created)) => human_time_ago(created),
|
||||
(Some(updated), _) => human_time_ago(updated, reference_now),
|
||||
(None, Some(created)) => human_time_ago(created, reference_now),
|
||||
(None, None) => "-".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_created_label(row: &Row) -> String {
|
||||
fn format_created_label_at(row: &Row, reference_now: DateTime<Utc>) -> String {
|
||||
match row.created_at {
|
||||
Some(created) => human_time_ago(created),
|
||||
Some(created) => human_time_ago(created, reference_now),
|
||||
None => "-".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -1452,7 +1461,7 @@ fn render_column_headers(
|
||||
if visibility.show_created {
|
||||
let label = format!(
|
||||
"{text:<width$}",
|
||||
text = "Created at",
|
||||
text = CREATED_COLUMN_LABEL,
|
||||
width = metrics.max_created_width
|
||||
);
|
||||
spans.push(Span::from(label).bold());
|
||||
@@ -1461,7 +1470,7 @@ fn render_column_headers(
|
||||
if visibility.show_updated {
|
||||
let label = format!(
|
||||
"{text:<width$}",
|
||||
text = "Updated at",
|
||||
text = UPDATED_COLUMN_LABEL,
|
||||
width = metrics.max_updated_width
|
||||
);
|
||||
spans.push(Span::from(label).bold());
|
||||
@@ -1515,7 +1524,11 @@ struct ColumnVisibility {
|
||||
show_cwd: bool,
|
||||
}
|
||||
|
||||
fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics {
|
||||
fn calculate_column_metrics(
|
||||
rows: &[Row],
|
||||
include_cwd: bool,
|
||||
reference_now: DateTime<Utc>,
|
||||
) -> ColumnMetrics {
|
||||
fn right_elide(s: &str, max: usize) -> String {
|
||||
if s.chars().count() <= max {
|
||||
return s.to_string();
|
||||
@@ -1536,8 +1549,8 @@ fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics {
|
||||
}
|
||||
|
||||
let mut labels: Vec<(String, String, String, String)> = Vec::with_capacity(rows.len());
|
||||
let mut max_created_width = UnicodeWidthStr::width("Created at");
|
||||
let mut max_updated_width = UnicodeWidthStr::width("Updated at");
|
||||
let mut max_created_width = UnicodeWidthStr::width(CREATED_COLUMN_LABEL);
|
||||
let mut max_updated_width = UnicodeWidthStr::width(UPDATED_COLUMN_LABEL);
|
||||
let mut max_branch_width = UnicodeWidthStr::width("Branch");
|
||||
let mut max_cwd_width = if include_cwd {
|
||||
UnicodeWidthStr::width("CWD")
|
||||
@@ -1546,8 +1559,8 @@ fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics {
|
||||
};
|
||||
|
||||
for row in rows {
|
||||
let created = format_created_label(row);
|
||||
let updated = format_updated_label(row);
|
||||
let created = format_created_label_at(row, reference_now);
|
||||
let updated = format_updated_label_at(row, reference_now);
|
||||
let branch_raw = row.git_branch.clone().unwrap_or_default();
|
||||
let branch = right_elide(&branch_raw, /*max*/ 24);
|
||||
let cwd = if include_cwd {
|
||||
@@ -1967,7 +1980,8 @@ mod tests {
|
||||
state.scroll_top = 0;
|
||||
state.update_view_rows(/*rows*/ 3);
|
||||
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all);
|
||||
state.relative_time_reference = Some(now);
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all, now);
|
||||
|
||||
let width: u16 = 80;
|
||||
let height: u16 = 6;
|
||||
@@ -2273,7 +2287,8 @@ mod tests {
|
||||
|
||||
state.update_thread_names().await;
|
||||
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all);
|
||||
state.relative_time_reference = Some(now);
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all, now);
|
||||
|
||||
let width: u16 = 80;
|
||||
let height: u16 = 5;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
source: tui/src/resume_picker.rs
|
||||
expression: snapshot
|
||||
---
|
||||
Created at Updated at Branch CWD Conversation
|
||||
Created Updated Branch CWD Conversation
|
||||
16 minutes ago 42 seconds ago - - Fix resume picker timestamps
|
||||
> 1 hour ago 35 minutes ago - - Investigate lazy pagination cap
|
||||
2 hours ago 2 hours ago - - Explain the codebase
|
||||
|
||||
+3
-3
@@ -2,6 +2,6 @@
|
||||
source: tui/src/resume_picker.rs
|
||||
expression: snapshot
|
||||
---
|
||||
Created at Updated at Branch CWD Conversation
|
||||
> - 2 days ago - - Keep this for now
|
||||
- 3 days ago - - Named thread
|
||||
Created Updated Branch CWD Conversation
|
||||
> - 2 days ago - - Keep this for now
|
||||
- 3 days ago - - Named thread
|
||||
|
||||
Reference in New Issue
Block a user