This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new da4fafc5ca2 UI: Fix visual and console-error bugs in Dag list and 
details (#75418)
da4fafc5ca2 is described below

commit da4fafc5ca22b25b670cf8d16daeac3ad4575c87
Author: Brent Bovenzi <[email protected]>
AuthorDate: Sun Oct 25 01:30:35 2026 -0400

    UI: Fix visual and console-error bugs in Dag list and details (#75418)
    
    * UI: Fix visual and console-error bugs in Dag list and details
    
    These are pre-existing bugs, all found while testing an unrelated change and
    all in the same corner of the UI, so they travel together rather than as a
    handful of one-line pull requests.
    
    Several survived review because nothing asserted on them: the Gantt axis 
only
    collided its React keys on runs of roughly twenty seconds or less, the 
warnings
    marker only stretched on a Dag that actually had a warning, and the HITL tab
    lookup only logged its 404 on a task that was not human-in-the-loop. Each 
now
    has a test that fails without its fix.
    
    The Dag status markers are consolidated because #74516 moved every other Dag
    status onto the shared StatusChip and left this one behind. That left two
    markers that looked identical, sat in different containers, and meant 
different
    things, with no single place to see everything wrong with a Dag.
    
    Co-Authored-By: Claude <[email protected]>
    
    * UI: Address review on the Dag status chip and warning translations
    
    The merged status chip was introducing the same class of console error the 
rest
    of the change removes. Rich detail was rendered inside a paragraph, which 
cannot
    hold the blocks the import-error view brings, so the browser closed the
    paragraph early and the file row inherited an error colour and type scale 
meant
    only for the traceback. Tests now fail on React's markup warnings instead of
    letting them scroll past in CI output, which is what let this through: the 
test
    that opens that very modal was passing while logging two nesting errors.
    
    Three assertions could not fail as written. One searched for a nested button
    only inside the inner button, so restoring the old wrapper would have gone
    unnoticed; one asked for a chip named after a deactivation that the 
import-error
    branch can never produce; one opened the modal for a failed request without
    checking the failure reached it.
    
    Warning types reached the heading as the backend's own lowercase enum 
values,
    which would have shown untranslated English in every locale, so they are
    translated now rather than passed through. The chip and its modal each 
walked
    the severity order themselves and would have disagreed the moment one 
changed.
    
    Co-Authored-By: Claude <[email protected]>
    
    ---------
    
    Co-authored-by: Claude <[email protected]>
---
 .../airflow/ui/public/i18n/locales/ar/common.json  |   1 -
 .../ui/public/i18n/locales/ar/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/ca/common.json  |   1 -
 .../ui/public/i18n/locales/ca/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/de/common.json  |   1 -
 .../ui/public/i18n/locales/de/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/el/common.json  |   1 -
 .../ui/public/i18n/locales/el/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/en/common.json  |   1 -
 .../ui/public/i18n/locales/en/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/es/common.json  |   1 -
 .../ui/public/i18n/locales/es/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/fr/common.json  |   1 -
 .../ui/public/i18n/locales/fr/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/he/common.json  |   1 -
 .../ui/public/i18n/locales/he/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/hi/common.json  |   1 -
 .../ui/public/i18n/locales/hi/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/hu/common.json  |   1 -
 .../ui/public/i18n/locales/hu/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/it/common.json  |   1 -
 .../ui/public/i18n/locales/it/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/ja/common.json  |   1 -
 .../ui/public/i18n/locales/ja/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/ko/common.json  |   1 -
 .../ui/public/i18n/locales/ko/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/nl/common.json  |   1 -
 .../ui/public/i18n/locales/nl/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/pl/common.json  |   1 -
 .../ui/public/i18n/locales/pl/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/pt/common.json  |   1 -
 .../ui/public/i18n/locales/pt/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/ru/common.json  |   1 -
 .../ui/public/i18n/locales/ru/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/th/common.json  |   1 -
 .../ui/public/i18n/locales/th/components.json      |   7 +
 .../airflow/ui/public/i18n/locales/tr/common.json  |   1 -
 .../ui/public/i18n/locales/tr/components.json      |   7 +
 .../ui/public/i18n/locales/zh-CN/common.json       |   1 -
 .../ui/public/i18n/locales/zh-CN/components.json   |   7 +
 .../ui/public/i18n/locales/zh-TW/common.json       |   1 -
 .../ui/public/i18n/locales/zh-TW/components.json   |   7 +
 .../ui/src/components/DAGWarningsModal.test.tsx    |  33 -----
 .../airflow/ui/src/components/DAGWarningsModal.tsx |  62 +++++---
 .../ui/src/components/DagDeactivatedChip.test.tsx  | 154 --------------------
 .../ui/src/components/DagDeactivatedChip.tsx       |  70 ---------
 ...ortErrorModal.tsx => DagImportErrorDetails.tsx} |  20 +--
 .../ui/src/components/DagImportErrorModal.tsx      |  52 +------
 .../ui/src/components/DagStatusChip.test.tsx       | 161 +++++++++++++++++++++
 .../airflow/ui/src/components/DagStatusChip.tsx    |  90 ++++++++++++
 .../ui/src/components/DataTable/DataTable.tsx      |   1 +
 .../src/airflow/ui/src/components/ErrorAlert.tsx   |  15 +-
 .../ui/src/components/FilterBar/FilterBar.test.tsx |  15 ++
 .../ui/src/components/FilterBar/FilterPill.tsx     |  29 ++--
 .../airflow/ui/src/components/StatusMessage.tsx    |  52 +++++++
 .../src/airflow/ui/src/components/WarningAlert.tsx |  28 ++--
 .../ui/src/hooks/useRequiredActionTabs.test.tsx    |  66 +++++++++
 .../airflow/ui/src/hooks/useRequiredActionTabs.ts  |  31 ++--
 .../ui/src/layouts/Details/DetailsLayout.tsx       |  46 +-----
 .../ui/src/layouts/Details/Gantt/utils.test.ts     |  18 ++-
 .../airflow/ui/src/layouts/Details/Gantt/utils.ts  |  13 +-
 .../ui/src/pages/AssetsList/AssetsList.test.tsx    |  14 +-
 .../src/airflow/ui/src/pages/DagsList/DagCard.tsx  |   8 +-
 .../DagsList/DagsFilters/DagsFilters.test.tsx      |   6 +-
 .../ui/src/pages/DagsList/RecentRuns.test.tsx      |  58 ++++++++
 .../airflow/ui/src/pages/DagsList/RecentRuns.tsx   |  10 +-
 .../src/airflow/ui/src/utils/dagStatus.test.ts     |  84 +++++++++++
 airflow-core/src/airflow/ui/src/utils/dagStatus.ts |  75 ++++++++++
 .../src/airflow/ui/src/utils/errorHandling.ts      |  23 +++
 airflow-core/src/airflow/ui/src/utils/index.ts     |   3 +-
 airflow-core/src/airflow/ui/testsSetup.ts          |  29 ++++
 71 files changed, 956 insertions(+), 478 deletions(-)

diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ar/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ar/common.json
index 7fe054512e3..e0dd904df03 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ar/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ar/common.json
@@ -111,7 +111,6 @@
   "dagRun_zero": "لا يوجد أي مثيل تشغيل لDag",
   "dagRunId": "معرف تشغيل الDag",
   "dagRunState": "حالة تشغيل Dag",
-  "dagWarnings": "تحذيرات الDag",
   "defaultToGraphView": "عرض الرسم البياني تلقائياً",
   "defaultToGridView": "عرض الشبكة تلقائياً",
   "delete": "حذف",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json
index 322ba48f78e..c4d336b4149 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "خطأ",
     "errorAndWarning": "1 خطأ و{{warningText}}",
+    "types": {
+      "asset conflict": "تعارض في الأصل",
+      "duplicate dag id": "تكرار Dag ID",
+      "non-existent pool": "مجموعة موارد غير موجودة",
+      "runtime varying value": "قيمة متغيّرة أثناء التشغيل",
+      "task group cycle": "دورة في مجموعة المهام"
+    },
     "warning_few": "{{count, number}} تحذيرات",
     "warning_many": "{{count, number}} تحذير",
     "warning_one": "1 تحذير",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ca/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ca/common.json
index 5a4dbb4e241..100bdcbe1ab 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ca/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ca/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "Execucions de Dag",
   "dagRunId": "ID d'Execució del Dag",
   "dagRunState": "Estat de l'execució de Dag",
-  "dagWarnings": "Advertències/errors del Dag",
   "delete": "Eliminar",
   "diff": "Diferència",
   "diffCompareWith": "Comparar amb",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ca/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ca/components.json
index f0b61365489..3de6456ef04 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ca/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ca/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 Error",
     "errorAndWarning": "1 Error i {{warningText}}",
+    "types": {
+      "asset conflict": "Conflicte d'Asset",
+      "duplicate dag id": "Dag ID duplicat",
+      "non-existent pool": "Pool inexistent",
+      "runtime varying value": "Valor variable en temps d'execució",
+      "task group cycle": "Cicle al grup de tasques"
+    },
     "warning_one": "1 Advertència",
     "warning_other": "{{count, number}} Advertències"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/de/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/de/common.json
index 74da2667749..655ce4adfe3 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/de/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/de/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "Dag Läufe",
   "dagRunId": "Dag Lauf ID",
   "dagRunState": "Status des Dag Laufs",
-  "dagWarnings": "Dag Warnungen und Fehler",
   "delete": "Löschen",
   "diff": "Differenzansicht",
   "diffCompareWith": "Vergleichen mit",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/de/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/de/components.json
index 03e94313f8e..61bd746d492 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/de/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/de/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 Fehler",
     "errorAndWarning": "1 Fehler und {{warningText}}",
+    "types": {
+      "asset conflict": "Asset-Konflikt",
+      "duplicate dag id": "Doppelte Dag ID",
+      "non-existent pool": "Nicht vorhandener Pool",
+      "runtime varying value": "Zur Laufzeit veränderlicher Wert",
+      "task group cycle": "Zyklus in der Task Gruppe"
+    },
     "warning_one": "1 Warnung",
     "warning_other": "{{count, number}} Warnungen"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json
index 969d9fe839c..ab842f13e32 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "Εκτελέσεις Dag",
   "dagRunId": "ID Εκτέλεσης Dag",
   "dagRunState": "Κατάσταση Εκτέλεσης Dag",
-  "dagWarnings": "Προειδοποιήσεις/Σφάλματα Dag",
   "delete": "Διαγραφή",
   "diff": "Διαφορές",
   "diffCompareWith": "Σύγκριση με",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/el/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/el/components.json
index 35391b38f71..91a2cabf1fd 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/el/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/el/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 Σφάλμα",
     "errorAndWarning": "1 Σφάλμα και {{warningText}}",
+    "types": {
+      "asset conflict": "Διένεξη οντότητας",
+      "duplicate dag id": "Διπλότυπο Dag ID",
+      "non-existent pool": "Ανύπαρκτο Pool",
+      "runtime varying value": "Τιμή που μεταβάλλεται κατά την εκτέλεση",
+      "task group cycle": "Κύκλος στην ομάδα εργασιών"
+    },
     "warning_one": "1 Προειδοποίηση",
     "warning_other": "{{count, number}} Προειδοποιήσεις"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index 7df9e8bc34c..84d6d786f61 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "Dag Runs",
   "dagRunId": "Dag Run ID",
   "dagRunState": "Dag Run State",
-  "dagWarnings": "Dag warnings/errors",
   "delete": "Delete",
   "diff": "Diff",
   "diffCompareWith": "Compare with",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/en/components.json
index 2a90a9cd84a..40d5f6ffff6 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 Error",
     "errorAndWarning": "1 Error and {{warningText}}",
+    "types": {
+      "asset conflict": "Asset conflict",
+      "duplicate dag id": "Duplicate Dag ID",
+      "non-existent pool": "Non-existent pool",
+      "runtime varying value": "Runtime-varying value",
+      "task group cycle": "Task group cycle"
+    },
     "warning_one": "1 Warning",
     "warning_other": "{{count, number}} Warnings"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/es/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/es/common.json
index 76f450bbc8a..f445446ca1e 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/es/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/es/common.json
@@ -89,7 +89,6 @@
   "dagRun_other": "Ejecuciones del Dag",
   "dagRunId": "ID de la Ejecución del Dag",
   "dagRunState": "Estado de la Ejecución del Dag",
-  "dagWarnings": "Advertencias/Errores del Dag",
   "delete": "Eliminar",
   "diff": "Diff",
   "diffCompareWith": "Comparar con",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json
index 62aee50f561..06183fc5876 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 Error",
     "errorAndWarning": "1 Error y {{warningText}}",
+    "types": {
+      "asset conflict": "Conflicto de Asset",
+      "duplicate dag id": "Dag ID duplicado",
+      "non-existent pool": "Pool inexistente",
+      "runtime varying value": "Valor variable en tiempo de ejecución",
+      "task group cycle": "Ciclo en el grupo de tareas"
+    },
     "warning_one": "1 Advertencia",
     "warning_other": "{{count, number}} Advertencias"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/fr/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/fr/common.json
index e2375bdb310..1c92b447dea 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/fr/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/fr/common.json
@@ -99,7 +99,6 @@
   "dagRun_other": "Exécutions de Dag",
   "dagRunId": "ID d'exécution du Dag",
   "dagRunState": "État de l'exécution du Dag",
-  "dagWarnings": "Avertissements/erreurs du Dag",
   "delete": "Supprimer",
   "diff": "Différences",
   "diffCompareWith": "Comparer avec",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/fr/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/fr/components.json
index 5c21550d331..5e43938f5f8 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/fr/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/fr/components.json
@@ -60,6 +60,13 @@
   "dagWarnings": {
     "error_one": "1 erreur",
     "errorAndWarning": "1 erreur et {{warningText}}",
+    "types": {
+      "asset conflict": "Conflit d'Asset",
+      "duplicate dag id": "Dag ID en double",
+      "non-existent pool": "Pool inexistant",
+      "runtime varying value": "Valeur variable à l'exécution",
+      "task group cycle": "Cycle dans le groupe de tâches"
+    },
     "warning_many": "{{count, number}} avertissements",
     "warning_one": "1 avertissement",
     "warning_other": "{{count, number}} avertissements"
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/he/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/he/common.json
index d54aa4455f1..a9aae364cc7 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/he/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/he/common.json
@@ -99,7 +99,6 @@
   "dagRun_two": "הרצת Dags",
   "dagRunId": "מזהה הרצת Dag",
   "dagRunState": "מצב הרצת Dag",
-  "dagWarnings": "אזהרות/שגיאות Dag",
   "delete": "מחק",
   "diff": "השוואה",
   "diffCompareWith": "השווה עם",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json
index b8e37339073..1363f41694d 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json
@@ -60,6 +60,13 @@
   "dagWarnings": {
     "error_one": "שגיאה",
     "errorAndWarning": "שגיאה אחת ו-{{warningText}}",
+    "types": {
+      "asset conflict": "התנגשות בנכס",
+      "duplicate dag id": "Dag ID כפול",
+      "non-existent pool": "מאגר משאבים לא קיים",
+      "runtime varying value": "ערך משתנה בזמן ריצה",
+      "task group cycle": "מעגל בקבוצת המשימות"
+    },
     "warning_one": "אזהרה אחת",
     "warning_other": "{{count, number}} אזהרות",
     "warning_two": "{{count, number}} אזהרות"
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json
index e1ad41320cc..e8e5f78ba84 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "डैग रन्स",
   "dagRunId": "डैग रन ID",
   "dagRunState": "डैग रन स्थिति",
-  "dagWarnings": "डैग चेतावनियां/त्रुटियां",
   "delete": "हटाएं",
   "diff": "अंतर",
   "diffCompareWith": "तुलना करें",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json
index b41e9aeddb7..3ecd092ae31 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 त्रुटि",
     "errorAndWarning": "1 त्रुटि और {{warningText}}",
+    "types": {
+      "asset conflict": "एसेट विरोध",
+      "duplicate dag id": "डुप्लिकेट Dag ID",
+      "non-existent pool": "अनुपस्थित पूल",
+      "runtime varying value": "रनटाइम पर बदलने वाला मान",
+      "task group cycle": "Task Group चक्र"
+    },
     "warning_one": "1 चेतावनी",
     "warning_other": "{{count, number}} चेतावनियां"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json
index 4814c9514ab..7f98cff7322 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json
@@ -83,7 +83,6 @@
   "dagRun_one": "Dag futás",
   "dagRun_other": "Dag futások",
   "dagRunId": "Dag futás azonosító",
-  "dagWarnings": "Dag figyelmeztetések/hibák",
   "defaultToGraphView": "Alapértelmezett gráf nézet",
   "defaultToGridView": "Alapértelmezett rács nézet",
   "delete": "Törlés",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json
index 99cc08dec7e..4664bfab45e 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json
@@ -47,6 +47,13 @@
   "dagWarnings": {
     "error_one": "1 hiba",
     "errorAndWarning": "1 hiba és {{warningText}}",
+    "types": {
+      "asset conflict": "Adatkészlet-ütközés",
+      "duplicate dag id": "Ismétlődő Dag ID",
+      "non-existent pool": "Nem létező Pool",
+      "runtime varying value": "Futásidőben változó érték",
+      "task group cycle": "Ciklus a feladatcsoportban"
+    },
     "warning_one": "1 figyelmeztetés",
     "warning_other": "{{count, number}} figyelmeztetés"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/it/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/it/common.json
index 5c454aa04e3..50b5a20522e 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/it/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/it/common.json
@@ -87,7 +87,6 @@
   "dagRun_other": "Run del Dag",
   "dagRun_zero": "Nessun run del Dag",
   "dagRunId": "ID del Run del Dag",
-  "dagWarnings": "Avvisi/Errori del Dag",
   "defaultToGraphView": "Default to vista grafica",
   "defaultToGridView": "Default to vista griglia",
   "direction": "Direzione",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json
index af70cc8fab3..6d97b28d7fd 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json
@@ -48,6 +48,13 @@
     "error_other": "{{count, number}} Errori",
     "error_zero": "Nessun errore",
     "errorAndWarning": "1 Errore e {{warningText}}",
+    "types": {
+      "asset conflict": "Conflitto di Asset",
+      "duplicate dag id": "Dag ID duplicato",
+      "non-existent pool": "Pool inesistente",
+      "runtime varying value": "Valore variabile a runtime",
+      "task group cycle": "Ciclo nel gruppo di task"
+    },
     "warning_many": "{{count, number}} Avvisi",
     "warning_one": "1 Avviso",
     "warning_other": "{{count, number}} Avvisi",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ja/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ja/common.json
index 46276b6f8f4..ea6f0b66d87 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ja/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ja/common.json
@@ -76,7 +76,6 @@
   "dagRun_one": "Dag 実行",
   "dagRun_other": "Dag 実行",
   "dagRunId": "Dag 実行 ID",
-  "dagWarnings": "Dag 実行警告/エラー",
   "defaultToGraphView": "グラフ表示を既定にします",
   "defaultToGridView": "グリッド表示を既定にします",
   "diff": "差分",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ja/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ja/components.json
index 2d08ff43186..60266dcd8b7 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ja/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ja/components.json
@@ -43,6 +43,13 @@
   "dagWarnings": {
     "error_one": "1 件のエラー",
     "errorAndWarning": "1 件のエラーと {{warningText}}",
+    "types": {
+      "asset conflict": "アセットの競合",
+      "duplicate dag id": "Dag ID の重複",
+      "non-existent pool": "存在しないプール",
+      "runtime varying value": "実行時に変化する値",
+      "task group cycle": "タスクグループの循環"
+    },
     "warning_one": "1 件の警告",
     "warning_other": "{{count, number}} 件の警告"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ko/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ko/common.json
index cb55dfe9fc2..ce01cd49ac7 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ko/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ko/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "Dag 실행들",
   "dagRunId": "Dag 실행 ID",
   "dagRunState": "Dag 실행 상태",
-  "dagWarnings": "Dag 경고/오류",
   "delete": "삭제",
   "diff": "비교",
   "diffCompareWith": "비교 대상",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ko/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ko/components.json
index dfabed3c967..6574b244641 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ko/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ko/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "오류 1개",
     "errorAndWarning": "오류 1개 및 {{warningText}}",
+    "types": {
+      "asset conflict": "에셋 충돌",
+      "duplicate dag id": "중복된 Dag ID",
+      "non-existent pool": "존재하지 않는 풀",
+      "runtime varying value": "런타임에 변하는 값",
+      "task group cycle": "태스크 그룹 순환"
+    },
     "warning_one": "경고 1개",
     "warning_other": "경고 {{count, number}}개"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/nl/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/nl/common.json
index 54a33939c19..e8356980918 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/nl/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/nl/common.json
@@ -83,7 +83,6 @@
   "dagRun_one": "Dag Run",
   "dagRun_other": "Dag Runs",
   "dagRunId": "Dag Run ID",
-  "dagWarnings": "Dag waarschuwingen/fouten",
   "defaultToGraphView": "Standaard grafiekweergave",
   "defaultToGridView": "Standaard rasterweergave",
   "delete": "Verwijder",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json
index d58405deeb3..a0c8169b959 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json
@@ -47,6 +47,13 @@
   "dagWarnings": {
     "error_one": "1 fout",
     "errorAndWarning": "1 fout en {{warningText}}",
+    "types": {
+      "asset conflict": "Asset-conflict",
+      "duplicate dag id": "Dubbele Dag ID",
+      "non-existent pool": "Niet-bestaande Pool",
+      "runtime varying value": "Waarde die tijdens runtime verandert",
+      "task group cycle": "Cyclus in de taakgroep"
+    },
     "warning_one": "1 waarschuwing",
     "warning_other": "{{count, number}} waarschuwingen"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/pl/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/pl/common.json
index f02677f696a..8bf54ee2680 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/pl/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/pl/common.json
@@ -106,7 +106,6 @@
   "dagRun_other": "Wykonania",
   "dagRunId": "Identyfikator wykonania",
   "dagRunState": "Stan wykonania Daga",
-  "dagWarnings": "Ostrzeżenia/Błędy Daga",
   "delete": "Usuń",
   "diff": "Porównaj",
   "diffCompareWith": "Porównaj z",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/pl/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/pl/components.json
index cd3526b7849..361ce5025ca 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/pl/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/pl/components.json
@@ -62,6 +62,13 @@
   "dagWarnings": {
     "error_one": "Błąd",
     "errorAndWarning": "1 Błąd i {{warningText}}",
+    "types": {
+      "asset conflict": "Konflikt zasobu",
+      "duplicate dag id": "Zduplikowany Dag ID",
+      "non-existent pool": "Nieistniejąca pula",
+      "runtime varying value": "Wartość zmienna w czasie wykonania",
+      "task group cycle": "Cykl w grupie zadań"
+    },
     "warning_few": "{{count, number}} Ostrzeżenia",
     "warning_many": "{{count, number}} Ostrzeżeń",
     "warning_one": "1 Ostrzeżenie",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/pt/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/pt/common.json
index e872832f6d8..bc583409766 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/pt/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/pt/common.json
@@ -91,7 +91,6 @@
   "dagRun_other": "Execuções do Dag",
   "dagRun_zero": "Nenhuma execução do Dag",
   "dagRunId": "ID da Execução do Dag",
-  "dagWarnings": "Avisos/Erros do Dag",
   "defaultToGraphView": "Padrão para visualização gráfica",
   "defaultToGridView": "Padrão para visualização em grade",
   "delete": "Excluir",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json
index 7b4fd10e906..54c7602e657 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json
@@ -49,6 +49,13 @@
     "error_other": "{{count, number}} Erros",
     "error_zero": "Nenhum erro",
     "errorAndWarning": "1 Erro e {{warningText}}",
+    "types": {
+      "asset conflict": "Conflito de Asset",
+      "duplicate dag id": "Dag ID duplicado",
+      "non-existent pool": "Pool inexistente",
+      "runtime varying value": "Valor variável em tempo de execução",
+      "task group cycle": "Ciclo no grupo de tarefas"
+    },
     "warning_many": "{{count, number}} Avisos",
     "warning_one": "1 Aviso",
     "warning_other": "{{count, number}} Avisos",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ru/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ru/common.json
index c020ff0c7ae..8070bc2c536 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ru/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ru/common.json
@@ -96,7 +96,6 @@
   "dagRun_one": "Запуск Dag-а",
   "dagRun_other": "Запуски Dag-а",
   "dagRunId": "ID запуска Dag-а",
-  "dagWarnings": "Предупреждения/ошибки Dag-а",
   "defaultToGraphView": "По умолчанию открывать граф",
   "defaultToGridView": "По умолчанию отображать в сетке",
   "delete": "Удалить",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ru/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/ru/components.json
index 388fd500b77..209c75df3dc 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ru/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ru/components.json
@@ -49,6 +49,13 @@
   "dagWarnings": {
     "error_one": "1 ошибка",
     "errorAndWarning": "1 ошибка и {{warningText}}",
+    "types": {
+      "asset conflict": "Конфликт актива",
+      "duplicate dag id": "Дублирующийся Dag ID",
+      "non-existent pool": "Несуществующий пул",
+      "runtime varying value": "Значение, меняющееся во время выполнения",
+      "task group cycle": "Цикл в группе задач"
+    },
     "warning_few": "{{count, number}} предупреждения",
     "warning_many": "{{count, number}} предупреждений",
     "warning_one": "1 предупреждение",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/th/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/th/common.json
index 63af67ef9a6..f7f7ab27d39 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/th/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/th/common.json
@@ -75,7 +75,6 @@
   "dagRun_one": "Dag Run",
   "dagRun_other": "Dag Runs",
   "dagRunId": "Dag Run ID",
-  "dagWarnings": "คำเตือน/ข้อผิดพลาดของ Dag",
   "defaultToGraphView": "ค่าเริ่มต้นเป็นมุมมองกราฟ",
   "defaultToGridView": "ค่าเริ่มต้นเป็นมุมมองกริด",
   "direction": "ทิศทาง",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/th/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/th/components.json
index 4e037978e95..523b8ba6363 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/th/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/th/components.json
@@ -43,6 +43,13 @@
   "dagWarnings": {
     "error_one": "1 ข้อผิดพลาด",
     "errorAndWarning": "1 ข้อผิดพลาดและ {{warningText}}",
+    "types": {
+      "asset conflict": "ความขัดแย้งของ Asset",
+      "duplicate dag id": "Dag ID ซ้ำ",
+      "non-existent pool": "ไม่พบพูล",
+      "runtime varying value": "ค่าที่เปลี่ยนแปลงขณะรันไทม์",
+      "task group cycle": "วงวนในกลุ่มงาน"
+    },
     "warning_one": "1 คำเตือน",
     "warning_other": "{{count, number}} คำเตือน"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/tr/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/tr/common.json
index e1a89372dc8..e9252b896d1 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/tr/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/tr/common.json
@@ -83,7 +83,6 @@
   "dagRun_one": "Dag Çalıştırması",
   "dagRun_other": "Dag Çalıştırmaları",
   "dagRunId": "Eşsiz Dag Çalıştırma Kimliği",
-  "dagWarnings": "Dag Uyarıları/Hataları",
   "defaultToGraphView": "Varsayılan olarak grafik görünümü",
   "defaultToGridView": "Varsayılan olarak ızgara görünümü",
   "delete": "Sil",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json
index 5f5f680dab5..8e6c63b29ec 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json
@@ -47,6 +47,13 @@
   "dagWarnings": {
     "error_one": "1 Hata",
     "errorAndWarning": "1 Hata ve {{warningText}}",
+    "types": {
+      "asset conflict": "Varlık çakışması",
+      "duplicate dag id": "Yinelenen Dag ID",
+      "non-existent pool": "Var olmayan işlem havuzu",
+      "runtime varying value": "Çalışma zamanında değişen değer",
+      "task group cycle": "Görev grubunda döngü"
+    },
     "warning_one": "1 Uyarı",
     "warning_other": "{{count, number}} Uyarı"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/common.json
index 20a5f0fe3a2..c8e0c4e8a3b 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "Dag 执行",
   "dagRunId": "Dag 执行 ID",
   "dagRunState": "Dag 执行状态",
-  "dagWarnings": "Dag 警告 / 错误",
   "delete": "删除",
   "diff": "差异对比",
   "diffCompareWith": "比较对象",
diff --git 
a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json
index 1904e2c86ab..027d8b0f55e 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 个错误",
     "errorAndWarning": "1 个错误与 {{warningText}}",
+    "types": {
+      "asset conflict": "资源冲突",
+      "duplicate dag id": "重复的 Dag ID",
+      "non-existent pool": "资源池不存在",
+      "runtime varying value": "运行时变化的值",
+      "task group cycle": "任务分组循环"
+    },
     "warning_one": "1 个警告",
     "warning_other": "{{count, number}} 个警告"
   },
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/common.json
index 0403a48c0e9..70119f7c134 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/common.json
@@ -92,7 +92,6 @@
   "dagRun_other": "Dag 執行",
   "dagRunId": "Dag 執行 ID",
   "dagRunState": "Dag 執行狀態",
-  "dagWarnings": "Dag 警告 / 錯誤",
   "delete": "刪除",
   "diff": "差異",
   "diffCompareWith": "與以下版本比較",
diff --git 
a/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/components.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/components.json
index d1eb096239f..d80cc959fc8 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/components.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/components.json
@@ -58,6 +58,13 @@
   "dagWarnings": {
     "error_one": "1 個錯誤",
     "errorAndWarning": "1 個錯誤與 {{warningText}}",
+    "types": {
+      "asset conflict": "資源衝突",
+      "duplicate dag id": "重複的 Dag ID",
+      "non-existent pool": "資源池不存在",
+      "runtime varying value": "執行時變動的值",
+      "task group cycle": "任務群組循環"
+    },
     "warning_one": "1 個警告",
     "warning_other": "{{count, number}} 個警告"
   },
diff --git 
a/airflow-core/src/airflow/ui/src/components/DAGWarningsModal.test.tsx 
b/airflow-core/src/airflow/ui/src/components/DAGWarningsModal.test.tsx
deleted file mode 100644
index fbd67b53b4a..00000000000
--- a/airflow-core/src/airflow/ui/src/components/DAGWarningsModal.test.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*!
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { describe, expect, it } from "vitest";
-
-import { countDagWarnings } from "./DAGWarningsModal";
-
-describe("countDagWarnings", () => {
-  it.each([
-    [undefined, null, 0],
-    [3, null, 3],
-    [undefined, new Error("boom"), 1],
-    [0, new Error("boom"), 1],
-    [3, new Error("boom"), 4],
-  ])("counts %s warnings and error %s as %s", (warningsTotal, error, expected) 
=> {
-    expect(countDagWarnings(warningsTotal, error)).toBe(expected);
-  });
-});
diff --git a/airflow-core/src/airflow/ui/src/components/DAGWarningsModal.tsx 
b/airflow-core/src/airflow/ui/src/components/DAGWarningsModal.tsx
index 15bdc0a986e..e2461373350 100644
--- a/airflow-core/src/airflow/ui/src/components/DAGWarningsModal.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DAGWarningsModal.tsx
@@ -20,33 +20,45 @@ import { Heading, HStack, Spacer, VStack } from 
"@chakra-ui/react";
 import { useTranslation } from "react-i18next";
 import { LuFileWarning } from "react-icons/lu";
 
-import type { DAGWarningResponse } from "openapi/requests/types.gen";
+import type { DAGWarningResponse, ImportErrorResponse } from 
"openapi/requests/types.gen";
 
 import { Modal } from "src/system-components";
 
-import { ErrorAlert } from "./ErrorAlert";
-import { WarningAlert } from "./WarningAlert";
+import { getApiErrorDetail } from "src/utils";
 
-/** The modal lists the page error as one more item next to the warnings, so 
the button count does too. */
-export const countDagWarnings = (warningsTotal: number | undefined, error: 
unknown): number =>
-  (warningsTotal ?? 0) + Number(Boolean(error));
+import { DagImportErrorDetails } from "./DagImportErrorDetails";
+import type { ExpandedApiError } from "./ErrorAlert";
+import { StatusMessage } from "./StatusMessage";
+import { WarningAlert } from "./WarningAlert";
 
-type ImportDAGErrorModalProps = {
+type Props = {
+  /** Stated as one more warning, for a Dag that is no longer in its file. */
+  readonly deactivatedMessage?: string;
   readonly error?: unknown;
+  /** Names the whole list. Derived with the chip's own label, so the two 
cannot disagree. */
+  readonly heading: string;
+  readonly importError?: ImportErrorResponse;
   readonly onClose: () => void;
   readonly open: boolean;
   readonly warnings?: Array<DAGWarningResponse>;
 };
 
-export const DAGWarningsModal = ({ error, onClose, open, warnings }: 
ImportDAGErrorModalProps) => {
-  const { t: translate } = useTranslation("components");
-  const heading = Boolean(error)
-    ? warnings?.length !== undefined && warnings.length > 0
-      ? translate("dagWarnings.errorAndWarning", {
-          warning: translate("dagWarnings.warning", { count: warnings.length 
}),
-        })
-      : translate("dagWarnings.error_one")
-    : "";
+export const DAGWarningsModal = ({
+  deactivatedMessage,
+  error,
+  heading,
+  importError,
+  onClose,
+  open,
+  warnings,
+}: Props) => {
+  const { t: translate } = useTranslation("common");
+  const apiError = error as ExpandedApiError | undefined;
+  // What the request itself said, falling back to the bare word when it 
carried no status or message.
+  const pageErrorHeading =
+    [apiError?.status, apiError?.message].filter(Boolean).join(" ") || 
translate("error.title");
+  const detail = getApiErrorDetail(error);
+  const errorDetail = Array.isArray(detail) ? detail.join("\n") : detail;
 
   return (
     <Modal
@@ -63,9 +75,25 @@ export const DAGWarningsModal = ({ error, onClose, open, 
warnings }: ImportDAGEr
       open={open}
       scrollBehavior="inside"
     >
+      {importError === undefined ? undefined : (
+        <VStack>
+          <StatusMessage
+            body={<DagImportErrorDetails importError={importError} />}
+            heading={translate("dashboard:importErrors.dagImportError", { 
count: 1 })}
+            severity="error"
+          />
+          <Spacer />
+        </VStack>
+      )}
       {Boolean(error) && (
         <VStack>
-          <ErrorAlert error={error} />
+          <StatusMessage body={errorDetail} heading={pageErrorHeading} 
severity="error" />
+          <Spacer />
+        </VStack>
+      )}
+      {deactivatedMessage === undefined ? undefined : (
+        <VStack>
+          <StatusMessage heading={deactivatedMessage} severity="warning" />
           <Spacer />
         </VStack>
       )}
diff --git 
a/airflow-core/src/airflow/ui/src/components/DagDeactivatedChip.test.tsx 
b/airflow-core/src/airflow/ui/src/components/DagDeactivatedChip.test.tsx
deleted file mode 100644
index 23d264188e3..00000000000
--- a/airflow-core/src/airflow/ui/src/components/DagDeactivatedChip.test.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-/*!
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import "@testing-library/jest-dom/vitest";
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
-import { beforeEach, describe, expect, it, vi } from "vitest";
-
-import i18n from "src/i18n/config";
-import { Wrapper } from "src/utils/Wrapper";
-
-import dagLocale from "../../public/i18n/locales/en/dag.json";
-import dashboardLocale from "../../public/i18n/locales/en/dashboard.json";
-import { DagDeactivatedChip } from "./DagDeactivatedChip";
-
-const { mockUseDagServiceGetDag, mockUseImportErrorServiceGetImportErrors } = 
vi.hoisted(() => ({
-  mockUseDagServiceGetDag: vi.fn(),
-  mockUseImportErrorServiceGetImportErrors: vi.fn(),
-}));
-
-vi.mock("react-router-dom", async () => {
-  const actual = await vi.importActual("react-router-dom");
-
-  return {
-    ...actual,
-    useParams: () => ({ dagId: "stale_dag" }),
-  };
-});
-
-vi.mock("openapi/queries", async (importOriginal) => {
-  // eslint-disable-next-line @typescript-eslint/consistent-type-imports -- 
`import()` type is the standard pattern for typing `importOriginal` in Vitest 
mocks.
-  const actual = await importOriginal<typeof import("openapi/queries")>();
-
-  return {
-    ...actual,
-    useDagServiceGetDag: mockUseDagServiceGetDag,
-    useImportErrorServiceGetImportErrors: 
mockUseImportErrorServiceGetImportErrors,
-  };
-});
-
-const staleDagQuery = {
-  data: { is_stale: true, relative_fileloc: "stale_dag.py" },
-  isLoading: false,
-};
-
-const notStaleDagQuery = {
-  data: { is_stale: false, relative_fileloc: "stale_dag.py" },
-  isLoading: false,
-};
-
-const emptyImportErrorsQuery = {
-  data: { import_errors: [], total_entries: 0 },
-  error: null,
-  isError: false,
-  isLoading: false,
-  isPending: false,
-};
-
-describe("DagDeactivatedChip", () => {
-  beforeEach(() => {
-    i18n.addResourceBundle("en", "dag", dagLocale, true, true);
-    i18n.addResourceBundle("en", "dashboard", dashboardLocale, true, true);
-    mockUseDagServiceGetDag.mockReturnValue(staleDagQuery);
-    
mockUseImportErrorServiceGetImportErrors.mockReturnValue(emptyImportErrorsQuery);
-  });
-
-  it("does not render when the dag is not stale", () => {
-    mockUseDagServiceGetDag.mockReturnValue(notStaleDagQuery);
-
-    render(
-      <Wrapper>
-        <DagDeactivatedChip />
-      </Wrapper>,
-    );
-
-    expect(
-      screen.queryByLabelText(i18n.t("header.status.deactivated", { ns: "dag" 
})),
-    ).not.toBeInTheDocument();
-  });
-
-  it("shows a deactivated banner when stale with no import error", () => {
-    render(
-      <Wrapper>
-        <DagDeactivatedChip />
-      </Wrapper>,
-    );
-
-    expect(screen.getByLabelText(i18n.t("header.status.deactivated", { ns: 
"dag" }))).toBeInTheDocument();
-    expect(
-      screen.queryByRole("button", {
-        name: i18n.t("importErrors.dagImportError", { count: 1, ns: 
"dashboard" }),
-      }),
-    ).not.toBeInTheDocument();
-  });
-
-  it("names the import error and opens the parse error on click", async () => {
-    mockUseImportErrorServiceGetImportErrors.mockReturnValue({
-      ...emptyImportErrorsQuery,
-      data: {
-        import_errors: [
-          {
-            bundle_name: "dags-folder",
-            filename: "stale_dag.py",
-            import_error_id: 42,
-            stack_trace: "Traceback (most recent call last):\nSyntaxError: 
invalid syntax",
-            timestamp: "2025-02-01T12:00:00Z",
-          },
-        ],
-        total_entries: 1,
-      },
-    });
-
-    render(
-      <Wrapper>
-        <DagDeactivatedChip />
-      </Wrapper>,
-    );
-
-    // One element states the status and opens the detail, rather than a badge 
plus a button.
-    const chip = screen.getByRole("button", {
-      name: i18n.t("importErrors.dagImportError_one", { ns: "dashboard" }),
-    });
-
-    // A parse error is named as such, so it is distinguishable from a Dag that
-    // merely disappeared from its file.
-    expect(
-      screen.queryByLabelText(i18n.t("header.status.deactivated", { ns: "dag" 
})),
-    ).not.toBeInTheDocument();
-
-    expect(chip).toBeInTheDocument();
-    expect(screen.queryByText(/SyntaxError: invalid 
syntax/u)).not.toBeInTheDocument();
-
-    fireEvent.click(chip);
-
-    await waitFor(() => {
-      expect(screen.getByText("stale_dag.py")).toBeInTheDocument();
-    });
-    expect(screen.getByText(/SyntaxError: invalid 
syntax/u)).toBeInTheDocument();
-  });
-});
diff --git a/airflow-core/src/airflow/ui/src/components/DagDeactivatedChip.tsx 
b/airflow-core/src/airflow/ui/src/components/DagDeactivatedChip.tsx
deleted file mode 100644
index 750db4cfb3b..00000000000
--- a/airflow-core/src/airflow/ui/src/components/DagDeactivatedChip.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*!
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { useDisclosure } from "@chakra-ui/react";
-import { useTranslation } from "react-i18next";
-import { LuFileWarning } from "react-icons/lu";
-import { useParams } from "react-router-dom";
-
-import { useDagServiceGetDag, useImportErrorServiceGetImportErrors } from 
"openapi/queries";
-
-import { DagImportErrorModal } from "./DagImportErrorModal";
-import { StatusChip } from "./StatusChip";
-
-export const DagDeactivatedChip = () => {
-  const { t: translate } = useTranslation(["dag", "dashboard"]);
-  const { dagId = "" } = useParams();
-  const { onClose, onOpen, open } = useDisclosure();
-
-  const { data: dag } = useDagServiceGetDag({ dagId }, undefined, { enabled: 
dagId !== "" });
-  const relativeFileloc = dag?.relative_fileloc ?? "";
-  const bundleName = dag?.bundle_name ?? undefined;
-
-  const { data } = useImportErrorServiceGetImportErrors(
-    { bundleName, filename: relativeFileloc },
-    undefined,
-    { enabled: dag?.is_stale && relativeFileloc.length > 0 },
-  );
-
-  if (dagId === "" || !dag?.is_stale) {
-    return undefined;
-  }
-
-  const importError = data?.import_errors[0];
-
-  return (
-    <>
-      <StatusChip
-        icon={<LuFileWarning />}
-        label={
-          importError === undefined
-            ? translate("dag:header.status.deactivated")
-            : translate("dashboard:importErrors.dagImportError_one")
-        }
-        // Inert without a parse error, since there is nothing to open.
-        onClick={importError === undefined ? undefined : onOpen}
-        // A Dag that merely disappeared from its file is not the same problem 
as one that
-        // failed to parse, so only the parse error shouts.
-        severity={importError === undefined ? "warning" : "error"}
-      />
-      {importError === undefined ? undefined : (
-        <DagImportErrorModal importError={importError} onClose={onClose} 
open={open} />
-      )}
-    </>
-  );
-};
diff --git a/airflow-core/src/airflow/ui/src/components/DagImportErrorModal.tsx 
b/airflow-core/src/airflow/ui/src/components/DagImportErrorDetails.tsx
similarity index 85%
copy from airflow-core/src/airflow/ui/src/components/DagImportErrorModal.tsx
copy to airflow-core/src/airflow/ui/src/components/DagImportErrorDetails.tsx
index 06c10749461..6eb5f825ce9 100644
--- a/airflow-core/src/airflow/ui/src/components/DagImportErrorModal.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DagImportErrorDetails.tsx
@@ -19,7 +19,6 @@
 import { ClipboardRoot, HStack, Text } from "@chakra-ui/react";
 import { useTranslation } from "react-i18next";
 import { AiOutlineFileSync } from "react-icons/ai";
-import { LuFileWarning } from "react-icons/lu";
 import { PiFilePy } from "react-icons/pi";
 
 import { useDagParsingServiceReparseDagFile } from "openapi/queries";
@@ -27,18 +26,12 @@ import type { ImportErrorResponse } from 
"openapi/requests/types.gen";
 
 import { ClipboardIconButton, IconButton, toaster } from 
"src/system-components";
 
-import { ErrorModal } from "src/components/ErrorModal";
 import Time from "src/components/Time";
 
 import { createErrorToaster } from "src/utils";
 
-type Props = {
-  readonly importError: ImportErrorResponse;
-  readonly onClose: () => void;
-  readonly open: boolean;
-};
-
-export const DagImportErrorModal = ({ importError, onClose, open }: Props) => {
+/** The parse failure itself — where it came from, when, the traceback — and a 
way to reparse. */
+export const DagImportErrorDetails = ({ importError }: { readonly importError: 
ImportErrorResponse }) => {
   const { t: translate } = useTranslation(["dashboard", "components", "dag"]);
 
   const { isPending, mutate } = useDagParsingServiceReparseDagFile({
@@ -52,12 +45,7 @@ export const DagImportErrorModal = ({ importError, onClose, 
open }: Props) => {
   });
 
   return (
-    <ErrorModal
-      icon={<LuFileWarning />}
-      onClose={onClose}
-      open={open}
-      title={translate("importErrors.dagImportError", { count: 1 })}
-    >
+    <>
       <HStack alignItems="center" flexWrap="wrap" gap={2} mb={2}>
         <Text fontWeight="bold">
           {translate("components:versionDetails.bundleName")}
@@ -87,6 +75,6 @@ export const DagImportErrorModal = ({ importError, onClose, 
open }: Props) => {
       <Text color="fg.error" fontSize="sm" whiteSpace="pre-wrap">
         <code>{importError.stack_trace}</code>
       </Text>
-    </ErrorModal>
+    </>
   );
 };
diff --git a/airflow-core/src/airflow/ui/src/components/DagImportErrorModal.tsx 
b/airflow-core/src/airflow/ui/src/components/DagImportErrorModal.tsx
index 06c10749461..edde4b676a9 100644
--- a/airflow-core/src/airflow/ui/src/components/DagImportErrorModal.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DagImportErrorModal.tsx
@@ -16,21 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { ClipboardRoot, HStack, Text } from "@chakra-ui/react";
 import { useTranslation } from "react-i18next";
-import { AiOutlineFileSync } from "react-icons/ai";
 import { LuFileWarning } from "react-icons/lu";
-import { PiFilePy } from "react-icons/pi";
 
-import { useDagParsingServiceReparseDagFile } from "openapi/queries";
 import type { ImportErrorResponse } from "openapi/requests/types.gen";
 
-import { ClipboardIconButton, IconButton, toaster } from 
"src/system-components";
-
+import { DagImportErrorDetails } from "src/components/DagImportErrorDetails";
 import { ErrorModal } from "src/components/ErrorModal";
-import Time from "src/components/Time";
-
-import { createErrorToaster } from "src/utils";
 
 type Props = {
   readonly importError: ImportErrorResponse;
@@ -39,17 +31,7 @@ type Props = {
 };
 
 export const DagImportErrorModal = ({ importError, onClose, open }: Props) => {
-  const { t: translate } = useTranslation(["dashboard", "components", "dag"]);
-
-  const { isPending, mutate } = useDagParsingServiceReparseDagFile({
-    onError: (error) => createErrorToaster(error, { titleKey: 
"dag:parse.toaster.error.title" }, translate),
-    onSuccess: () =>
-      toaster.create({
-        description: translate("dag:parse.toaster.success.description"),
-        title: translate("dag:parse.toaster.success.title"),
-        type: "success",
-      }),
-  });
+  const { t: translate } = useTranslation("dashboard");
 
   return (
     <ErrorModal
@@ -58,35 +40,7 @@ export const DagImportErrorModal = ({ importError, onClose, 
open }: Props) => {
       open={open}
       title={translate("importErrors.dagImportError", { count: 1 })}
     >
-      <HStack alignItems="center" flexWrap="wrap" gap={2} mb={2}>
-        <Text fontWeight="bold">
-          {translate("components:versionDetails.bundleName")}
-          {": "}
-          {importError.bundle_name}
-        </Text>
-        <PiFilePy />
-        {importError.filename}
-        <ClipboardRoot value={importError.filename}>
-          <ClipboardIconButton variant="outline" />
-        </ClipboardRoot>
-        <IconButton
-          data-testid="reparse-import-error"
-          label={translate("components:reparseDag")}
-          loading={isPending}
-          onClick={() => mutate({ fileToken: importError.file_token })}
-          variant="outline"
-        >
-          <AiOutlineFileSync />
-        </IconButton>
-      </HStack>
-      <Text color="fg.muted" fontSize="sm" mb={2}>
-        {translate("importErrors.timestamp")}
-        {": "}
-        <Time datetime={importError.timestamp} />
-      </Text>
-      <Text color="fg.error" fontSize="sm" whiteSpace="pre-wrap">
-        <code>{importError.stack_trace}</code>
-      </Text>
+      <DagImportErrorDetails importError={importError} />
     </ErrorModal>
   );
 };
diff --git a/airflow-core/src/airflow/ui/src/components/DagStatusChip.test.tsx 
b/airflow-core/src/airflow/ui/src/components/DagStatusChip.test.tsx
new file mode 100644
index 00000000000..9065d51aac4
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/DagStatusChip.test.tsx
@@ -0,0 +1,161 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import "@testing-library/jest-dom/vitest";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import i18n from "src/i18n/config";
+import { Wrapper } from "src/utils/Wrapper";
+
+import componentsLocale from "../../public/i18n/locales/en/components.json";
+import dagLocale from "../../public/i18n/locales/en/dag.json";
+import dashboardLocale from "../../public/i18n/locales/en/dashboard.json";
+import { DagStatusChip } from "./DagStatusChip";
+
+const {
+  mockUseDagServiceGetDag,
+  mockUseDagWarningServiceListDagWarnings,
+  mockUseImportErrorServiceGetImportErrors,
+} = vi.hoisted(() => ({
+  mockUseDagServiceGetDag: vi.fn(),
+  mockUseDagWarningServiceListDagWarnings: vi.fn(),
+  mockUseImportErrorServiceGetImportErrors: vi.fn(),
+}));
+
+vi.mock("openapi/queries", async (importOriginal) => {
+  // eslint-disable-next-line @typescript-eslint/consistent-type-imports -- 
`import()` type is the standard pattern for typing `importOriginal` in Vitest 
mocks.
+  const actual = await importOriginal<typeof import("openapi/queries")>();
+
+  return {
+    ...actual,
+    useDagServiceGetDag: mockUseDagServiceGetDag,
+    useDagWarningServiceListDagWarnings: 
mockUseDagWarningServiceListDagWarnings,
+    useImportErrorServiceGetImportErrors: 
mockUseImportErrorServiceGetImportErrors,
+  };
+});
+
+const staleDagQuery = { data: { is_stale: true, relative_fileloc: 
"stale_dag.py" }, isLoading: false };
+const liveDagQuery = { data: { is_stale: false, relative_fileloc: 
"stale_dag.py" }, isLoading: false };
+const noImportErrors = { data: { import_errors: [], total_entries: 0 }, 
isLoading: false };
+const noWarnings = { data: { dag_warnings: [], total_entries: 0 }, isLoading: 
false };
+
+const importErrorQuery = {
+  data: {
+    import_errors: [
+      {
+        bundle_name: "dags-folder",
+        filename: "stale_dag.py",
+        import_error_id: 42,
+        stack_trace: "Traceback (most recent call last):\nSyntaxError: invalid 
syntax",
+        timestamp: "2025-02-01T12:00:00Z",
+      },
+    ],
+    total_entries: 1,
+  },
+  isLoading: false,
+};
+
+const warningsQuery = {
+  data: {
+    dag_warnings: [{ message: "duplicate task id", warning_type: "non-existent 
pool" }],
+    total_entries: 1,
+  },
+  isLoading: false,
+};
+
+const deactivated = () => i18n.t("header.status.deactivated", { ns: "dag" });
+const importErrorName = () => i18n.t("importErrors.dagImportError", { count: 
1, ns: "dashboard" });
+const warningName = (count: number) => i18n.t("dagWarnings.warning", { count, 
ns: "components" });
+
+const renderChip = () =>
+  render(
+    <Wrapper>
+      <DagStatusChip dagId="stale_dag" />
+    </Wrapper>,
+  );
+
+describe("DagStatusChip", () => {
+  beforeEach(() => {
+    i18n.addResourceBundle("en", "components", componentsLocale, true, true);
+    i18n.addResourceBundle("en", "dag", dagLocale, true, true);
+    i18n.addResourceBundle("en", "dashboard", dashboardLocale, true, true);
+    mockUseDagServiceGetDag.mockReturnValue(staleDagQuery);
+    mockUseImportErrorServiceGetImportErrors.mockReturnValue(noImportErrors);
+    mockUseDagWarningServiceListDagWarnings.mockReturnValue(noWarnings);
+  });
+
+  it("renders nothing when the Dag is live and has no warnings", () => {
+    mockUseDagServiceGetDag.mockReturnValue(liveDagQuery);
+
+    renderChip();
+
+    expect(screen.queryByRole("button")).not.toBeInTheDocument();
+  });
+
+  it("names the deactivation when that is the only status", () => {
+    renderChip();
+
+    expect(screen.getByRole("button", { name: deactivated() 
})).toBeInTheDocument();
+  });
+
+  it("names the warnings when the Dag is live", () => {
+    mockUseDagServiceGetDag.mockReturnValue(liveDagQuery);
+    mockUseDagWarningServiceListDagWarnings.mockReturnValue(warningsQuery);
+
+    renderChip();
+
+    expect(screen.getByRole("button", { name: warningName(1) 
})).toBeInTheDocument();
+  });
+
+  it("lets the parse error outrank the warnings for name and severity", async 
() => {
+    mockUseImportErrorServiceGetImportErrors.mockReturnValue(importErrorQuery);
+    mockUseDagWarningServiceListDagWarnings.mockReturnValue(warningsQuery);
+
+    renderChip();
+
+    const chip = screen.getByRole("button", { name: importErrorName() });
+
+    fireEvent.click(chip);
+
+    // Both land in the one modal, the parse error ahead of the warning.
+    await waitFor(() => expect(screen.getByText(/SyntaxError: invalid 
syntax/u)).toBeInTheDocument());
+    expect(screen.getByText("duplicate task id")).toBeInTheDocument();
+    // The parse error is why the Dag went stale, so the list does not state 
the staleness again.
+    expect(screen.queryByText(deactivated())).not.toBeInTheDocument();
+  });
+
+  it("reports a page load failure as an error beside the warnings", async () 
=> {
+    mockUseDagServiceGetDag.mockReturnValue(liveDagQuery);
+    mockUseDagWarningServiceListDagWarnings.mockReturnValue(warningsQuery);
+
+    render(
+      <Wrapper>
+        <DagStatusChip dagId="stale_dag" error={new Error("request failed")} />
+      </Wrapper>,
+    );
+
+    fireEvent.click(
+      screen.getByRole("button", { name: i18n.t("dagWarnings.error_one", { ns: 
"components" }) }),
+    );
+
+    // The failure itself has to reach the list, not just the warnings it is 
listed beside.
+    await waitFor(() => expect(screen.getByText("request 
failed")).toBeInTheDocument());
+    expect(screen.getByText("duplicate task id")).toBeInTheDocument();
+  });
+});
diff --git a/airflow-core/src/airflow/ui/src/components/DagStatusChip.tsx 
b/airflow-core/src/airflow/ui/src/components/DagStatusChip.tsx
new file mode 100644
index 00000000000..86956d8d735
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/DagStatusChip.tsx
@@ -0,0 +1,90 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+import { LuFileWarning } from "react-icons/lu";
+
+import {
+  useDagServiceGetDag,
+  useDagWarningServiceListDagWarnings,
+  useImportErrorServiceGetImportErrors,
+} from "openapi/queries";
+
+import { DAGWarningsModal } from "src/components/DAGWarningsModal";
+import { StatusChip } from "src/components/StatusChip";
+
+import { summarizeDagStatus } from "src/utils";
+
+/**
+ * Every problem with a Dag under one marker: a parse failure, the page's own 
load error, a Dag that
+ * has gone from its file, and its parse-time warnings. The worst of them 
names and colours the chip;
+ * the modal lists them all, worst first. Separate chips let one hide behind 
another and made two
+ * identical markers mean different things.
+ */
+export const DagStatusChip = ({ dagId, error }: { readonly dagId: string; 
readonly error?: unknown }) => {
+  const { t: translate } = useTranslation(["common", "dag", "dashboard", 
"components"]);
+  const { onClose, onOpen, open } = useDisclosure();
+
+  const { data: dag } = useDagServiceGetDag({ dagId }, undefined, { enabled: 
dagId !== "" });
+  const relativeFileloc = dag?.relative_fileloc ?? "";
+  const isStale = dag?.is_stale ?? false;
+
+  const { data: importErrorData } = useImportErrorServiceGetImportErrors(
+    { bundleName: dag?.bundle_name ?? undefined, filename: relativeFileloc },
+    undefined,
+    { enabled: isStale && relativeFileloc.length > 0 },
+  );
+  const { data: warningData } = useDagWarningServiceListDagWarnings({ dagId }, 
undefined, {
+    enabled: dagId !== "",
+  });
+
+  const importError = importErrorData?.import_errors[0];
+  const warnings = warningData?.dag_warnings ?? [];
+  // A Dag that merely vanished from its file is not the same problem as one 
that failed to parse,
+  // so the deactivation only speaks for itself while no parse error explains 
it.
+  const deactivatedMessage =
+    isStale && importError === undefined ? 
translate("dag:header.status.deactivated") : undefined;
+  const hasError = importError !== undefined || Boolean(error);
+
+  if (dagId === "" || (!hasError && deactivatedMessage === undefined && 
warnings.length === 0)) {
+    return undefined;
+  }
+
+  const { chipLabel, heading, severity } = summarizeDagStatus(translate, {
+    deactivatedMessage,
+    error,
+    importError,
+    warnings,
+  });
+
+  return (
+    <>
+      <StatusChip icon={<LuFileWarning />} label={chipLabel} onClick={onOpen} 
severity={severity} />
+      <DAGWarningsModal
+        deactivatedMessage={deactivatedMessage}
+        error={error}
+        heading={heading}
+        importError={importError}
+        onClose={onClose}
+        open={open}
+        warnings={warnings}
+      />
+    </>
+  );
+};
diff --git a/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx 
b/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx
index 67e59fc0b4d..2cdfdf4da4e 100644
--- a/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx
@@ -310,6 +310,7 @@ export const DataTable = <TData extends RowData>({
           data-testid="data-table-header"
           gap={2}
           justifyContent="space-between"
+          mb={2}
           minH={10}
         >
           <VStack alignItems="flex-start" flex="1" gap={2} minW={0} w="100%">
diff --git a/airflow-core/src/airflow/ui/src/components/ErrorAlert.tsx 
b/airflow-core/src/airflow/ui/src/components/ErrorAlert.tsx
index 8bbd419ba5e..1fc88668dff 100644
--- a/airflow-core/src/airflow/ui/src/components/ErrorAlert.tsx
+++ b/airflow-core/src/airflow/ui/src/components/ErrorAlert.tsx
@@ -22,6 +22,8 @@ import type { HTTPExceptionResponse, HTTPValidationError } 
from "openapi-gen/req
 
 import { Alert } from "src/system-components";
 
+import { getApiErrorDetail } from "src/utils";
+
 export type ExpandedApiError = {
   body: HTTPExceptionResponse | HTTPValidationError | undefined;
 } & ApiError;
@@ -37,18 +39,7 @@ export const ErrorAlert = ({ error: err }: Props) => {
     return undefined;
   }
 
-  const details = error.body?.detail;
-  let detailMessage;
-
-  if (details !== undefined) {
-    if (typeof details === "string") {
-      detailMessage = details;
-    } else if (Array.isArray(details)) {
-      detailMessage = details.map((detail) => `${detail.loc.join(".")} 
${detail.msg}`);
-    } else {
-      detailMessage = Object.keys(details).map((key) => `${key}: 
${details[key] as string}`);
-    }
-  }
+  const detailMessage = getApiErrorDetail(error);
 
   return (
     <Alert data-testid="error-alert" status="error">
diff --git 
a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.test.tsx 
b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.test.tsx
index 18b0b96be7a..0e865dc450d 100644
--- a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.test.tsx
+++ b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.test.tsx
@@ -90,6 +90,21 @@ describe("FilterBar boolean filters", () => {
     expect(screen.getByTestId("needs_review-pill")).not.toHaveTextContent(":");
   });
 
+  it("keeps the remove control out of the pill button", () => {
+    const { container } = render(
+      <FilterBar
+        configs={[booleanConfig]}
+        initialValues={{ needs_review: "true" }}
+        onFiltersChange={vi.fn()}
+      />,
+      { wrapper },
+    );
+
+    // Asked of the whole pill rather than of the inner button, so putting 
`as="button"` back on the
+    // wrapper fails here too. A button inside a button is invalid HTML and 
React warns at runtime.
+    expect(container.querySelector("button button")).toBeNull();
+  });
+
   it("clears the filter when the pill is clicked", async () => {
     const onFiltersChange = vi.fn();
 
diff --git 
a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterPill.tsx 
b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterPill.tsx
index ec12b78d3b5..f742eabd096 100644
--- a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterPill.tsx
+++ b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterPill.tsx
@@ -149,34 +149,45 @@ export const FilterPill = ({
     );
   }
 
+  // The remove control is a button, so the pill body is a sibling button 
rather than the wrapper:
+  // nesting one button inside another is invalid HTML and React warns about 
it.
   return (
     <Box
       _hover={{ bg: "colorPalette.subtle" }}
       alignItems="center"
-      as="button"
       bg={hasValue ? "brand.emphasized" : "gray.muted"}
       borderRadius="full"
       color="colorPalette.fg"
       colorPalette={hasValue ? "brand" : "gray"}
-      cursor="pointer"
-      data-testid={`${filter.config.key}-pill`}
       display="flex"
       fontSize="sm"
       fontWeight="medium"
       gap={2}
       h="9"
-      onClick={handlePillClick}
-      pl={3}
       pr={1}
     >
-      {filter.config.icon ?? getDefaultFilterIcon(filter.config.type)}
-      <Box alignItems="center" display="flex" flex="1" textWrap="nowrap">
-        {filter.config.label}
-        {displayValue === undefined || displayValue === "" ? undefined : 
<strong>: {displayValue}</strong>}
+      <Box
+        alignItems="center"
+        as="button"
+        cursor="pointer"
+        data-testid={`${filter.config.key}-pill`}
+        display="flex"
+        flex="1"
+        gap={2}
+        h="100%"
+        onClick={handlePillClick}
+        pl={3}
+      >
+        {filter.config.icon ?? getDefaultFilterIcon(filter.config.type)}
+        <Box alignItems="center" display="flex" flex="1" textWrap="nowrap">
+          {filter.config.label}
+          {displayValue === undefined || displayValue === "" ? undefined : 
<strong>: {displayValue}</strong>}
+        </Box>
       </Box>
       <IconButton
         aria-label={`Remove ${filter.config.label} filter`}
         borderRadius="full"
+        data-testid={`${filter.config.key}-pill-remove`}
         label={translate("common:filters.removeFilter")}
         onClick={(event) => {
           event.stopPropagation();
diff --git a/airflow-core/src/airflow/ui/src/components/StatusMessage.tsx 
b/airflow-core/src/airflow/ui/src/components/StatusMessage.tsx
new file mode 100644
index 00000000000..55eb54e6ec7
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/StatusMessage.tsx
@@ -0,0 +1,52 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import type { ReactNode } from "react";
+
+import { Box, HStack, Text, VStack } from "@chakra-ui/react";
+import { FiAlertCircle, FiAlertTriangle } from "react-icons/fi";
+
+type Props = {
+  /** The detail under the heading. Omit for a status that says all it has to 
say in its heading. */
+  readonly body?: ReactNode;
+  readonly heading: string;
+  readonly severity: "error" | "warning";
+};
+
+/**
+ * One problem stated in a list of them: its severity in the icon and the 
colour of its text, its
+ * short form as the heading, the long form underneath. Coloured text rather 
than a filled block,
+ * so a list of these stays readable and a long traceback does not become a 
wall of colour.
+ */
+export const StatusMessage = ({ body, heading, severity }: Props) => (
+  <VStack align="start" gap={1} w="100%">
+    <HStack color={`fg.${severity}`} gap={2}>
+      {severity === "error" ? <FiAlertCircle /> : <FiAlertTriangle />}
+      <Text fontWeight="bold">{heading}</Text>
+    </HStack>
+    {body === undefined ? undefined : typeof body === "string" ? (
+      <Text color={`fg.${severity}`} fontSize="sm" whiteSpace="pre-wrap" 
wordBreak="break-word">
+        {body}
+      </Text>
+    ) : (
+      // Rich detail brings its own blocks, which a <p> may not contain, and 
its own type scale and
+      // colours: a traceback is not the same kind of text as the row naming 
the file it came from.
+      <Box w="100%">{body}</Box>
+    )}
+  </VStack>
+);
diff --git a/airflow-core/src/airflow/ui/src/components/WarningAlert.tsx 
b/airflow-core/src/airflow/ui/src/components/WarningAlert.tsx
index 3374af36769..cb5cfaa08fa 100644
--- a/airflow-core/src/airflow/ui/src/components/WarningAlert.tsx
+++ b/airflow-core/src/airflow/ui/src/components/WarningAlert.tsx
@@ -16,26 +16,32 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Flex } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
 
-import { Alert } from "src/system-components";
+import type { DAGWarningResponse } from "openapi/requests/types.gen";
+
+import { StatusMessage } from "src/components/StatusMessage";
 
 type Props = {
-  readonly warning?: {
-    message: string;
-  };
+  readonly warning?: DAGWarningResponse;
 };
 
 export const WarningAlert = ({ warning }: Props) => {
-  if (!Boolean(warning)) {
+  const { t: translate } = useTranslation("components");
+
+  if (warning === undefined) {
     return undefined;
   }
 
+  // The warning's type is its short form, but it arrives as the backend's own 
lowercase enum value,
+  // which has no business being a heading in any language. An unmapped one 
falls back to the word.
   return (
-    <Alert data-testid="warning-alert" status="warning">
-      <Flex align="center" whiteSpace="preserve" wordBreak="break-all">
-        {warning?.message}
-      </Flex>
-    </Alert>
+    <StatusMessage
+      body={warning.message}
+      heading={translate(`dagWarnings.types.${warning.warning_type}`, {
+        defaultValue: translate("dagWarnings.warning", { count: 1 }),
+      })}
+      severity="warning"
+    />
   );
 };
diff --git 
a/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.test.tsx 
b/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.test.tsx
new file mode 100644
index 00000000000..ab183f16871
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.test.tsx
@@ -0,0 +1,66 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { renderHook, waitFor } from "@testing-library/react";
+import { afterEach, expect, it, vi } from "vitest";
+
+import { TaskInstanceService } from "openapi/requests";
+
+import { Wrapper } from "src/utils/Wrapper";
+
+import { useRequiredActionTabs } from "./useRequiredActionTabs";
+
+const REGION_ID = "11111111-1111-4111-8111-111111111111";
+
+afterEach(() => vi.restoreAllMocks());
+
+it("answers the exact lookup from the collection rather than the single 
detail", async () => {
+  const collection = vi
+    .spyOn(TaskInstanceService, "getHitlDetails")
+    .mockResolvedValue({ hitl_details: [], total_entries: 0 });
+  const singleDetail = vi.spyOn(TaskInstanceService, "getHitlDetail");
+
+  renderHook(
+    () =>
+      useRequiredActionTabs(
+        {
+          dagId: "dag",
+          dagRunId: "run",
+          mapIndex: -1,
+          regionId: REGION_ID,
+          regionIndex: 3,
+          taskId: "work",
+        },
+        [],
+      ),
+    { wrapper: Wrapper },
+  );
+
+  await waitFor(() => expect(collection).toHaveBeenCalledTimes(1));
+  expect(collection.mock.lastCall?.[0]).toMatchObject({
+    dagId: "dag",
+    dagRunId: "run",
+    mapIndex: -1,
+    regionId: REGION_ID,
+    regionIndex: 3,
+    taskId: "work",
+  });
+  // The single-detail endpoint says "not human-in-the-loop" with a 404, which 
every ordinary task
+  // then logged as a failed request. Asking it at all is the bug.
+  expect(singleDetail).not.toHaveBeenCalled();
+});
diff --git a/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts 
b/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts
index b7f8b221112..55e926d127b 100644
--- a/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts
+++ b/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts
@@ -21,7 +21,7 @@ import { useEffect, type ReactNode } from "react";
 import { useTranslation } from "react-i18next";
 import { useLocation, useNavigate } from "react-router-dom";
 
-import { useTaskInstanceServiceGetHitlDetails, 
useTaskInstanceServiceGetHitlDetail } from "openapi/queries";
+import { useTaskInstanceServiceGetHitlDetails } from "openapi/queries";
 
 export type HITLQueryParams = {
   dagId: string;
@@ -72,38 +72,27 @@ export const useRequiredActionTabs = (
     redirectPath = location.pathname.replace("/required_actions", "");
   }
 
+  // Both the broad and the exact lookup go through the collection endpoint. 
Asking the single-detail
+  // endpoint whether one task instance is HITL answers "no" with a 404, which 
every non-HITL task
+  // then logs as a failed request; the collection answers the same question 
with `total_entries: 0`.
   const collection = useTaskInstanceServiceGetHitlDetails(
     {
       dagId,
       dagRunId: dagRunId ?? "~",
+      mapIndex: exact ? (mapIndex ?? -1) : undefined,
+      regionId: exact ? regionId : undefined,
+      regionIndex: exact ? regionIndex : undefined,
       taskId,
       taskIdPrefixPattern: taskIdPattern,
     },
     undefined,
     {
-      enabled: enabled && !exact && Boolean(dagId),
+      enabled: enabled && Boolean(dagId),
       refetchInterval,
     },
   );
-  const detail = useTaskInstanceServiceGetHitlDetail(
-    {
-      dagId,
-      dagRunId: dagRunId ?? "",
-      mapIndex: mapIndex ?? -1,
-      regionId,
-      regionIndex,
-      taskId: taskId ?? "",
-    },
-    undefined,
-    { enabled: enabled && exact, refetchInterval, retry: false },
-  );
-  const hitlData = exact
-    ? {
-        hitl_details: detail.data === undefined ? [] : [detail.data],
-        total_entries: detail.data === undefined ? 0 : 1,
-      }
-    : collection.data;
-  const isLoadingHitl = exact ? detail.isLoading : collection.isLoading;
+  const hitlData = collection.data;
+  const isLoadingHitl = collection.isLoading;
 
   const hasHitlData = enabled && (hitlData?.total_entries ?? 0) > 0;
   const pendingActionsCount =
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index 2672b9f1c6a..95b30d7599a 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -19,27 +19,20 @@
 import type { PropsWithChildren, ReactNode, RefObject } from "react";
 import { useEffect, useRef, useState } from "react";
 
-import { Box, Flex, HStack, useDisclosure } from "@chakra-ui/react";
+import { Box, Flex, HStack } from "@chakra-ui/react";
 import { useReactFlow } from "@xyflow/react";
 import { useTranslation } from "react-i18next";
 import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
-import { LuFileWarning } from "react-icons/lu";
 import { Group, Panel, Separator, useDefaultLayout, useGroupRef } from 
"react-resizable-panels";
 import { Outlet, useParams, useSearchParams } from "react-router-dom";
 import { useLocalStorage } from "usehooks-ts";
 
-import {
-  useDagRunServiceGetDagRun,
-  useDagRunServiceGetDagRuns,
-  useDagServiceGetDag,
-  useDagWarningServiceListDagWarnings,
-} from "openapi/queries";
+import { useDagRunServiceGetDagRun, useDagRunServiceGetDagRuns, 
useDagServiceGetDag } from "openapi/queries";
 import type { DagRunState, DagRunType } from "openapi/requests/types.gen";
 
 import { IconButton, ProgressBar, Toaster } from "src/system-components";
 
-import { countDagWarnings, DAGWarningsModal } from 
"src/components/DAGWarningsModal";
-import { DagDeactivatedChip } from "src/components/DagDeactivatedChip";
+import { DagStatusChip } from "src/components/DagStatusChip";
 import { TogglePause } from "src/components/TogglePause";
 import { TriggerDAGButton } from "src/components/TriggerDag/TriggerDAGButton";
 
@@ -50,7 +43,7 @@ import { VersionIndicatorOptions } from 
"src/constants/showVersionIndicatorOptio
 import { GroupsProvider } from "src/context/groups";
 import { useDagRunsLimit } from "src/hooks/useDagRunsLimit";
 import { useGridRuns } from "src/queries/useGridRuns.ts";
-import { formatNumber, useAutoRefresh, useContainerWidth } from "src/utils";
+import { useAutoRefresh, useContainerWidth } from "src/utils";
 
 import { DagBreadcrumb } from "./DagBreadcrumb";
 import { Gantt } from "./Gantt/Gantt";
@@ -223,8 +216,6 @@ export const DetailsLayout = ({ children, error, isLoading, 
outletContext, tabs
   };
 
   const { fitView, getZoom } = useReactFlow();
-  const { data: warningData } = useDagWarningServiceListDagWarnings({ dagId });
-  const { onClose, onOpen, open } = useDisclosure();
   const [isRightPanelCollapsed, setIsRightPanelCollapsed] = useState(false);
   const { i18n } = useTranslation();
   const direction = i18n.dir();
@@ -240,9 +231,10 @@ export const DetailsLayout = ({ children, error, 
isLoading, outletContext, tabs
       <Box display="flex" flex={1} flexDirection="column" minH={0} minW={{ 
base: "1280px", md: "auto" }}>
         <HStack justifyContent="space-between" mb={3} mx={-3} px={3}>
           <HStack gap={2}>
-            {/* Ahead of the trail rather than inside a level: it speaks for 
the Dag as a whole,
-              and a level is too tight to hold it without crowding what it 
names. */}
-            <DagDeactivatedChip />
+            {/* Ahead of the trail rather than inside a level: these speak for 
the Dag as a whole,
+              and a level is too tight to hold them without crowding what it 
names. Highest
+              severity first, and together so one glance covers every problem 
with the Dag. */}
+            <DagStatusChip dagId={dagId} error={error} />
             <DagBreadcrumb />
           </HStack>
           <Flex gap={2}>
@@ -451,28 +443,6 @@ export const DetailsLayout = ({ children, error, 
isLoading, outletContext, tabs
                     position="relative"
                   >
                     {children}
-                    {Boolean(error) || (warningData?.dag_warnings.length ?? 0) 
> 0 ? (
-                      <>
-                        <IconButton
-                          colorPalette={Boolean(error) ? "red" : "orange"}
-                          label={`${translate("common:dagWarnings")} 
(${formatNumber(countDagWarnings(warningData?.total_entries, error), 
i18n.language)})`}
-                          margin="2"
-                          marginBottom="-1"
-                          onClick={onOpen}
-                          rounded="full"
-                          variant="solid"
-                        >
-                          <LuFileWarning />
-                        </IconButton>
-
-                        <DAGWarningsModal
-                          error={error}
-                          onClose={onClose}
-                          open={open}
-                          warnings={warningData?.dag_warnings}
-                        />
-                      </>
-                    ) : undefined}
                     <ProgressBar size="xs" visibility={isLoading ? "visible" : 
"hidden"} />
                     <NavTabs tabs={tabs} />
                     <Box flexGrow={1} overflow="auto" pb={3} px={2}>
diff --git 
a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts 
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts
index e7f343f2ac5..513303bf804 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.test.ts
@@ -40,13 +40,14 @@ describe("buildGanttTimeAxisTicks", () => {
     const maxMs = 60_000;
     const ticks = buildGanttTimeAxisTicks(minMs, maxMs);
 
-    expect(ticks).toHaveLength(GANTT_TIME_AXIS_TICK_COUNT);
+    // The 10s step snapped from a 60s span lands on the span one tick before 
the requested count.
+    expect(ticks).toHaveLength(7);
     expect(ticks[0]?.leftPct).toBe(0);
     expect(ticks[0]?.label).toBe("0s");
     expect(ticks[0]?.labelAlign).toBe("left");
-    expect(ticks[GANTT_TIME_AXIS_TICK_COUNT - 1]?.leftPct).toBe(100);
-    expect(ticks[GANTT_TIME_AXIS_TICK_COUNT - 1]?.labelAlign).toBe("right");
-    expect(ticks[GANTT_TIME_AXIS_TICK_COUNT - 1]?.label).toBe("1m");
+    expect(ticks.at(-1)?.leftPct).toBe(100);
+    expect(ticks.at(-1)?.labelAlign).toBe("right");
+    expect(ticks.at(-1)?.label).toBe("1m");
     expect(ticks[1]?.labelAlign).toBe("center");
     expect(ticks.every((tick) => typeof tick.label === "string" && 
tick.label.length > 0)).toBe(true);
   });
@@ -68,6 +69,15 @@ describe("buildGanttTimeAxisTicks", () => {
     expect(labels).toStrictEqual(["0s", "1s", "2s", "3s", "4s", "5s", "6s", 
"7s"]);
   });
 
+  it.each([3, 5, 10, 20, 45])("places every tick of a %ss span at its own 
position", (seconds) => {
+    const ticks = buildGanttTimeAxisTicks(0, seconds * 1000, { locale: "en" });
+
+    expect(new Set(ticks.map((tick) => tick.leftPct)).size).toBe(ticks.length);
+    expect(ticks.length).toBeLessThanOrEqual(GANTT_TIME_AXIS_TICK_COUNT);
+    expect(ticks.at(-1)?.leftPct).toBe(100);
+    expect(ticks.at(-1)?.label).toBe(`${seconds}s`);
+  });
+
   it("supports a single tick", () => {
     const ticks = buildGanttTimeAxisTicks(1000, 1000, { tickCount: 1 });
 
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts 
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
index 5303455a2c4..5e2da820e19 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
@@ -397,16 +397,23 @@ export const buildGanttTimeAxisTicks = (
   const stepMs = getDurationTickStep(spanMs / 1000, denominator) * 1000;
 
   for (let tickIndex = 0; tickIndex < tickCount; tickIndex += 1) {
-    const elapsedMs =
-      tickCount > 1 && tickIndex === lastIndex ? spanMs : Math.min(tickIndex * 
stepMs, spanMs);
+    // A snapped step overshoots a short span, so stop at the first tick that 
reaches it rather than
+    // clamping the remaining ones onto the span: identical ticks stack 
gridlines and labels on top
+    // of each other at the right edge.
+    const isLastTick = tickIndex === lastIndex || (tickIndex > 0 && tickIndex 
* stepMs >= spanMs);
+    const elapsedMs = tickCount > 1 && isLastTick ? spanMs : tickIndex * 
stepMs;
     const labelAlign: GanttAxisTickLabelAlign =
-      tickCount === 1 ? "left" : tickIndex === 0 ? "left" : tickIndex === 
lastIndex ? "right" : "center";
+      tickCount === 1 || tickIndex === 0 ? "left" : isLastTick ? "right" : 
"center";
 
     ticks.push({
       label: formatElapsedMsForGanttAxis(elapsedMs, locale),
       labelAlign,
       leftPct: (elapsedMs / spanMs) * 100,
     });
+
+    if (isLastTick) {
+      break;
+    }
   }
 
   return ticks;
diff --git 
a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.test.tsx 
b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.test.tsx
index 5a8438214f3..12fc4fb295e 100644
--- a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.test.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import "@testing-library/jest-dom/vitest";
-import { fireEvent, render, screen, waitFor, within } from 
"@testing-library/react";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
 import { delay, http, HttpResponse } from "msw";
 import { describe, expect, it } from "vitest";
 
@@ -62,16 +62,14 @@ describe("AssetsList filtering", () => {
 
     await waitFor(() => expect(requests.at(-1)).toEqual({ dagIds: 
["consumer_dag"], hasEvents }));
 
-    const dagIdPill = await screen.findByTestId("dag_id-pill");
+    await screen.findByTestId("dag_id-pill");
 
-    fireEvent.click(within(dagIdPill).getByRole("button", { name: /Remove .* 
filter/u }));
+    fireEvent.click(screen.getByTestId("dag_id-pill-remove"));
 
     await waitFor(() => expect(requests.at(-1)).toEqual({ dagIds: [], 
hasEvents }));
     expect(screen.getByTestId("has_events-pill")).toBeInTheDocument();
 
-    fireEvent.click(
-      within(screen.getByTestId("has_events-pill")).getByRole("button", { 
name: /Remove .* filter/u }),
-    );
+    fireEvent.click(screen.getByTestId("has_events-pill-remove"));
 
     await waitFor(() => expect(requests.at(-1)).toEqual({ dagIds: [], 
hasEvents: null }));
   });
@@ -155,9 +153,9 @@ describe("AssetsList filtering", () => {
 
     await waitFor(() => 
expect(requestedDagIds.at(-1)).toEqual(["consumer_dag"]));
 
-    const dagIdPill = await screen.findByTestId("dag_id-pill");
+    await screen.findByTestId("dag_id-pill");
 
-    fireEvent.click(within(dagIdPill).getByRole("button", { name: /Remove .* 
filter/u }));
+    fireEvent.click(screen.getByTestId("dag_id-pill-remove"));
 
     await waitFor(() => expect(requestedDagIds.at(-1)).toEqual([]));
     expect(screen.queryByTestId("dag_id-pill")).not.toBeInTheDocument();
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
index 99ac39fd633..c9d276e44e0 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Box, Flex, Grid, GridItem, HStack, Spinner, Text } from 
"@chakra-ui/react";
+import { Box, Flex, Grid, GridItem, HStack, Text } from "@chakra-ui/react";
 import { useTranslation } from "react-i18next";
 
 import type { DAGWithLatestDagRunsResponse } from "openapi/requests/types.gen";
@@ -31,7 +31,6 @@ import { TeamName } from "src/components/TeamName";
 import { useNearViewport } from "src/hooks/useNearViewport";
 import { useConfig } from "src/queries/useConfig";
 import type { RecentTasks } from "src/queries/useRecentTaskStateCounts";
-import { isStatePending, useAutoRefresh } from "src/utils";
 
 import { DagCardActions } from "./DagCardActions";
 import { DagRunStateCounts } from "./DagRunStateCounts";
@@ -65,8 +64,6 @@ export const DagCard = ({
     revealKey: dag.dag_id,
   });
 
-  const refetchInterval = useAutoRefresh({});
-
   return (
     <Box
       borderColor="border.emphasized"
@@ -124,9 +121,6 @@ export const DagCard = ({
                   startDate={latestRun.start_date}
                   state={latestRun.state}
                 />
-                {isStatePending(latestRun.state) && !dag.is_paused && 
Boolean(refetchInterval) ? (
-                  <Spinner />
-                ) : undefined}
               </RouterLink>
             ) : undefined}
           </Stat>
diff --git 
a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.test.tsx
 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.test.tsx
index 25aab51084c..e32eec62fa5 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.test.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.test.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import "@testing-library/jest-dom/vitest";
-import { fireEvent, render, screen, waitFor, within } from 
"@testing-library/react";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
 import { afterEach, describe, it, expect, vi } from "vitest";
 
 import { dagsListHandler, defaultDags, drainingDag } from 
"src/mocks/handlers/dags";
@@ -116,7 +116,7 @@ describe("Paused filter with hide_paused_dags_by_default 
enabled", () => {
     expect(screen.queryByText("paused_dag")).not.toBeInTheDocument();
 
     // There is no "All" option any more; removing the pill is how you ask for 
every Dag.
-    
fireEvent.click(within(screen.getByTestId("paused-pill")).getByRole("button"));
+    fireEvent.click(screen.getByTestId("paused-pill-remove"));
 
     await waitFor(() => 
expect(screen.getByText("paused_dag")).toBeInTheDocument());
     
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument();
@@ -161,7 +161,7 @@ describe("Paused filter with hide_paused_dags_by_default 
enabled", () => {
     await waitFor(() => 
expect(screen.getByText("tutorial_taskflow_api_failed")).toBeInTheDocument());
     
expect(screen.queryByText("tutorial_taskflow_api_success")).not.toBeInTheDocument();
 
-    fireEvent.click(within(await 
screen.findByTestId("timetable_type-pill")).getByRole("button"));
+    fireEvent.click(await screen.findByTestId("timetable_type-pill-remove"));
 
     await waitFor(() => 
expect(screen.getByText("tutorial_taskflow_api_success")).toBeInTheDocument());
     
expect(screen.getByText("tutorial_taskflow_api_failed")).toBeInTheDocument();
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.test.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.test.tsx
new file mode 100644
index 00000000000..7d2fe7ce1a4
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.test.tsx
@@ -0,0 +1,58 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import type { DAGWithLatestDagRunsResponse } from "openapi/requests/types.gen";
+
+import { Wrapper } from "src/utils/Wrapper";
+
+import { RecentRuns } from "./RecentRuns";
+
+type LatestRun = DAGWithLatestDagRunsResponse["latest_dag_runs"][number];
+
+const run = (duration: number | null, runId: string): LatestRun =>
+  ({
+    dag_id: "dag",
+    duration,
+    end_date: duration === null ? null : "2026-10-21T00:00:10Z",
+    run_id: runId,
+    start_date: "2026-10-21T00:00:00Z",
+    state: duration === null ? "queued" : "success",
+  }) as LatestRun;
+
+const barRow = () => screen.getAllByTestId("recent-run")[0]?.parentElement;
+
+describe("RecentRuns", () => {
+  it("holds the full bar height so pending runs stay on the baseline", () => {
+    render(<RecentRuns latestRuns={[run(null, "pending-1"), run(null, 
"pending-2")]} />, {
+      wrapper: Wrapper,
+    });
+
+    // Without a height of its own the row collapses to the stub height, and 
the card's reserved
+    // space pushes the whole group to the top instead of the baseline.
+    expect(barRow()).toHaveStyle({ minHeight: "65px" });
+  });
+
+  it("keeps the same floor once a run has a duration", () => {
+    render(<RecentRuns latestRuns={[run(10, "done-1")]} />, { wrapper: Wrapper 
});
+
+    expect(barRow()).toHaveStyle({ minHeight: "65px" });
+  });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx
index 1da938fb28c..c31601b92fc 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx
@@ -88,7 +88,15 @@ export const RecentRuns = ({
         placement: "bottom-start",
       }}
     >
-      <Flex alignItems="flex-end" flexDirection="row-reverse" gap={[0.5, 0.5, 
0.5, 1]} pb={1}>
+      {/* Hold the full bar height even when every run is short or still 
pending, so the bars stay
+          anchored to the bottom of the card's chart area instead of floating 
at the top of it. */}
+      <Flex
+        alignItems="flex-end"
+        flexDirection="row-reverse"
+        gap={[0.5, 0.5, 0.5, 1]}
+        minHeight={`${BAR_HEIGHT}px`}
+        pb={1}
+      >
         {latestRuns.map((run) => (
           <Tooltip.Trigger asChild key={run.run_id} value={run.run_id}>
             <Link data-testid="recent-run" 
to={`/dags/${run.dag_id}/runs/${run.run_id}/`}>
diff --git a/airflow-core/src/airflow/ui/src/utils/dagStatus.test.ts 
b/airflow-core/src/airflow/ui/src/utils/dagStatus.test.ts
new file mode 100644
index 00000000000..ccf2b475ac1
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/utils/dagStatus.test.ts
@@ -0,0 +1,84 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import type { TFunction } from "i18next";
+import { beforeAll, describe, expect, it } from "vitest";
+
+import type { DAGWarningResponse, ImportErrorResponse } from 
"openapi/requests/types.gen";
+
+import i18n from "src/i18n/config";
+
+import componentsLocale from "../../public/i18n/locales/en/components.json";
+import dagLocale from "../../public/i18n/locales/en/dag.json";
+import dashboardLocale from "../../public/i18n/locales/en/dashboard.json";
+import { summarizeDagStatus } from "./dagStatus";
+
+const warning = (message: string): DAGWarningResponse =>
+  ({ message, warning_type: "non-existent pool" }) as DAGWarningResponse;
+
+const importError = { filename: "broken.py" } as ImportErrorResponse;
+
+describe("summarizeDagStatus", () => {
+  let translate: TFunction;
+
+  beforeAll(() => {
+    i18n.addResourceBundle("en", "components", componentsLocale, true, true);
+    i18n.addResourceBundle("en", "dag", dagLocale, true, true);
+    i18n.addResourceBundle("en", "dashboard", dashboardLocale, true, true);
+    translate = i18n.t.bind(i18n);
+  });
+
+  it("fills the count into the combined heading", () => {
+    const { heading } = summarizeDagStatus(translate, {
+      error: new Error("boom"),
+      warnings: [warning("one"), warning("two")],
+    });
+
+    // The interpolation key is `warningText`; passing the wrong name left the 
placeholder raw.
+    expect(heading).toBe("1 Error and 2 Warnings");
+    expect(heading).not.toContain("{{");
+  });
+
+  it("lets a parse error name and colour the whole set", () => {
+    const summary = summarizeDagStatus(translate, { importError, warnings: 
[warning("one")] });
+
+    expect(summary.chipLabel).toBe("Dag Import Error");
+    expect(summary.severity).toBe("error");
+    expect(summary.heading).toBe("1 Error and 1 Warning");
+  });
+
+  it("counts a deactivation among the warnings but lets it speak for itself on 
the chip", () => {
+    const summary = summarizeDagStatus(translate, {
+      deactivatedMessage: "Deactivated",
+      warnings: [warning("one")],
+    });
+
+    expect(summary.chipLabel).toBe("Deactivated");
+    expect(summary.severity).toBe("warning");
+    expect(summary.warningCount).toBe(2);
+    expect(summary.heading).toBe("2 Warnings");
+  });
+
+  it("names the warnings alone when nothing else is wrong", () => {
+    const summary = summarizeDagStatus(translate, { warnings: [warning("one")] 
});
+
+    expect(summary.chipLabel).toBe("1 Warning");
+    expect(summary.heading).toBe("1 Warning");
+    expect(summary.severity).toBe("warning");
+  });
+});
diff --git a/airflow-core/src/airflow/ui/src/utils/dagStatus.ts 
b/airflow-core/src/airflow/ui/src/utils/dagStatus.ts
new file mode 100644
index 00000000000..311f9a97aa8
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/utils/dagStatus.ts
@@ -0,0 +1,75 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import type { TFunction } from "i18next";
+
+import type { DAGWarningResponse, ImportErrorResponse } from 
"openapi/requests/types.gen";
+
+type DagStatus = {
+  /** Stated as one more warning, for a Dag that is no longer in its file. */
+  deactivatedMessage?: string;
+  /** The page's own load failure, which the Dag's problems are listed beside. 
*/
+  error?: unknown;
+  importError?: ImportErrorResponse;
+  warnings: Array<DAGWarningResponse>;
+};
+
+export type DagStatusSummary = {
+  /** Names the worst problem alone: the chip has room for one thing. */
+  chipLabel: string;
+  /** Names everything the modal lists. */
+  heading: string;
+  severity: "error" | "warning";
+  warningCount: number;
+};
+
+/**
+ * One reading of everything wrong with a Dag. The chip and the modal that 
opens from it both
+ * describe the same set, so they derive it here rather than each walking the 
severity order
+ * themselves and disagreeing the moment one of them changes.
+ */
+export const summarizeDagStatus = (
+  translate: TFunction,
+  { deactivatedMessage, error, importError, warnings }: DagStatus,
+): DagStatusSummary => {
+  const hasError = importError !== undefined || Boolean(error);
+  const warningCount = warnings.length + Number(deactivatedMessage !== 
undefined);
+  const errorLabel =
+    importError === undefined
+      ? translate("components:dagWarnings.error_one")
+      : translate("dashboard:importErrors.dagImportError", { count: 1 });
+  const warningLabel = translate("components:dagWarnings.warning", { count: 
warningCount });
+  let heading = "";
+
+  if (hasError) {
+    heading =
+      warningCount > 0
+        ? translate("components:dagWarnings.errorAndWarning", { warningText: 
warningLabel })
+        : errorLabel;
+  } else if (warningCount > 0) {
+    heading = warningLabel;
+  }
+
+  return {
+    // A deactivation speaks for itself on the chip; in the list it is one 
warning among others.
+    chipLabel: hasError ? errorLabel : (deactivatedMessage ?? warningLabel),
+    heading,
+    severity: hasError ? "error" : "warning",
+    warningCount,
+  };
+};
diff --git a/airflow-core/src/airflow/ui/src/utils/errorHandling.ts 
b/airflow-core/src/airflow/ui/src/utils/errorHandling.ts
index df2e9078bd5..e0888221fc4 100644
--- a/airflow-core/src/airflow/ui/src/utils/errorHandling.ts
+++ b/airflow-core/src/airflow/ui/src/utils/errorHandling.ts
@@ -17,6 +17,8 @@
  * under the License.
  */
 import type { TFunction } from "i18next";
+import type { ApiError } from "openapi-gen/requests/core/ApiError";
+import type { HTTPExceptionResponse, HTTPValidationError } from 
"openapi-gen/requests/types.gen";
 
 import { toaster } from "src/system-components";
 
@@ -44,6 +46,27 @@ export const getErrorStatus = (error: unknown): number | 
undefined => {
   return errorObj.status ?? errorObj.response?.status;
 };
 
+type ApiErrorWithBody = {
+  body?: HTTPExceptionResponse | HTTPValidationError;
+} & Partial<ApiError>;
+
+/** The error's own detail, however the API phrased it: a string, validation 
entries, or a mapping. */
+export const getApiErrorDetail = (error: unknown): Array<string> | string | 
undefined => {
+  const details = (error as ApiErrorWithBody | undefined)?.body?.detail;
+
+  if (details === undefined) {
+    return undefined;
+  }
+
+  if (typeof details === "string") {
+    return details;
+  }
+
+  return Array.isArray(details)
+    ? details.map((detail) => `${detail.loc.join(".")} ${detail.msg}`)
+    : Object.keys(details).map((key) => `${key}: ${(details as Record<string, 
string>)[key]}`);
+};
+
 /**
  * Safely extracts the error message from an error object
  */
diff --git a/airflow-core/src/airflow/ui/src/utils/index.ts 
b/airflow-core/src/airflow/ui/src/utils/index.ts
index e16659d78a8..756d5197787 100644
--- a/airflow-core/src/airflow/ui/src/utils/index.ts
+++ b/airflow-core/src/airflow/ui/src/utils/index.ts
@@ -18,7 +18,8 @@
  */
 
 export { capitalize } from "./capitalize";
-export { createErrorToaster, getErrorStatus } from "./errorHandling";
+export { summarizeDagStatus, type DagStatusSummary } from "./dagStatus";
+export { createErrorToaster, getApiErrorDetail, getErrorStatus } from 
"./errorHandling";
 export { formatNumber } from "./formatNumber";
 export { getMetaKey } from "./getMetaKey";
 export { hasDagRunConfig } from "./hasDagRunConfig";
diff --git a/airflow-core/src/airflow/ui/testsSetup.ts 
b/airflow-core/src/airflow/ui/testsSetup.ts
index c0373e2bf12..51a2ef977c5 100644
--- a/airflow-core/src/airflow/ui/testsSetup.ts
+++ b/airflow-core/src/airflow/ui/testsSetup.ts
@@ -90,15 +90,44 @@ server.events.on("request:unhandled", ({ request }) => {
   unhandledRequests.push(`${request.method} ${request.url}`);
 });
 
+// React reports invalid markup and duplicate keys by logging, not by failing 
anything, so these
+// reach CI as console noise nobody reads and ship as browser console errors. 
Fail the test that
+// produced one instead. Each pattern here is a bug in the markup, never a 
property of the
+// environment, so none of them is noise to allow-list.
+const REACT_MARKUP_ERRORS = [
+  /validateDOMNesting/u,
+  /cannot contain a nested/u,
+  /cannot appear as a descendant of/u,
+  /cannot be a descendant of/u,
+  /Encountered two children with the same key/u,
+];
+const markupErrors: Array<string> = [];
+const logError = console.error.bind(console);
+
+console.error = (...args: Array<unknown>) => {
+  const message = args.map((arg) => String(arg)).join(" ");
+
+  if (REACT_MARKUP_ERRORS.some((pattern) => pattern.test(message))) {
+    markupErrors.push(message.split("\n")[0] ?? message);
+  }
+
+  logError(...args);
+};
+
 beforeEach(() => server.resetHandlers());
 afterEach(() => {
   cleanup();
 
   const requests = unhandledRequests.splice(0);
+  const markup = markupErrors.splice(0);
 
   if (requests.length > 0) {
     throw new Error(
       `Requests without an msw handler (add one to src/mocks/handlers or with 
server.use()):\n${requests.join("\n")}`,
     );
   }
+
+  if (markup.length > 0) {
+    throw new Error(`React reported invalid markup:\n${markup.join("\n")}`);
+  }
 });

Reply via email to