@redsift/products 12.1.1-muiv6 → 12.2.0-muiv5

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/index2.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { _ as _objectSpread2, a as _objectWithoutProperties, b as _extends } from './_internal/_rollupPluginBabelHelpers.js';
2
2
  import React__default, { useMemo, useContext, createContext, forwardRef, useCallback, Component, useState, useEffect, useId, useRef, Suspense, memo } from 'react';
3
- import { mdiCheck, mdiAlert, mdiClose, mdiInformation, mdiHelpCircle, mdiEmail, mdiEarth, mdiArrowDownBold } from '@redsift/icons';
3
+ import { mdiCheck, mdiAlert, mdiClose, mdiInformation, mdiHelpCircle, mdiEmail, mdiEarth, mdiInformationOutline, mdiArrowDownBold } from '@redsift/icons';
4
4
  import styled, { css } from 'styled-components';
5
5
  import { useLocalizedStringFormatter, useTheme, Button, Icon, DetailedCard, Flexbox, Link, TextField } from '@redsift/design-system';
6
6
  import classNames from 'classnames';
@@ -562,6 +562,7 @@ var enUS = {
562
562
  "investigate.card.bimi.record-passed_md": "**[](BIMI_RECORD#:glossary)** **Present and compliant**",
563
563
  "investigate.card.bimi.record-declination_md": "**[](BIMI_RECORD#:glossary)** Present and containing a **valid declination to publish**",
564
564
  "investigate.card.bimi.record-no-cert_md": "**[](BIMI_RECORD#:glossary)** **Certificate not found**",
565
+ "investigate.card.bimi.cert-forbidden_md": "Your certificate appears to be behind an anti-bot protection system. [Find out more and how to resolve the issue](https://knowledge.ondmarc.redsift.com/en/articles/8047013-ondmarc-cannot-fetch-my-bimi-logo-or-certificate)",
565
566
  "investigate.card.bimi.record-failed_md": "**[](BIMI_RECORD#:glossary)** **Present** but with errors",
566
567
  "investigate.card.bimi.record-missing_md": "**[](BIMI_RECORD#:glossary)** **Missing.** [Learn how to fix it](https://community.redsift.com/s/article/000001130)",
567
568
  "investigate.card.bimi.could-not-fetch_md": "Failed to fetch **[](BIMI_RECORD#:glossary)**",
@@ -575,6 +576,7 @@ var enUS = {
575
576
  "investigate.card.bimi.vmc.expiry-date_md": "**Expires on:** {date} ({daysLeft} days left)",
576
577
  "investigate.card.bimi.vmc.expired-date_md": "Expired on {date}",
577
578
  "investigate.card.bimi.vmc.trademarkAuthority_md": "**Trademark Authority: {trademarkAuthority}**",
579
+ "investigate.card.bimi.vmc.assertionMark_md": "**Assertion Mark:** {assertionMark}",
578
580
  "investigate.card.bimi.logoErrors.invalid-svg_md": "**[](VMC#:glossary)** Invalid SVG. Logo from certificate cannot be converted to a valid SVG.",
579
581
  "investigate.card.bimi.logoErrors.invalid-svg-version_md": "**[](VMC#:glossary)** Invalid SVG version. Should be **1.2**",
580
582
  "investigate.card.bimi.logoErrors.invalid-svg-profile_md": "**[](VMC#:glossary)** Invalid SVG profile. Should be **tiny-ps**",
@@ -595,10 +597,11 @@ var enUS = {
595
597
  "investigate.card.bimi.logoErrors.logo-and-evidence-mismatch_md": "**[](VMC#:glossary)** Logo on l record and on certificate do not match",
596
598
  "investigate.card.bimi.logoErrors.logo-extract-error_md": "**[](VMC#:glossary)** Error extracting logo",
597
599
  "investigate.card.bimi.logoErrors.unknown-svg-error_md": "**[](VMC#:glossary)** Unknown SVG error",
600
+ "investigate.card.bimi.svg-error-fallback_md": "**[](VMC#:glossary)** {error}",
598
601
  "investigate.card.bimi.pemErrors.cert-not-found_md": "**[](VMC#:glossary)** Certificate not found",
599
602
  "investigate.card.bimi.pemErrors.cert-not-url_md": "**[](VMC#:glossary)** Certificate is not an url",
600
603
  "investigate.card.bimi.pemErrors.cert-not-file_md": "**[](VMC#:glossary)** Certificate is not a file",
601
- "investigate.card.bimi.pemErrors.cert-not-https_md": "**[](VMC#:glossary)** Certificate has not an HTTPS protocol",
604
+ "investigate.card.bimi.pemErrors.cert-not-https_md": "**[](VMC#:glossary)** Certificate does not use the HTTPS protocol",
602
605
  "investigate.card.bimi.pemErrors.cert-not-pem_md": "**[](VMC#:glossary)** Certificate is not a PEM file",
603
606
  "investigate.card.bimi.pemErrors.cert-not-valid_md": "**[](VMC#:glossary)** Certificate is not valid",
604
607
  "investigate.card.bimi.pemErrors.cert-bad-date_md": "**[](VMC#:glossary)** Certificate has expired",
@@ -704,6 +707,7 @@ var enUS = {
704
707
  "It is a protocol that was built to enforce **the existing SPF and DKIM protocols**."
705
708
  ],
706
709
  "investigate.signal-description.DMARC_DOMAIN_md_LONG": [
710
+ "",
707
711
  "DMARC does a few things:",
708
712
  "",
709
713
  "1. It takes into account the results from SPF and DKIM.",
@@ -718,6 +722,7 @@ var enUS = {
718
722
  "It is a protocol that was built to enforce **the existing SPF and DKIM protocols**."
719
723
  ],
720
724
  "investigate.signal-description.DMARC_md_LONG": [
725
+ "",
721
726
  "DMARC does a few things:",
722
727
  "",
723
728
  "1. It takes into account the results from SPF and DKIM.",
@@ -728,6 +733,7 @@ var enUS = {
728
733
  "investigate.signal-description.DMARC_md_INTRO": null,
729
734
  "investigate.signal-description.DKIM_md_SHORT": "**DKIM** stands for *DomainKeys Identified Mail*. It is used to sign different header fields and the body of an email in order to authenticate the sending domain and prevent message modification during transit.",
730
735
  "investigate.signal-description.DKIM_md_LONG": [
736
+ "",
731
737
  "It achieves this by using **asymmetric cryptography** which consists of public and private keys. The private key is private to the sender’s domain and used to sign the emails. The public key is published in the sender’s DNS so it can be retrieved by anyone receiving messages from the sender.",
732
738
  "",
733
739
  "In essence, when an email is composed, its headers and body are signed using the private key of the sender to create a digital signature, which is also sent as a header field along with the email. On the receiver’s side (if DKIM enabled), the server retrieves the public key and verifies if the email was indeed signed by the sending domain. If the signature is successfully validated that proves that the sending domain sent the message and also that the headers and body of the message have not been modified during transmission."
@@ -735,11 +741,13 @@ var enUS = {
735
741
  "investigate.signal-description.DKIM_md_INTRO": null,
736
742
  "investigate.signal-description.TLS_md_SHORT": "TLS stands for **Transport Layer Security** and it is a protocol which helps **protect your emails by encrypting the connection** from the sender to recipient (client to server). Encryption makes snooping on your emails much harder while they travel on their way to your recipients. Encryption in transit is important so that your emails are read only by the intended recipients and not on their way there.",
737
743
  "investigate.signal-description.TLS_md_LONG": [
744
+ "",
738
745
  "Without using TLS your emails are exposed to snooping which means that they can be read by your internet provider or people on your network. Using TLS ensures that your emails are only read by the intended recipients. Please contact your service provider or administrator to enable TLS."
739
746
  ],
740
747
  "investigate.signal-description.TLS_md_INTRO": null,
741
748
  "investigate.signal-description.FCRDNS_md_SHORT": "**Forward-confirmed reverse DNS** (FCrDNS) is a spam filtering mechanism that tests if a given IP address has both **forward (name-to-address) and reverse (address-to-name)** Domain Name System (DNS) entries that match each other.",
742
749
  "investigate.signal-description.FCRDNS_md_LONG": [
750
+ "",
743
751
  "This verification is a weak form of authentication that exists to prove that there is a **valid relationship** between the owner of a domain name and the owner of the network that has been given an IP address. As this is difficult for spammers and spoofers to bypass FCrDNS is checked by some mail receivers in an effort to reduce phishing attacks and spam.",
744
752
  "",
745
753
  "Legitimate email servers should be correctly configured for FCrDNS to avoid deliverability issues."
@@ -759,29 +767,32 @@ var enUS = {
759
767
  "investigate.signal-description.SUBDO_DOMAIN_ANALYZER_md": "We've checked your SPF record and haven't found any compromised \"includes\"",
760
768
  "investigate.signal-description.SPF_md_SHORT": "SPF stands for **Sender Policy Framework**. It was developed to combat sender address forgery. It is an authentication protocol which **verifies the `MAIL FROM` or `HELO/EHLO` identities during email transmission**.",
761
769
  "investigate.signal-description.SPF_md_LONG": [
770
+ "",
762
771
  "SPF does this by comparing the sending server's IP address to a **list of authorized senders**. The authorized senders are the IP addresses which are allowed to send on behalf of the sending domain. They are **specified in a TXT record** which is published in the domain owner's DNS.",
763
772
  "",
764
773
  "If the receiving end supports SPF, then, upon receipt of an email it checks if the sending IP address is authorized to send on behalf of the domain and if is not in that list SPF authentication will fail.",
765
774
  "",
766
- "DMARC uses both SPF and DKIM. For information on what DKIM is please click the button below."
775
+ "DMARC uses both SPF and DKIM. For information on what DKIM is please [click here](https://knowledge.ondmarc.redsift.com/en/articles/1142653-what-is-dkim)."
767
776
  ],
768
777
  "investigate.signal-description.SPF_md_INTRO": null,
769
778
  "investigate.signal-description.SPF_DOMAIN_md_SHORT": "SPF stands for **Sender Policy Framework**. It was developed to combat sender address forgery. It is an authentication protocol which **verifies the `MAIL FROM` or `HELO/EHLO` identities during email transmission**.",
770
779
  "investigate.signal-description.SPF_DOMAIN_md_LONG": [
780
+ "",
771
781
  "SPF does this by comparing the sending server's IP address to a **list of authorized senders**. The authorized senders are the IP addresses which are allowed to send on behalf of the sending domain. They are **specified in a TXT record** which is published in the domain owner's DNS.",
772
782
  "",
773
783
  "If the receiving end supports SPF, then, upon receipt of an email it checks if the sending IP address is authorized to send on behalf of the domain and if is not in that list SPF authentication will fail.",
774
784
  "",
775
- "DMARC uses both SPF and DKIM. For information on what DKIM is please click the button below."
785
+ "DMARC uses both SPF and DKIM. For information on what DKIM is please [click here](https://knowledge.ondmarc.redsift.com/en/articles/1142653-what-is-dkim)."
776
786
  ],
777
787
  "investigate.signal-description.SPF_DOMAIN_md_INTRO": null,
778
788
  "investigate.signal-description.SPF_DOMAIN_ANALYZER_md_SHORT": "SPF stands for **Sender Policy Framework**. It was developed to combat sender address forgery. It is an authentication protocol which **verifies the `MAIL FROM` or `HELO/EHLO` identities during email transmission**.",
779
789
  "investigate.signal-description.SPF_DOMAIN_ANALYZER_md_LONG": [
790
+ "",
780
791
  "SPF does this by comparing the sending server's IP address to a **list of authorized senders**. The authorized senders are the IP addresses which are allowed to send on behalf of the sending domain. They are **specified in a TXT record** which is published in the domain owner's DNS.",
781
792
  "",
782
793
  "If the receiving end supports SPF, then, upon receipt of an email it checks if the sending IP address is authorized to send on behalf of the domain and if is not in that list SPF authentication will fail.",
783
794
  "",
784
- "DMARC uses both SPF and DKIM. For information on what DKIM is please click the button below."
795
+ "DMARC uses both SPF and DKIM. For information on what DKIM is please [click here](https://knowledge.ondmarc.redsift.com/en/articles/1142653-what-is-dkim)."
785
796
  ],
786
797
  "investigate.signal-description.SPF_DOMAIN_ANALYZER_md_INTRO": null,
787
798
  "investigate.signal-description.URLS_md_SHORT": "Shows all URLs extracted from the email. This is useful if you need to subscribe via a link to the service you are testing.",
@@ -789,6 +800,7 @@ var enUS = {
789
800
  "investigate.signal-description.URLS_md_INTRO": null,
790
801
  "investigate.signal-description.THREATINV_md_SHORT": "The results of the **Threat Intelligence** check help to identify suspicious emails or ones that should be blocked. It also highlights **deliverability** issues from legit sources.",
791
802
  "investigate.signal-description.THREATINV_md_LONG": [
803
+ "",
792
804
  "The **Spamhaus** databases that are checked are **comprehensive** and effective **threat intelligence data sets**. They are **continuously updated** and maintained by the Spamhaus Project researchers."
793
805
  ],
794
806
  "investigate.signal-description.THREATINV_md_INTRO": "**Sender IP** and **Domain** were checked against databases of known **spammers**, **malware** disseminators, **non mail transfer agents**, **botnet** resources, **phishers**, or **low-reputation** senders.",
@@ -796,16 +808,20 @@ var enUS = {
796
808
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_NO_THREATS_md": "Domain was checked against several of trusted spammers database, web-mail transfer agents, botnet resources, phishers, or Non-reputation source: No suspicious activity.",
797
809
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_md_SHORT": "The results of the **Threat Intelligence** check help to identify suspicious domains or ones that should be blocked. It also highlights **deliverability** issues from legit sources.",
798
810
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_md_LONG": [
811
+ "",
799
812
  "The **Spamhaus** databases that are checked are **comprehensive** and effective **threat intelligence data sets**. They are **continuously updated** and maintained by the Spamhaus Project researchers."
800
813
  ],
801
814
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_md_INTRO": "**Domain** was checked against databases of known **spammers**, **malware** disseminators, **non mail transfer agents**, **botnet** resources, **phishers**, or **low-reputation** senders.",
802
815
  "investigate.signal-description.BIMI_md_SHORT": "**OnDMARC** and the Red Sift team can get your brand **BIMI compliant**, including issuing a **VMC** or **CMC** through our partners. [Learn more](https://redsift.com/pulse-platform/ondmarc/bimi)",
803
816
  "investigate.signal-description.BIMI_md_LONG": [
817
+ "",
804
818
  "To become **BIMI compliant** the following criteria must be met:",
805
819
  "",
806
820
  "1. The sending domain must be **DMARC compliant**.",
807
821
  "2. You must hold the **trademark or certified rights for the brand logo** you want displayed.",
808
- "3. You should have a **valid VMC or CMC certificate** which proves you are authorised to use the logo and are in control of the sending domain."
822
+ "3. You should have a **valid VMC or CMC certificate** which proves you are authorised to use the logo and are in control of the sending domain.",
823
+ "",
824
+ "For more details on the differences between VMC and CMC, see our [BIMI guide](https://redsift.com/guides/bimi#key-differences-between-vmc-and-cmc)."
809
825
  ],
810
826
  "investigate.signal-description.BIMI_md_INTRO": "**BIMI**, or **Brand Indicators** for **Message Identification**, is a proposed standard that lets you display your company or brand logo next to your authenticated email messages in your customers inbox. [Learn more](https://redsift.com/pulse-platform/ondmarc/bimi)",
811
827
  "investigate.signal-description.DNSSEC_md_SHORT": "The Domain Name System Security Extensions (DNSSEC) is a feature of the Domain Name System (DNS) that authenticates responses to domain name lookups. It does not provide privacy protections for those lookups, but prevents attackers from manipulating or poisoning the responses to DNS requests.",
@@ -818,6 +834,7 @@ var enUS = {
818
834
  ],
819
835
  "investigate.signal-description.MTA-STS_md_SHORT": "**MTA-STS** (Mail Transfer Agent Strict Transport Security) tells sending servers to only deliver mail to your domain when a TLS connection can be negotiated and the policy you published is valid.",
820
836
  "investigate.signal-description.MTA-STS_md_LONG": [
837
+ "",
821
838
  "To be protected by **MTA-STS** you must:",
822
839
  "",
823
840
  "1. Publish an **mta-sts TXT record** that advertises your policy.",
@@ -951,8 +968,8 @@ var enUS = {
951
968
  "glossary.label.RETURN_PATH": "Return Path",
952
969
  "glossary.label.RETURN_PATH.value": "Return Path",
953
970
  "glossary.label.RETURN_PATH.link": "return-path",
954
- "glossary.label.DMARC_POLICY": "p=",
955
- "glossary.label.DMARC_POLICY.value": "p=",
971
+ "glossary.label.DMARC_POLICY": "DMARC Policy",
972
+ "glossary.label.DMARC_POLICY.value": "DMARC Policy",
956
973
  "glossary.label.DMARC_POLICY.value_FULL": "DMARC Policy",
957
974
  "glossary.label.DMARC_POLICY.value_REPORTED": "report",
958
975
  "glossary.label.DMARC_POLICY.value_QUARANTINE": "quarantine",
@@ -1466,6 +1483,7 @@ var frFR = {
1466
1483
  "investigate.card.bimi.record-passed_md": "**[](BIMI_RECORD#:glossary)** **Présent et conforme**",
1467
1484
  "investigate.card.bimi.record-declination_md": "**[](BIMI_RECORD#:glossary)** Présenter et contenir une **déclaration valide de publication**",
1468
1485
  "investigate.card.bimi.record-no-cert_md": "**[](BIMI_RECORD#:glossary)** **Certificat non trouvé**",
1486
+ "investigate.card.bimi.cert-forbidden_md": "Votre certificat semble être derrière un système de protection anti-bot. [En savoir plus et comment résoudre le problème](https://knowledge.ondmarc.redsift.com/en/articles/8047013-ondmarc-cannot-fetch-my-bimi-logo-or-certificate)",
1469
1487
  "investigate.card.bimi.record-failed_md": "**[](BIMI_RECORD#:glossary)** **Présent** mais avec des erreurs",
1470
1488
  "investigate.card.bimi.record-missing_md": "**[](BIMI_RECORD#:glossary)** **Missing.** [Learn how to fix it](https://community.redsift.com/s/article/000001130)",
1471
1489
  "investigate.card.bimi.could-not-fetch_md": "Échec de la récupération de **[](BIMI_RECORD#:glossary)**",
@@ -1479,6 +1497,7 @@ var frFR = {
1479
1497
  "investigate.card.bimi.vmc.expiry-date_md": "**Expire le:** {date} ({daysLeft} jours restants)",
1480
1498
  "investigate.card.bimi.vmc.expired-date_md": "Expiré le {date}",
1481
1499
  "investigate.card.bimi.vmc.trademarkAuthority_md": "**Autorité de la marque : {trademarkAuthority}**",
1500
+ "investigate.card.bimi.vmc.assertionMark_md": "**Marque d'assertion :** {assertionMark}",
1482
1501
  "investigate.card.bimi.logoErrors.invalid-svg_md": "**[](VMC#:glossary)** SVG non valide. Le logo du certificat ne peut pas être converti en un SVG valide.",
1483
1502
  "investigate.card.bimi.logoErrors.invalid-svg-version_md": "**[](VMC#:glossary)** Version SVG invalide. Devrait être **1.2**",
1484
1503
  "investigate.card.bimi.logoErrors.invalid-svg-profile_md": "**[](VMC#:glossary)** Profil SVG invalide. Doit être **tiny-ps**",
@@ -1499,10 +1518,11 @@ var frFR = {
1499
1518
  "investigate.card.bimi.logoErrors.logo-and-evidence-mismatch_md": "**[](VMC#:glossary)** Logo sur l'enregistrement et sur le certificat ne correspondent pas",
1500
1519
  "investigate.card.bimi.logoErrors.logo-extract-error_md": "**[](VMC#:glossary)** Erreur lors de l'extraction du logo",
1501
1520
  "investigate.card.bimi.logoErrors.unknown-svg-error_md": "**[](VMC#:glossary)** Erreur inconnue lors de la validation du SVG",
1521
+ "investigate.card.bimi.svg-error-fallback_md": "**[](VMC#:glossary)** {error}",
1502
1522
  "investigate.card.bimi.pemErrors.cert-not-found_md": "**[](VMC#:glossary)** Certificat non trouvé",
1503
1523
  "investigate.card.bimi.pemErrors.cert-not-url_md": "**[](VMC#:glossary)** Le certificat n'est pas une url",
1504
1524
  "investigate.card.bimi.pemErrors.cert-not-file_md": "**[](VMC#:glossary)** Le certificat n'est pas un fichier",
1505
- "investigate.card.bimi.pemErrors.cert-not-https_md": "**[](VMC#:glossary)** Le certificat n'a pas de protocole HTTPS",
1525
+ "investigate.card.bimi.pemErrors.cert-not-https_md": "**[](VMC#:glossary)** Le certificat n'utilise pas le protocole HTTPS",
1506
1526
  "investigate.card.bimi.pemErrors.cert-not-pem_md": "**[](VMC#:glossary)** Le certificat n'est pas un fichier PEM",
1507
1527
  "investigate.card.bimi.pemErrors.cert-not-valid_md": "**[](VMC#:glossary)** Le certificat n'est pas valide",
1508
1528
  "investigate.card.bimi.pemErrors.cert-bad-date_md": "**[](VMC#:glossary)** Le certificat a expiré",
@@ -1608,6 +1628,7 @@ var frFR = {
1608
1628
  "Il s'agit d'un protocole qui a été conçu pour renforcer **les protocoles SPF et DKIM existants**."
1609
1629
  ],
1610
1630
  "investigate.signal-description.DMARC_DOMAIN_md_LONG": [
1631
+ "",
1611
1632
  "DMARC a plusieurs fonctions :",
1612
1633
  "",
1613
1634
  "1. Il prend en compte les résultats de SPF et DKIM.",
@@ -1622,6 +1643,7 @@ var frFR = {
1622
1643
  "Il s'agit d'un protocole qui a été conçu pour renforcer **les protocoles SPF et DKIM existants**."
1623
1644
  ],
1624
1645
  "investigate.signal-description.DMARC_md_LONG": [
1646
+ "",
1625
1647
  "DMARC a plusieurs fonctions :",
1626
1648
  "",
1627
1649
  "1. Il prend en compte les résultats de SPF et DKIM.",
@@ -1632,6 +1654,7 @@ var frFR = {
1632
1654
  "investigate.signal-description.DMARC_md_INTRO": null,
1633
1655
  "investigate.signal-description.DKIM_md_SHORT": "**DKIM** signifie *DomainKeys Identified Mail*. Il est utilisé pour signer différents champs d'en-tête et le corps d'un courrier électronique afin d'authentifier le domaine d'envoi et d'empêcher la modification du message pendant le transit.",
1634
1656
  "investigate.signal-description.DKIM_md_LONG": [
1657
+ "",
1635
1658
  "Pour ce faire, il utilise la **cryptographie asymétrique** qui consiste en des clés publiques et privées. La clé privée est propre au domaine de l'expéditeur et est utilisée pour signer les courriels. La clé publique est publiée dans le DNS de l'expéditeur et peut donc être récupérée par toute personne recevant des messages de l'expéditeur.",
1636
1659
  "",
1637
1660
  "Essentiellement, lorsqu'un courriel est composé, ses en-têtes et son corps sont signés à l'aide de la clé privée de l'expéditeur pour créer une signature numérique, qui est également envoyée en tant que champ d'en-tête avec le courriel. Du côté du destinataire (si le DKIM est activé), le serveur récupère la clé publique et vérifie si le courriel a bien été signé par le domaine d'envoi. Si la signature est validée avec succès, cela prouve que le domaine d'envoi a envoyé le message et que les en-têtes et le corps du message n'ont pas été modifiés pendant la transmission."
@@ -1639,11 +1662,13 @@ var frFR = {
1639
1662
  "investigate.signal-description.DKIM_md_INTRO": null,
1640
1663
  "investigate.signal-description.TLS_md_SHORT": "TLS signifie **Transport Layer Security** et il s'agit d'un protocole qui aide à **protéger vos courriels en chiffrant la connexion** entre l'expéditeur et le destinataire (du client au serveur). Le cryptage rend l'espionnage de vos courriels beaucoup plus difficile pendant leur acheminement vers vos destinataires. Le cryptage en transit est important pour que vos courriels ne soient lus que par les destinataires prévus et non en cours de route.",
1641
1664
  "investigate.signal-description.TLS_md_LONG": [
1665
+ "",
1642
1666
  "Sans l'utilisation de TLS, vos courriels sont exposés à l'espionnage, ce qui signifie qu'ils peuvent être lus par votre fournisseur d'accès à Internet ou par des personnes de votre réseau. L'utilisation de TLS garantit que vos courriels ne sont lus que par les destinataires prévus. Veuillez contacter votre fournisseur de services ou votre administrateur pour activer TLS."
1643
1667
  ],
1644
1668
  "investigate.signal-description.TLS_md_INTRO": null,
1645
1669
  "investigate.signal-description.FCRDNS_md_SHORT": "Le **Forward-confirmed reverse DNS** (FCrDNS) est un mécanisme de filtrage du spam qui teste si une adresse IP donnée possède à la fois des entrées **forward (nom vers adresse) et reverse (adresse vers nom)** du système de noms de domaine (DNS) qui correspondent l'une à l'autre.",
1646
1670
  "investigate.signal-description.FCRDNS_md_LONG": [
1671
+ "",
1647
1672
  "Cette vérification est une forme faible d'authentification qui existe pour prouver qu'il y a une **relation valide** entre le propriétaire d'un nom de domaine et le propriétaire du réseau qui a reçu une adresse IP. Comme il est difficile pour les spammeurs et les usurpateurs de contourner cette vérification, FCrDNS est vérifié par certains destinataires de courrier électronique dans le but de réduire les attaques par hameçonnage et le spam.",
1648
1673
  "",
1649
1674
  "Les serveurs de courrier électronique légitimes doivent être correctement configurés pour FCrDNS afin d'éviter les problèmes de délivrabilité."
@@ -1660,20 +1685,22 @@ var frFR = {
1660
1685
  "investigate.signal-description.SUBDO_md": "Nous vérifions votre enregistrement SPF pour détecter les \"includes\" compromis qui pourraient laisser ce domaine ouvert aux attaques de spoofing.",
1661
1686
  "investigate.signal-description.SPF_md_SHORT": "SPF signifie **Sender Policy Framework**. Il a été développé pour lutter contre la falsification de l'adresse de l'expéditeur. Il s'agit d'un protocole d'authentification qui **vérifie les identités `MAIL FROM` ou `HELO/EHLO` pendant la transmission du courrier électronique**.",
1662
1687
  "investigate.signal-description.SPF_md_LONG": [
1688
+ "",
1663
1689
  "Pour ce faire, SPF compare l'adresse IP du serveur d'envoi à une **liste d'expéditeurs autorisés**. Les expéditeurs autorisés sont les adresses IP qui sont autorisées à envoyer des messages au nom du domaine d'envoi. Elles sont **spécifiées dans un enregistrement TXT** qui est publié dans le DNS du propriétaire du domaine.",
1664
1690
  "",
1665
1691
  "Si le destinataire prend en charge SPF, il vérifie, lors de la réception d'un courrier électronique, si l'adresse IP d'envoi est autorisée à envoyer des messages au nom du domaine et, si elle ne figure pas dans cette liste, l'authentification SPF échoue.",
1666
1692
  "",
1667
- "DMARC utilise à la fois SPF et DKIM. Pour plus d'informations sur DKIM, cliquez sur le bouton ci-dessous."
1693
+ "DMARC utilise à la fois SPF et DKIM. Pour plus d'informations sur DKIM, [cliquez ici](https://knowledge.ondmarc.redsift.com/en/articles/1142653-what-is-dkim)."
1668
1694
  ],
1669
1695
  "investigate.signal-description.SPF_md_INTRO": "",
1670
1696
  "investigate.signal-description.SPF_DOMAIN_md_SHORT": "SPF signifie **Sender Policy Framework**. Il a été développé pour lutter contre la falsification de l'adresse de l'expéditeur. Il s'agit d'un protocole d'authentification qui **vérifie les identités `MAIL FROM` ou `HELO/EHLO` pendant la transmission du courrier électronique**.",
1671
1697
  "investigate.signal-description.SPF_DOMAIN_md_LONG": [
1698
+ "",
1672
1699
  "Pour ce faire, SPF compare l'adresse IP du serveur d'envoi à une **liste d'expéditeurs autorisés**. Les expéditeurs autorisés sont les adresses IP qui sont autorisées à envoyer des messages au nom du domaine d'envoi. Elles sont **spécifiées dans un enregistrement TXT** qui est publié dans le DNS du propriétaire du domaine.",
1673
1700
  "",
1674
1701
  "Si le destinataire prend en charge SPF, il vérifie, lors de la réception d'un courrier électronique, si l'adresse IP d'envoi est autorisée à envoyer des messages au nom du domaine et, si elle ne figure pas dans cette liste, l'authentification SPF échoue.",
1675
1702
  "",
1676
- "DMARC utilise à la fois SPF et DKIM. Pour plus d'informations sur DKIM, cliquez sur le bouton ci-dessous."
1703
+ "DMARC utilise à la fois SPF et DKIM. Pour plus d'informations sur DKIM, [cliquez ici](https://knowledge.ondmarc.redsift.com/en/articles/1142653-what-is-dkim)."
1677
1704
  ],
1678
1705
  "investigate.signal-description.SPF_DOMAIN_md_INTRO": "",
1679
1706
  "investigate.signal-description.SPF_DOMAIN_ANALYZER_md_SHORT": "SPF signifie **Sender Policy Framework**. Il a été développé pour lutter contre la falsification de l'adresse de l'expéditeur. Il s'agit d'un protocole d'authentification qui **vérifie les identités `MAIL FROM` ou `HELO/EHLO` pendant la transmission du courrier électronique**.",
@@ -1681,11 +1708,12 @@ var frFR = {
1681
1708
  "investigate.signal-description.SUBDO_DOMAIN_ANALYZER_md_LONG": null,
1682
1709
  "investigate.signal-description.SUBDO_DOMAIN_ANALYZER_md": "Nous avons vérifié votre enregistrement SPF et n'avons trouvé aucune inclusion compromise",
1683
1710
  "investigate.signal-description.SPF_DOMAIN_ANALYZER_md_LONG": [
1711
+ "",
1684
1712
  "Pour ce faire, SPF compare l'adresse IP du serveur d'envoi à une **liste d'expéditeurs autorisés**. Les expéditeurs autorisés sont les adresses IP qui sont autorisées à envoyer des messages au nom du domaine d'envoi. Elles sont **spécifiées dans un enregistrement TXT** qui est publié dans le DNS du propriétaire du domaine.",
1685
1713
  "",
1686
1714
  "Si le destinataire prend en charge SPF, il vérifie, lors de la réception d'un courrier électronique, si l'adresse IP d'envoi est autorisée à envoyer des messages au nom du domaine et, si elle ne figure pas dans cette liste, l'authentification SPF échoue.",
1687
1715
  "",
1688
- "DMARC utilise à la fois SPF et DKIM. Pour plus d'informations sur DKIM, cliquez sur le bouton ci-dessous."
1716
+ "DMARC utilise à la fois SPF et DKIM. Pour plus d'informations sur DKIM, [cliquez ici](https://knowledge.ondmarc.redsift.com/en/articles/1142653-what-is-dkim)."
1689
1717
  ],
1690
1718
  "investigate.signal-description.SPF_DOMAIN_ANALYZER_md_INTRO": "",
1691
1719
  "investigate.signal-description.URLS_md_SHORT": "Affiche toutes les URL extraites de l'e-mail. Ceci est utile si vous avez besoin de vous abonner via un lien au service que vous testez.",
@@ -1693,6 +1721,7 @@ var frFR = {
1693
1721
  "investigate.signal-description.URLS_md_INTRO": "",
1694
1722
  "investigate.signal-description.THREATINV_md_SHORT": "Les résultats du contrôle **Threat Intelligence** permettent d'identifier les courriels suspects ou ceux qui devraient être bloqués. Ils mettent également en évidence les problèmes de **délivrabilité** provenant de sources légitimes.",
1695
1723
  "investigate.signal-description.THREATINV_md_LONG": [
1724
+ "",
1696
1725
  "Les bases de données **Spamhaus** qui sont vérifiées sont des **ensembles de données de renseignements sur les menaces** complets** et efficaces. Elles sont **mises à jour en permanence** et entretenues par les chercheurs du projet Spamhaus."
1697
1726
  ],
1698
1727
  "investigate.signal-description.THREATINV_md_INTRO": "**L'IP** et le domaine** de l'expéditeur ont été comparés à des bases de données de **spammers**, **malwares**, **non mail transfer agents**, **botnet**, **phishers** ou **low-reputation** connus.",
@@ -1700,16 +1729,20 @@ var frFR = {
1700
1729
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_NO_THREATS_md": "Le domaine a été comparé à des bases de données de spammeurs, d'agents de transfert de courrier web, de botnets, de phishers et de sources non réputées de confiance : aucune activité suspecte.",
1701
1730
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_md_SHORT": "Les résultats du contrôle **Threat Intelligence** permettent d'identifier les domaines suspects ou ceux qui devraient être bloqués. Ils mettent également en évidence les problèmes de **délivrabilité** des sources légitimes.",
1702
1731
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_md_LONG": [
1732
+ "",
1703
1733
  "Les bases de données **Spamhaus** qui sont vérifiées sont des **ensembles de données de renseignements sur les menaces** complets** et efficaces. Elles sont **mises à jour en permanence** et entretenues par les chercheurs du projet Spamhaus."
1704
1734
  ],
1705
1735
  "investigate.signal-description.THREATINV_DOMAIN_ANALYZER_md_INTRO": "Le **domaine** a été comparé à des bases de données de **spammers**, **malwares**, **non mail transfer agents**, **botnet** resources, **phishers**, ou **low-reputation** senders connus.",
1706
1736
  "investigate.signal-description.BIMI_md_SHORT": "**OnDMARC** et l'équipe de Red Sift peuvent faire en sorte que votre marque soit **conforme au BIMI**, y compris l'émission d'un **VMC** ou d'un **CMC** par l'intermédiaire de nos partenaires. [En savoir plus](https://redsift.com/pulse-platform/ondmarc/bimi)",
1707
1737
  "investigate.signal-description.BIMI_md_LONG": [
1738
+ "",
1708
1739
  "Pour devenir **conforme au BIMI**, les critères suivants doivent être remplis :",
1709
1740
  "",
1710
1741
  "1. Le domaine d'envoi doit être **DMARC compliant**.",
1711
1742
  "2. Vous devez détenir la **marque ou des droits certifiés pour le logo** que vous souhaitez afficher.",
1712
- "3. Vous devez disposer d'un **certificat VMC ou CMC valide** qui prouve que vous êtes autorisé à utiliser le logo et que vous contrôlez le domaine d'envoi."
1743
+ "3. Vous devez disposer d'un **certificat VMC ou CMC valide** qui prouve que vous êtes autorisé à utiliser le logo et que vous contrôlez le domaine d'envoi.",
1744
+ "",
1745
+ "Pour plus de détails sur les différences entre VMC et CMC, consultez notre [guide BIMI](https://redsift.com/guides/bimi#key-differences-between-vmc-and-cmc)."
1713
1746
  ],
1714
1747
  "investigate.signal-description.BIMI_md_INTRO": "**BIMI**, ou **Brand Indicators** for **Message Identification**, est une proposition de norme qui vous permet d'afficher le logo de votre entreprise ou de votre marque à côté de vos messages électroniques authentifiés dans la boîte de réception de vos clients. [En savoir plus](https://redsift.com/pulse-platform/ondmarc/bimi)",
1715
1748
  "investigate.signal-description.DNSSEC_md_SHORT": "Les extensions de sécurité du système de noms de domaine (DNSSEC) sont une fonctionnalité du système de noms de domaine (DNS) qui authentifie les réponses aux recherches de noms de domaine. Elle ne fournit pas de protection de la vie privée pour ces recherches, mais empêche les attaquants de manipuler ou d'empoisonner les réponses aux requêtes DNS.",
@@ -1722,6 +1755,7 @@ var frFR = {
1722
1755
  ],
1723
1756
  "investigate.signal-description.MTA-STS_md_SHORT": "**MTA-STS** (Mail Transfer Agent Strict Transport Security) indique aux serveurs d'envoi de ne délivrer vos messages que lorsqu'une connexion TLS est négociée et que la politique que vous publiez est valide.",
1724
1757
  "investigate.signal-description.MTA-STS_md_LONG": [
1758
+ "",
1725
1759
  "Pour être protégé par **MTA-STS**, vous devez :",
1726
1760
  "",
1727
1761
  "1. Publier un **enregistrement TXT mta-sts** qui annonce votre politique.",
@@ -1851,8 +1885,8 @@ var frFR = {
1851
1885
  "glossary.label.RETURN_PATH": "Return path",
1852
1886
  "glossary.label.RETURN_PATH.value": "Return path",
1853
1887
  "glossary.label.RETURN_PATH.link": "return-path",
1854
- "glossary.label.DMARC_POLICY": "p=",
1855
- "glossary.label.DMARC_POLICY.value": "p=",
1888
+ "glossary.label.DMARC_POLICY": "Politique DMARC",
1889
+ "glossary.label.DMARC_POLICY.value": "Politique DMARC",
1856
1890
  "glossary.label.DMARC_POLICY.value_FULL": "Politique DMARC",
1857
1891
  "glossary.label.DMARC_POLICY.value_REPORTED": "rapporté",
1858
1892
  "glossary.label.DMARC_POLICY.value_QUARANTINE": "mis en quarantaine",
@@ -3346,44 +3380,64 @@ function getCardType$6() {
3346
3380
  bimiRecordStatus = recordStatus.FAILED_TO_FETCH,
3347
3381
  imageUrl,
3348
3382
  svgErrors = [],
3349
- vmc
3383
+ vmc,
3384
+ reportNew
3350
3385
  } = bimi;
3351
3386
  const svgValid = Boolean(imageUrl) && svgErrors.length === 0;
3387
+
3388
+ // Helper to get worst status from reportNew messages
3389
+ const getReportNewWorstStatus = () => {
3390
+ if (!reportNew || reportNew.length === 0) return null;
3391
+ const hasError = reportNew.some(r => r.status === 'error');
3392
+ const hasWarning = reportNew.some(r => r.status === 'warning');
3393
+ if (hasError) return CARD_STATUS$1.DANGER;
3394
+ if (hasWarning) return CARD_STATUS$1.WARNING;
3395
+ return null;
3396
+ };
3397
+
3398
+ // Helper to return the worst of two statuses
3399
+ const applyReportNewStatus = baseStatus => {
3400
+ const reportNewStatus = getReportNewWorstStatus();
3401
+ if (!reportNewStatus) return baseStatus;
3402
+ // DANGER is worse than WARNING is worse than GOOD
3403
+ if (reportNewStatus === CARD_STATUS$1.DANGER) return CARD_STATUS$1.DANGER;
3404
+ if (reportNewStatus === CARD_STATUS$1.WARNING && baseStatus === CARD_STATUS$1.GOOD) {
3405
+ return CARD_STATUS$1.WARNING;
3406
+ }
3407
+ return baseStatus;
3408
+ };
3352
3409
  if (bimiRecordStatus === recordStatus.DECLINATION_TO_PUBLISH) {
3353
- return CARD_STATUS$1.GOOD;
3410
+ return applyReportNewStatus(CARD_STATUS$1.GOOD);
3354
3411
  }
3355
3412
 
3356
- // Check DMARC compliance first - if not compliant, it's always danger
3357
- if (!dmarcCompliant) {
3358
- return CARD_STATUS$1.DANGER;
3413
+ // Check noRecordTxt first - this is a warning regardless of DMARC compliance
3414
+ // (matches legacy behavior from prod)
3415
+ if (bimiRecordStatus === recordStatus.NO_RECORD_TXT) {
3416
+ return applyReportNewStatus(CARD_STATUS$1.WARNING);
3359
3417
  }
3360
3418
 
3361
- // If DMARC compliant but no BIMI record, it's a warning (BIMI is optional)
3362
- if (bimiRecordStatus === recordStatus.NO_RECORD_TXT) {
3363
- return CARD_STATUS$1.WARNING;
3419
+ // Check DMARC compliance - if not compliant, it's danger
3420
+ if (!dmarcCompliant) {
3421
+ return applyReportNewStatus(CARD_STATUS$1.DANGER);
3364
3422
  }
3365
- if (bimiRecordStatus === recordStatus.FAILED_TO_FETCH || bimiRecordStatus === recordStatus.CERT_INVALID || bimiRecordStatus === recordStatus.NOT_VMC || bimiRecordStatus === recordStatus.WITH_ERRORS || bimiRecordStatus === recordStatus.CERT_NOT_FOUND || bimiRecordStatus === recordStatus.CERTIFICATE_FORBIDDEN) {
3366
- return CARD_STATUS$1.DANGER;
3423
+ if (bimiRecordStatus === recordStatus.INVALID_RECORD_TXT || bimiRecordStatus === recordStatus.FAILED_TO_FETCH || bimiRecordStatus === recordStatus.CERT_INVALID || bimiRecordStatus === recordStatus.NOT_VMC || bimiRecordStatus === recordStatus.WITH_ERRORS || bimiRecordStatus === recordStatus.CERT_NOT_FOUND || bimiRecordStatus === recordStatus.CERTIFICATE_FORBIDDEN) {
3424
+ return applyReportNewStatus(CARD_STATUS$1.DANGER);
3367
3425
  }
3368
3426
 
3369
- // Check for expired VMC certificate
3427
+ // Check for expired VMC certificate - expired certs should be treated as danger
3370
3428
  if (vmc !== null && vmc !== void 0 && vmc.expiryDate) {
3371
3429
  const expiryDate = new Date(vmc.expiryDate);
3372
3430
  const now = new Date();
3373
3431
  if (expiryDate < now) {
3374
- return CARD_STATUS$1.WARNING;
3432
+ return applyReportNewStatus(CARD_STATUS$1.DANGER);
3375
3433
  }
3376
3434
  }
3377
3435
  if (svgValid) {
3378
- return CARD_STATUS$1.GOOD;
3436
+ return applyReportNewStatus(CARD_STATUS$1.GOOD);
3379
3437
  }
3380
3438
 
3381
- // SVG validation errors are warnings, not critical failures
3382
- // The logo might not display properly but BIMI record is valid
3383
- if (svgErrors.length > 0) {
3384
- return CARD_STATUS$1.WARNING;
3385
- }
3386
- return CARD_STATUS$1.DANGER;
3439
+ // SVG errors and missing images are danger conditions
3440
+ return applyReportNewStatus(CARD_STATUS$1.DANGER);
3387
3441
  }
3388
3442
  function extractDataUnsafe$9() {
3389
3443
  let bimi = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -3854,7 +3908,7 @@ const StyledGlossaryTag = styled.a`
3854
3908
  display: inline-flex;
3855
3909
  align-items: center;
3856
3910
  justify-content: center;
3857
- padding: 0.2em 0.5em;
3911
+ padding: 4px 8px;
3858
3912
  margin: 0 0.35rem;
3859
3913
  border-radius: 4px;
3860
3914
  background-color: ${_ref => {
@@ -3869,6 +3923,7 @@ const StyledGlossaryTag = styled.a`
3869
3923
  font-weight: 400;
3870
3924
  font-family: var(--sc-font-body, ${DS_FONT_BODY$b});
3871
3925
  white-space: nowrap;
3926
+ line-height: 1.3;
3872
3927
  color: ${COLOR.grey.darkest} !important;
3873
3928
  text-decoration: none !important;
3874
3929
  transition: background-color 150ms ease;
@@ -4606,9 +4661,10 @@ const isValidUrl = url => {
4606
4661
  */
4607
4662
 
4608
4663
  // Combined regex pattern for O(n) parsing - matches all markdown patterns in a single pass
4609
- // Groups: 1=glossary text, 2=glossary term, 3=link text, 4=link url, 5=bold, 6=italic, 7=code
4664
+ // Groups: 1=glossary text, 2=glossary term, 3=http link text, 4=http link url, 5=bold, 6=italic, 7=code, 8=plain link text, 9=plain link value
4610
4665
  // NOTE: Create new RegExp instance per call to avoid shared state issues with global flag
4611
- const COMBINED_PATTERN_SOURCE = /\[([^\]]*)\]\(([A-Z_]+)#:glossary\)|\[([^\]]+)\]\((https?:\/\/[^)]+)\)|\*\*([^*]+)\*\*|\*(?!\*)([^*]+)\*(?!\*)|`([^`]+)`/.source;
4666
+ // Order matters: check glossary first, then http links, then plain links (most specific to least specific)
4667
+ const COMBINED_PATTERN_SOURCE = /\[([^\]]*)\]\(([A-Z_]+)#:glossary\)|\[([^\]]+)\]\((https?:\/\/[^)]+)\)|\*\*([^*]+)\*\*|\*(?!\*)([^*]+)\*(?!\*)|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\)/.source;
4612
4668
  function parseGlossaryMarkdown(text) {
4613
4669
  if (!text || typeof text !== 'string') {
4614
4670
  return text;
@@ -4711,6 +4767,13 @@ function parseGlossaryMarkdown(text) {
4711
4767
  parts.push( /*#__PURE__*/React__default.createElement("code", {
4712
4768
  key: `code-${keyCounter++}`
4713
4769
  }, codeText));
4770
+ } else if (match[8] !== undefined && match[9] !== undefined) {
4771
+ // Plain link (non-http): [text](value) - render value as plain text
4772
+ // This handles cases like [svg-url](123) where 123 is not a valid URL
4773
+ const linkValue = match[9];
4774
+ parts.push( /*#__PURE__*/React__default.createElement("span", {
4775
+ key: `plain-link-${keyCounter++}`
4776
+ }, linkValue));
4714
4777
  }
4715
4778
  lastIndex = match.index + match[0].length;
4716
4779
  }
@@ -5677,9 +5740,10 @@ const DescriptionToggle = styled.div`
5677
5740
  */
5678
5741
  const CardWrapper = styled.div`
5679
5742
  height: 100%;
5743
+ text-align: left;
5680
5744
 
5681
5745
  @media only screen and (max-width: 768px) {
5682
- min-width: 350px;
5746
+ min-width: auto;
5683
5747
  }
5684
5748
 
5685
5749
  /* Override DetailedCard header font (uses CSS var with DS fallback) */
@@ -6609,7 +6673,7 @@ const getIcon$1 = _ref5 => {
6609
6673
  });
6610
6674
  }
6611
6675
  return /*#__PURE__*/React__default.createElement(Icon, {
6612
- icon: mdiInformation,
6676
+ icon: mdiInformationOutline,
6613
6677
  size: "small"
6614
6678
  });
6615
6679
  case 'warning':
@@ -6641,7 +6705,8 @@ const InfoItem = _ref6 => {
6641
6705
  subtexts,
6642
6706
  hasWarning,
6643
6707
  fluid,
6644
- extractionBox
6708
+ extractionBox,
6709
+ 'data-testid': dataTestId
6645
6710
  } = _ref6;
6646
6711
  const renderedText = renderValue(text, 'info-item-text');
6647
6712
  const icon = getIcon$1({
@@ -6657,11 +6722,13 @@ const InfoItem = _ref6 => {
6657
6722
  // Handle image type specially - render without flex wrapper to preserve overflow
6658
6723
  if (type === 'image') {
6659
6724
  return /*#__PURE__*/React__default.createElement(StyledInfoItem, {
6660
- $fluid: fluid
6725
+ $fluid: fluid,
6726
+ "data-testid": dataTestId
6661
6727
  }, renderedText.length ? renderedText : null);
6662
6728
  }
6663
6729
  const content = /*#__PURE__*/React__default.createElement(StyledInfoItem, {
6664
- $fluid: fluid
6730
+ $fluid: fluid,
6731
+ "data-testid": dataTestId
6665
6732
  }, /*#__PURE__*/React__default.createElement(StyledItemText, null, icon && /*#__PURE__*/React__default.createElement(StyledIconWrapper, null, icon), /*#__PURE__*/React__default.createElement(StyledContent, null, renderedText.length ? renderedText : null)), subtexts !== null && subtexts !== void 0 && subtexts.length ? /*#__PURE__*/React__default.createElement(StyledSubtexts, null, subtexts.map((subtext, idx) => /*#__PURE__*/React__default.createElement(React__default.Fragment, {
6666
6733
  key: idx
6667
6734
  }, renderValue(subtext.text, `info-item-subtext-${idx}`)))) : null);
@@ -7428,7 +7495,8 @@ const SignalCardNormal = /*#__PURE__*/forwardRef((props, ref) => {
7428
7495
  key: `generic-${section.id}-${i}`,
7429
7496
  header: section.title,
7430
7497
  isCollapsible: (_section$isCollapsibl = section.isCollapsible) !== null && _section$isCollapsibl !== void 0 ? _section$isCollapsibl : false,
7431
- isCollapsed: areAllCollapsed
7498
+ isCollapsed: areAllCollapsed,
7499
+ "data-testid": section.testId
7432
7500
  }, section.content);
7433
7501
  });
7434
7502
  }, [sections, areAllCollapsed]);
@@ -7485,7 +7553,8 @@ const SignalCardNormal = /*#__PURE__*/forwardRef((props, ref) => {
7485
7553
  key: genericSection.id,
7486
7554
  header: genericSection.title,
7487
7555
  isCollapsible: (_genericSection$isCol = genericSection.isCollapsible) !== null && _genericSection$isCol !== void 0 ? _genericSection$isCol : false,
7488
- isCollapsed: areAllCollapsed
7556
+ isCollapsed: areAllCollapsed,
7557
+ "data-testid": genericSection.testId
7489
7558
  }, genericSection.content)];
7490
7559
  }
7491
7560
 
@@ -15199,7 +15268,7 @@ const LogosWrapper = styled.div`
15199
15268
  display: flex;
15200
15269
  flex-direction: row;
15201
15270
  justify-content: flex-start;
15202
- gap: 1rem;
15271
+ gap: 16px;
15203
15272
  }
15204
15273
 
15205
15274
  border: 1px solid #dddddd;
@@ -15210,7 +15279,7 @@ const LogosWrapper = styled.div`
15210
15279
  }
15211
15280
  margin-bottom: 1.125rem;
15212
15281
  max-width: 100%;
15213
- overflow: auto;
15282
+ overflow: hidden;
15214
15283
  box-sizing: border-box;
15215
15284
 
15216
15285
  background: ${_ref => {
@@ -15221,7 +15290,7 @@ const LogosWrapper = styled.div`
15221
15290
  }};
15222
15291
 
15223
15292
  & > :first-child {
15224
- padding-right: 0.93rem;
15293
+ padding-right: 8px;
15225
15294
  @media only screen and (min-width: 768px) {
15226
15295
  padding-right: 1.25rem;
15227
15296
  }
@@ -15236,6 +15305,9 @@ const LogosWrapper = styled.div`
15236
15305
  }
15237
15306
  `;
15238
15307
  const LogoElementWrapper = styled.div`
15308
+ flex-shrink: 1;
15309
+ min-width: 0;
15310
+
15239
15311
  &,
15240
15312
  & > div:first-child {
15241
15313
  display: flex;
@@ -15256,6 +15328,7 @@ const LogoElementWrapper = styled.div`
15256
15328
  background-size: cover;
15257
15329
  background-repeat: no-repeat;
15258
15330
  background-position: center;
15331
+ max-width: 100%;
15259
15332
 
15260
15333
  ${_ref2 => {
15261
15334
  let {
@@ -15347,10 +15420,13 @@ const LogoElement = _ref8 => {
15347
15420
  const [aspectRatio, setAspectRatio] = useState(`${size}x${size}`);
15348
15421
  const [url, setUrl] = useState(svgUrl !== null && svgUrl !== void 0 ? svgUrl : isDark ? PLACEHOLDER_LIGHT : PLACEHOLDER_DARK);
15349
15422
  const [error, setError] = useState(false);
15423
+ const [isLoaded, setIsLoaded] = useState(false);
15350
15424
  useEffect(() => {
15425
+ setIsLoaded(false);
15351
15426
  if (!svgUrl) {
15352
15427
  setUrl(isDark ? PLACEHOLDER_LIGHT : PLACEHOLDER_DARK);
15353
15428
  setError(true);
15429
+ setIsLoaded(true);
15354
15430
  return;
15355
15431
  }
15356
15432
  const img = new Image();
@@ -15360,6 +15436,7 @@ const LogoElement = _ref8 => {
15360
15436
  height
15361
15437
  } = img;
15362
15438
  setAspectRatio(getAspectRatio(height, width));
15439
+ setIsLoaded(true);
15363
15440
  };
15364
15441
  img.onerror = error => {
15365
15442
  componentLogger.error('Error loading image', error);
@@ -15369,6 +15446,7 @@ const LogoElement = _ref8 => {
15369
15446
  }
15370
15447
  setUrl(isDark ? PLACEHOLDER_LIGHT : PLACEHOLDER_DARK);
15371
15448
  setError(true);
15449
+ setIsLoaded(true);
15372
15450
  };
15373
15451
  const newUrl = uncompressLogo(svgUrl);
15374
15452
  img.src = newUrl;
@@ -15379,7 +15457,9 @@ const LogoElement = _ref8 => {
15379
15457
  $square: square,
15380
15458
  $size: size
15381
15459
  }, /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("div", {
15460
+ "data-testid": "bimi-logo-image",
15382
15461
  "data-error": error,
15462
+ "data-loaded": isLoaded,
15383
15463
  className: 'img',
15384
15464
  style: {
15385
15465
  backgroundImage: `url(${url})`
@@ -15498,7 +15578,9 @@ const BIMI_LINK = Object.freeze({
15498
15578
  UNDERSTAND_ANALYZER: 'https://community.redsift.com/s/article/000001131',
15499
15579
  LOGO_REQUIREMENTS: 'https://community.redsift.com/s/article/000001131',
15500
15580
  HOW_TO_CREATE: 'https://blog.redsift.com/bimi/how-to-create-a-bimi-supported-svg-logo-file',
15501
- CANNOT_FETCH_LOGO_OR_CERTIFICATE: 'https://knowledge.ondmarc.redsift.com/en/articles/8047013-ondmarc-cannot-fetch-my-bimi-logo-or-certificate'
15581
+ CANNOT_FETCH_LOGO_OR_CERTIFICATE: 'https://knowledge.ondmarc.redsift.com/en/articles/8047013-ondmarc-cannot-fetch-my-bimi-logo-or-certificate',
15582
+ WHAT_IS_BIMI: 'https://knowledge.ondmarc.redsift.com/en/articles/4124230-what-is-bimi',
15583
+ DMARC_POLICIES: 'https://knowledge.ondmarc.redsift.com/en/articles/2868231-what-are-the-different-dmarc-policies'
15502
15584
  });
15503
15585
 
15504
15586
  // ============================================================================
@@ -15578,34 +15660,44 @@ function getExtractedItemsDmarc(_ref2, t) {
15578
15660
  fromDomain
15579
15661
  } = dmarc;
15580
15662
  if (policy) {
15581
- extractedItemsCategories.push({
15582
- title: t('section-titles.bimi-domain'),
15583
- categoryType: 'dmarc-domain',
15584
- extractionBox: {
15585
- caption: 'dns'
15586
- },
15587
- items: [{
15663
+ const items = [];
15664
+
15665
+ // Only add fromDomain item if it has a valid value
15666
+ if (fromDomain) {
15667
+ items.push({
15588
15668
  inline: true,
15589
15669
  label: /*#__PURE__*/React__default.createElement(From, null),
15590
15670
  text: fromDomain,
15591
15671
  theme: 'email',
15592
15672
  type: ''
15593
- }, {
15594
- label: /*#__PURE__*/React__default.createElement(DmarcPolicy, null),
15595
- text: policy.p,
15596
- theme: 'dns',
15597
- type: ''
15598
- }, {
15599
- label: /*#__PURE__*/React__default.createElement(Pct, null),
15600
- text: policy.pct,
15601
- theme: 'dns',
15602
- type: ''
15603
- }, ...(policy.sp === 'none' ? [{
15673
+ });
15674
+ }
15675
+ items.push({
15676
+ label: /*#__PURE__*/React__default.createElement(DmarcPolicy, null),
15677
+ text: policy.p,
15678
+ theme: 'dns',
15679
+ type: ''
15680
+ }, {
15681
+ label: /*#__PURE__*/React__default.createElement(Pct, null),
15682
+ text: policy.pct,
15683
+ theme: 'dns',
15684
+ type: ''
15685
+ });
15686
+ if (policy.sp === 'none') {
15687
+ items.push({
15604
15688
  label: /*#__PURE__*/React__default.createElement(Sp, null),
15605
15689
  text: policy.sp,
15606
15690
  theme: 'dns',
15607
15691
  type: ''
15608
- }] : [])]
15692
+ });
15693
+ }
15694
+ extractedItemsCategories.push({
15695
+ title: t('section-titles.bimi-domain'),
15696
+ categoryType: 'dmarc-domain',
15697
+ extractionBox: {
15698
+ caption: 'dns'
15699
+ },
15700
+ items
15609
15701
  });
15610
15702
  }
15611
15703
  return extractedItemsCategories;
@@ -15614,70 +15706,142 @@ function getExtractedItemsDmarc(_ref2, t) {
15614
15706
  // ============================================================================
15615
15707
  // Info Items Helpers
15616
15708
  // ============================================================================
15617
-
15618
- function getBimiItems(bimiRecordStatus, recordValidationErrors, t) {
15709
+ function getBimiItems(bimiRecordStatus, recordValidationErrors, reportNew, bimiRecordErrors, t) {
15619
15710
  const items = [];
15620
- const validationErrors = recordValidationErrors !== null && recordValidationErrors !== void 0 ? recordValidationErrors : [];
15711
+ // Filter out 'bimi-error-invalid-record' when status is INVALID_RECORD_TXT to avoid duplicate messages
15712
+ const validationErrors = (recordValidationErrors !== null && recordValidationErrors !== void 0 ? recordValidationErrors : []).filter(errorCode => {
15713
+ if (bimiRecordStatus === recordStatus.INVALID_RECORD_TXT && errorCode === 'bimi-error-invalid-record') {
15714
+ return false;
15715
+ }
15716
+ return true;
15717
+ });
15621
15718
  switch (bimiRecordStatus) {
15622
15719
  case recordStatus.DECLINATION_TO_PUBLISH:
15623
15720
  items.push({
15624
15721
  text: t('card.bimi.record-declination_md'),
15625
- type: 'good'
15722
+ type: 'good',
15723
+ 'data-testid': 'bimi-record-declination'
15626
15724
  });
15627
15725
  break;
15628
15726
  case recordStatus.VALID:
15629
15727
  items.push({
15630
15728
  text: t('card.bimi.record-passed_md'),
15631
- type: 'good'
15729
+ type: 'good',
15730
+ 'data-testid': 'bimi-record-valid'
15632
15731
  });
15633
15732
  break;
15634
15733
  case recordStatus.INVALID_RECORD_TXT:
15635
15734
  items.push({
15636
15735
  text: t('card.bimi.record-invalid_md'),
15637
- type: 'danger'
15736
+ type: 'danger',
15737
+ 'data-testid': 'bimi-record-invalid'
15638
15738
  });
15639
15739
  validationErrors.forEach(errorCode => {
15640
15740
  items.push({
15641
15741
  text: t(`card.bimi.errors.${errorCode}`),
15642
- type: '',
15643
- theme: 'info'
15742
+ type: 'info',
15743
+ theme: ''
15644
15744
  });
15645
15745
  });
15646
15746
  break;
15647
15747
  case recordStatus.CERT_NOT_FOUND:
15648
15748
  items.push({
15649
15749
  text: t('card.bimi.record-no-cert_md'),
15650
- type: 'danger'
15750
+ type: 'danger',
15751
+ 'data-testid': 'bimi-record-no-cert'
15651
15752
  });
15652
15753
  break;
15653
15754
  case recordStatus.NOT_VMC:
15654
- case recordStatus.CERT_INVALID:
15655
15755
  case recordStatus.WITH_ERRORS:
15656
15756
  items.push({
15657
15757
  text: t('card.bimi.record-failed_md'),
15658
- type: 'danger'
15758
+ type: 'warning',
15759
+ 'data-testid': 'bimi-record-failed'
15760
+ });
15761
+ validationErrors.forEach(errorCode => {
15762
+ items.push({
15763
+ text: t(`card.bimi.errors.${errorCode}`),
15764
+ type: 'info',
15765
+ theme: ''
15766
+ });
15767
+ });
15768
+ break;
15769
+ case recordStatus.CERT_INVALID:
15770
+ items.push({
15771
+ text: t('card.bimi.record-failed_md'),
15772
+ type: 'warning',
15773
+ 'data-testid': 'bimi-record-cert-invalid'
15659
15774
  });
15660
15775
  validationErrors.forEach(errorCode => {
15661
15776
  items.push({
15662
15777
  text: t(`card.bimi.errors.${errorCode}`),
15663
- type: '',
15664
- theme: 'info'
15778
+ type: 'info',
15779
+ theme: ''
15665
15780
  });
15666
15781
  });
15667
15782
  break;
15783
+ case recordStatus.CERTIFICATE_FORBIDDEN:
15784
+ items.push({
15785
+ text: t('card.bimi.cert-forbidden_md'),
15786
+ type: 'danger',
15787
+ 'data-testid': 'bimi-record-cert-forbidden'
15788
+ });
15789
+ break;
15668
15790
  case recordStatus.FAILED_TO_FETCH:
15669
15791
  items.push({
15670
15792
  text: t('card.bimi.could-not-fetch_md'),
15671
- type: 'danger'
15793
+ type: 'danger',
15794
+ 'data-testid': 'bimi-record-fetch-failed'
15672
15795
  });
15673
15796
  break;
15674
15797
  case recordStatus.NO_RECORD_TXT:
15675
15798
  default:
15676
15799
  items.push({
15677
15800
  text: t('card.bimi.record-missing_md'),
15678
- type: 'danger'
15801
+ type: 'danger',
15802
+ 'data-testid': 'bimi-record-not-found'
15679
15803
  });
15680
15804
  }
15805
+
15806
+ // Add reportNew messages if present, filtering out duplicates
15807
+ if (reportNew && reportNew.length > 0) {
15808
+ reportNew.forEach(report => {
15809
+ var _report$message;
15810
+ // Handle both camelCase (msgCode) and snake_case (msg_code) from API
15811
+ const msgCode = report.msgCode || report.msg_code;
15812
+
15813
+ // Skip "No BIMI record found" (DNS-BIMI-0008) when bimiRecordStatus already shows missing record
15814
+ if (msgCode === 'DNS-BIMI-0008' && bimiRecordStatus === recordStatus.NO_RECORD_TXT) {
15815
+ return;
15816
+ }
15817
+ // Skip "Declination to publish" (DNS-BIMI-0013) when bimiRecordStatus already shows declination
15818
+ // Also match on message text as fallback in case msgCode is missing
15819
+ const isDeclinationMessage = msgCode === 'DNS-BIMI-0013' || ((_report$message = report.message) === null || _report$message === void 0 ? void 0 : _report$message.toLowerCase().includes('declination to publish'));
15820
+ if (isDeclinationMessage && bimiRecordStatus === recordStatus.DECLINATION_TO_PUBLISH) {
15821
+ return;
15822
+ }
15823
+ // Map API status to item type: error -> danger, warning -> warning, info -> info
15824
+ const itemType = report.status === 'error' ? 'danger' : report.status;
15825
+ items.push({
15826
+ text: report.message,
15827
+ type: itemType,
15828
+ 'data-testid': `bimi-report-${msgCode || report.status}`
15829
+ });
15830
+ });
15831
+ }
15832
+
15833
+ // Add bimiRecord.errors messages if present (specific validation errors like duplicate tags)
15834
+ if (bimiRecordErrors && bimiRecordErrors.length > 0) {
15835
+ bimiRecordErrors.forEach(error => {
15836
+ if (error.message) {
15837
+ items.push({
15838
+ text: error.message,
15839
+ type: 'danger',
15840
+ 'data-testid': `bimi-record-error-${error.code || 'unknown'}`
15841
+ });
15842
+ }
15843
+ });
15844
+ }
15681
15845
  return items;
15682
15846
  }
15683
15847
  function getInfoItemsCategoriesBimi() {
@@ -15685,11 +15849,14 @@ function getInfoItemsCategoriesBimi() {
15685
15849
  let t = arguments.length > 1 ? arguments[1] : undefined;
15686
15850
  const {
15687
15851
  bimiRecordStatus = recordStatus.FAILED_TO_FETCH,
15688
- recordValidationErrors
15852
+ recordValidationErrors,
15853
+ reportNew,
15854
+ bimiRecord
15689
15855
  } = bimi;
15856
+ const bimiRecordErrors = bimiRecord === null || bimiRecord === void 0 ? void 0 : bimiRecord.errors;
15690
15857
  return {
15691
15858
  infoItemsCategories: [{
15692
- items: getBimiItems(bimiRecordStatus, recordValidationErrors, t),
15859
+ items: getBimiItems(bimiRecordStatus, recordValidationErrors, reportNew, bimiRecordErrors, t),
15693
15860
  title: t('card.bimi.record-status-title')
15694
15861
  }]
15695
15862
  };
@@ -15708,12 +15875,13 @@ function getInfoItemsCategoriesDmarc() {
15708
15875
  }
15709
15876
  const dmarcCompliantItems = [{
15710
15877
  text: /*#__PURE__*/React__default.createElement(DmarcCompliant, null),
15711
- type: dmarcCompliant ? 'good' : 'danger'
15878
+ type: dmarcCompliant ? 'good' : 'danger',
15879
+ 'data-testid': `dmarc-compliance-${dmarcCompliant ? 'pass' : 'fail'}`
15712
15880
  }];
15713
15881
  if (!dmarcCompliant) {
15714
15882
  dmarcCompliantItems.push({
15715
15883
  text: parseGlossaryMarkdown(t('investigate:card.bimi.record-info_md')),
15716
- type: 'no-icon',
15884
+ type: 'info',
15717
15885
  theme: 'info'
15718
15886
  });
15719
15887
  }
@@ -15771,14 +15939,39 @@ function getSafeSVG() {
15771
15939
  };
15772
15940
  }
15773
15941
  const items = [textItem, svgItem];
15774
- if (!validSvg) {
15942
+
15943
+ // Only show svgErrors when imageUrl exists - if imageUrl is null, the "Logo missing" message is sufficient
15944
+ // Raw API errors like "can't fetch SVG from null: Get 'null': unsupported protocol scheme" are confusing
15945
+ if (!validSvg && imageAdded) {
15775
15946
  svgErrors.forEach(msg => {
15776
- const errorTextBase = t(`card.bimi.logoErrors.${msg}_md`);
15777
- const errorText = localizeAssertionMarkdown(errorTextBase, assertionMarkType) || errorTextBase;
15778
- items.push({
15779
- text: errorText,
15780
- type: 'warning'
15781
- });
15947
+ const translationKey = `card.bimi.logoErrors.${msg}_md`;
15948
+ const translatedText = t(translationKey);
15949
+
15950
+ // Check if translation exists (key returned means no translation found)
15951
+ const hasTranslation = translatedText !== translationKey && !translatedText.endsWith('_md');
15952
+ if (hasTranslation) {
15953
+ // Known error with translation - use warning icon
15954
+ const errorText = localizeAssertionMarkdown(translatedText, assertionMarkType) || translatedText;
15955
+ items.push({
15956
+ text: errorText,
15957
+ type: 'warning'
15958
+ });
15959
+ } else {
15960
+ // Unknown error (e.g., "http status not OK") - use fallback message with outlined info icon
15961
+ // The API error may already include context, so just prefix with VMC glossary tag
15962
+ const fallbackText = t('card.bimi.svg-error-fallback_md', {
15963
+ error: msg
15964
+ });
15965
+ const errorText = localizeAssertionMarkdown(fallbackText, assertionMarkType) || fallbackText;
15966
+ // Pass as React element to prevent double-processing by Checklist's parseGlossaryMarkdown
15967
+ // Use type: 'info' with theme: '' to get the outlined info icon (mdiInformationOutline)
15968
+ // Empty string bypasses the default 'email' theme in Checklist.getIcon
15969
+ items.push({
15970
+ text: parseGlossaryMarkdown(errorText),
15971
+ type: 'info',
15972
+ theme: ''
15973
+ });
15974
+ }
15782
15975
  });
15783
15976
  }
15784
15977
  return {
@@ -15800,10 +15993,20 @@ function getVMCCertificate() {
15800
15993
  vmc,
15801
15994
  pemUrl
15802
15995
  } = bimi;
15803
- if (!vmc || !pemUrl || bimiRecordStatus === recordStatus.FAILED_TO_FETCH || bimiRecordStatus === recordStatus.NO_RECORD_TXT) {
15996
+
15997
+ // Validate pemUrl is a real URL - test placeholders like 'any-string-should-work' should not show VMC section
15998
+ const isValidPemUrl = pemUrl && (pemUrl.startsWith('http://') || pemUrl.startsWith('https://'));
15999
+ if (!vmc || !isValidPemUrl || bimiRecordStatus === recordStatus.FAILED_TO_FETCH || bimiRecordStatus === recordStatus.NO_RECORD_TXT) {
16000
+ return null;
16001
+ }
16002
+
16003
+ // Skip VMC section if pemUrl is actually a logo URL (ends with .svg)
16004
+ // This is a temporary hack - pemUrl shouldn't be populated when there's no a= tag
16005
+ if (pemUrl.endsWith('.svg')) {
15804
16006
  return null;
15805
16007
  }
15806
16008
  const {
16009
+ assertionMark = null,
15807
16010
  assertionMarkType: assertionMarkTypeRaw,
15808
16011
  compliant = false,
15809
16012
  expiryDate = null,
@@ -15824,7 +16027,17 @@ function getVMCCertificate() {
15824
16027
  // Handle PEM errors
15825
16028
  if (pemErrors.length > 0) {
15826
16029
  pemErrors.forEach(error => {
15827
- if (error === 'cert-forbidden') {
16030
+ if (error === 'cert-not-found') {
16031
+ // Show "Certificate not found" error
16032
+ const certNotFoundTextBase = t('card.bimi.pemErrors.cert-not-found_md');
16033
+ const certNotFoundText = localizeAssertionMarkdown(certNotFoundTextBase, assertionMarkType) || certNotFoundTextBase;
16034
+ vmcItems.items.push({
16035
+ text: certNotFoundText,
16036
+ type: 'danger',
16037
+ 'data-testid': 'vmc-cert-not-found'
16038
+ });
16039
+ } else if (error === 'cert-forbidden') {
16040
+ // Show cert-forbidden which has special UI with a help link
15828
16041
  vmcItems.items.push({
15829
16042
  text: /*#__PURE__*/React__default.createElement(React__default.Fragment, null, t('certForbidden'), /*#__PURE__*/React__default.createElement(Link, {
15830
16043
  href: BIMI_LINK.CANNOT_FETCH_LOGO_OR_CERTIFICATE,
@@ -15833,14 +16046,8 @@ function getVMCCertificate() {
15833
16046
  }, t('certForbiddenLinkText'))),
15834
16047
  type: 'danger'
15835
16048
  });
15836
- } else {
15837
- const pemErrorTextBase = t(`card.bimi.pemErrors.${error}_md`);
15838
- const pemErrorText = localizeAssertionMarkdown(pemErrorTextBase, assertionMarkType) || pemErrorTextBase;
15839
- vmcItems.items.push({
15840
- text: pemErrorText,
15841
- type: 'danger'
15842
- });
15843
16049
  }
16050
+ // Skip other pemErrors - the compliance status "is invalid" provides this info with pem-url context
15844
16051
  });
15845
16052
  }
15846
16053
 
@@ -15859,15 +16066,16 @@ function getVMCCertificate() {
15859
16066
  // Handle general errors (skip if error code is already in pemErrors or logoErrors)
15860
16067
  const normalizedErrors = Array.isArray(errors) && errors.length ? errors.filter(error => typeof (error === null || error === void 0 ? void 0 : error.message) === 'string') : [];
15861
16068
 
15862
- // Normalize pemErrors and logoErrors for comparison
15863
- const normalizedPemErrors = pemErrors.map(e => e.toLowerCase());
16069
+ // Normalize logoErrors for comparison
15864
16070
  const normalizedLogoErrors = logoErrors.map(e => e.toLowerCase());
15865
- if (normalizedErrors.length > 0) {
16071
+
16072
+ // Only process general errors if no pemErrors exist (they contain duplicate/related messages)
16073
+ if (pemErrors.length === 0 && normalizedErrors.length > 0) {
15866
16074
  normalizedErrors.forEach(error => {
15867
16075
  var _error$code$toLowerCa, _error$code, _error$message;
15868
- // Skip if this error code is already represented in pemErrors or logoErrors
16076
+ // Skip if this error code is already represented in logoErrors
15869
16077
  const errorCode = (_error$code$toLowerCa = error === null || error === void 0 ? void 0 : (_error$code = error.code) === null || _error$code === void 0 ? void 0 : _error$code.toLowerCase()) !== null && _error$code$toLowerCa !== void 0 ? _error$code$toLowerCa : '';
15870
- if (errorCode && (normalizedPemErrors.includes(errorCode) || normalizedLogoErrors.includes(errorCode))) {
16078
+ if (errorCode && normalizedLogoErrors.includes(errorCode)) {
15871
16079
  return;
15872
16080
  }
15873
16081
  const messageBase = (_error$message = error === null || error === void 0 ? void 0 : error.message) !== null && _error$message !== void 0 ? _error$message : '';
@@ -15896,12 +16104,14 @@ function getVMCCertificate() {
15896
16104
  if (compliant) {
15897
16105
  vmcItems.items.push({
15898
16106
  text,
15899
- type: 'good'
16107
+ type: 'good',
16108
+ 'data-testid': 'vmc-validity-valid'
15900
16109
  });
15901
16110
  } else if (!pemErrors.find(err => err === 'cert-not-found')) {
15902
16111
  vmcItems.items.push({
15903
16112
  text,
15904
- type: 'warning'
16113
+ type: pemErrors.length > 0 ? 'danger' : 'warning',
16114
+ 'data-testid': 'vmc-validity-invalid'
15905
16115
  });
15906
16116
  }
15907
16117
 
@@ -15939,6 +16149,18 @@ function getVMCCertificate() {
15939
16149
  });
15940
16150
  }
15941
16151
 
16152
+ // Add assertion mark
16153
+ if (assertionMark) {
16154
+ const assertionMarkTextBase = t('card.bimi.vmc.assertionMark_md', {
16155
+ assertionMark
16156
+ });
16157
+ const assertionMarkText = localizeAssertionMarkdown(assertionMarkTextBase, assertionMarkType) || assertionMarkTextBase;
16158
+ vmcItems.items.push({
16159
+ text: assertionMarkText,
16160
+ type: 'no-icon'
16161
+ });
16162
+ }
16163
+
15942
16164
  // Add trademark authority
15943
16165
  if ((trademarkAuthority === null || trademarkAuthority === void 0 ? void 0 : trademarkAuthority.length) > 0) {
15944
16166
  const trademarkTextBase = t('card.bimi.vmc.trademarkAuthority_md', {
@@ -15964,7 +16186,8 @@ function getVMCCertificate() {
15964
16186
  vmcItems.items.push({
15965
16187
  text: expiryText,
15966
16188
  type: 'no-icon',
15967
- theme: expired ? 'warn' : undefined
16189
+ theme: expired ? 'warn' : undefined,
16190
+ 'data-testid': `vmc-expiration-${expired ? 'expired' : 'valid'}`
15968
16191
  });
15969
16192
  }
15970
16193
  return vmcItems;
@@ -16060,7 +16283,8 @@ const SignalCardBimi$1 = /*#__PURE__*/forwardRef((props, ref) => {
16060
16283
  title: cat.title,
16061
16284
  content: /*#__PURE__*/React__default.createElement(Checklist$1, {
16062
16285
  items: cat.items
16063
- })
16286
+ }),
16287
+ testId: `bimi-dmarc-section-${i}`
16064
16288
  }))) || []), ...((extractedItemsDmarc === null || extractedItemsDmarc === void 0 ? void 0 : extractedItemsDmarc.map((cat, i) => ({
16065
16289
  id: `extractedItemsDmarc-${i}`,
16066
16290
  title: cat.title,
@@ -16068,13 +16292,15 @@ const SignalCardBimi$1 = /*#__PURE__*/forwardRef((props, ref) => {
16068
16292
  content: /*#__PURE__*/React__default.createElement(ExtractedCategoryContent, {
16069
16293
  category: cat,
16070
16294
  getTranslatedCaption: getTranslatedCaption
16071
- })
16295
+ }),
16296
+ testId: `bimi-dmarc-extracted-${i}`
16072
16297
  }))) || []), ...((infoItemsCategoriesBimi === null || infoItemsCategoriesBimi === void 0 ? void 0 : infoItemsCategoriesBimi.map((cat, i) => ({
16073
16298
  id: `checklistsBimi-${i}`,
16074
16299
  title: cat.title,
16075
16300
  content: /*#__PURE__*/React__default.createElement(Checklist$1, {
16076
16301
  items: cat.items
16077
- })
16302
+ }),
16303
+ testId: `bimi-record-section-${i}`
16078
16304
  }))) || []), ...((extractedItemsBimi === null || extractedItemsBimi === void 0 ? void 0 : extractedItemsBimi.map((cat, i) => ({
16079
16305
  id: `extractedItemsBimi-${i}`,
16080
16306
  title: cat.title,
@@ -16082,7 +16308,8 @@ const SignalCardBimi$1 = /*#__PURE__*/forwardRef((props, ref) => {
16082
16308
  content: /*#__PURE__*/React__default.createElement(ExtractedCategoryContent, {
16083
16309
  category: cat,
16084
16310
  getTranslatedCaption: getTranslatedCaption
16085
- })
16311
+ }),
16312
+ testId: `bimi-record-extracted-${i}`
16086
16313
  }))) || [])];
16087
16314
  if (VMCCertificate) {
16088
16315
  sections.push({
@@ -16090,7 +16317,8 @@ const SignalCardBimi$1 = /*#__PURE__*/forwardRef((props, ref) => {
16090
16317
  title: VMCCertificate.title,
16091
16318
  content: /*#__PURE__*/React__default.createElement(Checklist$1, {
16092
16319
  items: VMCCertificate.items
16093
- })
16320
+ }),
16321
+ testId: 'bimi-vmc-section'
16094
16322
  });
16095
16323
  }
16096
16324
  if (safeSVG) {
@@ -16099,14 +16327,14 @@ const SignalCardBimi$1 = /*#__PURE__*/forwardRef((props, ref) => {
16099
16327
  title: safeSVG.title,
16100
16328
  content: /*#__PURE__*/React__default.createElement(Checklist$1, {
16101
16329
  items: safeSVG.items
16102
- })
16330
+ }),
16331
+ testId: 'bimi-svg-section'
16103
16332
  });
16104
16333
  }
16105
16334
  const sectionIds = sections.map(s => s.id);
16106
16335
 
16107
16336
  // Generate summary items
16108
16337
  const introDescriptionContent = t('signal-description.BIMI_md_INTRO', {
16109
- context: 'Intro',
16110
16338
  defaultValue: '',
16111
16339
  returnObjects: true
16112
16340
  });
@@ -16125,7 +16353,9 @@ const SignalCardBimi$1 = /*#__PURE__*/forwardRef((props, ref) => {
16125
16353
  sections: sections,
16126
16354
  isCollapsible: isCollapsible,
16127
16355
  areAllCollapsed: areAllCollapsed,
16128
- onCollapseAll: onCollapseAll
16356
+ onCollapseAll: onCollapseAll,
16357
+ "data-testid": "signal-card-bimi",
16358
+ "data-card-type": cardType
16129
16359
  }, restProps));
16130
16360
  } else {
16131
16361
  return null;
@@ -20648,7 +20878,8 @@ const SignalCardDmarcDomain$1 = /*#__PURE__*/forwardRef(function SignalCardDmarc
20648
20878
  title: cat.title,
20649
20879
  content: /*#__PURE__*/React__default.createElement(Checklist$1, {
20650
20880
  items: cat.items
20651
- })
20881
+ }),
20882
+ testId: `dmarc-domain-section-${i}`
20652
20883
  }))) || []), ...((extractedItemsCategories === null || extractedItemsCategories === void 0 ? void 0 : extractedItemsCategories.filter(cat => cat.items && cat.items.length > 0).map((cat, i) => {
20653
20884
  var _cat$extractionBox;
20654
20885
  return {
@@ -20659,7 +20890,8 @@ const SignalCardDmarcDomain$1 = /*#__PURE__*/forwardRef(function SignalCardDmarc
20659
20890
  content: /*#__PURE__*/React__default.createElement(ExtractedCategoryContent, {
20660
20891
  category: cat,
20661
20892
  getTranslatedCaption: getTranslatedCaption
20662
- })
20893
+ }),
20894
+ testId: `dmarc-domain-extracted-${i}`
20663
20895
  };
20664
20896
  })) || [])], [infoItemsCategories, extractedItemsCategories, useInvestigateColors, t]);
20665
20897
  return /*#__PURE__*/React__default.createElement(SignalCardNormal, _extends({
@@ -20682,7 +20914,9 @@ const SignalCardDmarcDomain$1 = /*#__PURE__*/forwardRef(function SignalCardDmarc
20682
20914
  bannerVariant: bannerVariant,
20683
20915
  isCollapsible: resolvedIsCollapsible,
20684
20916
  areAllCollapsed: resolvedAreAllCollapsed,
20685
- onCollapseAll: handleCollapseAll
20917
+ onCollapseAll: handleCollapseAll,
20918
+ "data-testid": "signal-card-dmarc-domain",
20919
+ "data-card-type": cardType
20686
20920
  }));
20687
20921
  });
20688
20922
  SignalCardDmarcDomain$1.className = CLASSNAME$c;
@@ -23242,6 +23476,7 @@ const _excluded$7 = ["useInvestigateColors", "isCollapsible", "areAllCollapsed",
23242
23476
  const COMPONENT_NAME$7 = 'SignalCardSpf';
23243
23477
  const CLASSNAME$7 = 'redsift-signal-card-spf';
23244
23478
  const SignalCardSpf$1 = /*#__PURE__*/forwardRef((props, ref) => {
23479
+ var _jmap$extsecrep;
23245
23480
  const {
23246
23481
  useInvestigateColors,
23247
23482
  isCollapsible,
@@ -23255,6 +23490,13 @@ const SignalCardSpf$1 = /*#__PURE__*/forwardRef((props, ref) => {
23255
23490
  const {
23256
23491
  t
23257
23492
  } = useSignalCardTranslation('investigate');
23493
+
23494
+ // Build SPF Checker link if domain is available
23495
+ const spf = jmap === null || jmap === void 0 ? void 0 : (_jmap$extsecrep = jmap.extsecrep) === null || _jmap$extsecrep === void 0 ? void 0 : _jmap$extsecrep.spf;
23496
+ const domain = spf === null || spf === void 0 ? void 0 : spf.domain;
23497
+ const spfCheckerLink = domain && typeof domain === 'string' ? buildSPFCheckerLink(domain) : null;
23498
+ const ctaButtonName = t('card.subdo.button-name');
23499
+ const onCTAButtonClick = spfCheckerLink ? () => window.open(spfCheckerLink, '_blank') : undefined;
23258
23500
  const computeInitialState = useMemo(() => {
23259
23501
  const {
23260
23502
  spf
@@ -23388,7 +23630,9 @@ const SignalCardSpf$1 = /*#__PURE__*/forwardRef((props, ref) => {
23388
23630
  type: "SPF"
23389
23631
  }, restProps, {
23390
23632
  cardType: cardType !== null && cardType !== void 0 ? cardType : undefined,
23391
- sections: sections
23633
+ sections: sections,
23634
+ ctaButtonName: ctaButtonName,
23635
+ onCTAButtonClick: onCTAButtonClick
23392
23636
  }));
23393
23637
  });
23394
23638
  SignalCardSpf$1.className = CLASSNAME$7;
@@ -23405,7 +23649,7 @@ const STATUS = {
23405
23649
  FAIL: 'fail'
23406
23650
  };
23407
23651
 
23408
- const _excluded$6 = ["className", "jmap", "onHeightChange", "bannerVariant", "useInvestigateColors"];
23652
+ const _excluded$6 = ["className", "jmap", "onHeightChange", "bannerVariant", "useInvestigateColors", "renderFooter"];
23409
23653
  const COMPONENT_NAME$6 = 'SignalCardSpfDomain';
23410
23654
  const CLASSNAME$6 = 'redsift-signal-card-spf-domain';
23411
23655
 
@@ -23422,21 +23666,75 @@ const getCardType$1 = status => {
23422
23666
 
23423
23667
  /**
23424
23668
  * Get SPF check result
23425
- * Simple pass/fail message - matches legacy behavior
23669
+ * Simple pass/fail/warning message - matches legacy behavior
23426
23670
  */
23427
23671
  const getSpfCheck = status => {
23428
23672
  if (status === STATUS.PASS) {
23429
23673
  return {
23430
23674
  type: CARD_STATUS$1.GOOD,
23431
- text: 'SPF authentication passed'
23675
+ text: /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Spf, null), " authentication passed")
23676
+ };
23677
+ }
23678
+ if (status === 'warning') {
23679
+ return {
23680
+ type: CARD_STATUS$1.WARNING,
23681
+ text: /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Spf, null), " authentication detected problems")
23432
23682
  };
23433
23683
  }
23434
23684
  return {
23435
23685
  type: CARD_STATUS$1.DANGER,
23436
- text: 'SPF authentication failed'
23686
+ text: /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Spf, null), " authentication failed")
23437
23687
  };
23438
23688
  };
23439
23689
 
23690
+ /**
23691
+ * Get additional info items from the report array
23692
+ * Matches legacy extractDataDomainSpf behavior
23693
+ * Returns items with type='danger' for error messages from report
23694
+ */
23695
+ const getReportItems = report => {
23696
+ if (!report || !Array.isArray(report)) {
23697
+ return [];
23698
+ }
23699
+ const items = [];
23700
+ report.forEach(item => {
23701
+ if (item.message === 'no-spf') {
23702
+ items.push({
23703
+ type: CARD_STATUS$1.DANGER,
23704
+ text: 'No SPF configured for the domain.'
23705
+ });
23706
+ } else if (item.message === 'ipf-error-spf') {
23707
+ var _item$spfError;
23708
+ const errorMessage = (_item$spfError = item.spfError) !== null && _item$spfError !== void 0 && _item$spfError.startsWith('dns-io') ? 'Temporary DNS error' : `SPF error: ${item.spfError}`;
23709
+ items.push({
23710
+ type: CARD_STATUS$1.DANGER,
23711
+ text: errorMessage
23712
+ });
23713
+ } else if (item.message === 'multiple-all') {
23714
+ items.push({
23715
+ type: CARD_STATUS$1.WARNING,
23716
+ text: 'Multiple all mechanisms detected.'
23717
+ });
23718
+ } else if (item.message === 'all-plus-warning') {
23719
+ items.push({
23720
+ type: CARD_STATUS$1.WARNING,
23721
+ text: 'All pass not recommended.'
23722
+ });
23723
+ } else if (item.message === 'all-not-last') {
23724
+ items.push({
23725
+ type: CARD_STATUS$1.WARNING,
23726
+ text: 'All not last detected.'
23727
+ });
23728
+ } else if (item.message === 'extra-spf-lookups') {
23729
+ items.push({
23730
+ type: CARD_STATUS$1.WARNING,
23731
+ text: 'Extra SPF lookups.'
23732
+ });
23733
+ }
23734
+ });
23735
+ return items;
23736
+ };
23737
+
23440
23738
  /**
23441
23739
  * SignalCardSpfDomain Component
23442
23740
  *
@@ -23461,7 +23759,8 @@ const SignalCardSpfDomain$1 = /*#__PURE__*/forwardRef(function SignalCardSpfDoma
23461
23759
  jmap,
23462
23760
  onHeightChange,
23463
23761
  bannerVariant,
23464
- useInvestigateColors = false
23762
+ useInvestigateColors = false,
23763
+ renderFooter
23465
23764
  } = props,
23466
23765
  forwardedProps = _objectWithoutProperties(props, _excluded$6);
23467
23766
  const {
@@ -23471,19 +23770,30 @@ const SignalCardSpfDomain$1 = /*#__PURE__*/forwardRef(function SignalCardSpfDoma
23471
23770
  const {
23472
23771
  raw,
23473
23772
  status,
23474
- txtExtractedFrom
23773
+ txtExtractedFrom,
23774
+ domain,
23775
+ report
23475
23776
  } = spf || {};
23476
23777
 
23778
+ // Build SPF Checker link - use domain if available, fall back to txtExtractedFrom
23779
+ const spfDomain = domain || txtExtractedFrom;
23780
+ const spfCheckerLink = spfDomain && typeof spfDomain === 'string' ? buildSPFCheckerLink(spfDomain) : null;
23781
+ const ctaButtonName = t('card.subdo.button-name');
23782
+ const onCTAButtonClick = spfCheckerLink ? () => window.open(spfCheckerLink, '_blank') : undefined;
23783
+
23477
23784
  // Handle both string and array formats for raw SPF record
23478
- // Take first record if array (BIMI Checker only needs to see one)
23479
- const rawRecord = useMemo(() => Array.isArray(raw) ? raw.length > 0 ? raw[0] : 'unknown' : raw || 'unknown', [raw]);
23785
+ // Show "No SPF record found" for empty/missing records instead of "unknown"
23786
+ const rawRecord = useMemo(() => {
23787
+ if (Array.isArray(raw)) {
23788
+ return raw.length > 0 ? raw[0] : 'No SPF record found';
23789
+ }
23790
+ // Check for empty string, null, or undefined
23791
+ return raw && raw.trim() !== '' ? raw : 'No SPF record found';
23792
+ }, [raw]);
23480
23793
  const cardType = useMemo(() => getCardType$1(status), [status]);
23481
23794
 
23482
- // Simple pass/fail check - matches legacy behavior
23483
- const infoItemsCategories = useMemo(() => [{
23484
- title: 'SPF',
23485
- items: [getSpfCheck(status)]
23486
- }], [status]);
23795
+ // Build all checklist items: main status check + report items
23796
+ const checklistItems = useMemo(() => [getSpfCheck(status), ...getReportItems(report)], [status, report]);
23487
23797
 
23488
23798
  // Simple extracted items - just show the SPF record with extraction box
23489
23799
  const extractedItemsCategories = useMemo(() => [{
@@ -23520,21 +23830,27 @@ const SignalCardSpfDomain$1 = /*#__PURE__*/forwardRef(function SignalCardSpfDoma
23520
23830
  if (!caption) return undefined;
23521
23831
  return t(EXTRACTION_CAPTION_KEYS[caption]);
23522
23832
  };
23523
- const sections = useMemo(() => [...extractedItemsCategories.filter(cat => cat.items && cat.items.length > 0).map((cat, i) => ({
23833
+ const sections = useMemo(() => [
23834
+ // SPF Policy section with DNS record
23835
+ ...extractedItemsCategories.filter(cat => cat.items && cat.items.length > 0).map((cat, i) => ({
23524
23836
  id: `extractedItems-${i}`,
23525
23837
  title: cat.title,
23526
23838
  isCollapsible: true,
23527
23839
  content: /*#__PURE__*/React__default.createElement(ExtractedCategoryContent, {
23528
23840
  category: cat,
23529
23841
  getTranslatedCaption: getTranslatedCaption
23530
- })
23531
- })), ...infoItemsCategories.map((cat, i) => ({
23532
- id: `checklists-${i}`,
23533
- title: cat.title,
23842
+ }),
23843
+ testId: `spf-domain-extracted-${i}`
23844
+ })),
23845
+ // SPF status section with all checklist items (main status + report errors)
23846
+ {
23847
+ id: 'spf-status',
23848
+ title: 'SPF',
23534
23849
  content: /*#__PURE__*/React__default.createElement(Checklist$1, {
23535
- items: cat.items
23536
- })
23537
- }))], [extractedItemsCategories, infoItemsCategories, useInvestigateColors, t]);
23850
+ items: checklistItems
23851
+ }),
23852
+ testId: 'spf-domain-status-section'
23853
+ }], [extractedItemsCategories, checklistItems, t]);
23538
23854
  if (!spf) return null;
23539
23855
  return /*#__PURE__*/React__default.createElement(SignalCardNormal, _extends({
23540
23856
  useInvestigateColors: useInvestigateColors
@@ -23546,9 +23862,20 @@ const SignalCardSpfDomain$1 = /*#__PURE__*/forwardRef(function SignalCardSpfDoma
23546
23862
  onComponentMount: handleComponentMount,
23547
23863
  sections: sections,
23548
23864
  bannerVariant: bannerVariant,
23865
+ renderFooter: renderFooter,
23866
+ ctaButtonName: ctaButtonName,
23867
+ onCTAButtonClick: onCTAButtonClick,
23868
+ shortDescription: t('signal-description.SPF_DOMAIN_md_SHORT', {
23869
+ returnObjects: true
23870
+ }),
23871
+ longDescription: t('signal-description.SPF_DOMAIN_md_LONG', {
23872
+ returnObjects: true
23873
+ }),
23549
23874
  isCollapsible: true,
23550
23875
  areAllCollapsed: areAllCollapsed,
23551
- onCollapseAll: handleCollapseAll
23876
+ onCollapseAll: handleCollapseAll,
23877
+ "data-testid": "signal-card-spf-domain",
23878
+ "data-card-type": cardType
23552
23879
  }));
23553
23880
  });
23554
23881
  SignalCardSpfDomain$1.displayName = COMPONENT_NAME$6;
@@ -23759,6 +24086,11 @@ const SignalCardSpfDomainAnalyzer$1 = /*#__PURE__*/forwardRef(function SignalCar
23759
24086
  } = spf || {};
23760
24087
  const recordsArray = useMemo(() => Array.isArray(records) ? records : [], [records]);
23761
24088
  const cardType = useMemo(() => getCardType(status), [status]);
24089
+
24090
+ // Build SPF Checker link if domain is available
24091
+ const spfCheckerLink = domain && typeof domain === 'string' ? buildSPFCheckerLink(domain) : null;
24092
+ const ctaButtonName = t('card.subdo.button-name');
24093
+ const onCTAButtonClick = spfCheckerLink ? () => window.open(spfCheckerLink, '_blank') : undefined;
23762
24094
  const resolveT = useResolveTranslation(['investigate'], {
23763
24095
  prefixes: ['card.spf.', 'card.spf.spf-']
23764
24096
  });
@@ -23853,6 +24185,8 @@ const SignalCardSpfDomainAnalyzer$1 = /*#__PURE__*/forwardRef(function SignalCar
23853
24185
  onHeightChange: onHeightChange,
23854
24186
  bannerVariant: bannerVariant,
23855
24187
  useInvestigateColors: useInvestigateColors,
24188
+ ctaButtonName: ctaButtonName,
24189
+ onCTAButtonClick: onCTAButtonClick,
23856
24190
  shortDescription: t('signal-description.SPF_DOMAIN_ANALYZER_md_SHORT', {
23857
24191
  returnObjects: true
23858
24192
  }),