Commit Graph

623 Commits

Author SHA1 Message Date
Arnas Donauskas
95faf58e75 fix(ai): grant EXECUTE on claim_ai_reply_slot so auto-reply works (#345)
Migration 029 created claim_ai_reply_slot(uuid, integer) as SECURITY
DEFINER but never GRANTed EXECUTE — the only function in the schema
missing its grant (007/012/017/018/019/025/030 all grant theirs).

SECURITY DEFINER sets the privileges a function runs *with*, not who may
*call* it: the caller still needs EXECUTE. On instances where the default
PUBLIC execute privilege on public-schema functions has been revoked
(hardened / self-hosted Supabase), the service_role that PostgREST uses
cannot invoke it. The AI auto-reply path runs entirely under the
service-role client (the inbound webhook has no auth.uid()), so
db.rpc('claim_ai_reply_slot') fails permission-denied, the dispatch bails
before sending, and the bot silently never answers ANY inbound message.
The Playground works because it never claims a slot — matching the report.

- 031_ai_reply_slot_grant.sql: new migration granting EXECUTE to
  service_role (029 is already applied on existing installs, so it needs
  its own migration to reach them). service_role only — the sole caller —
  matching the 007/012 increment-counter precedent.
- 029_ai_reply.sql: add the same grant next to the definition so fresh
  installs are correct.
- auto-reply.ts: log a real claim RPC error instead of returning
  silently, so this class of deploy failure is diagnosable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 10:43:03 +03:00
Arnas Donauskas
274db1c7ce
Merge pull request #334 from ArnasDon/feat/ai-agents-sidebar
feat: AI Agents sidebar section (setup + playground)
2026-07-02 18:22:51 +03:00
Arnas Donauskas
10d31bcfc8 feat: AI Agents sidebar section (setup + playground)
Promotes the AI assistant out of Settings into a first-class "AI Agents"
sidebar area.

- Sidebar: new "AI Agents" item → /agents.
- /agents page with two tabs:
  - Playground — a test chat (POST /api/ai/playground) that runs the
    real auto-reply path (KB retrieval + provider, auto_reply prompt,
    multi-turn, shows handoff). loadAiConfig gains requireActive so you
    can test before enabling.
  - Setup — the existing provider/key + knowledge-base config, relocated.
- Removed the AI section from Settings (settings-sections + page); same
  account config, new home. No migration.

Tests +6 (query, loadAiConfig requireActive). 593 pass; typecheck, lint,
build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 18:20:18 +03:00
Arnas Donauskas
5dbc786fdc
Merge pull request #333 from ArnasDon/feat/ai-knowledge-base
feat: AI knowledge base (RAG grounding) — retarget to main
2026-07-02 17:50:18 +03:00
Arnas Donauskas
020dc2ae11 fix(ai-kb): address code-review findings
- Security: REVOKE ALL FROM PUBLIC + GRANT EXECUTE (authenticated,
  service_role) on the match RPCs so anon can't read a KB by supplying
  an account_id (mirrors migrations 018/025).
- Ingest: store chunks even when embedding fails (insert first, rethrow
  after) so a doc stays lexically searchable; the "lexical still works"
  warning is now truthful.
- Retrieval: short-circuit on an empty KB (cheap COUNT) to avoid a paid
  query embedding + 2 RPCs on every draft/auto-reply.
- Migration: HNSW instead of IVFFlat (robust on small/growing tables);
  language-neutral 'simple' FTS config (was 'english'); semantic RPC
  takes text and casts ::vector(1536) to remove PostgREST bind ambiguity.
- Embeddings key: distinguish corrupt-from-absent (loadEmbeddingsKey
  returns {key,corrupt}); log on decrypt failure; reindex now reports
  failure instead of false success; POST/PATCH warn on keyword-only save.
- embedTexts: fail loud when the provider omits result indices (was
  silently misaligning chunks with vectors).

Tests: +3 (empty-KB guard, embed-failure-still-indexes, missing-index).
587 pass; typecheck/lint/build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:20:38 +03:00
Arnas Donauskas
ce1fa4705d feat: AI knowledge base (RAG grounding)
Grounds the AI assistant (0.5.0) in the account's own content so it can
answer instead of handing off.

- Hybrid retrieval: Postgres full-text search for every account; optional
  semantic search (pgvector + OpenAI text-embedding-3-small) when an
  embeddings key is set. Semantic-primary, topped up with FTS.
- src/lib/ai: chunk, embeddings, knowledge (ingest + retrieve), query;
  buildSystemPrompt gains a knowledge block; config carries an
  (optional) embeddings key.
- Wired into /api/ai/draft and the auto-reply bot (best-effort — never
  blocks a reply).
- KB API (/api/ai/knowledge CRUD + /reindex) and a Settings → AI
  Assistant knowledge-base manager + embeddings-key field.
- Migration 030: pgvector, ai_knowledge_documents, ai_knowledge_chunks
  (fts + embedding), match RPCs, ai_configs.embeddings_api_key.

Migration required: supabase/migrations/030_ai_knowledge.sql.
Stacked on feat/ai-reply-assistant (#329).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:06:09 +03:00
Ivan Mercedes
260a7469a6 fix: sync package-lock.json to resolve npm ci error 2026-07-02 09:25:31 -04:00
Ivan Mercedes
50d71b1ed6 feat: add fallback logic for missing locale dictionaries and update NextIntlClientProvider configuration 2026-07-02 08:42:37 -04:00
Arnas Donauskas
1f24b1ff48
Merge pull request #329 from ArnasDon/feat/ai-reply-assistant
feat: AI reply assistant (bring-your-own-key)
2026-07-02 12:58:39 +03:00
Arnas Donauskas
2748028c1d feat: AI reply assistant (bring-your-own-key)
Adds an AI reply assistant powered by the account's own OpenAI or
Anthropic key (stored AES-256-GCM-encrypted; no per-seat AI fee).

- AI-drafted replies in the inbox composer (✨ button, agent+), via
  read-only POST /api/ai/draft.
- Optional auto-reply bot on inbound messages that no Flow consumed and
  no agent owns, with a per-conversation cap and clean [[HANDOFF]] to a
  human. Stands down when a message-level automation would also reply.
- Settings → AI Assistant (admin+): provider/model, key, business
  context, toggles, and a Test-key button that verifies before saving.
- Provider layer (src/lib/ai) with OpenAI + Anthropic adapters behind
  one interface; migration 029 (ai_configs + per-conversation columns +
  atomic claim_ai_reply_slot RPC).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:56:04 +03:00
Arnas Donauskas
041cdabe03
Merge pull request #327 from eldon922/fix/add-contact-gated-button
fix: gated button add your first contact
2026-07-02 09:08:54 +03:00
Ivan Mercedes
1ce9581117 chore: add NEXT_PUBLIC_APP_LOCALE to env example 2026-07-01 23:23:27 -04:00
Ivan Mercedes
8327c0906d feat(i18n): setup next-intl and migrate login page
This introduces the base i18n setup using next-intl without routing. It adds the default messages/en.json and sets up the app layout and next.config.ts to consume the locale from the environment. The login page has been migrated as a first example.
2026-07-01 17:46:00 -04:00
Chris Eldon
c5f2544ff9 fix: gated button add your first contact
(cherry picked from commit 90fd9bc4cc5b8270c44a8c8d1cbc864f38eb6e97)
2026-07-01 23:33:04 +07:00
Arnas Donauskas
54b8652852
Merge pull request #326 from ArnasDon/feat/public-api-webhooks
feat(api): outbound event webhooks — /api/v1/webhooks (#245)
2026-07-01 14:55:22 +03:00
Arnas Donauskas
c0f390c401 feat(api): outbound event webhooks — /api/v1/webhooks (#245)
Completes the public API roadmap (#245): register HTTPS endpoints to be
POSTed to when things happen in an account, so automations can react to
inbound activity instead of polling.

- Migration 028: account-scoped webhook_endpoints (secret stored
  AES-GCM-encrypted) + an atomic record_webhook_failure() function.
- Scope webhooks:manage; management API GET/POST /api/v1/webhooks and
  GET/PATCH/DELETE /api/v1/webhooks/{id} (secret returned once).
- Events message.received / conversation.created / message.status_updated
  dispatched from the inbound webhook handler's after() block.
- Signed delivery (X-Wacrm-Signature = HMAC-SHA256 over t.body), single
  best-effort attempt, auto-disable after consecutive failures.
- Versioned 0.4.0; docs/public-api.md Webhooks section; CHANGELOG.

Fixes from the adversarial code review of this diff:
- SSRF: block private/loopback/link-local (incl. cloud metadata) targets
  at delivery time + redirect:'manual' so a public URL can't bounce
  internal (src/lib/webhooks/ssrf.ts).
- Status mirror UPDATE no longer uses .select().maybeSingle() (message_id
  is non-unique); account resolved via a bounded limit(1) read.
- Atomic failure counter via SQL RPC (was a racy read-modify-write).
- conversation.created emitted before the reaction short-circuit.
- Per-delivery id added for receiver dedupe; docs state at-least-once /
  unordered delivery.
- Signature verify tolerates hex case / header whitespace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:53:18 +03:00
Arnas Donauskas
9f76468a41
Merge pull request #325 from ArnasDon/feat/public-api-rest
feat(api): public REST API v1 — messages, contacts, conversations, broadcasts (#245)
2026-07-01 14:17:46 +03:00
Arnas Donauskas
8cb5792823 feat(api): public REST API v1 — messages, contacts, conversations, broadcasts (#245)
Complete the public API roadmap on top of the existing key-auth layer
(#290). Re-lands POST /api/v1/messages (lost when the stacked #291 never
reached main) by extracting the dashboard send route's core into
`sendMessageToConversation`, then adds:

- Contacts: GET/POST /api/v1/contacts, GET/PATCH /api/v1/contacts/{id}
- Conversations: GET list + /{id} + /{id}/messages
- Broadcasts: POST /api/v1/broadcasts + GET /{id} (persist rows, fan out
  in after(), poll for status)

All list endpoints share one keyset-pagination contract
({ data, meta: { next_cursor } }). No migration required — scopes and
tables already exist. Versioned as 0.3.0 (folded into [Unreleased]);
docs/public-api.md updated; outbound webhooks left as the sole roadmap
item. API stays v1 (additive).

Includes fixes from an adversarial code review: cursor validation
against filter injection, broadcast counts deferred to the DB aggregate
trigger, terminal broadcast status reflects failures, duplicate-phone
de-dup, up-front send-param validation (no orphan conversations),
PATCH null-clear semantics, error-checked tag sync, bounded tag filter,
unified audit-user attribution, and a maxDuration on the broadcast
fan-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:10:19 +03:00
Arnas Donauskas
41815acc88
Merge pull request #324 from ArnasDon/feat/inbox-tag-company-filter
feat(inbox): filter conversations by contact tags and company
2026-07-01 13:06:14 +03:00
Arnas Donauskas
f8aaac131b feat(inbox): filter conversations by contact tags and company
Adds contact-based filters to the Inbox conversation list (issue #272):

- Multi-select tag filter with OR logic (a conversation matches if its
  contact has any selected tag), consistent with Broadcast audiences.
- Company filter (exact match) over companies present in the inbox.
- Filters stack with the existing status filter and search; active
  selections show as removable chips with a clear-all control.

Tags are hydrated via a shared CONVERSATION_SELECT embed and normalized
onto contact.tags, so the realtime hydrate path keeps new conversations
matching an active filter. Filtering logic lives in a pure, unit-tested
matchesContactFilters helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:03:53 +03:00
Arnas Donauskas
6fb71d21d2
Merge pull request #247 from ArnasDon/dependabot/npm_and_yarn/supabase-76c1b082ed
chore(deps): bump the supabase group across 1 directory with 2 updates
2026-07-01 12:00:13 +03:00
Arnas Donauskas
427b4612da
Merge pull request #310 from bellopushon/feat/automation-deal-selectors
feat(automations): add deal pipeline selectors
2026-07-01 11:46:13 +03:00
Arnas Donauskas
f4beb6f368
Merge pull request #322 from RiadDeveloper/main
Prevent redundant profile fetches on refresh
2026-07-01 11:27:20 +03:00
Arnas Donauskas
6ff44644c6
Merge pull request #279 from AlexTPaul/main
feat: Notifications
2026-07-01 11:24:36 +03:00
Arnas Donauskas
1e05ba028c
Merge pull request #282 from ArnasDon/dependabot/npm_and_yarn/base-ui/react-1.6.0
chore(deps): bump @base-ui/react from 1.5.0 to 1.6.0
2026-07-01 11:24:02 +03:00
Arnas Donauskas
1229de52d2
Merge pull request #315 from ArnasDon/dependabot/npm_and_yarn/dev-dependencies-d62044d6eb
chore(deps-dev): bump prettier from 3.8.4 to 3.9.1 in the dev-dependencies group
2026-07-01 11:21:14 +03:00
Arnas Donauskas
e60b08ac2e
Merge pull request #323 from ArnasDon/fix/whatsapp-config-draft-preserve
fix(settings): preserve WhatsApp credential drafts on tab refocus
2026-07-01 11:18:59 +03:00
Arnas Donauskas
7baeb94003 fix(settings): preserve WhatsApp credential drafts on tab refocus
The WhatsApp settings load effect re-ran fetchConfig() whenever `user`
or `profileLoading` changed. Supabase's onAuthStateChange fires a token
refresh (new `user` object, profileLoading toggles) when the browser
tab regains focus, so returning to the tab re-hydrated the form and
overwrote any unsaved credential input.

Guard the effect with a ref tracking the account id already loaded, so
it only fetches once per account and ignores auth-state churn that
doesn't actually change the account. Explicit reloads after save,
registration verification, and reset still call fetchConfig directly.

Closes #307

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 11:16:54 +03:00
Arnas Donauskas
493cf4195a
Merge pull request #316 from ArnasDon/dependabot/npm_and_yarn/types/node-26.0.1
chore(deps-dev): bump @types/node from 25.9.3 to 26.0.1
2026-07-01 11:14:46 +03:00
Arnas Donauskas
92934d6828
Merge pull request #317 from ArnasDon/dependabot/npm_and_yarn/lucide-react-1.22.0
chore(deps): bump lucide-react from 1.18.0 to 1.22.0
2026-07-01 11:13:00 +03:00
Riad Developer
1fce87c4c6
Prevent redundant profile fetches on refresh
Replace `user` object with `user?.id` in dependency arrays to prevent unnecessary re-renders. Add `lastFetchedUserIdRef` to track the user ID for successfully initiated profile fetches, preventing redundant API calls when the window refocuses or the auth token is refreshed.
2026-06-30 13:01:09 -07:00
AlexTPaul
cd863cf9ee feat(notifications): implement account-based notification retrieval and update logic 2026-06-30 11:16:20 +05:30
Alex Titus Paul
cf842fcee2
Merge branch 'ArnasDon:main' into main 2026-06-30 10:44:07 +05:30
Arnas Donauskas
65b677181e
Merge pull request #320 from ArnasDon/fix/template-picker-account-scope
fix(templates): scope template picker to account, not user
2026-06-29 17:35:51 +03:00
Arnas Donauskas
3110932d78
fix(templates): scope template picker to account, not user
The inbox/contact TemplatePicker fetched approved templates filtered by
`user_id = current user`, so in a shared account a teammate only saw
templates they personally created — others' approved templates were
hidden, leaving them unable to send those templates.

message_templates is account-owned and its RLS SELECT policy already
scopes rows to account members (is_account_member, migration 017), so
the user_id filter was both redundant and over-restrictive. Drop it and
rely on RLS, matching the broadcast template picker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:33:41 +03:00
Arnas Donauskas
26f246b3d0
Merge pull request #319 from ArnasDon/feat/send-template-to-contact
feat(contacts): send an approved template to a single contact (#296)
2026-06-29 17:28:32 +03:00
Arnas Donauskas
19e35accb6
feat(contacts): send an approved template to a single contact (#296)
Adds a "Send template" action to the contact detail view so the
business can initiate (or re-open) a WhatsApp conversation with one
known contact by sending an approved template — without the awkward
single-recipient broadcast workaround.

The /api/whatsapp/send route now accepts `contact_id` as an
alternative to `conversation_id`: it verifies the contact is in the
caller's account, find-or-creates the contact's conversation
server-side, then runs the existing send path (phone-variant retry,
template send-builder, message + conversation persistence, flow
pause). The contact view reuses the existing inbox TemplatePicker for
template selection + variable/header/button input, keeping Meta
compliance unchanged (approved templates only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:11:02 +03:00
Arnas Donauskas
30cc7a6ab2
Merge pull request #318 from ArnasDon/feat/flows-grouped-add-menu
feat(flows): group the add-step menu by node category
2026-06-29 15:41:02 +03:00
Arnas Donauskas
ce0d04ba80
feat(flows): group the add-step menu by node category
The canvas and list add-node menus both listed all 10 node types in
one flat dropdown. As the type list grows — especially for forks
adding their own node types (see #312) — a flat list gets hard to
scan. Bucket the types into Messaging / Logic & data / Flow control,
driven by a new `category` field on NODE_META so a new type can't go
uncategorized, and a `groupNodeTypesByCategory` helper shared by both
menus so they stay in lockstep.

No triggers/events category: in wacrm a flow is triggered by
flow-level config (trigger_type), not a canvas node, so `start` lives
under Flow control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:21:07 +03:00
dependabot[bot]
979372c7cd
chore(deps): bump lucide-react from 1.18.0 to 1.22.0
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 1.18.0 to 1.22.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.22.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 10:35:27 +00:00
dependabot[bot]
71852d38d3
chore(deps-dev): bump @types/node from 25.9.3 to 26.0.1
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.9.3 to 26.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 10:35:15 +00:00
dependabot[bot]
2de40e39a3
chore(deps): bump the supabase group across 1 directory with 2 updates
Bumps the supabase group with 1 update in the / directory: [@supabase/ssr](https://github.com/supabase/ssr).


Updates `@supabase/ssr` from 0.10.3 to 0.12.0
- [Release notes](https://github.com/supabase/ssr/releases)
- [Changelog](https://github.com/supabase/ssr/blob/main/CHANGELOG.md)
- [Commits](https://github.com/supabase/ssr/compare/v0.10.3...v0.12.0)

Updates `@supabase/supabase-js` from 2.107.0 to 2.108.2
- [Release notes](https://github.com/supabase/supabase-js/releases)
- [Changelog](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/CHANGELOG.md)
- [Commits](https://github.com/supabase/supabase-js/commits/v2.108.2/packages/core/supabase-js)

---
updated-dependencies:
- dependency-name: "@supabase/ssr"
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: supabase
- dependency-name: "@supabase/supabase-js"
  dependency-version: 2.108.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: supabase
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 10:35:09 +00:00
dependabot[bot]
0e7c42b7ef
chore(deps-dev): bump prettier in the dev-dependencies group
Bumps the dev-dependencies group with 1 update: [prettier](https://github.com/prettier/prettier).


Updates `prettier` from 3.8.4 to 3.9.1
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.4...3.9.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 10:35:06 +00:00
Arnas Donauskas
39aeac8c0c
Merge pull request #303 from ArnasDon/fix/webhook-after-processing
fix(webhook): process inbound WhatsApp messages in after() so they survive serverless freeze (#301)
2026-06-29 09:55:29 +03:00
bellopushon
377c1b738a feat(automations): add deal pipeline selectors 2026-06-26 15:57:06 -04:00
Arnas Donauskas
11d1c622d0
fix(webhook): run inbound processing in after() so it survives serverless freeze
The POST handler ack'd Meta with 200 and then ran processWebhook() as a
detached promise. On Vercel the function can be frozen/terminated the moment
the response is sent, so those DB writes were not guaranteed to complete.

This dropped a non-deterministic subset of inbound messages: the contact and
conversation rows (written first) landed, but the message insert (written
after) often did not — leaving conversations that appear in the inbox with an
empty thread, with no durable logs to explain it (issue #301).

Move the work into next/server `after()`, which keeps the function alive until
the callback resolves (within the route's maxDuration) while still ack'ing Meta
fast enough to avoid retry-driven duplicate inserts. Add maxDuration=60 for
headroom since processing can fan out to per-media Meta verification calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:34:44 +03:00
Arnas Donauskas
b2ed58a41f
Merge pull request #302 from ArnasDon/feat/flows-editor-redesign
Redesign flow editor UI to match Flow Builder design
2026-06-26 16:57:32 +03:00
Arnas Donauskas
1f4d1a3a11
Redesign flow editor UI to match Flow Builder design
Reworks the flow editor (/flows/[id]) to the Flow Builder design
handoff, adapted to wacrm's existing tokens, components, and
light+dark theming. Keeps all behaviour — React Flow canvas,
drag-to-connect, typed config forms, validation, save/activate/delete,
per-browser view persistence, mobile-forces-list.

- shared.tsx: per-node-type color system (nodeColors) + one-line
  blurbs, and a shared NodeIconChip component. Label color blends
  toward --foreground via color-mix so it stays readable in both modes.
- header.tsx: compact single-row toolbar (back / icon / inline name /
  status chip / edited dot · Runs / Delete / Activate / Save) plus a
  subtle inline description note.
- flow-editor-shell.tsx: app-like full-height layout (toolbar → mode
  row → stage → validation bar) with a segmented Canvas/List control
  and a complete node-type legend derived from NODE_META.
- flow-canvas.tsx: per-type colored node cards, branch ports, dot-grid
  background, rounded controls/minimap, top-left primary Add-node menu,
  colored inspector header.
- flow-builder.tsx: list rows tied to the same per-type color language.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:53:03 +03:00
Arnas Donauskas
b72e7a6e62
Merge pull request #299 from ArnasDon/fix/broadcast-media-header-url
fix(broadcasts): collect header media URL for image/video templates
2026-06-25 17:25:20 +03:00
Arnas Donauskas
33942dc8ac
fix(broadcasts): collect header media URL for image/video templates (#298)
Templates with an IMAGE/VIDEO/DOCUMENT header could not be broadcast:
the wizard never offered a place to provide the header media URL, so
the send either shipped the template's stale stored URL or — when none
was stored — threw "image header requires a media link or id at send
time", failing the whole broadcast. The send path already supported
`messageParams.headerMediaUrl`; only the UI/hook never collected it.

- step3-personalize: show a required "Header media" URL field (with
  http(s) validation and an image preview) when the selected template
  has a media header; seed it from the template's stored sample URL and
  block Next until a valid URL is provided.
- new-broadcast page: thread headerMediaUrl through wizard state.
- use-broadcast-sending: pass it as messageParams.headerMediaUrl to the
  broadcast API for media-header templates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 17:17:18 +03:00