@redsift/products 12.5.9 → 12.5.10-muiv7

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.
@@ -5,7 +5,7 @@ import styled, { css } from 'styled-components';
5
5
  import pako from 'pako';
6
6
  import { interpolateTranslation, mapStatusToDetailedCardColor, isStructuredData, calculateSankeyHeight, create2LevelSankey, extractDmarcData, extractSpfData, extractTlsData, extractFcrdnsData, extractDnssecData, extractThreatIntelligenceData, extractUrlsData, extractBimiData, extractAmpData, extractSubdoData, extractDomainAnalyzerDkimData, extractDomainAnalyzerSpfData, extractDmarcDomainData, extractSpfDomainData, extractMtaStsFullData, extractDkimData, recordStatus } from '@redsift/signal-logic';
7
7
  import { useLocalizedStringFormatter, useTheme, Button, Icon, DetailedCard, ButtonLink, Flexbox } from '@redsift/design-system';
8
- import { mdiHelpCircle, mdiClose, mdiAlert, mdiEmail, mdiEarth, mdiInformationOutline, mdiCheck, mdiInformation } from '@redsift/icons';
8
+ import { mdiHelpCircle, mdiClose, mdiAlert, mdiEmail, mdiEarth, mdiInformationOutline, mdiCheck, mdiInformation, mdiFormatAlignRight, mdiFormatAlignJustify } from '@redsift/icons';
9
9
  import { createPortal } from 'react-dom';
10
10
 
11
11
  const COLOR = {
@@ -283,6 +283,8 @@ var EXTRACTOR_STRINGS = {
283
283
  "card.dkim.unavailable-try-later_md": "Please try sending another email later.",
284
284
  "card.dkim.unavailable_md": "**[](DKIM#:glossary) evaluation temporarily unavailable.**",
285
285
  "card.dmarc.domain-aligned": "The domain is aligned with the From Domain. It can contribute to passing DMARC.",
286
+ "card.dmarc.domain-aligned-not-verified": "The domain is aligned with the From Domain, but its signature did not verify, so it cannot contribute to passing DMARC.",
287
+ "card.dmarc.spf-domain-aligned-not-verified": "The domain is aligned with the From Domain, but SPF did not pass, so it cannot contribute to passing DMARC.",
286
288
  "card.dmarc.domain-not-aligned": "The domain is not aligned with the From Domain. It cannot contribute to passing DMARC.",
287
289
  "card.dmarc.error.dns-timeout": "DNS timeout when looking up DMARC record",
288
290
  "card.dmarc.error.invalid-pct-value": "Pct value must be between 0 and 100",
@@ -766,6 +768,8 @@ var EXTRACTOR_STRINGS = {
766
768
  "card.dkim.unavailable-try-later_md": "Veuillez essayer d'envoyer un autre courriel plus tard.",
767
769
  "card.dkim.unavailable_md": "**[](DKIM#:glossary) \xE9valuation temporairement indisponible.**",
768
770
  "card.dmarc.domain-aligned": "Le domaine est align\xE9 avec le domaine exp\xE9diteur. Il peut contribuer \xE0 la r\xE9ussite de DMARC.",
771
+ "card.dmarc.domain-aligned-not-verified": "Le domaine est align\xE9 avec le domaine exp\xE9diteur, mais sa signature n'a pas \xE9t\xE9 v\xE9rifi\xE9e, il ne peut donc pas contribuer \xE0 la r\xE9ussite de DMARC.",
772
+ "card.dmarc.spf-domain-aligned-not-verified": "Le domaine est align\xE9 avec le domaine exp\xE9diteur, mais SPF n'a pas r\xE9ussi, il ne peut donc pas contribuer \xE0 la r\xE9ussite de DMARC.",
769
773
  "card.dmarc.domain-not-aligned": "Le domaine n'est pas align\xE9 avec le domaine exp\xE9diteur. Il ne peut pas contribuer \xE0 la r\xE9ussite de DMARC.",
770
774
  "card.dmarc.error.dns-timeout": "D\xE9lai d'attente DNS lors de la recherche de l'enregistrement DMARC",
771
775
  "card.dmarc.error.invalid-pct-value": "La valeur pct doit \xEAtre entre 0 et 100",
@@ -1334,7 +1338,7 @@ var enUS = {
1334
1338
  "investigate.card.dmarc.void-dynamic-dmarc-ns-record": "Dynamic DMARC record (NS) is added but Dynamic Services aren't enabled",
1335
1339
  "investigate.card.dmarc.policy-none-pct-0_md": "No reports will be sent with this policy, because the sample rate [](PCT#:glossary) is set to `0`.",
1336
1340
  "investigate.card.dmarc.policy-not-none-pct-0_md": "All **unauthorized** email will be delivered to receivers although the policy is set to [{policy}](DMARC_POLICY#:glossary), because the sample rate [](PCT#:glossary) is set to `0`.",
1337
- "investigate.card.dmarc.policy-inherited-from-org-domain_md": "The DMARC policy for the [](FROM#:glossary) `{fromDomain}` comes from the [](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
1341
+ "investigate.card.dmarc.policy-inherited-from-org-domain_md": "The DMARC policy for the [](FROM#:glossary) `{fromDomain}` comes from the [Organizational Domain](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
1338
1342
  "investigate.card.dmarc.category-policy-good_md": "The policy on your DNS conforms with the DMARC specification.",
1339
1343
  "investigate.card.dmarc.all-emails-reported_md": "All emails will be [REPORTED](DMARC_POLICY#:glossary).",
1340
1344
  "investigate.card.dmarc.all-emails-reported-only_md": "DMARC configured in report-only mode. All emails will only be [REPORTED](DMARC_POLICY#:glossary).",
@@ -1924,6 +1928,8 @@ var enUS = {
1924
1928
  "investigate.section-titles.bimi-domain": "DMARC Domains",
1925
1929
  "investigate.section-titles.dmarc-domain": "DMARC Domains",
1926
1930
  "investigate.card.dmarc.domain-aligned": "The domain is aligned with the From Domain. It can contribute to passing DMARC.",
1931
+ "investigate.card.dmarc.domain-aligned-not-verified": "The domain is aligned with the From Domain, but its signature did not verify, so it cannot contribute to passing DMARC.",
1932
+ "investigate.card.dmarc.spf-domain-aligned-not-verified": "The domain is aligned with the From Domain, but SPF did not pass, so it cannot contribute to passing DMARC.",
1927
1933
  "investigate.card.dmarc.domain-not-aligned": "The domain is not aligned with the From Domain. It cannot contribute to passing DMARC."
1928
1934
  };
1929
1935
 
@@ -2022,11 +2028,11 @@ var frFR = {
2022
2028
  "investigate.card.dmarc.policy-remaining-emails-pct-reject_md": "Les **{remainingPercent}%** restants de courriels non autorisés seront soumis à la politique de **mise en quarantaine**.",
2023
2029
  "investigate.card.dmarc.policy-remaining-emails-pct-quarantine_md": "Les **{remainingPercent}%** restants de courriels non autorisés seront **livrés normalement**.",
2024
2030
  "investigate.card.dmarc.no-policy_md": "**Aucune [FULL](DMARC_POLICY#:glossary) publiée sur le domaine**\n\n**`{policyDomain}`**",
2025
- "investigate.card.dmarc.dmarc-passed_md": "[](DMARC#:glossary) **passé**",
2026
- "investigate.card.dmarc.dmarc-passed-no-rua_md": "[](DMARC#:glossary) est conforme au BIMI **MAIS** il n'a pas d'entrée **aggregate report address** (rua).",
2027
- "investigate.card.dmarc.dmarc-no-rua_md": "[](DMARC#:glossary) n'a pas d'entrée **aggregate report address** (rua).",
2031
+ "investigate.card.dmarc.dmarc-passed_md": "[](DMARC#:glossary) **réussi**",
2032
+ "investigate.card.dmarc.dmarc-passed-no-rua_md": "[](DMARC#:glossary) est conforme à BIMI **MAIS** n'a pas d'**adresse de rapport agrégé** (rua).",
2033
+ "investigate.card.dmarc.dmarc-no-rua_md": "[](DMARC#:glossary) n'a pas d'**adresse de rapport agrégé** (rua).",
2028
2034
  "investigate.card.dmarc.dmarc-invalid-record_md": "[](DMARC#:glossary) a un enregistrement **invalide** .",
2029
- "investigate.card.dmarc.dmarc-failed_md": "[](DMARC#:glossary) **ne sont pas** conformes au BIMI",
2035
+ "investigate.card.dmarc.dmarc-failed_md": "Les paramètres [](DMARC#:glossary) **ne sont pas** conformes à BIMI",
2030
2036
  "investigate.card.dmarc.misplaced-policy_md": "**Le [FULL](DMARC_POLICY#:glossary) est mal placé.",
2031
2037
  "investigate.card.dmarc.misplaced-bad-policy_md": "**Le [FULL](DMARC_POLICY#:glossary) est mal placé et a une erreur de syntaxe:**",
2032
2038
  "investigate.card.dmarc.bad-policy_md_one": "**Le [FULL](DMARC_POLICY#:glossary) présente l'erreur de syntaxe suivante:**.",
@@ -2049,7 +2055,7 @@ var frFR = {
2049
2055
  "investigate.card.dmarc.void-dynamic-dmarc-ns-record": "L'enregistrement DMARC dynamique (NS) est ajouté mais les Dynamic Services ne sont pas activés.",
2050
2056
  "investigate.card.dmarc.policy-none-pct-0_md": "Aucun rapport ne sera envoyé avec cette politique, car le taux d'échantillonnage [](PCT#:glossary) est fixé à `0`.",
2051
2057
  "investigate.card.dmarc.policy-not-none-pct-0_md": "Tous les emails **non autorisés** seront délivrés aux destinataires bien que la politique soit définie sur [{policy}](DMARC_POLICY#:glossary), parce que le taux d'échantillonnage [](PCT#:glossary) est défini sur `0`.",
2052
- "investigate.card.dmarc.policy-inherited-from-org-domain_md": "La politique DMARC du [](FROM#:glossary) `{fromDomain}` provient du [](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
2058
+ "investigate.card.dmarc.policy-inherited-from-org-domain_md": "La politique DMARC du [](FROM#:glossary) `{fromDomain}` provient du [domaine organisationnel](ORGANIZATIONAL_DOMAIN#:glossary) `{orgDomain}`",
2053
2059
  "investigate.card.dmarc.category-policy-good_md": "La politique de votre DNS est conforme à la spécification DMARC.",
2054
2060
  "investigate.card.dmarc.all-emails-reported_md": "Tous les courriels seront [REPORTED](DMARC_POLICY#:glossary).",
2055
2061
  "investigate.card.dmarc.all-emails-reported-only_md": "DMARC configuré en mode rapport uniquement. Tous les courriels seront uniquement [REPORTED](DMARC_POLICY#:glossary).",
@@ -2104,21 +2110,21 @@ var frFR = {
2104
2110
  "investigate.card.subdo.blog": "Vous pouvez obtenir un aperçu complet de votre configuration SPF sur notre vérificateur SPF, ou en savoir plus sur l'attaque SubdoMailing sur notre base de connaissances.",
2105
2111
  "investigate.card.subdo.spf-subdo-poisoned-include_md": "Inclusion compromise : **{raw}**",
2106
2112
  "investigate.card.subdo.spf-subdo-poisoned-mechanism_md": "Inclusion compromise : **{raw}**",
2107
- "investigate.card.spf.spf-no-spf_md": "Pas de SPF configuré pour le domaine.",
2113
+ "investigate.card.spf.spf-no-spf_md": "Aucun SPF configuré pour le domaine.",
2108
2114
  "investigate.card.spf.spf-ipf-error-spf_md": "Erreur SPF : {msg}",
2109
2115
  "investigate.card.spf.spf-extra-spf-lookups_md": "Trop de recherches SPF ({lookupCount} trouvées, 10 autorisées)",
2110
2116
  "investigate.card.spf.spf-multiple-mx-providers_md": "Plusieurs fournisseurs de messagerie configurés dans les enregistrements MX de ce domaine.",
2111
- "investigate.card.spf.spf-multiple-spf_md": "Plusieurs enregistrements SPF ont été trouvés pour ce domaine",
2112
- "investigate.card.spf.spf-multiple-all_md": "Plusieurs mécanismes **tous** dans l'enregistrement SPF",
2113
- "investigate.card.spf.spf-recursive-include-spf_md": "L'inclusion SPF redondante peut être supprimée car elle renvoie à votre propre domaine.",
2114
- "investigate.card.spf.spf-v-tag-duplicated-spf_md": "La balise de la version est dupliquée.",
2115
- "investigate.card.spf.spf-all-not-last_md": "Toute donnée située après le **tout** est ignorée.",
2116
- "investigate.card.spf.spf-all-and-redirect_md": "Toute **redirection** est ignorée s'il existe un mécanisme **tous**.",
2117
- "investigate.card.spf.spf-all-plus-warning_md": "Le mécanisme **+all** ne doit pas être utilisé car il autorise toutes les adresses IP d'envoi.",
2117
+ "investigate.card.spf.spf-multiple-spf_md": "Plusieurs enregistrements SPF trouvés pour ce domaine",
2118
+ "investigate.card.spf.spf-multiple-all_md": "Plusieurs mécanismes **all** dans l'enregistrement SPF",
2119
+ "investigate.card.spf.spf-recursive-include-spf_md": "L'inclusion SPF redondante peut être supprimée car elle pointe vers votre propre domaine",
2120
+ "investigate.card.spf.spf-v-tag-duplicated-spf_md": "La balise de version est dupliquée.",
2121
+ "investigate.card.spf.spf-all-not-last_md": "Toute donnée après le **all** est ignorée.",
2122
+ "investigate.card.spf.spf-all-and-redirect_md": "Toute **redirection** est ignorée s'il y a un mécanisme **all**.",
2123
+ "investigate.card.spf.spf-all-plus-warning_md": "Le mécanisme **+all** ne devrait pas être utilisé car il autorise toutes les adresses IP d'envoi.",
2118
2124
  "investigate.card.spf.spf-all-question-mark-warning_md": "**?all** ne doit pas être utilisé car il permet une réponse ambiguë.",
2119
- "investigate.card.spf.spf-no-all_md": "Il est recommandé d'utiliser un modificateur **redirect** ou un mécanisme **all** pour mettre fin explicitement au traitement.",
2120
- "investigate.card.spf.spf-invalid-spf1-header_md": "Enregistrement SPF non valide",
2121
- "investigate.card.spf.spf-invalid-spf-header-include_md": "L'enregistrement SPF n'est pas valide car il contient un espace entre la déclaration include :.",
2125
+ "investigate.card.spf.spf-no-all_md": "Il est recommandé d'utiliser soit un modificateur **redirect** soit un mécanisme **all** pour terminer explicitement le traitement.",
2126
+ "investigate.card.spf.spf-invalid-spf1-header_md": "Enregistrement SPF invalide",
2127
+ "investigate.card.spf.spf-invalid-spf-header-include_md": "L'enregistrement SPF est invalide car il a un espace entre l'instruction include:.",
2122
2128
  "investigate.card.spf.spf-ip-subnet-mask-too-small_md": "Le sous-réseau **{raw}** permet à un grand nombre d'hôtes d'envoyer du courrier électronique au nom du domaine.",
2123
2129
  "investigate.card.spf.spf-ip-subnet-overlaps-public-range_md": "Le sous-réseau **{raw}** chevauche la plage **{value}**, appartenant à un fournisseur de cloud accessible au public : **{sourceRef}**.",
2124
2130
  "investigate.card.spf.spf-directive-permissive-exists_md": "Le mécanisme **{raw}** a été défini pour permettre à n'importe quel hôte d'envoyer un courrier électronique au nom du domaine.",
@@ -2641,6 +2647,8 @@ var frFR = {
2641
2647
  "investigate.section-titles.bimi-domain": "Domaines DMARC",
2642
2648
  "investigate.section-titles.dmarc-domain": "Domaines DMARC",
2643
2649
  "investigate.card.dmarc.domain-aligned": "Le domaine est aligné avec le domaine expéditeur. Il peut contribuer à la réussite de DMARC.",
2650
+ "investigate.card.dmarc.domain-aligned-not-verified": "Le domaine est aligné avec le domaine expéditeur, mais sa signature n'a pas été vérifiée, il ne peut donc pas contribuer à la réussite de DMARC.",
2651
+ "investigate.card.dmarc.spf-domain-aligned-not-verified": "Le domaine est aligné avec le domaine expéditeur, mais SPF n'a pas réussi, il ne peut donc pas contribuer à la réussite de DMARC.",
2644
2652
  "investigate.card.dmarc.domain-not-aligned": "Le domaine n'est pas aligné avec le domaine expéditeur. Il ne peut pas contribuer à la réussite de DMARC."
2645
2653
  };
2646
2654
 
@@ -6982,6 +6990,206 @@ function renderList(items) {
6982
6990
  }, item)));
6983
6991
  }
6984
6992
 
6993
+ // =============================================================================
6994
+ // STATUS LIST RENDERING
6995
+ // =============================================================================
6996
+
6997
+ /**
6998
+ * Status-list container. Mirrors the plain `<ul>` that `renderList` emits so the
6999
+ * two list variants sit at the same indent and rhythm, adding only the per-entry
7000
+ * row layout the status icons need.
7001
+ */
7002
+ const StyledStatusList = styled.ul`
7003
+ li {
7004
+ display: flex;
7005
+ align-items: center;
7006
+ gap: 0.35em;
7007
+ min-width: 0;
7008
+ }
7009
+ `;
7010
+
7011
+ /**
7012
+ * The entry's text (a domain, on the alignment row).
7013
+ *
7014
+ * `min-width: 0` has to be on the text itself, not just the `li`: a flex item
7015
+ * defaults to `min-width: auto`, which refuses to shrink below its content's
7016
+ * intrinsic width, so a long unbroken domain would push the status icon
7017
+ * (`flex-shrink: 0`) out of the row. `overflow-wrap: anywhere` lets a label with no
7018
+ * natural break opportunity wrap instead of overflowing.
7019
+ *
7020
+ * A dedicated component rather than an `li > span` descendant selector, which would
7021
+ * also be matched by the strike wrapper if the markup were ever reshuffled.
7022
+ */
7023
+ const StyledStatusListText = styled.span`
7024
+ min-width: 0;
7025
+ overflow-wrap: anywhere;
7026
+ `;
7027
+
7028
+ // A div, not a span: TooltipInPortalWrapper renders a <div> (TooltipInPortal.tsx),
7029
+ // and a <div> inside a <span> is invalid HTML — span is phrasing content, div is
7030
+ // flow content. `display: inline-flex` keeps it laid out inline regardless. This
7031
+ // matches the row-level alignment icon in SignalCardExtractedItems, which wraps
7032
+ // the same tooltip in a div for the same reason.
7033
+ const StyledStatusListIcon = styled.div`
7034
+ display: inline-flex;
7035
+ align-items: center;
7036
+ flex-shrink: 0;
7037
+ `;
7038
+
7039
+ /**
7040
+ * Diagonal strike across a status icon, for the not-aligned case.
7041
+ *
7042
+ * Drawn in CSS over the mdi glyph rather than inlining a bespoke crossed-out SVG,
7043
+ * so it scales with the glyph and the DS keeps a single source for the underlying
7044
+ * icon.
7045
+ *
7046
+ * The colour is declared **here, once**, on the wrapper: the `::after` gradient
7047
+ * picks it up through `currentColor`, and the `Icon` inside is passed
7048
+ * `currentColor` too, so glyph and strike cannot drift apart. Setting it on the
7049
+ * pseudo-element instead would make `currentColor` resolve to that local
7050
+ * declaration rather than being inherited, which is the trap this replaced.
7051
+ */
7052
+ const StyledStruckThrough = styled.span`
7053
+ position: relative;
7054
+ display: inline-flex;
7055
+ align-items: center;
7056
+ color: ${COLOR.main.red};
7057
+
7058
+ &::after {
7059
+ content: '';
7060
+ position: absolute;
7061
+ inset: 0;
7062
+ background: linear-gradient(
7063
+ to top right,
7064
+ transparent calc(50% - 0.7px),
7065
+ currentColor calc(50% - 0.7px),
7066
+ currentColor calc(50% + 0.7px),
7067
+ transparent calc(50% + 0.7px)
7068
+ );
7069
+ pointer-events: none;
7070
+ }
7071
+ `;
7072
+
7073
+ /**
7074
+ * Status icon for a single {@link StatusListItem}.
7075
+ *
7076
+ * Mirrors the icon vocabulary `getIcon` uses for row-level statuses in
7077
+ * SignalCardExtractedItems, so a per-entry tick reads identically to a row-level
7078
+ * one. A `''` status means "no status" — the entry renders as plain text, exactly
7079
+ * as a `ListData` entry would.
7080
+ *
7081
+ * Per Antony's spec (2026-08-06) this row uses an **align** glyph rather than a
7082
+ * generic tick/cross, with the not-aligned case struck through:
7083
+ *
7084
+ * | status | `matchMode` | meaning | render |
7085
+ * | --- | --- | --- | --- |
7086
+ * | `good`/`warning` | `exact` | signing domain equals the From domain | green `FormatAlignJustify` |
7087
+ * | `good`/`warning` | `organizational` | shares the Organizational Domain | green `FormatAlignRight` |
7088
+ * | `danger` | — | does not align | red `FormatAlignRight`, struck through |
7089
+ *
7090
+ * **`warning` renders as aligned, not amber.** This row is about *domains*, not
7091
+ * mechanism results (Antony, option (a), 2026-08-06). signal-logic's `warning`
7092
+ * branch fires on `aligned && !verified`, where `verified` is
7093
+ * `signature.code === 'pass'` — a property of the crypto, not of the domain pair —
7094
+ * and the DKIM card already reports verification per signature. Colouring it
7095
+ * differently here would make one icon answer two questions and duplicate the DKIM
7096
+ * card, inviting the two to disagree. The distinct `tooltipKey`
7097
+ * (`card.dmarc.domain-aligned-not-verified`) is preserved, so the detail is still
7098
+ * available on hover.
7099
+ *
7100
+ * The known cost: an aligned-but-unverified domain cannot actually contribute to
7101
+ * DMARC passing, so green slightly overstates it. That is conveyed by the
7102
+ * "Alignment Check" prose rows and the overall card verdict, which read as failed —
7103
+ * the honest place for it, rather than overloading a per-domain icon.
7104
+ *
7105
+ * **Exact vs organizational** comes from `matchMode`, added upstream by WEB-1203.
7106
+ * An exact match is *inherently* aligned under either `adkim` mode, so it earns the
7107
+ * fully-justified glyph; an Organizational Domain match only aligns under `adkim=r`
7108
+ * and gets the right-aligned one. `matchMode` is absent on non-aligned entries and
7109
+ * on the fallback `ListData` shape, so a missing value degrades to `FormatAlignRight`
7110
+ * rather than guessing.
7111
+ */
7112
+ function renderItemStatusIcon(status, matchMode, accessibleName) {
7113
+ // The DS `Icon` is decorative by default — it sets aria-hidden unless given an
7114
+ // aria-label. The status here is not decorative: it is the only thing telling a
7115
+ // reader whether the domain aligned, and the tooltip carrying that meaning is
7116
+ // hover-only. So pass the translated tooltip text as the accessible name, with
7117
+ // `aria-hidden={false}` to un-hide it, and fall back to the bare status word
7118
+ // when an entry supplies no tooltipKey.
7119
+ const label = accessibleName !== null && accessibleName !== void 0 ? accessibleName : status || undefined;
7120
+ const aria = label ? {
7121
+ 'aria-label': label,
7122
+ 'aria-hidden': false
7123
+ } : {};
7124
+ switch (status) {
7125
+ // `warning` is deliberately identical to `good` — see above.
7126
+ case 'good':
7127
+ case 'warning':
7128
+ return /*#__PURE__*/React__default.createElement(Icon, _extends({
7129
+ icon: matchMode === 'exact' ? mdiFormatAlignJustify : mdiFormatAlignRight,
7130
+ color: COLOR.main.green,
7131
+ size: "small"
7132
+ }, aria));
7133
+ case 'danger':
7134
+ // Struck through: aligned-looking glyph, cancelled. `currentColor` so the
7135
+ // glyph and the strike share the one colour declared on the wrapper.
7136
+ return /*#__PURE__*/React__default.createElement(StyledStruckThrough, null, /*#__PURE__*/React__default.createElement(Icon, _extends({
7137
+ icon: mdiFormatAlignRight,
7138
+ color: "currentColor",
7139
+ size: "small"
7140
+ }, aria)));
7141
+ default:
7142
+ return null;
7143
+ }
7144
+ }
7145
+
7146
+ /**
7147
+ * One status-list entry: its text, plus its own status icon and tooltip.
7148
+ *
7149
+ * A component rather than a plain render call because the tooltip text needs
7150
+ * translating, and `renderStructuredData` is a pure function called from the
7151
+ * adapter layer outside React — so it has no `t` to pass down. Resolving the key
7152
+ * here keeps the renderer's signature unchanged and matches how the rest of the
7153
+ * card resolves strings.
7154
+ */
7155
+ const StatusListEntry = _ref => {
7156
+ let {
7157
+ item
7158
+ } = _ref;
7159
+ const {
7160
+ t
7161
+ } = useSignalCardTranslation('investigate');
7162
+ const tooltip = item.tooltipKey ? t(item.tooltipKey) : undefined;
7163
+ // The tooltip text doubles as the icon's accessible name — see renderItemStatusIcon.
7164
+ const icon = renderItemStatusIcon(item.status, item.matchMode, tooltip);
7165
+ return /*#__PURE__*/React__default.createElement("li", null, /*#__PURE__*/React__default.createElement(StyledStatusListText, null, item.text), icon ? /*#__PURE__*/React__default.createElement(StyledStatusListIcon, null, tooltip ? /*#__PURE__*/React__default.createElement(TooltipInPortalWrapper, {
7166
+ message: tooltip
7167
+ }, /*#__PURE__*/React__default.createElement("span", {
7168
+ style: {
7169
+ display: 'inline-flex'
7170
+ }
7171
+ }, icon)) : icon) : null);
7172
+ };
7173
+
7174
+ /**
7175
+ * Render a list whose entries each carry their own status.
7176
+ *
7177
+ * Used by the DMARC card's "DKIM Signing Domain" row, where one message can
7178
+ * carry several DKIM signatures whose alignment differs — an aligned domain gets a
7179
+ * green align glyph while a non-aligned one gets a red struck-through glyph, in the
7180
+ * same list. The row-level status is cleared by signal-logic whenever per-entry
7181
+ * statuses are present, so only these icons render and nothing double-counts.
7182
+ */
7183
+ function renderStatusList(items) {
7184
+ if (!(items !== null && items !== void 0 && items.length)) {
7185
+ return null;
7186
+ }
7187
+ return /*#__PURE__*/React__default.createElement(StyledStatusList, null, items.map((item, i) => /*#__PURE__*/React__default.createElement(StatusListEntry, {
7188
+ key: i,
7189
+ item: item
7190
+ })));
7191
+ }
7192
+
6985
7193
  // =============================================================================
6986
7194
  // SIGNED HEADERS RENDERING
6987
7195
  // =============================================================================
@@ -7180,6 +7388,13 @@ function renderStructuredData(data) {
7180
7388
  }
7181
7389
  case 'list':
7182
7390
  return renderList(data.items);
7391
+
7392
+ // Additive: `list` above still ships. signal-logic falls back to it whenever
7393
+ // per-signature alignment cannot be computed (Domain Analyzer / `fromDns`
7394
+ // results, no `fromDomain`, or a domain sourced from the `dmarc.dkimDomain`
7395
+ // scalar rather than a signature), so both variants must keep working.
7396
+ case 'status-list':
7397
+ return renderStatusList(data.items);
7183
7398
  case 'highlighted-text':
7184
7399
  return /*#__PURE__*/React__default.createElement(WordHighlighter$1, {
7185
7400
  searchWord: data.searchWord,