Skip to content

feat(panel): inline audio playback for Calls tab recordings #44

Description

@Nik1125

Concept

Add native HTML5 <audio controls> to each row in the Person panel Calls tab so managers can listen to a Zadarma recording without leaving the contact page — analogous to embedded audio in Notion or in Twenty's own rich-text / Notes blocks.

Click a row → the row expands and reveals a compact player plus an explicit [Open record →] button for navigation to the canonical /object/callLog/:id detail page.

Why

  • Every modern CRM lets you preview the call inline. Today the user has to click the row, wait for the detail page to load, and only then see the recording link — three extra interactions for a 10-second listen.
  • Transcript is great for skimming but tone / pace / interruption patterns only come through audio. Sales managers explicitly asked for "play in panel".
  • Twenty SDK 2.2+ already allows <audio> in frontComponent (PR Allow video, audio and iFrame in front components twentyhq/twenty#18200, shipped in v2.2.0). No SDK upgrade needed; current engines.twenty >=2.2.0 floor covers it.
  • callLog.recording (LINKS, primaryLinkUrl) is already populated by the enrichment job on the current feat/sync-recording-transcript-enrichment branch — the data is there, only the UI is missing.

Scope

UI (src/front-components/zadarma-person-panel.front-component.tsx):

  • Add expandedCallId: string | null state to renderCalls.
  • Replace row onClick={navigate(...)} with onClick={() => toggleExpand(c.id)}.
  • When expandedCallId === c.id, render an inline panel containing:
    • <audio controls preload="none" src={c.recording?.primaryLinkUrl}> if URL present.
    • An [Open record →] button preserving the previous navigation behaviour.
    • For recording == null: an inline "No recording" tag, no expand affordance.
  • On <audio> error event → swap player for a [Refresh recording] button that POSTs the new logic-function below; on success swap src; on second failure render fallback text "Recording no longer available — transcript on detail page".

Backend (new logic-function):

  • src/logic-functions/refresh-zadarma-recording.logic-function.tsPOST /s/zadarma/refresh-recording, isAuthRequired: true, body { callLogId }.
  • Reuse src/modules/zadarma/utils/fetch-call-recording.ts (new on the current branch) to resolve a fresh signed URL via Zadarma /v1/pbx/record/request.
  • Persist updated recording.primaryLinkUrl via updateOneCallLog so the next render is fresh.
  • Return { url } (or { error: 'NOT_AVAILABLE' } when Zadarma 404 → recording purged by retention).

Tests:

  • Unit: refresh-zadarma-recording.test.ts — mocks fetchCallRecording, asserts persistence call shape and JSON response.
  • Manual smoke: probe one production recording URL with curl -I to confirm Accept-Ranges: bytes (seek-bar functional). If absent — ship anyway, document limitation in README.md § Calls panel.

Out of scope

  • Transcript inline preview in the expand panel — transcript is fully visible on the detail page and rendering it twice creates UI churn.
  • Bulk re-fetch cron for stale URLs — analysis showed background refresh wastes Zadarma quota on calls nobody plays. Reactive lazy refresh on user click is sufficient because transcript is the durable artefact; audio is a "nice to have replay". Revisit only if ops feedback shows pattern of stale-on-first-click.
  • Custom waveform / scrubber UI — native browser controls are good enough and zero-maintenance.

Risks / open questions

  • CORS / Range — native <audio> cross-origin playback works without CORS headers, but the seek bar requires Accept-Ranges: bytes from Zadarma's CDN. If Zadarma omits it, playback is linear-only. Probe before merge; document if degraded.
  • Retention — Zadarma free plan stores ~200 MB shared (FIFO purge as new recordings come in); paid plans store more for longer. The lazy refresh handles "URL expired" cleanly but cannot resurrect a purged recording. Final fallback text covers that case.
  • Click semantics change — current row click navigates; this PR makes click toggle expand. Mitigation: explicit [Open record →] button preserves the navigation path. Heads-up worth a line in CHANGELOG.

Effort

  • Frontend: ~150 LOC (state + render + handlers)
  • Backend: ~50 LOC (logic-function + persist call) + 1 unit test
  • Roughly half a day end-to-end including manual probe.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions