diff --git a/crates/native/src/backend.rs b/crates/native/src/backend.rs
--- a/crates/native/src/backend.rs
+++ b/crates/native/src/backend.rs
@@ -233,12 +233,12 @@ impl NativeBackend {
         let mirror = local_mirror();
         std::thread::spawn(move || {
             shared.emit(Event::CheckStarted);
-            let (feed, fallback) = if let Some(base) = &mirror {
+            let (feed, fallback, feed_failed) = if let Some(base) = &mirror {
                 // A local mirror answers for everything: GitHub is never asked.
-                (local_feed(&http, base), Fallback { trust_feed: true, github_every: GITHUB_FALLBACK_EVERY })
+                (local_feed(&http, base), Fallback { trust_feed: true, github_every: GITHUB_FALLBACK_EVERY }, false)
             } else if pre {
                 // The feed serves stable releases only; with pre-releases on, GitHub decides.
-                (None, Fallback { trust_feed: false, github_every: EARLY_GITHUB_EVERY })
+                (None, Fallback { trust_feed: false, github_every: EARLY_GITHUB_EVERY }, false)
             } else {
                 feed_and_fallback(&http, &paths, &shared)
             };
@@ -298,15 +298,7 @@ impl NativeBackend {
                 report_partial(app, &shared, &paths, platform, &options);
             }
             let sources = sources.into_inner().unwrap_or_else(PoisonError::into_inner);
-            let source_used = if sources.contains(&Source::Cache) {
-                Source::Cache
-            } else if sources.contains(&Source::GitHubPages) {
-                Source::GitHubPages
-            } else if sources.contains(&Source::GitHubApi) {
-                Source::GitHubApi
-            } else {
-                Source::Feed
-            };
+            let source_used = source_used(&sources, feed_failed);
             let at = crate::now();
             *shared.last_check.lock().unwrap_or_else(PoisonError::into_inner) = Some(at);
             shared.checking.store(false, Ordering::SeqCst);
@@ -681,32 +673,32 @@ struct Fallback {
 /// - After that: the feed's last answer stands in while it fails, and GitHub is asked only for
 ///   apps the feed doesn't list or hasn't refreshed in a day, or once the feed has seriously
 ///   failed (several checks in a row and an hour without an answer); then hourly per app.
-fn feed_and_fallback(http: &Http, paths: &Paths, shared: &Shared) -> (Option<Feed>, Fallback) {
+fn feed_and_fallback(http: &Http, paths: &Paths, shared: &Shared) -> (Option<Feed>, Fallback, bool) {
     let saved = load_feed(paths);
     let loaded = shared.loaded.load(Ordering::SeqCst);
     let first_time = !loaded && saved.is_none();
     let attempts = if first_time { 1 } else { FEED_ATTEMPTS };
     let fetched = fetch_feed(http, paths, &feed_url(), &shared.feed_throttle, attempts);
     let now = crate::now();
-    let (fresh, answered, failures) = {
+    let (fresh, answered, failures, feed_failed) = {
         let mut failures = shared.feed_failures.lock().unwrap_or_else(PoisonError::into_inner);
         match fetched {
             FeedFetch::Answered(feed) => {
                 *failures = 0;
                 *shared.feed_ok_at.lock().unwrap_or_else(PoisonError::into_inner) = Some(now);
-                (Some(feed), true, 0)
+                (Some(feed), true, 0, false)
             }
             FeedFetch::Failed => {
                 *failures = failures.saturating_add(1);
-                (None, false, *failures)
+                (None, false, *failures, true)
             }
             // Not asked this time: things stand as the last request left them.
-            FeedFetch::Held => (None, *failures == 0 && saved.is_some(), *failures),
+            FeedFetch::Held => (None, *failures == 0 && saved.is_some(), *failures, *failures > 0),
         }
     };
     let ok_at = *shared.feed_ok_at.lock().unwrap_or_else(PoisonError::into_inner);
     let fallback = decide_fallback(answered, loaded, first_time, failures, ok_at, now);
-    (fresh.or(saved), fallback)
+    (fresh.or(saved), fallback, feed_failed)
 }
 
 /// The policy in [`feed_and_fallback`], on its own so it can be tested.
@@ -755,6 +747,20 @@ fn latest_for(
     }
 }
 
+/// The one source the footer reports for a check: saved answers if any app had to settle for
+/// them, which includes a failing feed (asked this time or held back) with only its saved copy to offer.
+fn source_used(sources: &[Source], feed_failed: bool) -> Source {
+    if sources.contains(&Source::Cache) || (feed_failed && sources.iter().all(|s| *s == Source::Feed)) {
+        Source::Cache
+    } else if sources.contains(&Source::GitHubPages) {
+        Source::GitHubPages
+    } else if sources.contains(&Source::GitHubApi) {
+        Source::GitHubApi
+    } else {
+        Source::Feed
+    }
+}
+
 /// Until when the footer says GitHub's limit holds details back: only while it does, that is
 /// when this check had to settle for the release pages or saved answers. When the feed (or the
 /// API itself) answered everything, nothing is missing, however recently GitHub said no.
@@ -1115,6 +1121,30 @@ mod tests {
         (url, hits)
     }
 
+    #[test]
+    fn a_failed_feed_with_only_saved_answers_reports_saved_results() {
+        // Offline: the saved feed answered every app, but nothing was reached this time.
+        assert_eq!(source_used(&[Source::Feed, Source::Feed], true), Source::Cache);
+        assert_eq!(source_used(&[], true), Source::Cache);
+        // Online, or GitHub really answered: the live source is named.
+        assert_eq!(source_used(&[Source::Feed, Source::Feed], false), Source::Feed);
+        assert_eq!(source_used(&[Source::Feed, Source::GitHubApi], true), Source::GitHubApi);
+        assert_eq!(source_used(&[Source::GitHubApi, Source::Cache], false), Source::Cache);
+    }
+
+    #[test]
+    fn a_feed_held_back_after_failing_still_counts_as_failed() {
+        // A refresh soon after the feed failed doesn't ask it again: its saved copy is all there is.
+        let dir = tempfile::tempdir().unwrap();
+        let paths = Paths::under(dir.path());
+        let http = Http::new().unwrap();
+        let shared = shared();
+        *shared.feed_failures.lock().unwrap() = 1;
+        shared.feed_throttle.lock().unwrap().finished(Outcome::Failed { retry_after: Some(600) }, crate::now(), 0.0);
+        let (_, _, feed_failed) = feed_and_fallback(&http, &paths, &shared);
+        assert!(feed_failed);
+    }
+
     #[test]
     fn a_429_stops_the_retries_and_holds_off_the_api() {
         let (url, hits) = serve("429 Too Many Requests");
diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv
--- a/crates/ui-egui/src/i18n/cs.tsv
+++ b/crates/ui-egui/src/i18n/cs.tsv
@@ -110,6 +110,7 @@
 	Hidden Apps	Skryté aplikace
 	Apps you hid	Aplikace, které jste skryli
 	Checked {ago} · using {sources}	Zkontrolováno {ago} · zdroje: {sources}
+	Checked {ago} · showing saved results	Zkontrolováno {ago} · zobrazeny uložené výsledky
 	{a} and {b}	{a} a {b}
 	Join the Discord	Připojit se na Discord
 	Learn more	Další informace
diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv
--- a/crates/ui-egui/src/i18n/de.tsv
+++ b/crates/ui-egui/src/i18n/de.tsv
@@ -124,6 +124,7 @@
 	Hidden Apps	Ausgeblendete Apps
 	Apps you hid	Apps, die du ausgeblendet hast
 	Checked {ago} · using {sources}	Zuletzt geprüft: {ago} · über {sources}
+	Checked {ago} · showing saved results	Zuletzt geprüft: {ago} · gespeicherte Ergebnisse
 	{a} and {b}	{a} und {b}
 	Join the Discord	Dem Discord beitreten
 	Learn more	Mehr erfahren
diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv
--- a/crates/ui-egui/src/i18n/es.tsv
+++ b/crates/ui-egui/src/i18n/es.tsv
@@ -123,6 +123,7 @@
 	Hidden Apps	Aplicaciones ocultas
 	Apps you hid	Aplicaciones que ocultaste
 	Checked {ago} · using {sources}	Comprobado {ago} · con {sources}
+	Checked {ago} · showing saved results	Comprobado {ago} · mostrando resultados guardados
 	{a} and {b}	{a} y {b}
 	Join the Discord	Únete al Discord
 	Learn more	Más información
diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv
--- a/crates/ui-egui/src/i18n/fr.tsv
+++ b/crates/ui-egui/src/i18n/fr.tsv
@@ -122,6 +122,7 @@
 	Hidden Apps	Applications masquées
 	Apps you hid	Applications que vous avez masquées
 	Checked {ago} · using {sources}	Vérifié {ago} · via {sources}
+	Checked {ago} · showing saved results	Vérifié {ago} · résultats enregistrés affichés
 	{a} and {b}	{a} et {b}
 	Join the Discord	Rejoindre le Discord
 	Learn more	En savoir plus
diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv
--- a/crates/ui-egui/src/i18n/hu.tsv
+++ b/crates/ui-egui/src/i18n/hu.tsv
@@ -124,6 +124,7 @@
 	Hidden Apps	Rejtett alkalmazások
 	Apps you hid	Az általad elrejtett alkalmazások
 	Checked {ago} · using {sources}	Ellenőrizve: {ago} · {sources}
+	Checked {ago} · showing saved results	Ellenőrizve: {ago} · mentett eredmények láthatók
 	{a} and {b}	{a} és {b}
 	Join the Discord	Csatlakozás a Discordhoz
 	Learn more	További információ
diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv
--- a/crates/ui-egui/src/i18n/ja.tsv
+++ b/crates/ui-egui/src/i18n/ja.tsv
@@ -126,6 +126,7 @@
 	Hidden Apps	非表示のアプリ
 	Apps you hid	非表示にしたアプリ
 	Checked {ago} · using {sources}	最終確認: {ago} · 取得元: {sources}
+	Checked {ago} · showing saved results	最終確認: {ago} · 保存済みの結果を表示中
 	{a} and {b}	{a} と {b}
 	Join the Discord	Discord に参加
 	Learn more	詳細を見る
diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv
--- a/crates/ui-egui/src/i18n/pl.tsv
+++ b/crates/ui-egui/src/i18n/pl.tsv
@@ -125,6 +125,7 @@
 	Hidden Apps	Ukryte aplikacje
 	Apps you hid	Aplikacje ukryte przez ciebie
 	Checked {ago} · using {sources}	Ostatnio sprawdzono {ago} · przy użyciu {sources}
+	Checked {ago} · showing saved results	Ostatnio sprawdzono {ago} · pokazano zapisane wyniki
 	{a} and {b}	{a} i {b}
 	Join the Discord	Dołącz na Discorda
 	Learn more	Więcej
diff --git a/crates/ui-egui/src/i18n/pt-BR.tsv b/crates/ui-egui/src/i18n/pt-BR.tsv
--- a/crates/ui-egui/src/i18n/pt-BR.tsv
+++ b/crates/ui-egui/src/i18n/pt-BR.tsv
@@ -112,6 +112,7 @@
 	Hidden Apps	Aplicativos ocultos
 	Apps you hid	Aplicativos que você ocultou
 	Checked {ago} · using {sources}	Verificado {ago} · via {sources}
+	Checked {ago} · showing saved results	Verificado {ago} · mostrando resultados salvos
 	{a} and {b}	{a} e {b}
 	Join the Discord	Entrar no Discord
 	Learn more	Saiba mais
diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv
--- a/crates/ui-egui/src/i18n/ru.tsv
+++ b/crates/ui-egui/src/i18n/ru.tsv
@@ -122,6 +122,7 @@
 	Hidden Apps	Скрытые приложения
 	Apps you hid	Приложения, которые вы скрыли
 	Checked {ago} · using {sources}	Проверено {ago} · через {sources}
+	Checked {ago} · showing saved results	Проверено {ago} · показаны сохранённые результаты
 	{a} and {b}	{a} и {b}
 	Join the Discord	Присоединиться к Discord
 	Learn more	Подробнее
diff --git a/crates/ui-egui/src/i18n/zh-CN.tsv b/crates/ui-egui/src/i18n/zh-CN.tsv
--- a/crates/ui-egui/src/i18n/zh-CN.tsv
+++ b/crates/ui-egui/src/i18n/zh-CN.tsv
@@ -255,6 +255,7 @@ figcraft	Interface design and prototyping	界面设计与原型制作
 	Download cancelled	下载已取消
 	Automatically	自动
 	Checked {ago} · using {sources}	上次检查：{ago} · 来源：{sources}
+	Checked {ago} · showing saved results	上次检查：{ago} · 显示已保存的结果
 	{a} and {b}	{a} 和 {b}
 	Learn more	了解更多
 	OK	确定
diff --git a/crates/ui-egui/src/views.rs b/crates/ui-egui/src/views.rs
--- a/crates/ui-egui/src/views.rs
+++ b/crates/ui-egui/src/views.rs
@@ -3,7 +3,7 @@
 
 use artcraft_launcher_core::details::AppDetails;
 use artcraft_launcher_core::platform::pick_asset;
-use artcraft_launcher_core::protocol::{Command, JobPhase};
+use artcraft_launcher_core::protocol::{Command, JobPhase, Source};
 use artcraft_launcher_core::status::AppStatus;
 use artcraft_launcher_core::time;
 use egui::{Color32, CornerRadius, FontId, Rect, RichText, Sense, Stroke, Ui, vec2};
@@ -298,9 +298,14 @@ impl LauncherUi {
             tl!("Checking for updates…").to_string()
         } else if let Some(at) = self.settings.last_check {
             let ago = ago_text(at, now);
-            match self.sources_label() {
-                Some(using) => fmt(tl!("Checked {ago} · using {sources}"), &[("ago", &ago), ("sources", &using)]),
-                None => fmt(tl!("Checked {ago}"), &[("ago", &ago)]),
+            // Nothing was reached: the cards show saved answers, whichever service they came from.
+            if self.last_source == Some(Source::Cache) {
+                fmt(tl!("Checked {ago} · showing saved results"), &[("ago", &ago)])
+            } else {
+                match self.sources_label() {
+                    Some(using) => fmt(tl!("Checked {ago} · using {sources}"), &[("ago", &ago), ("sources", &using)]),
+                    None => fmt(tl!("Checked {ago}"), &[("ago", &ago)]),
+                }
             }
         } else {
             tl!("Not checked yet").to_string()
diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs
--- a/crates/ui-egui/tests/ui.rs
+++ b/crates/ui-egui/tests/ui.rs
@@ -1199,6 +1199,9 @@ fn the_footer_names_the_services_behind_what_is_on_screen() {
     let from_github =
         vec![Event::Latest { app: "wordcraft".into(), latest: LatestRelease::Found(release("wordcraft", "0.3.1", NOW)), from: Origin::GitHub }];
     assert_eq!(footer(from_github), "Checked 3 min ago · using ArtCraft API and GitHub");
+    // Nothing could be reached: the cards show saved answers, so no service is named.
+    let offline = vec![Event::CheckFinished { at: NOW - 180, source: Source::Cache, rate_limited_until: None }];
+    assert_eq!(footer(offline), "Checked 3 min ago · showing saved results");
 }
 
 #[test]
