+ {intro}
+
+ {/* Status tiles */}
+
+ {[
+ { label: t('memoryFreshness.metricFresh'), value: report.freshCount },
+ { label: t('memoryFreshness.metricFading'), value: report.fadingCount },
+ { label: t('memoryFreshness.metricStale'), value: report.staleCount },
+ ].map(tile => (
+
+
+ {tile.label}
+
+
+ {tile.value}
+
+
+ ))}
+
+
+ {t('memoryFreshness.recallCaption')
+ .replace('{recall}', String(pct(report.averageRecall)))
+ .replace('{total}', String(report.total))}
+
+
+ {/* Re-confirm queue */}
+
+
+ {t('memoryFreshness.queueHeading')}
+
+ {queue.length === 0 ? (
+
+ {t('memoryFreshness.allFresh')}
+
+ ) : (
+
+ {queue.map(fact => (
+ -
+
+
+ {fact.subject} {fact.predicate} {fact.object}
+
+
+ {fact.status === 'stale'
+ ? t('memoryFreshness.statusStale')
+ : t('memoryFreshness.statusFading')}
+
+
+
+
+
+ {pct(fact.recall)}%
+
+
+ {t('memoryFreshness.ageLabel').replace(
+ '{days}',
+ String(Math.round(fact.ageDays))
+ )}
+
+
+
+ ))}
+
+ )}
+ {report.staleQueue.length > MAX_QUEUE_ROWS && (
+
+ {t('memoryFreshness.queueTruncated')
+ .replace('{shown}', String(MAX_QUEUE_ROWS))
+ .replace('{total}', String(report.staleQueue.length))}
+
+ )}
+
+
+ );
+};
+
+export default MemoryFreshnessPanel;
diff --git a/app/src/components/intelligence/MemoryFreshnessTab.test.tsx b/app/src/components/intelligence/MemoryFreshnessTab.test.tsx
new file mode 100644
index 000000000..e6381bab4
--- /dev/null
+++ b/app/src/components/intelligence/MemoryFreshnessTab.test.tsx
@@ -0,0 +1,66 @@
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { computeFreshness } from '../../lib/memory/memoryFreshness';
+import type { GraphRelation } from '../../utils/tauriCommands/memory';
+import MemoryFreshnessTab from './MemoryFreshnessTab';
+
+const mockLoadFreshness = vi.fn();
+const mockLoadNamespaces = vi.fn();
+
+vi.mock('../../services/api/memoryFreshnessApi', () => ({
+ loadFreshness: (...args: unknown[]) => mockLoadFreshness(...args),
+ loadNamespaces: (...args: unknown[]) => mockLoadNamespaces(...args),
+}));
+
+const NOW = 1_700_000_000;
+
+function rel(subject: string, object: string): GraphRelation {
+ return {
+ namespace: 'n',
+ subject,
+ predicate: 'p',
+ object,
+ attrs: {},
+ updatedAt: NOW,
+ evidenceCount: 1,
+ orderIndex: null,
+ documentIds: [],
+ chunkIds: [],
+ };
+}
+
+const report = computeFreshness([rel('You', 'Berlin')], NOW);
+
+describe('