Vibecode
De afsluiter van het AI in de Stad-congres in Breda: 350 bezoekers bouwen in groepjes tegelijk een eigen app met AI, en zien het resultaat samen op het grote scherm.
Na een dag praten over AI in de stad mocht de zaal het zelf doen. Je zit in een groepje, met een telefoon. Scan de code op het kaartje, beschrijf een stedelijk probleem en een idee, en druk op genereren. Je ziet de pagina groeien terwijl het model schrijft: een kaart, een simulatie, een dashboard. Wil je iets anders, dan zeg je dat, en er komt een nieuwe versie. Publiceer, en je werk hangt in de galerij op het scherm van de zaal, tussen dat van alle andere groepjes. Zo eindigde het congres niet met een laatste spreker, maar met een wand vol apps die het publiek zelf had gemaakt.
Ik heb de applicatie gebouwd, uitgerold en op precies die belasting getest: een zaal vol groepjes die op hetzelfde moment op genereren drukken. Een Node-server met Fastify en een beheerde Postgres op Scaleway; de container houdt niets vast, dus er kunnen er meer naast. De gegenereerde code wordt niet gefilterd maar geïsoleerd: elke pagina draait in een afgeschermd venster zonder toegang tot de rest van de site, met dezelfde beperking als Content-Security-Policy-header. Geen accounts: een code van vijf tekens en een sleutel per apparaat. Een eigen stresstest bootst honderd gelijktijdige gebruikers na en meet wachttijden, plek in de rij en bereikbaarheid; daaruit kwam compressie van de pagina en een schaling van één tot drie containers. Een vastgelopen aanvraag wordt na drie minuten vrijgegeven; bij een configuratiefout weigert de server te starten. De applicatie is zelf met AI-hulp geschreven.
Colofon
- Getoond: Het Grote AI in de Stad Vibe Coding Experiment, Chassé Theater Breda, 24 september 2026
- Studio: Starling Studio
- Mijn aandeel: projectmanagement, klantcontact, ontwerp, bouw, uitrol, stresstest
Code
De wachtrij achter het genereren, ingekort: een rij in de database per aanvraag, een teller die het aantal gelijktijdige aanroepen naar het model begrenst, en een veegactie die rijen vrijgeeft waarvan de eigenaar niet meer meldt dat hij leeft.
const STALE_AFTER_MINUTES = 3;
readonly #pending: number[] = [];
#active = 0;
#enqueue(id: number, messages: ChatMessage[]): void {
this.#messages.set(id, messages);
this.#pending.push(id);
this.#pump();
}
#pump(): void {
while (this.#active < this.#config.generateConcurrency && this.#pending.length > 0) {
const id = this.#pending.shift()!;
this.#active += 1;
void this.#run(id).finally(() => {
this.#active -= 1;
this.#messages.delete(id);
this.#pump();
});
}
}
/** Rijen waarvan de eigenaar niet meer meldt dat hij leeft zijn kwijt. */
async sweepStale(): Promise<void> {
await run(this.#db,
`UPDATE generations SET status = 'error', error = 'stale', finished_at = now()
WHERE status IN ('queued', 'running')
AND heartbeat_at < now() - ($1 || ' minutes')::interval`,
String(STALE_AFTER_MINUTES));
}