diff --git a/frontend/src/lib/components/ConversationThumbnail.svelte b/frontend/src/lib/components/ConversationThumbnail.svelte
new file mode 100644
index 00000000..61c62c00
--- /dev/null
+++ b/frontend/src/lib/components/ConversationThumbnail.svelte
@@ -0,0 +1,48 @@
+
+
+
+ {#if url}
+

+ {:else}
+
+ {/if}
+
+ {#if pageCount > 1}
+
{pageCount}
+ {/if}
+
diff --git a/frontend/src/lib/components/ConversationThumbnail.svelte.spec.ts b/frontend/src/lib/components/ConversationThumbnail.svelte.spec.ts
new file mode 100644
index 00000000..e8874710
--- /dev/null
+++ b/frontend/src/lib/components/ConversationThumbnail.svelte.spec.ts
@@ -0,0 +1,110 @@
+import { describe, it, expect, afterEach } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+
+import ConversationThumbnail from './ConversationThumbnail.svelte';
+
+afterEach(() => {
+ cleanup();
+});
+
+describe('ConversationThumbnail', () => {
+ it('renders the thumbnail image with a cache-busting v= query param', () => {
+ render(ConversationThumbnail, {
+ doc: {
+ id: '1111',
+ thumbnailKey: 'thumbnails/1111.jpg',
+ thumbnailGeneratedAt: '2026-04-10T09:00:00Z',
+ thumbnailAspect: 'PORTRAIT',
+ pageCount: 1
+ }
+ });
+
+ const img = document.querySelector('img') as HTMLImageElement | null;
+ expect(img).not.toBeNull();
+ expect(img!.getAttribute('src')).toContain('/api/documents/1111/thumbnail');
+ expect(img!.getAttribute('src')).toContain('v=');
+ });
+
+ it('uses portrait dimensions when aspect is PORTRAIT', () => {
+ render(ConversationThumbnail, {
+ doc: {
+ id: 'p1',
+ thumbnailKey: 'thumbnails/p1.jpg',
+ thumbnailAspect: 'PORTRAIT',
+ pageCount: 1
+ }
+ });
+
+ const tile = document.querySelector('[data-testid="conv-thumb-tile"]') as HTMLElement;
+ expect(tile.getAttribute('data-aspect')).toBe('PORTRAIT');
+ });
+
+ it('uses landscape dimensions when aspect is LANDSCAPE', () => {
+ render(ConversationThumbnail, {
+ doc: {
+ id: 'l1',
+ thumbnailKey: 'thumbnails/l1.jpg',
+ thumbnailAspect: 'LANDSCAPE',
+ pageCount: 1
+ }
+ });
+
+ const tile = document.querySelector('[data-testid="conv-thumb-tile"]') as HTMLElement;
+ expect(tile.getAttribute('data-aspect')).toBe('LANDSCAPE');
+ });
+
+ it('falls back to PORTRAIT when thumbnailAspect is missing', () => {
+ render(ConversationThumbnail, {
+ doc: {
+ id: 'n1',
+ thumbnailKey: 'thumbnails/n1.jpg'
+ }
+ });
+
+ const tile = document.querySelector('[data-testid="conv-thumb-tile"]') as HTMLElement;
+ expect(tile.getAttribute('data-aspect')).toBe('PORTRAIT');
+ });
+
+ it('renders the page badge when pageCount is greater than 1', () => {
+ render(ConversationThumbnail, {
+ doc: {
+ id: 'm1',
+ thumbnailKey: 'thumbnails/m1.jpg',
+ thumbnailAspect: 'PORTRAIT',
+ pageCount: 4
+ }
+ });
+
+ const badge = document.querySelector('[data-testid="conv-thumb-page-badge"]') as HTMLElement;
+ expect(badge).not.toBeNull();
+ expect(badge.textContent).toContain('4');
+ });
+
+ it('hides the page badge when pageCount is 1 or missing', () => {
+ render(ConversationThumbnail, {
+ doc: {
+ id: 's1',
+ thumbnailKey: 'thumbnails/s1.jpg',
+ thumbnailAspect: 'PORTRAIT',
+ pageCount: 1
+ }
+ });
+
+ const badge = document.querySelector('[data-testid="conv-thumb-page-badge"]');
+ expect(badge).toBeNull();
+ });
+
+ it('renders a skeleton placeholder when no thumbnailKey is set yet', () => {
+ render(ConversationThumbnail, {
+ doc: {
+ id: 'blank',
+ thumbnailAspect: 'PORTRAIT'
+ }
+ });
+
+ expect(document.querySelector('img')).toBeNull();
+ const skeleton = document.querySelector('[data-testid="conv-thumb-skeleton"]');
+ expect(skeleton).not.toBeNull();
+ expect(skeleton!.className).toContain('motion-safe:animate-pulse');
+ });
+});