Merge remote-tracking branch 'origin/pr-229' into agent/release-4.9.19

This commit is contained in:
kuekhaoyang
2026-07-31 23:08:41 +08:00
4 changed files with 44 additions and 6 deletions
+2 -1
View File
@@ -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 (
<div className="flex-1 overflow-y-auto -mx-2 px-2 space-y-2 scroll-smooth">
{favorites.map((item) => (
{keepRenderableFavorites(favorites).map((item) => (
<FavoritesItem
key={`${item.source}:${item.videoId}`}
item={item}
+2 -1
View File
@@ -1,6 +1,7 @@
import { HistoryItem } from './HistoryItem';
import { HistoryEmptyState } from './HistoryEmptyState';
import type { VideoHistoryItem } from '@/lib/types';
import { keepRenderableHistory } from '@/lib/utils/sync-records';
interface HistoryListProps {
history: VideoHistoryItem[];
@@ -18,7 +19,7 @@ export function HistoryList({ history, onRemove, isPremium = false }: HistoryLis
<HistoryEmptyState />
) : (
<div className="space-y-3">
{history.map((item) => (
{keepRenderableHistory(history).map((item) => (
<HistoryItem
key={item.showIdentifier}
item={item}
+8 -4
View File
@@ -1,6 +1,7 @@
import { useState, useCallback } from 'react';
import { useHistoryStore, usePremiumHistoryStore } from '@/lib/store/history-store';
import { useFavoritesStore, usePremiumFavoritesStore } from '@/lib/store/favorites-store';
import { keepRenderableFavorites, keepRenderableHistory } from '@/lib/utils/sync-records';
import { getProfileId } from '@/lib/store/auth-store';
export function useCloudSync(isPremium = false) {
@@ -19,11 +20,14 @@ export function useCloudSync(isPremium = false) {
const result = await response.json();
if (result.success && result.data) {
if (result.data.history?.length > 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) {
+32
View File
@@ -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) : [];
}