fix: preserve metadata overrides and upload job state#167
Conversation
📝 WalkthroughWalkthroughThe PR adds a ChangesCLI UseSeasonEpisode flag and override signature keying
MetadataOverrides end-to-end propagation
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 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 `@gui/frontend/src/app.tsx`:
- Around line 1443-1476: The metadata override builder in metadataOverrideState
is still serializing cleared text fields as empty strings. Update the
Distributor and OriginalLanguage handling so touched-but-empty edits are treated
as unset (not emitted in overrides), letting normalizeMetadataOverrides drop
them and revert to auto behavior. Use the existing metadataTouched,
metadataEdits, and normalizeMetadataOverrides flow to ensure only non-empty text
overrides are written.
In `@gui/frontend/src/hooks/useScreenshots.ts`:
- Around line 144-145: The screenshot follow-up mutations in useScreenshots are
using the live metadataOverrideState at click time, which can drift from the
snapshot used when the screenshots were loaded. Capture and reuse the same
normalized override snapshot from the initial load path (the call that uses
normalizeMetadataOverrides in useScreenshots) for the save/delete/update flows,
and thread that snapshot through the relevant handlers instead of reading
current overrides again. Make sure the handlers around the follow-up calls in
useScreenshots all reference the same loaded override state so the UI plan and
the mutation target stay aligned.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 62da0244-2c1d-469b-b193-fc8cd46688c1
📒 Files selected for processing (28)
cmd/upbrr/cli_options.gocmd/upbrr/cli_options_test.gocmd/upbrr/main.gogui/frontend/src/app.test.tsgui/frontend/src/app.tsxgui/frontend/src/hooks/useScreenshots.tsgui/frontend/src/hooks/useUploadImages.tsgui/frontend/src/pages/input/index.tsxgui/frontend/src/pages/menu_images/index.tsxgui/frontend/src/types.tsgui/frontend/src/utils/helpers.test.tsgui/frontend/src/utils/helpers.tsgui/frontend/src/utils/index.tsgui/frontend/src/utils/runtime.test.tsgui/frontend/src/utils/runtime.tsinternal/core/core.gointernal/core/description_builder_test.gointernal/guiapp/app.gointernal/guiapp/app_core_test.gointernal/guiapp/dupe_jobs.gointernal/guiapp/run_options_test.gointernal/guiapp/upload_jobs.gointernal/webserver/app_routes.gointernal/webserver/backend.gointernal/webserver/backend_repo_test.gointernal/webserver/jobs.gopkg/api/core.gopkg/api/services.go
| const metadataOverrideState = useMemo(() => { | ||
| const overrides: MetadataOverrides = {}; | ||
| if (metadataTouched.distributor) { | ||
| overrides.Distributor = metadataEdits.distributor.trim(); | ||
| } | ||
| if (metadataTouched.originalLanguage) { | ||
| overrides.OriginalLanguage = metadataEdits.originalLanguage.trim(); | ||
| } | ||
| const personalRelease = parseBoolOverrideEditValue(metadataEdits.personalRelease); | ||
| const commentary = parseBoolOverrideEditValue(metadataEdits.commentary); | ||
| const webDV = parseBoolOverrideEditValue(metadataEdits.webDV); | ||
| const streamOptimized = parseBoolOverrideEditValue(metadataEdits.streamOptimized); | ||
| const anime = parseBoolOverrideEditValue(metadataEdits.anime); | ||
| if (metadataTouched.personalRelease && personalRelease !== null) { | ||
| overrides.PersonalRelease = personalRelease; | ||
| } | ||
| if (metadataTouched.commentary && commentary !== null) { | ||
| overrides.Commentary = commentary; | ||
| } | ||
| if (metadataTouched.webDV && webDV !== null) { | ||
| overrides.WebDV = webDV; | ||
| } | ||
| if (metadataTouched.streamOptimized && streamOptimized !== null) { | ||
| overrides.StreamOptimized = streamOptimized; | ||
| } | ||
| if (metadataTouched.anime && anime !== null) { | ||
| overrides.Anime = anime; | ||
| } | ||
| return { | ||
| overrides, | ||
| dirty: Object.keys(overrides).length > 0, | ||
| invalid: false, | ||
| }; | ||
| }, [metadataEdits, metadataTouched]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Don't serialize cleared text overrides as empty strings.
Line 1446 and Line 1449 still write "" once a text field has been touched, and normalizeMetadataOverrides only drops null/undefined. After a user clears Distributor or Original language, later refreshes/uploads keep sending a blank override instead of reverting to auto.
Suggested fix
const metadataOverrideState = useMemo(() => {
const overrides: MetadataOverrides = {};
if (metadataTouched.distributor) {
- overrides.Distributor = metadataEdits.distributor.trim();
+ const distributor = metadataEdits.distributor.trim();
+ if (distributor) {
+ overrides.Distributor = distributor;
+ }
}
if (metadataTouched.originalLanguage) {
- overrides.OriginalLanguage = metadataEdits.originalLanguage.trim();
+ const originalLanguage = metadataEdits.originalLanguage.trim();
+ if (originalLanguage) {
+ overrides.OriginalLanguage = originalLanguage;
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const metadataOverrideState = useMemo(() => { | |
| const overrides: MetadataOverrides = {}; | |
| if (metadataTouched.distributor) { | |
| overrides.Distributor = metadataEdits.distributor.trim(); | |
| } | |
| if (metadataTouched.originalLanguage) { | |
| overrides.OriginalLanguage = metadataEdits.originalLanguage.trim(); | |
| } | |
| const personalRelease = parseBoolOverrideEditValue(metadataEdits.personalRelease); | |
| const commentary = parseBoolOverrideEditValue(metadataEdits.commentary); | |
| const webDV = parseBoolOverrideEditValue(metadataEdits.webDV); | |
| const streamOptimized = parseBoolOverrideEditValue(metadataEdits.streamOptimized); | |
| const anime = parseBoolOverrideEditValue(metadataEdits.anime); | |
| if (metadataTouched.personalRelease && personalRelease !== null) { | |
| overrides.PersonalRelease = personalRelease; | |
| } | |
| if (metadataTouched.commentary && commentary !== null) { | |
| overrides.Commentary = commentary; | |
| } | |
| if (metadataTouched.webDV && webDV !== null) { | |
| overrides.WebDV = webDV; | |
| } | |
| if (metadataTouched.streamOptimized && streamOptimized !== null) { | |
| overrides.StreamOptimized = streamOptimized; | |
| } | |
| if (metadataTouched.anime && anime !== null) { | |
| overrides.Anime = anime; | |
| } | |
| return { | |
| overrides, | |
| dirty: Object.keys(overrides).length > 0, | |
| invalid: false, | |
| }; | |
| }, [metadataEdits, metadataTouched]); | |
| const metadataOverrideState = useMemo(() => { | |
| const overrides: MetadataOverrides = {}; | |
| if (metadataTouched.distributor) { | |
| const distributor = metadataEdits.distributor.trim(); | |
| if (distributor) { | |
| overrides.Distributor = distributor; | |
| } | |
| } | |
| if (metadataTouched.originalLanguage) { | |
| const originalLanguage = metadataEdits.originalLanguage.trim(); | |
| if (originalLanguage) { | |
| overrides.OriginalLanguage = originalLanguage; | |
| } | |
| } | |
| const personalRelease = parseBoolOverrideEditValue(metadataEdits.personalRelease); | |
| const commentary = parseBoolOverrideEditValue(metadataEdits.commentary); | |
| const webDV = parseBoolOverrideEditValue(metadataEdits.webDV); | |
| const streamOptimized = parseBoolOverrideEditValue(metadataEdits.streamOptimized); | |
| const anime = parseBoolOverrideEditValue(metadataEdits.anime); | |
| if (metadataTouched.personalRelease && personalRelease !== null) { | |
| overrides.PersonalRelease = personalRelease; | |
| } | |
| if (metadataTouched.commentary && commentary !== null) { | |
| overrides.Commentary = commentary; | |
| } | |
| if (metadataTouched.webDV && webDV !== null) { | |
| overrides.WebDV = webDV; | |
| } | |
| if (metadataTouched.streamOptimized && streamOptimized !== null) { | |
| overrides.StreamOptimized = streamOptimized; | |
| } | |
| if (metadataTouched.anime && anime !== null) { | |
| overrides.Anime = anime; | |
| } | |
| return { | |
| overrides, | |
| dirty: Object.keys(overrides).length > 0, | |
| invalid: false, | |
| }; | |
| }, [metadataEdits, metadataTouched]); |
🤖 Prompt for 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.
In `@gui/frontend/src/app.tsx` around lines 1443 - 1476, The metadata override
builder in metadataOverrideState is still serializing cleared text fields as
empty strings. Update the Distributor and OriginalLanguage handling so
touched-but-empty edits are treated as unset (not emitted in overrides), letting
normalizeMetadataOverrides drop them and revert to auto behavior. Use the
existing metadataTouched, metadataEdits, and normalizeMetadataOverrides flow to
ensure only non-empty text overrides are written.
| normalizeMetadataOverrides(metadataOverrideState?.overrides || {}), | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Reuse the same override snapshot for screenshot follow-up calls.
Line 144 fetches the plan under the current metadata overrides, but Line 255, Line 422, Line 450, and Line 471 reuse whatever override state is current when the user clicks save/delete. The parent app does not invalidate the loaded screenshot state on override edits, so the UI can show plan A while these mutations are sent against plan B.
Suggested direction
+ const planMetadataOverridesRef = useRef<MetadataOverrides>({});
+
const loadScreenshotPlan = useCallback(
async (revealSelections = false): Promise<ScreenshotPlan | null> => {
+ const normalizedMetadataOverrides = normalizeMetadataOverrides(
+ metadataOverrideState?.overrides || {},
+ );
setScreenshotsError("");
const fetcher = globalThis.go?.guiapp?.App?.FetchScreenshotPlan;
@@
const result = await fetcher(
path.trim(),
normalizeOverrides(idOverrideState?.overrides || {}),
normalizeReleaseOverrides(releaseOverrideState?.overrides || {}),
- normalizeMetadataOverrides(metadataOverrideState?.overrides || {}),
+ normalizedMetadataOverrides,
);
+ planMetadataOverridesRef.current = normalizedMetadataOverrides;
@@
await saver(
path.trim(),
normalizeOverrides(idOverrideState?.overrides || {}),
normalizeReleaseOverrides(releaseOverrideState?.overrides || {}),
- normalizeMetadataOverrides(metadataOverrideState?.overrides || {}),
+ planMetadataOverridesRef.current,
next.map((entry) => entry.image),
);Also applies to: 255-262, 422-478
🤖 Prompt for 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.
In `@gui/frontend/src/hooks/useScreenshots.ts` around lines 144 - 145, The
screenshot follow-up mutations in useScreenshots are using the live
metadataOverrideState at click time, which can drift from the snapshot used when
the screenshots were loaded. Capture and reuse the same normalized override
snapshot from the initial load path (the call that uses
normalizeMetadataOverrides in useScreenshots) for the save/delete/update flows,
and thread that snapshot through the relevant handlers instead of reading
current overrides again. Make sure the handlers around the follow-up calls in
useScreenshots all reference the same loaded override state so the UI plan and
the mutation target stay aligned.
Summary by CodeRabbit
New Features
season-episoderelease override in the CLI and app, with help text and alias support.Bug Fixes