Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 17 additions & 42 deletions apps/native/.storybook/mocks/tauri-api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { storybookDarwinAPI, tauriEvent } from "./tauri-runtime";

export const DEFAULT_MAX_ITERATIONS = 25;

export const darwinAPI = storybookDarwinAPI;

export const EVOLVE_EVENT_CHANNEL = "darwin:evolve:event";
export const CONFIG_CHANGED_CHANNEL = "config:changed";

export const ipcRenderer = {
on: tauriEvent.listen,
once: tauriEvent.once,
Expand All @@ -15,33 +10,40 @@ export const ipcRenderer = {
// Re-export types from shared (Specta-generated)
export type {
BuildCheckResult,
Change,
ChangeType,
CliToolsState,
Config as DarwinConfig,
ConfigChangedEvent,
ConfigEditApplyResult,
Commit,
CommitResult,
DarwinApplyDataEvent,
DarwinApplyEndEvent,
DarwinApplySummaryEvent,
EvolutionFailureResult,
EvolutionResult,
EvolutionState,
EvolutionTelemetry,
EvolveCancelResult,
EvolveEvent,
EvolveEventType,
EvolveState,
EvolveStep,
EvolutionFailureResult,
EvolutionResult,
EvolutionState,
EvolutionTelemetry,
FeedbackAiProviderModelInfo,
FeedbackFlakeInputEntry,
FeedbackFlakeInputsSnapshot,
FeedbackMetadata,
FeedbackMetadataRequest,
FeedbackPanicDetails,
FeedbackShareOptions,
FeedbackSystemInfo,
FileEntry,
FeedbackUsageStats,
FinalizeApplyResult,
GitFileStatus,
GitStatus,
HomebrewState,
HistoryItem,
HomebrewState,
NixCheckResult,
NixDarwinRebuildEndEvent,
NixInstallEndEvent,
Expand All @@ -53,44 +55,17 @@ export type {
PermissionStatus,
PermissionsState,
PreviewIndicatorState,
RecommendedPrompt,
RebuildErrorType,
RecommendedPrompt,
RollbackResult,
RustPanicEvent,
SemanticChangeMap,
SetDirResult,
SummarizerUpdateEvent,
SummarizedChangeSet,
RustPanicEvent,
SummarizerUpdateEvent,
SystemDefault,
SystemDefaultsScan,
UiPrefs as DarwinPrefs,
UiPrefsUpdate as DarwinPrefsUpdate,
WatcherEvent,
Change,
Commit,
CommitResult,
ConfigEditApplyResult,
EvolveCancelResult,
FinalizeApplyResult,
RollbackResult,
} from "../../src/types/shared";

export interface FeedbackUsageStats {
totalEvolutions?: number;
successRate?: number;
avgIterations?: number;
lastComputedAt?: string;
extra?: Record<string, unknown>;
}

export interface FeedbackMetadata {
currentAppStateSnapshot?: unknown;
systemInfo?: import("../../src/types/shared").FeedbackSystemInfo;
usageStats?: FeedbackUsageStats;
evolutionLogContent?: string;
changedNixFilesDiff?: string;
aiProviderModelInfo?: import("../../src/types/shared").FeedbackAiProviderModelInfo;
buildErrorOutput?: string;
flakeInputsSnapshot?: import("../../src/types/shared").FeedbackFlakeInputsSnapshot;
appLogsContent?: string;
panicDetails?: import("../../src/types/shared").FeedbackPanicDetails;
}
2 changes: 0 additions & 2 deletions apps/native/.storybook/mocks/tauri-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,5 +461,3 @@ if (typeof window !== "undefined") {
transformCallback,
};
}

export { invoke, storybookDarwinAPI, tauriEvent };
2 changes: 1 addition & 1 deletion apps/native/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fancy-regex = "0.17.0"
toml = "1.0.3"
backtrace = "0.3"
clap = { version = "4", features = ["derive"] }
specta = { version = "=2.0.0-rc.22", features = ["derive"] }
specta = { version = "=2.0.0-rc.22", features = ["derive", "serde_json"] }
specta-typescript = "0.0.9"
rnix = "0.14.0"
serde_yaml = "0.9"
Expand Down
5 changes: 3 additions & 2 deletions apps/native/src-tauri/examples/specta_gen_ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ fn main() {
.register::<shared_types::Config>()
.register::<shared_types::FeedbackShareOptions>()
.register::<shared_types::FeedbackSystemInfo>()
.register::<shared_types::FeedbackUsageStats>()
.register::<shared_types::FeedbackMetadata>()
.register::<shared_types::FeedbackAiProviderModelInfo>()
.register::<shared_types::FeedbackFlakeInputEntry>()
.register::<shared_types::FeedbackFlakeInputsSnapshot>()
Expand Down Expand Up @@ -94,8 +96,7 @@ fn main() {
.register::<shared_types::PermissionsState>()
.register::<shared_types::SystemDefault>()
.register::<shared_types::SystemDefaultsScan>()
.register::<shared_types::RecommendedPrompt>()
.register::<shared_types::FileEntry>();
.register::<shared_types::RecommendedPrompt>();

let shared_output_path = "../src/types/shared.ts";

Expand Down
4 changes: 2 additions & 2 deletions apps/native/src-tauri/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ Provider abstraction layer for chat completions. **Not** evolve-specific.
### `commands/` — Tauri IPC handlers

Thin `#[tauri::command]` handlers that the frontend calls via `invoke()`.
Each file (config, apply, evolve, git, rollback, summarize, homebrew, feedback, ui_prefs, permissions, system_defaults, cli_tool, updater, editor, evolve_state, peek, debug) delegates to the corresponding backend module.
Each file (apply, cli_tool, config, debug, editor, evolve, evolve_state, feedback, git, homebrew, peek, permissions, rollback, summarize, system_defaults, ui_prefs, updater) delegates to the corresponding backend module. `helpers.rs` contains shared command utilities.

**Called by:** Tauri invoke handler in main.rs; primary callers are the TypeScript frontend
**Called by:** Tauri invoke handler in `main.rs`. All frontend calls go through `tauri-api.ts` in the TypeScript layer.

### `db/` — SQLite persistence

Expand Down
95 changes: 0 additions & 95 deletions apps/native/src-tauri/src/commands/apply.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use super::helpers::capture_err;
use crate::bootstrap::default_config;
use crate::storage::store;
use crate::system::nix;
use crate::{rebuild, shared_types};
use std::process::Command;
use tauri::AppHandle;

/// Starts a streaming darwin-rebuild switch operation.
Expand Down Expand Up @@ -53,72 +51,6 @@ pub async fn darwin_activate_store_path(
.map_err(|e| capture_err("darwin_activate_store_path", e))
}

/// Cancels an in-progress apply by stashing changes on a new branch and returning to the previous branch.
/// Does not kill the running darwin-rebuild process; process cancellation is not yet implemented.
#[tauri::command]
pub async fn darwin_apply_stream_cancel(app: AppHandle) -> Result<shared_types::OkResult, String> {
let dir = store::ensure_config_dir_exists(&app)
.map_err(|e| capture_err("darwin_apply_stream_cancel", e))?;

let output = Command::new("git")
.args(["add", "."])
.current_dir(&dir)
.env("PATH", nix::get_nix_path())
.output()
.map_err(|e| capture_err("darwin_apply_stream_cancel", e))?;
if !output.status.success() {
return Err(format!(
"Failed to add files to git: {}",
String::from_utf8_lossy(&output.stderr)
));
}

let date = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
let output = Command::new("git")
.args(["checkout", "-b", &format!("canceled-{}", date)])
.current_dir(&dir)
.env("PATH", nix::get_nix_path())
.output()
.map_err(|e| capture_err("darwin_apply_stream_cancel", e))?;
if !output.status.success() {
return Err(format!(
"Failed to checkout canceled commit: {}",
String::from_utf8_lossy(&output.stderr)
));
}

let commit_hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
let output = Command::new("git")
.args(["commit", "-m", &format!("Canceled commit: {}", commit_hash)])
.current_dir(&dir)
.env("PATH", nix::get_nix_path())
.output()
.map_err(|e| capture_err("darwin_apply_stream_cancel", e))?;
if !output.status.success() {
return Err(format!(
"Failed to commit canceled commit: {}",
String::from_utf8_lossy(&output.stderr)
));
}

// check out prev branch
let output = Command::new("git")
.args(["checkout", "-"])
.current_dir(&dir)
.env("PATH", nix::get_nix_path())
.output()
.map_err(|e| capture_err("darwin_apply_stream_cancel", e))?;
if !output.status.success() {
return Err(format!(
"Failed to checkout previous branch: {}",
String::from_utf8_lossy(&output.stderr)
));
}

// TODO: Implement actual cancellation by tracking the child process
Ok(shared_types::OkResult::yes())
}

/// Records build state and changeset after a successful darwin-rebuild switch.
#[tauri::command]
pub async fn finalize_apply(app: AppHandle) -> Result<shared_types::FinalizeApplyResult, String> {
Expand All @@ -139,27 +71,6 @@ pub async fn finalize_rollback(
.map_err(|e| capture_err("finalize_rollback", e))
}

#[tauri::command]
pub async fn flake_installed_apps(app: AppHandle) -> Result<Vec<serde_json::Value>, String> {
let dir = store::ensure_config_dir_exists(&app)
.map_err(|e| capture_err("flake_installed_apps", e))?;

let host = nix::determine_host_attr(&app)
.or_else(|| {
let hosts = nix::list_darwin_hosts(&dir).ok()?;
if hosts.len() == 1 {
Some(hosts[0].clone())
} else {
None
}
})
.ok_or_else(|| "Host attribute not found".to_string())?;

let apps = nix::evaluate_installed_apps(&dir, &host)
.map_err(|e| capture_err("flake_installed_apps", e))?;
Ok(apps)
}

#[tauri::command]
pub async fn nix_check() -> Result<shared_types::NixCheckResult, String> {
let installed = nix::is_nix_installed();
Expand Down Expand Up @@ -193,12 +104,6 @@ pub async fn nix_install_start(app: AppHandle) -> Result<shared_types::OkResult,
Ok(shared_types::OkResult::yes())
}

#[tauri::command]
pub async fn finalize_flake_lock(app: AppHandle) -> Result<shared_types::OkResult, String> {
default_config::finalize_flake_lock(&app)?;
Ok(shared_types::OkResult::yes())
}

/// Lists all darwinConfigurations defined in the flake.
#[tauri::command]
pub async fn flake_list_hosts(app: AppHandle) -> Result<Vec<String>, String> {
Expand Down
8 changes: 1 addition & 7 deletions apps/native/src-tauri/src/commands/editor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{editor, shared_types};
use crate::editor;
use tauri::AppHandle;

/// Read a file relative to the config directory.
Expand All @@ -17,12 +17,6 @@ pub async fn editor_write_file(
editor::write_file(&app, &rel_path, &content).await
}

/// List files in the config directory.
#[tauri::command]
pub async fn editor_list_files(app: AppHandle) -> Result<Vec<shared_types::FileEntry>, String> {
editor::list_files(&app).await
}

/// Start the nixd LSP server.
#[tauri::command]
pub async fn lsp_start(app: AppHandle) -> Result<(), String> {
Expand Down
6 changes: 0 additions & 6 deletions apps/native/src-tauri/src/commands/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ pub async fn git_status_and_cache(app: AppHandle) -> Result<shared_types::GitSta
Ok(status)
}

/// Returns the cached git status if available.
#[tauri::command]
pub async fn git_cached(app: AppHandle) -> Result<Option<shared_types::GitStatus>, String> {
git::cached(&app).map_err(|e| capture_err("git_cached", e))
}

/// Stages all changes and creates a commit with the given message.
#[tauri::command]
pub async fn git_commit(
Expand Down
5 changes: 0 additions & 5 deletions apps/native/src-tauri/src/commands/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,3 @@ pub async fn permissions_request(
.map_err(|e| capture_err("permissions_request", e))
}

/// Check if all required permissions are granted.
#[tauri::command]
pub async fn permissions_all_required_granted() -> Result<bool, String> {
Ok(permissions::all_required_permissions_granted())
}
10 changes: 10 additions & 0 deletions apps/native/src-tauri/src/commands/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,13 @@ pub fn relaunch_after_update(app: AppHandle) -> Result<(), String> {
// (it does not call std::process::exit directly), so we must return Ok here.
Ok(())
}

#[tauri::command]
pub async fn install_version(app: AppHandle, version: String) -> Result<(), String> {
crate::updater_pin::install_version(app, version).await
}

#[tauri::command]
pub async fn clear_pinned_version(app: AppHandle) -> Result<(), String> {
crate::updater_pin::clear_pinned_version(app).await
}
46 changes: 0 additions & 46 deletions apps/native/src-tauri/src/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ pub mod lsp;

use std::path::Path;

pub(crate) use crate::shared_types::FileEntry;
use tauri::AppHandle;

use crate::evolve::file_ops;
Expand Down Expand Up @@ -46,48 +45,3 @@ pub async fn write_file(app: &AppHandle, rel_path: &str, content: &str) -> Resul
std::fs::write(&full_path, content).map_err(|e| format!("Failed to write {}: {}", rel_path, e))
}

/// List files in the config directory recursively.
pub async fn list_files(app: &AppHandle) -> Result<Vec<FileEntry>, String> {
let config_dir = store::get_config_dir(app).map_err(|e| e.to_string())?;
let base = Path::new(&config_dir);

let mut entries = Vec::new();
collect_entries(base, base, &mut entries).map_err(|e| e.to_string())?;
entries.sort_by(|a, b| a.path.cmp(&b.path));
Ok(entries)
}

fn collect_entries(
base: &Path,
dir: &Path,
entries: &mut Vec<FileEntry>,
) -> Result<(), std::io::Error> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let name = entry.file_name().to_string_lossy().to_string();

// Skip hidden files/dirs and common noise
if name.starts_with('.') || name == "node_modules" || name == "result" {
continue;
}

let rel = path
.strip_prefix(base)
.unwrap_or(&path)
.to_string_lossy()
.to_string();

let is_dir = path.is_dir();
entries.push(FileEntry {
path: rel,
name,
is_dir,
});

if is_dir {
collect_entries(base, &path, entries)?;
}
}
Ok(())
}
Loading
Loading