feat(frontend): assemble accessible application shell

This commit is contained in:
Justin Visser 2026-08-10 15:04:17 +02:00
parent 96c69507ec
commit 036ef3cc6f
27 changed files with 1852 additions and 13 deletions

View file

@ -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.