Files
Bookra/project.md
T
Tomas DvorakandDevin 5884203e9d feat(catalog): class template editing
Templates were delete-and-recreate only. Adds PUT
/v1/catalog/class-templates/{id} across repository, service (sharing
the create-time validation), route, spec, and client; the classes tab
gets a pencil action that loads the row into the form with a
save/cancel affordance.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:13:37 +02:00

24 KiB

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
  • [done/partial] Milestone 14: Metrics completion + reminder dispatch scheduling
    • DashboardMetrics gained NewClientsCount — distinct customer emails whose earliest-ever booking (starts_at) lands in the window; a fifth KPI new_clients completes the set the demo data promised
    • KPI grid rebalanced for five cards (lg:grid-cols-3 xl:grid-cols-5); new_clients maps to the existing dashboard.kpi.newClients i18n key
    • reminder dispatch now has a real trigger: .github/workflows/reminder-dispatch.yml calls POST /v1/internal/jobs/reminders/dispatch every 15 minutes with X-Bookra-Job-Key from secrets (BOOKRA_API_URL, BOOKRA_JOB_RUNNER_KEY); jobs are timestamp-based so scheduler lateness delays but never skips
    • remaining: live staging validation; the workflow no-ops gracefully until the two secrets are configured
  • [done/partial] Milestone 15: SMS reminder channel
    • booking creation arms a second reminder job on channel: sms when the tenant has SMS enabled (tenant_sms_settings.enabled) and the customer left a phone number; email jobs are unchanged
    • notifications.Service gained an injected smsSender function adapter (wired to sms.Service.SendMessage in the API server, keeping the package dependency-free); dispatch routes channel: sms through it with localized body text and the customer phone as recipient
    • sms.Service.SendMessage still enforces enabled/plan/monthly-limit and logs usage, so SMS reminders inherit metering and billing automatically; missing provider config or phone marks the job failed with a delivery-log reason
    • reminder lifecycle work from milestone 13 applies per-booking, so cancel/reschedule clears and re-arms both channels
    • remaining: live staging validation against a real SMS Manager key
  • [done/partial] Milestone 16: Waitlist position display
    • GetWaitlistPosition repository method (pgx + memory) returns the 1-based queue spot: count of earlier-created waitlisted bookings on the same session + 1
    • CustomerBookingView carries waitlistPosition for waitlisted class bookings; the manage page now shows a proper status badge for waitlisted/cancelled (previously both rendered as "Pending") plus a notice with the queue position and the auto-promotion explanation, in CS and EN
    • fixed invalid token names along the way (--danger-soft/--warning-soft do not exist; --error-subtle/--warning-subtle do)
    • remaining: live staging validation; manual waitlist promotion controls are still not exposed
  • [done/partial] Milestone 17: Class template editing
    • PUT /v1/catalog/class-templates/{id} end-to-end: UpdateClassTemplate in both repositories, catalog service method sharing validateClassTemplateInput with create, route, OpenAPI path, regenerated client
    • classes tab template rows gained a pencil edit action that loads the template into the form; the submit button switches to save-changes with a cancel affordance
    • remaining: live staging validation

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