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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user