From a15167c44ecd2cd7c620a4f85f7e3ce63940db02 Mon Sep 17 00:00:00 2001 From: Jake Date: Sat, 25 Apr 2026 23:43:00 +0100 Subject: [PATCH] =?UTF-8?q?feat(ux):=20dogfood=20pass=20=E2=80=94=20onboar?= =?UTF-8?q?ding,=20tasks,=20LLM=20chip,=20float=20popout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bundles a session of dogfood UX feedback plus the two Cursor Bugbot findings on the auto-titles branch. Onboarding (FirstRunPage): - Welcome leads with "Set up automatically"; system breakdown and the full model list move behind a "Choose manually" disclosure - Morning / evening / autostart modal copy trimmed to one short sentence each; CTAs shortened - "Corbie" autostart string reverted to "Kon" - Already-downloaded models are clickable in the picker so the Settings → About → Replay onboarding flow doesn't re-download - Autostart "No thanks" now does isEnabled() → disable() to actually remove the OS login item when replaying after a previous "Yes" Tasks page: - Bucket nav (All / Inbox / Today / Soon / Later) now a horizontal pill row; was stacking because nav was block-level - List sidebar sized to content via self-start max-h-full instead of stretching to viewport when sparse - Energy chip surfaces at opacity-60 when unset (was opacity-0, hidden until hover) so the affordance is discoverable - "Brain-Dead" energy label → "Zero" everywhere user-facing; enum stays brain_dead to avoid a destructive DB migration LLM status chip (llmStatus.svelte.ts + Dictation/Settings): - Chip no longer auto-warms when the engine isn't loaded; it's hidden unless ready / generating / loading / error - refreshLlmStatus takes { force: true } so post-load reconcile clears stale "warming"; ambient refreshes still preserve in-flight state - markError exported; failed loads surface "AI error" with detail rather than silently going to off - check_llm_model is the source of truth (replaces the bool-only get_llm_status path in the store) Float popout window: - Native decorations off — was stacking two titlebars + two close X's on KWin, one of which silently failed - ResizeHandles mounted outside the animate-float-enter wrapper so fixed-position handles anchor to the viewport, not the transformed root; secondary-windows capability gains core:window:allow-start-resize-dragging for tasks-float - GTK Utility WindowTypeHint applied pre-map (mirroring the preview window) so KWin Wayland honours always-on-top reliably - visible_on_all_workspaces(true) so the pinned tasks list follows workspace switches - togglePin does hide()+show()+focus() on re-pin to nudge the compositor into re-evaluating window state - Pop-out / Edit / Open viewer buttons hidden on Android via isAndroid() — the multi-window Tauri commands stub out there Build / Bugbot: - src-tauri Cargo.toml: whisper feature now chains whisper-vulkan, so the dev runner's --no-default-features --features whisper invocation actually pulls Vulkan acceleration instead of silently falling back to CPU-only - jsconfig.json's inherited "types": ["node"] fixed by adding @types/node; corresponding @ts-expect-error in vite.config.js removed now that process is a known global Verification: svelte-check + cargo check pass clean. Manual device-side validation still pending for float resize and replay autostart "No thanks" — those are the only remaining confidence items. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 18 ++ package.json | 1 + src-tauri/Cargo.toml | 23 ++- src-tauri/capabilities/secondary-windows.json | 1 + src-tauri/gen/schemas/capabilities.json | 2 +- src-tauri/src/commands/windows.rs | 40 +++- src/lib/components/EnergyChip.svelte | 29 +-- src/lib/components/ModelDownloader.svelte | 31 ++-- src/lib/pages/DictationPage.svelte | 31 +++- src/lib/pages/FirstRunPage.svelte | 172 +++++++++++------- src/lib/pages/HistoryPage.svelte | 25 ++- src/lib/pages/SettingsPage.svelte | 45 ++++- src/lib/pages/TasksPage.svelte | 46 +++-- src/lib/stores/llmStatus.svelte.ts | 76 ++++++-- src/routes/+layout.svelte | 2 +- src/routes/float/+layout@.svelte | 18 +- src/routes/float/+page.svelte | 16 +- vite.config.js | 1 - 18 files changed, 403 insertions(+), 174 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11b4dfe..09aa27b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/vite": "^4.2.1", "@tauri-apps/cli": "^2", + "@types/node": "^25.6.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "tailwindcss": "^4.2.1", @@ -1662,6 +1663,16 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3181,6 +3192,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", diff --git a/package.json b/package.json index 75d1deb..4f76c11 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/vite": "^4.2.1", "@tauri-apps/cli": "^2", + "@types/node": "^25.6.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "tailwindcss": "^4.2.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 185bd83..7ad6f4d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -10,13 +10,22 @@ name = "kon_lib" crate-type = ["staticlib", "cdylib", "rlib"] [features] -# Default build includes the Whisper backend. Disabling this feature -# also drops it from kon-transcription (see Cargo.toml in that crate) -# so a --no-default-features workspace build does not pull whisper-rs-sys. -# load_model_from_disk returns a runtime error for Engine::Whisper when -# this feature is off; Parakeet continues to work. +# Default build includes the Whisper backend with Vulkan GPU acceleration. +# +# Vulkan is chained into `whisper` (rather than being a separate top-level +# default) because Tauri's dev runner invokes +# `cargo run --no-default-features --features whisper`, which would +# otherwise drop the vulkan feature and silently fall back to CPU-only +# inference. The Bugbot finding is satisfied: desktop builds get GPU +# acceleration by default, while the workspace-level kon-transcription +# crate still keeps `whisper` and `whisper-vulkan` separable for +# CPU-only-capable targets that build the crate directly. +# +# `whisper-vulkan` is kept as an aliased explicit-opt-in for callers who +# want to spell out the dependency. default = ["whisper"] -whisper = ["kon-transcription/whisper"] +whisper = ["kon-transcription/whisper", "whisper-vulkan"] +whisper-vulkan = ["kon-transcription/whisper-vulkan"] [build-dependencies] tauri-build = { version = "2", features = [] } @@ -41,7 +50,7 @@ kon-llm = { path = "../crates/llm" } # autostart plugins are desktop-only — gated below under # cfg(not(target_os = "android")). The dialog, opener, and notification # plugins all support Android natively so live in the unconditional list. -tauri = { version = "2" } +tauri = { version = "2", features = [] } tauri-plugin-opener = "2" tauri-plugin-dialog = "2" # Phase 6 nudges: OS-native notifications via the frontend-owned nudge diff --git a/src-tauri/capabilities/secondary-windows.json b/src-tauri/capabilities/secondary-windows.json index f1cbf40..b3c244d 100644 --- a/src-tauri/capabilities/secondary-windows.json +++ b/src-tauri/capabilities/secondary-windows.json @@ -6,6 +6,7 @@ "permissions": [ "core:event:default", "core:window:allow-start-dragging", + "core:window:allow-start-resize-dragging", "core:window:allow-close", "core:window:allow-hide", "core:window:allow-show", diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index d7aa9d1..3a5b5b9 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"main":{"identifier":"main","description":"Main window capability for user-initiated app control.","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-always-on-top","core:window:allow-minimize","core:window:allow-toggle-maximize","core:window:allow-is-maximized","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus","opener:default","dialog:default","global-shortcut:allow-register","global-shortcut:allow-unregister","autostart:allow-enable","autostart:allow-disable","autostart:allow-is-enabled","notification:allow-is-permission-granted","notification:allow-request-permission","notification:allow-notify"]},"secondary-windows":{"identifier":"secondary-windows","description":"Narrow capability for passive secondary windows.","local":true,"windows":["tasks-float","transcript-viewer","transcription-preview"],"permissions":["core:event:default","core:window:allow-start-dragging","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus"]}} \ No newline at end of file +{"main":{"identifier":"main","description":"Main window capability for user-initiated app control.","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-start-dragging","core:window:allow-set-always-on-top","core:window:allow-minimize","core:window:allow-toggle-maximize","core:window:allow-is-maximized","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus","opener:default","dialog:default","global-shortcut:allow-register","global-shortcut:allow-unregister","autostart:allow-enable","autostart:allow-disable","autostart:allow-is-enabled","notification:allow-is-permission-granted","notification:allow-request-permission","notification:allow-notify"]},"secondary-windows":{"identifier":"secondary-windows","description":"Narrow capability for passive secondary windows.","local":true,"windows":["tasks-float","transcript-viewer","transcription-preview"],"permissions":["core:event:default","core:window:allow-start-dragging","core:window:allow-start-resize-dragging","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus"]}} \ No newline at end of file diff --git a/src-tauri/src/commands/windows.rs b/src-tauri/src/commands/windows.rs index b7d141f..ad1a0b7 100644 --- a/src-tauri/src/commands/windows.rs +++ b/src-tauri/src/commands/windows.rs @@ -58,14 +58,33 @@ pub async fn open_task_window(app: tauri::AppHandle) -> Result<(), String> { // custom frameless chrome drawn by the Titlebar component. let use_native_decorations = cfg!(target_os = "linux"); + // Built hidden so the GTK utility type hint can be applied pre-map on + // Linux — see the preview window for the same pattern. KWin/Mutter on + // Wayland reliably keep utility-class windows above normal windows + // (and respect runtime keep-above toggles for them); for normal + // windows the same hint requests are flaky post-map. + // + // Decorations are off for this window: the float route renders its own + // titlebar with the pin / close controls, and stacking native KDE + // decorations on top of that produced two titlebars and two close X's + // (the native one didn't even close the window because the in-page + // chrome captured the click first). Custom drag is wired via + // handleDragStart (startDragging) and ResizeHandles in the route. + let _ = use_native_decorations; // keep the OS detection for future use let mut builder = WebviewWindowBuilder::new(&app, "tasks-float", WebviewUrl::App("/float".into())) .title("Kon Tasks") .inner_size(480.0, 520.0) .min_inner_size(360.0, 480.0) .always_on_top(true) - .decorations(use_native_decorations) - .resizable(true); + // Pin across virtual desktops so users who flip workspaces + // mid-task don't lose the floating tasks list. Combined with + // always_on_top + utility hint, this matches what users + // expect from a "pin" button on KDE Plasma and GNOME. + .visible_on_all_workspaces(true) + .decorations(false) + .resizable(true) + .visible(false); // Inject preferences before Svelte mounts if let Some(script) = app.try_state::() { @@ -74,8 +93,23 @@ pub async fn open_task_window(app: tauri::AppHandle) -> Result<(), String> { } } - builder.build().map_err(|e| e.to_string())?; + let window = builder.build().map_err(|e| e.to_string())?; + // Apply the GTK Utility type hint before the window maps. On X11 and + // XWayland the hint is honoured immediately; on native Wayland-only + // compositors GTK uses the closest semantic equivalent. Must happen + // before show() per GTK3 docs. + #[cfg(target_os = "linux")] + { + use gdk::WindowTypeHint; + use gtk::prelude::GtkWindowExt; + if let Ok(gtk_window) = window.gtk_window() { + gtk_window.set_type_hint(WindowTypeHint::Utility); + } + } + + window.show().map_err(|e| e.to_string())?; + window.set_focus().map_err(|e| e.to_string())?; Ok(()) } diff --git a/src/lib/components/EnergyChip.svelte b/src/lib/components/EnergyChip.svelte index 2bda1ad..8666823 100644 --- a/src/lib/components/EnergyChip.svelte +++ b/src/lib/components/EnergyChip.svelte @@ -1,18 +1,21 @@