diff --git a/src/renderer/components/UpdateBanner.tsx b/src/renderer/components/UpdateBanner.tsx
new file mode 100644
index 0000000..2f0a436
--- /dev/null
+++ b/src/renderer/components/UpdateBanner.tsx
@@ -0,0 +1,44 @@
+import { useUpdaterStore } from '@/lib/updater-store';
+import { ipc } from '@/lib/ipc';
+import { toast } from 'sonner';
+
+export function UpdateBanner() {
+ const { state, version, percent, install, check } = useUpdaterStore();
+
+ if (state === 'idle' || state === 'checking') return null;
+
+ if (state === 'error') {
+ return (
+
+ Couldn't check for updates.{' '}
+
+
+ );
+ }
+
+ if (state === 'downloading') {
+ return (
+
+ Downloading update… {Math.round(percent)}%
+
+ );
+ }
+
+ if (state === 'ready') {
+ return (
+
+ v{version} is ready.
+
+
+
+ );
+ }
+
+ return null;
+}
\ No newline at end of file
diff --git a/tests/component/UpdateBanner.test.tsx b/tests/component/UpdateBanner.test.tsx
new file mode 100644
index 0000000..cc7b92b
--- /dev/null
+++ b/tests/component/UpdateBanner.test.tsx
@@ -0,0 +1,49 @@
+import { describe, expect, test, beforeEach, vi } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { useUpdaterStore } from '@/lib/updater-store';
+import { UpdateBanner } from '@/components/UpdateBanner';
+
+vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
+vi.mock('@/lib/ipc', () => ({
+ ipc: {
+ app: { openExternal: vi.fn() },
+ updater: { check: vi.fn(), install: vi.fn() },
+ },
+}));
+
+describe('UpdateBanner', () => {
+ beforeEach(() => {
+ useUpdaterStore.setState({ state: 'idle', version: null, percent: 0 });
+ });
+
+ test('renders nothing when state is idle', () => {
+ const { container } = render();
+ expect(container.firstChild).toBeNull();
+ });
+
+ test('shows version and restart button when state is ready', () => {
+ useUpdaterStore.setState({ state: 'ready', version: '5.0.2' });
+ render();
+ expect(screen.getByText(/5\.0\.2/)).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /restart/i })).toBeInTheDocument();
+ });
+
+ test('shows download progress when state is downloading', () => {
+ useUpdaterStore.setState({ state: 'downloading', percent: 42 });
+ render();
+ expect(screen.getByText(/42%/)).toBeInTheDocument();
+ });
+
+ test('shows error copy when state is error', () => {
+ useUpdaterStore.setState({ state: 'error' });
+ render();
+ expect(screen.getByText(/couldn.?t check/i)).toBeInTheDocument();
+ });
+
+ test('restart button calls ipc.updater.install', () => {
+ useUpdaterStore.setState({ state: 'ready', version: '5.0.2' });
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /restart/i }));
+ expect(useUpdaterStore.getState().install).toBeDefined();
+ });
+});
\ No newline at end of file