Skip to content

Commit 315609a

Browse files
committed
Refactor: Split 4 large files for clarity
- `friendly_error.rs` (1805 lines) → extract provider detection into `provider.rs` - `ai/manager.rs` → extract system memory measurement into top-level `system_memory.rs` - `AiSection.svelte` (1582 lines) → split into `AiCloudSection` + `AiLocalSection` - `+page.svelte` (1222→750 lines) → extract `command-dispatch.ts`, `mcp-listeners.ts`, `explorer-api.ts`
1 parent 2939bfe commit 315609a

20 files changed

Lines changed: 2979 additions & 2745 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ resilience, and common pitfalls.
159159
`var(--font-size-*)`, `var(--radius-*)`, `var(--font-*)`, and `var(--z-*)` tokens. Stylelint enforces this.
160160
- **Coverage allowlist is a last resort.** Extract pure functions and test them. Only allowlist what genuinely can't be
161161
tested. Name the specific untestable API in the reason.
162-
- When adding a new user-facing action, add it to `command-registry.ts` and `handleCommandExecute` in `+page.svelte`.
162+
- When adding a new user-facing action, add it to `command-registry.ts` and `handleCommandExecute` in `routes/(main)/command-dispatch.ts`.
163163
- If you added a new Tauri command touching the filesystem, check `docs/architecture.md` § Platform constraints.
164164
- We use [mise](https://mise.jdx.dev/) to manage tool versions (Go, Node, etc.), pinned in `.mise.toml`. Shims are on
165165
PATH via `~/.bashrc` and `~/.zshenv`, so `go` and `node` should just work. If `go` is "not found", check that

apps/desktop/coverage-allowlist.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@
3838
"file-explorer/pane/FilePane.svelte": { "reason": "Tested in integration.test.ts, complex component" },
3939
"file-explorer/pane/MtpConnectionView.svelte": { "reason": "MTP connection UI, depends on Tauri APIs" },
4040
"file-explorer/pane/NetworkMountView.svelte": { "reason": "Network mount UI, depends on Tauri APIs" },
41-
"file-explorer/pane/initialization.ts": {
42-
"reason": "Happy path covered by DualPaneExplorer tests; timeout and E2E override branches tested via E2E in CI"
43-
},
4441
"file-explorer/pane/rename-flow.svelte.ts": {
4542
"reason": "Extracted from FilePane, depends on Tauri commands and rename operations"
4643
},
@@ -213,6 +210,12 @@
213210
"reason": "UI component, simple show/hide toggle + settings store wiring"
214211
},
215212
"settings/sections/AiSection.svelte": {
213+
"reason": "UI section, thin wrapper for AI provider toggle"
214+
},
215+
"settings/sections/AiCloudSection.svelte": {
216+
"reason": "UI section, depends on Tauri commands and event listeners"
217+
},
218+
"settings/sections/AiLocalSection.svelte": {
216219
"reason": "UI section, depends on Tauri commands and event listeners"
217220
},
218221
"search/AiSearchRow.svelte": {

apps/desktop/src-tauri/src/ai/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Three provider modes:
2121

2222
### Tauri commands
2323

24-
Core: `get_ai_status`, `get_ai_model_info`, `get_ai_runtime_status`, `configure_ai`, `start_ai_server`, `stop_ai_server`, `check_ai_connection`, `start_ai_download`, `cancel_ai_download`, `get_folder_suggestions`.
24+
Core: `get_ai_status`, `get_ai_model_info`, `get_ai_runtime_status`, `configure_ai`, `start_ai_server`, `stop_ai_server`, `check_ai_connection`, `start_ai_download`, `cancel_ai_download`, `get_folder_suggestions`. Note: `get_system_memory_info` moved to top-level `system_memory.rs`.
2525
Legacy (still wired, used by toast): `uninstall_ai`, `dismiss_ai_offer`, `opt_out_ai`, `opt_in_ai`, `is_ai_opted_out`.
2626

2727
## Startup flow

apps/desktop/src-tauri/src/ai/manager.rs

Lines changed: 0 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -365,114 +365,6 @@ pub fn get_ai_runtime_status() -> AiRuntimeStatus {
365365
}
366366
}
367367

368-
/// System memory breakdown returned to frontend for the RAM gauge.
369-
/// Categories are non-overlapping and sum to `total_bytes`.
370-
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
371-
#[serde(rename_all = "camelCase")]
372-
pub struct SystemMemoryInfo {
373-
pub total_bytes: u64,
374-
/// Wired + compressor-occupied memory (kernel, drivers — can't be freed).
375-
pub wired_bytes: u64,
376-
/// App memory: active + inactive - purgeable (process memory the user can free by quitting apps).
377-
pub app_bytes: u64,
378-
/// Free: free + purgeable + speculative (available for new allocations).
379-
pub free_bytes: u64,
380-
}
381-
382-
/// Returns system memory breakdown using macOS `host_statistics64` for accurate,
383-
/// non-overlapping categories (unlike `sysinfo` where used + available > total).
384-
#[tauri::command]
385-
pub fn get_system_memory_info() -> SystemMemoryInfo {
386-
get_system_memory_info_inner()
387-
}
388-
389-
/// Testable inner function that reads macOS vm_statistics64 via Mach API.
390-
pub fn get_system_memory_info_inner() -> SystemMemoryInfo {
391-
#[cfg(target_os = "macos")]
392-
{
393-
macos_memory_info()
394-
}
395-
#[cfg(not(target_os = "macos"))]
396-
{
397-
// Fallback for non-macOS: use sysinfo (best effort)
398-
let mut sys = sysinfo::System::new();
399-
sys.refresh_memory();
400-
let total = sys.total_memory();
401-
let used = sys.used_memory();
402-
let free = total.saturating_sub(used);
403-
SystemMemoryInfo {
404-
total_bytes: total,
405-
wired_bytes: 0,
406-
app_bytes: used,
407-
free_bytes: free,
408-
}
409-
}
410-
}
411-
412-
/// Reads macOS vm_statistics64 via `host_statistics64` for accurate memory breakdown.
413-
#[cfg(target_os = "macos")]
414-
fn macos_memory_info() -> SystemMemoryInfo {
415-
use std::mem;
416-
417-
let total_bytes = {
418-
let mut sys = sysinfo::System::new();
419-
sys.refresh_memory();
420-
sys.total_memory()
421-
};
422-
423-
// Safety: calling Mach kernel API with proper struct size.
424-
let page_size: u64;
425-
let (wired_pages, compressor_pages, internal_pages, purgeable_pages);
426-
427-
unsafe {
428-
page_size = libc::sysconf(libc::_SC_PAGESIZE) as u64;
429-
430-
#[allow(deprecated, reason = "libc says use mach2, but not worth a new dep for one call")]
431-
let host = libc::mach_host_self();
432-
let mut vm_info: libc::vm_statistics64 = mem::zeroed();
433-
let mut count = (size_of::<libc::vm_statistics64>() / size_of::<libc::integer_t>()) as u32;
434-
435-
let ret = libc::host_statistics64(
436-
host,
437-
libc::HOST_VM_INFO64,
438-
&mut vm_info as *mut _ as *mut libc::integer_t,
439-
&mut count,
440-
);
441-
442-
if ret != libc::KERN_SUCCESS {
443-
log::warn!("host_statistics64 returned {ret}, falling back to sysinfo");
444-
let mut sys = sysinfo::System::new();
445-
sys.refresh_memory();
446-
let used = sys.used_memory();
447-
return SystemMemoryInfo {
448-
total_bytes,
449-
wired_bytes: 0,
450-
app_bytes: used,
451-
free_bytes: total_bytes.saturating_sub(used),
452-
};
453-
}
454-
455-
wired_pages = vm_info.wire_count as u64;
456-
compressor_pages = vm_info.compressor_page_count as u64;
457-
// internal_page_count = anonymous pages owned by processes (what Activity Monitor calls "App Memory").
458-
// Unlike active+inactive, this excludes file-backed cache that macOS freely reclaims.
459-
internal_pages = vm_info.internal_page_count as u64;
460-
purgeable_pages = vm_info.purgeable_count as u64;
461-
}
462-
463-
let wired_bytes = (wired_pages + compressor_pages) * page_size;
464-
let app_bytes = internal_pages.saturating_sub(purgeable_pages) * page_size;
465-
// Free = everything not wired or app (includes file cache, inactive, purgeable, speculative)
466-
let free_bytes = total_bytes.saturating_sub(wired_bytes + app_bytes);
467-
468-
SystemMemoryInfo {
469-
total_bytes,
470-
wired_bytes,
471-
app_bytes,
472-
free_bytes,
473-
}
474-
}
475-
476368
/// Stores provider + context size + OpenAI config in manager state.
477369
/// If provider is `local` and model is installed and hardware is supported, starts the server
478370
/// in a background task. If provider is NOT `local` and a server is running, stops it.
@@ -1093,40 +985,4 @@ mod tests {
1093985
let status = get_ai_status();
1094986
assert_eq!(status, AiStatus::Unavailable);
1095987
}
1096-
1097-
#[test]
1098-
fn test_system_memory_info_adds_up() {
1099-
let info = get_system_memory_info_inner();
1100-
1101-
// Total must be positive (every machine has RAM)
1102-
assert!(info.total_bytes > 0, "total_bytes should be positive");
1103-
1104-
// Non-overlapping segments must sum to total
1105-
let sum = info.wired_bytes + info.app_bytes + info.free_bytes;
1106-
assert_eq!(
1107-
sum, info.total_bytes,
1108-
"wired ({}) + app ({}) + free ({}) = {} != total ({})",
1109-
info.wired_bytes, info.app_bytes, info.free_bytes, sum, info.total_bytes,
1110-
);
1111-
1112-
// Each segment should be reasonable (not more than total)
1113-
assert!(info.wired_bytes <= info.total_bytes);
1114-
assert!(info.app_bytes <= info.total_bytes);
1115-
assert!(info.free_bytes <= info.total_bytes);
1116-
}
1117-
1118-
#[test]
1119-
fn test_system_memory_info_serialization() {
1120-
let info = SystemMemoryInfo {
1121-
total_bytes: 68_719_476_736,
1122-
wired_bytes: 5_000_000_000,
1123-
app_bytes: 30_000_000_000,
1124-
free_bytes: 33_719_476_736,
1125-
};
1126-
let json = serde_json::to_string(&info).unwrap();
1127-
assert!(json.contains("\"totalBytes\":68719476736"));
1128-
assert!(json.contains("\"wiredBytes\":5000000000"));
1129-
assert!(json.contains("\"appBytes\":30000000000"));
1130-
assert!(json.contains("\"freeBytes\":33719476736"));
1131-
}
1132988
}

apps/desktop/src-tauri/src/file_system/volume/CLAUDE.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ Every file system operation (listing, copy, rename, delete, indexing, watching)
1111
| File | Role |
1212
|---|---|
1313
| `mod.rs` | `Volume` trait, `VolumeScanner`, `VolumeWatcher`, `VolumeReadStream` traits, `MutationEvent` enum, shared types (`VolumeError`, `SpaceInfo`, `CopyScanResult`, `ScanConflict`, `SourceItemInfo`) |
14-
| `friendly_error.rs` | User-facing error messages. See [Friendly error system](#friendly-error-system) below. |
14+
| `friendly_error.rs` | User-facing error messages: `FriendlyError`, `ErrorCategory`, errno mapping. See [Friendly error system](#friendly-error-system) below. |
15+
| `provider.rs` | Provider detection and enrichment: `Provider` enum (19 variants), `detect_provider()`, `provider_suggestion()`, `enrich_with_provider()`. Re-exported via `friendly_error.rs`. |
1516
| `manager.rs` | `VolumeManager` — thread-safe `RwLock<HashMap>` registry; supports a default volume |
1617
| `local_posix.rs` | `LocalPosixVolume` — real filesystem; delegates listing to `file_system::listing`, indexing to `indexing::scanner`, watching to `indexing::watcher` (FSEvents), copy scanning via `walkdir`. Uses `libc::statvfs` FFI for space info. |
1718
| `mtp.rs` | `MtpVolume` — MTP device storage; synchronous `Volume` trait bridged to async MTP calls via `tokio::runtime::Handle::block_on`. Gated with `#[cfg(any(target_os = "macos", target_os = "linux"))]`. |
@@ -93,12 +94,14 @@ collapsible "Technical details" section, never hidden but never in your face eit
9394

9495
### Architecture
9596

96-
Two-layer mapping, both in this file:
97+
Two-layer mapping across two files:
9798

98-
1. **`friendly_error_from_volume_error(err, path)`** — maps `VolumeError` variants and macOS errno codes (37 codes) to a
99-
`FriendlyError` with category (Transient/NeedsAction/Serious), title, explanation, suggestion, and raw detail.
100-
2. **`enrich_with_provider(error, path)`** — detects 19 cloud/mount providers from path patterns and `statfs` filesystem
101-
type, then overwrites the suggestion with provider-specific advice.
99+
1. **`friendly_error_from_volume_error(err, path)`** (`friendly_error.rs`) — maps `VolumeError` variants and macOS errno
100+
codes (37 codes) to a `FriendlyError` with category (Transient/NeedsAction/Serious), title, explanation, suggestion,
101+
and raw detail.
102+
2. **`enrich_with_provider(error, path)`** (`provider.rs`, re-exported from `friendly_error.rs`) — detects 19
103+
cloud/mount providers from path patterns and `statfs` filesystem type, then overwrites the suggestion with
104+
provider-specific advice.
102105

103106
The frontend receives the fully-baked `FriendlyError` struct via the `listing-error` Tauri event and renders it with
104107
category-based visual styling. The frontend never sees errno codes or does OS-specific logic.

0 commit comments

Comments
 (0)