Imported from snicksnk/ai-dev-calend-ci (
AGENTS.md). Install upstream withnpx skills add snicksnk/ai-dev-calend-ci. Copyright stays with the author.
AGENTS.md — Booking Calendar
Repo State
- The API contract lives in
api-spect.tsp. - The Vite + React 18 + TypeScript frontend is scaffolded with
src/,package.json,vite.config.ts,tailwind.config.js, etc. - The app is wired with Mantine, TanStack Query,
react-router-dom, Zustand,dayjs, shadcn/ui (New York / Zinc), and Storybook. .github/workflows/hexlet-check.ymlis auto-generated by Hexlet. Do not delete, rename, or edit it. External tests run on every push..github/workflows/ci.ymltype-checks, builds, and runs the Playwright e2e suite on every push/PR..github/workflows/release-please.ymlautomates versioning/changelog/releases from Conventional Commits (see Commit Messages)..github/workflows/opencode.ymlruns the OpenCode GitHub integration: comment/oc <request>(or/opencode …) on an issue or PR and the agent runs in the Action and replies on the thread (it can also open PRs). Runskimi-k2.7-codevia the OpenCode Zen gateway. Needs theopencode-agentGitHub App installed on the repo (https://github.com/apps/opencode-agent) and anOPENCODE_API_KEYrepo secret (key from opencode.ai)..github/workflows/lighthouse.ymlis a scheduled quality check: on a schedule (cron: "0 14 * * *", currently 17:00 MSK for verification — move back to a nightly slot once confirmed) — and on demand via Run workflow (workflow_dispatch, optionaltarget_url) — it builds the app, serves it on:4010, runs the Lighthouse CLI over/and/owner, uploads the HTML/JSON report as an artifact (30 days), writes a scores table to the job summary, and opens aLighthouse report — <date>issue (labellighthouse) for the team to triage in the morning. Summary generator:scripts/lighthouse-summary.mjs. Process + findings log:docs/lighthouse/.
API Source of Truth
Always verify against api-spect.tsp before coding. Compile it to OpenAPI and generate TypeScript types before coding against the API:
npm run generate-api-types
This compiles the contract to tsp-output/@typespec/openapi3/openapi.yaml, copies it to openapi.yaml (ignored by git), and generates src/api/types.ts from it. Both openapi.yaml and src/api/types.ts must stay in sync with api-spect.tsp.
Endpoints & Models
| Method | Path | Notes |
|---|---|---|
GET |
/event-types |
Owner |
POST |
/event-types |
Owner |
PUT |
/event-types/{id} |
Owner |
DELETE |
/event-types/{id} |
Owner |
GET |
/bookings |
Guest / debug |
POST |
/bookings |
Guest |
GET |
/schedule?days=14&fromDate=... |
Owner |
GET |
/availability?eventTypeId=...&date=... |
Guest |
Models:
- EventType:
id,title,description?,durationMinutes - BookingCreate:
eventTypeId,startTime(UTC) - Booking:
id,eventType(denormalized),startTime,createdAt - AvailableSlot:
startTime,endTime
Architecture Constraints
- No authentication. Owner routes are just
/owner; guest flows are/or/guest/*. - Occupancy rule: a booking blocks its time slot for all event types. The mock server must enforce this; expect
409 Conflicton double-booking. - All API dates are UTC. Use
dayjswith the UTC plugin for API traffic; convert to local time only for display. - Availability window: today through today + 13 days inclusive (14 days).
Dev Server Setup
- Generate the OpenAPI contract and TypeScript types:
npm run generate-api-types - Start the backend server on
http://localhost:4010:
This is a stateful Fastify + TypeScript server (npm run dev:backendserver/src/) that implements the full OpenAPI contract, including the 409 Conflict response on double-booking. It serves interactive Swagger UI athttp://localhost:4010/docs(raw OpenAPI at/docs/json), generated from the route schemas which mirrorapi-spect.tsp. Data is persisted in SQLite (better-sqlite3) atserver/data/booking.db— it survives restarts; delete that file (or the wholeserver/data/directory) to reset to the seeded event types with no bookings. Override the path with theBOOKING_DB_PATHenv var (e.g.:memory:for ephemeral runs/tests). The legacy Express mock (server.js) is kept for reference only. - Start the Vite dev server on
http://localhost:5173(or the configured port).
Alternatively, run both the backend and the frontend with one command:
npm run dev:all
A standard Prism mock (stateless, no occupancy enforcement) is also available for contract testing:
npm run prism:mock
Frontend apiClient base URL is configured in src/api/client.ts and defaults to http://localhost:4010; override it with the VITE_API_BASE_URL env var (used by the e2e tests to point at an isolated backend instance).
End-to-End Tests
Playwright drives a real browser against a real Fastify + SQLite backend — no mocks:
e2e/booking.spec.ts— the guest journey end-to-end (select event type → date → slot → book → confirmation → "Book another appointment" resets the flow), plus the409double-booking conflict rule under a real two-guest race.e2e/owner.spec.ts— the owner journey: create/edit/delete an event type (asserting each change is immediately visible/removed on the guest page too, since both roles hit the same backend), and that a guest's booking shows up in the owner's schedule.e2e/support.ts/e2e/ports.ts— shared helpers (slot selection, row locators) and the dedicated test ports, used by both spec files.
npm run test:e2e # headless run
npm run test:e2e:ui # Playwright UI mode
playwright.config.ts boots dedicated backend/frontend instances on ports 4020/5180 with an in-memory SQLite DB (BOOKING_DB_PATH=:memory:), isolated from the normal dev servers (4010/5173) and from server/data/booking.db — running the suite never touches real dev data or clashes with npm run dev:all already running.
Commit Messages
This repo follows Conventional Commits (feat:, fix:, chore:, refactor:, docs:, test:, etc.), because release-please (.github/workflows/release-please.yml) parses commit history to generate the changelog and bump the version automatically. Every commit — including ones written by an agent — must use this format, e.g.:
feat(guest): add back button to booking confirmation
fix(backend): enforce cross-event-type slot overlap
A fix:/feat: commit on main triggers a release PR; merging it cuts a GitHub release. Breaking changes use feat!:/fix!: or a BREAKING CHANGE: footer.
Storybook
Storybook is configured for isolated component/page development without the backend.
- Start the Storybook dev server on
http://localhost:6006:npm run storybook - Verify the Storybook config without starting the long-running server:
npm run storybook -- --smoke-test .storybook/preview.tsxwraps every story inMantineProvider,QueryClientProvider, andMemoryRouter, so components and pages can use Mantine, TanStack Query, andreact-router-domout of the box.
Scripts
| Command | Purpose |
|---|---|
npm run dev |
Vite dev server |
npm run dev:backend |
Fastify backend (watch mode) on http://localhost:4010, Swagger UI at /docs |
npm run start:backend |
Fastify backend (no watch) |
npm run build:backend |
Compile the backend to server/dist |
npm run typecheck:backend |
Type-check the backend (server/tsconfig.json) |
npm run dev:all |
Backend + Vite dev server |
npm run prism:mock |
Standard Prism mock server on http://localhost:4010 |
npm run build |
Type-check + production build |
npm run typecheck |
tsc --noEmit |
npm run storybook |
Storybook dev server |
npm run storybook -- --smoke-test |
Verify Storybook config |
npm run generate-api-types |
Compile TypeSpec to openapi.yaml and generate src/api/types.ts |
npm run test:e2e |
Run the Playwright e2e suite (headless) |
npm run test:e2e:ui |
Run the Playwright e2e suite in UI mode |
npm run typecheck:e2e |
Type-check the e2e tests (e2e/tsconfig.json) |
Tech Stack
- Vite + React 18 + TypeScript
- shadcn/ui (New York / Zinc) — primary UI components
- Mantine (
@mantine/core,@mantine/hooks,@mantine/dates) — DatePicker, Calendar, notifications - TanStack Query — data fetching and cache invalidation
- Zustand — global state (event types, bookings)
openapi-typescript— generate types from the Prism/OpenAPI specreact-router-dom— routing- Storybook — isolated component and page development
Notes
api-spect.tspcomments are in Russian; model/field names are self-explanatory.- The Hexlet test suite is external and opaque. Routes, request shapes, and API calls must match
api-spect.tspexactly.