- New crates/renamr-core/src/history module: HistoryBatch/HistoryEntry
records, revert_batch() moves files back to their original paths
(skip-on-conflict, same policy as rename::execute_renames; recreates a
deleted destination folder defensively), push_batch() maintains a
most-recent-first log capped at 50 batches. A partial port of the idea
behind net.filebot.HistoryDialog, as a flat undo stack rather than a full
session/element log. 6 new tests.
- New src-tauri/src/history.rs: persists the log as history.json in the OS
app-config directory (same location as settings.json). execute_rename now
takes an AppHandle and calls history::record_batch after every rename,
recording only the items that actually got renamed. Two new commands:
list_rename_history and undo_rename_batch.
- Frontend: the History button (previously a no-op) now opens a panel
listing past rename batches with a timestamp, file count, and the
old-name/new-name pairs, each with an Undo button. Undoing reconciles any
currently-loaded item back to its pre-rename path/name, same pattern as
post-rename reconciliation.
- Verified via cargo test (45 total)/check + npm build + a full Playwright
pass: loaded mocked files, renamed them, opened History, undid the batch,
and confirmed both the UI and the (mocked) backend state matched at every
step.
- rename::rename_one now resolves new_name relative to the source file's
own directory, allowing / as a subdirectory separator (e.g. a naming
scheme like {show name}/{episode name}) instead of rejecting it. Missing
intermediate directories are created automatically via create_dir_all.
- New validate_relative_target() replaces the old is_plain_filename():
still rejects anything that could escape the source directory (empty
path segments - which also catches a leading-slash absolute path -
and . / .. segments) plus backslashes (Renamr always treats / as the
scheme's folder separator regardless of OS), but now allows a
legitimate multi-segment relative path through.
- The "Unchanged" check now compares the full resolved target path to the
source, not just the filename, since a computed name can now include
directory components.
- 6 new tests: organizing into a new subfolder, nested subfolders that
don't exist yet, conflict detection still works for organized targets,
and rejection of backslashes / doubled / leading / trailing slashes.
- Frontend: naming-scheme editor hint explains the / convention, and the
rename confirmation modal notes when a plan will create new subfolders.
- No changes needed in the naming-expression engine itself - it already
treats / between two {} scriptlets as ordinary literal text.
- New crates/renamr-core/src/similarity module: a partial port of the
scoring idea behind FileBot's EpisodeMetrics.java - normalizes titles
(lowercase, strips a trailing release-year parenthetical, collapses
punctuation/whitespace) and scores them with a Sorensen-Dice coefficient
over character bigrams. best_title_match() picks whichever candidate is
textually closest to the query, provided it clears a minimum threshold;
ties keep the earlier (i.e. TMDB-ranked) candidate.
- TmdbClient::lookup_episode now uses best_title_match instead of blindly
taking the first search result, so a more popular but differently-named
show can no longer beat an exact-but-lower-ranked match to the top - and
reports NotFound instead of guessing when nothing in the results is a
plausible match at all.
- 7 new tests (5 in similarity, 2 in tmdb) including the concrete regression
case this fixes: searching "Chuck" with TMDB returning "Chucky" ranked
above "Chuck" now correctly picks "Chuck".
- Also fixes a gitignore gap from last delivery: the _to_delete*/ scratch-dir
pattern used to work around this repo's mounted-folder git-lock quirk had
only ever been applied directly on the device, never carried back into the
zip this delivery is built from - so the last delivery's cp -rf silently
reintroduced the untracked-lock-file risk. Fixed in both places now.
- ExpressionFormat::compile_scriptlet now falls back to treating a {token} as a
literal multi-word binding key when it's neither a valid Rhai identifier nor
valid Rhai script, but still looks like a plain human-typed field name (only
letters/digits/underscores/spaces). This is what lets `{show name}` work
without anyone needing to know Rhai, while real Rhai script (even containing
spaces) still takes priority and genuine syntax errors still surface.
- New default naming scheme app-wide: `{show name} - {season number}x
{episode number} - {episode name}` (previously the terser {n} - {s00e00} -
{t} form). Match/Fetch Data auto-upgrade targets updated to match, and
sample items carry both the short FileBot-style keys and the new
human-readable ones side by side.
- Naming scheme editor UI rewritten: explains the field is fully editable,
lists available fields grouped by when they become available (always /
after Match / after Fetch Data), and clarifies Rhai script is also
supported for anyone who wants it.
- 3 new unit tests for the plain-field-name fallback (multi-word lookup
works, real Rhai still takes priority, genuine syntax errors still error).