@adatechnology/conversations-ui 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -0
- package/dist/{ConversationSimulatorPanel-q-wVgBCN.d.ts → ConversationSimulatorPanel-P6eqTSAO.d.ts} +1 -16
- package/dist/{chunk-O6457XHK.js → chunk-WCZNIJ3M.js} +16 -1
- package/dist/{chunk-L3OFAQPS.js → chunk-Y326LLEP.js} +4 -14
- package/dist/index.d.ts +10 -3
- package/dist/index.js +3 -3
- package/dist/participant/index.d.ts +55 -3
- package/dist/participant/index.js +1369 -319
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +2 -2
- package/dist/styles.css +187 -0
- package/package.json +2 -2
- package/src/AudioRecorderButton.tsx +4 -28
- package/src/audioRecorderFormat.ts +27 -0
- package/src/participant/ConversationThread.tsx +7 -0
- package/src/participant/ParticipantAttachmentItem.tsx +7 -10
- package/src/participant/ParticipantAudioAttachment.tsx +34 -0
- package/src/participant/ParticipantAudioPlayer.tsx +68 -0
- package/src/participant/ParticipantAudioPlayerControls.tsx +68 -0
- package/src/participant/ParticipantAudioRecorderView.tsx +55 -0
- package/src/participant/ParticipantAudioTranscription.tsx +90 -0
- package/src/participant/ParticipantComposer.tsx +50 -15
- package/src/participant/ParticipantComposerAudio.tsx +95 -0
- package/src/participant/ParticipantConversations.tsx +5 -0
- package/src/participant/ParticipantRecorderParts.tsx +118 -0
- package/src/participant/ParticipantThread.test.tsx +46 -0
- package/src/participant/ParticipantThread.tsx +17 -0
- package/src/participant/ParticipantThreadScreen.tsx +2 -0
- package/src/participant/index.ts +3 -0
- package/src/participant/participantAudio.types.ts +10 -0
- package/src/participant/participantAudioCapture.test.ts +15 -0
- package/src/participant/participantAudioCapture.ts +85 -0
- package/src/participant/participantAudioCoordinator.ts +13 -0
- package/src/participant/participantAudioFormat.test.ts +93 -0
- package/src/participant/participantAudioFormat.ts +72 -0
- package/src/participant/participantAudioGolden.test.tsx +168 -0
- package/src/participant/participantAudioLabels.test.ts +22 -0
- package/src/participant/participantAudioLabels.ts +76 -0
- package/src/participant/participantAudioPlayerControlsDom.test.tsx +123 -0
- package/src/participant/participantAudioPlayerCss.test.ts +63 -0
- package/src/participant/participantAudioPlayerDom.test.tsx +154 -0
- package/src/participant/participantAudioPlayerDomKit.test-helper.tsx +84 -0
- package/src/participant/participantAudioPlayerState.ts +35 -0
- package/src/participant/participantAudioRecorderDom.test.tsx +127 -0
- package/src/participant/participantAudioRecorderInterruptionDom.test.tsx +135 -0
- package/src/participant/participantAudioRecorderSpecDom.test.tsx +110 -0
- package/src/participant/participantAudioRecorderState.test.ts +144 -0
- package/src/participant/participantAudioRecorderState.ts +93 -0
- package/src/participant/participantAudioRecorderView.test.tsx +98 -0
- package/src/participant/participantAudioSupport.ts +36 -0
- package/src/participant/participantAudioTranscriptionDom.test.tsx +198 -0
- package/src/participant/participantComposerAudioDom.test.tsx +185 -0
- package/src/participant/participantComposerAudioGolden.test.tsx +68 -0
- package/src/participant/participantComposerAudioSsr.test.tsx +74 -0
- package/src/participant/participantFileValidation.ts +1 -1
- package/src/participant/participantGolden.test.tsx +2 -0
- package/src/participant/participantGoldenScenarios.test-helper.tsx +14 -1
- package/src/participant/participantLabels.ts +4 -1
- package/src/participant/participantMediaFakes.test-helper.ts +82 -0
- package/src/participant/participantNoMicrophoneDom.test.tsx +44 -0
- package/src/participant/participantPublicTypes.test.ts +11 -0
- package/src/participant/participantRecorderDomKit.test-helper.tsx +131 -0
- package/src/participant/participantRecorderFakes.test-helper.ts +47 -0
- package/src/participant/participantSpecRecorderFake.test-helper.ts +52 -0
- package/src/participant/participantThemeContrast.test.ts +9 -0
- package/src/participant/test-fixtures/composer-audio-denied.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-idle.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-recording.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-review.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-native.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-player.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-empty.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-pending.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-ready.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-too_long.golden.html +1 -0
- package/src/participant/test-fixtures/thread-composer-microphone-idle.golden.html +1 -0
- package/src/participant/test-fixtures/thread-empty-draft.golden.html +1 -0
- package/src/participant/useParticipantAudioMediaEvents.ts +46 -0
- package/src/participant/useParticipantAudioPlayer.ts +52 -0
- package/src/participant/useParticipantAudioPlayerActions.ts +47 -0
- package/src/participant/useParticipantAudioRecorder.ts +153 -0
- package/src/participant/useParticipantAudioRecorderCore.ts +55 -0
- package/src/participant/useParticipantAudioRecorderEffects.ts +70 -0
- package/src/participant/useParticipantAudioSourceRetry.ts +42 -0
- package/src/participant/useParticipantSendAudio.test.tsx +85 -0
- package/src/participant/useParticipantSendAudio.ts +28 -0
- package/src/participant/useParticipantThreadController.ts +20 -7
- package/src/participant/useStableAttachmentUrl.ts +71 -0
- package/src/participant/useTranscriptionOverflow.ts +25 -0
- package/src/participant/useTranscriptionReadyAnnouncement.ts +24 -0
- package/src/styles.css +30 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type FakeRecordingControls = {
|
|
2
|
+
chunkBytes: number
|
|
3
|
+
readonly restore: () => void
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
class FakeTrack {
|
|
7
|
+
onended: (() => void) | null = null
|
|
8
|
+
onmute: (() => void) | null = null
|
|
9
|
+
onunmute: (() => void) | null = null
|
|
10
|
+
stop(): void {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class FakeStream {
|
|
14
|
+
getTracks(): FakeTrack[] {
|
|
15
|
+
return [new FakeTrack()]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Installs a MediaRecorder and getUserMedia that record `controls.chunkBytes` bytes on stop. */
|
|
20
|
+
export function installFakeRecording(initialChunkBytes = 4096): FakeRecordingControls {
|
|
21
|
+
const globals = globalThis as Record<string, unknown>
|
|
22
|
+
const navigatorObject = globalThis.navigator as unknown as Record<string, unknown>
|
|
23
|
+
const previousRecorder = globals.MediaRecorder
|
|
24
|
+
const previousMediaDevices = navigatorObject.mediaDevices
|
|
25
|
+
const controls: FakeRecordingControls = {
|
|
26
|
+
chunkBytes: initialChunkBytes,
|
|
27
|
+
restore: () => {
|
|
28
|
+
globals.MediaRecorder = previousRecorder
|
|
29
|
+
navigatorObject.mediaDevices = previousMediaDevices
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
globals.MediaRecorder = class FakeMediaRecorder {
|
|
33
|
+
static isTypeSupported(mimeType: string): boolean {
|
|
34
|
+
return mimeType === 'audio/ogg;codecs=opus'
|
|
35
|
+
}
|
|
36
|
+
ondataavailable: ((event: { data: Blob }) => void) | null = null
|
|
37
|
+
onstop: (() => void) | null = null
|
|
38
|
+
onerror: (() => void) | null = null
|
|
39
|
+
start(): void {}
|
|
40
|
+
stop(): void {
|
|
41
|
+
this.ondataavailable?.({ data: new Blob([new Uint8Array(controls.chunkBytes)]) })
|
|
42
|
+
this.onstop?.()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
navigatorObject.mediaDevices = { getUserMedia: async () => new FakeStream() }
|
|
46
|
+
return controls
|
|
47
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class SpecTrack {
|
|
2
|
+
stopCount = 0
|
|
3
|
+
onended: (() => void) | null = null
|
|
4
|
+
onmute: (() => void) | null = null
|
|
5
|
+
onunmute: (() => void) | null = null
|
|
6
|
+
stop(): void {
|
|
7
|
+
this.stopCount += 1
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SpecStream {
|
|
12
|
+
readonly track = new SpecTrack()
|
|
13
|
+
getTracks(): SpecTrack[] {
|
|
14
|
+
return [this.track]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Follows the MediaRecorder spec: stop() makes state inactive at once; dataavailable and stop arrive later. */
|
|
19
|
+
export class SpecMediaRecorder {
|
|
20
|
+
static instances: SpecMediaRecorder[] = []
|
|
21
|
+
static chunkBytes = 4096
|
|
22
|
+
static isTypeSupported(mimeType: string): boolean {
|
|
23
|
+
return mimeType === 'audio/ogg;codecs=opus'
|
|
24
|
+
}
|
|
25
|
+
state: 'inactive' | 'recording' = 'inactive'
|
|
26
|
+
stopCalls = 0
|
|
27
|
+
ondataavailable: ((event: { data: Blob }) => void) | null = null
|
|
28
|
+
onstop: (() => void) | null = null
|
|
29
|
+
onerror: (() => void) | null = null
|
|
30
|
+
constructor() {
|
|
31
|
+
SpecMediaRecorder.instances.push(this)
|
|
32
|
+
}
|
|
33
|
+
start(): void {
|
|
34
|
+
this.state = 'recording'
|
|
35
|
+
}
|
|
36
|
+
stop(): void {
|
|
37
|
+
if (this.state === 'inactive') throw new DOMException('The MediaRecorder is already inactive', 'InvalidStateError')
|
|
38
|
+
this.stopCalls += 1
|
|
39
|
+
this.finishAsynchronously()
|
|
40
|
+
}
|
|
41
|
+
/** The user agent stops the recorder by itself (for example when the track ends). */
|
|
42
|
+
autoStop(): void {
|
|
43
|
+
this.finishAsynchronously()
|
|
44
|
+
}
|
|
45
|
+
private finishAsynchronously(): void {
|
|
46
|
+
this.state = 'inactive'
|
|
47
|
+
void Promise.resolve().then(() => {
|
|
48
|
+
this.ondataavailable?.({ data: new Blob([new Uint8Array(SpecMediaRecorder.chunkBytes)]) })
|
|
49
|
+
this.onstop?.()
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -61,6 +61,15 @@ describe.each(SCENARIOS.map((entry) => [entry.name, entry] as const))(
|
|
|
61
61
|
}
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
it('reads the transcription block (text, machine notice, see-all button) on both bubbles', () => {
|
|
65
|
+
for (const side of ['received', 'mine'] as const) {
|
|
66
|
+
const fill = fillOf(palette, side)
|
|
67
|
+
expect(contrastRatio(palette.text, fill)).toBeGreaterThanOrEqual(TEXT_MINIMUM)
|
|
68
|
+
expect(contrastRatio(palette.muted, fill)).toBeGreaterThanOrEqual(TEXT_MINIMUM)
|
|
69
|
+
expect(contrastRatio(palette.accent, fill)).toBeGreaterThanOrEqual(TEXT_MINIMUM)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
64
73
|
it('keeps every tick at 3:1 or more on both bubbles', () => {
|
|
65
74
|
for (const side of ['received', 'mine'] as const) {
|
|
66
75
|
const fill = fillOf(palette, side)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p class="cv-p-composer__error" role="alert">No permission to use the microphone.</p><button type="button" class="cv-p-button cv-p-button--primary cv-p-composer__record" aria-label="Record audio" data-cv-tooltip="Record audio"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mic" aria-hidden="true" focusable="false"><path d="M12 19v3"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><rect x="9" y="2" width="6" height="13" rx="3"></rect></svg></button>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<button type="button" class="cv-p-button cv-p-button--primary cv-p-composer__record" aria-label="Record audio" data-cv-tooltip="Record audio"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mic" aria-hidden="true" focusable="false"><path d="M12 19v3"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><rect x="9" y="2" width="6" height="13" rx="3"></rect></svg></button>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p-recorder" role="group" aria-label="Recording"><button type="button" class="cv-p-button cv-p-recorder__discard" aria-label="Discard audio" data-cv-tooltip="Discard audio"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash lucide-trash-2" aria-hidden="true" focusable="false"><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M3 6h18"></path><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg></button><span class="cv-p-recorder__dot" aria-hidden="true"></span><span class="cv-p-recorder__elapsed" role="timer">0:12 of 5:00</span><button type="button" class="cv-p-button cv-p-button--primary cv-p-recorder__stop" aria-label="Stop recording" data-cv-tooltip="Stop recording"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square" aria-hidden="true" focusable="false"><rect width="18" height="18" x="3" y="3" rx="2"></rect></svg></button></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p-recorder cv-p-recorder--review" role="group" aria-label="Listen before sending"><p class="cv-p-recorder__hint" aria-hidden="true">Listen before sending</p><button type="button" class="cv-p-button cv-p-recorder__discard" aria-label="Discard audio" data-cv-tooltip="Discard audio"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash lucide-trash-2" aria-hidden="true" focusable="false"><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M3 6h18"></path><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg></button><div class="cv-p-audio" role="group" aria-label="Audio"><button type="button" class="cv-p-audio__toggle" aria-label="Play audio" data-cv-tooltip="Play audio"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play" aria-hidden="true" focusable="false"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"></path></svg></button><input type="range" class="cv-p-audio__seek" aria-label="Audio position" min="0" max="0" step="1" aria-valuetext="0:00" value="0"/><span class="cv-p-audio__time">0:00 / --:--</span><button type="button" class="cv-p-audio__rate" aria-label="Playback speed 1×">1×</button><audio class="cv-p-audio__media" preload="none"></audio></div><button type="button" class="cv-p-button cv-p-button--primary cv-p-recorder__send" aria-label="Send audio" data-cv-tooltip="Send audio"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-attachment cv-p-attachment--audio"><div class="cv-p-audio" role="group" aria-label="Audio: voice.webm"><button type="button" class="cv-p-audio__toggle" aria-label="Play audio" data-cv-tooltip="Play audio" aria-busy="true"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play" aria-hidden="true" focusable="false"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"></path></svg></button><input type="range" class="cv-p-audio__seek" aria-label="Audio position" min="0" max="0" step="1" aria-valuetext="0:00" value="0"/><span class="cv-p-audio__time">0:00 / --:--</span><button type="button" class="cv-p-audio__rate" aria-label="Playback speed 1×">1×</button><audio class="cv-p-audio__media" preload="none"></audio></div><span class="cv-p-attachment__name">voice.webm</span></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1">abc</textarea><button type="submit" class="cv-p-button cv-p-button--primary cv-p-composer__send" aria-label="Send" data-cv-tooltip="Send"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div></form></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-attachment cv-p-attachment--audio"><div class="cv-p-audio" role="group" aria-label="Audio: voice.webm"><button type="button" class="cv-p-audio__toggle" aria-label="Play audio" data-cv-tooltip="Play audio" aria-busy="true"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play" aria-hidden="true" focusable="false"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"></path></svg></button><input type="range" class="cv-p-audio__seek" aria-label="Audio position" min="0" max="65" step="1" aria-valuetext="0:00 of 1:05" value="0"/><span class="cv-p-audio__time">0:00 / 1:05</span><button type="button" class="cv-p-audio__rate" aria-label="Playback speed 1×">1×</button><audio class="cv-p-audio__media" preload="none"></audio></div><span class="cv-p-attachment__name">voice.webm</span></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1">abc</textarea><button type="submit" class="cv-p-button cv-p-button--primary cv-p-composer__send" aria-label="Send" data-cv-tooltip="Send"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div></form></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-attachment cv-p-attachment--audio"><div class="cv-p-audio" role="group" aria-label="Audio: voice.webm"><button type="button" class="cv-p-audio__toggle" aria-label="Play audio" data-cv-tooltip="Play audio" aria-busy="true"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play" aria-hidden="true" focusable="false"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"></path></svg></button><input type="range" class="cv-p-audio__seek" aria-label="Audio position" min="0" max="65" step="1" aria-valuetext="0:00 of 1:05" value="0"/><span class="cv-p-audio__time">0:00 / 1:05</span><button type="button" class="cv-p-audio__rate" aria-label="Playback speed 1×">1×</button><audio class="cv-p-audio__media" preload="none"></audio></div><span class="cv-p-attachment__name">voice.webm</span><div class="cv-p-transcription"><p class="cv-p-transcription__notice">Transcription generated by machine</p><p class="cv-p-transcription__empty">Nothing was said in the audio</p></div></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1">abc</textarea><button type="submit" class="cv-p-button cv-p-button--primary cv-p-composer__send" aria-label="Send" data-cv-tooltip="Send"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div></form></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-attachment cv-p-attachment--audio"><div class="cv-p-audio" role="group" aria-label="Audio: voice.webm"><button type="button" class="cv-p-audio__toggle" aria-label="Play audio" data-cv-tooltip="Play audio" aria-busy="true"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play" aria-hidden="true" focusable="false"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"></path></svg></button><input type="range" class="cv-p-audio__seek" aria-label="Audio position" min="0" max="65" step="1" aria-valuetext="0:00 of 1:05" value="0"/><span class="cv-p-audio__time">0:00 / 1:05</span><button type="button" class="cv-p-audio__rate" aria-label="Playback speed 1×">1×</button><audio class="cv-p-audio__media" preload="none"></audio></div><span class="cv-p-attachment__name">voice.webm</span><p class="cv-p-transcription cv-p-transcription--pending">Transcribing…</p></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1">abc</textarea><button type="submit" class="cv-p-button cv-p-button--primary cv-p-composer__send" aria-label="Send" data-cv-tooltip="Send"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div></form></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-attachment cv-p-attachment--audio"><div class="cv-p-audio" role="group" aria-label="Audio: voice.webm"><button type="button" class="cv-p-audio__toggle" aria-label="Play audio" data-cv-tooltip="Play audio" aria-busy="true"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play" aria-hidden="true" focusable="false"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"></path></svg></button><input type="range" class="cv-p-audio__seek" aria-label="Audio position" min="0" max="65" step="1" aria-valuetext="0:00 of 1:05" value="0"/><span class="cv-p-audio__time">0:00 / 1:05</span><button type="button" class="cv-p-audio__rate" aria-label="Playback speed 1×">1×</button><audio class="cv-p-audio__media" preload="none"></audio></div><span class="cv-p-attachment__name">voice.webm</span><div class="cv-p-transcription"><p class="cv-p-transcription__notice">Transcription generated by machine</p><p class="cv-p-transcription__text" lang="pt">Chego em dez minutos.</p></div></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1">abc</textarea><button type="submit" class="cv-p-button cv-p-button--primary cv-p-composer__send" aria-label="Send" data-cv-tooltip="Send"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div></form></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-attachment cv-p-attachment--audio"><div class="cv-p-audio" role="group" aria-label="Audio: voice.webm"><button type="button" class="cv-p-audio__toggle" aria-label="Play audio" data-cv-tooltip="Play audio" aria-busy="true"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play" aria-hidden="true" focusable="false"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"></path></svg></button><input type="range" class="cv-p-audio__seek" aria-label="Audio position" min="0" max="65" step="1" aria-valuetext="0:00 of 1:05" value="0"/><span class="cv-p-audio__time">0:00 / 1:05</span><button type="button" class="cv-p-audio__rate" aria-label="Playback speed 1×">1×</button><audio class="cv-p-audio__media" preload="none"></audio></div><span class="cv-p-attachment__name">voice.webm</span><p class="cv-p-transcription cv-p-transcription--too-long">Transcription only for audios shorter than 5 minutes</p></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1">abc</textarea><button type="submit" class="cv-p-button cv-p-button--primary cv-p-composer__send" aria-label="Send" data-cv-tooltip="Send"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div></form></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-text">*<strong>oi</strong>* <button type="button" class="cv-p-copyable cv-p-copyable--phone" aria-label="Copy 11 98888-7777">11 98888-7777</button> <button type="button" class="cv-p-copyable cv-p-copyable--email" aria-label="Copy a@b.com">a@b.com</button> <span class="cv-p-text__link-group"><a class="cv-p-text__link" href="https://x.com" rel="noopener noreferrer nofollow ugc" target="_blank">https://x.com<span class="cv-p-sr-only"> (opens in a new tab)</span></a><button type="button" class="cv-p-text__link-copy" aria-label="Copy https://x.com" data-cv-tooltip="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div><div class="cv-p-bubble"><span class="cv-p-bubble__author">Op</span><div class="cv-p-text">Hello</div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:05:00.000Z">12:05</time></span></div><div class="cv-p-day" role="separator" aria-label="Fri, 10/02"><time class="cv-p-day__label" dateTime="2026-10-02">Fri, 10/02</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-text">Hello</div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-02T12:05:00.000Z">12:05</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--delivered" data-cv-tooltip="Delivered"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L4.5 10L11 2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path><path d="M6 6.5L9.5 10L19 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Delivered</span></span></span></div><div class="cv-p-bubble cv-p-bubble--mine cv-p-bubble--failed"><button type="button" class="cv-p-failed-retry" aria-label="Failed — tap to retry" aria-describedby="_R_1kd_" data-cv-tooltip="Failed — tap to retry"><span class="cv-p-failed-retry__icon"><svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false"><path d="M13 8a5 5 0 1 1-1.6-3.7M13 2.5v3h-3" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg></span></button><span class="cv-p-sr-only">Me</span><div class="cv-p-text" id="_R_1kd_">W</div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-02T12:06:00.000Z">12:06</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--failed" data-cv-tooltip="Failed"><svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert lucide-alert-triangle" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg></span><span class="cv-p-sr-only">Failed</span></span><span class="cv-p-failed-menu"><button type="button" id="_R_erkd_" class="cv-p-failed-menu__trigger" aria-haspopup="menu" aria-expanded="false" aria-label="Message options" data-cv-tooltip="Message options"><span aria-hidden="true">⋯</span></button></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1"></textarea><button type="button" class="cv-p-button cv-p-button--primary cv-p-composer__record" aria-label="Record audio" data-cv-tooltip="Record audio"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mic" aria-hidden="true" focusable="false"><path d="M12 19v3"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><rect x="9" y="2" width="6" height="13" rx="3"></rect></svg></button></div></form></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="cv-p cv-p-thread" aria-busy="false"><header class="cv-p-thread__header"><button type="button" class="cv-p-thread__back" aria-label="Back" data-cv-tooltip="Back"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left" aria-hidden="true" focusable="false"><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg></button><div class="cv-p-thread__heading"><h2 class="cv-p-thread__title"><span class="cv-p-thread__title-text">Invoice 4521</span></h2><div class="cv-p-thread__meta"><span class="cv-p-protocol cv-p-protocol--header"><span class="cv-p-protocol__code"><span class="cv-p-sr-only">Protocol </span>P-1</span><button type="button" class="cv-p-protocol__copy" aria-label="Copy protocol P-1" data-cv-tooltip="Copy protocol"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span><div class="cv-p-sr-only" role="status" aria-live="polite"></div><span class="cv-p-channels"><span class="cv-p-sr-only">Channels in this conversation: </span><span class="cv-p-channel cv-p-channel--app" data-cv-tooltip="App"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-smartphone" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"></rect><path d="M12 18h.01"></path></svg><span class="cv-p-sr-only">App, </span></span><span class="cv-p-channel cv-p-channel--whatsapp" data-cv-tooltip="WhatsApp"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"></path></svg><span class="cv-p-sr-only">WhatsApp</span></span></span></div></div></header><div class="cv-p-thread__scroll" tabindex="-1"><button type="button" class="cv-p-thread__older">Load older messages</button><div class="cv-p-thread__messages"><div class="cv-p-day" role="separator" aria-label="Thu, 10/01"><time class="cv-p-day__label" dateTime="2026-10-01">Thu, 10/01</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-text">*<strong>oi</strong>* <button type="button" class="cv-p-copyable cv-p-copyable--phone" aria-label="Copy 11 98888-7777">11 98888-7777</button> <button type="button" class="cv-p-copyable cv-p-copyable--email" aria-label="Copy a@b.com">a@b.com</button> <span class="cv-p-text__link-group"><a class="cv-p-text__link" href="https://x.com" rel="noopener noreferrer nofollow ugc" target="_blank">https://x.com<span class="cv-p-sr-only"> (opens in a new tab)</span></a><button type="button" class="cv-p-text__link-copy" aria-label="Copy https://x.com" data-cv-tooltip="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></span></div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:00:00.000Z">12:00</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--sent" data-cv-tooltip="Sent"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L5 10.5L14.5 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Sent</span></span></span></div><div class="cv-p-bubble"><span class="cv-p-bubble__author">Op</span><div class="cv-p-text">Hello</div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-01T12:05:00.000Z">12:05</time></span></div><div class="cv-p-day" role="separator" aria-label="Fri, 10/02"><time class="cv-p-day__label" dateTime="2026-10-02">Fri, 10/02</time></div><div class="cv-p-bubble cv-p-bubble--mine"><span class="cv-p-sr-only">Me</span><div class="cv-p-text">Hello</div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-02T12:05:00.000Z">12:05</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--delivered" data-cv-tooltip="Delivered"><svg viewBox="0 0 20 12" width="15" height="9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6.5L4.5 10L11 2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path><path d="M6 6.5L9.5 10L19 1" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="cv-p-sr-only">Delivered</span></span></span></div><div class="cv-p-bubble cv-p-bubble--mine cv-p-bubble--failed"><button type="button" class="cv-p-failed-retry" aria-label="Failed — tap to retry" aria-describedby="_R_1kd_" data-cv-tooltip="Failed — tap to retry"><span class="cv-p-failed-retry__icon"><svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false"><path d="M13 8a5 5 0 1 1-1.6-3.7M13 2.5v3h-3" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg></span></button><span class="cv-p-sr-only">Me</span><div class="cv-p-text" id="_R_1kd_">W</div><span class="cv-p-bubble__meta"><time class="cv-p-bubble__time" dateTime="2026-10-02T12:06:00.000Z">12:06</time><span class="cv-p-bubble__status"><span class="cv-status-ticks cv-status-ticks--failed" data-cv-tooltip="Failed"><svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert lucide-alert-triangle" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg></span><span class="cv-p-sr-only">Failed</span></span><span class="cv-p-failed-menu"><button type="button" id="_R_erkd_" class="cv-p-failed-menu__trigger" aria-haspopup="menu" aria-expanded="false" aria-label="Message options" data-cv-tooltip="Message options"><span aria-hidden="true">⋯</span></button></span></span></div></div><div class="cv-p-thread__latest"><button type="button" class="cv-p-thread__latest-button" aria-label="Scroll to latest message" data-cv-tooltip="Scroll to latest message"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"></path></svg><span class="cv-p-thread__latest-badge" aria-hidden="true">2</span></button></div></div><div class="cv-p-thread__live" role="status" aria-live="polite">New messages</div><form class="cv-p-composer"><div class="cv-p-quick" role="group" aria-label="Quick replies"><button type="button" class="cv-p-chip">T</button></div><div class="cv-p-composer__row"><input class="cv-p-composer__file-input" type="file" multiple="" hidden=""/><button type="button" class="cv-p-composer__attach" aria-label="Attach" data-cv-tooltip="Attach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-paperclip" aria-hidden="true" focusable="false"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"></path></svg></button><textarea class="cv-p-composer__input" aria-label="Message" placeholder="Write a message…" rows="1"></textarea><button type="submit" class="cv-p-button cv-p-button--primary cv-p-composer__send" aria-label="Send" data-cv-tooltip="Send" disabled=""><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send" aria-hidden="true" focusable="false"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"></path><path d="m21.854 2.147-10.94 10.939"></path></svg></button></div></form></div>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SyntheticEvent } from 'react'
|
|
2
|
+
|
|
3
|
+
import { claimAudioPlayback, releaseAudioPlayback } from './participantAudioCoordinator'
|
|
4
|
+
import { resolveMediaDurationSeconds } from './participantAudioFormat'
|
|
5
|
+
import type { PlayerContext } from './participantAudioPlayerState'
|
|
6
|
+
import { useParticipantAudioSourceRetry } from './useParticipantAudioSourceRetry'
|
|
7
|
+
|
|
8
|
+
export type UseParticipantAudioMediaEventsParams = {
|
|
9
|
+
readonly context: PlayerContext
|
|
10
|
+
readonly url: string | undefined
|
|
11
|
+
readonly onSourceError: (() => Promise<string | undefined>) | undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Media element events of the player. */
|
|
15
|
+
export function useParticipantAudioMediaEvents({ context, url, onSourceError }: UseParticipantAudioMediaEventsParams) {
|
|
16
|
+
const { handleRef, state, update } = context
|
|
17
|
+
const { handleError, allowRetry } = useParticipantAudioSourceRetry({ context, url, onSourceError })
|
|
18
|
+
|
|
19
|
+
function handleStop(): void {
|
|
20
|
+
releaseAudioPlayback(handleRef.current)
|
|
21
|
+
update({ isPlaying: false })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleDuration(event: SyntheticEvent<HTMLAudioElement>): void {
|
|
25
|
+
update({ mediaDuration: resolveMediaDurationSeconds(event.currentTarget.duration) })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
onPlay: () => {
|
|
30
|
+
claimAudioPlayback(handleRef.current)
|
|
31
|
+
update({ isPlaying: true })
|
|
32
|
+
},
|
|
33
|
+
onPause: handleStop,
|
|
34
|
+
onEnded: handleStop,
|
|
35
|
+
onTimeUpdate: (event: SyntheticEvent<HTMLAudioElement>) => {
|
|
36
|
+
const position = event.currentTarget.currentTime
|
|
37
|
+
update({ position, maxSeen: Math.max(state.maxSeen, position) })
|
|
38
|
+
},
|
|
39
|
+
onLoadedMetadata: (event: SyntheticEvent<HTMLAudioElement>) => {
|
|
40
|
+
allowRetry()
|
|
41
|
+
handleDuration(event)
|
|
42
|
+
},
|
|
43
|
+
onDurationChange: handleDuration,
|
|
44
|
+
onError: handleError,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState, type Ref } from 'react'
|
|
2
|
+
|
|
3
|
+
import { releaseAudioPlayback, type AudioPlaybackHandle } from './participantAudioCoordinator'
|
|
4
|
+
import { PLAYBACK_RATES, resolveServerDurationSeconds } from './participantAudioFormat'
|
|
5
|
+
import { INITIAL_PLAYER_STATE, type PlayerState } from './participantAudioPlayerState'
|
|
6
|
+
import { useParticipantAudioMediaEvents } from './useParticipantAudioMediaEvents'
|
|
7
|
+
import { useParticipantAudioPlayerActions } from './useParticipantAudioPlayerActions'
|
|
8
|
+
|
|
9
|
+
export type UseParticipantAudioPlayerParams = {
|
|
10
|
+
readonly url: string | undefined
|
|
11
|
+
readonly durationMs: number | undefined
|
|
12
|
+
/** Fetches a fresh URL after a media error; without it the first error is final. */
|
|
13
|
+
readonly onSourceError?: () => Promise<string | undefined>
|
|
14
|
+
readonly mediaRef?: Ref<HTMLAudioElement>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function assignRef(target: Ref<HTMLAudioElement> | undefined, element: HTMLAudioElement | null): void {
|
|
18
|
+
if (typeof target === 'function') target(element)
|
|
19
|
+
else if (target) (target as { current: HTMLAudioElement | null }).current = element
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useParticipantAudioPlayer({ url, durationMs, onSourceError, mediaRef }: UseParticipantAudioPlayerParams) {
|
|
23
|
+
const audioRef = useRef<HTMLAudioElement | null>(null)
|
|
24
|
+
const handleRef = useRef<AudioPlaybackHandle>({ pause: () => audioRef.current?.pause() })
|
|
25
|
+
const [state, setState] = useState<PlayerState>(INITIAL_PLAYER_STATE)
|
|
26
|
+
const update = useCallback((patch: Partial<PlayerState>) => setState((previous) => ({ ...previous, ...patch })), [])
|
|
27
|
+
const mediaEvents = useParticipantAudioMediaEvents({ context: { audioRef, handleRef, state, update }, url, onSourceError })
|
|
28
|
+
const duration = resolveServerDurationSeconds(durationMs) ?? state.mediaDuration
|
|
29
|
+
|
|
30
|
+
const setMedia = useCallback(
|
|
31
|
+
(element: HTMLAudioElement | null) => {
|
|
32
|
+
audioRef.current = element
|
|
33
|
+
assignRef(mediaRef, element)
|
|
34
|
+
},
|
|
35
|
+
[mediaRef],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const handle = handleRef.current
|
|
40
|
+
return () => releaseAudioPlayback(handle)
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
const actions = useParticipantAudioPlayerActions({ context: { audioRef, handleRef, state, update }, url, duration })
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
state,
|
|
47
|
+
duration,
|
|
48
|
+
rate: PLAYBACK_RATES[state.rateIndex] ?? 1,
|
|
49
|
+
mediaProps: { ...mediaEvents, ref: setMedia },
|
|
50
|
+
...actions,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { KeyboardEvent } from 'react'
|
|
2
|
+
|
|
3
|
+
import { claimAudioPlayback } from './participantAudioCoordinator'
|
|
4
|
+
import { nextRateIndex, resolveSeekKeyTarget } from './participantAudioFormat'
|
|
5
|
+
import { applyPlaybackRate, type PlayerContext } from './participantAudioPlayerState'
|
|
6
|
+
|
|
7
|
+
export type UseParticipantAudioPlayerActionsParams = {
|
|
8
|
+
readonly context: PlayerContext
|
|
9
|
+
readonly url: string | undefined
|
|
10
|
+
readonly duration: number | undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useParticipantAudioPlayerActions({ context, url, duration }: UseParticipantAudioPlayerActionsParams) {
|
|
14
|
+
const { audioRef, handleRef, state, update } = context
|
|
15
|
+
|
|
16
|
+
function handleToggle(): void {
|
|
17
|
+
const audio = audioRef.current
|
|
18
|
+
if (!audio || !url) return
|
|
19
|
+
if (!audio.paused) return audio.pause()
|
|
20
|
+
claimAudioPlayback(handleRef.current)
|
|
21
|
+
applyPlaybackRate(audio, state.rateIndex)
|
|
22
|
+
Promise.resolve(audio.play()).catch(() => update({ isPlaying: false }))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function seekTo(seconds: number): void {
|
|
26
|
+
const audio = audioRef.current
|
|
27
|
+
if (!audio || !url) return
|
|
28
|
+
audio.currentTime = seconds
|
|
29
|
+
update({ position: seconds, maxSeen: Math.max(state.maxSeen, seconds) })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {
|
|
33
|
+
const position = audioRef.current?.currentTime ?? state.position
|
|
34
|
+
const target = resolveSeekKeyTarget({ key: event.key, position, duration })
|
|
35
|
+
if (target === undefined) return
|
|
36
|
+
event.preventDefault()
|
|
37
|
+
seekTo(target)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleRateClick(): void {
|
|
41
|
+
const rateIndex = nextRateIndex(state.rateIndex)
|
|
42
|
+
if (audioRef.current) applyPlaybackRate(audioRef.current, rateIndex)
|
|
43
|
+
update({ rateIndex })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { handleToggle, seekTo, handleKeyDown, handleRateClick }
|
|
47
|
+
}
|