#533: Remove redundant Call History top header (duplicate TopBar)
#531: Block logout during active call (confirm dialog + UCID check)
#529: Block outbound calls when agent is on Break/Training
#527: Remove updatePatient during appointment creation (was mutating
shared Patient entity, affecting all past appointments)
#547: SLA rules seeded via API (config issue, not code)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
disconnectSip() now guards against disconnect when outboundPending
or outboundActive is true. Accepts force=true for intentional
disconnects (logout, page unload, component unmount). Prevents
React re-render cycles from killing the SIP WebSocket mid-dial,
which was causing the call to drop and disposition modal to not appear.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fixed useEffect dependency bug: callState in deps caused cleanup
(disconnectSip) to fire on every state transition, killing SIP
mid-dial. Now uses useRef for callState in beforeunload handler
with empty deps array — cleanup only fires on unmount.
- sendBeacon auto-dispose now includes agentId from agent config
- Disposition modal submit now includes agentId from agent config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Defect 1: Setup wizard (/setup) now guarded by AdminSetupGuard —
CC agents and other non-admin roles are redirected to / instead of
seeing the setup wizard they can't complete.
Defect 3: Removed all references to Doctor.clinic (relation was
replaced by DoctorVisitSlot entity). Updated queries.ts,
appointments.tsx, transforms.ts, doctors.tsx, appointment-form.tsx.
Defect 6 (frontend side): Dial request now sends agentId and
campaignName from localStorage agent config so the sidecar dials
with the correct per-agent credentials, not global defaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: when an agent refreshes the page during or after a call,
the React state (UCID, callState, disposition modal) is wiped. The
SIP BYE event fires but no component exists to trigger the disposition
modal → no POST to /api/ozonetel/dispose → agent stuck in ACW.
Layer 1 (beforeunload warning):
Shows browser's native "Leave page?" dialog during active calls.
Agent can cancel and stay.
Layer 2 (sendBeacon auto-dispose):
UCID persisted to localStorage when call activates. On page unload,
navigator.sendBeacon fires /api/ozonetel/dispose with
CALLBACK_REQUESTED. Guaranteed delivery even during page death.
Cleared from localStorage when disposition modal submits normally.
Layer 3 lives in helix-engage-server (separate commit).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SIP connection only for users with Agent entity (no env var fallback)
- Supervisor no longer intercepts CC agent calls
- Auth controller checks Agent entity for ALL roles, not just cc-agent
- Token refresh handles GraphQL UNAUTHENTICATED errors (200 with error body)
- Token refresh handles sidecar 400s from expired upstream tokens
- Network quality indicator in sidebar (offline/unstable/good)
- Ozonetel IDLE event mapped to ready state (fixes stuck calling after canceled call)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Single dialOutbound() in sip-provider handles all outbound state:
callState, callerNumber, outboundPending, API call, error recovery
- ClickToCallButton, PhoneActionCell, Dialler all use dialOutbound()
- Removed direct Jotai atom manipulation from calling components
- Removed setOutboundPending imports from components
- SIP disconnects on provider unmount + auth logout
- Dialler input is now editable (type or numpad)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SIP provider reads credentials from agentConfig (login response)
- Auth logout calls sidecar to unlock Redis + Ozonetel logout
- AppShell heartbeat every 5 min for CC agents
- Login stores agentConfig in localStorage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ActiveCallCard now handles the full post-call flow:
- Call ends → Disposition form appears (6 options + notes)
- "Appointment Booked" → Opens appointment booking slideout
- "Follow-up Needed" → Auto-creates follow-up in platform
- Other dispositions → Logs call and returns to worklist
- "Book Appt" button available during active call too
- Creates Call record in platform on disposition submit
- Removed auto-reset to idle (ActiveCallCard manages lifecycle)
- "Back to Worklist" resets SIP state via Jotai atoms
Also fixes:
- All 7 GraphQL queries corrected (LINKS subfields, field renames)
- Campaign edit button moved to bottom-right
- Avg Response Time uses Math.abs for seed data edge case
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Collapsible sidebar with Jotai atom (icon-only mode, persisted to localStorage)
- 2-panel call desk: worklist (60%) + context panel (40%) with AI + Lead 360 tabs
- Inline AI call prep card — known lead summary or unknown caller script
- Active call card with compact Answer/Decline buttons
- Worklist panel with human-readable labels, priority badges, click-to-select
- Context panel auto-switches to Lead 360 when lead selected or call incoming
- Browser ringtone via Web Audio API on incoming calls
- Sonner + Untitled UI IconNotification for toast system
- apiClient pattern: centralized post/get/graphql with auto-toast on errors
- Remove duplicate avatar from top bar, hide floating widget on call desk
- Fix Link routing in collapsed sidebar (was using <a> causing full page reload)
- Fix GraphQL field names: adStatus→status, platformUrl needs subfield selection
- Silent mode for DataProvider queries to prevent toast spam
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace mock DataProvider with real GraphQL queries through sidecar
- Add queries.ts and transforms.ts for platform field name mapping
- Migrate SIP state from React Context to Jotai atoms (React 19 compat)
- Add singleton SIP manager to survive StrictMode remounts
- Remove hardcoded Olivia/Sienna accounts from nav menu
- Add password eye toggle, remember me checkbox, forgot password link
- Fix worklist hook to transform platform field names
- Add seed scripts for clinics, health packages, lab tests
- Update test harness for new doctor→clinic relation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Known issues to fix:
- Must refresh page after each call (SIP session not resetting to idle properly)
- Decline/reject call not working
- Caller ID shows DID instead of original caller (Ozonetel IVR config issue with call:cid)
- Socket.IO reconnect noise in console (sidecar not running)
Introduce a shared SipProvider context so all components use the same SIP
connection. Add a floating CallWidget (idle pill, ringing, active with
disposition, ended states) visible for CC agents on every page. Add a
ClickToCallButton for the worklist. Wire SIP status badge and worklist
into the call-desk page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>