From ae343cbc650903387c2d1e8f18ceb66350f0dd8e Mon Sep 17 00:00:00 2001 From: Julian Dice <19397727+windoze95@users.noreply.github.com> Date: Sat, 11 Jul 2026 18:34:19 -0500 Subject: [PATCH] fix: treat an already-gone recommendation as cleared, not an error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tester report: subscribing to a recommendation showed "Subscribed to X, but the suggestion could not be cleared" and left the card with a stale Subscribe button. discoverProvider.dismiss optimistically removed the card, then on ANY failure rolled it back and rethrew — so a 404 (the recommendation already gone) made the card reappear and the screen show the cleanup error. Now a 404 is treated as success: the card stays cleared and nothing is rethrown (other errors keep the rollback). Covers the staleness-sweep race and, with the backend fix (windoze95/nullfeed-backend#133) that stops subscribe from wiping the list, makes the Explore/Home subscribe->clear flow reliable. Co-Authored-By: Claude Fable 5 --- lib/providers/discover_provider.dart | 23 +++++++--- .../home_screen_recommendations_test.dart | 45 +++++++++++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/lib/providers/discover_provider.dart b/lib/providers/discover_provider.dart index 2500111..55fe650 100644 --- a/lib/providers/discover_provider.dart +++ b/lib/providers/discover_provider.dart @@ -51,17 +51,28 @@ class DiscoverNotifier extends Notifier>> { } try { await _api.dismissRecommendation(id); + } on ApiException catch (e) { + // The recommendation is already gone (e.g. cleared by the staleness + // sweep, or by subscribing to it). The card's job is done — keep it + // removed rather than resurrecting it and alarming the user. + if (e.statusCode == 404) return; + _rollback(requestId, scope, previous); + rethrow; } catch (_) { - if (ref.mounted && - requestId == _requestId && - ref.read(activeSessionScopeProvider) == scope && - previous != null) { - state = AsyncValue.data(previous); - } + _rollback(requestId, scope, previous); rethrow; } } + void _rollback(int requestId, Object? scope, List? previous) { + if (ref.mounted && + requestId == _requestId && + ref.read(activeSessionScopeProvider) == scope && + previous != null) { + state = AsyncValue.data(previous); + } + } + Future refresh() async { final scope = ref.read(activeSessionScopeProvider); if (scope == null) { diff --git a/test/widgets/home_screen_recommendations_test.dart b/test/widgets/home_screen_recommendations_test.dart index 3abe5a2..477ead0 100644 --- a/test/widgets/home_screen_recommendations_test.dart +++ b/test/widgets/home_screen_recommendations_test.dart @@ -143,4 +143,49 @@ void main() { await tester.pump(const Duration(seconds: 5)); await tester.pumpAndSettle(); }); + + testWidgets( + 'a 404 when clearing the recommendation is treated as already-cleared', + (tester) async { + when(() => api.getRecommendations()).thenAnswer( + (_) async => [ + makeRecommendation( + id: 'r1', + channelName: 'Veritasium', + youtubeChannelId: 'UC-x', + ), + ], + ); + when(() => api.getChannels()).thenAnswer((_) async => const []); + when( + () => api.subscribeToChannel( + any(), + trackingMode: any(named: 'trackingMode'), + ), + ).thenAnswer((_) async {}); + // The recommendation is already gone server-side (e.g. cleared by the + // staleness sweep) — dismiss 404s. + when(() => api.dismissRecommendation(any())).thenThrow( + const ApiException( + message: 'Recommendation not found', + statusCode: 404, + ), + ); + + await pumpHome(tester); + await revealRecommendations(tester); + await tester.ensureVisible(find.text('Subscribe')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Subscribe')); + await tester.pumpAndSettle(); + + // The card is cleared and NO "could not be cleared" error is shown. + expect(find.text('Subscribed to Veritasium'), findsOneWidget); + expect(find.text('Recommended for you'), findsNothing); + expect(find.textContaining('could not be cleared'), findsNothing); + + await tester.pump(const Duration(seconds: 5)); + await tester.pumpAndSettle(); + }, + ); }