From 036ef3cc6f4da8d2f3f2a52c2b0cb4692ade9177 Mon Sep 17 00:00:00 2001 From: Justin Visser Date: Mon, 10 Aug 2026 15:04:17 +0200 Subject: [PATCH] feat(frontend): assemble accessible application shell --- docs/logboek.md | 50 ++- frontend/index.html | 1 + frontend/public/favicon.svg | 6 +- frontend/src/App.vue | 9 +- frontend/src/components/AppHeader.vue | 140 +++++++++ frontend/src/components/AssistantMessage.vue | 132 ++++++++ frontend/src/components/AuthStatus.vue | 128 ++++++++ frontend/src/components/ChatView.vue | 123 ++++++++ frontend/src/components/DevPanel.vue | 285 ++++++++++++++++++ frontend/src/components/EmptyResults.vue | 28 ++ frontend/src/components/EmptyState.vue | 72 +++++ frontend/src/components/MessageInput.vue | 139 +++++++++ frontend/src/components/MessageList.vue | 130 ++++++++ frontend/src/components/ModeBanner.vue | 36 +++ frontend/src/components/PlaylistError.vue | 17 ++ frontend/src/components/PlaylistSaved.vue | 64 ++++ frontend/src/components/RequestMetadata.vue | 30 ++ frontend/src/components/ResultActions.vue | 74 +++++ frontend/src/components/ResultSet.vue | 20 ++ frontend/src/components/StreamError.vue | 37 +++ frontend/src/components/StreamWarning.vue | 20 ++ frontend/src/components/SuggestionChips.vue | 59 ++++ frontend/src/components/ThinkingIndicator.vue | 54 ++++ frontend/src/components/TrackCard.vue | 169 +++++++++++ frontend/src/components/UserMessage.vue | 26 ++ frontend/src/lib/suggestions.ts | 9 + frontend/vite.config.ts | 7 +- 27 files changed, 1852 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/AppHeader.vue create mode 100644 frontend/src/components/AssistantMessage.vue create mode 100644 frontend/src/components/AuthStatus.vue create mode 100644 frontend/src/components/ChatView.vue create mode 100644 frontend/src/components/DevPanel.vue create mode 100644 frontend/src/components/EmptyResults.vue create mode 100644 frontend/src/components/EmptyState.vue create mode 100644 frontend/src/components/MessageInput.vue create mode 100644 frontend/src/components/MessageList.vue create mode 100644 frontend/src/components/ModeBanner.vue create mode 100644 frontend/src/components/PlaylistError.vue create mode 100644 frontend/src/components/PlaylistSaved.vue create mode 100644 frontend/src/components/RequestMetadata.vue create mode 100644 frontend/src/components/ResultActions.vue create mode 100644 frontend/src/components/ResultSet.vue create mode 100644 frontend/src/components/StreamError.vue create mode 100644 frontend/src/components/StreamWarning.vue create mode 100644 frontend/src/components/SuggestionChips.vue create mode 100644 frontend/src/components/ThinkingIndicator.vue create mode 100644 frontend/src/components/TrackCard.vue create mode 100644 frontend/src/components/UserMessage.vue create mode 100644 frontend/src/lib/suggestions.ts diff --git a/docs/logboek.md b/docs/logboek.md index fc98d85..e6d274c 100644 --- a/docs/logboek.md +++ b/docs/logboek.md @@ -77,14 +77,14 @@ Wat ik deed: - Domeinbasis: titel/artiest-matching (normalisatie, met tolerantie voor Spotify's versie-suffixen zoals "- Remaster 2023"), compressie van het - Spotify-smaakprofiel naar prompttekst plus een set bekende track-ids, + Spotify taste profile naar prompttekst plus een set bekende track-ids, prompts als data in een eigen module, en alle instelbare waarden gesectioneerd in de config met per waarde het waarom. - Twee LLM-calls achter een eigen interface: call 1 interpreteert de - vraag (stemming, activiteit, taal, bekendheid) en stelt 30-40 echte + vraag (mood, activity, language, familiarity) en stelt 30-40 echte nummers voor als gestructureerde output; call 2 herordent uitsluitend - geverifieerde nummers en streamt per nummer een eerlijke onderbouwing. - Elke output wordt gevalideerd, met hooguit 1 herstelpoging. + geverifieerde nummers en streamt per nummer een eerlijke justification. + Elke output wordt gevalideerd, met hooguit 1 correctie-retry. - Grounding: begrensde parallelle search fan-out met early stop, een deadline, een naam-naar-id cache en twee aparte metrieken: niet gevonden versus wel gevonden maar afgekeurd door de match-check. Een track-id dat niet in @@ -96,7 +96,7 @@ Wat ik deed: Waarom: -- De LLM is hier de aanbeveler, maar mag alleen creatief zijn tussen twee +- De LLM is hier de recommender, maar mag alleen creatief zijn tussen twee deterministische muren: alles wat hij ziet is echte data, alles wat de gebruiker ziet is geverifieerd op Spotify. Een verzonnen nummer valt stilletjes af en verschijnt nooit. @@ -165,7 +165,7 @@ Wat ik deed: - Per unresolved kandidaat wordt titel, artiest en status gelogd, zodat zichtbaar is WAT het model verzon in plaats van alleen hoeveel. -### Eerlijke gedeeltelijke resultaten +### Eerlijke partial results Wat ik deed: @@ -201,3 +201,41 @@ Waarom: - De frontend is de tweede consument van het bevroren contract: elke event wordt tegen de TypeScript-union gevalideerd in plaats van los geparst; wat niet valideert is een transport failure, geen gok. + +### Frontend: componenten en app shell + +Dit deel is grotendeels geautomatiseerde omzetting: het UI bouwpakket uit +Open Design heb ik door een cli agent laten converteren naar Vue single file components +tegen het bevroren contract. De transportlaag eronder (stream client, +validatie) komt uit de vorige stap. Review- en hardeningpassen op dit +resultaat volgen hierna als eigen stappen; wat hier staat is de ruwe +conversie. + +Wat ik deed: + +- Alle componenten uit het bouwpakket laten omzetten: chat view, + composer met suggestion chips, streamende track cards met album covers + en een justification per nummer, warning banners, error states, login-status, + mode banner en een dev panel. +- App shell met keyboard-navigatie, focus management en reduced-motion + support. + +Waarom: + +- Het bouwpakket was hierop ontworpen; de componenten volgen de tokens en + states daaruit, en elke regel gaat alsnog door review voordat die op + main landt. +- Styling is scoped CSS per component bovenop globale design tokens + (custom properties). Scoped omdat machine-geconverteerde componenten + om style leakage te voorkomen, en omdat het bouwpakket al gewone CSS + per component leverde: een utility + framework had een rewrite plus een extra dependency gekost. De + tokens houden het thema op 1 plek, dus het bekende scoped-CSS risico + (waarden die per component uiteenlopen) is afgedekt. + +Wat ik heb laten vallen of uitgesteld: + +- Een lijst met eerdere chats. Gesprekken leven bewust alleen client-side + en de server bewaart niets; een history-lijst kan later contract-schoon + via localStorage, zonder server-state. Potentiele extra als er tijd + over is, geen onderdeel van de kern. diff --git a/frontend/index.html b/frontend/index.html index 0ec46d0..624cf41 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,6 +4,7 @@ + discovery-by-llm diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg index 6893eb1..3ec7371 100644 --- a/frontend/public/favicon.svg +++ b/frontend/public/favicon.svg @@ -1 +1,5 @@ - \ No newline at end of file + + + + d + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 982c73d..c38c1d3 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,8 +1,7 @@ - + diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue new file mode 100644 index 0000000..58444a4 --- /dev/null +++ b/frontend/src/components/AppHeader.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/frontend/src/components/AssistantMessage.vue b/frontend/src/components/AssistantMessage.vue new file mode 100644 index 0000000..1120133 --- /dev/null +++ b/frontend/src/components/AssistantMessage.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/frontend/src/components/AuthStatus.vue b/frontend/src/components/AuthStatus.vue new file mode 100644 index 0000000..70d2bbc --- /dev/null +++ b/frontend/src/components/AuthStatus.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/frontend/src/components/ChatView.vue b/frontend/src/components/ChatView.vue new file mode 100644 index 0000000..28eac2e --- /dev/null +++ b/frontend/src/components/ChatView.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/frontend/src/components/DevPanel.vue b/frontend/src/components/DevPanel.vue new file mode 100644 index 0000000..a15afd0 --- /dev/null +++ b/frontend/src/components/DevPanel.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/frontend/src/components/EmptyResults.vue b/frontend/src/components/EmptyResults.vue new file mode 100644 index 0000000..a46ef54 --- /dev/null +++ b/frontend/src/components/EmptyResults.vue @@ -0,0 +1,28 @@ + + + diff --git a/frontend/src/components/EmptyState.vue b/frontend/src/components/EmptyState.vue new file mode 100644 index 0000000..a16e32a --- /dev/null +++ b/frontend/src/components/EmptyState.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/frontend/src/components/MessageInput.vue b/frontend/src/components/MessageInput.vue new file mode 100644 index 0000000..f44c61b --- /dev/null +++ b/frontend/src/components/MessageInput.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/frontend/src/components/MessageList.vue b/frontend/src/components/MessageList.vue new file mode 100644 index 0000000..e52a7cf --- /dev/null +++ b/frontend/src/components/MessageList.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/frontend/src/components/ModeBanner.vue b/frontend/src/components/ModeBanner.vue new file mode 100644 index 0000000..4b79150 --- /dev/null +++ b/frontend/src/components/ModeBanner.vue @@ -0,0 +1,36 @@ + + + diff --git a/frontend/src/components/PlaylistError.vue b/frontend/src/components/PlaylistError.vue new file mode 100644 index 0000000..ea5829a --- /dev/null +++ b/frontend/src/components/PlaylistError.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/frontend/src/components/PlaylistSaved.vue b/frontend/src/components/PlaylistSaved.vue new file mode 100644 index 0000000..cfe49ec --- /dev/null +++ b/frontend/src/components/PlaylistSaved.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/frontend/src/components/RequestMetadata.vue b/frontend/src/components/RequestMetadata.vue new file mode 100644 index 0000000..1ea7397 --- /dev/null +++ b/frontend/src/components/RequestMetadata.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/frontend/src/components/ResultActions.vue b/frontend/src/components/ResultActions.vue new file mode 100644 index 0000000..f8e5209 --- /dev/null +++ b/frontend/src/components/ResultActions.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/frontend/src/components/ResultSet.vue b/frontend/src/components/ResultSet.vue new file mode 100644 index 0000000..42f7c31 --- /dev/null +++ b/frontend/src/components/ResultSet.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/frontend/src/components/StreamError.vue b/frontend/src/components/StreamError.vue new file mode 100644 index 0000000..fa27e79 --- /dev/null +++ b/frontend/src/components/StreamError.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/frontend/src/components/StreamWarning.vue b/frontend/src/components/StreamWarning.vue new file mode 100644 index 0000000..4fec607 --- /dev/null +++ b/frontend/src/components/StreamWarning.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/frontend/src/components/SuggestionChips.vue b/frontend/src/components/SuggestionChips.vue new file mode 100644 index 0000000..3252a6b --- /dev/null +++ b/frontend/src/components/SuggestionChips.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/src/components/ThinkingIndicator.vue b/frontend/src/components/ThinkingIndicator.vue new file mode 100644 index 0000000..fd4c87c --- /dev/null +++ b/frontend/src/components/ThinkingIndicator.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/frontend/src/components/TrackCard.vue b/frontend/src/components/TrackCard.vue new file mode 100644 index 0000000..52287b6 --- /dev/null +++ b/frontend/src/components/TrackCard.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/frontend/src/components/UserMessage.vue b/frontend/src/components/UserMessage.vue new file mode 100644 index 0000000..c04cc0a --- /dev/null +++ b/frontend/src/components/UserMessage.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/frontend/src/lib/suggestions.ts b/frontend/src/lib/suggestions.ts new file mode 100644 index 0000000..2a5ef6d --- /dev/null +++ b/frontend/src/lib/suggestions.ts @@ -0,0 +1,9 @@ +export const DISCOVERY_SUGGESTIONS = [ + 'Focus while coding', + 'Energy for the gym', + 'Dutch-language and relaxed', + '90s nostalgia', + 'Surprise me with something new', + 'Rainy Sunday', + 'Background for dinner', +] as const diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index bbcf80c..d5ef513 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,12 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -// https://vite.dev/config/ export default defineConfig({ plugins: [vue()], + server: { + proxy: { + '/api': 'http://127.0.0.1:8888', + '/callback': 'http://127.0.0.1:8888', + }, + }, })