Add in-app micro-feedback prompts (#497)

This commit is contained in:
Gregory Schier
2026-07-04 23:21:53 -07:00
committed by GitHub
parent c833aeba78
commit 4ee080fa49
18 changed files with 377 additions and 16 deletions
@@ -0,0 +1,67 @@
use log::{debug, warn};
use serde::Serialize;
use tauri::{AppHandle, Runtime, is_dev};
use yaak_api::{ApiClientKind, yaak_api_client};
use yaak_common::platform::get_os_str;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct FeedbackPayload {
feature: String,
text: String,
app_version: String,
os: String,
}
/// Send explicit user feedback for a feature. Fire-and-forget: errors are
/// logged and swallowed so a failed send never surfaces to the user.
pub async fn send_feedback<R: Runtime>(app_handle: &AppHandle<R>, feature: String, text: String) {
let app_version = app_handle.package_info().version.to_string();
let payload = FeedbackPayload {
feature,
text,
app_version: app_version.clone(),
os: get_os_str().to_string(),
};
let client = match yaak_api_client(ApiClientKind::App, &app_version) {
Ok(c) => c,
Err(e) => {
debug!("Failed to build feedback client: {e:?}");
return;
}
};
let url = build_url("/app-feedback");
debug!(
"Sending feature feedback to {url}: feature={}, app_version={}, os={}, text_len={}",
payload.feature,
payload.app_version,
payload.os,
payload.text.len()
);
match client.post(&url).json(&payload).send().await {
Ok(resp) => {
let status = resp.status();
if status.is_success() {
debug!("Sent feature feedback with status {status}");
} else {
let body = resp
.text()
.await
.unwrap_or_else(|e| format!("<failed to read response body: {e:?}>"));
warn!("Failed to send feature feedback with status {status}: {body}");
}
}
Err(e) => warn!("Failed to send feature feedback: {e:?}"),
}
}
fn build_url(path: &str) -> String {
if is_dev() {
format!("http://localhost:9444/api/v1{path}")
} else {
format!("https://api.yaak.app/api/v1{path}")
}
}
+12
View File
@@ -65,6 +65,7 @@ use yaak_tls::find_client_certificate;
mod commands;
mod encoding;
mod error;
mod feedback;
mod git_ext;
mod git_watcher;
mod grpc;
@@ -292,6 +293,16 @@ async fn cmd_render_template<R: Runtime>(
Ok(result)
}
#[tauri::command]
async fn cmd_send_feedback<R: Runtime>(
app_handle: AppHandle<R>,
feature: String,
text: String,
) -> YaakResult<()> {
feedback::send_feedback(&app_handle, feature, text).await;
Ok(())
}
#[tauri::command]
async fn cmd_dismiss_notification<R: Runtime>(
window: WebviewWindow<R>,
@@ -1819,6 +1830,7 @@ pub fn run() {
cmd_delete_send_history,
cmd_dismiss_notification,
cmd_export_data,
cmd_send_feedback,
cmd_http_request_body,
cmd_http_response_body,
cmd_format_json,
@@ -1,13 +1,18 @@
use std::sync::{Arc, Mutex};
#[cfg(target_os = "linux")]
use std::time::Duration;
#[cfg(target_os = "linux")]
use log::{debug, warn};
use tauri::{AppHandle, Emitter, Runtime};
#[cfg(target_os = "linux")]
use tauri::Emitter;
use tauri::{AppHandle, Runtime};
pub const INITIAL_APPEARANCE_GLOBAL: &str = "__YAAK_INITIAL_APPEARANCE__";
pub const INITIAL_APPEARANCE_SOURCE_GLOBAL: &str = "__YAAK_INITIAL_APPEARANCE_SOURCE__";
pub const SYSTEM_APPEARANCE_CHANGE_EVENT: &str = "system_appearance_change";
#[cfg(target_os = "linux")]
const SYSTEM_APPEARANCE_POLL_INTERVAL: Duration = Duration::from_secs(1);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -42,6 +47,8 @@ impl InitialAppearanceSource {
#[derive(Clone)]
pub struct SystemAppearanceState {
// Only read by the Linux polling thread
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
last_appearance: Arc<Mutex<Option<Appearance>>>,
}