Skip to main content

brows3r_lib/commands/
bookmarks_cmd.rs

1//! Tauri commands for bookmarks and recent locations.
2//!
3//! # Commands
4//!
5//! - [`bookmarks_list`]    — list all bookmarks for the caller.
6//! - [`bookmark_add`]      — add a new bookmark; returns the created record.
7//! - [`bookmark_remove`]   — remove a bookmark by id.
8//! - [`bookmark_update`]   — rename a bookmark.
9//! - [`recents_list`]      — list recent locations (newest first).
10//! - [`recent_track`]      — record a navigation (called after every pane move).
11//! - [`recents_clear`]     — clear all recent locations.
12//!
13//! # Validation gate
14//!
15//! `bookmarks_list` and `recents_list` return the full unfiltered list.  The
16//! **frontend** validation gate (per round-1 finding #9) is applied in the
17//! `<Bookmarks>` and `<Recents>` React components via `useValidatedProfile`.
18//! This keeps the data layer transport-agnostic and lets the UI render a
19//! disabled state for unvalidated profiles without an extra round-trip.
20//!
21//! # OCP
22//!
23//! Adding a new bookmark field = one new arm in `BookmarkPatch` + one line in
24//! `bookmark_update`.  No existing commands change.
25
26use tauri::State;
27
28use crate::{
29    bookmarks::{BookmarkInput, BookmarkPatch, BookmarkStoreHandle, RecentsHandle},
30    error::AppError,
31    ids::{BucketId, ProfileId},
32};
33
34// ---------------------------------------------------------------------------
35// bookmarks_list
36// ---------------------------------------------------------------------------
37
38/// Return all persisted bookmarks in insertion order.
39#[tauri::command]
40pub async fn bookmarks_list(
41    store: State<'_, BookmarkStoreHandle>,
42) -> Result<Vec<crate::bookmarks::Bookmark>, AppError> {
43    let guard = store.read().await;
44    Ok(guard.store.list())
45}
46
47// ---------------------------------------------------------------------------
48// bookmark_add
49// ---------------------------------------------------------------------------
50
51/// Add a new bookmark.  Returns the created record.
52#[tauri::command]
53pub async fn bookmark_add(
54    profile_id: ProfileId,
55    bucket: BucketId,
56    prefix: String,
57    label: Option<String>,
58    store: State<'_, BookmarkStoreHandle>,
59) -> Result<crate::bookmarks::Bookmark, AppError> {
60    let mut guard = store.write().await;
61    let path = guard.path.clone();
62    guard.store.add(
63        BookmarkInput {
64            profile_id,
65            bucket,
66            prefix,
67            label,
68        },
69        &path,
70    )
71}
72
73// ---------------------------------------------------------------------------
74// bookmark_remove
75// ---------------------------------------------------------------------------
76
77/// Remove a bookmark by `id`.
78///
79/// Returns `Ok(())` even when the id is not found — this matches the pattern
80/// established by `search_cancel` and avoids frontend races.
81#[tauri::command]
82pub async fn bookmark_remove(
83    id: String,
84    store: State<'_, BookmarkStoreHandle>,
85) -> Result<(), AppError> {
86    let mut guard = store.write().await;
87    let path = guard.path.clone();
88    guard.store.remove(&id, &path)?;
89    Ok(())
90}
91
92// ---------------------------------------------------------------------------
93// bookmark_update
94// ---------------------------------------------------------------------------
95
96/// Update mutable fields of a bookmark.
97///
98/// Returns `NotFound` when `id` does not match any stored bookmark.
99#[tauri::command]
100pub async fn bookmark_update(
101    id: String,
102    patch: BookmarkPatch,
103    store: State<'_, BookmarkStoreHandle>,
104) -> Result<crate::bookmarks::Bookmark, AppError> {
105    let mut guard = store.write().await;
106    let path = guard.path.clone();
107    guard.store.update(&id, patch, &path)
108}
109
110// ---------------------------------------------------------------------------
111// recents_list
112// ---------------------------------------------------------------------------
113
114/// Return recent locations, newest first.
115#[tauri::command]
116pub async fn recents_list(
117    handle: State<'_, RecentsHandle>,
118) -> Result<Vec<crate::bookmarks::RecentLocation>, AppError> {
119    let guard = handle.read().await;
120    Ok(guard.list())
121}
122
123// ---------------------------------------------------------------------------
124// recent_track
125// ---------------------------------------------------------------------------
126
127/// Record a navigation.  Called after every pane location change.
128///
129/// Always returns `Ok(())` — tracking failures must never surface to the user.
130#[tauri::command]
131pub async fn recent_track(
132    profile_id: ProfileId,
133    bucket: BucketId,
134    prefix: String,
135    handle: State<'_, RecentsHandle>,
136) -> Result<(), AppError> {
137    let mut guard = handle.write().await;
138    guard.track(profile_id, bucket, prefix);
139    Ok(())
140}
141
142// ---------------------------------------------------------------------------
143// recents_clear
144// ---------------------------------------------------------------------------
145
146/// Clear all recent locations and flush the empty list to disk.
147///
148/// Propagates the flush failure so the frontend's `clearMutation.onError`
149/// can surface it. Previously the flush was swallowed and a disk error
150/// would silently leave the on-disk list intact — the next launch would
151/// reload the "cleared" entries.
152#[tauri::command]
153pub async fn recents_clear(handle: State<'_, RecentsHandle>) -> Result<(), AppError> {
154    let mut guard = handle.write().await;
155    guard.clear();
156    guard.flush()
157}