wacrm/CHANGELOG.md
Arnas Donauskas ec91cc3382
docs: changelog for 0.2.1 (whatsapp_config single-tenant fix)
PATCH bump for the bug fix in #143 (closes #136). Surfaces the
required migration (013_whatsapp_config_phone_number_id_unique.sql)
and the diagnostic SQL self-hosters should run before applying it,
plus a note that wacrm is single-tenant per WhatsApp number by
design.
2026-05-26 11:26:17 +02:00

10 KiB

Changelog

User-visible changes in wacrm. Self-hosters: when pulling an update, check this file for any migration required notes and apply the matching SQL files from supabase/migrations/ against your Supabase project before restarting the app.

Versions follow Keep a Changelog. Pre-1.0, MINOR bumps cover new modules; PATCH bumps cover bug fixes and polish.

[0.2.1] — 2026-05-26

Bug-fix release. Plugs a silent inbound-message drop that triggered when two users on the same instance saved the same WhatsApp phone_number_id.

Fixed

  • Inbound WhatsApp messages no longer silently disappear when two users have claimed the same phone_number_id. Previously the webhook used .single() to look up the owning config, which errors PGRST116 for both 0 rows and ≥2 rows — the second user's save put the DB into the ≥2-row state and every inbound message was dropped while the log misleadingly reported "No config found for phone_number_id". Three layers of fix: POST /api/whatsapp/config now returns 409 when another user has already claimed the number, the webhook lookup distinguishes 0 rows from ≥2 rows and logs the conflicting user_ids, and a new DB constraint (UNIQUE(phone_number_id)) prevents the bad state at the storage layer. Reported in #136, fixed in #143.

Migration required

Apply against your Supabase project before deploying this version:

  • supabase/migrations/013_whatsapp_config_phone_number_id_unique.sql — adds UNIQUE(phone_number_id) to whatsapp_config. Fails loudly with a copy-pasteable resolution hint if duplicate rows already exist; auto-deduping would destroy encrypted tokens, so the operator picks which row keeps the number. To check first:

    SELECT phone_number_id, array_agg(user_id) AS owners, count(*) AS n
    FROM whatsapp_config
    GROUP BY phone_number_id
    HAVING count(*) > 1;
    

    If that returns rows, DELETE the duplicate row(s) you want to drop, then re-run the migration.

Note on multi-user setups

wacrm is intentionally single-tenant per WhatsApp number. RLS on conversations/messages is auth.uid() = user_id, so a second user physically cannot read messages routed to a different owner — two users sharing one number was never supported. If you need multiple humans handling the same inbox, run them under one shared account.

[0.2.0] — 2026-05-22

The Flows release. Adds a no-code, branching, button-driven WhatsApp conversation engine that runs alongside Automations. Also ships a 5-theme color picker in Settings and opens Flows to all users.

Added

Flows — branching chatbot conversations

  • Module + schema. New flows, flow_nodes, flow_runs, flow_run_events tables with partial unique indexes that enforce one active run per contact. Widened messages.content_type CHECK to accept 'interactive'; added interactive_reply_id column so the inbox can render button/list taps. (#112)
  • Runner engine. dispatchInboundToFlows parses every inbound webhook, decides whether the message is a reply on an active run or a fresh trigger, advances the state machine, and reports back to the webhook so consumed messages don't also fire automations. Idempotent on Meta's message_id. (#114)
  • No-code builder UI at /flows. Linear-list editor with per-node config forms, live validator, draft/active/archived status, and a 5-route REST API (GET/POST /api/flows, GET/PUT/DELETE /api/flows/[id], POST /api/flows/[id]/activate, GET /api/flows/[id]/runs, GET /api/flows/templates). (#115)
  • Templates + v1.5 node types. Three starter templates (Welcome menu, FAQ bot, Lead capture) cloneable from the New-flow dialog. Three new node types: collect_input (capture customer text into a variable), condition (branch on var / tag / contact field), set_tag (add or remove a tag). {{vars.X}} interpolation in send_message + collect_input prompts. Per-flow run-history viewer at /flows/[id]/runs. (#117)
  • Stale-run sweep cron at GET /api/flows/cron — marks runs past their configured timeout (default 24h) as timed_out so abandoned conversations free up the contact for new triggers. Reuses AUTOMATION_CRON_SECRET. (#114)

Color themes

  • 5 color themes (Violet default, Emerald, Cobalt, Amber, Rose) selectable from a new Appearance tab in Settings. CSS variables scoped under html[data-theme="..."], applied at runtime via dataset.theme, persisted to localStorage. Inline boot script in layout.tsx replays the choice before first paint so there's no flash of the default. (#132)
  • Theme tokenization sweep — every previously hard-coded violet-* Tailwind class replaced with primary tokens across ~49 files. Picking a non-violet theme now themes the whole app, not just the chrome. (#133)

Changed

Flows — soft-GA

  • Flows is now available to every authenticated user. The per-account beta gate is gone; the sidebar entry + page header carry a small "Beta" chip as the only remaining signal. (#134)
  • Editor UX:
    • Internal node_key + per-button/row reply_id identifiers hidden behind a per-node "Show advanced" disclosure. (#118)
    • send_list nodes can have multiple sections. (#119)
    • Collapsed node cards show a 1-line content preview per node type (text excerpt, button titles, condition summary, etc.). (#120)
    • Validation issues are clickable: jump to + flash the offending node. (#121)
    • Unsaved-changes "● Edited" indicator + beforeunload reload guard. (#122)
    • New-flow dialog actually widens to fit the 3 template cards (was capped at 384px by a baked-in sm:max-w-sm from shadcn). (#129, #131)
    • Validation panel pinned to the viewport bottom so activate-readiness follows the user as they scroll through nodes. (#130)

Engine reliability

  • Atomic execution_count increment via SECURITY DEFINER RPC — prevents lost counts when two webhooks start runs concurrently. Mirrors the automations engine pattern. (#124)
  • Preload all flow_nodes once per dispatch — one SELECT per inbound instead of one per advance-loop iteration. A 5-node auto-advance chain now costs 1 round trip, not 5. (#125)
  • Wasted re-read dropped after reprompt reset; loadActiveRun switched to defensive .limit(1) so a migration glitch producing duplicates can't crash dispatch. (#126)

Security

  • PII redacted from reply_received event payload — customer text is no longer persisted to flow_run_events.payload; only the length is. A collect_input prompt asking "what's your card number?" used to leave the PAN sitting in the events table. (#123)
  • Constant-time cron-secret compare on /api/flows/cron (crypto.timingSafeEqual) to close a theoretical timing-side-channel on the x-cron-secret header check. (#127)

Fixed

  • /flows no longer spuriously redirects to /dashboard when navigating in. Root cause: useAuth flipped loading: false before the profile fetch resolved. use-auth now exposes a separate profileLoading boolean. (#128)

Migration required

Apply, in order, against your Supabase project:

  1. supabase/migrations/010_flows.sql — Flows core tables, indexes, RLS policies, and the messages schema widening.
  2. supabase/migrations/011_profile_beta_features.sql — adds the profiles.beta_features column. Surviving for future betas; Flows no longer reads it.
  3. supabase/migrations/012_flows_increment_counter.sql — atomic counter RPC. Without this the engine still runs but flows.execution_count is racy.

Each migration is idempotent — safe to re-run if you're not sure whether you applied a previous one.

Removed

  • src/lib/flows/feature-flag.ts + its tests. Flows is open to all users; the profiles.beta_features column itself survives for future beta gates. (#134)

[0.1.1] — 2026-05-19

Added

  • Chat actions in the inbox: emoji reactions, reply-with-quote, and copy-text on individual messages. Hover on desktop, long-press on touch. Outbound reactions and replies forward to WhatsApp via the Cloud API; inbound reactions and swipe-replies from customers arrive through the webhook and appear in real time.

Migration required

  • Apply supabase/migrations/009_message_actions.sql to your Supabase project. It adds messages.reply_to_message_id and the new message_reactions table (with RLS and realtime). The migration is idempotent — safe to re-run.

Changed

  • The webhook no longer stores inbound customer reactions as fake text messages. They are written to message_reactions instead, so any custom queries that counted reactions as messages will need updating.

[0.1.0]

Initial template release. Core CRM: inbox, contacts, pipelines, broadcasts, automations (with a Wait-step cron drain), WhatsApp Cloud API integration, Supabase auth + RLS.