fix(backup): export and restore hidden Xtream categories by real xtream IDs (#1224)

Category rows crossed the DB-worker IPC boundary with Drizzle's camelCase
property names while the renderer contracts declare snake_case, so backup
export dropped hidden-category IDs and restore degraded to a type-only
match that hid every category. Project category ops to the declared wire
shape, normalize restore state from untrusted sources (dropping entries
without a numeric xtreamId), reject entries with missing user-state
collections, and add full export→import round-trip coverage (unit
manifest-equality + Electron e2e) plus regression tests.

Closes #1017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 authored and GitHub committed 2026-07-24 20:32:45 +02:00
1 parent bd07e17857
commit b4c0cce741
16 files changed
+1424 -151

No files matched your search

@@ -150,6 +150,18 @@ Runtime contract:
- settings backup import
- Xtream content initialization
Restore state originates from untrusted sources (user-supplied backup files,
stale localStorage entries), so every read and write goes through
`normalizeXtreamPendingRestoreState` (`libs/shared/interfaces`). Entries in
`hiddenCategories`, `favorites`, and `recentlyViewed` without a usable numeric
`xtreamId` are dropped rather than restored: backups exported by builds
affected by issue #1017 contain ID-less hidden-category entries, and matching
them against category rows would otherwise degrade to a type-only comparison
that hides every category of that type. Category rows themselves cross the DB
worker IPC boundary in the snake_case wire shape declared by
`XCategoryFromDb`/`XtreamCategoryFromDb`; the category operations project
their Drizzle rows explicitly to keep that contract true.
Electron restore behavior:
1. Category import reads pending hidden-category state while saving categories.