Skip to content

Commit 0cdd7d7

Browse files
committed
Bugfix: Fix copy/move crash from reactivity race
- `TransferDialog` props become null when parent sets `transferDialogProps = null` on confirm, but Svelte 5 reactive getters in the child re-evaluate before `{#if}` tears it down - Guard `$derived.by` for `pathError` against null `sourcePaths` - Add `destroyed` flag checked in `checkConflicts()` to skip stale async IPC results - Defer `transferDialogProps = null` via `queueMicrotask` so `onDestroy` fires first
1 parent 48ea603 commit 0cdd7d7

7 files changed

Lines changed: 58 additions & 51 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Provides three backend strategies for serving file content line-by-line with ins
99
- `full_load.rs` — loads entire file into `String` (<1MB files)
1010
- `byte_seek.rs` — seeks by byte offset, scans backward for newline (instant open)
1111
- `line_index.rs` — sparse newline index (1 checkpoint per 256 lines), SIMD-accelerated via `memchr`
12+
- `*_test.rs` — unit tests for each backend: UTF-8 edge cases, search highlighting, checkpoint math
1213

1314
## Backend selection logic
1415

apps/desktop/src/lib/file-explorer/pane/dialog-state.svelte.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,11 @@ export function createDialogState(deps: DialogStateDeps) {
281281
snapshotSourcePaneSelection()
282282

283283
showTransferDialog = false
284-
transferDialogProps = null
285284
showTransferProgressDialog = true
285+
// Defer nulling props so onDestroy fires first (avoids reactive reads of nulled props)
286+
queueMicrotask(() => {
287+
transferDialogProps = null
288+
})
286289
},
287290

288291
handleTransferCancel() {

apps/desktop/src/lib/file-explorer/selection/CLAUDE.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ falls back to showing only dir count and percentage.
5050
Stale indicator (UnoCSS/Lucide `i-lucide:hourglass` icon in accent color) appears in `selection-summary` when
5151
`isScanning()` is true and directories are selected, because dir sizes may be incomplete during scanning.
5252

53-
Filename truncation in `file-info` mode uses the `useShortenMiddle` action with `preferBreakAt: '.'` to preserve
54-
file extensions. The action uses pretext for canvas-based measurement and a built-in ResizeObserver.
53+
Filename truncation in `file-info` mode uses the `useShortenMiddle` action with `preferBreakAt: '.'` to preserve file
54+
extensions. The action uses pretext for canvas-based measurement and a built-in ResizeObserver.
5555

5656
Date column width is computed via `measureDateColumnWidth(formatDateTime)` to stay in sync with FullList —
5757
`formatDateTime` comes from `reactive-settings.svelte`.
@@ -80,11 +80,11 @@ Handles both `onclick` and `onkeydown` (Enter/Space).
8080
Human-readable values lose precision and make it impossible to compare similarly-sized files. Triads with tier-based CSS
8181
coloring (bytes/KB/MB/GB/TB) give both precision and quick visual scanning. Human-readable is available as a tooltip.
8282

83-
**Decision**: Middle truncation in `file-info` mode uses the `useShortenMiddle` Svelte action (from `$lib/utils/`)
84-
with `preferBreakAt: '.'` and `startRatio: 0.7`, not CSS `text-overflow: ellipsis` **Why**: CSS ellipsis truncates from
85-
the right, losing the file extension. Middle truncation with dot-snapping preserves both the start of the filename and
86-
the extension (e.g. `very-lon….txt`). The action uses pretext for pixel-accurate canvas measurement (no DOM reflow)
87-
with a built-in ResizeObserver.
83+
**Decision**: Middle truncation in `file-info` mode uses the `useShortenMiddle` Svelte action (from `$lib/utils/`) with
84+
`preferBreakAt: '.'` and `startRatio: 0.7`, not CSS `text-overflow: ellipsis` **Why**: CSS ellipsis truncates from the
85+
right, losing the file extension. Middle truncation with dot-snapping preserves both the start of the filename and the
86+
extension (e.g. `very-lon….txt`). The action uses pretext for pixel-accurate canvas measurement (no DOM reflow) with a
87+
built-in ResizeObserver.
8888

8989
**Decision**: `SelectionInfo` derives display mode from props rather than accepting an explicit `mode` prop **Why**: The
9090
display mode depends on `viewMode`, `selectedCount`, and `stats` together. Letting the component derive it internally

apps/desktop/src/lib/file-operations/transfer/TransferDialog.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
118118
// Whether the user confirmed (so we don't cancel the scan on destroy)
119119
let confirmed = false
120+
let destroyed = false
120121
121122
// Conflict detection state
122123
let conflicts = $state<VolumeConflictInfo[]>([])
@@ -170,6 +171,7 @@
170171
}
171172
172173
const pathError = $derived.by(() => {
174+
if (!sourcePaths) return null // props tearing down
173175
const structural = validateDirectoryPath(editedPath)
174176
if (structural.severity === 'error') return structural.message
175177
return getPathValidationError(sourcePaths, editedPath)
@@ -219,7 +221,7 @@
219221
220222
/** Checks for conflicts at the destination. */
221223
async function checkConflicts() {
222-
if (isCheckingConflicts || conflictCheckComplete) return
224+
if (destroyed || isCheckingConflicts || conflictCheckComplete) return
223225
224226
isCheckingConflicts = true
225227
try {
@@ -342,6 +344,7 @@
342344
})
343345
344346
onDestroy(() => {
347+
destroyed = true
345348
// Cancel scan preview if still running — but only if the user cancelled, not confirmed.
346349
// On confirm, the TransferProgressDialog takes over listening to the same scan.
347350
if (previewId && isScanning && !confirmed) {

apps/desktop/src/lib/tauri-commands/CLAUDE.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,24 @@ import { listDirectoryStart } from '$lib/tauri-commands/file-listing'
1313

1414
## Files
1515

16-
| File | Contents |
17-
| --------------------- | ------------------------------------------------------------------------------------------------------------------- |
18-
| `ipc-types.ts` | `TimedOut<T>`, `IpcError`, `isIpcError()`, `getIpcErrorMessage()` — shared timeout-aware types |
19-
| `index.ts` | Barrel re-export of everything below |
16+
| File | Contents |
17+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18+
| `ipc-types.ts` | `TimedOut<T>`, `IpcError`, `isIpcError()`, `getIpcErrorMessage()` — shared timeout-aware types |
19+
| `index.ts` | Barrel re-export of everything below |
2020
| `file-listing.ts` | Virtual-scroll listing API, batch accessors (`getPathsAtIndices`, `getFilesAtIndices`), drag-and-drop, `pathExists`, `createDirectory`, `createFile`, sync status, font metrics |
21-
| `file-viewer.ts` | Viewer session only: open, seek, search, close, word wrap menu |
22-
| `file-actions.ts` | Open file/URL, Finder reveal, Quick Look, Get Info, context menu, clipboard, open in editor |
23-
| `icons.ts` | Icon fetching (`getIcons`, `refreshDirectoryIcons`) and cache invalidation |
24-
| `app-state.ts` | MCP pane state, dialog open/close tracking, menu context, view settings, `showMainWindow` |
25-
| `write-operations.ts` | Copy/move/delete, conflict resolution, scan preview, `formatBytes`/`formatDuration` |
26-
| `rename.ts` | `checkRenamePermission`, `checkRenameValidity`, `renameFile`, `moveToTrash` |
27-
| `storage.ts` | `listVolumes`, `getVolumeSpace`, `watchVolumeSpace`/`unwatchVolumeSpace`, `checkFullDiskAccess`, `openPrivacySettings` |
28-
| `networking.ts` | SMB host discovery, share listing, Keychain credential ops, mounting |
29-
| `mtp.ts` | Android MTP: device listing, connect/disconnect, file ops, transfer progress, volume copy |
30-
| `licensing.ts` | License status, activation, expiry, server validation |
31-
| `settings.ts` | Port checking, file watcher debounce, indexing toggle, MCP server control, AI subsystem commands |
32-
| `tab.ts` | Tab context menu: `showTabContextMenu`, `onTabContextAction` |
33-
| `clipboard-files.ts` | Clipboard file operations: copy/cut files to system clipboard, read/paste, clear cut state |
21+
| `file-viewer.ts` | Viewer session only: open, seek, search, close, word wrap menu |
22+
| `file-actions.ts` | Open file/URL, Finder reveal, Quick Look, Get Info, context menu, clipboard, open in editor |
23+
| `icons.ts` | Icon fetching (`getIcons`, `refreshDirectoryIcons`) and cache invalidation |
24+
| `app-state.ts` | MCP pane state, dialog open/close tracking, menu context, view settings, `showMainWindow` |
25+
| `write-operations.ts` | Copy/move/delete, conflict resolution, scan preview, `formatBytes`/`formatDuration` |
26+
| `rename.ts` | `checkRenamePermission`, `checkRenameValidity`, `renameFile`, `moveToTrash` |
27+
| `storage.ts` | `listVolumes`, `getVolumeSpace`, `watchVolumeSpace`/`unwatchVolumeSpace`, `checkFullDiskAccess`, `openPrivacySettings` |
28+
| `networking.ts` | SMB host discovery, share listing, Keychain credential ops, mounting |
29+
| `mtp.ts` | Android MTP: device listing, connect/disconnect, file ops, transfer progress, volume copy |
30+
| `licensing.ts` | License status, activation, expiry, server validation |
31+
| `settings.ts` | Port checking, file watcher debounce, indexing toggle, MCP server control, AI subsystem commands |
32+
| `tab.ts` | Tab context menu: `showTabContextMenu`, `onTabContextAction` |
33+
| `clipboard-files.ts` | Clipboard file operations: copy/cut files to system clipboard, read/paste, clear cut state |
3434

3535
## Where to put new commands
3636

docs/architecture.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,30 +38,30 @@ All under `apps/desktop/src/lib/`.
3838

3939
All under `apps/desktop/src-tauri/src/`.
4040

41-
| Directory/file | Purpose |
42-
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
43-
| `file_system/listing/` | Directory reading, streaming, caching, sorting — serves virtual scroll |
44-
| `file_system/write_operations/` | Copy/move/delete with safety patterns (temp+rename, staging, rollback) |
45-
| `file_viewer/` | Three-backend file viewer (FullLoad, ByteSeek, LineIndex) |
46-
| `network/` | SMB: mDNS discovery, share listing (smb-rs + smbutil), mounting, Keychain |
47-
| `mtp/` | MTP device management, file ops, event-based watching |
48-
| `mcp/` | MCP server (19 tools, YAML resources, agent-centric API) |
49-
| `ai/` | llama-server lifecycle, model download, inference client |
50-
| `licensing/` | Ed25519 license verification, server validation |
51-
| `settings/` | Settings persistence (tauri-plugin-store) |
52-
| `indexing/` | Background drive indexing (SQLite, jwalk, FSEvents), recursive directory sizes |
53-
| `search/` | In-memory search index (lazy load, rayon parallel scan, glob/regex) and AI query translation pipeline (`search/ai/`) |
54-
| `font_metrics/` | Binary font metrics cache, per-directory width calculation |
55-
| `volumes/` | Volume abstraction (local, network, MTP), scanner/watcher traits |
41+
| Directory/file | Purpose |
42+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
43+
| `file_system/listing/` | Directory reading, streaming, caching, sorting — serves virtual scroll |
44+
| `file_system/write_operations/` | Copy/move/delete with safety patterns (temp+rename, staging, rollback) |
45+
| `file_viewer/` | Three-backend file viewer (FullLoad, ByteSeek, LineIndex) |
46+
| `network/` | SMB: mDNS discovery, share listing (smb-rs + smbutil), mounting, Keychain |
47+
| `mtp/` | MTP device management, file ops, event-based watching |
48+
| `mcp/` | MCP server (19 tools, YAML resources, agent-centric API) |
49+
| `ai/` | llama-server lifecycle, model download, inference client |
50+
| `licensing/` | Ed25519 license verification, server validation |
51+
| `settings/` | Settings persistence (tauri-plugin-store) |
52+
| `indexing/` | Background drive indexing (SQLite, jwalk, FSEvents), recursive directory sizes |
53+
| `search/` | In-memory search index (lazy load, rayon parallel scan, glob/regex) and AI query translation pipeline (`search/ai/`) |
54+
| `font_metrics/` | Binary font metrics cache, per-directory width calculation |
55+
| `volumes/` | Volume abstraction (local, network, MTP), scanner/watcher traits |
5656
| `space_poller.rs` | Live disk-space polling — per-volume-type intervals via `Volume::space_poll_interval()`, threshold-based change detection, emits `volume-space-changed` |
57-
| `stubs/` | Linux compilation stubs for macOS-only modules (used by Docker E2E pipeline) |
58-
| `menu/` | Native menu bar: platform-specific construction, dispatch mapping, accelerator sync, context-aware enable/disable |
59-
| `drag_image_detection.rs` | macOS method swizzle for drag image size detection |
60-
| `drag_image_swap.rs` | Rich/transparent drag image swap for self-drags |
61-
| `crash_reporter/` | Crash capture (panic hook + signal handler), next-launch detection, report sending |
62-
| `commands/` | Tauri command definitions (IPC entry points) |
63-
| `capabilities/` | Per-window Tauri API permissions — must be updated when using new Tauri APIs from a window |
64-
| `icons/` | App icons for all platforms + macOS Tahoe Liquid Glass (Assets.car). See [CLAUDE.md](../apps/desktop/src-tauri/icons/CLAUDE.md) for regeneration steps |
57+
| `stubs/` | Linux compilation stubs for macOS-only modules (used by Docker E2E pipeline) |
58+
| `menu/` | Native menu bar: platform-specific construction, dispatch mapping, accelerator sync, context-aware enable/disable |
59+
| `drag_image_detection.rs` | macOS method swizzle for drag image size detection |
60+
| `drag_image_swap.rs` | Rich/transparent drag image swap for self-drags |
61+
| `crash_reporter/` | Crash capture (panic hook + signal handler), next-launch detection, report sending |
62+
| `commands/` | Tauri command definitions (IPC entry points) |
63+
| `capabilities/` | Per-window Tauri API permissions — must be updated when using new Tauri APIs from a window |
64+
| `icons/` | App icons for all platforms + macOS Tahoe Liquid Glass (Assets.car). See [CLAUDE.md](../apps/desktop/src-tauri/icons/CLAUDE.md) for regeneration steps |
6565

6666
## Other apps
6767

0 commit comments

Comments
 (0)