Changelog
0.2.2 — Server-side dial queue, authoritative leasing, sensitive Lead Details
The dial queue is now computed in Postgres with keyset pagination, two operators on the same bucket can no longer double-dial, and sensitive Lead Details fields reveal on click with an audit trail.
What's new
- The dial queue runs in Postgres. The dialer no longer bulk-loads
every row in the source table to apply filters / cap / cooldown /
sort in the browser. The bucket's filter tree compiles to a SQL
predicate and
dialer_next_leadsreturns a keyset-paginated page of candidates. Buckets that were sluggish on big tables — or that just burned tab memory on the dial loop — now scale. - Two operators on the same bucket can no longer double-dial.
Authoritative per-
(bucket, row)leases live indialer_lead_leases. Dialing claims the lease; advancing or dispositioning releases it; a one-minute heartbeat covers the active lead.dialer_next_leadsexcludes teammates' active leases from every page, so a leased row never appears in another operator's queue. The race window that the old presence soft-lock could only narrow to sub-second is now closed at the database. - Sensitive Lead Details fields reveal on click — with an audit
log. Columns marked sensitive in the data-table schema now
render masked in the dial Lead Details panel with an Eye / EyeOff
toggle. Clicking the eye calls the audited
user_table_row_reveal_sensitiveRPC to decrypt that one value; double-clicking after reveal opens the inline editor as normal. Advancing to the next lead re-masks every sensitive field automatically — nothing decrypted leaks across leads. - Date pickers track their field. The shared
DatePickernow measures its popover's real height and anchors flush upward, and repositions on scroll / resize so the calendar follows its trigger. The dialer'sdobfield that used to mis-position is the most visible beneficiary; the same fix applies to everyDatePickerinstance (data-filter menu, bucket builder, etc.).
Under the hood
Server (all SECURITY INVOKER, RLS-enforced, no new advisor
warnings):
- Migration 142 —
app_hidden.dialer_filter_predicate. Injection- safe SQL mirror of the JSmatchesFilter, built withformat(%L). - Migrations 143 / 146 —
dialer_next_leads. Filter / sort / cap / cooldown / archived-exclusion / teammates'-lease-exclusion + keyset pagination;include_row_idscarve-out for session pins; returnsdata_encso the client can mask sensitive fields without a second round-trip. - Migration 144 —
dialer_lead_leases+dialer_claim_lead/dialer_release_lead. Per-(bucket, row)lease with a TTL; conditional upsert ⇒ exactly one winner under concurrency. - Migration 145 —
dialer_queue_stats.matching_count+ cappedcalls_todayfor the header daily-progress bar.
Client:
- New
useDialQueuehook — keyset cursor, fetch-more, optimisticpatchRow(data+data_enc). DialWidgetnow drives offuseDialQueue. Schedule, eligibility, and active-event stay JS post-filters over the page. Presence is retained for the live "Also dialing: ‹name›" chip only — state is held in the lease table, not in presence.DialLeadDetailsPaneladds reveal / re-mask wiring; reveal-then-edit routes through the inline editors that already existed.DatePickeradds theusePopoverPositionhook for height measurement, flush anchoring, and scroll / resize tracking.
What's next
- Server-side header-search RPC + lazy record fetch, retiring the
bulk
ensureRowsLoadedthat header search and the off-queue record viewer still rely on. - JS ↔ SQL filter-parity fixtures, pgTAP for the new RPCs, a two-session contention test, and CI wiring on PRs.
0.3.0 — Native app surfaces for Dial, Pages, Data, Calendar, Inbox
The five main apps move off the React Flow canvas onto full-bleed native shell surfaces with their own opinionated layouts; Home and user-created boards stay canvas-based and keep every dashboard widget.
0.2.1 — Dial header search opens Search mode instead of pinning
Picking a lead from the header search now displays it in an ephemeral Search mode that never enters the bucket queue or bypasses filters.