Repository Public
1# Decision log23| # | Date | Decision | Why | Alternative set aside |4|---|---|---|---|---|5| 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. |6| 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. |7| 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. |8| 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. |9| 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. |10| 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. |11| 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). |12| 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. |13| 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. |14| 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. |15| 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. |16| 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. |17| 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). |18| D-14 | 2026-11-11 | Rate limits in PostgreSQL (fixed windows). | Shared across processes, survives restarts, no extra service. | In-memory counters. |19| 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. |20| 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). |21| 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. |22