Previously these only appeared as plain, non-clickable <code> text in the hint
paragraph below the field-chip groups, styled almost identically to the real
chips (same tinted-pill background) - a user reported field references as
'not clickable, you have to type it out' after clicking that inert text
instead of the real chips above it. Added a proper 'Short names' chip group
so they're actually clickable, and changed the remaining plain hint-text code
snippets to a dotted underline instead of a pill background so they can't be
mistaken for buttons again.
Windows section now gives real, actionable steps (MSVC toolchain via rustup,
VS Build Tools with the Desktop development with C++ workload, WebView2 note,
NSIS auto-download note) instead of a one-line mention. Also corrects the
stale verification paragraph: test count (199, not 160) and confirms the
production build now launches and runs correctly outside dev mode on both
macOS and Linux (Windows remains the one unverified platform).
User feedback after delivery 26: "wheres white and black themes?" -
the previous "Light" theme (white bg, black text) was a reasonable
literal reading of "have white/black" as one combined theme, but the
user meant two distinct named themes.
Renamed that theme's id/label from "light" to "White" and added a
genuinely new "Black" theme: a true near-black background with a
deliberately grayscale accent (light neutral grays instead of any
hue), so it reads as an actual black-and-white theme rather than
"Aurora but darker" - Aurora already covers the teal/green-accented
dark look. White keeps its colorful green/cyan/lilac accents, which
is now the visual difference between the two beyond just background
color. Settings now lists 5 themes total (Aurora/White/Black/Ocean/
Sunset); the picker grid moved from 2 to 3 columns to fit them
better.
Verified with an expanded Playwright script (12 checks, up from 10):
confirms all 5 themes list correctly, White and Black each apply
their real computed background color (not just the data-theme
attribute) and persist via save_theme, and the rest of the boot-time/
persistence behavior from delivery 26 still holds. All prior
verification suites (47 checks), `cargo test --workspace` (162
tests), and `npm run build` still pass clean.
Requested directly: "lets add some themeing into it now, perhaps
under the configuration, have white/black, and some more colors."
There was no existing app-wide Settings/Configuration surface -
every prior setting lived in its own panel-specific modal (Fetch
Data Settings, subtitle provider settings) - so this adds the
app's first one: a palette-icon button in the sidebar footer opens
a new Settings modal with a 2x2 grid of theme swatches.
Four themes: Aurora (the existing dark teal/green look, unchanged,
stays the default), Light (a clean white/black theme - the literal
"white/black" ask), Ocean (a cool blue-toned dark variant), and
Sunset (a warm amber-toned dark variant) - the "some more colors"
part. Picking one applies instantly via a `data-theme` attribute on
<html> and persists through new get_theme/save_theme commands
(added to settings.rs/lib.rs alongside delivery 25's movie-naming-
scheme fix), applied on every future launch by a boot-time effect
in App.tsx so it takes effect even if Settings is never reopened.
theme.css's variable set was expanded to cover every place a color
was previously hardcoded outside the existing --dock/--surface/etc.
variables, so the whole app actually re-themes instead of leaving
dark boxes behind on the Light theme: button resting/hover/active
shades, the Load-files popup background, the primary button's hover
tint, the header's gradient stops, and five levels of white-tint
row/button hover highlights (RenamePanel.css/Header.css) all moved
to new variables (--btn-bg*, --popup-bg, --accent-hover, --header-
grad-a/b, --tint-02/03/05/06/16) with theme-specific values - the
light theme in particular needed dark tints instead of white ones
for hover states to stay visible on a white background.
Verified with a new Playwright script covering: default theme on a
fresh install, the Settings modal listing all 4 themes with the
right one marked selected, clicking a theme applying it immediately
(actual computed background color and CSS variable checked, not
just the data-theme attribute) and saving it, and a previously-saved
theme applying on boot before Settings is ever opened. Screenshots
taken of all 4 themes across the main view and the Naming Scheme
editor (whose buttons/popups were part of the hardcoded-color
cleanup) to confirm visually. All prior verification suites (47
checks across naming-scheme/match-fetch/hash-files/movie-scheme),
`cargo test --workspace` (162 tests), and `npm run build` still
pass clean.
The Rename panel used a single shared naming-scheme expression for
every item regardless of media kind. A movie item never gets
"show name"/"season number"/"episode number"/"episode name" bindings
(guess_movie sets "n"/"y"/"movie name"/"year" instead) - so rendering
a movie against a TV-oriented scheme like
"{show name} - {season number}x{episode number} - {episode name}"
left nothing but the scheme's own literal text: "- x -", exactly as
reported (screenshot of a real movie file after Match).
TV and movie items now render against two independent naming
schemes (`expression` for TV, a new `movieExpression` for movies),
each persisted separately (new `last_movie_naming_scheme` settings
field/commands, mirroring the existing TV ones) and each editable
through a "TV Shows" / "Movies" toggle added to the Naming Scheme
editor - presets, field chips, and the 1-9 hotkeys all act on
whichever one is currently selected. The movie scheme needs no
onboarding auto-upgrade chain (unlike TV's sample -> matched ->
fetched staging): a movie item only ever has a title and year, both
already available right after local Match, so it just starts at a
sensible default ("{movie name} ({year})") instead.
A mixed batch of TV episodes and movies now renders both kinds
correctly at the same time, each against its own scheme. Verified
with a new Playwright script reproducing the exact reported scenario
(load a movie, click Match) plus the toggle/independent-persistence
behavior; all prior naming-scheme/match-fetch/hash-files suites,
`cargo test --workspace` (162 tests), and `npm run build` still pass
clean.
The Match/Fetch "showcase the next field" auto-upgrade heuristics in
RenamePanel.tsx each detected "the user hasn't customized the naming
scheme yet" via an exact string match against SAMPLE_EXPRESSION /
REAL_FILE_EXPRESSION / MATCHED_EXPRESSION. That was safe before the
naming-scheme persistence feature existed, since in-memory expression
state could only reach one of those exact strings by progressing
through the auto-upgrade chain itself.
Once persistence restores an arbitrary saved string on mount, a
user's real custom scheme can legitimately equal one of those
constants (this project's own default sample scheme is textually
identical to the fully-fetched milestone expression) - so loading
files, matching, or fetching would silently reset a correctly
restored scheme back to a "simpler" milestone the next time a
checkpoint's condition happened to match, undoing the just-loaded
persisted value.
Added restoredCustomSchemeRef, set once a real non-blank scheme is
restored from disk, and gated all three auto-upgrade checkpoints on
it so the onboarding auto-simplify chain only ever fires for a
genuinely fresh session with nothing persisted - never for a
returning session whose scheme was explicitly restored, regardless
of what that restored text happens to equal. Verified with a new
Playwright script reproducing the exact collision (persisted scheme
== SAMPLE_EXPRESSION), confirming the scheme now survives Load
Files, Match, and Fetch Data intact; all prior naming-scheme/
match-fetch/hash-files verification suites and `npm run build`
still pass clean.
Closes the last remaining gap in porting-roadmap item 1 (full naming-
scheme bindings) - user picked this from a short list of next-feature
options after the recent naming-scheme/Match usability work.
crates/aurora-curator-core/src/sfv: new compute_all_checksums(path),
alongside the existing compute_checksum(path, kind). Streams the file
ONCE, updating CRC32/MD5/SHA-1 hashers concurrently over each chunk,
rather than calling compute_checksum three times (three full reads of
what's often a multi-gigabyte video file). The SFV panel itself keeps
using compute_checksum directly - it only ever needs one algorithm at a
time, picked by the user, so computing the other two nobody asked for
would be wasteful in the other direction. 3 new tests: matches known
CRC32/MD5/SHA-1 test vectors together, agrees with compute_checksum on a
file bigger than the read buffer (catches a hasher only seeing the first
chunk), errors on a missing file.
src-tauri/src/sfv.rs: new NamingHashOutcome struct (plain
#[serde(rename_all = "camelCase")] struct, not a tagged enum - the class
of bug fixed in the nineteenth delivery doesn't apply here) and
compute_naming_hashes command, batching compute_all_checksums the same
blocking-thread way compute_checksums already does, with the same
per-file error isolation (one bad file doesn't abort the batch).
Registered in lib.rs.
RenamePanel.tsx: new runHashFiles() and a "Hash Files" button (new
HashIcon in Icons.tsx) next to Fetch Data. Deliberately NOT wired into
Match automatically like the rest of its local steps - hashing reads a
file's entire contents rather than a small header, so running it
unasked on every Match click for every loaded file would make Match
unpredictably slow. Its own busy state (hashing) disables Match/Fetch
Data/Hash Files against each other while any one is running. New "File
hashing" field-reference group ({crc32}/{md5}/{sha1} as clickable chips,
reusing the field-chip insert-at-cursor mechanism from the earlier
naming-scheme UX delivery) and updated inline help text.
Verified via cargo test --workspace (191 -> 193 in the core crate: 145 ->
148) + cargo check --workspace (clean) + npm run build (clean) + a
10-check Playwright script against npm run dev (mocking
window.__TAURI_INTERNALS__.invoke): confirmed the three field chips are
present, Hash Files calls compute_naming_hashes with every loaded path,
the status line correctly reports hashed-vs-failed counts, a computed
CRC32 reaches the live rename preview, a failed file's row doesn't
fabricate a hash, and clicking Hash Files with nothing loaded is handled
gracefully. Caught one test-mock bug along the way (not an app bug): an
early mock response for a successful hash omitted the `error` field
entirely rather than setting it to `null`, which the app correctly (per
the real Rust command's always-present Option<String> field) treated as
a truthy non-null value and skipped merging - fixed the mock to match
the real wire contract.
Updated README (status paragraph, project layout for sfv.rs's now-four
commands, a new "Hash Files" section placed after "Real media-file
probing", porting roadmap item 1's "still missing" list).
The full rebrand (session 9, seventh delivery) covered the app's own
identity - name, icon, no FileBot/FB-Mod branding visible - but two
literal "FileBot" mentions were still reachable by the end user, both
found via a targeted grep for FileBot/Filebot/filebot/FB-Mod across every
string actually rendered to a user (JSX text and CLI help/usage text),
as opposed to source comments and README documentation, which still
legitimately explain what this project is a rewrite of and aren't shown
inside the running app:
- The Naming Scheme editor's own inline help text described the FileBot-
style short field names ({n}, {s00e00}, {t}, etc.) as "FileBot's own
short field names" - reworded to just "Short field names", same
meaning, no brand name.
- The CLI's -h/--help output described -non-strict as "Accepted for
FileBot script compatibility" - reworded to "Accepted for compatibility
with existing rename scripts".
Grepped the whole frontend and every Rust source string (not just these
two files) to confirm nothing else user-visible remained - tauri.conf.json,
Cargo.toml, and package.json were already clean. What's left after this
is exclusively source-code comments/doc-comments and README.md, none of
which the app itself ever displays.
Verified via cargo test --workspace (193/193, unaffected - text-only
change) + npm run build (clean).
Requested directly: "when you click Match, I would prefer it to do what
the lookup does. pulls the info, renames properly. just one less step
needed." Match's local filename scan and Fetch Data's online lookup were
two separate button clicks with no relationship between them in code -
Fetch Data just happened to require Match to have already run.
New runMatchAndFetch() wraps both: runs the same local match as before,
then - if it found anything recognizable and either a credential-free
source (TVmaze) is selected or the needed credential is already saved -
immediately fetches real online metadata for it too, exactly as if the
user had clicked both buttons in sequence. The standalone "Fetch Data"
button next to New Names is untouched and still works on its own, for
re-fetching after changing the metadata source or fixing a guessed name
by hand.
Two supporting changes made this safe to chain:
- runMatching() now computes its updated items as a plain array
(`items.map(...)`) instead of the usual `setItems((prev) =>
prev.map(...))` functional-update form, and returns that array. Chaining
straight into runFetch() needed the *actual* just-matched items, not
`items` React state - which wouldn't reflect the match yet, since
setItems() only queues a re-render rather than updating what this
function's own closure sees.
- runFetch() gained itemsOverride/silentIfEmpty/chained options: read
targets from the array just handed to it rather than state; skip the
"Run Match first" message when Match simply found nothing to look up
(its own message already covers that); and skip auto-opening the
Settings modal on a missing credential when chained, so someone who
hasn't set up Fetch Data (or doesn't want to) isn't interrupted by a
popup on every single Match click - the status line still explains why
fields are staying blank either way.
Also fixed a status-message race: both functions clear their own status
after a timeout, and chaining them naively could let Match's leftover
timer blank out Fetch Data's result message early (observed while
testing: a fast TVmaze lookup's "Fetched..." message got wiped a few
seconds later by Match's own stale timer). Match now skips scheduling its
success-path timer when chained, so whichever step actually finishes last
owns the status line's timer.
Verified via npm run build (clean, no Rust changes needed) + an 11-check
Playwright script against npm run dev (mocking
window.__TAURI_INTERNALS__.invoke): confirmed Match triggers guess_episode
then automatically fetch_metadata with no separate click, in that order;
the fetched episode name reaches the live preview; a missing TMDB token
still runs the local match and explains the gap in the status line
without forcing the Settings modal open; and clicking Match with nothing
loaded is handled gracefully.
Updated the naming-scheme editor's inline help text and the Match
button's tooltip to describe the new combined behavior.
Two usability fixes to the naming-scheme editor, both reported/requested
directly: the field-reference list ({show name}, {episode number}, etc.)
was plain unclickable text, so building a scheme meant typing every field
name out by hand; and the editor's text always reset to the built-in
sample expression on every launch, since nothing ever wrote it to disk -
only the explicit 1-9 preset slots (presets.rs) were persisted.
Field list -> clickable buttons: each field reference is now a real
<button> (.format-field-chip) wired to insertField(), which reads the
input's real DOM selectionStart/selectionEnd and inserts the token at
the caret, then restores focus and moves the caret to just after the
inserted text so clicking several fields in a row builds up
left-to-right. Purely a frontend change - no new Tauri commands needed.
Persistence: added last_naming_scheme to settings.rs's StoredSettings
(same settings.json already used for tv/movie source, credentials,
etc.), plus get_last_naming_scheme/save_last_naming_scheme commands.
RenamePanel.tsx loads it once on mount (restoring the saved scheme
before the user can interact with anything) and saves it back debounced
(600ms after typing settles) as the user edits - gated behind a
schemeLoaded flag so the save effect can never fire before the load
completes and clobber a saved scheme with the not-yet-replaced sample
default. An empty/blank saved value (fresh install) leaves the built-in
sample expression in place, same fallback behavior as every other
optional setting in this file.
Verified via cargo test --workspace (191 -> 193, two new settings.rs
tests: schema-evolution for the new field, and the blank-clears-to-None
behavior) + npm run build (clean) + a 7-check Playwright script against
npm run dev (mocking window.__TAURI_INTERNALS__.invoke): confirmed the
editor restores a mocked saved scheme on launch, clicking a field chip
inserts its token at a caret placed via setSelectionRange (not just
appended), a second click lands immediately after the first rather than
at the old position, and save_last_naming_scheme fires once (debounced)
with the exact edited text rather than on every keystroke.
Updated README's naming-scheme paragraph to describe both.
`#[serde(tag = "status", rename_all = "camelCase")]` on a tagged enum only
renames the tag's own values ("Found" -> "found"), not the field names
inside each struct-like variant - so `series_name` stayed `series_name` on
the wire instead of becoming `seriesName`. The frontend only ever reads
camelCase, so these fields came back `undefined`, and JSON.stringify
silently drops undefined keys when Tauri invoke args are serialized -
making the bug invisible rather than throwing.
This is why Fetch Data (TMDB/TVmaze/OMDb) blanked out {show name} and
{episode name} in naming schemes even on a full "found" result: reported
by the user with screenshots showing the naming preview correct after
Match but blank after Fetch Data.
The same pattern existed in 4 more places, fixed identically by adding
`rename_all_fields = "camelCase"` alongside the existing tag/rename_all:
- metadata.rs: FetchOutcome (the reported bug) and MovieFetchOutcome
- episodes.rs: EpisodeListOutcome
- history/mod.rs: RevertOutcome (restoredPath) - broke Undo reconciliation
- rename/mod.rs: RenameOutcome (newPath) - broke post-rename item tracking
after a REAL on-disk rename; RenamePanel.tsx calls
fileNameFromPath(result.outcome.newPath), which throws on undefined,
so this was likely the most severe of the five.
No frontend changes needed - the TypeScript side already expected
camelCase throughout. Added 5 permanent regression tests (one per fixed
enum, two in metadata.rs) that assert the exact camelCase JSON keys and
assert the old snake_case keys are absent. cargo test --workspace: 186 ->
191 passing. npm run build: clean, no frontend diff.
Extends the sixteenth delivery's container-header probing
(probe_media_files) beyond the Rename panel: Filter and List now run the
same batched probe alongside their existing local scan, using the same
override rule everywhere - a filename tag wins when present, otherwise a
real probed value fills in.
List: {width}/{height}/{fps}/{duration}/{file size} are now available in
naming-scheme-style expressions (no filename-tag equivalent exists), and a
probed {resolution}/{video codec}/{audio codec} fills in when the filename
didn't already carry that tag.
Filter: gained Resolution and Video codec chip rows (populated from the
same merged filename-tag-or-probed value, with an Unknown bucket for files
with neither) so a batch can be narrowed by either. Each result row's
subtitle now also shows the resolved resolution when one exists.
Verified via cargo check/test --workspace (unaffected, frontend-only
change) + npm run build (clean) + a 15-check Playwright script against
npm run dev covering both panels: filename-tag resolution winning over a
conflicting probed value, probed fields filling in for an otherwise-
unmatched file, the new Filter chip rows narrowing results correctly.
Updated README (status paragraph, Filter section, List section, Real
media-file probing section, roadmap item 1).
Ports the next slice of FileBot's ExpressionFormatMethods.java as Rhai-callable
methods usable inside any { } naming-scheme expression, alongside the existing
any/all_of/concat helpers:
- pad/pad_with: left-pad a value's string form (default '0'), non-mutating,
non-truncating - deliberately a different Rhai method name for the 3-arg
custom-char form (pad_with) since Rhai's own built-in pad(length, char)
right-pads and mutates in place, which collides in behavior (not API) with
what FileBot's zero-padded season/episode numbers need.
- sort_name/sort_initial: move a leading The/A/An article to the end for
alphabetical sorting, e.g. "The Office" -> "Office, The".
- upper_initial/lower_initial: case-convert only the first character.
- acronym: first letter of each word, uppercased.
- validate_file_name: strip characters illegal in filenames, collapse
whitespace runs, trim trailing dots.
- space/space_with, colon/colon_with: separator and colon normalization.
16 new unit tests in functions.rs (143 lib tests total, up from 126) plus 3
new integration tests in expression_format.rs demonstrating the helpers used
inside real naming-scheme expressions (14 total, up from 11). Verified via
cargo test --workspace, cargo check --workspace, and npm run build, all
clean. Updated README (status paragraph and porting-roadmap item 2).
Wires real container-header probing into the Rename panel's Match step, the
first item on the porting roadmap: aurora-curator-core::probe reads actual
resolution, frame rate, duration, and video/audio codecs straight from a
file's own header (mp4 crate for MP4/M4V/MOV, matroska crate for MKV/WebM,
no ffmpeg/MediaInfo dependency), batched through a new probe_media_files
Tauri command. Probed values fill {resolution}/{video codec}/{audio codec}
only when a filename release tag hasn't already, and populate five bindings
with no filename-tag equivalent at all: {width}, {height}, {fps},
{duration}, {file size}. 8 new Rust unit tests against real ffmpeg-generated
fixture files (161 -> 169 total), verified end to end with a 15-check
Playwright script against npm run dev, and README updated (status
paragraph, project layout, a new "Real media-file probing" section, and
roadmap item 1 narrowed to what's still missing).
Renders a naming-scheme-style expression per loaded file via the same
render_expression engine the Rename panel's live preview uses, fed by
the same local guess_episode/guess_movie/guess_release_info matching
Filter already uses. Copy List and Export to File... (a new
export_text_list Tauri command - the only new backend surface this
feature needed) round it out. Closes out all six original sidebar
panels as real screens.
Fully local, and adds no new Rust/Tauri commands at all - reuses pick_files plus the
same guess_episode/guess_movie/guess_release_info local matchers "Match" already calls
in the Rename panel, just applied to a second, independently-loaded file list and
rendered as toggleable filter criteria instead of a rename preview.
New src/components/FilterPanel.tsx/.css: Load Files scans the batch immediately,
toggleable chips appear for every extension and media kind actually present, a
filename-contains text field and a season number field (TV only) narrow further, and
Copy File Paths exports whatever currently matches to the clipboard.
Verified with a 21-check Playwright script against npm run dev (chip toggling, the
filename/season filters, the no-match empty state, and the clipboard export).
Fully local feature - no network access, no credentials. New aurora-curator-core::sfv
module streams file hashing in 64KB chunks and handles both the traditional .sfv
format (CRC32, "name checksum" layout) and the GNU coreutils md5sum/sha1sum sidecar
convention (.md5/.sha1, "checksum name" layout), dispatched on a shared ChecksumKind
enum. New src-tauri/src/sfv.rs wraps it in three Tauri commands - compute_checksums
(per-file error isolation), save_checksum_file (native Save dialog), and
verify_checksum_file (resolves each entry against the checksum file's own parent
directory, reports OK/Mismatch/Missing per entry). New SfvPanel.tsx/.css wires it all
into the sidebar's SFV screen - load files, pick a checksum type, Compute, Save, or
Verify against an existing checksum file.
14 new core-crate tests + 1 new src-tauri test, all passing (workspace: 160/160).
Verified end-to-end with a 22-check Playwright script against npm run dev.
New src-tauri/src/episodes.rs exposes browse_episodes, reusing the
existing AnyTvClient::fetch_season machinery Fetch Data already built
(via a build_tv_client helper factored out of metadata.rs) so this
panel needs no credentials of its own - it reads whatever TV source
(TMDB/TVmaze/OMDb) is already configured in Fetch Data Settings.
New EpisodesPanel.tsx/.css: series name + season stepper, an episode
list (number/title/air date), and a Copy list button that copies the
season as plain text. Wired into App.tsx's sidebar routing as the
second stub panel (after Subtitles) to become a real screen.
145/145 Rust tests passing (+1), npm run build clean, 15/15 Playwright
checks against npm run dev (mocked Tauri bridge) covering search,
the empty-season case, a not-found series, the season stepper, Copy
list, and the blank-query guard.
New web client (crates/aurora-curator-core/src/web/opensubtitles.rs, 9 tests):
search, login, download, and fetch-subtitle-text against OpenSubtitles' REST
API. Search only needs a free app-level API key; download additionally needs
the end user's own OpenSubtitles account login (username/password -> JWT),
since downloads are quota-limited per account. Settings gained three new
fields (opensubtitles_api_key/username/password) following the existing
TMDB/OMDb credential pattern, with one deliberate exception: the password is
never returned to the frontend, only whether one is saved
(has_opensubtitles_password), since it's a real account password rather than
an API token.
New src-tauri/src/subtitles.rs wraps search/download as Tauri commands and
writes a downloaded subtitle right next to its video file
(Show.S01E01.mkv -> Show.S01E01.en.srt).
New src/components/SubtitlesPanel.tsx is the first real screen behind a
sidebar entry besides Rename (previously a stub) - load a video, auto-fill
the search form from the same local guess_episode/guess_movie Match uses,
search, and download with live remaining/allowed quota shown per result.
TheTVDB was considered instead but deliberately skipped for now: its v4 API
requires either a paid commercial license or a per-end-user paid TheTVDB
subscription just to log in, unlike every other source in this app.
144 tests passing (workspace). Updated README (status paragraph, project
layout, new "Subtitles" section, roadmap items 5/6/7).
Movies get their own independent metadata-source setting for the first
time (previously hardcoded to TMDB) so the GUI's Fetch Data Settings now
has two separate pickers - TV episode source (TMDB/TVmaze/OMDb) and
movie source (TMDB/OMDb) - and a batch can freely mix sources across its
TV and movie halves. The -rename CLI's --db flag gained omdb alongside
tmdb/tvmaze, with movies now looked up online for both tmdb and omdb.
Ports FileBot's net.filebot.cli flag surface so Aurora Curator can be scripted,
not just driven through the GUI. Argument parsing and file-expansion are pure
Rust in aurora-curator-core::cli (14 unit tests, no Tauri/network dependency);
orchestration lives in src-tauri/src/cli.rs, which reuses the exact same
binding vocabulary the GUI's RenamePanel.tsx builds client-side and the exact
same TMDB/TVmaze season-caching lookup fetch_metadata already uses, so a
naming scheme renders identically whether it's typed into the app or passed
via --format.
main.rs branches to CLI mode before any Tauri App/window is built at all,
using tauri::async_runtime::block_on to drive the async metadata lookups
(confirmed safe standalone by reading Tauri's own async_runtime.rs) and a
hand-rolled settings::cli_tmdb_token() that replicates Tauri's own
app_config_dir() computation (dirs::config_dir().join(identifier), pinned
against tauri.conf.json by a new test) so a token saved through the GUI's
Fetch Data Settings is found from the CLI path too - the CLI still never
accepts a token as an argument.
Deliberately not ported: --output <dir> (the rename engine's safety
invariant against escaping the source directory tree would need to be
relaxed) and -non-strict is accepted as a no-op (local matching has no
confidence score to loosen).
16 new tests in src-tauri (10 in cli.rs, 1 pinning settings::
APP_CONFIG_IDENTIFIER) plus 14 in aurora-curator-core's cli module - full
workspace suite is 113 passing. README updated with a new "Command-line
usage" section and roadmap item 6 marked done for the CLI surface.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QLbx8ov7zMcdDPEqQm4Y98
TV episodes can now resolve against either TMDB (default) or TVmaze - picked
via a segmented source picker in the renamed "Fetch Data Settings" modal.
TVmaze is free and needs no API token at all; movies still always resolve
against TMDB regardless of the choice, since TVmaze has no movie catalog.
- New TvmazeClient (crates/aurora-curator-core/src/web/tvmaze.rs): search +
season/episode lookup against api.tvmaze.com, filtering out unnumbered
specials rather than panicking on them (TVmaze returns every season's
episodes in one flat list, unlike TMDB's per-season endpoint).
- New TvMetadataSource setting (settings.rs), persisted alongside the TMDB
token, defaulting to Tmdb for schema-evolution safety.
- metadata.rs now branches TV fetches through an AnyTvClient wrapper so the
existing per-(series, season) caching loop doesn't care which source
produced a result; the movie-fetch path is untouched.
- Frontend: TMDB/TVmaze picker in Fetch Data Settings, persisted on change;
Fetch Data's token gating now only requires a TMDB token when it's actually
needed (movies always, TV only when TMDB is the chosen source) instead of
unconditionally blocking on it.
cargo test --workspace: 88/88 passing (81 + 7 new TVmaze + 3 new settings).
npm run build passing. Playwright verification: 18/18 checks (picker renders
and persists across reload, TV Fetch Data succeeds on TVmaze with zero TMDB
token configured, movies still correctly demand a token even with TVmaze
selected, switching back to TMDB still works end-to-end).
New ConflictPolicy enum in rename/ (Skip = original/default behavior,
AutoNumber = save as "Name (1).ext" counting up to the first free slot,
Override = replace the conflicting file outright), threaded through
execute_renames/rename_one alongside RenameAction. Override never deletes
a conflicting directory (falls back to Skip for that case); AutoNumber
sidesteps directories the same way by just trying the next number.
execute_rename now takes a conflict argument; undo/history are
deliberately NOT conflict-aware and always use their own fixed
skip-on-conflict policy regardless of what the original rename used.
Frontend: a second segmented selector in the rename confirmation modal
("On conflict: Skip / Auto-number / Override"), with Override styled in
red and its warning copy calling out that it can't be undone.
Align Up/Down now swaps the selected file with its neighbor in the shared
item list (Original Files and New Names stay positionally paired, matching
the original FileBot's "align" buttons for manually lining up mismatched
lists).
Naming-scheme Presets: up to 9 saved expressions, one per digit slot,
persisted as presets.json (src-tauri/src/presets.rs, same load/save
pattern as settings.rs/history.rs). A new Presets panel lists all 9 slots
with Save Current / Apply / Clear, and a global 1-9 keydown handler applies
a slot instantly from anywhere in the app (ignored while a text field is
focused or a modal is open).
Match now also parses filename-only release-tag text - resolution, source
(BluRay/HDTV/WEB-DL/...), video/audio codec, and release group - via a new
guess_release_info() in renamr-core's matching module, wired through a new
guess_release_info Tauri command and called alongside the existing TV/movie
guess for every file. Naming schemes can now reference
{resolution}/{source}/{video codec}/{audio codec}/{release group}, or
FileBot's own short names ({vf}/{source}/{vc}/{ac}/{group}).
- renamr-core::rename: new RenameAction enum (Move/Copy/Symlink, default Move,
mirroring net.filebot.cli.CmdlineOperations's --action modes). execute_renames() now
takes an action applied to the whole batch. Copy uses fs::copy (source untouched);
Symlink uses std::os::unix::fs::symlink / std::os::windows::fs::symlink_file behind a
cfg(unix)/cfg(windows) split. Same conflict policy (skip, never overwrite) and
auto-created destination folders apply to all three actions. 8 new tests.
- renamr-core::history: HistoryBatch now records which action produced it
(#[serde(default)] so old history.json entries - all implicitly Move - still
deserialize). revert_one() branches on it: Move still moves the file back; Copy/
Symlink undo by deleting the copy/symlink (using symlink_metadata so a broken link is
still recognized as present and removable) - the original was never touched by those
actions, so there's nothing to move back. 3 new tests.
- src-tauri: execute_rename command takes the chosen action and threads it through to
both execute_renames and record_batch.
- Frontend: a Move/Copy/Symlink segmented selector in the "Confirm Rename" modal
(remembered across modal opens, defaults to Move), action-aware subtitle/button
copy, and a status message using the right verb (Renamed/Copied/Symlinked). Only Move
reconciles a loaded item's id/name/bindings to the new path afterward, since Copy/
Symlink leave the source file exactly where it was. History panel now shows a Copy/
Symlink badge on non-Move batches and explains what Undo does for each.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Each row in the "Confirm Rename" plan now has a small x button that drops just that
file from the batch without closing the modal or needing to cancel and re-select
beforehand (previously it was all-or-nothing - "Exclude Selected" on the file list was
the only way to leave a file out, and only before opening Rename at all). Excluding
every row disables the confirm button and shows an empty-plan hint instead of sending
an empty batch. No Rust changes needed - this is pure plan-state filtering in the
frontend, RenamePlanEntry's shape is unchanged.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- renamr-core::matching: guess_movie() extracts a movie title (and release year, if
present) from a filename using the same year/quality-token boundary heuristics real
release names follow, as a fallback when guess_episode() finds no season/episode
marker.
- renamr-core::web::tmdb: search_movie()/lookup_movie() mirror the TV client, reusing
the same Sorensen-Dice title-disambiguation as lookup_episode(), narrowed further by
TMDB's own primary_release_year filter when a year was locally guessed. Also exposed
search_tv_best_match() and fixed a latent gap where the real Fetch Data command path
(metadata.rs::fetch_season) bypassed the fuzzy show-name disambiguation entirely,
doing its own raw first-result selection instead of reusing it.
- src-tauri: new guess_movie command and fetch_movie_metadata command/types
(MovieFetchQuery/Outcome/Result), registered alongside the existing TV commands.
- Frontend: Item now tracks mediaKind ("tv"/"movie"/null); Match tries guess_episode
then falls back to guess_movie; Fetch Data resolves TV and movie targets via their
own commands concurrently and merges results. A single batch can mix TV and movie
files - each only fills in the fields that apply to it. Movie fields ({movie name},
{year}, reusing {airdate} for release date, matching FileBot's own unified binding)
are documented in the naming-scheme field reference.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- 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).