chore: restore wrapped chips, trim unused scopes, and complete env docs
This commit is contained in:
parent
401a0ddea7
commit
5080e9a609
6 changed files with 74 additions and 57 deletions
18
.env.example
18
.env.example
|
|
@ -1,5 +1,17 @@
|
|||
# Copy to .env and adjust. Without a .env the app starts in demo mode.
|
||||
APP_MODE=demo
|
||||
# Runtime mode; set to demo for no-key fixtures or live for Spotify and Anthropic.
|
||||
APP_MODE=
|
||||
|
||||
# The redirect URI must exactly match the URI registered in the Spotify dashboard.
|
||||
# Spotify app client ID; required in live mode and unused in demo mode.
|
||||
SPOTIFY_CLIENT_ID=
|
||||
|
||||
# Spotify OAuth callback URI; set in live mode if the local default is not registered.
|
||||
SPOTIFY_REDIRECT_URI=
|
||||
|
||||
# Anthropic API key; required in live mode and unused in demo mode.
|
||||
ANTHROPIC_API_KEY=
|
||||
|
||||
# Pre-authorized Spotify token; only for hosted instances without interactive login.
|
||||
SPOTIFY_SEED_REFRESH_TOKEN=
|
||||
|
||||
# Secure session cookie flag; set to true on every HTTPS deployment.
|
||||
SESSION_COOKIE_SECURE=
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ api -> pipeline -> ports <- adapters domain imports nothing app-level
|
|||
- Spotify JSON never escapes `adapters/spotify/mapping.py`.
|
||||
- Pipeline tunables (counts, thresholds, budgets, model id, effort, TTLs) live
|
||||
in `app/config.py` (pydantic-settings). No magic numbers in code.
|
||||
- LLM prompts are versioned template files under `app/prompts/`, not inline
|
||||
strings.
|
||||
- `eval/scenarios.yaml` is one source of truth for three consumers: golden
|
||||
eval queries, demo fixture keys, and UI suggestion chips.
|
||||
- LLM prompts live as constants in `app/prompts.py`, not inline in adapters.
|
||||
- `eval/scenarios.yaml` defines golden eval queries and fixture-recording keys.
|
||||
UI suggestion chips live in `frontend/src/lib/suggestions.ts`.
|
||||
|
||||
## Naming
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@ from app.adapters.spotify.errors import SpotifyAuthenticationError
|
|||
|
||||
AUTHORIZE_URL = "https://accounts.spotify.com/authorize"
|
||||
TOKEN_URL = "https://accounts.spotify.com/api/token"
|
||||
SCOPES = (
|
||||
"user-top-read user-library-read user-read-recently-played "
|
||||
"playlist-modify-public playlist-modify-private "
|
||||
"user-read-playback-state user-modify-playback-state"
|
||||
)
|
||||
SCOPES = "user-top-read user-library-read playlist-modify-public playlist-modify-private"
|
||||
TOKEN_EXPIRY_SKEW_SECONDS = 60.0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -396,3 +396,45 @@ Wat ik heb laten vallen of uitgesteld:
|
|||
|
||||
- Byte-snapshots per pipeline-stap; de checks hierboven en de cassettes
|
||||
zijn nu het bewijs.
|
||||
|
||||
### Demo mode
|
||||
|
||||
Wat ik deed:
|
||||
|
||||
- Demo mode af: zonder keys speelt de app de opgenomen cassettes af door
|
||||
dezelfde pipeline en hetzelfde streamingpad als live. Onbekende vragen
|
||||
krijgen het dichtstbijzijnde scenario, met een banner die dat eerlijk
|
||||
benoemt; playlist-acties zijn gesimuleerd en zo gelabeld.
|
||||
- Een eerste versie deelde replay-state tussen gelijktijdige requests;
|
||||
dat kon elkaars refinement verstoren. Nu krijgt elke request zijn
|
||||
eigen replay-pool, met een test die de botsing naspeelt.
|
||||
- Het docker image bestaat nu in twee smaken: live zonder fixtures, demo
|
||||
met. docker compose bouwt standaard de demo-smaak, dus een verse clone
|
||||
zonder keys werkt direct. Een .dockerignore bracht de build context
|
||||
van ~350 MB terug naar ~7 MB.
|
||||
|
||||
Waarom:
|
||||
|
||||
- De reviewer moet de app kunnen starten met alleen docker compose,
|
||||
zonder accounts of keys; demo mode is ook het bewijs dat de
|
||||
ports-and-adapters opzet echt is (dezelfde poorten, andere adapter).
|
||||
|
||||
Wat ik heb laten vallen of uitgesteld:
|
||||
|
||||
- Meer demo-scenario's; de 8 opgenomen zijn de suggestion chips en dat
|
||||
is genoeg voor het verhaal.
|
||||
|
||||
### Opruimen
|
||||
|
||||
Wat ik deed:
|
||||
|
||||
- De suggestion chips terug naar de wrapped look; de scrollbare box uit de
|
||||
vorige batch stond lelijker dan wat er eerst was.
|
||||
- Ongebruikte OAuth scopes weggehaald (player en recently-played; nergens
|
||||
in de code gebruikt), .env.example compleet gemaakt en AGENTS.md
|
||||
gelijkgetrokken met hoe de repo er echt uitziet.
|
||||
|
||||
Waarom:
|
||||
|
||||
- Minder scopes vragen dan je gebruikt is netter richting de reviewer en
|
||||
richting Spotify.
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ defineEmits<{ pick: [suggestion: string] }>()
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="frame">
|
||||
<div class="chips" role="region" :aria-label="messages.suggestionChipsRegionLabel" tabindex="0">
|
||||
<div class="chips" role="region" :aria-label="messages.suggestionChipsRegionLabel">
|
||||
<button
|
||||
v-for="suggestion in suggestions"
|
||||
:key="suggestion"
|
||||
|
|
@ -18,47 +17,16 @@ defineEmits<{ pick: [suggestion: string] }>()
|
|||
{{ suggestion }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.frame {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background: var(--c-surface-raised);
|
||||
border: 1px solid var(--c-line);
|
||||
border-radius: var(--r-md);
|
||||
}
|
||||
|
||||
.frame::after {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
bottom: var(--s-3);
|
||||
width: var(--s-10);
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
background: linear-gradient(to right, transparent, var(--c-surface-raised));
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--s-3);
|
||||
padding: var(--s-3) var(--s-10) var(--s-3) var(--s-3);
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-color: var(--c-line-strong) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.chips:focus-visible {
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
.chip {
|
||||
flex: none;
|
||||
padding: var(--s-3) 15px;
|
||||
color: var(--c-text-chip);
|
||||
font-size: var(--t-small);
|
||||
|
|
|
|||
|
|
@ -150,14 +150,14 @@ describe('result presentation', () => {
|
|||
expect(scrollIntoView).toHaveBeenCalledWith({ block: 'start' })
|
||||
})
|
||||
|
||||
it('keeps suggestions in a labeled focusable row', () => {
|
||||
it('keeps suggestions in a labeled region', () => {
|
||||
const onPick = vi.fn()
|
||||
const suggestions = ['Focus', 'Energy', 'Surprise']
|
||||
const root = mountComponent(SuggestionChips, { suggestions, onPick })
|
||||
const region = root.querySelector<HTMLElement>('[aria-label="Listening suggestions"]')
|
||||
const buttons = root.querySelectorAll<HTMLButtonElement>('button')
|
||||
|
||||
expect(region?.tabIndex).toBe(0)
|
||||
expect(region).not.toBeNull()
|
||||
expect(buttons).toHaveLength(suggestions.length)
|
||||
expect([...buttons].map((button) => button.textContent?.trim())).toEqual(suggestions)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue