Skip to content

prevent Date objects from being silently dropped during state persist#21677

Merged
IDCs merged 2 commits intomasterfrom
fix/app-73
Mar 19, 2026
Merged

prevent Date objects from being silently dropped during state persist#21677
IDCs merged 2 commits intomasterfrom
fix/app-73

Conversation

@IDCs
Copy link
Copy Markdown
Contributor

@IDCs IDCs commented Mar 18, 2026

Date objects (e.g. installTime) were treated as plain objects by the state diff engine because isObject() only checked for non-null, typeof "object", and non-array. Since Date has no enumerable own keys, Object.keys() returned [], causing collectSetOperations and collectRemoveOperations to generate no diff operations for Date values.

This meant:

  • When a mod was installed, installTime (set as new Date()) was never persisted to LevelDB
  • When a mod was removed, the old installTime string (from the initial startInstallCB) was never removed from LevelDB
  • On next startup, the orphaned installTime reconstructed a partial mod entry: { attributes: { installTime: "..." } } with no installationPath
  • The state verifier detected the missing required field and deleted the entry with deleteBroken: "parent", potentially disrupting the mod state

Fix: isObject() now checks Object.getPrototypeOf(state) === Object.prototype to ensure only plain objects are recursed into. Date and other built-in objects are treated as leaf values and correctly generate set/remove ops.

Also added type: "object" with deleteBroken: true at the game-level verifier in modsReducer to contain blast radius — a corrupt entry can only take itself down, never the entire persistent.mods tree.

Finally, re-introduced the "Application State Corrupted" dialog so the user can sanitize, quit or ignore state corruption rather than just automatically quit the app (leaving the user unable to restart Vortex)

fixes https://linear.app/nexus-mods/issue/APP-73/app-not-restarting-properly
closes https://linear.app/nexus-mods/issue/APP-107/re-introduce-application-state-corrupted-dialog

@IDCs IDCs requested a review from a team March 18, 2026 14:43
@IDCs IDCs self-assigned this Mar 18, 2026
Aragas
Aragas previously approved these changes Mar 19, 2026
@github-actions
Copy link
Copy Markdown

This PR has conflicts. You need to rebase the PR before it can be merged.

IDCs added 2 commits March 19, 2026 11:03
…ence

Date objects (e.g. installTime) were treated as plain objects by the state
diff engine because isObject() only checked for non-null, typeof "object",
and non-array. Since Date has no enumerable own keys, Object.keys() returned
[], causing collectSetOperations and collectRemoveOperations to generate no
diff operations for Date values.

This meant:
- When a mod was installed, installTime (set as new Date()) was never
  persisted to LevelDB
- When a mod was removed, the old installTime string (from the initial
  startInstallCB) was never removed from LevelDB
- On next startup, the orphaned installTime reconstructed a partial mod
  entry: { attributes: { installTime: "..." } } with no installationPath
- The state verifier detected the missing required field and deleted the
  entry with deleteBroken: "parent", potentially disrupting the mod state

Fix: isObject() now checks Object.getPrototypeOf(state) === Object.prototype
to ensure only plain objects are recursed into. Date and other built-in
objects are treated as leaf values and correctly generate set/remove ops.

Also added type: "object" with deleteBroken: true at the game-level verifier
in modsReducer to contain blast radius — a corrupt entry can only take itself
down, never the entire persistent.mods tree.

fixes https://linear.app/nexus-mods/issue/APP-73/app-not-restarting-properly
This allows the user to decide whether to sanitize, quit or ignore
invalid application state before hydration. Previously we would just
quit the app automatically when a state corruption is detected.

closes https://linear.app/nexus-mods/issue/APP-107/re-introduce-application-state-corrupted-dialog
@github-actions
Copy link
Copy Markdown

This PR doesn't have conflicts anymore. It can be merged after all status checks have passed and it has been reviewed.

@IDCs IDCs requested a review from Aragas March 19, 2026 11:06
@IDCs IDCs merged commit ae46a18 into master Mar 19, 2026
5 checks passed
@IDCs IDCs deleted the fix/app-73 branch March 19, 2026 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants