diff --git a/crates/ely_app/src/shell/internal_pages/history.rs b/crates/ely_app/src/shell/internal_pages/history.rs index 3230d6f..28a93d8 100644 --- a/crates/ely_app/src/shell/internal_pages/history.rs +++ b/crates/ely_app/src/shell/internal_pages/history.rs @@ -281,10 +281,18 @@ fn render_history_row( ) .child( div() + .flex() + .items_center() + .gap_2() .text_xs() - .truncate() .text_color(rgb(colors::MUTED)) - .child(entry.url().display_url()), + .child(div().min_w_0().truncate().child(entry.url().display_url())) + .child(div().text_color(rgb(colors::MUTED_SOFT)).child("-")) + .child( + div() + .text_color(rgb(colors::MUTED_SOFT)) + .child(visit_count_label(entry.visit_count())), + ), ), ) .child( @@ -317,3 +325,10 @@ fn render_history_row( ) .into_any_element() } + +fn visit_count_label(visit_count: u32) -> String { + match visit_count { + 1 => "1 visit".to_string(), + count => format!("{count} visits"), + } +} diff --git a/crates/ely_browser_core/src/state/history.rs b/crates/ely_browser_core/src/state/history.rs index a3192ea..d58f8fe 100644 --- a/crates/ely_browser_core/src/state/history.rs +++ b/crates/ely_browser_core/src/state/history.rs @@ -38,12 +38,22 @@ impl BrowserCore { return; } + let visited_at = SystemTime::now(); + if let Some(entry) = self.history_entries.iter_mut().find(|entry| { + entry.profile_id() == tab.profile_id() + && entry.space_id() == tab.space_id() + && entry.url() == tab.url() + }) { + entry.record_visit(tab.title(), visited_at); + return; + } + self.history_entries.push(HistoryEntry::new( tab.profile_id().clone(), tab.space_id().clone(), tab.title(), tab.url().clone(), - SystemTime::now(), + visited_at, )); } diff --git a/crates/ely_browser_core/tests/history.rs b/crates/ely_browser_core/tests/history.rs index ed3e39e..7ad9277 100644 --- a/crates/ely_browser_core/tests/history.rs +++ b/crates/ely_browser_core/tests/history.rs @@ -20,6 +20,54 @@ fn navigation_records_profile_and_space_history() -> Result<(), Box> assert_eq!(snapshot.history_entries[0].space_id(), &active_space_id); assert_eq!(snapshot.history_entries[0].title(), "example.com"); assert_eq!(snapshot.history_entries[0].url().as_str(), "https://example.com/research"); + assert_eq!(snapshot.history_entries[0].visit_count(), 1); + Ok(()) +} + +#[test] +fn repeated_history_visits_increment_count_in_active_context() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + core.open_tab(UrlText::parse("https://example.com/research")?); + core.open_tab(UrlText::parse("https://example.com/research")?); + + let snapshot = core.snapshot()?; + + assert_eq!(snapshot.history_entries.len(), 1); + assert_eq!(snapshot.history_entries[0].url().as_str(), "https://example.com/research"); + assert_eq!(snapshot.history_entries[0].visit_count(), 2); + Ok(()) +} + +#[test] +fn repeated_history_visits_stay_scoped_by_space_and_profile() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let work_space_id = core.snapshot()?.active_space_id; + let default_profile_id = core.snapshot()?.active_profile_id; + + core.open_tab(UrlText::parse("https://example.com/research")?); + + let research_space_id = core.create_space("Research", "R", 0xf54e00)?; + core.open_tab(UrlText::parse("https://example.com/research")?); + + core.select_space(&work_space_id)?; + let personal_profile_id = core.create_profile("Personal", 0x26251e, ProfileKind::Standard)?; + core.open_tab(UrlText::parse("https://example.com/research")?); + + core.select_profile(&default_profile_id)?; + let work_snapshot = core.snapshot()?; + assert_eq!(work_snapshot.history_entries.len(), 1); + assert_eq!(work_snapshot.history_entries[0].visit_count(), 1); + + core.select_space(&research_space_id)?; + let research_snapshot = core.snapshot()?; + assert_eq!(research_snapshot.history_entries.len(), 1); + assert_eq!(research_snapshot.history_entries[0].visit_count(), 1); + + core.select_space(&work_space_id)?; + core.select_profile(&personal_profile_id)?; + let personal_snapshot = core.snapshot()?; + assert_eq!(personal_snapshot.history_entries.len(), 1); + assert_eq!(personal_snapshot.history_entries[0].visit_count(), 1); Ok(()) } @@ -211,7 +259,8 @@ fn history_scoped_search_opens_recent_matching_entry() -> Result<(), Box Self { - Self { profile_id, space_id, title: title.into(), url, visited_at } + Self { profile_id, space_id, title: title.into(), url, visited_at, visit_count: 1 } + } + + pub fn record_visit(&mut self, title: impl Into, visited_at: SystemTime) { + self.title = title.into(); + self.visited_at = visited_at; + self.visit_count = self.visit_count.saturating_add(1); } #[must_use] @@ -47,4 +54,9 @@ impl HistoryEntry { pub fn visited_at(&self) -> SystemTime { self.visited_at } + + #[must_use] + pub fn visit_count(&self) -> u32 { + self.visit_count + } }