LabReserve
Web system to check availability and book labs and equipment, with automated tests.
Problem
Booking labs and equipment without a central system leads to schedule conflicts and manual control. The project also tested how to build a working app with AI agents following good engineering practices.
Decisions
- TypeScript end to end, with React for the interface and SQLite as a simple database with no separate server.
- Plan before code: scope split into small stories with acceptance criteria.
- Test-first: every behavior change starts with a failing test.
- Validation gate with typecheck, lint, build, tests and E2E before calling anything done.
Stack
- TypeScript
- React
- SQLite
Result
Local system with availability search, bookings with schedule conflict detection, filters, cancellation and event history.
Learnings
Using tests, planning and review to stay in control of code written with AI agents.
How I used AI
Built with AI agents. CLAUDE.md pins down the architecture, the business rules that must stay true, test-first work and the full gate (typecheck, lint, build, tests and E2E). AGENT_ISSUES.md is the persistent handoff board between sessions. The UI delivery went through a self-audit and two independent, blind reviews run in parallel, recorded under docs/verification/; it was approved in the sixth round.
Evidence
IllustrationArchitecture: modular monolith with dependencies pointing inward, as described in the repository's docs/ARCHITECTURE.md. IllustrationBooking creation flow: interval validation, conflict check and atomic write of the booking and its event. overlaps(other: TimeInterval): boolean { return this.startAt < other.endAt && this.endAt > other.startAt; }Overlap rule in the domain: equal limits don't conflict. View in repository
createReservation(input: CreateReservationInput): Reservation { const interval = TimeInterval.create(input.startAt, input.endAt); return this.repository.transaction(() => { if (!this.repository.findResource(input.resourceId)) { throw new ApplicationError('RESOURCE_NOT_FOUND', 'Recurso não encontrado.'); } const conflict = this.repository.findConflict(input.resourceId, interval.startAt, interval.endAt); if (conflict) { throw new ApplicationError( 'RESERVATION_CONFLICT', 'O recurso já está reservado nesse período.', { conflictingReservationId: conflict.id }, ); } const createdAt = this.clock.now().toISOString(); const reservation = createConfirmedReservation({ id: this.ids.generate(), resourceId: input.resourceId, startAt: interval.startAt, endAt: interval.endAt, createdAt, }); const event: ReservationEvent = { id: this.ids.generate(), reservationId: reservation.id, type: 'RESERVATION_CREATED', occurredAt: createdAt, }; this.repository.insertReservation(reservation); this.repository.insertEvent(event); return reservation; }); }Creation use case: conflict check and write of the booking and its event in the same transaction. View in repository
test('detecta sobreposição estrita e permite intervalos consecutivos', () => { const existing = TimeInterval.create('2026-09-16T10:00:00Z', '2026-09-16T11:00:00Z'); assert.equal( existing.overlaps(TimeInterval.create('2026-09-16T10:30:00Z', '2026-09-16T11:30:00Z')), true, ); assert.equal( existing.overlaps(TimeInterval.create('2026-09-16T11:00:00Z', '2026-09-16T12:00:00Z')), false, ); });Domain test pinning the rule: partial overlap conflicts, back-to-back slots don't. View in repository
Real output of the domain, integration and API tests (node:test), run on 2026-09-30 at commit 204de08, Node 24.20.0.$ npm test ✔ responde 413 legível para corpos realmente grandes sem resetar a conexão (386.123ms) ✔ lista recursos e cria reserva pelo contrato HTTP (55.2194ms) ✔ lista, cancela e mostra histórico com erros estruturados (77.333ms) ✔ responde 404 para o histórico de uma reserva inexistente (10.1546ms) ✔ protege respostas e rejeita corpos que não sejam JSON (89.2057ms) ✔ registra falhas inesperadas sem expor seus detalhes (8.7134ms) ✔ mantém a resposta interna segura quando o logger falha (12.5998ms) ✔ informa disponibilidade, histórico global e erros de domínio pelo contrato (42.4548ms) ✔ cancela uma reserva confirmada uma única vez (4.9019ms) ✔ normaliza um intervalo ISO 8601 com offset para UTC (3.9276ms) ✔ rejeita um intervalo cujo término seja igual ao início (1.2695ms) ✔ rejeita uma data que não esteja em ISO 8601 com fuso explícito (0.5249ms) ✔ rejeita datas ISO 8601 sintaticamente válidas, mas inexistentes (0.415ms) ✔ detecta sobreposição estrita e permite intervalos consecutivos (0.5614ms) ✔ o projeto declara scripts reproduzíveis sem versões flutuantes (3.5348ms) ✔ cancelamento gera evento, libera intervalo e suporta filtros (9.6265ms) ✔ cria reserva e evento atomicamente, recusa conflito e aceita horário consecutivo (6.282ms) ✔ reverte a reserva quando a gravação do evento falha (1.5119ms) ✔ um banco novo recebe laboratório e equipamento determinísticos (5.8135ms) ℹ tests 19 ℹ suites 0 ℹ pass 19 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ duration_ms 1194.8207
Real output of the Playwright E2E scenarios (Chromium), in the same run.$ npm run test:e2e Running 3 tests using 2 workers ok 1 tests\e2e\reservations.spec.ts:73:1 › consulta período com data vazia sem deixar carregamento pendente (3.0s) ok 2 tests\e2e\reservations.spec.ts:31:1 › cria, rejeita conflito, permite período consecutivo, cancela e mostra histórico (4.1s) ok 3 tests\e2e\reservations.spec.ts:85:1 › aplica filtros de reserva (3.1s) 3 passed (12.5s)- Source code, implementation plan and verification records on GitHub