diff --git a/backend/static/export-viewer/index.html b/backend/static/export-viewer/index.html
index 9251a67..2775b61 100644
--- a/backend/static/export-viewer/index.html
+++ b/backend/static/export-viewer/index.html
@@ -4,9 +4,9 @@
EventSnap — Galerie
-
-
+
+
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
index ddd416d..91a567f 100644
--- a/frontend/eslint.config.js
+++ b/frontend/eslint.config.js
@@ -58,10 +58,16 @@ export default ts.config(
},
{
ignores: [
- '.svelte-kit/',
- 'build/',
- 'dist/',
- 'node_modules/',
+ // `**/` matters: these patterns are relative to this config's directory, so a bare
+ // `.svelte-kit/` matched only the top-level one and left `export-viewer/.svelte-kit/`
+ // — SvelteKit's GENERATED types, gitignored but present after a viewer build — being
+ // linted, for 10 errors in code nobody wrote. Rebuilding the viewer is a required
+ // step whenever its source changes (it is embedded in the binary via include_dir!),
+ // so that directory exists in any normal working tree.
+ '**/.svelte-kit/',
+ '**/build/',
+ '**/dist/',
+ '**/node_modules/',
'static/export-viewer/',
'*.config.js',
'*.config.ts'
diff --git a/frontend/export-viewer/package-lock.json b/frontend/export-viewer/package-lock.json
index 4807d1b..667dbbd 100644
--- a/frontend/export-viewer/package-lock.json
+++ b/frontend/export-viewer/package-lock.json
@@ -901,7 +901,6 @@
"integrity": "sha512-9hDOl3yUh8UXWt+mN29dbcdrW0vNwPvMqi01y2Mw+ceErNIISh8MeEY7fXT2Dx1CjC/kfsVqrbxw7DifYr4hsg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5",
@@ -944,7 +943,6 @@
"integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"deepmerge": "^4.3.1",
@@ -1291,7 +1289,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1904,7 +1901,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -2024,7 +2020,6 @@
"integrity": "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -2114,7 +2109,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -2129,7 +2123,6 @@
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
diff --git a/frontend/export-viewer/src/routes/+page.svelte b/frontend/export-viewer/src/routes/+page.svelte
index 4ab0769..1611d2f 100644
--- a/frontend/export-viewer/src/routes/+page.svelte
+++ b/frontend/export-viewer/src/routes/+page.svelte
@@ -57,22 +57,25 @@
...allTags.slice(0, 3).map(([t]) => ({ type: 'tag' as const, value: t }))
];
}
+ // Once the user has TYPED, every match is offered — no `.slice()`. Capping matters
+ // more here than in the live app, for two reasons: this viewer holds the WHOLE event
+ // (the export queries are uncapped), so the tag set is far larger than any one feed
+ // page; and the keepsake is a frozen artifact — a guest opening it years later has no
+ // server to re-query and no "scroll to load more" escape hatch. A silently dropped
+ // match would simply be unreachable forever. The dropdown scrolls instead.
if (q.startsWith('#')) {
const prefix = q.slice(1).toLowerCase();
return allTags
.filter(([t]) => t.startsWith(prefix))
- .slice(0, 8)
.map(([t]) => ({ type: 'tag' as const, value: t }));
}
const lower = q.toLowerCase();
return [
...allUploaders
.filter((u) => u.toLowerCase().includes(lower))
- .slice(0, 4)
.map((u) => ({ type: 'user' as const, value: u })),
...allTags
.filter(([t]) => t.includes(lower))
- .slice(0, 4)
.map(([t]) => ({ type: 'tag' as const, value: t }))
];
});
@@ -381,13 +384,24 @@
{#if showAutocomplete && suggestions.length > 0}
+
e.preventDefault()}
+ role="listbox"
+ tabindex="-1"
>
{#each suggestions as item (item.type + ':' + item.value)}