feat(desktop): migrate from Electron to Tauri v2#119
Conversation
Add 40 new tests targeting previously uncovered branches across four modules, closing 67 additional patch-coverage lines. api.rs (+18 tests, -33 missing lines): - upload_name_from_file_name: prefix stripping, deep subfolder, fallback - upload_file_to_api: missing song folder, missing file, path traversal - read_preview_within_workspace: empty root, missing root, outside workspace, absent preview, present preview - run_graphql_value_with_client: server timeout - upload_form_to_api: server timeout - load_asset_files_impl: NOT_FOUND graphql code, simfile_id=0, general graphql error - create_simfile_record_impl: preview upload warnings surface in response songs.rs (+9 tests, -17 missing lines): - parse_dtx_folder: empty folder (DtxParseResult::empty), unreadable .dtx (parse_failures count), .dtx without metadata - resolve_export_directory: explicit path, empty string, tilde expansion - data_url_for_asset: png, jpg, jpeg, unknown extension mime types - valid_export_files: directories and invalid extensions skipped filesystem.rs (+6 tests, -11 missing lines): - read_file_path: file exceeding TEXT_FILE_SIZE_LIMIT, file exceeding AUDIO_FILE_SIZE_LIMIT - decode_text_content: all-null fallback to SHIFT_JIS/UTF-8, BOM stripping - join_path_parts: base with parts, empty parts - file_path_to_string: FilePath::Path conversion auth.rs (+3 tests, -6 missing lines): - session_value_from_data: includes user when present, omits when None - auth_client: builds client with timeout Verification: - cargo fmt --check: clean - cargo clippy --all-targets --locked -- -D warnings: clean - cargo test --locked --lib: 321 passed (was 281), 0 flakes over 3 runs - Patch coverage: 77.62% -> 80.40% (missing: 539 -> 472, -67 lines) - Cumulative since original Codecov baseline: 630 -> 472 missing (-25.1%)
Replace PathBuf::from(".") with *"." in test assertion to avoid
creating an owned instance just for comparison, which triggers
clippy::cmp_owned warning with -D warnings flag.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Replace outdated Electron references with Tauri 2 / Rust backend - Add dtx-api package to project overview and commands - Expand development commands (cargo test, codegen, API deploy) - Update deployment and infrastructure sections - Add GraphQL API and Desktop backend architecture sections Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… version in CI - Add canonicalization check in read_preview_within_workspace to reject symlinks pointing outside the workspace, preventing potential data exfiltration - Add Set Release Version step in desktop-build-deploy.yml to bake SemVer into tauri.conf.json and Cargo.toml before building, ensuring updater comparisons work correctly - Add Unix-only test for symlink rejection security check Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ad of env vars - Add get_default_downloads_dir Tauri command using dirs crate - Replace getEnvironment with getDefaultDownloadsDir in desktopHost - Update settingsStore to use async Rust command for default path - Harden CSP in tauri.conf.json and index.html - Remove unused Versions component - Make console logs DEV-only in main.ts - Update tests for new Downloads directory API Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… artifact handling - Add best-effort Supabase session revocation in logout_session() by POSTing to /auth/v1/logout with the access token before clearing local state. Falls back to local-only logout if config or HTTP client is unavailable. - Fix desktop-build-deploy.yml to handle Tauri v2 artifact format: the setup .exe is both installer and updater bundle (no separate .nsis.zip wrapper), with .exe.sig as the minisign signature sidecar. - Add comprehensive Rust tests for revoke_session_with_client covering happy path, unreachable server, and no session scenarios. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Implement proactive access token refresh to keep desktop sessions working past the Supabase token lifetime (default 1 hour). The auth module now decodes JWT expiry and refreshes tokens when they're within 60 seconds of expiry, preventing expired-JWT errors during long desktop sessions. Changes: - Add ensure_valid_access_token function that checks token expiry and refreshes proactively - Extract perform_refresh as a pure HTTP refresh function for better testability - Add jwt_exp_seconds to decode JWT expiry without signature verification (API still validates server-side) - Update access_token_from_auth_state to delegate to proactive refresh - Add comprehensive unit tests for refresh logic and JWT expiry decoding - Fix getDefaultDownloadsDir to properly invoke the Rust command instead of hard-coding null Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…cation logging - Add refresh_lock to AuthState to serialize concurrent token refreshes, preventing duplicate refresh token submissions that could cause Supabase to reject the second call or revoke the token family - Extract session_access_token and session_refresh_token helpers to reduce boilerplate and centralize token extraction logic - Improve logout revocation error logging to surface failed server-side revocations instead of silently treating them as success - Update CLAUDE.md to document configurable auth callback port via DTX_DESKTOP_AUTH_CALLBACK_PORT - Add filesystem security improvements and expand test coverage for auth and filesystem error paths Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…erations Add mandatory workspace_root parameter to file system commands (path_exists, load_tree_structure, parse_dtx_files, upload_file, export_song_to_zip) to prevent path traversal and symlink exfiltration attacks. All file access now routes through the canonicalize_within_workspace primitive, ensuring compromised renderers cannot access files outside the user-selected workspace. - Add workspace_root parameter to IPC commands and frontend service calls - Reject empty/missing workspace roots with actionable error messages - Add symlink type reporting in directory listings - Update GitHub workflow environment name from "Electron" to "Desktop" - Expand test coverage for containment enforcement and error paths Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The Rust backend proactively refreshes Supabase tokens during long-running sessions and on startup validation, but the rotated refresh token was only stored in the in-memory AuthState. The renderer kept the now-revoked refresh token in localStorage, so the next launch logged the user out. Emit a `session-refreshed` event from the refresh paths (startup validation and proactive near-expiry refresh) carrying the new Supabase session value, and have the renderer persist it via `storeSessionData`. `AppHandle` is threaded as `Option` through the refresh functions so unit tests can exercise the logic without a real app handle. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
PR builds now run unsigned to prevent the updater signing key from being exposed to PR-controlled build scripts. A leaked signing key would allow an attacker to ship trusted updater bundles to every installed desktop client. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Set createUpdaterArtifacts=false in committed tauri.conf.json so PR builds (which run without the signing key) and local builds without TAURI_SIGNING_PRIVATE_KEY don't hard-fail. The CI workflow re-enables it only for signed release builds via jq, where the signing key is present and the deploy job consumes the .sig sidecars. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/dtx-desktop/src-tauri/src/songs.rs (1)
118-125:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse the canonical path after the workspace containment check.
Line 118 proves the canonical target is inside the workspace, but Line 125 reopens the original
song_path. A symlink swapped after validation can make the export read and zip files outside the workspace.Use the validated canonical path for export
- crate::filesystem::canonicalize_within_workspace(&song_path, Some(&workspace_root)).await?; + let canonical_song_path = + crate::filesystem::canonicalize_within_workspace(&song_path, Some(&workspace_root)) + .await?; let export_directory = resolve_export_directory(export_directory.as_deref()); let song_title = song_title .as_deref() .filter(|title| !title.trim().is_empty()) .unwrap_or("song"); - export_song_folder_to_zip(Path::new(&song_path), song_title, &export_directory).await + export_song_folder_to_zip(&canonical_song_path, song_title, &export_directory).await🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dtx-desktop/src-tauri/src/songs.rs` around lines 118 - 125, The code validates that song_path is within the workspace bounds on line 118 using canonicalize_within_workspace, but then passes the original song_path to export_song_folder_to_zip on line 125. This creates a security vulnerability where a symlink could be changed between validation and use. Store the result of the canonicalize_within_workspace call as the validated canonical path, then pass this canonical path to export_song_folder_to_zip instead of the original song_path to ensure the validated path is actually used for the export operation.
🧹 Nitpick comments (4)
packages/dtx-desktop/src-tauri/src/tests/api_tests.rs (1)
1464-1480: ⚡ Quick winAvoid waiting for the production timeout in this unit test.
This mock delays for
API_REQUEST_TIMEOUT_MS / 1000 + 5and callsupload_form_to_api, so CI waits for the full production timeout before the assertion can pass. Mirror the GraphQL timeout test by injecting a short-timeout client into a testable upload helper.Shape of the test-side change
let form = Form::new().text("data", "test"); - let result = upload_form_to_api(&server.uri(), "tok", form).await; + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_millis(100)) + .build() + .unwrap(); + let result = upload_form_to_api_with_client(client, &server.uri(), "tok", form).await; assert_eq!(result["success"], false); assert!(result["error"].as_str().unwrap().contains("timed out"));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dtx-desktop/src-tauri/src/tests/api_tests.rs` around lines 1464 - 1480, The test `upload_form_to_api_times_out_on_slow_server` is slow because it waits for the full production timeout value `API_REQUEST_TIMEOUT_MS`. Refactor the `upload_form_to_api` function to accept an injectable HTTP client parameter with a configurable timeout, then create a test helper that takes a custom timeout duration. In the test, inject a client with a short timeout duration (e.g., a few milliseconds) instead of waiting for the production timeout, and call the helper with this short-timeout client to make the test run quickly while still verifying timeout behavior.packages/dtx-desktop/src/renderer/src/components/Settings.test.ts (1)
12-14: ⚡ Quick winUse
$lib/alias when mocking internal services.Switch this mock target to alias-based pathing to match repository import conventions.
Proposed change
-vi.mock('../services/desktopHost', () => ({ +vi.mock('$lib/services/desktopHost', () => ({ desktopHost: mockDesktopHost }));As per coding guidelines, "Use
$lib/aliases for imports within packages instead of relative paths".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dtx-desktop/src/renderer/src/components/Settings.test.ts` around lines 12 - 14, The vi.mock call for desktopHost is using a relative path '../services/desktopHost' instead of the repository's $lib/ alias convention. Replace the relative path string in the vi.mock function call with the appropriate $lib/ alias path to match the project's import conventions and ensure consistency across the codebase.Source: Coding guidelines
packages/dtx-desktop/src/renderer/src/components/NewSong.svelte (1)
6-6: ⚡ Quick winUse
$lib/alias for the new internal service import.Please switch this import to the package alias form for consistency and safer path refactors.
Proposed change
-import { desktopHost } from '../services/desktopHost'; +import { desktopHost } from '$lib/services/desktopHost';As per coding guidelines, "Use
$lib/aliases for imports within packages instead of relative paths".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dtx-desktop/src/renderer/src/components/NewSong.svelte` at line 6, In the NewSong.svelte file, the import statement for desktopHost currently uses a relative path that should be replaced with the package alias for consistency and maintainability. Change the desktopHost import statement from using the relative path `../services/desktopHost` to instead use the `$lib/` alias pattern, which will resolve to the same location but provides a more stable path reference that is safer for future refactors.Source: Coding guidelines
packages/dtx-desktop/src/renderer/src/components/Settings.svelte (1)
5-5: ⚡ Quick winUse
$lib/alias for internal import.Please move this new import to
$lib/...to match package import conventions.Proposed change
-import { desktopHost } from '../services/desktopHost'; +import { desktopHost } from '$lib/services/desktopHost';As per coding guidelines, "Use
$lib/aliases for imports within packages instead of relative paths".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dtx-desktop/src/renderer/src/components/Settings.svelte` at line 5, The import statement for desktopHost currently uses a relative path ../services/desktopHost instead of following the package convention of using the $lib/ alias. Replace the relative path import in Settings.svelte with the $lib/ aliased path, changing from '../services/desktopHost' to '$lib/services/desktopHost' to match the codebase conventions for internal package imports.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/desktop-build-deploy.yml:
- Around line 82-86: The version extraction logic only handles tags with the v
prefix (refs/tags/v*) but fails to extract the version from bare SemVer tags
like 1.2.3. To fix this, add an additional condition in all three
release-version blocks (lines 82-86, 197-201, and 285-289) that also matches
tags in the refs/tags/ format directly and extracts the version by stripping the
refs/tags/ prefix. The VERSION variable assignment should handle both cases:
first check for refs/tags/v* and strip the prefix accordingly, then fall back to
checking for refs/tags/ with a simple suffix removal, ensuring bare SemVer tags
are properly recognized and VERSION is populated correctly for all tag formats.
In `@packages/dtx-desktop/src-tauri/src/filesystem.rs`:
- Around line 378-389: The while let Ok(Some(entry)) pattern at the start of the
loop in inspect_tree_folder silently exits iteration when next_entry()
encounters an error without logging it, causing incomplete directory processing.
Replace this pattern with explicit error handling that logs the error from
next_entry().await before deciding to break the loop, similar to how file_type
errors are logged in the match statement, to ensure visibility into why
directory iteration failed.
- Around line 59-67: The order of checks in the match statement for
canonicalize_within_workspace is leaking information about the existence of
paths outside the workspace boundary. Before attempting to canonicalize, first
validate that the full_path_string is actually contained within the
workspace_root, and return a consistent "outside the workspace" error for all
cases where the path is outside the boundary regardless of whether the path
exists. Only proceed with canonicalization for paths that pass the containment
check first, ensuring that NotFound errors only occur for paths that are
legitimately within the workspace.
In `@packages/dtx-desktop/src-tauri/src/lib.rs`:
- Around line 42-45: The deep-link scheme filtering in the `map` and `filter`
chain is case-sensitive and only matches lowercase "dtx://" schemes, causing
valid uppercase or mixed-case variants like "DTX://" to be filtered out and
ignored. Convert each argument to lowercase before checking if it starts with
the deep-link scheme by using `to_lowercase()` on the arg within the filter
closure, so the check becomes case-insensitive while preserving the original
case of the captured URL.
---
Outside diff comments:
In `@packages/dtx-desktop/src-tauri/src/songs.rs`:
- Around line 118-125: The code validates that song_path is within the workspace
bounds on line 118 using canonicalize_within_workspace, but then passes the
original song_path to export_song_folder_to_zip on line 125. This creates a
security vulnerability where a symlink could be changed between validation and
use. Store the result of the canonicalize_within_workspace call as the validated
canonical path, then pass this canonical path to export_song_folder_to_zip
instead of the original song_path to ensure the validated path is actually used
for the export operation.
---
Nitpick comments:
In `@packages/dtx-desktop/src-tauri/src/tests/api_tests.rs`:
- Around line 1464-1480: The test `upload_form_to_api_times_out_on_slow_server`
is slow because it waits for the full production timeout value
`API_REQUEST_TIMEOUT_MS`. Refactor the `upload_form_to_api` function to accept
an injectable HTTP client parameter with a configurable timeout, then create a
test helper that takes a custom timeout duration. In the test, inject a client
with a short timeout duration (e.g., a few milliseconds) instead of waiting for
the production timeout, and call the helper with this short-timeout client to
make the test run quickly while still verifying timeout behavior.
In `@packages/dtx-desktop/src/renderer/src/components/NewSong.svelte`:
- Line 6: In the NewSong.svelte file, the import statement for desktopHost
currently uses a relative path that should be replaced with the package alias
for consistency and maintainability. Change the desktopHost import statement
from using the relative path `../services/desktopHost` to instead use the
`$lib/` alias pattern, which will resolve to the same location but provides a
more stable path reference that is safer for future refactors.
In `@packages/dtx-desktop/src/renderer/src/components/Settings.svelte`:
- Line 5: The import statement for desktopHost currently uses a relative path
../services/desktopHost instead of following the package convention of using the
$lib/ alias. Replace the relative path import in Settings.svelte with the $lib/
aliased path, changing from '../services/desktopHost' to
'$lib/services/desktopHost' to match the codebase conventions for internal
package imports.
In `@packages/dtx-desktop/src/renderer/src/components/Settings.test.ts`:
- Around line 12-14: The vi.mock call for desktopHost is using a relative path
'../services/desktopHost' instead of the repository's $lib/ alias convention.
Replace the relative path string in the vi.mock function call with the
appropriate $lib/ alias path to match the project's import conventions and
ensure consistency across the codebase.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 10073051-9970-4d69-9f3d-287ae2e08b27
⛔ Files ignored due to path filters (1)
packages/dtx-desktop/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (45)
.github/workflows/desktop-build-deploy.yml.github/workflows/tauri-rust-ci.yml.husky/pre-commitCLAUDE.mdpackages/dtx-desktop/.gitignorepackages/dtx-desktop/src-tauri/Cargo.tomlpackages/dtx-desktop/src-tauri/src/api.rspackages/dtx-desktop/src-tauri/src/auth.rspackages/dtx-desktop/src-tauri/src/error.rspackages/dtx-desktop/src-tauri/src/filesystem.rspackages/dtx-desktop/src-tauri/src/lib.rspackages/dtx-desktop/src-tauri/src/macros.rspackages/dtx-desktop/src-tauri/src/models.rspackages/dtx-desktop/src-tauri/src/songs.rspackages/dtx-desktop/src-tauri/src/tests/api_tests.rspackages/dtx-desktop/src-tauri/src/tests/auth_tests.rspackages/dtx-desktop/src-tauri/src/tests/error_tests.rspackages/dtx-desktop/src-tauri/src/tests/filesystem_tests.rspackages/dtx-desktop/src-tauri/src/tests/lib_tests.rspackages/dtx-desktop/src-tauri/src/tests/models_tests.rspackages/dtx-desktop/src-tauri/src/tests/songs_tests.rspackages/dtx-desktop/src-tauri/src/tests/updater_tests.rspackages/dtx-desktop/src-tauri/src/updater.rspackages/dtx-desktop/src-tauri/tauri.conf.jsonpackages/dtx-desktop/src/renderer/index.htmlpackages/dtx-desktop/src/renderer/src/App.sveltepackages/dtx-desktop/src/renderer/src/components/DesktopEditor.test.tspackages/dtx-desktop/src/renderer/src/components/NewSong.sveltepackages/dtx-desktop/src/renderer/src/components/NewSong.test.tspackages/dtx-desktop/src/renderer/src/components/Settings.sveltepackages/dtx-desktop/src/renderer/src/components/Settings.test.tspackages/dtx-desktop/src/renderer/src/components/SongDetails.sveltepackages/dtx-desktop/src/renderer/src/components/SongDetails.test.tspackages/dtx-desktop/src/renderer/src/components/Templates.sveltepackages/dtx-desktop/src/renderer/src/components/Versions.sveltepackages/dtx-desktop/src/renderer/src/components/Versions.test.tspackages/dtx-desktop/src/renderer/src/main.tspackages/dtx-desktop/src/renderer/src/services/authService.test.tspackages/dtx-desktop/src/renderer/src/services/authService.tspackages/dtx-desktop/src/renderer/src/services/desktopHost.test.tspackages/dtx-desktop/src/renderer/src/services/desktopHost.tspackages/dtx-desktop/src/renderer/src/services/workspaceService.test.tspackages/dtx-desktop/src/renderer/src/services/workspaceService.tspackages/dtx-desktop/src/renderer/src/stores/settingsStore.test.tspackages/dtx-desktop/src/renderer/src/stores/settingsStore.ts
✅ Files skipped from review due to trivial changes (5)
- packages/dtx-desktop/src-tauri/src/tests/updater_tests.rs
- packages/dtx-desktop/src-tauri/src/tests/error_tests.rs
- packages/dtx-desktop/src-tauri/src/macros.rs
- .husky/pre-commit
- packages/dtx-desktop/.gitignore
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/dtx-desktop/src-tauri/src/error.rs
- packages/dtx-desktop/src-tauri/Cargo.toml
- packages/dtx-desktop/src/renderer/src/App.svelte
- .github/workflows/tauri-rust-ci.yml
- packages/dtx-desktop/src/renderer/src/components/NewSong.test.ts
- packages/dtx-desktop/src/renderer/src/components/DesktopEditor.test.ts
- packages/dtx-desktop/src/renderer/src/components/SongDetails.svelte
- packages/dtx-desktop/src-tauri/src/api.rs
- Add ancestor resolution for non-existent paths to prevent info disclosure - Use canonical paths after validation to eliminate TOCTOU windows in export - Accept case-insensitive URI schemes per RFC 3986 for deep links - Add injectable client variant for upload_form_to_api timeout tests - Improve directory iteration error handling in inspect_tree_folder - Add comprehensive test coverage for missing-path containment semantics Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Summary
electron-builder,electron-vite, main/preload processes, GraphQL client)desktopHostadapter in renderer to abstract Tauri IPCdesktopHosttest suiteTest plan
bun run --filter=dtx-desktop test)Breaking Changes
electron.vite.config.ts,electron-builder.yml, and allsrc/main//src/preload/codeGenerated with Devin
Summary by CodeRabbit