Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 15s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m50s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m19s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Successful in 7m59s
Note 3127 §5 removed version tags, so an artifact's self-report is now the only
answer to "which build is this?" — and nothing exists to contradict it when it
is wrong. Three surfaces gain a dim build line: the foot of the web rail, the
login screen, and the foot of Sync on Android.
The login screen because "I can't sign in" is a bug report like any other, and
requiring an account to read a build number withholds it from exactly the people
who can't get past that page. `/api/config` is already public.
Two of the values it was going to show were wrong, which is the part worth
knowing about.
The DESKTOP reported `env!("CARGO_PKG_VERSION")` from `config_get` and from the
startup log. `cargo tauri build --config '{"version": ...}'` overrides
tauri.conf.json, not Cargo's own metadata — so both read the literal `0.2.0` in
Cargo.toml, on every build ever shipped. They now read a display version baked in
by the lane through `option_env!`, hoisted to the crate root because two readers
of one fact is how this repo keeps producing 2181-2183. Not the ordering key
either: `1.0.<minutes>` is the opaque value Tauri's updater compares and must
never be shown to a person, and `update.rs` still reads it because a comparator
is exactly what it is (rule 149).
The SERVER fell back to `__version__` when APP_VERSION was absent, so a server
run from a checkout reported `0.2.0` — a real-looking version naming no build
anybody could obtain. `__init__.py` already asserted the honest answer was
"APP_VERSION being missing, which app.py already handles"; it did not, and a
comment claiming a behaviour two files away is how that stayed true-sounding.
Now an explicit "unknown", with the packaging version left where "unknown" is
not a legal value.
Android reads the INSTALLED package's versionName rather than BuildConfig, so it
reports what is actually on the phone.
Everything renders "unknown" rather than blank when it cannot say. A blank looks
like a layout bug; a plausible default cannot be caught by anything.
build.rs gets `rerun-if-env-changed` for the baked value: cargo does not track an
`option_env!` variable on its own, and the desktop lane having no cache today is
what makes that easy to forget the day one is added.
262 lines
9.1 KiB
Rust
262 lines
9.1 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,
|
|
// The build a person reads, baked at compile time — see crate::display_version
|
|
// for why this is neither CARGO_PKG_VERSION nor the updater's ordering key.
|
|
version: crate::display_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())
|
|
}
|