# Bookra ## Delivery Tracker - `[done]` Milestone 1: Remote-first monorepo scaffold completed - SolidJS frontend shell - Go + Gin backend shell - OpenAPI-driven TS client generation - Neon Auth adapter boundary - remote-first env layout for Vercel, Railway, and Neon - `[done]` Milestone 2: Repository-backed booking baseline completed - DB/in-memory repository boundary added - public availability now generated through backend service logic - public booking creation now goes through real service validation - appointment conflict detection implemented - class capacity and waitlist fallback implemented - backend tests added for booking and slot behavior - frontend public booking page can submit a demo booking - `[done/partial]` Milestone 3: Tenant bootstrap, dashboard hydration, and Neon-authenticated API flows - tenant bootstrap endpoint now resolves through repository-backed membership lookup - dashboard summary endpoint now resolves through repository-backed metrics lookup - frontend dashboard now hydrates from API when a Neon Auth JWT is available - frontend dashboard degrades to contract-shaped preview mode when auth is unavailable - Neon Auth token acquisition remains adapter-isolated and still needs production validation against live Neon Auth - `[done/partial]` Milestone 4: Stripe subscription sync and entitlement baseline completed - normalized billing snapshot model added to backend and OpenAPI contract - backend-owned subscription state now syncs through one overwrite path - Stripe checkout launch endpoint added - Stripe webhook endpoint added with signature verification and event filtering - billing snapshot refresh endpoint added - customer portal endpoint added - plan entitlements now derive from normalized plan state - dashboard now surfaces billing status, entitlements, and checkout actions - mock/no-key fallback still works for local development - `[done/partial]` Milestone 5: Reminder jobs, notification delivery abstractions, and remote job-runner baseline completed - booking creation now schedules reminder jobs for qualifying upcoming bookings - notification service now dispatches due jobs through replaceable provider interfaces - localized reminder copy now renders with tenant locale/timezone context - notification delivery log persistence added - internal reminder dispatch endpoint added for Railway-style scheduled execution - shared job-runner key added for remote job execution - current providers are noop-safe abstractions; real email/SMS vendors still need production integration - `[done/partial]` Milestone 6: Production auth completion and tenant identity sync baseline completed - verified Neon JWTs now sync app-side users automatically on protected requests - app-side membership resolution now maps by `users.neon_subject` instead of assuming JWT subject equals DB primary key - frontend auth provider now reads JWT from the Neon session contract instead of a fallback token hook - bootstrap payload now returns user identity fields needed for authenticated shells - Neon subject schema migration added for production databases - live Neon Auth / staging validation still needs to be completed against real infrastructure - `[done/partial]` Milestone 7: First-tenant onboarding flow added to the dashboard - authenticated users without membership now see a real workspace creation flow in `/dashboard` - protected tenant onboarding endpoint added with backend validation for slug, preset, locale, and timezone - onboarding now creates the tenant, owner membership, and first default location - dashboard can transition from empty bootstrap shell to a provisioned tenant state - post-onboarding entity screens for locations, services, staff, blocked days, working hours, and class templates/sessions now exist (Milestones 8 and 10) - `[done/partial]` Milestone 8: Catalog management shipped; live staging validation and operational hardening remain - service CRUD end-to-end: repository (pgx + in-memory), catalog service with validation and tenant isolation, protected `/v1/catalog/services` routes, OpenAPI schemas, dashboard services page - staff CRUD end-to-end with active toggle, optional location assignment, and `MaxStaff` entitlement enforcement; dashboard staff page with upgrade prompt - dashboard summary now resolves `staffName` on bookings and computes `setupCompletion` from real state (brand, location, service, staff, schedule) - booking modal service field is a select bound to the tenant's real services; blocked-day creation supports optional per-staff scoping - deleting a service or staff member preserves bookings (FK `SET NULL`) and removes the member's availability rules (`CASCADE`), mirrored in the in-memory repository - live staging validation, real Neon Auth/Stripe/SMS provider checks, and operational hardening are still open - `[done/partial]` Milestone 9: Public booking flow with real service/staff selection - public booking page is now a stepped flow: pick service → optional staff preference → pick day → pick time; class sessions still listed below - `GET /v1/public/tenants/{slug}/catalog` exposes bookable services and active staff for the booking page - availability endpoint accepts `serviceId`, `staffId`, and `date` filters; default response is unchanged for the embeddable widget - slots now carry `staffName`, respect tenant-wide and per-staff blocked days, and are generated per-day without the preview cap when `date` is given - booking creation hardened: appointment requests must fit inside a matching availability rule, not overlap a blocked day, and staff/location IDs must belong to the tenant with staff active - remaining: live staging validation - `[done/partial]` Milestone 10: Per-staff availability and class/session management - working-hours repository now accepts a nullable staff scope on list/update (pgx + in-memory); staff-scoped rules live in `availability_rules` and directly drive public slot generation - protected routes added: `GET/PUT /v1/catalog/staff/{id}/working-hours[/{day}]` with tenant-ownership checks - class template + session CRUD end-to-end: repository (`classes.go`), catalog service with validation, protected `/v1/catalog/class-templates` and `/v1/catalog/class-sessions` routes, OpenAPI + regenerated client - sessions derive `endsAt` from template duration, default capacity from the template, validate future start, tenant-owned template and optional location; list resolves template title, location name, and live booked count - deletion semantics mirror the schema: deleting a template cascades its sessions; deleting a session preserves bookings via `class_session_id SET NULL` (mirrored in the in-memory repo) - dashboard zones page gained a staff-scope selector on the working-hours tab (business-wide vs individual schedule) and a Classes tab with template create/delete and session scheduling - normalized `availability_rules` time columns (`HH:MM:SS`) down to the `HH:MM` API contract in the service layer - remaining: live staging validation - `[done/partial]` Milestone 11: Booking lifecycle — dashboard cancellation and waitlist promotion - businesses can cancel bookings from the dashboard (`POST /v1/catalog/bookings/{id}/cancel`), tenant-checked, with a two-step confirm in the booking detail modal - cancelling a class booking (dashboard or customer self-service) now promotes the earliest waitlisted bookings while free capacity remains, ordered by booking `created_at`; promoted customers receive a confirmation email, cancelled customers a cancellation email - shared `bookings.PromoteWaitlist` helper drives both cancel paths; waitlist ledger entries are marked resolved on promotion - bookings surface the `waitlisted` status: stats card, filter option, warning-tone badge, demo seed entry; `dashboard.{status}` interpolation resolves the new i18n key automatically - `BookingRecord` now carries `created_at` (needed for fair waitlist ordering); `GetBookingByID` added to the repository surface - remaining: live staging validation; waitlist position display and manual promotion controls are not yet exposed - `[done/partial]` Milestone 12: Real dashboard business metrics - `DashboardMetrics` gained `CompletedCount` (confirmed bookings already ended within the window) in both pgx and in-memory repositories - `DashboardSummary` now queries three trailing windows (next 7d, last 7d, 7-14d ago) and emits four KPIs with real trend deltas: `bookings_this_week`, `completed`, `cancellations`, `utilization` (delta in percentage points) - `DashboardKPI` carries optional `change`/`trend` (`up|down|neutral`); the KPI card already rendered these, so real data now drives the trend arrows instead of the previous `neutral`/`—` fallback - KPI labels are localized in the frontend via a `code` → i18n-key map (`dashboard.kpi.bookingsThisWeek`, `cancellations`, `utilization`; `completed` reuses the existing key); backend labels stay English as a fallback - the booking-trend chart was already bucketing real `allBookings` into the current week — confirmed, no change needed - demo mode intentionally keeps curated static KPI values; real tenants get the computed set - remaining: live staging validation; "new clients" KPI still has no backend metric (customer entity lacks first-booking tracking) - `[done/partial]` Milestone 13: Reminder pipeline reliability - reminder jobs are lifecycle-aware: customer and dashboard cancellation mark pending jobs `cancelled`; reschedule deletes pending jobs and re-arms one via `reminderSchedule` for the new time (no job when moved inside the no-reminder horizon) - dispatch now joins `bookings.status` and skips non-confirmed bookings (`skipped` job status, `skippedCount` in the response contract) — cancelled/waitlisted customers no longer receive reminders for bookings that are not happening - reschedule hardened with the same validation as booking creation: appointment reschedules must fit an availability rule and avoid blocked days (`validateAppointmentTarget` is now a shared package-level helper); class bookings keep session-defined times - new repository methods `CancelPendingReminderJobsForBooking` / `DeletePendingReminderJobsForBooking` in pgx and in-memory implementations - remaining: live staging validation; SMS reminder channel exists in schema (`channel` column) but only `email` is dispatched — SMS reminders are a separate opt-in feature ## Overview Bookra is a Czech-first booking SaaS for small local service businesses. It targets salons, massage therapists, small clinics, repair shops, and small studios/clubs that need a simple public booking flow, internal schedule management, reminders, and basic business reporting without becoming a full ERP. The product is multi-tenant, calm in scope, and optimized for production quality rather than a fast prototype. ## Product Direction Bookra uses one unified booking core with tenant presets. This means: - one platform for appointments and simple classes - different business presets for salon, clinic, repair, massage, sports/studio - shared scheduling, reminders, dashboard, and billing logic - no industry-specific forked products in v1 ## Launch Scope ### Market - Czech Republic first - English included at launch - EU-friendly hosting and data handling defaults ### Supported booking models - Appointments - Capacity-based classes ### Explicitly out of scope for v1 - Customer payments for bookings - Deposits or prepaid services - Memberships, passes, or package accounting - Regulated healthcare workflows - Medical records or sensitive health data - Marketplace/discovery layer ## Customer Experience - Public booking page per business - Guest booking supported by default - Optional customer accounts for history and faster rebooking - Confirmation, cancellation, and reschedule flow - Czech and English UI from day one - Timezone-aware booking and reminders ## Business Experience Each business can manage: - multiple locations - multiple staff members - services - class sessions - business hours and exceptions - buffer times - blackout periods - reminder settings - branding basics - dashboard metrics Owner dashboard should show: - bookings this week - cancellations - utilization - simple revenue forecast based on scheduled services ## Core Scheduling Rules The booking engine must support: - multi-tenant isolation - staff and location assignment - conflict detection - buffer times - timezone-safe date handling - waitlists - class capacity limits - exception-based availability rules Appointments and classes should share the same scheduling foundation where possible. ## Notifications v1 reminder strategy: - email is first-class - SMS is optional and treated as a paid add-on - SMS must be abstracted behind a provider interface - Czech-first provider support is preferred, with room for generic fallback providers later ## Billing Bookra uses Stripe for B2B SaaS subscription billing. (Migrated from Paddle; the Paddle integration was removed.) Stripe scope in v1: - tenant subscription plans - plan upgrades/downgrades - add-ons such as SMS or expanded limits - webhook-based subscription state sync - customer self-serve billing portal Stripe is not used for end-customer booking checkout in v1. ## Remote-First Architecture Bookra follows the `tdvorak-fullstack` profile with remote deployment overrides. ### Stack - Frontend: SolidJS + Vite + TypeScript + Tailwind - Backend: Go + Gin - Database: Neon Postgres + goose (handwritten pgx repositories; sqlc removed) - Auth: Neon Auth - API contract: OpenAPI as source of truth - Frontend deployment: Vercel - Backend deployment: Railway - Local development: direct app processes against remote services ### Structure - modular monolith first - monorepo layout - generated TypeScript API client from OpenAPI - strong tenant boundaries - background jobs for reminders and waitlist handling - no Docker Compose and no self-hosted infra path in v1 Suggested layout: - `/apps/frontend` - `/apps/backend` - `/packages/api-client` - `/packages/shared-types` ## Auth And Identity - Neon Auth handles sign-up, sign-in, sessions, and browser auth state - frontend owns browser auth flows - Go backend verifies Neon Auth JWTs through JWKS - backend treats Neon `sub` as the authenticated principal - backend now syncs `neon_subject` into app-side `users` records on authenticated requests - JWT usage is isolated to frontend-to-backend API calls across domains - Neon-specific auth code must stay behind replaceable adapters because Neon Auth is beta ## Database And Environments - Neon is the only database platform - pooled connections for application traffic - direct connections for migrations and admin tasks - staging and production only in v1 - no per-PR backend preview environments in v1 ## Main Domain Model Key entities: - tenants - tenant users - customer accounts - locations - staff - services - class templates - class sessions - availability rules - availability exceptions - bookings - waitlist entries - reminder jobs - subscription accounts - subscription events ## v1 Delivery Phases ### Phase 1 - monorepo setup - Neon Auth integration shell - tenant foundation - locations, staff, services - i18n foundation - OpenAPI baseline - status: completed ### Phase 2 - appointment booking flow - availability rules - conflict detection - cancellation and rescheduling - timezone-safe scheduling - status: in progress - current implementation: - public availability endpoint returns generated appointment and class slots - public booking creation validates tenant existence, timestamps, conflicts, and class capacity - frontend public route can trigger booking creation from live slots - repository pattern supports Neon-backed persistence with memory fallback ### Phase 3 - class sessions - capacity limits - waitlists - email reminders - dashboard metrics - status: materially implemented - current implementation: - class sessions appear in public availability - class capacity and waitlist status are enforced at booking time - dashboard metrics endpoint is repository-backed but still minimal in scope - booking creation now schedules reminder jobs - due reminder jobs can now be dispatched through the notifications service - delivery logging now exists for reminder execution ### Phase 4 - Stripe subscriptions - SMS add-on support - plan enforcement - hardening, logging, and operational tooling - status: in progress - current implementation: - billing snapshot persistence added - Stripe checkout route added (Checkout Sessions) - Stripe webhook route added with signature verification - Stripe portal route added (customer portal sessions) - subscription refresh route added - entitlement mapping is now available to the frontend - API edge now includes security headers and public route rate limiting - remote job-runner endpoint added for reminder execution - goose commands are wired into repeatable npm scripts ## Current Implementation Notes - Backend is no longer static-demo-only for booking flows. - The repository layer supports: - Neon-backed pgx access when `BOOKRA_DATABASE_URL` is configured - deterministic in-memory fallback when Neon is not configured - OpenAPI remains the frontend/backend contract source. - Frontend public booking flow is now interactive, not just presentational. - Route structure is now explicit: - `/` is the primary marketing landing page - `/dashboard` is the main application entry - `/book/:tenantSlug` stays public and customer-facing - Neon Auth remains intentionally isolated behind adapters because the managed product is still beta. - Billing now follows a backend-owned snapshot model rather than trusting incremental webhook payloads. - `apps/auth-service` is now retired from active runtime and docs. - Reminder delivery now has a working runtime path instead of just a queued schema. - goose migration execution is runnable through workspace scripts. - Protected backend requests now perform automatic app-side identity sync before tenant membership resolution. - `/dashboard` now includes first-run onboarding instead of only a passive empty state for unassigned authenticated users. - Catalog management now covers services and staff end-to-end; staff creation enforces `MaxStaff` entitlements and staff location assignment validates tenant ownership. - Dashboard summary computes `setupCompletion` from real configured state (brand, location, service, staff, availability rules) rather than a static value. - Public booking flow offers real service and staff selection via a stepped picker backed by `GET /v1/public/tenants/{slug}/catalog` and filtered availability (`serviceId`, `staffId`, `date`). - Appointment creation validates that requested times fit inside availability rules, skip blocked days, and reference tenant-owned staff and locations. ## Current Gaps - Neon Auth token extraction is wired structurally, but still needs live browser validation against a real Neon Auth environment. - Tenant membership bootstrap now syncs identities structurally, but staging still needs seeded membership records and onboarding flows for first-time tenants. - Post-onboarding CRUD now covers services, staff, locations, blocked days, working hours, and customers; class/session template management and per-staff availability rules still have no management UI. - Stripe lifecycle sync is implemented; still needs live environment validation against real products, price IDs, and webhook signatures. - Real email/SMS provider integrations are not implemented yet; current delivery providers are safe noops. - The internal reminder dispatch route exists, but Railway scheduling and secret rotation still need production setup. ## Next Build Targets - replace preview-mode dashboard hydration with fully validated Neon Auth JWT flow - sync Neon Auth identities into tenant membership records and onboarding bootstrap - validate Stripe checkout/webhooks/portal and reminder dispatch against live staging infrastructure - replace noop notification providers with the first production email transport and a gated SMS transport - add management UI for class/session templates and per-staff availability rules so service duration, capacity, and staff schedules are editable after onboarding ## Product Principles - calm, premium, structured UX - production-grade architecture - no generic SaaS clutter - deterministic scheduling behavior - minimal operational complexity in v1 - build for maintainability, not feature sprawl ## Success Criteria For v1 Bookra is successful when a small business can: - create its business profile - configure staff, locations, and services - publish a booking page - accept bookings without double-booking - send reminders reliably - manage cancellations and waitlists - view simple business health metrics - subscribe to a paid plan through Stripe ## Final Positioning Bookra is not an ERP, not a medical system, and not a marketplace. It is a focused booking operating layer for small local service businesses that need: - simple setup - reliable scheduling - clear reminders - lightweight reporting - clean SaaS billing