From 1d94bdfbb0eb71d4b38978c6238924e60c6a8c35 Mon Sep 17 00:00:00 2001
From: Hikari
Date: Fri, 6 Feb 2026 21:49:13 -0800
Subject: [PATCH] feat: add close confirmation modal with minimize to tray
option
Implemented a confirmation modal when users try to close the application:
- Modal always shows with three options: Cancel, Minimize to Tray, Close Application
- Detects if Claude is actively running and shows appropriate warning message
- Removed minimize_to_tray config setting (no longer needed)
- Added core:window:allow-hide permission for window hiding
- Created CloseAppConfirmModal component with keyboard shortcuts (Escape to cancel)
- Added close_application command to properly exit the app
- Backend emits window-close-requested event for frontend to handle
This provides better UX by giving users clear choices every time they close,
preventing accidental closures during active work sessions.
---
src-tauri/capabilities/default.json | 3 +-
src-tauri/src/commands.rs | 13 ++
src-tauri/src/config.rs | 6 -
src-tauri/src/lib.rs | 18 +--
src-tauri/src/tray.rs | 20 ---
.../components/CloseAppConfirmModal.svelte | 116 ++++++++++++++++++
src/lib/components/ConfigSidebar.svelte | 16 ---
src/lib/components/StatusBar.svelte | 1 -
src/lib/stores/config.test.ts | 2 -
src/lib/stores/config.ts | 2 -
src/routes/+page.svelte | 65 ++++++++++
vitest.setup.ts | 1 -
12 files changed, 206 insertions(+), 57 deletions(-)
create mode 100644 src/lib/components/CloseAppConfirmModal.svelte
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index f8363c3..a7a7827 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -30,6 +30,7 @@
},
"core:window:allow-set-size",
"core:window:allow-set-always-on-top",
- "core:window:allow-inner-size"
+ "core:window:allow-inner-size",
+ "core:window:allow-hide"
]
}
diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs
index 1be5ccd..69c418c 100644
--- a/src-tauri/src/commands.rs
+++ b/src-tauri/src/commands.rs
@@ -1154,6 +1154,19 @@ pub async fn log_discord_rpc(
Ok(())
}
+#[tauri::command]
+pub async fn close_application(app_handle: AppHandle) -> Result<(), String> {
+ // Get the main window
+ if let Some(window) = app_handle.get_webview_window("main") {
+ // Hide the window first for a smoother close
+ let _ = window.hide();
+ }
+
+ // Exit the application
+ app_handle.exit(0);
+ Ok(())
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs
index b8fe217..f66086b 100644
--- a/src-tauri/src/config.rs
+++ b/src-tauri/src/config.rs
@@ -71,9 +71,6 @@ pub struct HikariConfig {
#[serde(default = "default_font_size")]
pub font_size: u32,
- #[serde(default)]
- pub minimize_to_tray: bool,
-
#[serde(default)]
pub streamer_mode: bool,
@@ -134,7 +131,6 @@ impl Default for HikariConfig {
update_checks_enabled: true,
character_panel_width: None,
font_size: 14,
- minimize_to_tray: false,
streamer_mode: false,
streamer_hide_paths: false,
compact_mode: false,
@@ -242,7 +238,6 @@ mod tests {
assert!(config.update_checks_enabled);
assert!(config.character_panel_width.is_none());
assert_eq!(config.font_size, 14);
- assert!(!config.minimize_to_tray);
assert!(!config.streamer_mode);
assert!(!config.streamer_hide_paths);
assert!(!config.compact_mode);
@@ -275,7 +270,6 @@ mod tests {
update_checks_enabled: true,
character_panel_width: Some(400),
font_size: 16,
- minimize_to_tray: true,
streamer_mode: false,
streamer_hide_paths: false,
compact_mode: false,
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 3fe20e9..d9a0a9d 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -33,11 +33,11 @@ use quick_actions::*;
use sessions::*;
use snippets::*;
use std::sync::Arc;
-use tauri::Manager;
+use tauri::{Emitter, Manager};
use temp_manager::create_shared_temp_manager;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
-use tray::{setup_tray, should_minimize_to_tray};
+use tray::setup_tray;
use vbs_notification::*;
use windows_toast::*;
use wsl_notifications::*;
@@ -89,17 +89,18 @@ pub fn run() {
eprintln!("Failed to set up system tray: {}", e);
}
- // Handle window close event for minimize to tray
+ // Handle window close event for minimize to tray and close confirmation
let main_window = app.get_webview_window("main").unwrap();
main_window.on_window_event({
let app_handle = app.handle().clone();
move |event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
- if should_minimize_to_tray(&app_handle) {
- api.prevent_close();
- if let Some(window) = app_handle.get_webview_window("main") {
- let _ = window.hide();
- }
+ // Always prevent default close - let frontend handle it
+ api.prevent_close();
+
+ // Emit event to frontend to show confirmation modal
+ if let Some(window) = app_handle.get_webview_window("main") {
+ let _ = window.emit("window-close-requested", ());
}
}
}
@@ -194,6 +195,7 @@ pub fn run() {
update_discord_rpc,
stop_discord_rpc,
log_discord_rpc,
+ close_application,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs
index 9f87b0b..4532ac7 100644
--- a/src-tauri/src/tray.rs
+++ b/src-tauri/src/tray.rs
@@ -4,8 +4,6 @@ use tauri::{
AppHandle, Manager,
};
-use crate::config::HikariConfig;
-
pub fn setup_tray(app: &AppHandle) -> tauri::Result<()> {
let show_item = MenuItem::with_id(app, "show", "Show Hikari", true, None::<&str>)?;
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
@@ -48,21 +46,3 @@ pub fn setup_tray(app: &AppHandle) -> tauri::Result<()> {
Ok(())
}
-
-pub fn should_minimize_to_tray(app: &AppHandle) -> bool {
- let config_path = app
- .path()
- .app_config_dir()
- .ok()
- .map(|p| p.join("hikari-config.json"));
-
- if let Some(path) = config_path {
- if let Ok(content) = std::fs::read_to_string(&path) {
- if let Ok(config) = serde_json::from_str::(&content) {
- return config.minimize_to_tray;
- }
- }
- }
-
- false
-}
diff --git a/src/lib/components/CloseAppConfirmModal.svelte b/src/lib/components/CloseAppConfirmModal.svelte
new file mode 100644
index 0000000..756f3b0
--- /dev/null
+++ b/src/lib/components/CloseAppConfirmModal.svelte
@@ -0,0 +1,116 @@
+
+
+
+
+{#if isOpen}
+ e.key === " " && onCancel()}
+ >
+
e.stopPropagation()}
+ onkeydown={(e) => e.stopPropagation()}
+ role="dialog"
+ aria-labelledby="confirm-title"
+ aria-describedby="confirm-message"
+ tabindex="-1"
+ >
+
+
+
+
+
+ Close Hikari Desktop?
+
+
+ {#if hasActiveConversation}
+ You have an active conversation with Claude. Are you sure you want to close the
+ application? Your conversation history will be saved, but any in-progress tasks will
+ be interrupted.
+ {:else}
+ Are you sure you want to close the application?
+ {/if}
+
+
+
+
+
+
+ Cancel
+
+
+ Minimize to Tray
+
+
+ Close Application
+
+
+
+
+
+{/if}
+
+
diff --git a/src/lib/components/ConfigSidebar.svelte b/src/lib/components/ConfigSidebar.svelte
index cd0f20e..2f3c94b 100644
--- a/src/lib/components/ConfigSidebar.svelte
+++ b/src/lib/components/ConfigSidebar.svelte
@@ -26,7 +26,6 @@
notifications_enabled: true,
notification_volume: 0.7,
always_on_top: false,
- minimize_to_tray: false,
update_checks_enabled: true,
character_panel_width: null,
font_size: 14,
@@ -728,21 +727,6 @@
-
-
-
-
- Minimize to system tray
-
-
- Hide to tray instead of closing when you click the X button
-
-
-
diff --git a/src/lib/components/StatusBar.svelte b/src/lib/components/StatusBar.svelte
index 4c4416d..af4a37b 100644
--- a/src/lib/components/StatusBar.svelte
+++ b/src/lib/components/StatusBar.svelte
@@ -70,7 +70,6 @@
update_checks_enabled: true,
character_panel_width: null,
font_size: 14,
- minimize_to_tray: false,
streamer_mode: false,
streamer_hide_paths: false,
compact_mode: false,
diff --git a/src/lib/stores/config.test.ts b/src/lib/stores/config.test.ts
index 16bdad9..8abcbd7 100644
--- a/src/lib/stores/config.test.ts
+++ b/src/lib/stores/config.test.ts
@@ -167,7 +167,6 @@ describe("config store", () => {
notifications_enabled: true,
notification_volume: 0.7,
always_on_top: false,
- minimize_to_tray: true,
update_checks_enabled: true,
character_panel_width: 300,
font_size: 14,
@@ -213,7 +212,6 @@ describe("config store", () => {
notifications_enabled: true,
notification_volume: 0.7,
always_on_top: false,
- minimize_to_tray: false,
update_checks_enabled: true,
character_panel_width: null,
font_size: 14,
diff --git a/src/lib/stores/config.ts b/src/lib/stores/config.ts
index 722f934..0c95945 100644
--- a/src/lib/stores/config.ts
+++ b/src/lib/stores/config.ts
@@ -27,7 +27,6 @@ export interface HikariConfig {
notifications_enabled: boolean;
notification_volume: number;
always_on_top: boolean;
- minimize_to_tray: boolean;
update_checks_enabled: boolean;
character_panel_width: number | null;
font_size: number;
@@ -60,7 +59,6 @@ const defaultConfig: HikariConfig = {
notifications_enabled: true,
notification_volume: 0.7,
always_on_top: false,
- minimize_to_tray: false,
update_checks_enabled: true,
character_panel_width: null,
font_size: 14,
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index e8ca5f0..08c6f66 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,6 +1,7 @@