尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Appointment booking for small service businesses — barbers, salons, trainers, tutors. A customer
opens a public page, sees the times an employee is actually free for the service they want, and
books one, without the owner answering a message to tell them.
Built for ASE-26 (Agentic Software Engineering) as the own project. The application is the
subject; the development method is the deliverable.
The grading rule is that only what can be opened and verified here counts. So the documents below
are the primary artefacts, and the code is downstream of them.查看完整 README ↗Appointment booking for small service businesses — barbers, salons, trainers, tutors. A customer opens a public page, sees the times an employee is actually free for the service they want, and books one, without the owner answering a message to tell them.
Built for ASE-26 (Agentic Software Engineering) as the own project. The application is the subject; the development method is the deliverable.
The grading rule is that only what can be opened and verified here counts. So the documents below are the primary artefacts, and the code is downstream of them.
| Read this | For |
|---|---|
docs/framing.md | The problem, the stakeholders, the testable definition of done, and what is deliberately out of scope — with reasons |
docs/spec/ | One specification per spiral turn, each committed before the code it generated |
docs/audit/ | One Merge-Readiness audit per turn: five criteria, each answered with a test name or a CI run rather than a claim |
docs/coordination.md | Which agents did what, and — more importantly — what each was and was not shown |
CLAUDE.md | The rules the agent works under |
The turns are visible in the history. Each is a branch merged into main:
git log --graph --oneline main
Within a turn, the order is always specification → implementation → verification → audit, and the commit timestamps are the evidence that it happened in that order rather than being written up afterwards.
Requires Docker (or Colima), and JDK 21 plus Node 22 if you want to run the parts outside containers.
cp .env.example .env
# JWT_SECRET has no default: the backend refuses to start without one, rather than
# quietly signing tokens with a key committed in this repository.
openssl rand -base64 48
docker compose up
export JAVA_HOME=$(/usr/libexec/java_home -v 21) # the project targets Java 21
docker compose up -d postgres redis
cd backend && ./mvnw spring-boot:run
cd backend
./mvnw test # unit tests — no containers, fast enough for the pre-commit hook
./mvnw verify # + integration, security and concurrency suites on real PostgreSQL and Redis
Testcontainers runs real PostgreSQL and Redis, never H2. A test against a different database than production is evidence about the wrong system — and the exclusion constraint that prevents double booking does not exist in H2 at all, so substituting it would quietly delete the guarantee the test is there to prove.
cd frontend
npm ci && npm run lint && npx tsc --noEmit && npm run build
A modular monolith. One deployable, module boundaries drawn so the pieces stay separable — and so the same REST API can serve the Android and iOS clients that come later without a browser assumption anywhere in the contract.
Next.js (browser) ─┐
Android (later) ─┼─→ REST + OpenAPI ─→ Spring Boot ─→ PostgreSQL (source of truth)
iOS (later) ─┘ └─→ Redis (cache, rate limits)
Three decisions worth knowing before reading the code:
Tenant isolation is decided in one place. Tenant-scoped routes are
/api/businesses/{businessId}/... and carry @PreAuthorize("@tenantGuard.canAccess(#businessId)").
TenantGuard looks the caller up in business_members on every request. No businessId is ever
read from a request body. A business that does not exist and a business you are not a member of
return identical responses, because distinguishing them tells an attacker which ids are real.
Double booking is prevented by the database, not by application code. A Postgres btree_gist
exclusion constraint on (employee_id, tstzrange(starts_at, ends_at)) refuses the overlapping row.
A lock in a service method depends on every future code path remembering to take it; a constraint
cannot be forgotten.
Time is computed, never stored as slots. All instants are timestamptz; each business carries
an IANA zone; availability is derived per request from working hours, service duration, existing
appointments and blocked time. A precomputed slot table would be a second source of truth that
drifts.
| Turn | Scope | State |
|---|---|---|
| 1 | Foundation, authentication, tenant isolation | complete — audit |
| 2 | Services, employees, working hours, availability engine | complete — audit |
| 3 | Booking, concurrency, public booking page, deployment | complete — audit · 32/32 |
| 4 | Design-standard rebuild, calendar hand-off, booking alerts, business logo | complete — audit · 15/15 |
Live at https://bookly-pearl.vercel.app — open /book/{slug} for a business and book a time,
no account needed. The frontend runs on Vercel; the API runs on Render at
https://bookly-backend-terp.onrender.com, with PostgreSQL on Neon and Redis on Render Key Value.
The deployment is described by render.yaml. It moved from Railway to Render when
Railway's trial ended; scripts/migrate-db.sh copied the data across.
The backend is on Render's free plan, so it sleeps after about 15 minutes without traffic and the first request afterwards takes a minute or two while it starts. Redis is used only for rate limiting, so its non-persistent free tier loses nothing that matters.
Configuration is by environment variable (see .env.example): DATABASE_URL is
either a JDBC URL or a provider-style postgres:// URL, REDIS_URL is optional and falls back to
REDIS_HOST/REDIS_PORT, and CORS_ALLOWED_ORIGINS must be the exact frontend origin.
To try the whole loop: register, create a business, add a service and an employee, link them and
give the employee working hours. Then open /book/{slug} in a private window, book a time, and
watch it appear in the dashboard's appointment list and calendar while the slot disappears from the
public page.
The deployment runbook is docs/deploy.md. It is worth reading before repeating
this: five attempts failed first, each for a different reason, and every one of them was a
configuration value wrong in a way no error message mentioned. A sixth failure mode is recorded
there too, from turn 4, when the backend still ran on Railway: Vercel and Railway don't agree about
when a merge means "deploy", so a merge going green is not evidence that both halves of the app
are running it — only a request that exercises something turn-4-specific (docs/deploy.md gives
the one-line check) is.