feat(lyrics): export editor output to standalone .lrc / .txt file#255
Conversation
Closes #201. Adds a third save option to the lyrics editor: write the serialized payload to an arbitrary on-disk path picked through the OS-native save dialog. Complements the two existing options (embed in audio file tag, DB cache only) β users who want the song's tag block kept clean can now ship the LRC/TXT as a sidecar next to the audio file, the convention every offline player (foobar2000, Musicolet, Spotify-style readers) already understands. ## Backend - New Tauri command `export_lyrics_to_path(target_path, content)` in `commands/lyrics.rs`: validates the parent dir exists, writes the bytes via `tokio::task::spawn_blocking + std::fs::write`. UTF-8 without BOM because LRCLIB / Musicolet / smaller offline players all read BOM-less files fine and a BOM trips some up. - Registered in `lib.rs` alongside the existing `save_lyrics` / `import_lrc_file` handlers. ## Frontend - `src/lib/tauri/lyrics.ts`: `exportLyricsToPath(targetPath, content)` wrapper. - `LyricsEditorModal`: extracted the content-serialization branch out of `handleSave` into a shared `buildPayload` helper so the new `handleExportToFile` doesn't have to re-implement the plain/line/word dispatch. - New `Save to fileβ¦` button in the footer next to `Save`, uses lucide `FileDown` icon. Opens `@tauri-apps/plugin-dialog`'s `save()` with sensible defaults: anchors at the song's parent directory with the song's basename as the filename stem + `.lrc` for synced output, `.txt` for plain. User can switch extensions in the dialog filter dropdown. - `LyricsPanel` passes `currentTrack?.file_path` through as the new `trackFilePath` prop so the dialog can land next to the song. - Helpers `filenameStem` + `defaultExportPath`: prefer the audio basename, fall back to a sanitized track title, then the literal `"lyrics"` so the dialog is never blank. Title sanitization strips the characters every common OS rejects (`/\:*?"<>|`) and collapses whitespace. ## i18n Three new keys under `lyricsEditor.*` propagated natively to all 17 locales: - `exportToFile` β button label (e.g. fr: "Enregistrer dans un fichierβ¦") - `exportToFileHint` β tooltip explaining the affordance doesn't touch the audio file - `exportedToFile` β success status (`{{path}}` interpolation) ## Validation - `cargo check --workspace --all-targets` clean - `cargo test --workspace` β 351 passing (baseline preserved) - `bun typecheck` + `bun lint` clean
|
No actionable comments were generated in the recent review. π βΉοΈ Recent review infoβοΈ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: π Files selected for processing (2)
π WalkthroughWalkthroughAjout d'une fonctionnalitΓ© d'export des paroles vers un fichier ChangesExport des paroles vers un fichier
Sequence Diagram(s)sequenceDiagram
actor User
participant LyricsEditorModal
participant showSaveDialog
participant exportLyricsToPath
participant TauriBackend
participant Filesystem
User->>LyricsEditorModal: Clique sur le bouton "Export to fileβ¦"
LyricsEditorModal->>LyricsEditorModal: buildPayload() β { content, format }
LyricsEditorModal->>LyricsEditorModal: defaultExportPath(trackFilePath) β defaultPath
LyricsEditorModal->>showSaveDialog: { defaultPath, filtres (.lrc, .txt) }
showSaveDialog-->>LyricsEditorModal: filePath | null
LyricsEditorModal->>exportLyricsToPath: invoke("export_lyrics_to_path", { targetPath, content })
exportLyricsToPath->>TauriBackend: export_lyrics_to_path(target_path, content)
TauriBackend->>Filesystem: spawn_blocking β fs::write(path, content)
Filesystem-->>TauriBackend: Ok(())
TauriBackend-->>LyricsEditorModal: Ok(())
LyricsEditorModal-->>User: Warning toast "Exported to {{path}}"
Estimated code review effortπ― 3 (Moderate) | β±οΈ ~25 minutes Poem
π₯ Pre-merge checks | β 5β Passed checks (5 passed)
βοΈ Tip: You can configure your own custom pre-merge checks in the settings. β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
π€ 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 `@src-tauri/crates/app/src/commands/lyrics.rs`:
- Around line 1579-1583: The export_lyrics_to_path function writes user data to
disk without first validating that an active profile exists, which violates the
coding guideline that all commands touching user data must go through
state.require_profile_pool().await?. Add a call to
state.require_profile_pool().await? at the beginning of the
export_lyrics_to_path function (after the path creation) to ensure the active
profile is validated before proceeding with the file write operation. This
requires accepting a state parameter in the function signature as well.
In `@src/components/common/LyricsEditorModal.tsx`:
- Around line 1077-1083: The `defaultExportPath` function currently returns
`null` when `filePath` is absent, but it should use the `stem` parameter as a
fallback to provide a suggested filename. Instead of immediately returning
`null` when `!filePath`, construct and return a default path using the `stem`
and `ext` parameters to ensure the export dialog always has a pre-filled
filename suggestion, even when no trackFilePath exists.
πͺ 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 77ca24ee-6852-4ca9-b1b5-f7867d0b9476
π Files selected for processing (22)
src-tauri/crates/app/src/commands/lyrics.rssrc-tauri/crates/app/src/lib.rssrc/components/common/LyricsEditorModal.tsxsrc/components/layout/LyricsPanel.tsxsrc/i18n/locales/ar.jsonsrc/i18n/locales/de.jsonsrc/i18n/locales/en.jsonsrc/i18n/locales/es.jsonsrc/i18n/locales/fr.jsonsrc/i18n/locales/hi.jsonsrc/i18n/locales/id.jsonsrc/i18n/locales/it.jsonsrc/i18n/locales/ja.jsonsrc/i18n/locales/ko.jsonsrc/i18n/locales/nl.jsonsrc/i18n/locales/pt-BR.jsonsrc/i18n/locales/pt.jsonsrc/i18n/locales/ru.jsonsrc/i18n/locales/tr.jsonsrc/i18n/locales/zh-CN.jsonsrc/i18n/locales/zh-TW.jsonsrc/lib/tauri/lyrics.ts
## 1 β export_lyrics_to_path missing active-profile sentry
CLAUDE.md cross-cutting rule: "every command that touches user
data goes through `state.require_profile_pool().await?`". The
command writes to the filesystem (not the per-profile DB) so
strict reading of the rule wouldn't require it β but adding
the gate provides defence in depth (no active profile β editor
unreachable β IPC payload to this command is malformed) and
aligns the signature with every sibling
`commands::lyrics::*` handler. Cheap to add, hard to argue
against.
- `export_lyrics_to_path` now takes `state: tauri::State<'_,
AppState>` as its first param.
- Calls `state.require_profile_pool().await?` first thing and
drops the pool β the command never touches SQLite.
- Doc-comment extended to explain the sentry's role + that
the pool isn't actually read.
## 2 β `defaultExportPath` returned `null` on missing filePath
The Tauri save dialog was getting `defaultPath: undefined` when
the editor opened on a track with no resolved `file_path`,
leaving the filename field blank. Returning
`${stem}.${ext}` as the fallback gives the OS-native dialog a
filename to pre-fill β it interprets a name-only `defaultPath`
as "last-used directory + this name" on Windows / macOS /
Linux, which is the right UX when we don't have a song path
to anchor on.
- `defaultExportPath` return type tightened from `string | null`
to `string` since it now always produces one.
- Caller drops the `?? undefined` guard at the dialog
invocation since the value can't be null any more.
## Validation
- `cargo check --workspace --all-targets` clean
- `bun typecheck` + `bun lint` clean
Closes #201.
Summary
Adds a third save option to the lyrics editor: write the serialized payload to an arbitrary on-disk path picked through the OS-native save dialog. The two existing options (embed in audio file tag, DB cache only) stay available. Users who want the song's tag block kept clean can now ship the LRC/TXT as a sidecar next to the audio file, the convention every offline player (foobar2000, Musicolet, Spotify-style readers) already understands.
Backend
Frontend
i18n
Three new `lyricsEditor.{exportToFile, exportToFileHint, exportedToFile}` keys propagated natively to all 17 locales.
Test plan
Summary by CodeRabbit
Release Notes
Nouvelles FonctionnalitΓ©s
Localisation