diff --git a/components/favorites/FavoritesList.tsx b/components/favorites/FavoritesList.tsx
index 52e8c43..ff8a058 100644
--- a/components/favorites/FavoritesList.tsx
+++ b/components/favorites/FavoritesList.tsx
@@ -5,6 +5,7 @@
import type { FavoriteItem } from '@/lib/types';
import { FavoritesItem } from './FavoritesItem';
import { FavoritesEmptyState } from './FavoritesEmptyState';
+import { keepRenderableFavorites } from '@/lib/utils/sync-records';
interface FavoritesListProps {
favorites: FavoriteItem[];
@@ -19,7 +20,7 @@ export function FavoritesList({ favorites, onRemove, isPremium = false }: Favori
return (
- {favorites.map((item) => (
+ {keepRenderableFavorites(favorites).map((item) => (
) : (
- {history.map((item) => (
+ {keepRenderableHistory(history).map((item) => (
0) {
- historyStore.getState().importHistory(result.data.history);
+ const history = keepRenderableHistory(result.data.history);
+ const favorites = keepRenderableFavorites(result.data.favorites);
+
+ if (history.length > 0) {
+ historyStore.getState().importHistory(history);
}
- if (result.data.favorites?.length > 0) {
- favoritesStore.getState().importFavorites(result.data.favorites);
+ if (favorites.length > 0) {
+ favoritesStore.getState().importFavorites(favorites);
}
}
} catch (error) {
diff --git a/lib/utils/sync-records.ts b/lib/utils/sync-records.ts
new file mode 100644
index 0000000..0d3dc7c
--- /dev/null
+++ b/lib/utils/sync-records.ts
@@ -0,0 +1,32 @@
+import type { FavoriteItem, VideoHistoryItem } from '@/lib/types';
+
+function hasIdentity(value: unknown): boolean {
+ if (!value || typeof value !== 'object') return false;
+ const record = value as { videoId?: unknown; source?: unknown; title?: unknown };
+ const videoId = record.videoId;
+ const hasVideoId = typeof videoId === 'string' ? videoId.length > 0 : typeof videoId === 'number';
+
+ return hasVideoId && typeof record.source === 'string' && typeof record.title === 'string';
+}
+
+/**
+ * Records restored from server-side sync are not guaranteed to be well formed —
+ * they may come from an older schema or a partial write. Rendering one without
+ * `videoId` throws while building the player URL, which takes down the whole
+ * page, so anything lacking a usable identity is dropped on the way in.
+ */
+export function isRenderableHistoryItem(value: unknown): value is VideoHistoryItem {
+ return hasIdentity(value);
+}
+
+export function isRenderableFavoriteItem(value: unknown): value is FavoriteItem {
+ return hasIdentity(value);
+}
+
+export function keepRenderableHistory(items: unknown): VideoHistoryItem[] {
+ return Array.isArray(items) ? items.filter(isRenderableHistoryItem) : [];
+}
+
+export function keepRenderableFavorites(items: unknown): FavoriteItem[] {
+ return Array.isArray(items) ? items.filter(isRenderableFavoriteItem) : [];
+}