diff --git a/frontend/src/lib/components/file/TagPicker.svelte b/frontend/src/lib/components/file/TagPicker.svelte
new file mode 100644
index 0000000..53b33b4
--- /dev/null
+++ b/frontend/src/lib/components/file/TagPicker.svelte
@@ -0,0 +1,206 @@
+
+
+
+
+ {#if fileTags.length > 0}
+
Assigned
+
+ {#each filteredAssigned as tag (tag.id)}
+
+ {/each}
+
+ {/if}
+
+
+
+
+
+ {#if filteredAvailable.length > 0}
+
Add tag
+
+ {#each filteredAvailable as tag (tag.id)}
+
+ {/each}
+
+ {:else if search.trim()}
+
No matching tags
+ {/if}
+
+
+
\ No newline at end of file
diff --git a/frontend/src/routes/files/+page.svelte b/frontend/src/routes/files/+page.svelte
index 29316fa..07a7883 100644
--- a/frontend/src/routes/files/+page.svelte
+++ b/frontend/src/routes/files/+page.svelte
@@ -1,5 +1,5 @@
+
+
+
+
+
+
{file?.original_name ?? ''}
+
+
+
+
+ {#if previewSrc}
+

+ {:else if loading}
+
+ {:else}
+
+ {/if}
+
+
+ {#if prevFile}
+
+ {/if}
+ {#if nextFile}
+
+ {/if}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/vite-mock-plugin.ts b/frontend/vite-mock-plugin.ts
index 7b3be8b..abc01e0 100644
--- a/frontend/vite-mock-plugin.ts
+++ b/frontend/vite-mock-plugin.ts
@@ -134,6 +134,16 @@ const MOCK_TAGS = TAG_NAMES.map((name, i) => ({
created_at: new Date(Date.now() - i * 3_600_000).toISOString(),
}));
+// Mutable in-memory state for file metadata and tags
+const fileOverrides = new Map