agent_ui: Add timestamp to thread item in the sidebar (#51327)

Release Notes:

- N/A
This commit is contained in:
Danilo Leal authored and GitHub committed 2026-03-11 18:15:43 -03:00
1 parent b5aa635947
commit d97d33b31f
2 files changed
+143 -23

No files matched your search

+26
View File
@@ -1180,11 +1180,37 @@ impl Sidebar {
let id = SharedString::from(format!("thread-entry-{}", ix));
let timestamp = thread
.session_info
.created_at
.or(thread.session_info.updated_at)
.map(|entry_time| {
let now = Utc::now();
let duration = now.signed_duration_since(entry_time);
let minutes = duration.num_minutes();
let hours = duration.num_hours();
let days = duration.num_days();
let weeks = days / 7;
let months = days / 30;
if minutes < 60 {
format!("{}m", minutes.max(1))
} else if hours < 24 {
format!("{}h", hours)
} else if weeks < 4 {
format!("{}w", weeks.max(1))
} else {
format!("{}mo", months.max(1))
}
});
ThreadItem::new(id, title)
.icon(thread.icon)
.when_some(thread.icon_from_external_svg.clone(), |this, svg| {
this.custom_icon_from_external_svg(svg)
})
.when_some(timestamp, |this, ts| this.timestamp(ts))
.highlight_positions(thread.highlight_positions.to_vec())
.status(thread.status)
.notified(has_notification)
+117 -23
View File
@@ -245,6 +245,8 @@ impl RenderOnce for ThreadItem {
let removed_count = self.removed.unwrap_or(0);
let diff_stat_id = self.id.clone();
let has_worktree = self.worktree.is_some();
let has_timestamp = !self.timestamp.is_empty();
let timestamp = self.timestamp;
v_flex()
.id(self.id.clone())
@@ -253,13 +255,7 @@ impl RenderOnce for ThreadItem {
.overflow_hidden()
.cursor_pointer()
.w_full()
.map(|this| {
if has_worktree || has_diff_stats {
this.p_2()
} else {
this.p_1()
}
})
.p_1()
.when(self.selected, |s| s.bg(color.element_active))
.border_1()
.border_color(gpui::transparent_black())
@@ -310,23 +306,47 @@ impl RenderOnce for ThreadItem {
.gap_1p5()
.child(icon_container()) // Icon Spacing
.child(worktree_label)
.child(dot_separator())
.when(has_diff_stats || has_timestamp, |this| {
this.child(dot_separator())
})
.when(has_diff_stats, |this| {
this.child(DiffStat::new(
diff_stat_id.clone(),
added_count,
removed_count,
))
})
.when(has_diff_stats && has_timestamp, |this| {
this.child(dot_separator())
})
.when(has_timestamp, |this| {
this.child(
Label::new(timestamp.clone())
.size(LabelSize::Small)
.color(Color::Muted),
)
}),
)
})
.when(!has_worktree && has_diff_stats, |this| {
.when(!has_worktree && (has_diff_stats || has_timestamp), |this| {
this.child(
h_flex()
.min_w_0()
.gap_1p5()
.child(icon_container()) // Icon Spacing
.child(DiffStat::new(diff_stat_id, added_count, removed_count)),
.when(has_diff_stats, |this| {
this.child(DiffStat::new(diff_stat_id, added_count, removed_count))
})
.when(has_diff_stats && has_timestamp, |this| {
this.child(dot_separator())
})
.when(has_timestamp, |this| {
this.child(
Label::new(timestamp.clone())
.size(LabelSize::Small)
.color(Color::Muted),
)
}),
)
})
.when_some(self.on_click, |this, on_click| this.on_click(on_click))
@@ -349,21 +369,31 @@ impl Component for ThreadItem {
let thread_item_examples = vec![
single_example(
"Default",
"Default (minutes)",
container()
.child(
ThreadItem::new("ti-1", "Linking to the Agent Panel Depending on Settings")
.icon(IconName::AiOpenAi)
.timestamp("1:33 AM"),
.timestamp("15m"),
)
.into_any_element(),
),
single_example(
"Notified",
"Timestamp Only (hours)",
container()
.child(
ThreadItem::new("ti-1b", "Thread with just a timestamp")
.icon(IconName::AiClaude)
.timestamp("3h"),
)
.into_any_element(),
),
single_example(
"Notified (weeks)",
container()
.child(
ThreadItem::new("ti-2", "Refine thread view scrolling behavior")
.timestamp("12:12 AM")
.timestamp("1w")
.notified(true),
)
.into_any_element(),
@@ -373,7 +403,7 @@ impl Component for ThreadItem {
container()
.child(
ThreadItem::new("ti-2b", "Execute shell command in terminal")
.timestamp("12:15 AM")
.timestamp("2h")
.status(AgentThreadStatus::WaitingForConfirmation),
)
.into_any_element(),
@@ -383,7 +413,7 @@ impl Component for ThreadItem {
container()
.child(
ThreadItem::new("ti-2c", "Failed to connect to language server")
.timestamp("12:20 AM")
.timestamp("5h")
.status(AgentThreadStatus::Error),
)
.into_any_element(),
@@ -394,7 +424,7 @@ impl Component for ThreadItem {
.child(
ThreadItem::new("ti-3", "Add line numbers option to FileEditBlock")
.icon(IconName::AiClaude)
.timestamp("7:30 PM")
.timestamp("23h")
.status(AgentThreadStatus::Running),
)
.into_any_element(),
@@ -405,30 +435,43 @@ impl Component for ThreadItem {
.child(
ThreadItem::new("ti-4", "Add line numbers option to FileEditBlock")
.icon(IconName::AiClaude)
.timestamp("7:37 PM")
.timestamp("2w")
.worktree("link-agent-panel"),
)
.into_any_element(),
),
single_example(
"With Changes",
"With Changes (months)",
container()
.child(
ThreadItem::new("ti-5", "Managing user and project settings interactions")
.icon(IconName::AiClaude)
.timestamp("7:37 PM")
.timestamp("1mo")
.added(10)
.removed(3),
)
.into_any_element(),
),
single_example(
"Worktree + Changes + Timestamp",
container()
.child(
ThreadItem::new("ti-5b", "Full metadata example")
.icon(IconName::AiClaude)
.worktree("my-project")
.added(42)
.removed(17)
.timestamp("3w"),
)
.into_any_element(),
),
single_example(
"Selected Item",
container()
.child(
ThreadItem::new("ti-6", "Refine textarea interaction behavior")
.icon(IconName::AiGemini)
.timestamp("3:00 PM")
.timestamp("45m")
.selected(true),
)
.into_any_element(),
@@ -439,23 +482,74 @@ impl Component for ThreadItem {
.child(
ThreadItem::new("ti-7", "Implement keyboard navigation")
.icon(IconName::AiClaude)
.timestamp("4:00 PM")
.timestamp("12h")
.focused(true),
)
.into_any_element(),
),
single_example(
"Focused + Docked Right",
container()
.child(
ThreadItem::new("ti-7b", "Focused with right dock border")
.icon(IconName::AiClaude)
.timestamp("1w")
.focused(true)
.docked_right(true),
)
.into_any_element(),
),
single_example(
"Selected + Focused",
container()
.child(
ThreadItem::new("ti-8", "Active and keyboard-focused thread")
.icon(IconName::AiGemini)
.timestamp("5:00 PM")
.timestamp("2mo")
.selected(true)
.focused(true),
)
.into_any_element(),
),
single_example(
"Hovered with Action Slot",
container()
.child(
ThreadItem::new("ti-9", "Hover to see action button")
.icon(IconName::AiClaude)
.timestamp("6h")
.hovered(true)
.action_slot(
IconButton::new("delete", IconName::Trash)
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
),
)
.into_any_element(),
),
single_example(
"Search Highlight",
container()
.child(
ThreadItem::new("ti-10", "Implement keyboard navigation")
.icon(IconName::AiClaude)
.timestamp("4w")
.highlight_positions(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
)
.into_any_element(),
),
single_example(
"Worktree Search Highlight",
container()
.child(
ThreadItem::new("ti-11", "Search in worktree name")
.icon(IconName::AiClaude)
.timestamp("3mo")
.worktree("my-project-name")
.worktree_highlight_positions(vec![3, 4, 5, 6, 7, 8, 9, 10, 11]),
)
.into_any_element(),
),
];
Some(