Fix naming scheme silently resetting when a restored scheme collides with a milestone default

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.
This commit is contained in:
Renamr Scaffold
2026-08-30 05:05:22 +00:00
parent 6d98750642
commit 8cabe7c9ff
+29 -8
View File
@@ -572,6 +572,19 @@ export default function RenamePanel() {
expressionRef.current = expression;
}, [expression]);
// Set true when a real persisted scheme is restored from disk below. The three
// "showcase the next field" auto-upgrade checkpoints further down (addRealFiles,
// runMatching, runFetch) each compare expressionRef.current against an exact
// milestone-constant string to infer "the user hasn't customized past this point
// yet" - a heuristic that only holds for a fresh in-memory session. A restored
// scheme can legitimately equal one of those constants (e.g. a user whose saved
// scheme IS the sample/fetched expression), and without this guard the very next
// checkpoint would silently stomp their correctly-restored scheme back to a
// "simpler" milestone the moment files are loaded/matched/fetched. Once a real
// value has been restored, none of the onboarding auto-upgrades should ever fire
// again for this session, regardless of what the restored text happens to equal.
const restoredCustomSchemeRef = useRef(false);
// Restores the naming-scheme editor's text from the last time the app was open (see
// `src-tauri/src/settings.rs`'s `last_naming_scheme`) - without this, the editor
// always reset to SAMPLE_EXPRESSION on every launch, silently discarding whatever
@@ -582,7 +595,10 @@ export default function RenamePanel() {
useEffect(() => {
invoke<string | null>("get_last_naming_scheme")
.then((saved) => {
if (saved && saved.trim()) setExpression(saved);
if (saved && saved.trim()) {
setExpression(saved);
restoredCustomSchemeRef.current = true;
}
})
.catch(() => {})
.finally(() => setSchemeLoaded(true));
@@ -642,8 +658,9 @@ export default function RenamePanel() {
if (entries.length === 0) return;
// Only auto-switch the naming scheme the first time real files replace the demo,
// and only if the user hasn't already customized it.
if (expressionRef.current === SAMPLE_EXPRESSION) {
// and only if the user hasn't already customized it (including a scheme restored
// from a previous session - see restoredCustomSchemeRef above).
if (expressionRef.current === SAMPLE_EXPRESSION && !restoredCustomSchemeRef.current) {
setExpression(REAL_FILE_EXPRESSION);
}
@@ -877,9 +894,11 @@ export default function RenamePanel() {
// Showcase the newly-populated fields, but only the first time and only if the
// user hasn't already customized the naming scheme away from the plain-filename
// default. Movies deliberately don't drive this auto-upgrade - a single "best
// expression" heuristic doesn't generalize across a batch mixing TV and movies.
if (expressionRef.current === REAL_FILE_EXPRESSION && matchedTv > 0) {
// default (including a scheme restored from a previous session - see
// restoredCustomSchemeRef above). Movies deliberately don't drive this
// auto-upgrade - a single "best expression" heuristic doesn't generalize across a
// batch mixing TV and movies.
if (expressionRef.current === REAL_FILE_EXPRESSION && matchedTv > 0 && !restoredCustomSchemeRef.current) {
setExpression(MATCHED_EXPRESSION);
}
@@ -1340,8 +1359,10 @@ export default function RenamePanel() {
);
// Showcase the episode title field, but only the first time and only if the user
// hasn't already customized the naming scheme away from the Match-set default.
if (expressionRef.current === MATCHED_EXPRESSION && found > 0) {
// hasn't already customized the naming scheme away from the Match-set default
// (including a scheme restored from a previous session - see
// restoredCustomSchemeRef above).
if (expressionRef.current === MATCHED_EXPRESSION && found > 0 && !restoredCustomSchemeRef.current) {
setExpression(FETCHED_EXPRESSION);
}