Commit Graph

1724 Commits

Author SHA1 Message Date
James Pine
37bb1b2f26 fix release CI + rustfmt + storage navigation
- Add bun setup and web frontend build steps to server-build job in
  release workflow (rust-embed needs apps/web/dist/ at compile time)
- Fix rustfmt violation in volume detection
- Fix storage dialog callback to use sdPath instead of location_id
- Navigate to explorer path after adding storage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:58:40 -07:00
James Pine
1e58784482 fix volume detection + redundancy doc 2026-04-14 15:50:44 -07:00
Jamie Pine
730b177965 Fix remaining CI failures: rustfmt, TypeScript errors
- cargo fmt across all modified files
- Add Vite client types and Window.__SPACEDRIVE__ declaration
- Fix @sd/interface/platform import to @sd/interface
- Align @types/react versions between tauri and interface packages
- Remove unused imports/vars in useDropZone, DragOverlay, ContextMenuWindow
- Fix WebviewWindow.location references to use globalThis
- Exclude updater.example.ts from typecheck
2026-04-12 23:13:50 -07:00
Jamie Pine
acde17c088 Fix CI: rustfmt, xtask empty TARGET_TRIPLE, and package linking
- Sort `pub mod adapters` alphabetically in ops/mod.rs
- Wrap long line in volume/fs/refs.rs
- Handle empty TARGET_TRIPLE env var in xtask setup
- Replace `link:@spacedrive/*` with published `^0.2.3` versions
2026-04-12 20:33:43 -07:00
Jamie Pine
74cb8c46bb Merge main into spacedrive-data 2026-04-12 13:40:59 -07:00
Jamie Pine
6c5e656e6d spacedrive server work 2026-04-10 16:58:43 -07:00
James Pine
adac2f3c45 Source sync jobs, voice overlay rewrite, TabBar migration, docs
- Refactor source sync to dispatch SourceSyncJob instead of inline sync
- Rewrite VoiceOverlay with audio recorder and TTS playback hooks
- Migrate TabBar to @spaceui/primitives
- Update SpacebotContext query invalidation
- Add SpaceUI section to CONTRIBUTING.md and README.md
- Update sources UI (Adapters, SourceDetail)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:12:52 -07:00
James Pine
907ab4e267 Add archive sources UI with full Rust integration
- Create core/src/data/ module with SourceManager wrapping sd-archive Engine
- Add Sources to GroupType and Source to ItemType enums
- Add default Sources group to new library creation
- Register source operations: create, list, get, delete, sync, list_items
- Register adapter operations: list, config, update
- Add bundled adapter sync from workspace adapters/ directory
- Add adapter update system with BLAKE3 change detection and backup/rollback
- Frontend: Sources home, source detail with virtualized list, adapters screen
- Frontend: SourcesGroup sidebar, SpaceGroup dispatch, spaceItemUtils
- Frontend: TopBar integration (path bar, search, sync, actions menu)
- Frontend: Tab title sync, adapter icon lookup hook
- Regenerate TypeScript types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:24:53 -07:00
Jamie Pine
48c574c5ea Merge pull request #3041 from slvnlrt/delete-improvements
fix(delete): resolve Content paths, progress updates, DEL keybinds
2026-03-26 12:22:38 -07:00
Jamie Pine
c02e3404b1 data 2026-03-26 12:19:56 -07:00
James Pine
63add551c9 progress 2026-03-25 17:29:12 -07:00
slvnlrt
7b867ef4f1 fix(windows): only strip \?\ prefix on drive-letter paths
Volume GUIDs (\?\Volume{...}\) and other verbatim forms are invalid
without the prefix. Only strip when followed by a drive letter (X:\).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:05:13 +01:00
slvnlrt
1d7097d64c refactor(locations): extract UNC path helper, add unwatch on removal
- Extract duplicated Windows extended path normalization into shared
  `common::utils::strip_windows_extended_prefix()` helper (was copy-pasted
  in location/manager.rs, locations/add/action.rs, volume/fs/refs.rs)
- Add `unwatch_location()` call in LocationRemoveAction to stop the
  filesystem watcher when a location is deleted (symmetric with the
  `watch_location()` added in LocationAddAction)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:45:48 +01:00
James Pine
e4955bf59a ui magic 2026-03-24 22:19:31 -07:00
James Pine
b7bbf29dbd fix(ephemeral): restore streaming design, fix event delivery race conditions
Reverts the query/response approach from #3037 and fixes the actual bugs
that caused empty ephemeral directories:

- directory_listing.rs: Restore async indexer dispatch (return empty,
  populate via events). Subdirectories from a parent's shallow index now
  correctly fall through to trigger their own indexer job.

- subscriptionManager.ts: Pre-register initial listener before calling
  transport.subscribe() so buffer replay events aren't broadcast to an
  empty listener Set.

- useNormalizedQuery.ts: Seed TanStack Query cache when oldData is
  undefined, so events arriving before the query response aren't silently
  dropped by the setQueryData updater.

Adds bridge test (Rust harness + TS integration) that reproduces the
ephemeral event streaming flow end-to-end.
2026-03-24 18:38:46 -07:00
James Pine
aff09bb087 init 2026-03-24 14:23:55 -07:00
slvnlrt
f6c5985211 fix(core): reject entries with no parent_id instead of building relative paths
addressing.rs: when resolving SdPath::Physical, entries with parent_id=None
produced a relative path like "file.txt" which violates the SdPath::Physical
contract (must be absolute). Now returns an error instead. In practice this
case shouldn't occur (root entries are directories), but it's correct to
fail explicitly rather than silently produce invalid paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:14:14 +01:00
slvnlrt
9004d868fb fix(windows): handle UNC network paths in canonicalize normalization
canonicalize() on Windows can produce \?\UNC\server\share\... for network
paths. The previous strip_prefix(r"\?\") would produce UNC\server\share\...
which is invalid. Now handles both forms:
- \?\UNC\server\share\... → \server\share\... (network UNC)
- \?\C:\... → C:\... (local drive)

Applied in both manager.rs (add_location) and add/action.rs (watcher
registration) to ensure consistency. Same normalization as
volume/fs/refs.rs:contains_path().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:07:48 +01:00
slvnlrt
2e778e90db fix(locations): canonicalize paths and register watcher on location add
Two upstream bugs fixed:

1. LocationManager::add_location() stored paths as-is without
   canonicalization. Relative paths (e.g. from cwd-dependent contexts)
   broke the watcher, volume manager, and indexer pipelines.
   Now calls tokio::fs::canonicalize() on local physical paths before
   storing, with UNC prefix stripping on Windows.

2. LocationAddAction::execute() never registered new locations with the
   FsWatcherService. The watcher only discovered locations at startup
   via load_library_locations(). Any location added at runtime had no
   filesystem monitoring — creates, deletes, and renames went undetected.
   Now calls fs_watcher.watch_location() after successful creation.
2026-03-24 17:14:54 +01:00
slvnlrt
4dd83734e8 refactor: extract shared useDeleteFiles hook, fix review issues
- Extract delete logic into useDeleteFiles hook (DRY: used by both
  useExplorerKeyboard and useFileContextMenu)
- Add isPending guard to prevent double-deletion on rapid key presses
- Remove dead DeleteProgress struct from delete job
- Use Progress::Indeterminate for intermediate phases instead of
  misleading percentage values that get overridden by with_completion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 23:21:13 +01:00
slvnlrt
c27febadbd fix(jobs): emit progress updates during delete job execution
DeleteJob now emits GenericProgress at 4 phases: Preparing (validation),
Preparing (path resolution), Deleting (strategy execution), and
Complete (with final counts and timing). Fixes the UI showing 0%
throughout delete operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 23:21:13 +01:00
slvnlrt
489fe31c7d fix(delete): resolve Content paths to Physical before deletion
DeleteJob now calls resolve_in_job() on each target path before strategy
selection, converting SdPath::Content to SdPath::Physical via DB lookup.
This follows the same pattern established by CopyJob (upstream).

Also replaces unimplemented!() panic in PathResolver::resolve() for
Content paths with a proper error return.

Note: RemoteDeleteStrategy still uses deprecated device_id() — fixing
this requires device_slug resolution infrastructure (separate concern).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 23:21:13 +01:00
slvnlrt
adad8c638f fix(core): clamp SQLite pool size and fix Win32 buffer in generic.rs
- Clamp pool_size to at least 1 and min_connections to min(pool_size, 5)
  to prevent panic when pool_size < 5
- Use dynamic buffer size (1024) for GetVolumePathNameW in generic.rs
  to handle folder-mounted volumes with long paths (same fix as fs/mod.rs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 22:45:03 +01:00
slvnlrt
274ad09a20 fix(volume): use dynamic buffer sizes in volume_guid Win32 calls
Use root_buf.len() and guid_buf.len() instead of hardcoded literals
to avoid size mismatches. Increased root_buf to 1024 to handle
folder-mounted volumes whose paths can exceed MAX_PATH.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 22:32:48 +01:00
slvnlrt
251088ed6f fix(volume): use stable DB UUID in volumes.list query response
Commit e6b9a630d correctly injects DB UUIDs into the VolumeManager
cache during refresh, but the volumes.list query was still returning
ephemeral Uuid::new_v4() IDs from the initial startup detection
(which runs before the library is loaded). This caused a UUID mismatch:
the frontend cached ephemeral IDs, then received ResourceChanged events
with DB UUIDs — no match by ID — volumes appended instead of updated.

Fix: when volumes.list merges a tracked DB volume with its live cache
counterpart, override live_vol.id with the stable DB UUID (tracked_vol.uuid).
This ensures the query response and subsequent events use the same ID.

Note: the VolumeManager cache still holds ephemeral UUIDs between
startup and the first refresh after library load (~30s). A future
improvement could sync DB UUIDs into the cache immediately after
library loading, but this is not necessary since volumes.list is a
LibraryQuery and always runs after library load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:45:40 +01:00
slvnlrt
e6b9a630df fix(volume): preserve DB UUID on cache refresh
When a tracked volume gets updated in the cache (mount status change,
space change, etc.), line 741 was overwriting the ID with existing.id
which could be an ephemeral Uuid::new_v4() if the cache was populated
before DB metadata was merged (startup race condition). This defeats
the stable UUID fix from 4f387eede.

Now prefers the DB UUID from tracked_volumes_map when available,
falling back to existing.id only for untracked volumes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 19:20:56 +01:00
slvnlrt
1ff018454c fix: address CodeRabbit review feedback
- BatteryFlag: use bitmask check instead of exact equality (0x80 = no battery)
- SystemDrive: use %SystemDrive% env var instead of hardcoded C:\
- SQLite PRAGMAs: apply per-connection via SqliteConnectOptions (not one-time exec)
- System volume detection: check for Windows\System32 dir instead of drive letter
- Volume serial: use GetVolumePathNameW to resolve actual mount point
- ReFS: proper IOCTL version detection, real volume GUID, capability storage
- Volume GUID: extract shared volume_guid() from ntfs.rs to fs/mod.rs
- Device manager: reuse reg_read_hklm() instead of duplicated registry code
- VolumesGroup: remove 'local' fallback for device slug
- useNormalizedQuery: case-insensitive path matching, catch subscription errors
- Volume struct: add supports_block_cloning field for ReFS CoW routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 19:09:55 +01:00
slvnlrt
ea970d39f4 fix: address review feedback from Tembo bot
- transport.ts: clean up event listener if subscribe_to_events throws
- is_hidden_path: use FILE_ATTRIBUTE_HIDDEN only on Windows, dot-prefix
  only on Unix (no cross-over that would mark .gitignore as hidden)
- reg_read_hklm: two-pass query to handle arbitrarily long REG_SZ values
- classify_volume: pass total_space instead of hardcoded 0
- ntfs volume_guid: unwrap_or(guid_buf.len()) instead of unwrap_or(0)
- wait_for_indexing: poll every 25ms instead of 5ms
- main.rs: accept both JsonOk and result keys for daemon compat
- VolumesGroup.tsx: remove unnecessary <any> type parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 16:36:38 +01:00
slvnlrt
cfa31cc04d fix(ephemeral): fix volume browsing — race condition, event filter, sync listing
Three bugs caused ephemeral volume browsing to either show nothing or require
navigation away and back before content appeared.

1. Transport race condition (packages/ts-client/src/transport.ts)
   listen("core-event") was called AFTER invoke("subscribe_to_events"), so any
   events replayed from the 5s EventBuffer during subscription setup were emitted
   before the listener existed and were silently dropped. Fixed by swapping the
   order: listen first, then subscribe.

2. Event filter: direct children not matched (core/src/infra/event/mod.rs)
   affects_path() with include_descendants=false only matched exact path equality
   (file_path == scope_path). A directory listing subscribes with the directory as
   scope, so files inside it never matched. Fixed by also checking
   file_path.parent() == scope_path (direct children).

3. Synchronous ephemeral directory listing (core/src/ops/files/query/directory_listing.rs)
   query_ephemeral_directory_impl() dispatched the indexer job and immediately
   returned empty, relying on events to update the UI. Since ephemeral indexing
   takes <500ms, it is better to wait for completion and return results directly.
   The function now polls cache.is_indexing() every 5ms (10s timeout) and reads
   from cache before returning. Concurrent requests also wait instead of returning
   empty. Subdirectories with no indexed children now trigger their own indexer
   job instead of returning empty (was: parent index has entry but no children →
   treated as "has results").
   Code refactored into read_ephemeral_listing() and wait_for_indexing() helpers
   to eliminate triplication.

4. Removed is_explicitly_indexed() duplicate (core/src/ops/indexing/ephemeral/cache.rs)
   Identical to is_indexed(); callers now use is_indexed() directly.

5. Debug logging infrastructure (packages/ts-client/src/hooks/useNormalizedQuery.ts)
   Added debug: boolean option to useNormalizedQuery. When true, logs subscription
   lifecycle, raw events, batch filter counts, and cache updates to the console.
   Off by default.
2026-03-14 23:51:46 +01:00
slvnlrt
e2bda37b65 fix(locations): use delete_subtree_in_txn to prevent database deadlock
remove_location() was calling delete_subtree() with library.db().conn()
while holding a transaction, causing SQLITE_BUSY (database is locked).
Use delete_subtree_in_txn(&txn) instead to run within the same transaction.
2026-03-14 23:51:46 +01:00
slvnlrt
27eefd1c67 fix(addressing): accept device UUID as device_slug in SdPath
The frontend VolumesGroup was sending volume.device_id (UUID) instead
of device.slug when constructing Physical SdPaths. This caused
as_local_path() and is_local() to return false, breaking ephemeral
volume browsing ("Location root path is not local").

Backend: SdPath::is_current_device() now accepts slug, UUID, or "local".
Frontend: VolumesGroup.tsx looks up the Device by ID and uses device.slug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 23:51:46 +01:00
slvnlrt
bd428bf131 perf(device): replace wmic in device/manager.rs with registry API
detect_hardware_model() in manager.rs still used wmic to get the
computer model. Replace with direct registry read from
HKLM\HARDWARE\DESCRIPTION\System\BIOS\SystemProductName.
2026-03-14 23:51:46 +01:00
slvnlrt
37dc0c97cd perf(device): replace wmic/powershell with native Win32 APIs on Windows
- detect_hardware_model: registry HKLM\HARDWARE\DESCRIPTION\System\BIOS\SystemProductName
- detect_manufacturer: registry HKLM\HARDWARE\DESCRIPTION\System\BIOS\SystemManufacturer
- detect_form_factor: GetSystemPowerStatus (BatteryFlag 128 = no battery = desktop)
- detect_gpu_models: registry enumeration of display adapter class {4d36e968...}\DriverDesc
- detect_boot_disk_type: sysinfo::Disks on C:\ mount point
- detect_boot_disk_capacity: sysinfo::Disks total_space() on C:\

Adds Win32_System_Registry and Win32_System_Power features to windows-sys.
2026-03-14 23:51:46 +01:00
slvnlrt
6785a807e2 fix(ntfs): remove dead code supports_hardlinks and supports_junctions
These methods always returned true unconditionally and had no callers.
NTFS always supports hardlinks and junction points by definition.
2026-03-14 23:51:46 +01:00
slvnlrt
6073480235 perf(ntfs): remove remaining dead code from NtfsHandler
Remove supports_hardlinks(), supports_junctions(), get_ntfs_features(),
resolve_ntfs_path() and NtfsFeatures — none are called outside the module.
Simplify enhance_volume() to a no-op. Drop now-unused PathBuf and
tracing::debug imports.
2026-03-14 23:51:45 +01:00
slvnlrt
9d3f9795fe perf(ntfs): replace PowerShell with native implementations
get_ntfs_features() spawned PowerShell to query compression/encryption
flags, but all modern NTFS volumes support these features unconditionally.
Replace with a function returning hardcoded NtfsFeatures.

resolve_ntfs_path() used PowerShell Resolve-Path to canonicalize paths.
Replace with std::fs::canonicalize() which handles symlinks and junctions
natively without spawning a shell.

supports_hardlinks() and supports_junctions() called PowerShell via
get_volume_info() to verify the filesystem type. Since NtfsHandler is
only used for NTFS volumes, both always return true unconditionally.

Remove get_volume_info(), NtfsVolumeInfo, parse_volume_info(),
parse_ntfs_features(), extract_json_* helpers, and their tests.
Remove unused imports: tokio::task, tracing::warn.
2026-03-14 23:51:45 +01:00
slvnlrt
febe4aa0d3 fix(locations): use exact match for root paths in system directory check
path.starts_with(PathBuf::from("C:\\")) returns true for every path
on drive C:, causing OneDrive and other user folders to be flagged as
system directories during location validation.

Extend the root path detection to cover Windows drive roots (C:\) in
addition to the Unix root ("/") already handled. Use d.parent().is_none()
which is true for both Unix "/" and Windows "C:\" — root paths must
match exactly, not via starts_with.
2026-03-14 23:51:45 +01:00
slvnlrt
adc2147dcf fix(volume): use Win32 APIs for same_physical_storage on Windows
GenericFilesystemHandler::same_physical_storage() returned hardcoded
false on Windows, causing same-volume file copies to always fall back
to streaming instead of atomic/fast-copy operations.

generic.rs: add volume_serial() helper using GetVolumeInformationW
to retrieve the 32-bit volume serial number from the drive-letter root
(e.g. C:\). Compare serial numbers to determine same-device status.

ntfs.rs: replace PowerShell Get-Volume with native Win32 calls.
Add volume_guid() helper using GetVolumePathNameW (mount point root)
+ GetVolumeNameForVolumeMountPointW (stable \?\Volume{guid}identifier). Compare GUIDs for reliable same-volume detection.
Both helpers are #[cfg(windows)] so non-Windows builds are unaffected.
2026-03-14 23:51:45 +01:00
slvnlrt
f653dfc496 fix(indexing): detect hidden files using FILE_ATTRIBUTE_HIDDEN on Windows
On Windows, files can be hidden via the FILE_ATTRIBUTE_HIDDEN attribute
without using a dot-prefix. The existing code only checked for dot-prefix
(Unix convention), causing Windows hidden files to appear in listings.

Add is_hidden_path() helper in database_storage with a #[cfg(windows)]
branch that calls GetFileAttributesW() and checks FILE_ATTRIBUTE_HIDDEN.
Falls back to dot-prefix check if the API call fails. Non-Windows
platforms keep the original dot-prefix logic unchanged.

Replace all 10 occurrences across 4 files (database_storage.rs,
ephemeral/index.rs, ephemeral/writer.rs, job.rs).
2026-03-14 23:51:45 +01:00
slvnlrt
89e5e9854f fix(db): add PRAGMA busy_timeout=5000 to prevent SQLITE_BUSY on contention
SQLite returns SQLITE_BUSY immediately when another connection holds a
write lock. This is triggered during location removal when delete_subtree()
opens a nested transaction while the outer removal transaction is active,
and additionally when the indexer/watcher writes concurrently.

Adding busy_timeout=5000 makes SQLite retry for up to 5 seconds before
failing, which is sufficient for the nested transaction pattern to resolve.
Applied in both Database::create() and Database::open() to cover all
database instances.

Note: the root cause (nested transaction in remove_location()) should
be fixed separately for full correctness.

Resolves Issue #8 (partial - eliminates the SQLITE_BUSY symptom).
2026-03-14 23:51:45 +01:00
slvnlrt
8ab1e10b3a fix(networking): normalize path separators for cross-platform file transfer
PathBuf is serialized with native OS separators (backslash on Windows).
When a PullRequest is sent from Windows to macOS/Linux, the path
C:\Users\alice\file.txt arrives as a string with backslashes which the
receiving OS cannot resolve (backslash is a valid filename character
on Unix, not a separator).

Sender (strategy.rs): normalize source_path to forward slashes before
building PullRequest, so the wire format is always OS-agnostic.

Receiver (file_transfer.rs): convert forward slashes back to the native
MAIN_SEPARATOR at the start of handle_pull_request, before any path
operations (canonicalize, metadata, checksum, streaming).
2026-03-14 23:51:45 +01:00
slvnlrt
816e641648 fix(watcher): downgrade expected startup warning to debug
During startup, locations are loaded from the database before the
FsWatcher service connects. This triggers a warn!() for every location
saying "FsWatcher not connected". However, PersistentEventHandler::start()
has a recovery mechanism that re-registers all locations once the watcher
is connected, so this is expected behavior, not an actual error.

Change warn!() to debug!() and clarify the message to indicate the
deferred registration will happen on start().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 23:51:45 +01:00
slvnlrt
7fab21da0f perf(volume): replace PowerShell with sysinfo for Windows volume detection
PowerShell Get-Volume | ConvertTo-Json fails on many Windows systems:
it returns non-JSON output (BOM, encoding artifacts, or error messages)
while still exiting with code 0, causing volume detection to abort
entirely with "Failed to parse PowerShell JSON object".

Replace the entire PowerShell + wmic detection stack with sysinfo crate
(v0.31, already a dependency) which uses native Win32 APIs underneath:
- Enumerate disks via sysinfo::Disks::new_with_refreshed_list()
- Map disk.kind() directly to DiskType (SSD/HDD) instead of Unknown
- Pass disk.is_removable() to the classifier for better volume typing
- No shell process spawning, no JSON parsing, no encoding issues

The same sysinfo approach was already proven in refs.rs for ReFS
detection. This makes all volume detection on Windows shell-free.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 23:51:45 +01:00
slvnlrt
71719533d2 fix(networking): use temp_dir() instead of hardcoded /tmp paths
Two places hardcoded Unix-only /tmp paths that break on Windows:

- file_transfer.rs: TransferSession::destination_path defaulted to "/tmp"
  which is not a valid path on Windows. Changed to std::env::temp_dir()
  converted to a String via to_string_lossy().

- file_sharing.rs: SharingOptions::default() used PathBuf::from("/tmp/spacedrive")
  as destination. Changed to std::env::temp_dir().join("spacedrive") which
  correctly resolves to %TEMP%\spacedrive on Windows and /tmp/spacedrive on Unix.

Fixes audit Issue #11 from memory-bank/windows_audit_2026_03.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 23:51:45 +01:00
slvnlrt
4c43ab4971 fix(ephemeral): handle Windows path separators in tree operations
Three Windows incompatibilities in the ephemeral path index:

1. count_entries_under_path() only checked for b'/' as child separator.
   On Windows, paths use backslashes so "C:\foo" would never be found
   as an ancestor of "C:\foo\bar". Added check for b'\' alongside b'/'.

2. remove_directory_tree() used format!("{}/", prefix) to find children.
   Same issue — "C:\foo\" never matches "C:\foo\bar". Added a parallel
   check with format!("{}\\", prefix).

3. The fallback display name for root nodes was hardcoded to "/" instead
   of the path itself. On Windows, drive roots like "C:\" have no
   file_name() component, so now falls back to the full path string
   (e.g. "C:\\") instead of the misleading "/" label.

Fixes audit Issue #10 from memory-bank/windows_audit_2026_03.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 23:51:45 +01:00
slvnlrt
4f387eede9 fix(volume): restore UUID from database at startup
Volume UUIDs were randomly generated (Uuid::new_v4()) on every startup,
causing the frontend to flash/reset on every app launch because React
uses volume.id as component keys (DevicePanel.tsx, VolumesGroup.tsx).

Fix by including db_vol.uuid in tracked_volumes_map and assigning it
to detected.id during the DB metadata merge step in reconcile_volumes().
Volumes recognized by fingerprint now retain their stable database UUID
across restarts, while new volumes still get a fresh UUID until tracked.

Fixes audit Issue #9 from memory-bank/windows_audit_2026_03.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 23:51:45 +01:00
slvnlrt
507ac32691 fix(tests): align progress.rs tests with actual implementation
The upstream tests had assertions that didn't match the implementation:
- completion.completed/total used phase-specific values but the impl
  uses total_found.files + total_found.dirs (real file counts, total=0)
- percentage used exact f32 equality (fails due to float precision)

Fix assertions to use abs() comparisons for floats and expect the
actual total_found-based completion values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:09:37 +01:00
slvnlrt
e8c15dd1cf fix(files): use native trash crate for cross-platform recycle bin support
The previous Windows implementation moved files to a temp directory
(spacedrive_trash) rather than the actual Recycle Bin. The macOS/Linux
implementations were also custom-rolled with manual collision handling.

Replace all platform-specific move_to_trash implementations with the
`trash` crate (v3.3), which uses native OS APIs:
- Windows: SHFileOperation → actual Recycle Bin
- macOS: NSFileManager → Trash
- Linux: XDG trash specification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:51:49 +01:00
slvnlrt
a7757d391d perf(refs): replace PowerShell with native Win32 IOCTL for ReFS detection
PowerShell process spawning for ReFS block-cloning checks caused visible
latency on every volume operation. Replace with:

- FSCTL_GET_REFS_VOLUME_DATA DeviceIoControl call to detect ReFS v2+
  (Win10 1703+) block cloning support instantly, with a static cache so
  each volume is queried at most once per process lifetime.
- sysinfo-based volume enumeration in get_volume_info/get_all_refs_volumes
  (consistent with ntfs.rs, no shell process).
- contains_path strips the \?\ extended-path prefix produced by
  canonicalize() so mount point comparisons work correctly.

Add Win32_System_Ioctl and Win32_System_IO features to windows-sys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:50:38 +01:00
slvnlrt
e8fc67de67 fix(volume): normalize mount point path in VolumeFingerprint::from_primary_volume
On Windows and macOS, volume paths are case-insensitive but the OS can
return them in different cases (e.g. "C:\" vs "c:\"). Without normalization,
the same physical volume could generate different fingerprints across
detection cycles, causing duplicate volume entries in the UI.

Also trims trailing slashes for consistency while preserving root paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:48:37 +01:00