@coventia/softphone 0.1.0 → 0.2.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/dist/{call-BTYlT2SU.d.cts → call-DUVnDxsz.d.cts} +152 -5
- package/dist/{call-BTYlT2SU.d.ts → call-DUVnDxsz.d.ts} +152 -5
- package/dist/{chunk-N2N2EQQJ.js → chunk-7R6LOOAI.js} +4 -2
- package/dist/chunk-7R6LOOAI.js.map +1 -0
- package/dist/{chunk-3V2L6UTK.js → chunk-LMJXX4YM.js} +17 -3
- package/dist/chunk-LMJXX4YM.js.map +1 -0
- package/dist/{chunk-5BGEHMPP.js → chunk-YWI6EQYS.js} +40 -7
- package/dist/chunk-YWI6EQYS.js.map +1 -0
- package/dist/element/index.d.ts +154 -5
- package/dist/element/index.js +512 -152
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-D9nbeY08.d.cts → engine-CDjkrXHY.d.cts} +1 -1
- package/dist/{engine-Doa6Brkm.d.ts → engine-EPCKf-Bl.d.ts} +1 -1
- package/dist/index.cjs +396 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +256 -8
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +15 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +14 -3
- package/dist/react/index.d.ts +14 -3
- package/dist/react/index.js +2 -2
- package/dist/{softphone-DhuhcKCy.d.cts → softphone-BtdgZWkm.d.ts} +17 -2
- package/dist/{softphone-DHmhotbf.d.ts → softphone-DHG9MTNg.d.cts} +17 -2
- package/dist/softphone.css +395 -1
- package/dist/{sources-N4BOEDRL.js → sources-5L2R4HTV.js} +104 -5
- package/dist/sources-5L2R4HTV.js.map +1 -0
- package/dist/testing/index.cjs +41 -5
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +8 -2
- package/dist/testing/index.d.ts +8 -2
- package/dist/testing/index.js +41 -5
- package/dist/testing/index.js.map +1 -1
- package/dist/ui/index.cjs +490 -139
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +2 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +478 -141
- package/dist/ui/index.js.map +1 -1
- package/dist/zadarma/index.cjs +38 -5
- package/dist/zadarma/index.cjs.map +1 -1
- package/dist/zadarma/index.d.cts +2 -2
- package/dist/zadarma/index.d.ts +2 -2
- package/dist/zadarma/index.js +1 -1
- package/dist/zadarma-engine-4VOPIBXG.js +5 -0
- package/dist/{zadarma-engine-DN2SZSQ3.js.map → zadarma-engine-4VOPIBXG.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-3V2L6UTK.js.map +0 -1
- package/dist/chunk-5BGEHMPP.js.map +0 -1
- package/dist/chunk-N2N2EQQJ.js.map +0 -1
- package/dist/sources-N4BOEDRL.js.map +0 -1
- package/dist/zadarma-engine-DN2SZSQ3.js +0 -5
package/dist/react/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { S as Softphone, A as AudioDevices } from '../softphone-
|
|
4
|
-
import { f as CallState, R as RemoteParty, a as Call, h as FatalSoftphoneError, o as SoftphoneState } from '../call-
|
|
3
|
+
import { S as Softphone, A as AudioDevices } from '../softphone-BtdgZWkm.js';
|
|
4
|
+
import { f as CallState, R as RemoteParty, a as Call, h as FatalSoftphoneError, P as PresenceEntry, o as SoftphoneState } from '../call-DUVnDxsz.js';
|
|
5
5
|
|
|
6
6
|
interface SoftphoneProviderProps {
|
|
7
7
|
readonly softphone: Softphone;
|
|
@@ -37,6 +37,17 @@ declare function useIsLeader(): boolean;
|
|
|
37
37
|
* `phone.activeCall` returns a stable reference for a given call, so it is a safe snapshot.
|
|
38
38
|
*/
|
|
39
39
|
declare function useActiveCall(): Call | null;
|
|
40
|
+
/**
|
|
41
|
+
* The live agent-presence list (`phone.presence`, Phase B) — reactive on `SoftphoneEvent.
|
|
42
|
+
* PresenceChanged`. `active` gates the SUBSCRIPTION only, not the underlying connection: the socket
|
|
43
|
+
* itself stays governed entirely by `online && isLeader` inside the softphone (spec's own
|
|
44
|
+
* always-connected decision), so passing `false` here does not close it — it only stops this
|
|
45
|
+
* particular component from re-rendering on every update while it has no reason to care, e.g. the
|
|
46
|
+
* transfer step being closed (spec §4.5: "subscribe when the step opens... unsubscribe when it
|
|
47
|
+
* closes"). `phone.presence` is still read fresh on every call, so flipping `active` back to `true`
|
|
48
|
+
* never shows a stale snapshot.
|
|
49
|
+
*/
|
|
50
|
+
declare function usePresence(active: boolean): readonly PresenceEntry[];
|
|
40
51
|
/** An immutable snapshot of a call's reactive fields. */
|
|
41
52
|
interface CallView {
|
|
42
53
|
readonly state: CallState;
|
|
@@ -65,4 +76,4 @@ declare function useCall(call: Call | null): CallView | null;
|
|
|
65
76
|
*/
|
|
66
77
|
declare function useDevices(): AudioDevices;
|
|
67
78
|
|
|
68
|
-
export { type CallView, SoftphoneProvider, type SoftphoneProviderProps, useActiveCall, useCall, useDevices, useIsLeader, useLastError, useSoftphone, useSoftphoneState };
|
|
79
|
+
export { type CallView, SoftphoneProvider, type SoftphoneProviderProps, useActiveCall, useCall, useDevices, useIsLeader, useLastError, usePresence, useSoftphone, useSoftphoneState };
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SoftphoneProvider, useActiveCall, useCall, useDevices, useIsLeader, useLastError, useSoftphone, useSoftphoneState } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { SoftphoneProvider, useActiveCall, useCall, useDevices, useIsLeader, useLastError, usePresence, useSoftphone, useSoftphoneState } from '../chunk-LMJXX4YM.js';
|
|
2
|
+
import '../chunk-7R6LOOAI.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { u as ContactsSource, v as HistorySource, g as Contact, A as AgentsDirectory, q as TypedEmitter, n as SoftphoneEventMap, o as SoftphoneState, h as FatalSoftphoneError, e as CallOptions, a as Call } from './call-
|
|
1
|
+
import { u as ContactsSource, v as HistorySource, g as Contact, A as AgentsDirectory, w as TransferOutcomesPage, x as PresenceSource, q as TypedEmitter, n as SoftphoneEventMap, o as SoftphoneState, h as FatalSoftphoneError, e as CallOptions, a as Call, P as PresenceEntry } from './call-DUVnDxsz.js';
|
|
2
2
|
|
|
3
|
-
/** Contacts, history, caller ID
|
|
3
|
+
/** Contacts, history, caller ID, the agents directory, transfer outcomes and presence, all from one
|
|
4
|
+
* config. */
|
|
4
5
|
interface BackendSources {
|
|
5
6
|
readonly contacts: ContactsSource;
|
|
6
7
|
readonly history: HistorySource;
|
|
7
8
|
readonly resolveContact: (number: string) => Promise<Contact | null>;
|
|
8
9
|
/** The Historial tab's "filter by person" directory. Always Coventia's own org membership. */
|
|
9
10
|
readonly listAgents: () => Promise<AgentsDirectory>;
|
|
11
|
+
/** Phase A (transfer outcome relay) — polled by the transfer watch, never by UI directly. */
|
|
12
|
+
readonly transferOutcomes: (params: {
|
|
13
|
+
readonly since?: string;
|
|
14
|
+
}) => Promise<TransferOutcomesPage>;
|
|
15
|
+
/** Phase B (presence-gated transfer picker) — opened by `presence-socket.ts`, never by UI directly. */
|
|
16
|
+
readonly presence: PresenceSource;
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
/**
|
|
@@ -114,6 +121,14 @@ interface Softphone extends TypedEmitter<SoftphoneEventMap> {
|
|
|
114
121
|
*/
|
|
115
122
|
readonly sources: BackendSources | null;
|
|
116
123
|
readonly audio: AudioDevices;
|
|
124
|
+
/**
|
|
125
|
+
* The live agent-presence list (Phase B, `docs/specs/transfer-presence-picker.md`) — every org
|
|
126
|
+
* member with a phone extension, whatever their state. Empty when there is no `PresenceSource`
|
|
127
|
+
* (no Coventia backend, or a host that omitted one), or while this tab is not `online && isLeader`
|
|
128
|
+
* — never an error, just nothing to show (spec §4.4's own success criterion). Updates fire
|
|
129
|
+
* `SoftphoneEvent.PresenceChanged`; `usePresence()` is the reactive read.
|
|
130
|
+
*/
|
|
131
|
+
readonly presence: readonly PresenceEntry[];
|
|
117
132
|
/**
|
|
118
133
|
* False when the browser has blocked audio playback.
|
|
119
134
|
*
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { u as ContactsSource, v as HistorySource, g as Contact, A as AgentsDirectory, q as TypedEmitter, n as SoftphoneEventMap, o as SoftphoneState, h as FatalSoftphoneError, e as CallOptions, a as Call } from './call-
|
|
1
|
+
import { u as ContactsSource, v as HistorySource, g as Contact, A as AgentsDirectory, w as TransferOutcomesPage, x as PresenceSource, q as TypedEmitter, n as SoftphoneEventMap, o as SoftphoneState, h as FatalSoftphoneError, e as CallOptions, a as Call, P as PresenceEntry } from './call-DUVnDxsz.cjs';
|
|
2
2
|
|
|
3
|
-
/** Contacts, history, caller ID
|
|
3
|
+
/** Contacts, history, caller ID, the agents directory, transfer outcomes and presence, all from one
|
|
4
|
+
* config. */
|
|
4
5
|
interface BackendSources {
|
|
5
6
|
readonly contacts: ContactsSource;
|
|
6
7
|
readonly history: HistorySource;
|
|
7
8
|
readonly resolveContact: (number: string) => Promise<Contact | null>;
|
|
8
9
|
/** The Historial tab's "filter by person" directory. Always Coventia's own org membership. */
|
|
9
10
|
readonly listAgents: () => Promise<AgentsDirectory>;
|
|
11
|
+
/** Phase A (transfer outcome relay) — polled by the transfer watch, never by UI directly. */
|
|
12
|
+
readonly transferOutcomes: (params: {
|
|
13
|
+
readonly since?: string;
|
|
14
|
+
}) => Promise<TransferOutcomesPage>;
|
|
15
|
+
/** Phase B (presence-gated transfer picker) — opened by `presence-socket.ts`, never by UI directly. */
|
|
16
|
+
readonly presence: PresenceSource;
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
/**
|
|
@@ -114,6 +121,14 @@ interface Softphone extends TypedEmitter<SoftphoneEventMap> {
|
|
|
114
121
|
*/
|
|
115
122
|
readonly sources: BackendSources | null;
|
|
116
123
|
readonly audio: AudioDevices;
|
|
124
|
+
/**
|
|
125
|
+
* The live agent-presence list (Phase B, `docs/specs/transfer-presence-picker.md`) — every org
|
|
126
|
+
* member with a phone extension, whatever their state. Empty when there is no `PresenceSource`
|
|
127
|
+
* (no Coventia backend, or a host that omitted one), or while this tab is not `online && isLeader`
|
|
128
|
+
* — never an error, just nothing to show (spec §4.4's own success criterion). Updates fire
|
|
129
|
+
* `SoftphoneEvent.PresenceChanged`; `usePresence()` is the reactive read.
|
|
130
|
+
*/
|
|
131
|
+
readonly presence: readonly PresenceEntry[];
|
|
117
132
|
/**
|
|
118
133
|
* False when the browser has blocked audio playback.
|
|
119
134
|
*
|
package/dist/softphone.css
CHANGED
|
@@ -219,6 +219,10 @@
|
|
|
219
219
|
|
|
220
220
|
background: var(--cv-bg);
|
|
221
221
|
overflow: hidden;
|
|
222
|
+
/* The containing block for `.cv-transfer-notices` — it overlays this whole shell, top edge to top
|
|
223
|
+
edge, not just `.cv-body`, so it reads as one seamless panel rather than a strip that starts a few
|
|
224
|
+
pixels short of the shell's own rounded corner. */
|
|
225
|
+
position: relative;
|
|
222
226
|
|
|
223
227
|
/* The frame is the HOST's call. The CRM's panel already draws `border rounded-xl shadow-2xl`, so
|
|
224
228
|
drawing our own put a card inside a card — nested chrome, which is always wrong. Defaulting to
|
|
@@ -293,6 +297,140 @@
|
|
|
293
297
|
.cv-tab:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
294
298
|
.cv-tab span { font-size: 10px; font-weight: 600; line-height: 1; letter-spacing: 0.01em; }
|
|
295
299
|
|
|
300
|
+
/* ── Transfer outcome notices (Phase A — transfer-outcome-relay.md §5.5) ──────── */
|
|
301
|
+
/*
|
|
302
|
+
A persistent, dismissable banner OVER `.cv-body`, never a toast and never auto-dismissed — it carries
|
|
303
|
+
an action, and WCAG 2.2.1 requires a time limit to be removable, adjustable or extended.
|
|
304
|
+
|
|
305
|
+
Overlaps rather than reserving its own height, the same reasoning `.cv-dial-outcome`'s own comment
|
|
306
|
+
already gives for why the widget never lets a line of text resize the keypad under a moving finger —
|
|
307
|
+
here that principle cuts the other way: appearing costs nothing but a repaint, not a reflow, so the
|
|
308
|
+
widget's overall height stays identical whether a notice is showing or not, exactly like `.cv-detail`
|
|
309
|
+
overlaying `.cv-body`'s own list rather than pushing it down. It sits over whatever the current tab
|
|
310
|
+
happens to be showing (most often the dialer's country picker and number field — an acceptable loss:
|
|
311
|
+
the agent is not expected to start a fresh manual dial before dealing with a transfer that just failed
|
|
312
|
+
to connect), which is also why it outranks `.cv-detail` in `z-index`.
|
|
313
|
+
|
|
314
|
+
`[data-has-notices='false']` collapses to zero footprint: the element stays mounted (so its
|
|
315
|
+
`role="status"` region exists before the FIRST notice ever arrives — an aria-live region inserted at
|
|
316
|
+
the same moment as its own content is not reliably announced), but an idle widget, the common case,
|
|
317
|
+
covers nothing and costs no paint either.
|
|
318
|
+
*/
|
|
319
|
+
.cv-transfer-notices {
|
|
320
|
+
position: absolute;
|
|
321
|
+
inset: 0 0 auto 0;
|
|
322
|
+
z-index: 3; /* above `.cv-detail`'s 2 — this is the higher-priority thing on screen */
|
|
323
|
+
}
|
|
324
|
+
.cv-transfer-notices[data-has-notices='true'] {
|
|
325
|
+
display: flex;
|
|
326
|
+
flex-direction: column;
|
|
327
|
+
gap: 0.5rem;
|
|
328
|
+
padding: 0.6rem 0.75rem;
|
|
329
|
+
background: var(--cv-bg);
|
|
330
|
+
box-shadow: var(--cv-shadow);
|
|
331
|
+
animation: cv-transfer-notices-in 200ms var(--cv-ease-out) both;
|
|
332
|
+
}
|
|
333
|
+
@keyframes cv-transfer-notices-in {
|
|
334
|
+
from { opacity: 0; transform: translateY(-6px); }
|
|
335
|
+
to { opacity: 1; transform: none; }
|
|
336
|
+
}
|
|
337
|
+
@media (prefers-reduced-motion: reduce) {
|
|
338
|
+
.cv-transfer-notices[data-has-notices='true'] { animation: none; }
|
|
339
|
+
}
|
|
340
|
+
.cv-transfer-notices-count {
|
|
341
|
+
margin: 0;
|
|
342
|
+
font: 600 0.72rem var(--cv-font);
|
|
343
|
+
color: var(--cv-muted);
|
|
344
|
+
text-transform: uppercase;
|
|
345
|
+
letter-spacing: 0.04em;
|
|
346
|
+
}
|
|
347
|
+
.cv-transfer-notice {
|
|
348
|
+
display: flex;
|
|
349
|
+
align-items: flex-start;
|
|
350
|
+
gap: 0.6rem;
|
|
351
|
+
padding: 0.65rem 0.7rem;
|
|
352
|
+
border-radius: var(--cv-radius-key);
|
|
353
|
+
border: 1px solid var(--cv-danger-soft);
|
|
354
|
+
background: var(--cv-surface);
|
|
355
|
+
}
|
|
356
|
+
/* Same shape as `.cv-gate-mark`, smaller — one visual language for "here is a status", not two. */
|
|
357
|
+
.cv-transfer-notice-mark {
|
|
358
|
+
flex-shrink: 0;
|
|
359
|
+
display: grid;
|
|
360
|
+
place-items: center;
|
|
361
|
+
width: 30px;
|
|
362
|
+
height: 30px;
|
|
363
|
+
border-radius: 999px;
|
|
364
|
+
background: var(--cv-danger-soft);
|
|
365
|
+
color: var(--cv-danger);
|
|
366
|
+
}
|
|
367
|
+
.cv-transfer-notice-body { flex: 1; min-width: 0; }
|
|
368
|
+
/*
|
|
369
|
+
Title shares its row with only the dismiss button (24px) — not the whole card's width, and not the
|
|
370
|
+
"Llamar de vuelta" button, which gets its own row below (`.cv-transfer-notice-footer`). The original
|
|
371
|
+
layout put mark + title + subtitle + a full button all in one flex row, leaving the title as little as
|
|
372
|
+
~80px to wrap into at the real ~340px embed width — nearly every word landed on its own line.
|
|
373
|
+
`align-items: flex-start` keeps the dismiss pinned to the first line even when the title wraps to two.
|
|
374
|
+
*/
|
|
375
|
+
.cv-transfer-notice-header { display: flex; align-items: flex-start; gap: 0.4rem; }
|
|
376
|
+
.cv-transfer-notice-title {
|
|
377
|
+
flex: 1;
|
|
378
|
+
min-width: 0;
|
|
379
|
+
margin: 0;
|
|
380
|
+
font: 600 0.82rem var(--cv-font);
|
|
381
|
+
color: var(--cv-text);
|
|
382
|
+
line-height: 1.35;
|
|
383
|
+
}
|
|
384
|
+
.cv-transfer-notice-subtitle {
|
|
385
|
+
margin: 0.15rem 0 0;
|
|
386
|
+
font-size: 0.78rem;
|
|
387
|
+
color: var(--cv-muted);
|
|
388
|
+
line-height: 1.35;
|
|
389
|
+
}
|
|
390
|
+
/* Age and the callback button share a row too, same reasoning as the header — a short fixed-width
|
|
391
|
+
timestamp beside a content-sized button, neither one fighting the title for space above. */
|
|
392
|
+
.cv-transfer-notice-footer {
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
justify-content: space-between;
|
|
396
|
+
gap: 0.5rem;
|
|
397
|
+
margin-top: 0.35rem;
|
|
398
|
+
}
|
|
399
|
+
.cv-transfer-notice-age { margin: 0; font-size: 0.7rem; color: var(--cv-faint); flex-shrink: 0; }
|
|
400
|
+
/*
|
|
401
|
+
`.cv-btn.cv-transfer-notice-callback` — TWO classes, not one. Equal-specificity selectors resolve by
|
|
402
|
+
source order, and this rule used to lose its `padding`/`font-size` to the shared `.cv-btn` rule below
|
|
403
|
+
simply because that one appears later in the file; the button rendered at full default CTA size
|
|
404
|
+
instead of this smaller, secondary one, which is what actually forced the title into that ~80px
|
|
405
|
+
column above. The extra class is what makes this override win regardless of source order.
|
|
406
|
+
*/
|
|
407
|
+
.cv-btn.cv-transfer-notice-callback {
|
|
408
|
+
flex-shrink: 0;
|
|
409
|
+
padding: 0.35rem 0.6rem;
|
|
410
|
+
font-size: 0.75rem;
|
|
411
|
+
display: inline-flex;
|
|
412
|
+
align-items: center;
|
|
413
|
+
gap: 0.3rem;
|
|
414
|
+
white-space: nowrap;
|
|
415
|
+
}
|
|
416
|
+
/* Same round icon-only shape as `.cv-search-clear` — one dismiss affordance, not a new one. */
|
|
417
|
+
.cv-transfer-notice-dismiss {
|
|
418
|
+
flex-shrink: 0;
|
|
419
|
+
width: 24px;
|
|
420
|
+
height: 24px;
|
|
421
|
+
display: inline-flex;
|
|
422
|
+
align-items: center;
|
|
423
|
+
justify-content: center;
|
|
424
|
+
border: none;
|
|
425
|
+
border-radius: 999px;
|
|
426
|
+
background: none;
|
|
427
|
+
color: var(--cv-muted);
|
|
428
|
+
cursor: pointer;
|
|
429
|
+
transition: color 0.12s ease, background 0.12s ease, transform 0.1s var(--cv-ease-out);
|
|
430
|
+
}
|
|
431
|
+
.cv-transfer-notice-dismiss:hover { color: var(--cv-text); background: var(--cv-surface-2); }
|
|
432
|
+
.cv-transfer-notice-dismiss:active { transform: scale(0.88); }
|
|
433
|
+
|
|
296
434
|
/* ── Buttons ─────────────────────────────────────────────────────────────────── */
|
|
297
435
|
.cv-btn {
|
|
298
436
|
font: 600 0.875rem var(--cv-font);
|
|
@@ -494,6 +632,16 @@
|
|
|
494
632
|
gap: 0.15rem;
|
|
495
633
|
text-align: center;
|
|
496
634
|
padding: 1rem;
|
|
635
|
+
/*
|
|
636
|
+
A REAL bug, found live (plan T7): the transfer picker's agent list can make this box's content
|
|
637
|
+
taller than the space `flex: 1` actually allocates it — the avatar/name/timer/status ABOVE the
|
|
638
|
+
picker are still shown while transferring, unlike `.cv-call-devices-inline`'s own "swap the
|
|
639
|
+
content" fix for the pre-connect devices screen. Without this, the overflow painted straight past
|
|
640
|
+
`.cv-call`'s own shrunk box and onto `.cv-controls` below it — not scroll, actual overlap, controls
|
|
641
|
+
rendered ON TOP of list rows. `overflow-y: auto` keeps any excess inside THIS box, scrollable,
|
|
642
|
+
exactly like `.cv-presence-list`'s own internal scroll already does for the list itself.
|
|
643
|
+
*/
|
|
644
|
+
overflow-y: auto;
|
|
497
645
|
}
|
|
498
646
|
|
|
499
647
|
.cv-avatar-wrap { position: relative; margin-bottom: 0.85rem; }
|
|
@@ -752,11 +900,250 @@
|
|
|
752
900
|
border-color: var(--cv-text);
|
|
753
901
|
}
|
|
754
902
|
|
|
903
|
+
/*
|
|
904
|
+
The attended-transfer target-entry panel (`docs/specs/transfer-availability.md` §5): an optional
|
|
905
|
+
outcome line, then a row of whatever's actionable (input+button today). Once a consult exists,
|
|
906
|
+
`ConsultScreen` takes over the whole screen instead — this panel is ONLY ever the pre-consult /
|
|
907
|
+
retry-after-failure state now; it used to also cover the consulting/answered phases, which is why
|
|
908
|
+
its rule is a column container built for more than one child even though there's only ever one row
|
|
909
|
+
here today. (History: was two near-duplicate rules — `.cv-transfer` as a bare ROW,
|
|
910
|
+
`.cv-transfer-consult` as a column — until the row version turned out to be a real,
|
|
911
|
+
reproduced-in-browser bug: the optional `.cv-dial-outcome` message rendered as a THIRD item in the
|
|
912
|
+
same horizontal row as the input and button, squeezing the input to its content-minimum. Merged
|
|
913
|
+
rather than patched.)
|
|
914
|
+
*/
|
|
755
915
|
.cv-transfer {
|
|
916
|
+
display: flex;
|
|
917
|
+
flex-direction: column;
|
|
918
|
+
gap: 0.3rem;
|
|
919
|
+
width: 100%;
|
|
920
|
+
padding: 0.2rem 0.25rem 0;
|
|
921
|
+
/* `.cv-call` centres and centre-aligns its normal (avatar) content — same reasoning
|
|
922
|
+
`.cv-call-devices-inline` already opts out of for the OTHER inline swap in this box (its own
|
|
923
|
+
comment): rows read left-aligned everywhere else in the widget, and a centred context line
|
|
924
|
+
sitting directly above a left-aligned input/toggle/list read as two layouts stacked on top of
|
|
925
|
+
each other, found live (2026-09-29). */
|
|
926
|
+
text-align: left;
|
|
927
|
+
/* Found live (2026-09-29): `.cv-call`'s avatar/name/timer block used to sit ABOVE this,
|
|
928
|
+
unconditionally, so this panel only ever got whatever was left over — see the JSX call site's own
|
|
929
|
+
comment. Now that this IS the whole of `.cv-call`'s content while a transfer is in progress
|
|
930
|
+
(a swap, not an addition), it should CLAIM `.cv-call`'s available height, not just sit in it —
|
|
931
|
+
`flex: 1` does that; `min-height: 0` is what lets a flex child actually shrink to fit instead of
|
|
932
|
+
forcing its parent to grow past it (the same reason `.cv-call`/`.cv-view`/`.cv-body` all set it,
|
|
933
|
+
see their own comments). This is what lets `.cv-presence-list` below flex-grow into real room
|
|
934
|
+
instead of needing its own hand-tuned `max-height` guess. */
|
|
935
|
+
flex: 1;
|
|
936
|
+
min-height: 0;
|
|
937
|
+
/* Same entrance `.cv-call-devices-inline` already uses for the analogous inline swap (its own
|
|
938
|
+
comment) — reused, not a second animation invented for this widget's second inline-swap view. */
|
|
939
|
+
animation: cv-call-devices-in 0.18s cubic-bezier(0.2, 0, 0.2, 1);
|
|
940
|
+
}
|
|
941
|
+
@media (prefers-reduced-motion: reduce) {
|
|
942
|
+
.cv-transfer { animation: none; }
|
|
943
|
+
}
|
|
944
|
+
/* The compact "who am I still on the phone with" line while the picker is open — reuses
|
|
945
|
+
`.cv-call-devices-inline`'s own two classes verbatim (`.cv-call-devices-title` for the name,
|
|
946
|
+
`.cv-call-devices-context` for the number/timer line) rather than inventing a second "quiet header"
|
|
947
|
+
style for the same widget; only the tiny stacking gap between them is new.
|
|
948
|
+
|
|
949
|
+
Found live (2026-09-29): `.cv-transfer`'s own `gap` alone gave every section — context, input row,
|
|
950
|
+
mode toggle, results list — the exact same spacing, so nothing read as its own group; the eye had
|
|
951
|
+
no cue for where "who I'm on the phone with" ends and "pick a transfer target" begins. This extra
|
|
952
|
+
`margin-bottom`, on top of the base `gap`, is that cue — the toggle stays close to the list it
|
|
953
|
+
controls (unchanged), only the boundary above the interactive controls opened up. */
|
|
954
|
+
.cv-transfer-context {
|
|
955
|
+
display: flex;
|
|
956
|
+
flex-direction: column;
|
|
957
|
+
gap: 2px;
|
|
958
|
+
flex-shrink: 0;
|
|
959
|
+
margin-bottom: 0.35rem;
|
|
960
|
+
}
|
|
961
|
+
/* Plan T3: the mode toggle reuses `.cv-segmented` (HistoryTab's own filter control) as-is, but its
|
|
962
|
+
`margin-bottom` (needed THERE, where the surrounding layout has no gap of its own) is redundant
|
|
963
|
+
here — `.cv-transfer`'s own `gap` above already spaces every child, so the two would stack. Scoped
|
|
964
|
+
to this context only; HistoryTab's own copy is untouched. */
|
|
965
|
+
.cv-transfer .cv-segmented { margin-bottom: 0; flex-shrink: 0; }
|
|
966
|
+
.cv-transfer .cv-presence-legend { flex-shrink: 0; }
|
|
967
|
+
/* Same treatment as `.cv-call-status` (the avatar's own "Llamando…"/"En espera" line) — one muted
|
|
968
|
+
status-line vocabulary for the whole call screen, not a second one invented for this panel. */
|
|
969
|
+
.cv-transfer-status {
|
|
970
|
+
margin: 0;
|
|
971
|
+
font-size: 0.78rem;
|
|
972
|
+
color: var(--cv-muted);
|
|
973
|
+
letter-spacing: 0.04em;
|
|
974
|
+
}
|
|
975
|
+
/* The target-entry/retry row: an input plus one button. */
|
|
976
|
+
.cv-transfer-row {
|
|
756
977
|
display: flex;
|
|
757
978
|
gap: 0.5rem;
|
|
979
|
+
}
|
|
980
|
+
/*
|
|
981
|
+
Flex items default to `min-width: auto`, which floors them at their CONTENT size rather than letting
|
|
982
|
+
them shrink or grow to fill the row — `.cv-search`'s own `width: 100%` never actually took effect
|
|
983
|
+
here, because `.cv-btn`'s text ("Llamar") set an unshrinkable floor and the input, having no
|
|
984
|
+
`flex-grow`, never reclaimed the space left over. `flex: 1` + `min-width: 0` is what actually makes
|
|
985
|
+
an input-plus-button row behave like one; `flex: none` keeps the button at its natural size.
|
|
986
|
+
*/
|
|
987
|
+
.cv-transfer-row .cv-search {
|
|
988
|
+
flex: 1;
|
|
989
|
+
min-width: 0;
|
|
990
|
+
}
|
|
991
|
+
/* `max-width` + ellipsis: the label now includes whatever was typed ("Llamar a {typed}", spec §4.5)
|
|
992
|
+
and `truncateForButton` already bounds that in JS — this is the CSS backstop, so a value that
|
|
993
|
+
slips past it (a very wide character set, a future copy change) still can't push `.cv-search` down
|
|
994
|
+
to an unusable sliver the way the pre-truncation button did before this was noticed. */
|
|
995
|
+
.cv-transfer-row .cv-btn {
|
|
996
|
+
flex: none;
|
|
997
|
+
max-width: 60%;
|
|
998
|
+
overflow: hidden;
|
|
999
|
+
text-overflow: ellipsis;
|
|
1000
|
+
white-space: nowrap;
|
|
1001
|
+
}
|
|
1002
|
+
/* The consult-answered row: one or two buttons, no input. */
|
|
1003
|
+
.cv-transfer-actions {
|
|
1004
|
+
display: flex;
|
|
1005
|
+
gap: 0.5rem;
|
|
1006
|
+
}
|
|
1007
|
+
.cv-transfer-actions .cv-btn {
|
|
1008
|
+
flex: 1;
|
|
1009
|
+
}
|
|
1010
|
+
/* The ringing phase has only "Cancelar" — a secondary, low-stakes action. Letting the row's own
|
|
1011
|
+
`flex: 1` stretch a LONE button edge to edge reads as oversized for what it is, and nothing else in
|
|
1012
|
+
this widget sizes a single button to the full row; every other `.cv-btn` sizes to its content. */
|
|
1013
|
+
.cv-transfer-actions .cv-btn:only-child {
|
|
1014
|
+
flex: none;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/*
|
|
1018
|
+
The agent picker (spec §4.5, plan T7) — an inline, always-visible filtered list under the
|
|
1019
|
+
input+button row, not a click-to-open popover like `.cv-person-filter-pop`: the whole point is that
|
|
1020
|
+
typing already filters it, so hiding it behind a second interaction would undo that.
|
|
1021
|
+
|
|
1022
|
+
Redesigned live (2026-09-29): T7 (72px) and T3 (30px) both hand-tuned a `max-height` in PIXELS
|
|
1023
|
+
against whatever else happened to be on screen at the time, and both were broken by the very next
|
|
1024
|
+
addition above the list (T3's own toggle broke T7's number the same way this would keep breaking on
|
|
1025
|
+
the next change) — down to "barely proves the list is scrollable" at 30px, which is what the user
|
|
1026
|
+
was actually seeing: "users cannot almost see any of the listed users." The real fix wasn't a bigger
|
|
1027
|
+
magic number, it was `.cv-transfer`'s own move to `flex: 1` (see its comment) freeing real height
|
|
1028
|
+
from the block that used to sit above this whole panel — `.cv-presence` and this list now flex-grow
|
|
1029
|
+
into whatever's ACTUALLY left after the context line, input row, toggle and legend take their own
|
|
1030
|
+
natural size, on whatever real height the host panel gives `.cv-call` (this SDK's fixed ~310×520
|
|
1031
|
+
embed today, but also whatever a different host gives it tomorrow — no more re-tuning a constant
|
|
1032
|
+
every time something above the list changes). `.cv-call`'s own `overflow-y: auto` (its own comment)
|
|
1033
|
+
is still the last-resort net if content ever genuinely doesn't fit; this just means that net should
|
|
1034
|
+
now rarely, if ever, have to catch anything for a normal agent list.
|
|
1035
|
+
*/
|
|
1036
|
+
.cv-presence {
|
|
1037
|
+
display: flex;
|
|
1038
|
+
flex-direction: column;
|
|
1039
|
+
gap: 0.35rem;
|
|
1040
|
+
flex: 1;
|
|
1041
|
+
min-height: 0;
|
|
1042
|
+
}
|
|
1043
|
+
.cv-presence-list {
|
|
1044
|
+
display: flex;
|
|
1045
|
+
flex-direction: column;
|
|
1046
|
+
gap: 2px;
|
|
1047
|
+
flex: 1;
|
|
1048
|
+
min-height: 0;
|
|
1049
|
+
overflow-y: auto;
|
|
1050
|
+
padding: 0.25rem;
|
|
1051
|
+
border: 1px solid var(--cv-border);
|
|
1052
|
+
border-radius: var(--cv-radius-key);
|
|
1053
|
+
background: var(--cv-surface);
|
|
1054
|
+
}
|
|
1055
|
+
.cv-presence-empty {
|
|
1056
|
+
margin: 0;
|
|
1057
|
+
padding: 0.5rem;
|
|
1058
|
+
text-align: center;
|
|
1059
|
+
font-size: 0.78rem;
|
|
1060
|
+
color: var(--cv-muted);
|
|
1061
|
+
}
|
|
1062
|
+
.cv-presence-item {
|
|
758
1063
|
width: 100%;
|
|
759
|
-
|
|
1064
|
+
display: flex;
|
|
1065
|
+
align-items: center;
|
|
1066
|
+
gap: 0.5rem;
|
|
1067
|
+
padding: 0.4rem 0.5rem;
|
|
1068
|
+
border: none;
|
|
1069
|
+
border-radius: calc(var(--cv-radius) - 4px);
|
|
1070
|
+
background: none;
|
|
1071
|
+
color: var(--cv-text);
|
|
1072
|
+
font: 400 0.8rem var(--cv-font);
|
|
1073
|
+
text-align: left;
|
|
1074
|
+
cursor: pointer;
|
|
1075
|
+
}
|
|
1076
|
+
.cv-presence-item:hover:not(:disabled) { background: var(--cv-surface-2); }
|
|
1077
|
+
/* En llamada rows are a hard block, not just a hint (CallScreen.tsx's own comment on the row has why)
|
|
1078
|
+
— same `:disabled` treatment `.cv-btn`/`.cv-tab` already use elsewhere in this file, not a new pattern. */
|
|
1079
|
+
.cv-presence-item:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
1080
|
+
.cv-presence-dot {
|
|
1081
|
+
flex-shrink: 0;
|
|
1082
|
+
width: 8px;
|
|
1083
|
+
height: 8px;
|
|
1084
|
+
border-radius: 999px;
|
|
1085
|
+
}
|
|
1086
|
+
/* Reuses existing intent tokens rather than inventing new ones — `--cv-accent` already means
|
|
1087
|
+
"good, reachable" (the answer button), `--cv-hold` already means "occupied elsewhere" (the hold
|
|
1088
|
+
toggle), and `--cv-faint` is this file's own DECORATIVE-only tier (see the `--cv-*` legend near the
|
|
1089
|
+
top) — exactly what "no signal for this person" is, no signal being the whole point of that tier. */
|
|
1090
|
+
.cv-presence-dot[data-state='disponible'] { background: var(--cv-accent); }
|
|
1091
|
+
.cv-presence-dot[data-state='en_llamada'] { background: var(--cv-hold); }
|
|
1092
|
+
.cv-presence-dot[data-state='desconectado'] { background: var(--cv-faint); }
|
|
1093
|
+
.cv-presence-name {
|
|
1094
|
+
flex: 1;
|
|
1095
|
+
min-width: 0;
|
|
1096
|
+
overflow: hidden;
|
|
1097
|
+
text-overflow: ellipsis;
|
|
1098
|
+
white-space: nowrap;
|
|
1099
|
+
}
|
|
1100
|
+
.cv-presence-meta {
|
|
1101
|
+
flex-shrink: 0;
|
|
1102
|
+
font-size: 0.72rem;
|
|
1103
|
+
color: var(--cv-muted);
|
|
1104
|
+
}
|
|
1105
|
+
/* Spec §4.6: Desconectado is a HINT, never "can't transfer" — this is what tells the agent so, right
|
|
1106
|
+
under the list it describes. `--cv-muted`, NOT `--cv-faint`: this text is informational (the reader
|
|
1107
|
+
needs it to not misread the state), and `--cv-faint` is reserved for decorative-only content that
|
|
1108
|
+
fails AA on purpose — using it here would repeat the exact tier mistake this file's own `--cv-*`
|
|
1109
|
+
comment block warns against. */
|
|
1110
|
+
.cv-presence-legend {
|
|
1111
|
+
margin: 0;
|
|
1112
|
+
font-size: 0.7rem;
|
|
1113
|
+
color: var(--cv-muted);
|
|
1114
|
+
line-height: 1.35;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/*
|
|
1118
|
+
`ConsultScreen`'s "someone is still waiting" banner (`docs/specs/transfer-availability.md` §5, the
|
|
1119
|
+
cross-talk fix) — full-width and tappable, not a passive status line, because the entire reason this
|
|
1120
|
+
screen exists is to make "go back to the caller" impossible to miss. Neutral surface + the same
|
|
1121
|
+
`--cv-hold` colour `.cv-badge-hold` already uses for the same fact elsewhere, not a new soft-fill
|
|
1122
|
+
token: a text/icon tint on a plain surface is already proven legible here, and inventing an
|
|
1123
|
+
unmeasured background-tint pairing risks a real contrast regression this file takes seriously
|
|
1124
|
+
elsewhere (see the `-soft`/`-strong` split's own comments).
|
|
1125
|
+
*/
|
|
1126
|
+
.cv-consult-hold-banner {
|
|
1127
|
+
display: flex;
|
|
1128
|
+
align-items: center;
|
|
1129
|
+
justify-content: center;
|
|
1130
|
+
gap: 0.4rem;
|
|
1131
|
+
width: 100%;
|
|
1132
|
+
padding: 0.5rem 0.75rem;
|
|
1133
|
+
border-radius: 999px;
|
|
1134
|
+
border: 1px solid var(--cv-border);
|
|
1135
|
+
background: var(--cv-surface);
|
|
1136
|
+
color: var(--cv-hold);
|
|
1137
|
+
font: 600 0.78rem var(--cv-font);
|
|
1138
|
+
cursor: pointer;
|
|
1139
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
1140
|
+
}
|
|
1141
|
+
.cv-consult-hold-banner:hover {
|
|
1142
|
+
background: var(--cv-surface-2);
|
|
1143
|
+
border-color: var(--cv-hold);
|
|
1144
|
+
}
|
|
1145
|
+
.cv-consult-hold-banner:active {
|
|
1146
|
+
transform: scale(0.98);
|
|
760
1147
|
}
|
|
761
1148
|
|
|
762
1149
|
/*
|
|
@@ -814,6 +1201,13 @@
|
|
|
814
1201
|
color: var(--cv-text);
|
|
815
1202
|
font: inherit;
|
|
816
1203
|
transition: border-color 0.15s ease, background 0.15s ease;
|
|
1204
|
+
/* Found live (2026-09-29): in the transfer row specifically, `.cv-btn`'s own `max-width: 60%`
|
|
1205
|
+
(needed for the dynamic "Llamar a {typed}" label) can leave the input narrower than its
|
|
1206
|
+
placeholder text — an `<input>` has no native ellipsis, so it hard-clipped mid-word
|
|
1207
|
+
("...o núm") instead of trailing off cleanly. A real, if rare, risk for ANY `.cv-search` usage
|
|
1208
|
+
wherever it's squeezed, not just this one row — the fix belongs on the base rule. */
|
|
1209
|
+
overflow: hidden;
|
|
1210
|
+
text-overflow: ellipsis;
|
|
817
1211
|
}
|
|
818
1212
|
.cv-search::placeholder { color: var(--cv-muted); }
|
|
819
1213
|
.cv-search:focus { outline: none; border-color: var(--cv-accent); background: var(--cv-bg); }
|