SPB Git

Repository Public

DECISIONS.md 5.1 KB

# Decision log

# Date Decision Why Alternative set aside
D-01 2026-11-11 Single Next.js app + tsx worker, postgres driver, hand-written SQL migrations. Same shape as clip-spboucher-ai, which mld already deploys; exclusion constraints and advisory locks are plain SQL. Prisma / Drizzle: no gain for constraints written in SQL anyway.
D-02 2026-11-11 Availability computed with the native Temporal API on the server only. Exact DST arithmetic without a dependency (Node 26). The browser formats with Intl. Polyfill in the browser: availability is never computed client-side.
D-03 2026-11-11 PostgreSQL as the job queue (transactional outbox); no Redis. A notification exists if and only if its change committed; one less service to run, back up and monitor. The brief's Redis was "to confirm after audit". BullMQ + Redis: dual-write problem, extra runtime. Revisit if volume ever needs it.
D-04 2026-11-11 Three layers against double booking: owner advisory lock, re-check in the lock, exclusion constraint on raw [start, end). Buffers are a preference enforced by the engine; the hard guarantee is "no two confirmed bookings overlap". Constraint on buffered ranges: would forbid legitimate adjacency when buffers differ.
D-05 2026-11-11 Pending requests: per-type policy hold (TTL, default 48 h, never past the slot) or none. An expired hold stops blocking; the request stays pending until its time. Avoids indefinite blocking; the owner can still approve late if the time is free. Auto-declining at hold expiry: loses requests the owner meant to answer.
D-06 2026-11-11 "Propose another time" uses status reschedule_requested, holds the proposed slot up to 72 h, and needs the visitor's acceptance (re-checked). Never confirms something the visitor has not agreed to. Confirming the proposed time directly.
D-07 2026-11-11 Admin authentication is passkey-only. First enrolment and last-resort recovery use a one-time link printed on the server (scripts/setup-token.ts); eight one-use recovery codes are shown at first enrolment. No password to phish or leak; recovery requires either a stored code or shell access to the node. Password + TOTP fallback; e-mail magic links (the mailbox would become the weakest link).
D-08 2026-11-11 Visitor links: token = HMAC(server secret, token row id); the database stores only SHA-256(token). The raw token can be rebuilt when an e-mail is rendered, without ever being stored. Storing raw tokens; putting them in job payloads.
D-09 2026-11-11 Only declining is offered in bulk. Each approval creates an appointment and must be looked at; bulk approval is not "safe". Bulk approve.
D-10 2026-11-11 External calendars V1 = ICS subscription (read-only). Google / Microsoft OAuth not built. Works today with iCloud, Google and Outlook published addresses, with no credentials; OAuth cannot be validated without client credentials, and unvalidated code would be a simulation. Shipping untested OAuth connectors.
D-11 2026-11-11 Stale external calendar → automatic confirmations are withheld (become requests), configurable to ignore. The brief's conservative default. Blocking all public slots when a calendar is stale.
D-12 2026-11-11 Video link is the type's location_detail, typed by the owner, sent only once confirmed. No real meeting integration is configured. Generating meeting links.
D-13 2026-11-11 Static CSP with 'unsafe-inline' for scripts and styles, no third-party origin. Next's inline bootstrap needs a nonce on every render otherwise; same choice as CLIP. Residual risk documented in SECURITY.md. Nonce-based CSP (possible later: every page is already dynamic).
D-14 2026-11-11 Rate limits in PostgreSQL (fixed windows). Shared across processes, survives restarts, no extra service. In-memory counters.
D-15 2026-11-11 The reason and the answers never leave the database except in the admin area. Brief: nothing confidential in URLs, logs, e-mails or ICS. Admin alert e-mails say "see the admin area". Including the reason in the owner's alert e-mail.
D-16 2026-11-11 Visual refresh (« SP » family, phone first): indigo → violet accent on primary actions and selected states, floating glass tab bar, bottom sheets, large titles, deep-black dark theme. The theme follows the system until the owner or visitor picks one. Tokens and components live in src/app/globals.css; no UI dependency was added. The public page is opened on phones; the admin is used on an iPhone. White on the accent stays ≥ 5.6:1, axe runs in the e2e suite. A component library; a web font (system fonts are SF Pro on Apple devices).
D-17 2026-11-11 Admin navigation uses next/link and React <ViewTransition>; booking steps change inside startTransition. Entrance animations use animation-fill-mode: backwards. Tab changes no longer reload the page. A transform animation that keeps applying (both) makes its element the containing block of position: fixed sheets. Full page loads with cross-document view transitions.
Files main
Menu

Theme