@coventia/softphone 0.1.0 → 0.2.1

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.
Files changed (54) hide show
  1. package/dist/{call-BTYlT2SU.d.cts → call-VKq1I8yP.d.cts} +158 -5
  2. package/dist/{call-BTYlT2SU.d.ts → call-VKq1I8yP.d.ts} +158 -5
  3. package/dist/{chunk-N2N2EQQJ.js → chunk-7R6LOOAI.js} +4 -2
  4. package/dist/chunk-7R6LOOAI.js.map +1 -0
  5. package/dist/{chunk-5BGEHMPP.js → chunk-EYEDPD7Y.js} +146 -20
  6. package/dist/chunk-EYEDPD7Y.js.map +1 -0
  7. package/dist/{chunk-3V2L6UTK.js → chunk-LMJXX4YM.js} +17 -3
  8. package/dist/chunk-LMJXX4YM.js.map +1 -0
  9. package/dist/element/index.d.ts +162 -7
  10. package/dist/element/index.js +866 -421
  11. package/dist/element/index.js.map +1 -1
  12. package/dist/{engine-D9nbeY08.d.cts → engine-BlQBo2Ys.d.cts} +1 -1
  13. package/dist/{engine-Doa6Brkm.d.ts → engine-C0BOkIcW.d.ts} +1 -1
  14. package/dist/index.cjs +586 -28
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +5 -5
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.js +335 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/react/index.cjs +15 -0
  21. package/dist/react/index.cjs.map +1 -1
  22. package/dist/react/index.d.cts +14 -3
  23. package/dist/react/index.d.ts +14 -3
  24. package/dist/react/index.js +2 -2
  25. package/dist/{softphone-DhuhcKCy.d.cts → softphone-Cyq3hiIM.d.ts} +17 -2
  26. package/dist/{softphone-DHmhotbf.d.ts → softphone-DlGV12Xp.d.cts} +17 -2
  27. package/dist/softphone.css +477 -35
  28. package/dist/{sources-N4BOEDRL.js → sources-IRKNQPWW.js} +108 -5
  29. package/dist/sources-IRKNQPWW.js.map +1 -0
  30. package/dist/testing/index.cjs +41 -5
  31. package/dist/testing/index.cjs.map +1 -1
  32. package/dist/testing/index.d.cts +8 -2
  33. package/dist/testing/index.d.ts +8 -2
  34. package/dist/testing/index.js +41 -5
  35. package/dist/testing/index.js.map +1 -1
  36. package/dist/ui/index.cjs +846 -410
  37. package/dist/ui/index.cjs.map +1 -1
  38. package/dist/ui/index.d.cts +17 -6
  39. package/dist/ui/index.d.ts +17 -6
  40. package/dist/ui/index.js +878 -456
  41. package/dist/ui/index.js.map +1 -1
  42. package/dist/zadarma/index.cjs +144 -18
  43. package/dist/zadarma/index.cjs.map +1 -1
  44. package/dist/zadarma/index.d.cts +2 -2
  45. package/dist/zadarma/index.d.ts +2 -2
  46. package/dist/zadarma/index.js +1 -1
  47. package/dist/zadarma-engine-L5DEFXUS.js +5 -0
  48. package/dist/{zadarma-engine-DN2SZSQ3.js.map → zadarma-engine-L5DEFXUS.js.map} +1 -1
  49. package/package.json +1 -1
  50. package/dist/chunk-3V2L6UTK.js.map +0 -1
  51. package/dist/chunk-5BGEHMPP.js.map +0 -1
  52. package/dist/chunk-N2N2EQQJ.js.map +0 -1
  53. package/dist/sources-N4BOEDRL.js.map +0 -1
  54. package/dist/zadarma-engine-DN2SZSQ3.js +0 -5
@@ -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
@@ -236,8 +240,6 @@
236
240
  .cv-dial-prefix { padding: 0.7rem 0.6rem 0; }
237
241
  .cv-dial-head { padding: 0.3rem 0.6rem 0.6rem; }
238
242
  .cv-display { font-size: 1.5rem; }
239
- .cv-controls-row { gap: 1.1rem; }
240
-
241
243
  }
242
244
 
243
245
  /* Short hosts. The flex chain already lets the keypad absorb what is left, but below this the keys
@@ -293,6 +295,140 @@
293
295
  .cv-tab:disabled { opacity: 0.35; cursor: not-allowed; }
294
296
  .cv-tab span { font-size: 10px; font-weight: 600; line-height: 1; letter-spacing: 0.01em; }
295
297
 
298
+ /* ── Transfer outcome notices (Phase A — transfer-outcome-relay.md §5.5) ──────── */
299
+ /*
300
+ A persistent, dismissable banner OVER `.cv-body`, never a toast and never auto-dismissed — it carries
301
+ an action, and WCAG 2.2.1 requires a time limit to be removable, adjustable or extended.
302
+
303
+ Overlaps rather than reserving its own height, the same reasoning `.cv-dial-outcome`'s own comment
304
+ already gives for why the widget never lets a line of text resize the keypad under a moving finger —
305
+ here that principle cuts the other way: appearing costs nothing but a repaint, not a reflow, so the
306
+ widget's overall height stays identical whether a notice is showing or not, exactly like `.cv-detail`
307
+ overlaying `.cv-body`'s own list rather than pushing it down. It sits over whatever the current tab
308
+ happens to be showing (most often the dialer's country picker and number field — an acceptable loss:
309
+ the agent is not expected to start a fresh manual dial before dealing with a transfer that just failed
310
+ to connect), which is also why it outranks `.cv-detail` in `z-index`.
311
+
312
+ `[data-has-notices='false']` collapses to zero footprint: the element stays mounted (so its
313
+ `role="status"` region exists before the FIRST notice ever arrives — an aria-live region inserted at
314
+ the same moment as its own content is not reliably announced), but an idle widget, the common case,
315
+ covers nothing and costs no paint either.
316
+ */
317
+ .cv-transfer-notices {
318
+ position: absolute;
319
+ inset: 0 0 auto 0;
320
+ z-index: 3; /* above `.cv-detail`'s 2 — this is the higher-priority thing on screen */
321
+ }
322
+ .cv-transfer-notices[data-has-notices='true'] {
323
+ display: flex;
324
+ flex-direction: column;
325
+ gap: 0.5rem;
326
+ padding: 0.6rem 0.75rem;
327
+ background: var(--cv-bg);
328
+ box-shadow: var(--cv-shadow);
329
+ animation: cv-transfer-notices-in 200ms var(--cv-ease-out) both;
330
+ }
331
+ @keyframes cv-transfer-notices-in {
332
+ from { opacity: 0; transform: translateY(-6px); }
333
+ to { opacity: 1; transform: none; }
334
+ }
335
+ @media (prefers-reduced-motion: reduce) {
336
+ .cv-transfer-notices[data-has-notices='true'] { animation: none; }
337
+ }
338
+ .cv-transfer-notices-count {
339
+ margin: 0;
340
+ font: 600 0.72rem var(--cv-font);
341
+ color: var(--cv-muted);
342
+ text-transform: uppercase;
343
+ letter-spacing: 0.04em;
344
+ }
345
+ .cv-transfer-notice {
346
+ display: flex;
347
+ align-items: flex-start;
348
+ gap: 0.6rem;
349
+ padding: 0.65rem 0.7rem;
350
+ border-radius: var(--cv-radius-key);
351
+ border: 1px solid var(--cv-danger-soft);
352
+ background: var(--cv-surface);
353
+ }
354
+ /* Same shape as `.cv-gate-mark`, smaller — one visual language for "here is a status", not two. */
355
+ .cv-transfer-notice-mark {
356
+ flex-shrink: 0;
357
+ display: grid;
358
+ place-items: center;
359
+ width: 30px;
360
+ height: 30px;
361
+ border-radius: 999px;
362
+ background: var(--cv-danger-soft);
363
+ color: var(--cv-danger);
364
+ }
365
+ .cv-transfer-notice-body { flex: 1; min-width: 0; }
366
+ /*
367
+ Title shares its row with only the dismiss button (24px) — not the whole card's width, and not the
368
+ "Llamar de vuelta" button, which gets its own row below (`.cv-transfer-notice-footer`). The original
369
+ layout put mark + title + subtitle + a full button all in one flex row, leaving the title as little as
370
+ ~80px to wrap into at the real ~340px embed width — nearly every word landed on its own line.
371
+ `align-items: flex-start` keeps the dismiss pinned to the first line even when the title wraps to two.
372
+ */
373
+ .cv-transfer-notice-header { display: flex; align-items: flex-start; gap: 0.4rem; }
374
+ .cv-transfer-notice-title {
375
+ flex: 1;
376
+ min-width: 0;
377
+ margin: 0;
378
+ font: 600 0.82rem var(--cv-font);
379
+ color: var(--cv-text);
380
+ line-height: 1.35;
381
+ }
382
+ .cv-transfer-notice-subtitle {
383
+ margin: 0.15rem 0 0;
384
+ font-size: 0.78rem;
385
+ color: var(--cv-muted);
386
+ line-height: 1.35;
387
+ }
388
+ /* Age and the callback button share a row too, same reasoning as the header — a short fixed-width
389
+ timestamp beside a content-sized button, neither one fighting the title for space above. */
390
+ .cv-transfer-notice-footer {
391
+ display: flex;
392
+ align-items: center;
393
+ justify-content: space-between;
394
+ gap: 0.5rem;
395
+ margin-top: 0.35rem;
396
+ }
397
+ .cv-transfer-notice-age { margin: 0; font-size: 0.7rem; color: var(--cv-faint); flex-shrink: 0; }
398
+ /*
399
+ `.cv-btn.cv-transfer-notice-callback` — TWO classes, not one. Equal-specificity selectors resolve by
400
+ source order, and this rule used to lose its `padding`/`font-size` to the shared `.cv-btn` rule below
401
+ simply because that one appears later in the file; the button rendered at full default CTA size
402
+ instead of this smaller, secondary one, which is what actually forced the title into that ~80px
403
+ column above. The extra class is what makes this override win regardless of source order.
404
+ */
405
+ .cv-btn.cv-transfer-notice-callback {
406
+ flex-shrink: 0;
407
+ padding: 0.35rem 0.6rem;
408
+ font-size: 0.75rem;
409
+ display: inline-flex;
410
+ align-items: center;
411
+ gap: 0.3rem;
412
+ white-space: nowrap;
413
+ }
414
+ /* Same round icon-only shape as `.cv-search-clear` — one dismiss affordance, not a new one. */
415
+ .cv-transfer-notice-dismiss {
416
+ flex-shrink: 0;
417
+ width: 24px;
418
+ height: 24px;
419
+ display: inline-flex;
420
+ align-items: center;
421
+ justify-content: center;
422
+ border: none;
423
+ border-radius: 999px;
424
+ background: none;
425
+ color: var(--cv-muted);
426
+ cursor: pointer;
427
+ transition: color 0.12s ease, background 0.12s ease, transform 0.1s var(--cv-ease-out);
428
+ }
429
+ .cv-transfer-notice-dismiss:hover { color: var(--cv-text); background: var(--cv-surface-2); }
430
+ .cv-transfer-notice-dismiss:active { transform: scale(0.88); }
431
+
296
432
  /* ── Buttons ─────────────────────────────────────────────────────────────────── */
297
433
  .cv-btn {
298
434
  font: 600 0.875rem var(--cv-font);
@@ -494,6 +630,16 @@
494
630
  gap: 0.15rem;
495
631
  text-align: center;
496
632
  padding: 1rem;
633
+ /*
634
+ A REAL bug, found live (plan T7): the transfer picker's agent list can make this box's content
635
+ taller than the space `flex: 1` actually allocates it — the avatar/name/timer/status ABOVE the
636
+ picker are still shown while transferring, unlike `.cv-call-devices-inline`'s own "swap the
637
+ content" fix for the pre-connect devices screen. Without this, the overflow painted straight past
638
+ `.cv-call`'s own shrunk box and onto `.cv-controls` below it — not scroll, actual overlap, controls
639
+ rendered ON TOP of list rows. `overflow-y: auto` keeps any excess inside THIS box, scrollable,
640
+ exactly like `.cv-presence-list`'s own internal scroll already does for the list itself.
641
+ */
642
+ overflow-y: auto;
497
643
  }
498
644
 
499
645
  .cv-avatar-wrap { position: relative; margin-bottom: 0.85rem; }
@@ -572,7 +718,7 @@
572
718
  }
573
719
  .cv-badge-hold { color: var(--cv-hold); font-weight: 600; }
574
720
 
575
- /* ── Pre-connect device check (ringing / dialing) ──────────────────────────────
721
+ /* ── Device check (ringing / dialing / in-call) ────────────────────────────────
576
722
  Before a call connects there is only ONE other control on screen (Colgar, or
577
723
  Rechazar/Contestar) — putting Dispositivos in that same neighbourhood, even
578
724
  spaced out, keeps it a mis-tap away from hanging up or answering. The entry
@@ -588,6 +734,11 @@
588
734
  cramped and scrolly for just two rows. `.cv-call-devices-inline` REPLACES the
589
735
  avatar/name/status block in `.cv-call` instead (see that JSX's own comment) —
590
736
  an inline swap, not an overlay, so it gets the full box, not a leftover sliver.
737
+
738
+ Once connected it is the SAME chip and the SAME swap. Dispositivos used to be a fifth control in
739
+ the call row (with a floating popover), which is what forced that row down to 40px buttons and
740
+ 9px labels in a 310px host. A set-once setting does not need a slot among the four things you do
741
+ mid-call (mute, hold, transfer, keypad), and the control row stays live under the swap.
591
742
  ───────────────────────────────────────────────────────────────────────────── */
592
743
  .cv-call-devices-chip {
593
744
  display: inline-flex;
@@ -660,16 +811,22 @@
660
811
  display: flex;
661
812
  flex-direction: column;
662
813
  align-items: center;
663
- gap: 0.7rem;
814
+ gap: 0.9rem;
664
815
  padding: 0.35rem 1rem 1.1rem;
665
- /* The containing block for `.cv-device-popover` — see that rule's own comment for why it is
816
+ /* The containing block for `.cv-keypad-popover` — see that rule's own comment for why it is
666
817
  anchored here rather than to the trigger button. */
667
818
  position: relative;
668
819
  }
820
+ /* FOUR controls, sized so they never need to shrink to fit. This row held five (Dispositivos too) and
821
+ the only way to fit five in the CRM's 310px host was a 40px button with a 9px label and ~6px gaps —
822
+ below the 44/48px a thumb needs, and below legible for a word read mid-call. The default 48px size
823
+ below was never actually seen: it only applied above a 360px host, which no real embed is.
824
+ Dispositivos is the `.cv-call-chips` chip up by the call info instead (the same entry point the
825
+ ringing and dialing screens already use). 4 × 52 + 3 × 20 = 268px, inside 310 - 2 × 16 padding. */
669
826
  .cv-controls-row {
670
827
  display: flex;
671
828
  justify-content: center;
672
- gap: 1.5rem;
829
+ gap: 1.25rem;
673
830
  }
674
831
  .cv-controls-primary {
675
832
  display: flex;
@@ -692,9 +849,12 @@
692
849
  white-space: nowrap;
693
850
  user-select: none;
694
851
  }
852
+ /* The four secondaries' words get a notch more than Colgar / Rechazar / Contestar's 10px — they are
853
+ the ones read mid-call, and the five-up row used to shrink them to 9px (8px in a sidebar). */
854
+ .cv-controls-row .cv-ctrl-label { font-size: 0.6875rem; }
695
855
  .cv-ctrl {
696
- width: 48px;
697
- height: 48px;
856
+ width: 52px;
857
+ height: 52px;
698
858
  border-radius: 999px;
699
859
  border: 1px solid var(--cv-border-strong);
700
860
  background: var(--cv-bg);
@@ -707,6 +867,11 @@
707
867
  }
708
868
  .cv-ctrl:hover { background: var(--cv-surface); }
709
869
  .cv-ctrl:active { transform: scale(0.94); }
870
+ .cv-ctrl:disabled {
871
+ opacity: 0.4;
872
+ cursor: not-allowed;
873
+ pointer-events: none;
874
+ }
710
875
 
711
876
  /* The ON states say what they MEAN, in the widget's own tokens: muted is danger,
712
877
  held is hold, transfer-open is accent. Colour is the state, not decoration. */
@@ -752,56 +917,326 @@
752
917
  border-color: var(--cv-text);
753
918
  }
754
919
 
920
+ /*
921
+ Sidebar-narrow (this file's own ≤300px tier, up in "Narrow hosts"). The four 52px controls with
922
+ 20px gaps need 268px, which a 260px host cannot give — so this tier steps down to 44px with 12px
923
+ gaps (4 × 44 + 3 × 12 = 212px) and reclaims `.cv-controls`'s own horizontal padding, the first
924
+ lever that block's own comment names. 44px is still the Apple HIG minimum tap target; the five-up
925
+ row this replaced went to 36px here.
926
+
927
+ Placed AFTER `.cv-controls`/`.cv-controls-row` above rather than up with this file's other
928
+ `@container` blocks near the top: `gap` and `padding-left`/`-right` here have the same specificity as
929
+ those base rules' own shorthand, so — unlike a compound selector such as `.cv-controls-row .cv-ctrl`,
930
+ which wins on specificity regardless of position — source order is what decides, and a narrower-width
931
+ override declared BEFORE the base rule it means to narrow loses to it. Confirmed live: moved up into
932
+ the "Narrow hosts" section, the `.cv-ctrl` width and label font-size reductions took effect (compound
933
+ selectors) but `gap` and `.cv-controls`'s padding silently did not (plain selectors, later base rule won).
934
+ */
935
+ @container cv (max-width: 300px) {
936
+ .cv-controls { padding-left: 0.5rem; padding-right: 0.5rem; }
937
+ .cv-controls-row { gap: 0.75rem; }
938
+ .cv-controls-row .cv-ctrl { width: 44px; height: 44px; }
939
+ .cv-controls-row .cv-ctrl-label { font-size: 0.625rem; }
940
+ }
941
+
942
+ /*
943
+ The attended-transfer target-entry panel (`docs/specs/transfer-availability.md` §5): an optional
944
+ outcome line, then a row of whatever's actionable (input+button today). Once a consult exists,
945
+ `ConsultScreen` takes over the whole screen instead — this panel is ONLY ever the pre-consult /
946
+ retry-after-failure state now; it used to also cover the consulting/answered phases, which is why
947
+ its rule is a column container built for more than one child even though there's only ever one row
948
+ here today. (History: was two near-duplicate rules — `.cv-transfer` as a bare ROW,
949
+ `.cv-transfer-consult` as a column — until the row version turned out to be a real,
950
+ reproduced-in-browser bug: the optional `.cv-dial-outcome` message rendered as a THIRD item in the
951
+ same horizontal row as the input and button, squeezing the input to its content-minimum. Merged
952
+ rather than patched.)
953
+ */
755
954
  .cv-transfer {
955
+ display: flex;
956
+ flex-direction: column;
957
+ gap: 0.3rem;
958
+ width: 100%;
959
+ padding: 0.2rem 0.25rem 0;
960
+ /* `.cv-call` centres and centre-aligns its normal (avatar) content — same reasoning
961
+ `.cv-call-devices-inline` already opts out of for the OTHER inline swap in this box (its own
962
+ comment): rows read left-aligned everywhere else in the widget, and a centred context line
963
+ sitting directly above a left-aligned input/toggle/list read as two layouts stacked on top of
964
+ each other, found live (2026-09-29). */
965
+ text-align: left;
966
+ /* Found live (2026-09-29): `.cv-call`'s avatar/name/timer block used to sit ABOVE this,
967
+ unconditionally, so this panel only ever got whatever was left over — see the JSX call site's own
968
+ comment. Now that this IS the whole of `.cv-call`'s content while a transfer is in progress
969
+ (a swap, not an addition), it should CLAIM `.cv-call`'s available height, not just sit in it —
970
+ `flex: 1` does that; `min-height: 0` is what lets a flex child actually shrink to fit instead of
971
+ forcing its parent to grow past it (the same reason `.cv-call`/`.cv-view`/`.cv-body` all set it,
972
+ see their own comments). This is what lets `.cv-presence-list` below flex-grow into real room
973
+ instead of needing its own hand-tuned `max-height` guess. */
974
+ flex: 1;
975
+ min-height: 0;
976
+ /* Same entrance `.cv-call-devices-inline` already uses for the analogous inline swap (its own
977
+ comment) — reused, not a second animation invented for this widget's second inline-swap view. */
978
+ animation: cv-call-devices-in 0.18s cubic-bezier(0.2, 0, 0.2, 1);
979
+ }
980
+ @media (prefers-reduced-motion: reduce) {
981
+ .cv-transfer { animation: none; }
982
+ }
983
+ /* The compact "who am I still on the phone with" line while the picker is open — reuses
984
+ `.cv-call-devices-inline`'s own two classes verbatim (`.cv-call-devices-title` for the name,
985
+ `.cv-call-devices-context` for the number/timer line) rather than inventing a second "quiet header"
986
+ style for the same widget; only the tiny stacking gap between them is new.
987
+
988
+ Found live (2026-09-29): `.cv-transfer`'s own `gap` alone gave every section — context, input row,
989
+ mode toggle, results list — the exact same spacing, so nothing read as its own group; the eye had
990
+ no cue for where "who I'm on the phone with" ends and "pick a transfer target" begins. This extra
991
+ `margin-bottom`, on top of the base `gap`, is that cue — the toggle stays close to the list it
992
+ controls (unchanged), only the boundary above the interactive controls opened up. */
993
+ .cv-transfer-context {
994
+ display: flex;
995
+ flex-direction: column;
996
+ gap: 2px;
997
+ flex-shrink: 0;
998
+ margin-bottom: 0.35rem;
999
+ }
1000
+ /* Plan T3: the mode toggle reuses `.cv-segmented` (HistoryTab's own filter control) as-is, but its
1001
+ `margin-bottom` (needed THERE, where the surrounding layout has no gap of its own) is redundant
1002
+ here — `.cv-transfer`'s own `gap` above already spaces every child, so the two would stack. Scoped
1003
+ to this context only; HistoryTab's own copy is untouched. */
1004
+ .cv-transfer .cv-segmented { margin-bottom: 0; flex-shrink: 0; }
1005
+ .cv-transfer .cv-presence-legend { flex-shrink: 0; }
1006
+ /* Same treatment as `.cv-call-status` (the avatar's own "Llamando…"/"En espera" line) — one muted
1007
+ status-line vocabulary for the whole call screen, not a second one invented for this panel. */
1008
+ .cv-transfer-status {
1009
+ margin: 0;
1010
+ font-size: 0.78rem;
1011
+ color: var(--cv-muted);
1012
+ letter-spacing: 0.04em;
1013
+ }
1014
+ /* The target-entry/retry row: an input plus one button. */
1015
+ .cv-transfer-row {
1016
+ display: flex;
1017
+ gap: 0.5rem;
1018
+ }
1019
+ /*
1020
+ Flex items default to `min-width: auto`, which floors them at their CONTENT size rather than letting
1021
+ them shrink or grow to fill the row — `.cv-search`'s own `width: 100%` never actually took effect
1022
+ here, because `.cv-btn`'s text ("Llamar") set an unshrinkable floor and the input, having no
1023
+ `flex-grow`, never reclaimed the space left over. `flex: 1` + `min-width: 0` is what actually makes
1024
+ an input-plus-button row behave like one; `flex: none` keeps the button at its natural size.
1025
+ */
1026
+ .cv-transfer-row .cv-search {
1027
+ flex: 1;
1028
+ min-width: 0;
1029
+ }
1030
+ /* `max-width` + ellipsis: the label now includes whatever was typed ("Llamar a {typed}", spec §4.5)
1031
+ and `truncateForButton` already bounds that in JS — this is the CSS backstop, so a value that
1032
+ slips past it (a very wide character set, a future copy change) still can't push `.cv-search` down
1033
+ to an unusable sliver the way the pre-truncation button did before this was noticed. */
1034
+ .cv-transfer-row .cv-btn {
1035
+ flex: none;
1036
+ max-width: 60%;
1037
+ overflow: hidden;
1038
+ text-overflow: ellipsis;
1039
+ white-space: nowrap;
1040
+ }
1041
+ /* The consult-answered row: one or two buttons, no input. */
1042
+ .cv-transfer-actions {
756
1043
  display: flex;
757
1044
  gap: 0.5rem;
1045
+ }
1046
+ .cv-transfer-actions .cv-btn {
1047
+ flex: 1;
1048
+ }
1049
+ /* The ringing phase has only "Cancelar" — a secondary, low-stakes action. Letting the row's own
1050
+ `flex: 1` stretch a LONE button edge to edge reads as oversized for what it is, and nothing else in
1051
+ this widget sizes a single button to the full row; every other `.cv-btn` sizes to its content. */
1052
+ .cv-transfer-actions .cv-btn:only-child {
1053
+ flex: none;
1054
+ }
1055
+
1056
+ /*
1057
+ The agent picker (spec §4.5, plan T7) — an inline, always-visible filtered list under the
1058
+ input+button row, not a click-to-open popover like `.cv-person-filter-pop`: the whole point is that
1059
+ typing already filters it, so hiding it behind a second interaction would undo that.
1060
+
1061
+ Redesigned live (2026-09-29): T7 (72px) and T3 (30px) both hand-tuned a `max-height` in PIXELS
1062
+ against whatever else happened to be on screen at the time, and both were broken by the very next
1063
+ addition above the list (T3's own toggle broke T7's number the same way this would keep breaking on
1064
+ the next change) — down to "barely proves the list is scrollable" at 30px, which is what the user
1065
+ was actually seeing: "users cannot almost see any of the listed users." The real fix wasn't a bigger
1066
+ magic number, it was `.cv-transfer`'s own move to `flex: 1` (see its comment) freeing real height
1067
+ from the block that used to sit above this whole panel — `.cv-presence` and this list now flex-grow
1068
+ into whatever's ACTUALLY left after the context line, input row, toggle and legend take their own
1069
+ natural size, on whatever real height the host panel gives `.cv-call` (this SDK's fixed ~310×520
1070
+ embed today, but also whatever a different host gives it tomorrow — no more re-tuning a constant
1071
+ every time something above the list changes). `.cv-call`'s own `overflow-y: auto` (its own comment)
1072
+ is still the last-resort net if content ever genuinely doesn't fit; this just means that net should
1073
+ now rarely, if ever, have to catch anything for a normal agent list.
1074
+ */
1075
+ .cv-presence {
1076
+ display: flex;
1077
+ flex-direction: column;
1078
+ gap: 0.35rem;
1079
+ flex: 1;
1080
+ min-height: 0;
1081
+ }
1082
+ .cv-presence-list {
1083
+ display: flex;
1084
+ flex-direction: column;
1085
+ gap: 2px;
1086
+ flex: 1;
1087
+ min-height: 0;
1088
+ overflow-y: auto;
1089
+ padding: 0.25rem;
1090
+ border: 1px solid var(--cv-border);
1091
+ border-radius: var(--cv-radius-key);
1092
+ background: var(--cv-surface);
1093
+ }
1094
+ .cv-presence-empty {
1095
+ margin: 0;
1096
+ padding: 0.5rem;
1097
+ text-align: center;
1098
+ font-size: 0.78rem;
1099
+ color: var(--cv-muted);
1100
+ }
1101
+ .cv-presence-item {
758
1102
  width: 100%;
759
- padding: 0.85rem 0.25rem 0;
1103
+ display: flex;
1104
+ align-items: center;
1105
+ gap: 0.5rem;
1106
+ padding: 0.4rem 0.5rem;
1107
+ border: none;
1108
+ border-radius: calc(var(--cv-radius) - 4px);
1109
+ background: none;
1110
+ color: var(--cv-text);
1111
+ font: 400 0.8rem var(--cv-font);
1112
+ text-align: left;
1113
+ cursor: pointer;
1114
+ }
1115
+ .cv-presence-item:hover:not(:disabled) { background: var(--cv-surface-2); }
1116
+ /* En llamada rows are a hard block, not just a hint (CallScreen.tsx's own comment on the row has why)
1117
+ — same `:disabled` treatment `.cv-btn`/`.cv-tab` already use elsewhere in this file, not a new pattern. */
1118
+ .cv-presence-item:disabled { opacity: 0.45; cursor: not-allowed; }
1119
+ .cv-presence-dot {
1120
+ flex-shrink: 0;
1121
+ width: 8px;
1122
+ height: 8px;
1123
+ border-radius: 999px;
1124
+ }
1125
+ /* Reuses existing intent tokens rather than inventing new ones — `--cv-accent` already means
1126
+ "good, reachable" (the answer button), `--cv-hold` already means "occupied elsewhere" (the hold
1127
+ toggle), and `--cv-faint` is this file's own DECORATIVE-only tier (see the `--cv-*` legend near the
1128
+ top) — exactly what "no signal for this person" is, no signal being the whole point of that tier. */
1129
+ .cv-presence-dot[data-state='disponible'] { background: var(--cv-accent); }
1130
+ .cv-presence-dot[data-state='en_llamada'] { background: var(--cv-hold); }
1131
+ .cv-presence-dot[data-state='desconectado'] { background: var(--cv-faint); }
1132
+ .cv-presence-name {
1133
+ flex: 1;
1134
+ min-width: 0;
1135
+ overflow: hidden;
1136
+ text-overflow: ellipsis;
1137
+ white-space: nowrap;
1138
+ }
1139
+ .cv-presence-meta {
1140
+ flex-shrink: 0;
1141
+ font-size: 0.72rem;
1142
+ color: var(--cv-muted);
1143
+ }
1144
+ /* Spec §4.6: Desconectado is a HINT, never "can't transfer" — this is what tells the agent so, right
1145
+ under the list it describes. `--cv-muted`, NOT `--cv-faint`: this text is informational (the reader
1146
+ needs it to not misread the state), and `--cv-faint` is reserved for decorative-only content that
1147
+ fails AA on purpose — using it here would repeat the exact tier mistake this file's own `--cv-*`
1148
+ comment block warns against. */
1149
+ .cv-presence-legend {
1150
+ margin: 0;
1151
+ font-size: 0.7rem;
1152
+ color: var(--cv-muted);
1153
+ line-height: 1.35;
760
1154
  }
761
1155
 
762
1156
  /*
763
- The in-call device quick-switch (SPEC §7 Phase 2 — device management). Same rows as the Ajustes tab
764
- (`.cv-settings-row`, via the shared `DevicePickers`), in a popover instead of a page — the call
765
- screen already takes over the whole body, so there is no tab bar to reach Ajustes from mid-call.
766
-
767
- Only the in-call state still uses a floating popover — before a call connects, Dispositivos is
768
- inline content instead (`.cv-call-devices-inline`, above), not an overlay. Opens UPWARD (`bottom:
769
- 100%`): the trigger sits at the bottom of the call screen, and there is no room to open downward.
770
- Anchored to `.cv-controls`'s own horizontal padding — a container whose width is bounded by ITS
771
- parent, never the narrow trigger itself — the same fix CountryPicker's own popover needed (see its
772
- comment).
1157
+ `ConsultScreen`'s "someone is still waiting" banner (`docs/specs/transfer-availability.md` §5, the
1158
+ cross-talk fix) — full-width and tappable, not a passive status line, because the entire reason this
1159
+ screen exists is to make "go back to the caller" impossible to miss. Neutral surface + the same
1160
+ `--cv-hold` colour `.cv-badge-hold` already uses for the same fact elsewhere, not a new soft-fill
1161
+ token: a text/icon tint on a plain surface is already proven legible here, and inventing an
1162
+ unmeasured background-tint pairing risks a real contrast regression this file takes seriously
1163
+ elsewhere (see the `-soft`/`-strong` split's own comments).
773
1164
  */
774
- .cv-device-popover {
1165
+ .cv-consult-hold-banner {
1166
+ display: flex;
1167
+ align-items: center;
1168
+ justify-content: center;
1169
+ gap: 0.4rem;
1170
+ width: 100%;
1171
+ padding: 0.5rem 0.75rem;
1172
+ border-radius: 999px;
1173
+ border: 1px solid var(--cv-border);
1174
+ background: var(--cv-surface);
1175
+ color: var(--cv-hold);
1176
+ font: 600 0.78rem var(--cv-font);
1177
+ cursor: pointer;
1178
+ transition: background 0.15s ease, border-color 0.15s ease;
1179
+ }
1180
+ .cv-consult-hold-banner:hover {
1181
+ background: var(--cv-surface-2);
1182
+ border-color: var(--cv-hold);
1183
+ }
1184
+ .cv-consult-hold-banner:active {
1185
+ transform: scale(0.98);
1186
+ }
1187
+
1188
+ @keyframes cv-pop-in-up {
1189
+ from { opacity: 0; transform: translateY(4px) scale(0.97); }
1190
+ to { opacity: 1; transform: none; }
1191
+ }
1192
+
1193
+ /*
1194
+ The in-call DTMF keypad — the one floating popover left on the call screen. (Dispositivos was the
1195
+ other; it is inline content now, `.cv-call-devices-inline` above, in every call state.)
1196
+
1197
+ Opens UPWARD (`bottom: 100%`): the trigger sits at the bottom of the call screen, and there is no
1198
+ room to open downward. Anchored to `.cv-controls`'s own horizontal padding — a container whose width
1199
+ is bounded by ITS parent, never the narrow trigger itself — the same fix CountryPicker's own popover
1200
+ needed (see its comment): anchoring to a narrow, off-centre trigger let the popover's edge reach
1201
+ `.cv-softphone`'s `overflow: hidden` and get clipped.
1202
+ */
1203
+ .cv-keypad-popover {
775
1204
  position: absolute;
776
1205
  z-index: 20;
777
- display: flex;
778
- flex-direction: column;
779
- gap: 0.6rem;
780
- padding: 0.7rem;
1206
+ display: grid;
1207
+ grid-template-columns: repeat(3, 1fr);
1208
+ gap: 8px;
1209
+ padding: 0.85rem 1rem;
781
1210
  background: var(--cv-surface);
782
1211
  border: 1px solid var(--cv-border);
783
1212
  border-radius: var(--cv-radius);
784
1213
  box-shadow: var(--cv-shadow);
785
1214
  }
786
- .cv-device-popover--above {
1215
+ .cv-keypad-popover--above {
787
1216
  bottom: 100%;
788
1217
  left: 1rem;
789
1218
  right: 1rem;
790
1219
  margin-bottom: 0.6rem;
791
1220
  transform-origin: bottom center;
792
- animation: cv-device-pop-in-up 0.14s cubic-bezier(0.2, 0, 0.2, 1);
793
- }
794
- @keyframes cv-device-pop-in-up {
795
- from { opacity: 0; transform: translateY(4px) scale(0.97); }
796
- to { opacity: 1; transform: none; }
1221
+ animation: cv-pop-in-up 0.14s cubic-bezier(0.2, 0, 0.2, 1);
1222
+ }
1223
+ /* `.cv-key` already carries the full key look (border, hover, active-scale, digit/letter type) from
1224
+ the pre-call dialer — only the size needs setting here, since this grid has no flex ancestor to
1225
+ stretch the rows the way `.cv-keypad` gets to.
1226
+
1227
+ A fixed HEIGHT, not an `aspect-ratio`. This popover opens upward from `.cv-controls`, so it has
1228
+ (host height - controls block - margin) to work with, and a ratio ties its height to the host's
1229
+ WIDTH, which says nothing about how much height there is. Found live: the CRM's real body is 310×473
1230
+ (a 520px panel minus its 45px header), and a 1.3 ratio made this popover 285px — with the 184px
1231
+ controls block that is 479px, so its top ~7px were clipped by `.cv-softphone`'s `overflow: hidden`.
1232
+ 48px keys give a 243px popover at ANY width, fit that host with room to spare, and stay at the
1233
+ 44-48px minimum tap target (the old ratio gave 45px keys at a 260px host, 58px at 310). */
1234
+ .cv-keypad-popover .cv-key {
1235
+ height: 48px;
797
1236
  }
798
1237
  @media (prefers-reduced-motion: reduce) {
799
- .cv-device-popover--above { animation: none; }
1238
+ .cv-keypad-popover--above { animation: none; }
800
1239
  }
801
- /* No per-row override needed here: `.cv-settings-row`/`.cv-settings-note` carry no border,
802
- background or padding of their own (see the Settings tab block) — this popover's own `gap` and
803
- `padding` are the only spacing in play, which is what keeps it lighter than the full Ajustes
804
- page's bordered section for what is meant to be a quick, transient panel. */
805
1240
 
806
1241
  /* ── Lists (contacts / history) ──────────────────────────────────────────────── */
807
1242
  .cv-list-head { flex-shrink: 0; padding: 0.85rem 1rem 0.5rem; }
@@ -814,6 +1249,13 @@
814
1249
  color: var(--cv-text);
815
1250
  font: inherit;
816
1251
  transition: border-color 0.15s ease, background 0.15s ease;
1252
+ /* Found live (2026-09-29): in the transfer row specifically, `.cv-btn`'s own `max-width: 60%`
1253
+ (needed for the dynamic "Llamar a {typed}" label) can leave the input narrower than its
1254
+ placeholder text — an `<input>` has no native ellipsis, so it hard-clipped mid-word
1255
+ ("...o núm") instead of trailing off cleanly. A real, if rare, risk for ANY `.cv-search` usage
1256
+ wherever it's squeezed, not just this one row — the fix belongs on the base rule. */
1257
+ overflow: hidden;
1258
+ text-overflow: ellipsis;
817
1259
  }
818
1260
  .cv-search::placeholder { color: var(--cv-muted); }
819
1261
  .cv-search:focus { outline: none; border-color: var(--cv-accent); background: var(--cv-bg); }