// Shared community + changelog data for all three directions.
// Exported to window so each direction's JSX can read from one source.

const CHANGELOG = [
  {
    id: "rel-147",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Featured",
    tone: "amber",
    title: "Four new smart-lock brands — igloohome, TTLock, SwitchBot, Tedee",
    body: "The LockProvider facade shipped last week paid off immediately. Four new lock integrations, each a self-contained LockProvider implementation with zero feature-code touch:",
    bullets: [
    ],
    hook: "If a property has any of these locks, the code path is now identical to a Nuki property — generate code, expiry rule, revoke, activity log all work out of the box.",
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-146",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Featured",
    tone: "blue",
    title: "Pulse Copilot quota + Stripe self-serve upgrade",
    body: "Copilot spend is now capped + billable in three tiers:",
    bullets: [
    ],
    hook: "Copilot cost is predictable at the tenant level; users hitting quota can upgrade themselves without ops involvement — the SaaS motion becomes real.",
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-145",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Featured",
    tone: "violet",
    title: "Messaging provider abstraction — WhatsApp behind providerKit",
    body: "The same providerKit pattern that hid Nuki + 2N behind LockProvider now hides WhatsApp behind a MessagingProvider interface. Feature code (send text, send template, send image) no longer imports the WhatsApp Cloud API SDK directly. Foundation for adding Telegram / SMS / iMessage as future MessagingProvider…",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-144",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Per-tenant Hospitable + Nuki credentials — no more global-env split-brain",
    body: "Hospitable API tokens + Nuki API tokens previously lived as global env vars — meaning every tenant used the same credentials. This blocked adding tenant #2 and produced sneaky bugs where \"which Hospitable account is this reservation from?\" answered by looking at the env. Now: creds live in tenantcredentials…",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-143",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Feature",
    tone: "amber",
    title: "MCP server rebuilt on tRPC callers with tenant scoping",
    body: "The MCP server (which exposes tRPC tools to Claude Code + other MCP clients) was rebuilt on top of tRPC's createCaller pattern with explicit tenant scoping on every tool call. Kills the gh#982 cross-tenant leak (8 of 13 MCP tools were previously unscoped by construction). (#980)",
    bullets: [
    ],
    docLink: "/docs/admin",
    author: "Product team",
  },
  {
    id: "rel-142",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Pulse dashboard task cards — EDIT + DELETE removed",
    body: "Task-level EDIT + DELETE actions have been removed from the Pulse (agenda) task cards — kept in the Operations task view. Pulse is for reading the day; Operations is for managing tasks. Reduces mis-clicks on the dashboard. (#998)",
    bullets: [
    ],
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-141",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Stuck auto Generate-access-code task — now dismissable",
    body: "A stuck / overdue AUTO Generate-access-code task previously had NO Dismiss button — operator couldn't resolve-without-action when the guest was already handled manually. Dismiss button restored (parity with other AUTO tasks). (#1001)",
    bullets: [
    ],
    docLink: "/docs/tasks",
    author: "Product team",
  },
  {
    id: "rel-140",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Check-in instructions double-sent + images dropped on nginx 504 — Natalja incident",
    body: "Check-in instructions sent twice (text body) + images dropped when nginx returned 504. Root cause: on 504, the task re-fired the full send instead of resuming with the failed images only — text hits 200 both times, images fall through. Same family as gh#453 + gh#856. Fix: partial-send-state persisted per task; retry…",
    bullets: [
    ],
    docLink: "/docs/reservations",
    author: "Product team",
  },
  {
    id: "rel-139",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Fix",
    tone: "amber",
    title: "[SEC-1] SSRF: outbound webhook URLs unvalidated (latent)",
    body: "Outbound webhook subscription URLs weren't validated against the internal-network SSRF allowlist. Latent (the public-API layer that would let a tenant subscribe wasn't shipped yet) but the code path was there. Validation added + a CI ratchet forbids new URL-accepting endpoints without the SSRF guard. (#981)",
    bullets: [
    ],
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-138",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Fix",
    tone: "amber",
    title: "[SEC-2] MCP cross-tenant leak by construction — 8 of 13 tools unscoped",
    body: "8 of the 13 MCP-server tools were unscoped by construction — a query for tenant A's data would return tenant B's rows because the tool never applied a tenant filter. Fixed by rebuilding the MCP server on tRPC callers with per-tool tenant scoping (see gh#980). (#982)",
    bullets: [
    ],
    docLink: "/docs/admin",
    author: "Product team",
  },
  {
    id: "rel-137",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Fix",
    tone: "amber",
    title: "[SEC-3] Tenant clobber: Hospitable handlers override URL-auth tenant from payload",
    body: "The Hospitable webhook handlers were reading tenantId from the payload (attacker-controlled) instead of the URL-scoped auth. Same shape as gh#623. Now retired before tenant #2 lands. (#983)",
    bullets: [
    ],
    docLink: "/docs/channel-sync",
    author: "Product team",
  },
  {
    id: "rel-136",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Improvement",
    tone: "emerald",
    title: "Fly cost cleanup — $125.95 invoice, down to something reasonable",
    body: "A billing analysis surfaced sandbox running 2x-2048 24/7 + a demo-db idle machine. Sandbox rightsized to auto-stop; demo-db idle stopped. Full retrospective on Fly infra sizing for the next scale-up. (#925)",
    bullets: [
    ],
    docLink: "/docs/cost-observability",
    author: "Product team",
  },
  {
    id: "rel-135",
    version: "v1.46",
    date: "July 5 – 6, 2026",
    tag: "Improvement",
    tone: "amber",
    title: "2N lock automation blocked 6–10 s per guest + leaked headless browser",
    body: "The 2N lock automation path used page.setTimeout hardcoded to 3–5 s + a headless Chrome that never .close()d — every guest arrival stalled the cron 6–10 s + leaked a Chrome process. Fixed via waitForSelector + explicit browser lifecycle. (#938)",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-134",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Pulse Copilot — confirm-before-mutate approval gate",
    body: "Every Copilot mutation (intents + executetrpc) now surfaces a confirmation preview before execution: \"About to update Property X → base price 89 → 95. Confirm?\" Applies to every write path, no exceptions. Companion fix: the agent can no longer self-confirm the gate (a bug where the agentic self-confirm loop bypassed…",
    bullets: [
    ],
    hook: "No more \"I asked it to change one thing and it modified three.\"",
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-133",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Pulse Copilot — full admin-UI parity",
    body: "The Copilot previously silently forwarded template-edit requests into FAQ edits (it lacked a messageTemplates.update tool + reported success anyway — gh#893). Now it reaches every admin domain:",
    bullets: [
    ],
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-132",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Pulse Copilot — cost + quality refactor",
    body: "The Copilot's plan → critique → revise → rehumanize pipeline (four separate LLM calls per turn, ~72% of spend was prompt-cache writes) collapsed into one specialist loop with a mutation-only verifier. Model sizing also swept: everything right-sized to Sonnet 5 / Haiku 4.5 with self-healing model resolution (no…",
    bullets: [
    ],
    hook: "Copilot responses become faster, cheaper, and stop stalling behind a 4-round LLM plan/revise loop.",
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-131",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Smartlock abstraction — one LockProvider API, Tuya added",
    body: "A unified LockProvider API hides Nuki + 2N behind one facade — feature code (send access code, generate PIN, unlock, revoke) no longer imports vendor SDKs. Immediate payoff: shipping Tuya smart-lock integration (Cloud API — reaches many OEM locks) as just a new LockProvider implementation, no feature-code changes…",
    bullets: [
    ],
    hook: "Any future property with a non-Nuki/2N lock (Tuya, Yale, August, Salto, etc.) is now a config-only onboarding.",
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-130",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Beds24 pipeline activates — rates / availability / content push",
    body: "The Beds24 connector was previously read-only (import bookings) — now it also pushes rates, availability, and content updates. Companion V28 change: the connector editor (provider + property id + Test Connection + Map room) is ported into V28 PM → Configuration tab. (#715, #722)",
    bullets: [
    ],
    docLink: "/docs/channel-sync",
    author: "Product team",
  },
  {
    id: "rel-129",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Base price editing — ported to V28",
    body: "Editing property availability + pricing (base price, weekend override, seasonal ramps) previously required a bounce to legacy /admin/properties. Now native in V28 PM. (#875)",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-128",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Cleaner walkthrough — self-approve/reject redesign EPIC opens",
    body: "An epic just landed: Copilot full UI-parity task-template configuration as the enabler for the upcoming cleaner self-approve/reject redesign. Cleaners will be able to approve/reject their own walkthrough completion via WhatsApp buttons; the epic groundwork (Copilot-editable task-templates so the operator can build the…",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-127",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Supplies — product image auto-fetch + LLM verifier",
    body: "Every supply now has an auto-fetched product image (canonical name lookup + LLM verifier that rejects wrong pictures — no more \"detergent\" showing a bottle of shampoo). Manual override available. Images render in the Inventory list and the History tab. (#873, #961)",
    bullets: [
    ],
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-126",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Supplies — units overhaul (pieces / litres / kilograms)",
    body: "Units graduate from ad-hoc strings to a canonical set — pieces / L / kg — AI-assigned per product, with unit-aware merge across brands (e.g. 750 mL Finish + 1 L Somat merge into one product-type row measured in L). Checklist supply-consumption widgets now accept ml / l / g / kg inputs. Re-analysis migration recomputes…",
    bullets: [
    ],
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-125",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "violet",
    title: "V28 Smart Locks — full screen overhaul",
    body: "The Smart Locks screen got a top-to-bottom rework:",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-124",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "V28 FINANCIALS — Discount % line",
    body: "The FINANCIALS panel now shows a Discount % line comparing app-defined base price to the real realized rate — quantifies the discount lift on every booking. (#871)",
    bullets: [
    ],
    docLink: "/docs/reports",
    author: "Product team",
  },
  {
    id: "rel-123",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "amber",
    title: "V28 Tasks — REOPEN affordance on collapsed done rows",
    body: "The REOPEN button (gh#232) required expanding a done task row to become visible — nobody knew it existed. Now surfaced on the collapsed row as a chip. (#906)",
    bullets: [
    ],
    docLink: "/docs/tasks",
    author: "Product team",
  },
  {
    id: "rel-122",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "blue",
    title: "V28 Tasks — edit task instance name / type / overview / property / duration",
    body: "Legacy TaskInstancesTab allowed editing a task instance in place; V28 was read-only. Ported — updateTask mutation already existed. Sibling parity fix to gh#868 (reassign dropdown last release). (#919)",
    bullets: [
    ],
    docLink: "/docs/tasks",
    author: "Product team",
  },
  {
    id: "rel-121",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Data-quality probe — cleaner-assign task DONE with NULL cleaner",
    body: "Three separate recurrences of \"assign-cleaner task marked DONE but no cleaner assigned\" had no monitor. A dedicated probe now fires on any active-stay reservation with assignCleaner.status='done' AND reservations.cleanerId IS NULL and auto-files. (#913)",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-120",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Nuki first-entry monitor silent since 2026-06-27 — no welcome messages sent",
    body: "The Nuki first-entry monitor stopped emitting guestfirstentry events at ~2026-06-27 — meaning no welcome message was sent to any guest for four days (including Michael). Root cause traced to a config drift after the gh#884 lock-abstraction landing; monitor re-wired via the new LockProvider API. Retroactive…",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-119",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Non-idempotent processGuestArrival minted 19 duplicate Nuki/2N codes for one guest",
    body: "One guest ended up with 19 duplicate access codes on the same lock because processGuestArrival had no idempotency key and the arrival cron re-fired the mint path on every retry. Companion symptoms: failing confirmation writes + wrong Offline state. Idempotency key added (reservation × arrival-day × lock). (#892)",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-118",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Prod app hung — HTTP 502 / tRPC 500s — no auto-recovery",
    body: "Prod app hung for ~20 minutes with 502s + tRPC 500s and required a manual restart. Root cause: no Fly health-check probe, so Fly's auto-restart never fired. Health-check wired + a Fly restart policy configured; recovery time in future goes from operator-noticed to ~30 s automatic. (#890)",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-117",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Predictable teammate guestbook token leaks smart-lock code + WiFi",
    body: "The teammate guestbook URL used a predictable token (sequential ID hash) — an outsider could enumerate URLs and read smart-lock codes + WiFi passwords for every cleaner. Fix: cryptographically random tokens + rotation on generation + rate-limit + audit-log every access. (#926)",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-116",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Admin-chat RAG vector search leaks knowledge-base rows across ALL tenants",
    body: "The admin-chat RAG (retrieval-augmented generation) vector search used $queryRawUnsafe which bypassed Prisma's tenant scoping — a tenant A admin's question could pull knowledge-base rows from tenant B. Fix: raw query rewritten with explicit tenant scope + a CI ratchet forbids $queryRawUnsafe on any tenant-scoped…",
    bullets: [
    ],
    docLink: "/docs/admin",
    author: "Product team",
  },
  {
    id: "rel-115",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "adminDebug endpoints — cross-tenant PII dump + hardcoded date tokens",
    body: "/adminDebug/ endpoints dumped cross-tenant PII on any authenticated GET (hardcoded date tokens in the query). Scoped down + auth-gated + audit-logged. (#927)",
    bullets: [
    ],
    docLink: "/docs/bug-report",
    author: "Product team",
  },
  {
    id: "rel-114",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Hospitable webhook logs unredacted guest PII",
    body: "The Hospitable webhook-log path stored raw payloads with unredacted guest PII (the redactor existed and the WA path used it; Hospitable was left out). Fix: shared redactor now applies to all webhook ingestion paths. (#928)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-113",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Validator-blocked tasks re-evaluated every 7 minutes — block-storm",
    body: "Extension of gh#854 (last release) — the auto-resolve cron's re-validation was still firing on validator-blocked tasks every ~7 minutes, burning LLM-validator tokens + risking a leak. Sticky-blocked state persisted; re-validation now requires an actual draft change. (#891)",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-112",
    version: "v1.45",
    date: "June 29 – July 4, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Pulse Copilot executetrpc operations.tasks.update bypassed anti-clobber",
    body: "The Pulse Copilot's raw executetrpc path bypassed the gh#855 anti-clobber read-merge — a spurious unrelated write to task #42 clobbered skipIfManuallyDone. Fix: generalised anti-clobber read-merge now covers every clobber-prone .update path (gh#898), not just the messageTemplates.update case. (#894, #898)",
    bullets: [
    ],
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-111",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Configurable outbound-validator blocking threshold",
    body: "Outbound validators now ship with a per-binding blocking threshold (block all findings / major+critical only / critical-only) plus a per-template override. Pair this with the per-validator confidence threshold shipped last release and operators get fine-grained control over what the AI is allowed to interrupt.",
    bullets: [
    ],
    hook: "Stop a noisy validator without disabling it entirely — let it surface advisories on minor, only intercept on critical. (#850)",
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-110",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Refine — the third option on a validator-blocked message",
    body: "Every validator-blocked send now offers three actions instead of two:",
    bullets: [
    ],
    hook: "Most validator blocks are pedantic — Refine lets the AI fix its own draft instead of forcing you to. (#879)",
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-109",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Feature",
    tone: "violet",
    title: "SEND-ANYWAY override + manual-send blocks — now properly audited",
    body: "A comment claimed these were audited; they weren't. Now they are — every operator SEND-ANYWAY override and every manual-send block produces a validatordecisions row with operator, finding, decision, and timestamp. Visible in the new Admin → Logs → Validator Decisions tab (with object filter); the bespoke inbox…",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-108",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "V28 Early check-in & Late checkout cards — editable approved time",
    body: "The read-only \"requested time\" field is now editable + savable on the Early Check-in and Late Checkout cards. Operator can approve a guest's request at a different time than they asked for (e.g. guest wanted 11:00, operator approves 13:00) without bouncing to a different screen.",
    bullets: [
    ],
    hook: "One-click time negotiation, captured in the right place. (#863)",
    docLink: "/docs/reservations",
    author: "Product team",
  },
  {
    id: "rel-107",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Feature",
    tone: "amber",
    title: "V28 Reservation FINANCIALS — gross vs net + channel commission",
    body: "The FINANCIALS panel headline rows now explicitly label gross vs net, and a new channel commission row surfaces so the math reconciles at a glance.",
    bullets: [
    ],
    hook: "\"Why doesn't 245 - 49 = the host-payout number I expected?\" is now answered on the same panel. (#872)",
    docLink: "/docs/reservations",
    author: "Product team",
  },
  {
    id: "rel-106",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Supplies — 4-level model (parsed / corrected / product type / category)",
    body: "The supplies model grew a third intermediate layer:",
    bullets: [
    ],
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-105",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Feature",
    tone: "violet",
    title: "V28 Tasks card — reassign dropdown returned",
    body: "The classic UI had an editable \"Assigned to\" dropdown on every task card; V28 was rendering the assignee as read-only text (the updateAssignee mutation already existed). Dropdown back. (#868)",
    bullets: [
    ],
    docLink: "/docs/tasks",
    author: "Product team",
  },
  {
    id: "rel-104",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "PWA blank white screen after deploy",
    body: "An installed iOS/desktop PWA opened to a blank white screen for ~5–15 minutes after every deploy. Root cause: the SPA shell requested old asset hashes that no longer existed; the server's catch-all returned index.html (200) for missing /assets/ instead of 404, so the browser tried to execute HTML as JS → crash →…",
    bullets: [
    ],
    docLink: "/docs/mobile",
    author: "Product team",
  },
  {
    id: "rel-103",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Outbound-message task settled DONE despite send validator-BLOCKED — 7 prod victims",
    body: "The gh#724 delivery guard introduced last release covered access-code tasks only — Welcome / Pre-checkout / Check-in-reminder tasks still settled as done (+ \"Task completed\" WA notify) when the send was validator-blocked. Seven prod guests had a task marked complete with no message delivered. The guard now blankets…",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-102",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Outbound validator defeated by retry — blocked sends leaked through",
    body: "The auto-resolve cron re-runs the (non-deterministic LLM) validator every 6 minutes until a blocked send eventually PASSES — Patrik's check-in reminder reached the guest after ~30 minutes of retries despite being initially blocked. Fix: a blocked verdict now persists; re-validation requires an actual draft change…",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-101",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Validator FALSE-POSITIVE on empty thread context",
    body: "A correct draft was being blocked because defaultLoadThread returned translatedBody='' (empty-string sentinel) — the validator saw a blank guest message + a contextual reply and flagged the reply as \"out of context.\" Fix: producer-side cleanup (NULL instead of empty string; 195 prod rows backfilled) + consumer-side…",
    bullets: [
    ],
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-100",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Mobile PWA section crashes — \"This section couldn't load\"",
    body: "The mobile PWA /m/ subtree was hitting a genuine render crash on load. Root cause traced via the apperrors.reacterrorboundary:Mobile row. Fixed + added a stricter mobile ErrorBoundary path that captures the error + offers a recovery action instead of just a blank state. (#847)",
    bullets: [
    ],
    docLink: "/docs/mobile",
    author: "Product team",
  },
  {
    id: "rel-099",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Teammate notifications silently suppressed when WA window closed",
    body: "The gh#484 cost-reduction (gate teammate messages by 24h service window) was silently dropping notifications when the window was closed — operator never knew. Fix: always deliver (template message when the window is closed), warn-log any silent loss path that remains. (#849)",
    bullets: [
    ],
    docLink: "/docs/team-and-access",
    author: "Product team",
  },
  {
    id: "rel-098",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Host-personality \"Propose from history\" compressing + language-switching",
    body: "A prior fix shipped but was ineffective — the LLM kept ignoring \"preserve formatting\" and reading \"host's primary language\" as Slovak. Re-prompted + added a behavioral test that exercises a long English personality and asserts the proposed output is NOT compressed and stays English. (#819)",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-097",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Daily-review walkthrough looped on the same task",
    body: "The WhatsApp daily-review walkthrough's Dismiss / Next advanced currentIndex in-place without persisting (gh#729 regression) — cleaner kept seeing the same task. Fix: persist + add a regression test for the index-progression path. (#876)",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-096",
    version: "v1.44",
    date: "June 24 – 28, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Supplies Pack Sizes Edit/SAVE — client-side no-op",
    body: "Edit → SAVE on a Pack Size returned silently with no DB write. Fix: wire the mutation + add scoped historic re-expansion when units-per-pack changes (property + date-from), so existing inventory rows are recomputed against the new pack size instead of staying frozen. (#853)",
    bullets: [
    ],
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-095",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Unified outbound-message validation — one layer, two modes",
    body: "Outbound message validation previously split across two code paths (autonomous send vs operator-in-loop send), each with subtly different rules — same template would block on one path and ship on the other. Now: one validator layer wires both paths, with two modes:",
    bullets: [
    ],
    hook: "Validator behaviour is now consistent everywhere. The \"validation blocks on screen A but ships from screen B\" class of bug is structurally impossible.",
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-094",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Supplies — consumption-model categories, AI naming, ignore list, % units",
    body: "The Supplies surface graduated from a flat inventory list into a real category-aware system:",
    bullets: [
    ],
    hook: "Supplies became actually-useful for portfolio planning, not just an inventory list.",
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-093",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "violet",
    title: "AI provider toggle (Anthropic ↔ Ollama) + auto-failsafe model",
    body: "A Setup toggle now flips the AI generation path between Anthropic (cloud, default) and Ollama (local). Includes a key-save gate fix that was previously rejecting valid keys.",
    bullets: [
    ],
    hook: "Pick the AI provider that fits your privacy/cost stance; model deprecations no longer take down production.",
    docLink: "/docs/ai-cockpit",
    author: "Product team",
  },
  {
    id: "rel-092",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Checklist items — multiple ON-COMPLETE actions + autocompletion config + reorder",
    body: "The task-template checklist editor became substantially more expressive:",
    bullets: [
    ],
    docLink: "/docs/task-templates",
    author: "Product team",
  },
  {
    id: "rel-091",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Message templates are now per-property, not tenant-wide",
    body: "Tenant-wide message templates moved out of Operations and into per-property scope. Reflects how operators actually edit them — most \"differences\" are property-specific (different check-in instructions per house, not one global template that branches). (#774)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-090",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Super-admin tenant-switcher in V28 top-nav",
    body: "Super-admins can now flip between tenants from the V28 top-nav (sets x-platform-tenant-id header, no re-login required). Includes a super-admin grant mechanism. Foundation for true SaaS multi-tenancy where one super-admin operates several properties' tenants. (#714)",
    bullets: [
    ],
    docLink: "/docs/admin",
    author: "Product team",
  },
  {
    id: "rel-089",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Platform-admin moves to its own subdomains",
    body: "Costs and Infra Status previously sat inside the tenant app. They're operator/super-admin concerns, not tenant concerns — moved to dedicated subdomains (costs.flatsbratislava.com, infra.flatsbratislava.com) and infraStatus re-scoped to a platform procedure. (#685)",
    bullets: [
    ],
    hook: "Tenants stop seeing platform-admin chrome they shouldn't see.",
    docLink: "/docs/admin",
    author: "Product team",
  },
  {
    id: "rel-088",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Mobile dashboard — booking-calendar timeline view",
    body: "Mobile previously had only the occupancy heatmap; desktop had the full BOOKING CALENDAR with check-in/check-out timeline. Mobile now matches — same timeline, mobile-laid-out. (#712)",
    bullets: [
    ],
    docLink: "/docs/reservations",
    author: "Product team",
  },
  {
    id: "rel-087",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Desktop PWA dock badge",
    body: "The desktop PWA dock icon now shows the unread/task badge count (mobile shell got this in gh#561; desktop's useV28Badges was never wiring setAppBadge). (#677)",
    bullets: [
    ],
    docLink: "/docs/mobile",
    author: "Product team",
  },
  {
    id: "rel-086",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "blue",
    title: "V28 navigation — Admin cleanup, Logs consolidation, Smart Locks consolidation",
    body: "A wave of nav reorganisation based on operator usage patterns:",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-085",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "violet",
    title: "AI Cockpit — click-to-filter, per-validator confidence threshold",
    body: "---",
    bullets: [
      "Clicking a feature row in AI Cockpit → Usage & Cost now filters the whole tab (Daily Spend chart + Usage Window cards), not just…",
      "Per-validator confidence threshold — Task Template Safety Validator now exposes a 0–1 confidence per finding plus a configurable…",
    ],
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-084",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Prod-deploy guard — mirror the sandbox lock",
    body: "After observing 14 sandbox deploys in 4 hours (agents bypassing the gh#472 lock, gh#615 RCA), the same guard now wraps prod deploys: pull-main-first + advisory lock + provenance metadata. Stale-tree clobbers on prod become structurally impossible. (#770)",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-083",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Cleaning board — clickable cleaner roster",
    body: "CLEANER ROSTER entries on the Cleaning board are now clickable → filters Upcoming turnovers to that cleaner. Lets the operator trace a per-cleaner count back to its turnover instead of mentally cross-referencing. (#698)",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-082",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Simulate-entry picker — guests + teammates",
    body: "The sandbox simulate-entry picker now lists guests arriving today distinct from teammates — lets the operator simulate a guest first-entry (welcome-message trigger) without choosing a teammate by accident. (#782)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-081",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Systematic sandbox mock-data mechanism + property-keyed Mock Nuki",
    body: "Sandbox mock data graduated from ad-hoc scripts into a real system:",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-080",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "V28 task card now shows the assignee",
    body: "Classic UI showed the assigned teammate on the task card; V28 was rendering only property + due date. Assignee back. (#748)",
    bullets: [
    ],
    docLink: "/docs/tasks",
    author: "Product team",
  },
  {
    id: "rel-079",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "amber",
    title: "WA Communication translation — surface the target language",
    body: "WA Communication translation now displays \"Translated to <lang>\" so the operator sees which language was applied; the operator's communicationLanguage is prompted/defaulted on first use (gh#616 UX follow-up). (#681)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-078",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "blue",
    title: "data-testid attributes — operator-facing interactive elements",
    body: "Every operator-facing button / dropdown / dialog now carries a stable data-testid so the real-UI-walk close-gate (per CLAUDE.md) is mechanically enforceable. Foundation for headless screenshot automation, e2e tests, and validator UI walkthrough — all of which were previously fragile selectors against text content or…",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-077",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Reservation + checklist polish",
    body: "---",
    bullets: [
      "Show the requested early check-in time in the rule-45 overview placeholder (#638)",
      "Clarify flat vs hourly rate basis in checklist DURATION (€5 flat-rate Laundry no longer ambiguously next to a duration field)…",
      "Portfolio Analytics: remove LLM Usage tab (it's admin metric, not property analytics); unify bespoke tabs with the shared…",
      "V28: edit actions open in a modal (like task templates), not a top-of-page panel — sweep applied to message templates + similar…",
      "Unified reservation field catalog — single source of truth (platformCapabilities) for trigger + completion condition pickers…",
    ],
    docLink: "/docs/task-templates",
    author: "Product team",
  },
  {
    id: "rel-076",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Pulse Copilot WIPED Klaudia's Nuki code time-limit — then hallucinated success",
    body: "Operator asked Pulse Copilot to set Klaudia's access code expiry to 13:00. The Copilot called updateAccessCode with null dates (effectively removing the time-limit entirely) and then reported success. The code now had no expiry. Fix: explicit time-limit preservation in the tool schema + behavioral test that exercises…",
    bullets: [
    ],
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-075",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Pulse Copilot + admin-chat 404'd from a retired model",
    body: "Anthropic retired claude-sonnet-4-20250514 on 2026-06-15 — the model id was hardcoded in Pulse Copilot + admin-chat, so both started returning 404s within an hour of the deprecation. Manual swap restored service; the gh#717 failsafe (auto-switch to current model on 404 for ALL AI calls) ensures this can't repeat…",
    bullets: [
    ],
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-074",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Tetiana arrived with NO access code — Send-Checkin auto-send hard-blocked",
    body: "A check-in instruction send to Tetiana (arriving tonight) was hard-blocked by an aivalidators MAJOR finding ([parking-info-irrelevant]), success:false, no override — yet the task was marked DONE despite the send failure. Code 881623 sat in the DB undelivered. Fourth recurrence of the send-checkin-blocked-code-present…",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-073",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Prod white-screen crash risk — ErrorBoundary had no chunk-load recovery",
    body: "A single root ErrorBoundary, no chunk-load recovery, no apperrors reporting on client-side crashes — meaning any chunk-load failure (rare but real on a long-lived tab through a deploy) would produce a blank prod page with no telemetry. Added: SPA crash-resilience mechanism (per-route boundary + chunk-reload retry) +…",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-072",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Validators UI was unusable — HTTP 400 on every binding edit",
    body: "PAUSE/DELETE/EDIT on a validator binding → HTTP 400 Id: expected number, received bigint. Operator couldn't disable a validator via the UI. Root cause: tRPC schema declared z.number() but the column type was bigint. (#741)",
    bullets: [
    ],
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-071",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "App failed to boot — 03-migrations-B retrospective expense loop timeout",
    body: "A retrospective expense backfill loop (1010 round-trips, 0 expenses, 24 s/boot) breached the 60 s Fly proxy boot budget under load → boot timeout → app refused to start. Rewritten as a single set-based UPDATE. (#749)",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-070",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Preprod completely down — migration 128 boot crash-loop",
    body: "Migration gh#740 added an FK → supplies(id) but preprod's supplies.id had no PK → exit 1 → boot loop. Same shape as gh#655. Fix: migration now asserts the PK constraint up-front. (#827)",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-069",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Refine/Improve-with-AI corrupted the draft",
    body: "Operator hits \"Improve with AI\" on a draft reply → the result prepends an exact copy of the prior HOST message + mixed languages → validator re-blocks → operator can't send. Root cause: prompt was concatenating context with the draft and the LLM treated the concatenation as content to emit. (#836)",
    bullets: [
    ],
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-068",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Cross-tenant cleaner-manual exposure (SECURITY)",
    body: "The public /cleaner-manual/:slug route resolved a property by slug with no tenant scope — meaning a tenant A cleaner's slug could resolve to a tenant B property if slugs collided. Fix: tenant-scoped lookup + explicit 404 when scopes mismatch. Audit-walk through every public route that touches tenant-bound data. (#792)",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-067",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Guest preferences silently lost — last-write-wins",
    body: "reservation.specialRequest was overwritten by each new analysed guest message (last-write-wins), not merged. Tereza's \"white wine\" was clobbered by a later \"no parking\" message — operator preparing the welcome had no idea about the wine. Fix: special requests now merge into a structured list, not overwrite. (#833)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-066",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Manual APPROVE & SEND could double-send",
    body: "Race window between the operator hitting APPROVE and the async pre-send probe completing — TOCTOU between client and the backend dedup. A guest got two identical messages. Fix: immediate button disable during probe + backend dedup tightened. (#756)",
    bullets: [
    ],
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-065",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "V28 infinite render→refetch loop",
    body: "useV28Badges embedded new Date() in a React Query key → key changed every render → webhookLogs.getStats refetched ~4–5×/s continuously on every V28 screen. The supply-chart tooltip flicker was a downstream symptom. Fix: stable key + a CI ratchet that forbids non-stable values in React Query keys. (#762)",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-064",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Admin WA notifications >1024 chars silently lost",
    body: "Admin task notifications via WhatsApp interactive-list path were silently rejected (error 131009 from Meta) when the body exceeded 1024 chars — the interactive-list path was missing the 1024 cap that the regular path has. Body now truncated with an explicit \"…see app for full text\" suffix + length-cap test. (#784)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-063",
    version: "v1.43",
    date: "June 14 – 23, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Auto cleaner-assign false-resolved as \"met\" without assigning",
    body: "The \"Assign cleaner\" auto-task was marking itself resolved as \"met\" while never actually assigning a cleaner (gh#466 global-default suppression interaction). ≤14-day cleanings were silently left unassigned. Fix: assignment success is now a precondition for \"met\". (#797)",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-062",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Per-property manuals — AI Q&A grounded in your actual documentation",
    body: "Upload property manuals (PDFs, photos, text — air-conditioning instructions, the dishwasher manual, the WiFi setup, parking diagrams) on each property's page. Documents are embedded and searched per-tenant. When a guest asks \"how do I use the air conditioning?\", the AI answer is now grounded in your manual — not a…",
    bullets: [
    ],
    hook: "No more guests being told the wrong dishwasher mode because the AI hallucinated. The AI answers from the source you uploaded. (#542)",
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-061",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "violet",
    title: "V28 inbox — paste images directly into the composer",
    body: "Hit CMD+V with an image on your clipboard while composing a reply — it auto-attaches via the existing image pipeline. Same ergonomics as the Pulse Copilot got in the May release.",
    bullets: [
    ],
    hook: "Send a guest the photo you just screenshot'd, without the paperclip detour. (#656)",
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-060",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Supplies — per-item history chart + category filter",
    body: "Click any inventory item — get a per-item pieces-remaining line chart showing restocks and use-downs over time. Filter the global supply HISTORY view by category to find the toilet-paper consumption pattern across all properties. Backend already supported it; the V28 UI now exposes it.",
    bullets: [
    ],
    hook: "\"How much detergent do we actually use per month?\" is now a glance, not a CSV export. (#673)",
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-059",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "amber",
    title: "V28 Supplies — AI-categorize per-row button (parity gap closed)",
    body: "The AI-categorize button (which classifies a supply into a sensible category from its name) was present in the classic SuppliesTab but missing from V28 — operators had to bounce to legacy to recategorize. Ported. (#674)",
    bullets: [
    ],
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-058",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Manual \"start cleaning\" trigger + Mock Nuki simulate-entry",
    body: "Two improvements bundled:",
    bullets: [
    ],
    hook: "No more \"the lock unlock event didn't fire, please walk the cleaner through manually.\" Trigger it from the dashboard or via WhatsApp.",
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-057",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "violet",
    title: "V28 task params — occupancy-aware date picker",
    body: "When a task asks \"what day should this fire?\", the V28 param widget now uses the existing occupancy-aware date picker (which shades booked/vacant days) instead of a raw native input. (#636)",
    bullets: [
    ],
    docLink: "/docs/tasks",
    author: "Product team",
  },
  {
    id: "rel-056",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Reservation FINANCIALS — booking discount now shown",
    body: "The Hospitable financials JSON stored discount lines exactly (e.g. \"Promotion Discount -€54.40\") but the V28 RESERVATION panel never rendered them. Now mapped + displayed. (#668)",
    bullets: [
    ],
    docLink: "/docs/reservations",
    author: "Product team",
  },
  {
    id: "rel-055",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Reservation panel — guest breakdown (adults / children / infants / pets)",
    body: "The \"N guests\" number now expands to the breakdown when Hospitable returns it (\"3 guests (2 adults · 1 child)\"). Across the property portfolio, 74 reservations have children — useful context for stocking child welcome packs vs not. (#671)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-054",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "blue",
    title: "V28 WA Communication — thread translation + realistic bubbles",
    body: "The WA Communication view now:",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-053",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Watched-condition auto-dismiss — ANY-false semantics",
    body: "The \"auto-dismiss when conditions stop matching\" sweep previously required ALL watched conditions to flip false. Operator-dictated change: any single condition flipping false is enough. Prod impact verified zero (only one template — \"Thank you\" — used watched conditions). Tooltips + UI copy updated. Sibling fix…",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-052",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Data-quality probes — auto-file tickets when prod invariants break",
    body: "A new probe family runs on a schedule against prod data. When an invariant is violated, it auto-files a triaged GitHub issue. Probes now live: reservations-without-guest-rows (41 violations caught), done-salaried-task-without-fee-expense (8 violations — sibling family to gh#464 cleaner-underpaid)…",
    bullets: [
    ],
    hook: "Bugs that previously only surfaced when a guest complained are now flagged before anyone notices. (#648, #652, #654, #664, #665)",
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-051",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Nuki \"Charge lock\" notification — no more 1-hour delay",
    body: "Battery polls happen hourly, so the operator's \"lock needs charging\" notification arrived up to ~60 minutes late. Fixed by surfacing the in-charge state earlier in the lock-state machine. (#580)",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-050",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "\"Improve with AI\" was DELETING operator-written directions",
    body: "Operators noticed the AI \"Improve\" button was rewriting (not polishing) their carefully-written guest directions — in one case, fabricating the wrong location. Root cause: the messageimprovement prompt's \"validate facts against KB, correct if incorrect\" rule licensed full content rewrites. Improve must polish style…",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-049",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Post-cancellation guest messages were silently invisible",
    body: "A direct-booking lead nearly lost: a guest cancelled, then messaged the inbox asking to rebook — the message was filtered out because the inbox query excluded cancelled/declined reservations. Now: cancelled-reservation messages are visible (with a distinct visual tag), so the operator can intercept rebooking attempts…",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-048",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Auto-resolve dedup was silently DEAD in prod for the entire gh#290 window",
    body: "The Layer 2 duplicate-welcome guard from gh#290 was keying off task.reservationId (a numeric code) but messages store under externalId (a UUID). Zero rows scanned, zero skips — all-time. Every duplicate the system was supposed to prevent slipped through. Fixed + a recurrence-proof test that exercises the key…",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-047",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Cleaner-WA help-menu mixed SK + UK for non-Slovak cleaners",
    body: "The cleaner WA help menu body was hardcoded Slovak (bypassing the cleanerStrings catalog) — Ukrainian-speaking cleaner saw Slovak help text. Fixed + audit-walk through every cleaner-WA surface that bypasses cleanerStrings. (#613)",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-046",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Arrival-map POIs silently lost after adding one",
    body: "Operator adds a POI in V28 Property Manager → save → reopen the map → it's empty. Root cause: navigationMap was double-stringified on save (V28 tab pre-stringified + server stringified again) — data intact in DB but un-parseable downstream. Restored + fixed the save path + added a parsing test. (#653)",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-045",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Preprod 503 outage — boot crash-loop on missing unique index",
    body: "A seedCostProviders ON CONFLICT (name) clause hit costprovider without a unique index (schema drift between Prisma model + actual table) → boot crashed × 10 → max restart count → preprod 503 for ~3 hours. Env repaired manually; migration now asserts the index up-front. (#655)",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-044",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Multi-tenant data loss — messages.externalMessageId was a GLOBAL unique",
    body: "A global-unique constraint on messages.externalMessageId (no tenantId scope) meant tenant B's message with the same provider message-id as tenant A's would be silently dropped. Fix: composite unique (tenantId, provider, externalMessageId) + a provider column to disambiguate the upstream. Epic #614 (multi-tenant data…",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-043",
    version: "v1.42",
    date: "June 10 – 13, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Boot migration runMigrationsKK silently no-op'd on prod + sandbox",
    body: "The INSERT ... ON CONFLICT (slug) clause hit a partial unique index → threw 42P10 → caught by a try/catch that abandoned the idempotent UPDATE+audit path. Every \"seeded-prompt\" migration silently failed. Caught because operator-edited prompts kept \"reverting\" between deploys. Fix: predicate-restated ON CONFLICT for…",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-042",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "blue",
    title: "In-app Report Bug — screenshot + video + voice + auto-context",
    body: "A floating Report Bug button now lives on every authenticated screen. Tap it (or Cmd/Ctrl+Shift+B) and:",
    bullets: [
    ],
    hook: "No more \"describe what you saw in Slack and hope it's enough.\" The bug report IS the repro. (#167, #342, #343, #344)",
    docLink: "/docs/reports",
    author: "Product team",
  },
  {
    id: "rel-041",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Bug reports are triaged by an AI agent — autonomously",
    body: "A Claude Code triage agent reads new bugreports, reproduces them on preprod (with mock services so no real guest is touched), analyses the codebase, and files a structured GitHub issue with root cause + proposed fix + reproduction steps. The operator goes from \"I'll write this up later\" to \"engineering already has a…",
    bullets: [
    ],
    hook: "Bug reports stop being a chore. Your screenshot at 11pm becomes a tracked issue with a proposed fix by morning. (#168)",
    docLink: "/docs/reports",
    author: "Product team",
  },
  {
    id: "rel-040",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Public /changelog page",
    body: "A new public-facing changelog at flatsbratislava.com/changelog renders the latest release notes for logged-out prospects and existing operators alike. Entry cards link straight to the relevant section of this document.",
    bullets: [
    ],
    hook: "A lightweight trust signal you can link to. Prospects evaluating the product see momentum; operators see what shipped without asking. (#305)",
    docLink: "/docs/changelog",
    author: "Product team",
  },
  {
    id: "rel-039",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "amber",
    title: "V28 Inbox — search across guest names + message bodies",
    body: "The V28 Inbox now has a top-of-list search field. Type a guest name, a phone, a phrase from a guest message — results match across reservations.guestName AND messages.body (last 90 days), debounced, with the matching substring bolded in each result tile.",
    bullets: [
    ],
    hook: "Stop scrolling through hundreds of threads to find \"the guest who asked about parking.\" (#319)",
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-038",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "blue",
    title: "V28 Message Templates editor — finally native",
    body: "V28's PM → Templates tab was previously task automations (operators clicking expected to find guest message bodies kept landing on the wrong screen). Now there's a native V28 Message Templates editor — tenant-wide templates AND per-property overrides — with placeholder insert, language tabs, and a Save Anyway override…",
    bullets: [
    ],
    hook: "Edit a check-in message, a thank-you, a how-to-park snippet — without leaving V28. (#313)",
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-037",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Pulse conversation drawer — now shows the full Inbox context",
    body: "The 💬 \"open conversation\" slide-over on Pulse task tiles now renders 8 reservation-detail cards alongside the message thread in a 2-column layout — guest contact, phone, email, # of guests, # of nights, financials, Nuki PIN, AI tags, invoice status — everything you'd see in the Inbox, without leaving Pulse.",
    bullets: [
    ],
    hook: "Triage a task without losing the agenda. Open, read, decide, close. (#268)",
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-036",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "V28 escape-hatch sweep — ~30 legacy redirects replaced",
    body: "V28 had ~30 buttons that quietly bounced operators out to the legacy /operations, /admin, /admin/properties UIs (\"everything in V28 design should call V28 dialog\" — operator). Shipped: V28-native dialogs for Settings → Notifications (#335), Houses portfolio (#336), AI Cockpit prompts + API rotation (#337), Pulse +…",
    bullets: [
    ],
    hook: "The V28 shell stays consistent. No more \"I'm in V28 but suddenly the UI looks different.\" (#314 — umbrella)",
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-035",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "amber",
    title: "AI Cockpit — operator-manageable subagents",
    body: "The AI Cockpit gets a new Agents tab where you tune each specialist subagent without a code deploy. Per-tenant DB-backed config: system prompt, model choice, thinking budget, max tool rounds, plan-then-execute toggle, max response tokens. Audit-logged via the user-owned config registry.",
    bullets: [
    ],
    hook: "Want the invoice agent on a cheaper model? Want the cleaning-walkthrough agent to reason longer? Edit in the UI, save, done. (#415)",
    docLink: "/docs/ai-cockpit",
    author: "Product team",
  },
  {
    id: "rel-034",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "blue",
    title: "AI cost dashboard — per-turn drilldown + invoice agent cost cut",
    body: "Two related shipments:",
    bullets: [
    ],
    hook: "You can see what your AI is spending money on, and the most expensive flow got cheaper.",
    docLink: "/docs/cost-observability",
    author: "Product team",
  },
  {
    id: "rel-033",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Per-property management fee config + AIRBNB-ONLY metric removed",
    body: "Management fees were hardcoded at 25% gross. Now configurable per property as % of gross, % of net, OR static monthly value. Reflected across Revenue Report + Owner Statements + Owner Payouts. The AIRBNB-ONLY REV metric (per operator request) is removed from reports and payout calculations.",
    bullets: [
    ],
    hook: "Your fee structure matches your actual contracts, not a hardcoded default. (#359)",
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-032",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "WhatsApp notification preferences — per-teammate, with template override",
    body: "Notification routing previously branched on tasktemplate.notifyWhatsApp (per template). Now it branches on teammate.notifyWhatsApp (per person) with a per-template override for critical cases (\"always notify, regardless of teammate prefs\"). Admins/managers get a scope toggle: all teammates vs mine only.",
    bullets: [
    ],
    hook: "A teammate on holiday flips one toggle and stops getting pinged. No more chasing 12 template-level checkboxes. (#361)",
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-031",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Google OAuth — replaces Manus IAM",
    body: "Login now uses direct Google OAuth (with a customer-gated allowlist) instead of the previous Manus IAM dependency. Cleaner auth, fewer moving parts, no third-party identity broker between you and your dashboard.",
    bullets: [
    ],
    hook: "Faster login, simpler password recovery (it's just Google), and unblocks the upcoming community portal. (#430)",
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-030",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Mock-mode preprod + 3-env Fly topology",
    body: "A new 3-environment topology on Fly: PROD (real traffic) / PREPROD (mock services + synthetic data, used by Claude Code for validation) / STAGING (mock services + prod-replica data, used by operator for final-test before prod). New mock-injection UI lets you simulate WhatsApp + Hospitable + Nuki events on preprod…",
    bullets: [
    ],
    hook: "Engineering tests changes on a real prod-shaped dataset without risking a single real message. (#353, #374, #375)",
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-029",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Demo dataset + Demo tenant",
    body: "A randomized privacy-safe replica of prod (every guest name swapped, every PII scrubbed) refreshed daily, served on a dedicated demo Fly app with lightweight auth — for showing the product to prospects or training new operators without ever exposing real guest data.",
    bullets: [
    ],
    hook: "Show the product. Train new team members. No NDA conversation needed. (#473, #474)",
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-028",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "V28 Supplies — property filter, source linkback, \"added by\" history",
    body: "Three improvements to V28 Supplies that came in via the new in-app bug-report channel:",
    bullets: [
    ],
    hook: "Stop scrolling through every property's supplies; trace a receipt entry back to its source in one click. (#479 — receipt parser fixes shipped here too)",
    docLink: "/docs/supplies",
    author: "Product team",
  },
  {
    id: "rel-027",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "amber",
    title: "V28 Invoice — guest details + link to existing",
    body: "The V28 \"Generate from reservation\" invoice modal previously dropped the GUEST INVOICE DETAILS fields (name/email/address) — parity gap with legacy, now closed (#407). When an invoice already exists on a reservation, the V28 INVOICE card now shows a link to the existing invoice instead of a GENERATE button that would…",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-026",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Task template editor — readable validator + Save Anyway",
    body: "Two improvements to the template editor that came directly from operator frustration:",
    bullets: [
    ],
    hook: "Edits land instead of bouncing. You can act on warnings instead of guessing what they mean.",
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-025",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Feature",
    tone: "violet",
    title: "V28 vacancy bar tooltips",
    body: "The V28 booking calendar's vacancy bars now show both the guest-visible discount AND the net-margin discount in the tooltip (the old -N% labeled \"platform promo\" conflated three different metrics). Redundant 2N · €55 €55 label simplified — the two prices already convey 2 nights.",
    bullets: [
    ],
    hook: "Pricing decisions made on what the guest sees vs what hits your margin. (#355, #356)",
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-024",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "V28 reply-with-template was DEAD for every property",
    body: "A slug-vs-int propertyId mismatch caused the V28 inbox template picker to show \"NO TEMPLATES · 16 OTHER\" (disabled) on every thread, for every property. Operators couldn't reply with a template anywhere. The condition matcher compared a string slug to an integer FK; the inbox dropdown rendered the empty result. Fixed…",
    bullets: [
    ],
    docLink: "/docs/task-templates",
    author: "Product team",
  },
  {
    id: "rel-023",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Send Check-in Instructions silently lost text body + first 3 images",
    body: "A check-in-instruction send to Filipe (Vydrica, checkin TODAY) silently dropped the text body and the first 3 of 4 attached images — but the task auto-resolved as done. Root cause: a batched-send retry path didn't propagate partial-failure state. Now: explicit fail-fast on partial sends, no silent done, apperrors row…",
    bullets: [
    ],
    docLink: "/docs/auto-refine",
    author: "Product team",
  },
  {
    id: "rel-022",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Duplicate welcome message sent to guest",
    body: "João received two welcome messages on 2026-05-23 — the gh#290 follow-up that was meant to prevent this had never shipped Layer 1, and Layer 2 had two gaps (legacy-action-format coverage + a 0.85 Jaccard similarity threshold too strict for AI-refined manual sends). Both layers now shipped + monitored. (#324)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-021",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "44 \"New Task Created\" WA notifications in 73 seconds",
    body: "A daily cron processing 44 reservations fired one WA notification per task creation — operator phone vibrated 44 times in a minute and a half. Same symptom class as gh#181 (27-notification flood) — now digest-aggregated with a 60-second batch window. (#348)",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-020",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Cleaner-fee expenses NOT created on auto-resolve — cleaners underpaid €410",
    body: "When a cleaning auto-resolved (smart-lock entry triggered completion), the createExpenseFromTask step was missing from the auto-resolve cron path — so the cleaning-fee expense never landed. Miriam was missing 12 cleanings, Marina 2 — about €410 in unpaid fees. Backfilled + the cron now wires through the same…",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-019",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Miriam auto-assigned to cleanings she shouldn't have (3× recurrence)",
    body: "The \"Approve cleaning\" rule (operationtasks.id=36) had a hardcoded defaultId=8 + auto=true that silently overrode the operator's cleaner assignment. Discovered when Miriam kept getting wrong cleanings; the same root cause recurred twice more under different proximate causes. reservations.cleanerId is now a registered…",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-018",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "V28 silently showed MOCK data when live queries failed",
    body: "Nine V28 surfaces (properties, reservations, threads, team, cleanings, maintenance, automations, locks, channels) silently fell through to mock data when the live tRPC call hit a rate-limit or 5xx — operator couldn't tell real from fake. Now: a useV28Query ratchet enforces explicit error state on failure (no silent…",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-017",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "V28 Cleaning Turnovers dropdown didn't persist re-assignments",
    body: "Operator changes cleaner in the V28 cleaning dropdown → mutation fired but no onError handler, no toast, no retry — the change silently failed to persist. Marina invisible for 05-25 Castle&River because the operator's earlier reassignment never made it to the DB. Now: explicit success/error toast + retry + optimistic…",
    bullets: [
    ],
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-016",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Mobile PWA crashed on /m/pulse mount",
    body: "A latent TypeScript as cast lie shipped 26 days ago in #106 blew up on first real-user load of the mobile PWA's Pulse screen — operator saw a red error triangle, every other layer of validation passed because vitest mocks + tsc casts didn't exercise the real runtime shape. Fix: drop the cast, use the inferred type…",
    bullets: [
    ],
    docLink: "/docs/mobile",
    author: "Product team",
  },
  {
    id: "rel-015",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Prod auth crash on first-time login",
    body: "A bundled-ESM incompatibility (Dynamic require of \"crypto\" is not supported) crashed first-time logins on prod after a deploy. Operator was locked out for ~30 minutes until a SUPERADMIN escape hatch landed. The underlying ESM bundling issue is now resolved. (#393, #399)",
    bullets: [
    ],
    docLink: "/docs/platform-admin",
    author: "Product team",
  },
  {
    id: "rel-014",
    version: "v1.41",
    date: "May 21 – June 9, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Access codes shipped without lock-side validation",
    body: "A \"Generate access code\" action returned success even when the Nuki API rejected the create — Piotr got a check-in code that didn't open the door. The lock-ops path now validates on the actual lock (round-trip Nuki API verify) before returning success. (#576)",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-013",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Pulse Copilot now accepts pasted images",
    body: "Drop a screenshot into the conversation the same way you would in any modern chat app. Hit CMD+V with an image on your clipboard — a screenshot of a damaged item, a photo from a guest's WhatsApp, a snippet from another tool — and it attaches instantly. No more paperclip → file picker → folder hunting.",
    bullets: [
    ],
    hook: "The Copilot becomes a first-class visual workspace. Show it what you're seeing instead of describing it. (#187)",
    docLink: "/docs/pulse-copilot",
    author: "Product team",
  },
  {
    id: "rel-012",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Vacancy Radar — now inside the booking calendar",
    body: "The 14-day booking calendar now highlights vacant slots of 2+ nights in real time, with a promotion-status badge so you can see at a glance which gaps already have active discounts and which need attention.",
    bullets: [
    ],
    hook: "Stop scrolling to a separate card to spot revenue leaks. The intelligence lives where you already look — and you can act on it before guests notice the price gap. (#160)",
    docLink: "/docs/reservations",
    author: "Product team",
  },
  {
    id: "rel-011",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "amber",
    title: "\"Open conversation\" right from any Pulse task",
    body: "Every task tile on the Pulse dashboard now has a chat icon. Click it — the guest's conversation slides in from the right edge without leaving your dashboard. Read what was said, then close the panel and move on.",
    bullets: [
    ],
    hook: "Context is one click away, never a full page navigation. The legacy UI made you jump to /messages; V28 keeps you in flow. (#162)",
    docLink: "/docs/tasks",
    author: "Product team",
  },
  {
    id: "rel-010",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "blue",
    title: "V28 Reports — full Revenue + Simulation parity",
    body: "The V28 Analytics surface is now organised as a tabbed workspace: Revenue · Simulation · LLM Usage — mirroring the legacy /metrics page and completing the financial picture with:",
    bullets: [
    ],
    hook: "You no longer need to bounce to the legacy URL for the numbers that drive financial decisions. (#161)",
    docLink: "/docs/reports",
    author: "Product team",
  },
  {
    id: "rel-009",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "violet",
    title: "Team & Account — full access-code and guestbook control",
    body: "The V28 Team & Account screen now matches everything the legacy editor did, and adds inline shortcuts:",
    bullets: [
    ],
    hook: "Onboarding a cleaner — assigning codes, generating their guestbook URL, and handing it over — is now a 60-second flow inside one modal. (#164)",
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-008",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Smart Locks — Charging history is back",
    body: "The Smart Locks screen now has the Charging tab restored. See each battery-charge event per lock with the exact transition (\"1% → 92%\"), absolute timestamp, relative time, and source (raw sensor reading vs. task-spawned recharge).",
    bullets: [
    ],
    hook: "Know which locks were last serviced and when the \"Charge nuki lock\" tasks actually fired. No more guesswork before a guest checks in. (#165)",
    docLink: "/docs/properties",
    author: "Product team",
  },
  {
    id: "rel-007",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "amber",
    title: "Cleaner walkthrough — special requests now translated",
    body: "When a cleaner asks WhatsApp for their plán, the upcoming list now translates every line — including the guest's special requests — into the cleaner's chosen communication language. No more Slovak scaffolding mixed with English requests mixed with Russian guest notes.",
    bullets: [
    ],
    hook: "Cleaners read once and know what to do. No more \"what does this English sentence mean?\" callbacks. (#152)",
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-006",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "blue",
    title: "Cleaner walkthrough — faster and forgiving",
    body: "Two improvements to the WhatsApp cleaning walkthrough cleaners use 20+ times a day:",
    bullets: [
    ],
    hook: "Cleaners are in-and-out faster, with less friction. (#151)",
    docLink: "/docs/cleaner-app",
    author: "Product team",
  },
  {
    id: "rel-005",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "violet",
    title: "V28 Task Template Editor — smarter parameter authoring",
    body: "A pack of editor improvements that compound:",
    bullets: [
    ],
    hook: "Building and editing templates is dramatically faster. Defaults are smart, dropdowns replace memorising IDs, and dead UI is gone.",
    docLink: "/docs/task-templates",
    author: "Product team",
  },
  {
    id: "rel-004",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Feature",
    tone: "emerald",
    title: "Pulse layout — Per-property panel up top",
    body: "Pulse's per-property panel now sits directly below the calendar — where the eye lands first — instead of below the action piles. (#158)",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-003",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Fix",
    tone: "amber",
    title: "FAQ \"Property not found\" — every Add-to-Knowledge-Base click was failing",
    body: "A Zod schema regression from May 13 was breaking the {{property.id}} placeholder in createFaq. Operators couldn't accept any AI-suggested FAQ entry for 4 days. Fixed and back-tested across all knowledge surfaces. (#185)",
    bullets: [
    ],
    docLink: "/docs",
    author: "Product team",
  },
  {
    id: "rel-002",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Silent skip on auto-resolve — guests weren't getting messages",
    body: "A guarded \"skip when runtime param is empty\" path was returning ok: true and marking tasks as done — even when the actual guest-facing send had been skipped. Pre-checkout reminders were silently not arriving. The success notification fired, the work didn't happen. Now the gate distinguishes \"intentionally empty…",
    bullets: [
    ],
    docLink: "/docs/communications",
    author: "Product team",
  },
  {
    id: "rel-001",
    version: "v1.40",
    date: "May 17, 2026",
    tag: "Fix",
    tone: "amber",
    title: "Removing an access code now actually revokes the Nuki PIN",
    body: "Operators expected that removing a teammate's access code deactivated the physical PIN on the lock. It wasn't — only the JSON column shrank, the Nuki authorization stayed live. Departed cleaners could still enter. Now the teammates.update mutation diffs the old vs. new code list and revokes any removed codes via the…",
    bullets: [
    ],
    docLink: "/docs/properties",
    author: "Product team",
  },
];

const FEATURE_REQUESTS = [
  {
    id: "fr-1",
    title: "Automated turnover photos via cleaner phone",
    body: "Force the cleaner to upload 6 photos of the property post-cleaning, AI checks against “golden” photos and flags discrepancies.",
    votes: 248,
    status: "in-progress",
    tag: "Operations",
    author: "Linda K.",
    avatar: "amber",
    comments: 34,
    age: "2 weeks ago",
    pinned: true,
  },
  {
    id: "fr-2",
    title: "Slack workspace integration for portfolio teams",
    body: "Get reservation events, mood flags, and daily summaries into a dedicated Slack channel. Per-property routing.",
    votes: 192,
    status: "planned",
    tag: "Integrations",
    author: "Marek S.",
    avatar: "blue",
    comments: 28,
    age: "1 month ago",
  },
  {
    id: "fr-3",
    title: "Custom AI tone per property",
    body: "I have 1 boutique loft (warm, intimate) and 4 city-center studios (efficient, direct). One global tone doesn’t fit both.",
    votes: 167,
    status: "in-progress",
    tag: "AI",
    author: "Anna F.",
    avatar: "emerald",
    comments: 41,
    age: "3 weeks ago",
  },
  {
    id: "fr-4",
    title: "Direct booking checkout page with luxury.host payments",
    body: "Move guests off OTAs over time. Hosted Stripe checkout with the property guestbook as the upsell page.",
    votes: 134,
    status: "planned",
    tag: "Revenue",
    author: "Petr O.",
    avatar: "violet",
    comments: 22,
    age: "1 month ago",
  },
  {
    id: "fr-5",
    title: "Hospitú + Lodgify channel sync",
    body: "Two regional channels missing. Hospitú is huge for SK/CZ direct bookings, Lodgify covers smaller hosts.",
    votes: 98,
    status: "open",
    tag: "Integrations",
    author: "Juraj M.",
    avatar: "amber",
    comments: 12,
    age: "2 months ago",
  },
  {
    id: "fr-6",
    title: "Damage deposit AI — photo evidence + dispute flow",
    body: "Cleaner reports damage. AI drafts the dispute. I review, approve, and luxury.host files it with Airbnb resolution center.",
    votes: 87,
    status: "open",
    tag: "Operations",
    author: "Eva H.",
    avatar: "blue",
    comments: 18,
    age: "3 weeks ago",
  },
  {
    id: "fr-7",
    title: "Per-guest tax exemption (corporate stays)",
    body: "Corporate guests in some EU jurisdictions are city tax exempt. Need a flag on the reservation and exclusion from monthly filing.",
    votes: 64,
    status: "planned",
    tag: "Finance",
    author: "Tomáš R.",
    avatar: "emerald",
    comments: 9,
    age: "1 month ago",
  },
  {
    id: "fr-8",
    title: "Voice notes for cleaning team",
    body: "Sometimes a 10-second voice memo is faster than typing. Auto-transcribe + translate to cleaner’s preferred language.",
    votes: 52,
    status: "open",
    tag: "Operations",
    author: "Carla V.",
    avatar: "violet",
    comments: 7,
    age: "2 weeks ago",
  },
  {
    id: "fr-9",
    title: "Two-way calendar sync with Google Calendar",
    body: "Block dates from my personal calendar (kids in the apartment for the holidays) and have it push to Airbnb + Booking.",
    votes: 41,
    status: "open",
    tag: "Operations",
    author: "Marie L.",
    avatar: "amber",
    comments: 5,
    age: "1 week ago",
  },
];

const ROADMAP = {
  inProgress: [
    { id: "rm-1", title: "Multi-property revenue forecasting", eta: "Shipping w/ v1.44", tag: "Revenue", votes: 312 },
    { id: "rm-2", title: "Automated turnover photo QC", eta: "Late June 2026", tag: "Operations", votes: 248 },
    { id: "rm-3", title: "Per-property AI tone & personality", eta: "Late June 2026", tag: "AI", votes: 167 },
    { id: "rm-4", title: "Slack workspace integration", eta: "July 2026", tag: "Integrations", votes: 192 },
  ],
  planned: [
    { id: "rm-5", title: "Direct booking checkout (hosted)", eta: "Q3 2026", tag: "Revenue", votes: 134 },
    { id: "rm-6", title: "Corporate stay tax exemption", eta: "Q3 2026", tag: "Finance", votes: 64 },
    { id: "rm-7", title: "Native iOS + Android apps", eta: "Q3 2026", tag: "Platform", votes: 421 },
    { id: "rm-8", title: "Auto guest review responses", eta: "Q4 2026", tag: "AI", votes: 88 },
  ],
  exploring: [
    { id: "rm-9", title: "luxury.host for serviced offices", eta: "Research", tag: "New segment", votes: 23 },
    { id: "rm-10", title: "AI concierge upsell engine", eta: "Research", tag: "Revenue", votes: 71 },
    { id: "rm-11", title: "Owner co-pilot — voice mode", eta: "Research", tag: "AI", votes: 36 },
  ],
  shipped: [
    { id: "rm-12", title: "Dynamic pricing v2 (event-aware)", date: "v1.40 · Apr 1", tag: "Revenue" },
    { id: "rm-13", title: "Nuki + TTLock native integrations", date: "v1.41 · Apr 15", tag: "Integrations" },
    { id: "rm-14", title: "Cleaning team mobile companion", date: "v1.42 · Apr 29", tag: "Operations" },
    { id: "rm-15", title: "Portfolio mood tracking", date: "v1.43 · May 7", tag: "AI" },
  ],
};

const THREADS = [
  {
    id: "th-1",
    title: "How are you handling guests who insist on calling before booking?",
    excerpt: "Some guests want to negotiate over phone before they’ll book direct. Most of mine drop off. Curious if anyone’s found a flow that works.",
    author: "Lukas R.",
    avatar: "amber",
    location: "Vienna",
    properties: 3,
    replies: 24,
    views: 412,
    lastReply: "2h ago",
    tag: "Guests",
    pinned: false,
    hot: true,
  },
  {
    id: "th-2",
    title: "⚡ v1.44 Forecasting — what are you using it for in your business?",
    excerpt: "Started using the new forecast on 8 properties this week. Curious how you’re acting on the numbers — do you change pricing? Marketing spend? Listings?",
    author: "Petra K.",
    avatar: "emerald",
    location: "Prague",
    properties: 5,
    replies: 41,
    views: 892,
    lastReply: "12m ago",
    tag: "Revenue",
    pinned: true,
    hot: true,
    shTeam: true,
  },
  {
    id: "th-3",
    title: "Best practice: handling Booking.com Genius discount and luxury.host pricing",
    excerpt: "When BDC offers 10% Genius and luxury.host’s also adjusting, am I stacking discounts I don’t want to give? How do you guardrail this?",
    author: "Marc D.",
    avatar: "blue",
    location: "Barcelona",
    properties: 4,
    replies: 18,
    views: 304,
    lastReply: "5h ago",
    tag: "Revenue",
  },
  {
    id: "th-4",
    title: "AI replies on a sensitive issue — do you have it draft or stay quiet?",
    excerpt: "Guest had a medical emergency mid-stay. AI drafted a fine response but I want to handle that one personally. How do you configure this kind of triage?",
    author: "Anna F.",
    avatar: "emerald",
    location: "Bratislava",
    properties: 2,
    replies: 32,
    views: 567,
    lastReply: "1d ago",
    tag: "AI",
    hot: true,
  },
  {
    id: "th-5",
    title: "Show-and-tell: my luxury.host × Notion accounting workflow",
    excerpt: "Wrote up how I export luxury.host monthly bundles into Notion + sync to my accountant. ~12 minutes saved a month. Sharing the template.",
    author: "Tomáš H.",
    avatar: "violet",
    location: "Bratislava",
    properties: 4,
    replies: 14,
    views: 218,
    lastReply: "6h ago",
    tag: "Templates",
  },
  {
    id: "th-6",
    title: "Cleaner says new mobile companion is ‘better than WhatsApp’ — wins from week 1",
    excerpt: "Two weeks in. Cleaner adoption: 100%. Damage reports up (in a good way — we’re catching things earlier). Photos coming in within 20 min of checkout.",
    author: "Carla V.",
    avatar: "amber",
    location: "Madrid",
    properties: 6,
    replies: 22,
    views: 488,
    lastReply: "3h ago",
    tag: "Operations",
  },
  {
    id: "th-7",
    title: "Feature request: per-property AI tone (anyone else?)",
    excerpt: "Boutique loft vs. studio rentals shouldn’t have the same voice. Linking the FR thread — give it an upvote if you agree.",
    author: "Anna F.",
    avatar: "emerald",
    location: "Bratislava",
    properties: 2,
    replies: 11,
    views: 156,
    lastReply: "8h ago",
    tag: "AI",
  },
];

const STORIES = [
  {
    id: "st-1",
    title: "From 3 properties to 11 in 18 months without hiring",
    host: "Lukas Rauch",
    location: "Vienna, AT",
    avatar: "amber",
    portfolio: "11 properties",
    lift: "+28% revenue",
    time: "8 min read",
    excerpt: "Lukas runs eleven boutique apartments across Vienna. No assistant, no co-host. Here’s how he uses luxury.host as the system between him and scale.",
  },
  {
    id: "st-2",
    title: "How Petra rebuilt occupancy from 64% to 81% in 90 days",
    host: "Petra Kollárová",
    location: "Prague, CZ",
    avatar: "emerald",
    portfolio: "5 properties",
    lift: "+17pp occupancy",
    time: "6 min read",
    excerpt: "Two underperforming flats were dragging down her portfolio. A focused 90-day pricing experiment turned them into her top earners.",
  },
  {
    id: "st-3",
    title: "Going off the OTA grid — 60% direct bookings, year three",
    host: "Marc Delvecchio",
    location: "Barcelona, ES",
    avatar: "blue",
    portfolio: "4 properties",
    lift: "60% direct",
    time: "9 min read",
    excerpt: "Marc walked us through the slow shift away from OTAs. Repeat guests, branded guestbooks, and a payment flow that just works.",
  },
];

const EVENTS = [
  {
    id: "ev-1",
    title: "Open office hours with the AI team",
    when: "May 23, 2026 · 5:00 PM CET",
    format: "Live · Zoom",
    host: "Ravi M. + Lukas D.",
    seats: "120 / 200",
    topic: "AI tone, mood, and how to override gracefully.",
    going: true,
  },
  {
    id: "ev-2",
    title: "Pricing playbook — summer in EU rentals",
    when: "May 29, 2026 · 4:00 PM CET",
    format: "Webinar",
    host: "Mira K. + guest: Petra K.",
    seats: "284 / 500",
    topic: "Event-aware pricing, festival windows, and shoulder season strategies.",
  },
  {
    id: "ev-3",
    title: "Bratislava host meetup — in person",
    when: "Jun 6, 2026 · 6:30 PM",
    format: "In person · ŽLTý KÕň",
    host: "luxury.host HQ",
    seats: "32 / 40",
    topic: "Drinks, demos, and a private tour of Tatra Loft.",
  },
  {
    id: "ev-4",
    title: "Office hours — accounting & VAT",
    when: "Jun 12, 2026 · 3:00 PM CET",
    format: "Live · Zoom",
    host: "Lukas D. + accountant Q&A",
    seats: "47 / 150",
    topic: "Monthly close, city tax, and exporting for your accountant.",
  },
];

const TEMPLATES = [
  {
    id: "tp-1",
    title: "Late check-out request — polite, conditional",
    by: "Lukas R.",
    avatar: "amber",
    type: "Message preset",
    installs: 412,
    rating: 4.9,
    excerpt: "A graceful late-checkout response that offers 1–2 PM free, charges thereafter, and protects same-day cleaning.",
  },
  {
    id: "tp-2",
    title: "Monthly close — luxury.host → Pohoda export",
    by: "Tomáš H.",
    avatar: "violet",
    type: "Workflow",
    installs: 188,
    rating: 4.8,
    excerpt: "Step-by-step export that drops cleanly into Pohoda. Includes city tax line items broken out.",
  },
  {
    id: "tp-3",
    title: "Boutique guestbook theme — Vienna 1900",
    by: "Sasha P.",
    avatar: "emerald",
    type: "Guestbook theme",
    installs: 306,
    rating: 4.7,
    excerpt: "Newsreader serif, sepia photo treatment, and an editorial tip layout. Best for >50 m² properties.",
  },
  {
    id: "tp-4",
    title: "Cleaner damage report — photo + form preset",
    by: "Carla V.",
    avatar: "blue",
    type: "Cleaner flow",
    installs: 251,
    rating: 4.9,
    excerpt: "What we ask cleaners to capture, in what order. Cuts disputes and missed restocks.",
  },
];

const SHOWCASE = [
  { id: "sc-1", title: "Tatra Loft", by: "Tomáš H.", location: "Bratislava", listings: 1, accent: "amber" },
  { id: "sc-2", title: "Vienna 7th", by: "Lukas R.", location: "Vienna", listings: 3, accent: "emerald" },
  { id: "sc-3", title: "Studio Setúbal", by: "Marc D.", location: "Barcelona", listings: 4, accent: "blue" },
  { id: "sc-4", title: "Letná Editions", by: "Petra K.", location: "Prague", listings: 5, accent: "violet" },
];

// Daylight palette — coral primary, lavender / peach / teal secondaries
const STATUS_MAP = {
  "open":         { label: "Open",        color: "#807e87", bg: "rgba(10,10,12,0.06)" },
  "planned":      { label: "Planned",     color: "#8b5fdb", bg: "rgba(180,141,255,0.14)" },
  "in-progress":  { label: "In progress", color: "#c43575", bg: "rgba(232,82,140,0.12)" },
  "shipped":      { label: "Shipped",     color: "#2ca6b8", bg: "rgba(77,208,225,0.14)" },
};

// Tone keys stay the same so JSX is unchanged; values shift to brand palette
const TONE_COLOR = {
  amber:   "#e8528c", // coral
  blue:    "#4dd0e1", // teal
  emerald: "#5c6d4f", // sage
  violet:  "#b48dff", // lavender
  ink:     "#807e87", // mid-ink (for minor/quiet entries)
};

const AVATAR_GRADIENT = {
  amber:   "linear-gradient(135deg, #ffb3d6, #e8528c)",
  blue:    "linear-gradient(135deg, #b8e8e8, #4dd0e1)",
  emerald: "linear-gradient(135deg, #c8d3b8, #5c6d4f)",
  violet:  "linear-gradient(135deg, #d4b8ff, #b48dff)",
};

Object.assign(window, {
  CHANGELOG, FEATURE_REQUESTS, ROADMAP, THREADS, STORIES, EVENTS, TEMPLATES, SHOWCASE,
  STATUS_MAP, TONE_COLOR, AVATAR_GRADIENT,
});
