fix: type the health test mode, pace cards with the stream, and size the results panel
Some checks are pending
ci / backend (push) Waiting to run
ci / frontend (push) Waiting to run

This commit is contained in:
Justin Visser 2026-08-11 10:15:20 +02:00
parent fa356ba5c5
commit cdace7d170
7 changed files with 41 additions and 15 deletions

View file

@ -154,6 +154,11 @@ Forgejo/GitHub mirror, deployment, how the agents are used) is mapped in
- A faster model on the intent call, which would cut most of the time to - A faster model on the intent call, which would cut most of the time to
first card; the grounding stage already measures the trade-off to watch first card; the grounding stage already measures the trade-off to watch
(fabrication rate), so the experiment is cheap to run safely. (fabrication rate), so the experiment is cheap to run safely.
- A guaranteed minimum grounding budget. The request deadline is anchored
at request start, so an unusually slow intent call can leave grounding
with no time at all; raising the deadline works around it (env-tunable),
but grounding deserves a floor of its own regardless of what call 1
spent.
- Deeper discovery. A repair loop that re-prompts the model for candidates - Deeper discovery. A repair loop that re-prompts the model for candidates
that failed verification would let the prompt chase less obvious work that failed verification would let the prompt chase less obvious work
without losing the pool, and a novelty signal beyond exact-id exclusion without losing the pool, and a novelty signal beyond exact-id exclusion

View file

@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, Mock
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from app.config import Settings from app.config import AppMode, Settings
from app.main import create_app from app.main import create_app
@ -23,7 +23,7 @@ def test_anthropic_client_uses_configured_timeout(monkeypatch: pytest.MonkeyPatc
monkeypatch.setattr("app.main.AsyncAnthropic", constructor) monkeypatch.setattr("app.main.AsyncAnthropic", constructor)
live_settings = Settings( live_settings = Settings(
app_mode="live", app_mode=AppMode.LIVE,
spotify_client_id="client-id", spotify_client_id="client-id",
anthropic_api_key="api-key", anthropic_api_key="api-key",
llm_timeout_seconds=42.0, llm_timeout_seconds=42.0,

View file

@ -472,3 +472,29 @@ Wat ik heb laten vallen of uitgesteld:
- Een sneller model op de intent call (zou de wachttijd flink verlagen; - Een sneller model op de intent call (zou de wachttijd flink verlagen;
de fabrication rate is de afweging om dan te meten) staat als vervolg de fabrication rate is de afweging om dan te meten) staat als vervolg
in de README, niet gebouwd. in de README, niet gebouwd.
### Afronding en de laatste live-test
Wat ik deed:
- Laatste ronde live testen op de gehoste instantie voor het insturen.
Twee dingen gevonden: een vraag waarbij call 1 zo lang nadacht dat de
request deadline al om was voordat grounding begon (0 resultaten,
0 Spotify calls), en opnieuw QUOTA_EXCEEDED 429's omdat het dagquotum
van gisteren nog meetelde in het huidige quota-venster.
- De deadline via env verhoogd naar 60 s en de cache-TTL's op de gehoste
instantie opgerekt (resolutie 24 h, taste profile 1 h) zodat het quotum
langer meegaat; een structurele fix (een gegarandeerd minimum
tijdsbudget voor grounding, los van wat call 1 opmaakt) staat in de
README als vervolgstap.
- De card-animatie versneld: elke kaart wachtte nog index maal 80 ms na
binnenkomst voordat hij verscheen, bovenop het tempo van de stream
zelf. De vertraging is weg; de stream is nu zelf de cadans. De
resultatenlijst toont nu ongeveer 5,5 kaarten zodat zichtbaar is dat er
meer te scrollen valt.
- Insturen gepland na de quota-reset vanavond.
Waarom:
- Een reviewer die de live instantie opent moet niet als eerste een
uitgeput quotum zien; de reset bepaalt dus het moment van insturen.

View file

@ -17,12 +17,7 @@ const regionLabel = computed(() =>
<template> <template>
<div class="set" role="region" :aria-label="regionLabel" tabindex="0"> <div class="set" role="region" :aria-label="regionLabel" tabindex="0">
<TrackCard <TrackCard v-for="event in tracks" :key="event.track.id" :event="event" />
v-for="(event, index) in tracks"
:key="event.track.id"
:event="event"
:style="`--card-order: ${index}`"
/>
</div> </div>
</template> </template>
@ -31,7 +26,8 @@ const regionLabel = computed(() =>
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--s-2); gap: var(--s-2);
max-height: min(42dvh, 480px); /* Roughly 5.5 cards tall: the half card signals there is more to scroll. */
max-height: min(66dvh, 610px);
padding: var(--s-2) var(--s-3) var(--s-2) 0; padding: var(--s-2) var(--s-3) var(--s-2) 0;
overflow-y: auto; overflow-y: auto;
overscroll-behavior-y: contain; overscroll-behavior-y: contain;
@ -42,7 +38,7 @@ const regionLabel = computed(() =>
@media (max-width: 560px) { @media (max-width: 560px) {
.set { .set {
max-height: min(34dvh, 360px); max-height: min(52dvh, 470px);
} }
} }
</style> </style>

View file

@ -53,7 +53,6 @@ const artworkAlt = computed(() =>
border-color var(--dur-fast) ease, border-color var(--dur-fast) ease,
background var(--dur-fast) ease; background var(--dur-fast) ease;
animation: card-in var(--dur-card) var(--ease) both; animation: card-in var(--dur-card) var(--ease) both;
animation-delay: calc(var(--card-order, 0) * var(--dur-card-stagger));
} }
.card:hover { .card:hover {

View file

@ -76,7 +76,6 @@
--ease: cubic-bezier(0.2, 0.8, 0.3, 1); --ease: cubic-bezier(0.2, 0.8, 0.3, 1);
--dur-fast: 0.16s; --dur-fast: 0.16s;
--dur-card: 0.4s; --dur-card: 0.4s;
--dur-card-stagger: 0.08s;
--dur-reduced-motion: 0.001s; --dur-reduced-motion: 0.001s;
color-scheme: dark; color-scheme: dark;

View file

@ -104,12 +104,13 @@ describe('result presentation', () => {
expect(region?.contains(action ?? null)).toBe(false) expect(region?.contains(action ?? null)).toBe(false)
}) })
it('assigns a deliberate stagger order to arriving cards', () => { it('renders arriving cards without an artificial entrance delay', () => {
const root = mountComponent(AssistantMessage, { turn: doneTurn(), canSave: true }) const root = mountComponent(AssistantMessage, { turn: doneTurn(), canSave: true })
const cards = root.querySelectorAll<HTMLElement>('article') const cards = root.querySelectorAll<HTMLElement>('article')
expect(cards[0]?.style.getPropertyValue('--card-order')).toBe('0') expect(cards.length).toBeGreaterThan(1)
expect(cards[1]?.style.getPropertyValue('--card-order')).toBe('1') expect(cards[0]?.style.getPropertyValue('--card-order')).toBe('')
expect(cards[1]?.style.getPropertyValue('--card-order')).toBe('')
}) })
it('anchors a completed response at the top of the conversation viewport', async () => { it('anchors a completed response at the top of the conversation viewport', async () => {