feat(timer): wire VisualTimer to tasks with notifications and persistence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jake
2026-03-21 13:59:12 +00:00
parent f1ef171acb
commit 2fc4ee7087
11 changed files with 371 additions and 5 deletions

View File

@@ -26,6 +26,7 @@ tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-opener = "2"
tauri-plugin-dialog = "2"
tauri-plugin-global-shortcut = "2"
tauri-plugin-notification = "2"
# Serialisation
serde = { version = "1", features = ["derive"] }

View File

@@ -17,6 +17,7 @@
"opener:default",
"dialog:default",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister"
"global-shortcut:allow-unregister",
"notification:default"
]
}

View File

@@ -4,5 +4,6 @@ pub mod hardware;
pub mod history;
pub mod models;
pub mod tasks;
pub mod timer;
pub mod transcription;
pub mod windows;

View File

@@ -0,0 +1,56 @@
use serde::Serialize;
use crate::AppState;
use kon_storage::{clear_timer_state, get_timer_state, save_timer_state};
/// Serialisable timer state response.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TimerStateResponse {
pub task_id: String,
pub total_seconds: i64,
pub remaining_seconds: i64,
pub started_at: String,
pub paused: bool,
}
/// Save or update the active timer state.
#[tauri::command]
pub async fn save_timer(
state: tauri::State<'_, AppState>,
task_id: String,
total_seconds: i64,
remaining_seconds: i64,
paused: bool,
) -> Result<(), String> {
save_timer_state(&state.db, &task_id, total_seconds, remaining_seconds, paused)
.await
.map_err(|e| e.to_string())
}
/// Retrieve the active timer state (for context restoration on app restart).
#[tauri::command]
pub async fn get_timer(
state: tauri::State<'_, AppState>,
) -> Result<Option<TimerStateResponse>, String> {
let row = get_timer_state(&state.db)
.await
.map_err(|e| e.to_string())?;
Ok(row.map(|r| TimerStateResponse {
task_id: r.task_id,
total_seconds: r.total_seconds,
remaining_seconds: r.remaining_seconds,
started_at: r.started_at,
paused: r.paused,
}))
}
/// Clear the active timer (completed or cancelled).
#[tauri::command]
pub async fn clear_timer(
state: tauri::State<'_, AppState>,
) -> Result<(), String> {
clear_timer_state(&state.db)
.await
.map_err(|e| e.to_string())
}

View File

@@ -72,6 +72,7 @@ pub fn run() {
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_notification::init())
.setup(|app| {
// Initialise database (blocking in setup — runs once at startup)
let db_path = database_path();
@@ -154,6 +155,10 @@ pub fn run() {
commands::tasks::reorder_tasks_cmd,
commands::tasks::complete_task_cmd,
commands::tasks::uncomplete_task_cmd,
// Timer
commands::timer::save_timer,
commands::timer::get_timer,
commands::timer::clear_timer,
// Transcription
commands::transcription::transcribe_pcm,
commands::transcription::transcribe_file,