CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / Python tests (push) Successful in 16s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Successful in 37s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m3s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m13s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Operator (note 2930): tags exist so you can *"filter during a search"*. The server has always been able to do that — `GET /api/notes` composes `?q=` with `?label=` and the rest into one AND-ed query. The frontend never reached it. The header search box navigated to `/search`, and that view called a DIFFERENT endpoint — `GET /api/notes/search?q=`, full text only, no facets at all. So the one screen you landed on when you searched was the one screen where you could not narrow by tag. Tag filtering lived on the board's FilterBar, which is where you weren't searching. Two search boxes, two endpoints, and only the hidden one did what tags are for. Now the header box writes `?q=` into the board's URL beside whatever labels are already there, and stays on the lens you're in — searching while looking at Trash searches Trash. The box READS from the URL rather than holding its own copy, so it stays in step with the Filters panel's Clear and with a saved view opened from the sidebar. Deleted: `SearchView.vue`, its route, `GET /api/notes/search`, `repo.notes.search` and both adapter implementations, and the `notes_search` Tauri command whose only caller was the adapter entry. FilterBar loses its own "Search text…" input — it was the same facet, hidden behind a collapsed panel, duplicating a box that is always on screen. Filters now does what its name says: narrowing. The header does searching. `core::store::search` STAYS. Android calls it through the FFI (`search_notes`) and has its own search surface — which has the same no-tag-filter gap the web just lost, and deserves the same fix on its own terms rather than as a rider here.
260 lines
9.0 KiB
Rust
260 lines
9.0 KiB
Rust
//! Tauri command surface for the local store — the offline mirror of the REST API
|
|
//! the frontend's repository seam calls. Each command locks the shared connection and
|
|
//! delegates to `store`; errors are stringified for the JS boundary. adapters/local.ts
|
|
//! (M10.5) invokes these.
|
|
|
|
use serde_json::Value;
|
|
use tauri::State;
|
|
|
|
use thoughtsync_core::local::models::*;
|
|
use thoughtsync_core::local::retention;
|
|
use thoughtsync_core::local::store;
|
|
use thoughtsync_core::local::Db;
|
|
use thoughtsync_core::sync::state;
|
|
|
|
// A macro would hide the (very regular) locking; kept explicit so each command reads
|
|
// as an obvious lock -> delegate -> stringify.
|
|
|
|
#[tauri::command]
|
|
pub fn config_get(db: State<'_, Db>) -> PublicConfig {
|
|
// What the Trash view counts down against: the linked server's window if we know
|
|
// it, else this device's own. Reading it here rather than hard-coding the offline
|
|
// default is what keeps the deadline on screen equal to the one that will actually
|
|
// be enforced. A store error falls back to the default rather than failing the
|
|
// call — the app must still boot.
|
|
let fallback = retention::LOCAL_RETENTION_DAYS;
|
|
let retention_days = match db.0.lock() {
|
|
Ok(conn) => state::effective_retention_days(&conn, fallback).unwrap_or(fallback),
|
|
Err(_) => fallback,
|
|
};
|
|
// Offline defaults: no signups, no server-side URL unfurling (needs network).
|
|
PublicConfig {
|
|
site_name: "ThoughtSync".to_string(),
|
|
allow_registration: false,
|
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
|
enable_url_unfurl: false,
|
|
trash_retention_days: retention_days.max(0) as u32,
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn auth_me() -> User {
|
|
// The single synthetic local user, so the auth-gated router resolves with no login.
|
|
User {
|
|
id: "local".to_string(),
|
|
email: "local@thoughtsync.app".to_string(),
|
|
display_name: "You".to_string(),
|
|
email_verified: true,
|
|
is_admin: false,
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_list(query: ListQuery, db: State<'_, Db>) -> Result<Vec<Note>, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::list_notes(&conn, &query).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_get(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::get_note(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_create(input: NoteCreateInput, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::create_note(&conn, &input).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_update(id: String, changes: Value, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::update_note(&conn, &id, &changes).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_complete_reminder(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::complete_reminder(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_snooze_reminder(id: String, minutes: i64, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::snooze_reminder(&conn, &id, minutes).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_set_labels(
|
|
id: String,
|
|
label_ids: Vec<String>,
|
|
db: State<'_, Db>,
|
|
) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::set_labels(&conn, &id, &label_ids).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_add_item(id: String, text: String, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::add_item(&conn, &id, &text).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_update_item(
|
|
id: String,
|
|
item_id: String,
|
|
changes: Value,
|
|
db: State<'_, Db>,
|
|
) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::update_item(&conn, &id, &item_id, &changes).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_delete_item(id: String, item_id: String, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::delete_item(&conn, &id, &item_id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_delete_attachment(
|
|
id: String,
|
|
att_id: String,
|
|
db: State<'_, Db>,
|
|
) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::delete_attachment(&conn, &id, &att_id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_delete_preview(
|
|
id: String,
|
|
preview_id: String,
|
|
db: State<'_, Db>,
|
|
) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::delete_preview(&conn, &id, &preview_id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_reorder(ordered_ids: Vec<String>, db: State<'_, Db>) -> Result<(), String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::reorder(&conn, &ordered_ids).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_trash(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::trash(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_restore(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::restore(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_delete_forever(id: String, db: State<'_, Db>) -> Result<(), String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::delete_forever(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_revisions(id: String, db: State<'_, Db>) -> Result<Vec<NoteRevision>, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::revisions(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_restore_revision(
|
|
id: String,
|
|
rev_id: String,
|
|
db: State<'_, Db>,
|
|
) -> Result<Note, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::restore_revision(&conn, &id, &rev_id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_reminders(db: State<'_, Db>) -> Result<Vec<Note>, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::reminders(&conn).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn notes_titles(db: State<'_, Db>) -> Result<Vec<TitleEntry>, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::titles(&conn).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn labels_list(db: State<'_, Db>) -> Result<Vec<Label>, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::list_labels(&conn).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn labels_create(name: String, db: State<'_, Db>) -> Result<Label, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::create_label(&conn, &name).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn labels_rename(id: String, name: String, db: State<'_, Db>) -> Result<Label, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::rename_label(&conn, &id, &name).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn labels_set_color(id: String, color: String, db: State<'_, Db>) -> Result<Label, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::set_label_color(&conn, &id, &color).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn labels_remove(id: String, db: State<'_, Db>) -> Result<(), String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::remove_label(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn labels_merge(source_id: String, into: String, db: State<'_, Db>) -> Result<Label, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::merge_labels(&conn, &source_id, &into).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn saved_filters_list(db: State<'_, Db>) -> Result<Vec<SavedFilter>, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::list_saved_filters(&conn).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn saved_filters_create(
|
|
name: String,
|
|
params: Value,
|
|
db: State<'_, Db>,
|
|
) -> Result<SavedFilter, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::create_saved_filter(&conn, &name, ¶ms).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn saved_filters_remove(id: String, db: State<'_, Db>) -> Result<(), String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::remove_saved_filter(&conn, &id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn saved_filters_rename(
|
|
id: String,
|
|
name: String,
|
|
db: State<'_, Db>,
|
|
) -> Result<SavedFilter, String> {
|
|
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
|
store::rename_saved_filter(&conn, &id, &name).map_err(|e| e.to_string())
|
|
}
|