Skip to content

Power Dialer A2: caller ID + gates + Lead Detail click-to-call + TCPA#235

Merged
4 commits merged into
mainfrom
feat/power-dialer-branch-a2
Jul 6, 2026
Merged

Power Dialer A2: caller ID + gates + Lead Detail click-to-call + TCPA#235
4 commits merged into
mainfrom
feat/power-dialer-branch-a2

Conversation

@bmoss2015

Copy link
Copy Markdown
Owner

Summary

Continuation of Branch A. Ships 8 more items on top of A1 (which is still open as PR #233).

Shipped:

  • Item 2: Local presence auto-pick
  • Item 3: Pinned caller ID
  • Item 5: DNC gate (via session-fetch queue filter)
  • Item 6: Litigation gate (same)
  • Item 7: Contact walk (verified already wired via filterContactsForCallWhom)
  • Item 18: Lead Detail primary phone click-to-call (with source={leadId})
  • Item 19: QuickDial topbar line pass-through (verified already wired)
  • Item 20: TCPA soft-enforce on QuickDial + CallButton (was DialerSession only)

Still deferred to A3:

  • Item 4: Drop Voicemail button
  • Item 8: Send Email button post-call
  • Item 12: Auto-DND during session
  • Item 13: Voicemail greeting prerequisite
  • Item 27: Path B inbound webhook cleanup

Test Plan

  • Start Power Dialer session with caller_id_mode='auto'. Verify Telnyx dial uses a phone number matching the destination area code.
  • Start session with a pinned caller ID. Verify all dials use that number.
  • Start session with skip_dnc=true. Verify contacts with is_dnc=true are excluded from the queue.
  • Open a lead. Click the phone-icon button next to primary phone. Verify Telnyx dial fires with leadId in source.
  • Open QuickDial after 9pm local. Verify confirm modal appears with Call Anyway option.

🤖 Generated with Claude Code

bmoss2015 and others added 4 commits July 2, 2026 14:56
This is a bounded, working slice of the Branch A scope. What actually
shipped, item-by-item, plus what was consciously deferred so the ship
is not oversold.

Shipped

- Item 9  wrap-up seconds: DialerSession now reads wrap_up_seconds
          from sessionView (session-fetch already exposed it from
          defaults_snapshot). Clamped 0-900. Falls back to 30 for
          mock-mode.
- Item 12 paused chip: DialerHeader shows an explicit "Paused" chip
          next to the Power Dialer title when paused=true, on top of
          the existing "Session Paused" subtitle. Chip uses portal
          neutral pattern (white bg, hairline, ink text), no
          tinted-green.
- Item 21 calls page: adds a Direction column (Inbound vs Outbound
          derived from outbound_number_id) rendered as a neutral
          chip. Column widths rebalanced. Type column was already
          a chip.
- Item 5  DNC list scaffold: 0182 migration adds an idempotent
          org-scoped dnc_list table with RLS. Existing DNC path
          (contacts.is_dnc + shouldSkipForDnc) is preserved; the new
          table is the manual-import surface a later commit wires
          into the pre-dial gate.
- Roadmap doc: appended a new "Explicitly Skipped (Power Dialer
          Branch A)" section listing preview mode, predictive dial,
          sequences, retry rules, per-lead multi-contact walk beyond
          primary+relatives, skip logic, warm/blind transfer, coach,
          whisper, barge, conference, CNAM UI. Also called out the
          Reporting dashboard as a Parking Lot addition.

Deferred (not shipped in this commit)

- Item 1  Real Telnyx dial() wire-up from DialerSession. The
          harness is still mock-driven. useTelnyxCall().dial exists
          and is called by FloatingCallPanel / QuickDial today;
          binding DialerSession to it needs the CallHero rewrite
          plus a source={leadId} pass-through, which is a bigger
          UI surgery than fit here.
- Item 2  sessionCallerIdMode="auto" already has a working server
          action (pickOutboundNumber) that does area-code then
          state then fallback. Not re-wired into DialerSession
          because DialerSession does not dial yet (see item 1).
- Item 3  Pinned caller ID pass-through: same dependency as item 1.
- Item 4  Drop VM button on FloatingCallPanel during ringing:
          requires Telnyx Call Control call_control_id plumbing
          plus a voicemail_greetings audio_url fetch. Cut for
          realism, not attempted.
- Item 6  leads.is_litigated: intentionally not added. Existing
          contacts.is_litigator is what the Skip Litigated toggle
          reads, per src/lib/dialer/dnc-scrub.ts. Adding a
          duplicate lead-level flag would fork the source of truth.
- Item 7  Contact walk modes: call-whom-filter.ts already exists
          and session-fetch honors call_whom. DialerSession still
          walks all contacts in queue order; the filter is applied
          during queue build already.
- Item 8  Post-call Send Email button: needs mail-module hookup
          plus a merge-fields pass. Not shipped.
- Item 10 Auto-save on wrap-up expire: current advance() already
          fires at t=0 and applies the selected outcome; a
          persistent write via logQuickCallOutcome is not yet
          wired because DialerSession is mock-driven.
- Item 11 Skip Wrap-Up button: not added in this pass.
- Item 12 Pause behavior fine-tune: chip added; the sequence-halt
          semantics were already correct (paused blocks the
          countdown effect via the guard clause in the useEffect).
- Item 13 Auto-DND during session: profiles.dnd_until already
          exists (migration 0176). No new server action wired.
- Item 14 VM greeting prerequisite modal: not shipped.
- Item 18 Lead Detail click-to-call: not touched.
- Item 19 QuickDial topbar line pass-through: not verified this pass.
- Item 20 TCPA soft-enforce in DialerSession + QuickDial + Lead
          Detail: checkTcpaWindow exists at
          _actions-quickcall-log.ts and VoicemailInbox already
          uses it; not extended to the other surfaces this pass.
- Item 27 Inbound Path B refactor: not touched.

Migration

- 0182_power_dialer_branch_a.sql — idempotent, adds dnc_list only.
  No is_litigated (intentionally not added).
  Apply with: npm run db:push:staging

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e + TCPA gate

Wires the harness to actually place calls. When a real session is active (sessionId present), DialerSession now:

- Fires useTelnyxCall().dial() on live state entry with the active contact's phone + source {leadId}
- Gates every dial through checkTcpaWindow, showing a Call Anyway modal when outside 8am-9pm local
- Watches Telnyx status for hangup and auto-transitions to wrap-up
- Persists every call to session_calls via logQuickCallOutcome on wrap-up expire, disposition pick, and Next Lead — mapping CallOutcome to QuickCallOutcome (Called → spoke, No Answer → no_answer, etc.)
- Captures duration from dial start to save time
- Surfaces dial errors inline with dismiss button
- Respects paused: no new dials fire while paused, wrap-up timer holds

TcpaBlockModal added inline; equal-width Cancel + Call Anyway buttons per portal design system.

Items 1 (real dial), 9 (wrap-up auto-save), 16 (TCPA soft-enforce) now shipped end-to-end.
Item 10 (End Wrap-Up Early) is behaviorally covered by the existing Next Lead button in wrap-up state.
Item 11 (Pause) tightened via paused check in dial effect.

Deferred to Branch A2: real Telnyx dial with local presence / pinned caller ID (items 2, 3), Drop VM button (4), DNC + litigation gates (5, 6), contact walk (7), Send Email post-call (8), auto-DND during session (12), VM greeting prerequisite (13), Lead Detail click-to-call (18), QuickDial line pass-through (19), Path B inbound cleanup (27).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Detail click-to-call + TCPA everywhere

- Item 2: Local presence auto-pick — DialerSession calls pickOutboundNumber({state, recipientE164}) when caller_id_mode is "auto" and passes the result as fromE164 to Telnyx dial
- Item 3: Pinned caller ID — when caller_id_mode is a specific E164, dial uses that directly
- Item 5: DNC gate — session-fetch filters contacts where is_dnc=true when skip_dnc=true is set on the session (default true). Removes DNC contacts from the queue at fetch time, cleaner than dial-time skipping
- Item 6: Litigation gate — same pattern for is_litigator when skip_litigated=true
- Item 7: Contact walk — verified filterContactsForCallWhom already respects primary vs relatives vs both per sessionCallWhom
- Item 18: Lead Detail click-to-call — CallButton added to OverviewTab primary phone row; passes leadId as source context so session_calls links correctly
- Item 19: QuickDial line pass-through — verified already wired: selectedNum.e164 flows through to dial's fromE164 param
- Item 20: TCPA soft-enforce — checkTcpaWindow gate added to QuickDial submit + CallButton handleClick with confirm() modal offering Call Anyway

SessionView now exposes skip_dnc, skip_litigated, caller_id_mode, voicemail_drop_id, email_template_id so downstream consumers can read the session config.

Callerid-used ref captures the actual fromE164 at dial time so persistCall writes the correct caller_id_used to session_calls even when local presence overrides the org default.

Deferred to A3: Drop VM button (4), Send Email post-call button (8), auto-DND during session (12), VM greeting prerequisite check (13), Path B inbound cleanup (27).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@supabase

supabase Bot commented Jul 2, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project rsdmyydyhqgkkvwlklif due to reaching the limit of concurrent preview branches.
Go to Project Integrations Settings ↗︎ if you wish to update this limit.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@bmoss2015 bmoss2015 closed this pull request by merging all changes into main in 6c538b7 Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant